From 181a3e9697c4f46fb0c4a1a3a2cff35fbd95808f Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 27 Nov 2015 12:59:21 +0000 Subject: [PATCH] Proper IPv6 and dual stack networking This means that rigctl & rigctld now work with default arguments on a modern Windows machine with dual stack and localhost being [::1] as the first interface returned by getaddrinfo(). Try all the interfaces return by DNS lookups to establish a connection or listening port. Handle Windows network errors correctly so that meaningful messages are printed. The rigctl program now accepts IPv6 numeric addresses in the portname field like [}: for example the IPv6 loopback on port 4531 would be [::1]:4531. --- src/network.c | 126 ++++++++++++++++++++++++++++++++++---------- tests/rigctld.c | 135 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 202 insertions(+), 59 deletions(-) diff --git a/src/network.c b/src/network.c index 214ecd48e..0b6719bcd 100644 --- a/src/network.c +++ b/src/network.c @@ -73,6 +73,36 @@ static int wsstarted; #endif +static void handle_error (enum rig_debug_level_e lvl, const char *msg) +{ + int e; +#ifdef __MINGW32__ + LPVOID lpMsgBuf; + + lpMsgBuf = (LPVOID)"Unknown error"; + e = WSAGetLastError(); + if (FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, e, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + // Default language + (LPTSTR)&lpMsgBuf, 0, NULL)) + { + rig_debug (lvl, "%s: Network error %d: %s\n", msg, e, lpMsgBuf); + LocalFree(lpMsgBuf); + } + else + { + rig_debug (lvl, "%s: Network error %d\n", msg, e); + } +#else + e = errno; + rig_debug (lvl, "%s: Network error %d: %s\n", msg, e, strerror (e)); +#endif +} + /** * \brief Open network port using rig.state data * @@ -87,9 +117,9 @@ int network_open(hamlib_port_t *rp, int default_port) { int fd; /* File descriptor for the port */ int status; - struct addrinfo hints, *res; - char *portstr; - char hostname[FILPATHLEN] = "127.0.0.1"; + struct addrinfo hints, *res, *saved_res; + char *hoststr, *portstr, *bracketstr1, *bracketstr2; + char hostname[FILPATHLEN]; char defaultportstr[8]; #ifdef __MINGW32__ @@ -110,48 +140,90 @@ int network_open(hamlib_port_t *rp, int default_port) else hints.ai_socktype = SOCK_STREAM; - if (rp->pathname[0] == ':') { - portstr = rp->pathname+1; - } else { - strncpy(hostname, rp->pathname, FILPATHLEN-1); - - /* search last ':', because IPv6 may have some */ - portstr = strrchr(hostname, ':'); - if (portstr) { - *portstr++ = '\0'; - } else { - sprintf(defaultportstr, "%d", default_port); - portstr = defaultportstr; + hoststr = NULL; /* default of all local interfaces */ + if (rp->pathname && rp->pathname[0] == ':') + { + portstr = rp->pathname + 1; + } + else + { + if (strlen (rp->pathname)) + { + strncpy(hostname, rp->pathname, FILPATHLEN-1); + hoststr = hostname; + /* look for IPv6 numeric form [] */ + bracketstr1 = strchr(hoststr, '['); + bracketstr2 = strrchr(hoststr, ']'); + if (bracketstr1 && bracketstr2 && bracketstr2 > bracketstr1) + { + hoststr = bracketstr1 + 1; + *bracketstr2 = '\0'; + portstr = bracketstr2 + 1; /* possible port after ]: */ + } + else + { + bracketstr2 = NULL; + portstr = hoststr; /* possible port after : */ + } + /* search last ':' */ + portstr = strrchr(portstr, ':'); + if (portstr) + { + *portstr++ = '\0'; + } + else + { + sprintf(defaultportstr, "%d", default_port); + portstr = defaultportstr; + } + } } - } - status=getaddrinfo(hostname, portstr, &hints, &res); + status=getaddrinfo(hoststr, portstr, &hints, &res); if (status != 0) { rig_debug(RIG_DEBUG_ERR, "Cannot get host \"%s\": %s\n", rp->pathname, gai_strerror(errno)); return -RIG_ECONF; } + saved_res = res; /* we don't want a signal when connection get broken */ #ifdef SIGPIPE signal(SIGPIPE, SIG_IGN); #endif - fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (fd < 0) - return -RIG_EIO; + do + { + fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (fd < 0) + { + handle_error (RIG_DEBUG_ERR, "socket"); + freeaddrinfo (saved_res); + return -RIG_EIO; + } - status = connect(fd, res->ai_addr, res->ai_addrlen); - freeaddrinfo(res); - if (status < 0) { - rig_debug(RIG_DEBUG_ERR, "Cannot open NET device \"%s\": %s\n", - rp->pathname, strerror(errno)); - close(fd); + if ((status = connect(fd, res->ai_addr, res->ai_addrlen)) == 0) + { + break; + } + handle_error (RIG_DEBUG_WARN, "connect (trying next interface)"); + +#ifdef __MINGW32__ + closesocket (fd); +#else + close (fd); +#endif + } while ((res = res->ai_next) != NULL); + + freeaddrinfo (saved_res); + + if (NULL == res) { + rig_debug (RIG_DEBUG_ERR, "Failed to connect to %s\n" + , rp->pathname ? rp->pathname : "localhost:4532"); return -RIG_EIO; } rp->fd = fd; - return RIG_OK; } diff --git a/tests/rigctld.c b/tests/rigctld.c index fd9abb6cc..de91c62bc 100644 --- a/tests/rigctld.c +++ b/tests/rigctld.c @@ -102,7 +102,7 @@ static struct option long_options[] = struct handle_data { RIG *rig; int sock; - struct sockaddr_in cli_addr; + struct sockaddr_storage cli_addr; socklen_t clilen; }; @@ -120,6 +120,36 @@ const char *src_addr = NULL; /* INADDR_ANY */ #define MAXCONFLEN 128 +static void handle_error (enum rig_debug_level_e lvl, const char *msg) +{ + int e; +#ifdef __MINGW32__ + LPVOID lpMsgBuf; + + lpMsgBuf = (LPVOID)"Unknown error"; + e = WSAGetLastError(); + if (FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, e, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + // Default language + (LPTSTR)&lpMsgBuf, 0, NULL)) + { + rig_debug (lvl, "%s: Network error %d: %s\n", msg, e, lpMsgBuf); + LocalFree(lpMsgBuf); + } + else + { + rig_debug (lvl, "%s: Network error %d\n", msg, e); + } +#else + e = errno; + rig_debug (lvl, "%s: Network error %d: %s\n", msg, e, strerror (e)); +#endif +} + int main (int argc, char *argv[]) { RIG *my_rig; /* handle to rig (instance) */ @@ -137,9 +167,12 @@ int main (int argc, char *argv[]) char *civaddr = NULL; /* NULL means no need to set conf */ char conf_parms[MAXCONFLEN] = ""; - struct addrinfo hints, *result; + int sockopt; + struct addrinfo hints, *result, *saved_result; int sock_listen; int reuseaddr = 1; + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; while(1) { int c; @@ -283,13 +316,13 @@ int main (int argc, char *argv[]) } } - rig_set_debug(verbose); + rig_set_debug(verbose); rig_debug(RIG_DEBUG_VERBOSE, "rigctld, %s\n", hamlib_version); rig_debug(RIG_DEBUG_VERBOSE, "Report bugs to " "\n\n"); - my_rig = rig_init(my_model); + my_rig = rig_init(my_model); if (!my_rig) { fprintf(stderr, "Unknown rig num %d, or initialization error.\n", @@ -370,7 +403,7 @@ int main (int argc, char *argv[]) exit(1); } - int sockopt = SO_SYNCHRONOUS_NONALERT; + sockopt = SO_SYNCHRONOUS_NONALERT; setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&sockopt, sizeof(sockopt)); #endif @@ -388,28 +421,56 @@ int main (int argc, char *argv[]) fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(retcode)); exit(2); } + saved_result = result; - sock_listen = socket(result->ai_family, result->ai_socktype, - result->ai_protocol); - if (sock_listen < 0) { - perror("ERROR opening socket"); - exit(2); - } + do + { + sock_listen = socket(result->ai_family, result->ai_socktype, + result->ai_protocol); + if (sock_listen < 0) { + handle_error (RIG_DEBUG_ERR, "socket"); + freeaddrinfo(saved_result); /* No longer needed */ + exit(2); + } - if (setsockopt(sock_listen, SOL_SOCKET, SO_REUSEADDR, - (char *)&reuseaddr, sizeof(reuseaddr)) < 0) { - rig_debug(RIG_DEBUG_ERR, "setsockopt: %s\n", strerror(errno)); - exit (1); - } - if (bind(sock_listen, result->ai_addr, result->ai_addrlen) < 0) { - rig_debug(RIG_DEBUG_ERR, "binding: %s\n", strerror(errno)); - exit (1); - } + if (setsockopt(sock_listen, SOL_SOCKET, SO_REUSEADDR, + (char *)&reuseaddr, sizeof(reuseaddr)) < 0) { + handle_error (RIG_DEBUG_ERR, "setsockopt"); + freeaddrinfo(saved_result); /* No longer needed */ + exit (1); + } - freeaddrinfo(result); /* No longer needed */ +#ifdef __MINGW32__ + /* allow IPv4 mapped to IPv6 clients, MS default this to 1! */ + sockopt = 0; + if (setsockopt(sock_listen, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&sockopt, sizeof(sockopt)) < 0) { + handle_error (RIG_DEBUG_ERR, "setsockopt"); + freeaddrinfo(saved_result); /* No longer needed */ + exit (1); + } +#endif + + if (0 == bind(sock_listen, result->ai_addr, result->ai_addrlen)) { + break; + } + handle_error (RIG_DEBUG_WARN, "binding failed (trying next interface)"); +#ifdef __MINGW32__ + closesocket (sock_listen); +#else + close (sock_listen); +#endif + } while ((result = result->ai_next) != NULL); + + freeaddrinfo(saved_result); /* No longer needed */ + if (NULL == result) + { + rig_debug(RIG_DEBUG_ERR, "bind error - no available interface\n"); + exit (1); + } if (listen(sock_listen, 4) < 0) { - rig_debug(RIG_DEBUG_ERR, "listening: %s\n", strerror(errno)); + handle_error (RIG_DEBUG_ERR, "listening"); exit (1); } @@ -430,17 +491,20 @@ int main (int argc, char *argv[]) } arg->rig = my_rig; - arg->clilen = sizeof(arg->cli_addr); - arg->sock = accept(sock_listen, (struct sockaddr *)&arg->cli_addr, - &arg->clilen); + arg->clilen = sizeof (arg->cli_addr); + arg->sock = accept(sock_listen, (struct sockaddr *)&arg->cli_addr, &arg->clilen); if (arg->sock < 0) { - rig_debug(RIG_DEBUG_ERR, "accept: %s\n", strerror(errno)); + handle_error (RIG_DEBUG_ERR, "accept"); break; } - rig_debug(RIG_DEBUG_VERBOSE, "Connection opened from %s:%d\n", - inet_ntoa(arg->cli_addr.sin_addr), - ntohs(arg->cli_addr.sin_port)); + if ((retcode = getnameinfo ((struct sockaddr const *)&arg->cli_addr, arg->clilen, host, sizeof (host) + , serv, sizeof (serv), NI_NOFQDN)) < 0) + { + rig_debug (RIG_DEBUG_WARN, "Peer lookup error: %s", gai_strerror (retcode)); + } + rig_debug(RIG_DEBUG_VERBOSE, "Connection opened from %s:%s\n", + host, serv); #ifdef HAVE_PTHREAD pthread_attr_init(&attr); @@ -477,6 +541,8 @@ void * handle_socket(void *arg) FILE *fsockin; FILE *fsockout; int retcode; + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; #ifdef __MINGW32__ int sock_osfhandle = _open_osfhandle(handle_data_arg->sock, _O_RDONLY); @@ -513,9 +579,14 @@ void * handle_socket(void *arg) } while (retcode == 0 || retcode == 2); - rig_debug(RIG_DEBUG_VERBOSE, "Connection closed from %s:%d\n", - inet_ntoa(handle_data_arg->cli_addr.sin_addr), - ntohs(handle_data_arg->cli_addr.sin_port)); + if ((retcode = getnameinfo ((struct sockaddr const *)&handle_data_arg->cli_addr + , handle_data_arg->clilen, host, sizeof (host) + , serv, sizeof (serv), NI_NOFQDN)) < 0) + { + rig_debug (RIG_DEBUG_WARN, "Peer lookup error: %s", gai_strerror (retcode)); + } + rig_debug(RIG_DEBUG_VERBOSE, "Connection closed from %s:%s\n", + host, serv); fclose(fsockin); fclose(fsockout);