diff --git a/docs/src/man/man1/linuxcncrsh.1.adoc b/docs/src/man/man1/linuxcncrsh.1.adoc index 4a3e5ec4f0c..1278e8d9923 100644 --- a/docs/src/man/man1/linuxcncrsh.1.adoc +++ b/docs/src/man/man1/linuxcncrsh.1.adoc @@ -62,6 +62,10 @@ this text-based interface than to a graphical one. who can read packets on the network between the server and the client. Defaults to EMC if omitted. +*-4*,*--ipv4*:: + Listen only for connections using IPv4. + Default is to accept connection on both IPv6 and IPv4. + In addition to the options listed above, *linuxcncrsh* accepts an optional special LINUXCNC_OPTION at the end: diff --git a/src/emc/usr_intf/emcrsh.cc b/src/emc/usr_intf/emcrsh.cc index ca26a503bbf..9bd1b0183d0 100644 --- a/src/emc/usr_intf/emcrsh.cc +++ b/src/emc/usr_intf/emcrsh.cc @@ -185,6 +185,7 @@ typedef struct { int commProt; // Deprecated } connectionRecType; +static bool isipv4 = false; // We try AF_INET6, but can fallback to AF_INET static int port = 5007; static std::string helloPwd = "EMC"; static std::string enablePWD = "EMCTOO"; @@ -262,6 +263,7 @@ static struct option longopts[] = { {"enablepw", 1, NULL, 'e'}, {"path", 1, NULL, 'd'}, {"quiet", 0, NULL, 'q'}, + {"ipv4", 0, NULL, '4'}, {} }; @@ -364,24 +366,66 @@ static void set_nonblock(int fd) } } +static int getSocket4(struct sockaddr_in &addr, socklen_t &slen) +{ + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + xperror("IPv4 socket()"); + return -1; + } + addr = {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + slen = sizeof(addr); + return sockfd; +} + static int initSocket() { int optval = 1; int err; int sockfd; - struct sockaddr_in6 address = {}; + union { + struct sockaddr_in6 addr6; + struct sockaddr_in addr4; + } addr; + socklen_t slen; + + if (isipv4) { + // Forced IPv4, set from command-line + if ((sockfd = getSocket4(addr.addr4, slen)) < 0) + return -1; + } else { + // Using AF_INET6 will also allow IPv4 to connect (v4-mapped-on-v6) + // We will try IPv6 first and hope it is available + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + if (sockfd < 0) { + if (EAFNOSUPPORT == errno) { + // Someone disabled IPv6 on the machine. Try IPv4 instead. + isipv4 = true; + if ((sockfd = getSocket4(addr.addr4, slen)) < 0) + return -1; + } else { + xperror("IPv6 socket()"); + return -1; + } + } else { + // Ensure that v4-mapped-on-v6 is enabled. In theory, it could fail if + // there is no IPv4 stack available, but we don't care in that case. + int zero = 0; + setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)); - sockfd = socket(AF_INET6, SOCK_STREAM, 0); - if (sockfd < 0) { - xperror("socket()"); - return -1; + addr.addr6 = {}; + addr.addr6.sin6_family = AF_INET6; + addr.addr6.sin6_addr = IN6ADDR_ANY_INIT; + addr.addr6.sin6_port = htons(port); + slen = sizeof(addr.addr6); + } } + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); - // Using AF_INET6 will also allow IPv4 to connect (v4-mapped-on-v6) - address.sin6_family = AF_INET6; - address.sin6_addr = IN6ADDR_ANY_INIT; - address.sin6_port = htons(port); - err = bind(sockfd, reinterpret_cast(&address), sizeof(address)); + err = bind(sockfd, reinterpret_cast(&addr), slen); if (err) { close(sockfd); xperror("bind()"); @@ -3424,6 +3468,13 @@ static void closeClient(connectionRecType &ctx) { if (ctx.sock < 0) return; + if(ctx.cmdtimeout > 0.0) { + // Happens when a client disconnects with an active SET command. This + // is bad because it desynchronizes the command channel from the error + // channel. We don't know what happened anymore. + info("Warning: Client '%s' disconnected while waiting for SET %s", ctx.hostname.c_str(), activeSetter.c_str()); + setterClear(ctx); + } info("Connection terminated '%s' (%d)", ctx.hostname.c_str(), ctx.sock); close(ctx.sock); ctx.sock = -1; @@ -3493,8 +3544,11 @@ static int sockMain(int svrfd) } else if (pfds[0].revents & POLLIN) { // POLLIN on a listen socket means new connection available int cfd; - struct sockaddr_in6 csa; - socklen_t csal = sizeof(csa); + union { + struct sockaddr_in6 csa6; + struct sockaddr_in csa4; + } csa; + socklen_t csal = sizeof(csa); // Will be the largest of the two cfd = accept(svrfd, reinterpret_cast(&csa), &csal); if (cfd < 0) { switch (errno) { @@ -3537,9 +3591,15 @@ static int sockMain(int svrfd) cr.waitmode = EMC_WAIT_DONE; //cr.timestamp= true; clients.push_back(cr); - char addr[INET6_ADDRSTRLEN] = {}; - inet_ntop(AF_INET6, &csa.sin6_addr, addr, sizeof(addr)); - info("New connection from %s:%d", addr, ntohs(csa.sin6_port)); + if(isipv4) { + char addr[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, &csa.csa4.sin_addr, addr, sizeof(addr)); + info("New connection from %s:%d", addr, ntohs(csa.csa4.sin_port)); + } else { + char addr[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, &csa.csa6.sin6_addr, addr, sizeof(addr)); + info("New connection from %s:%d", addr, ntohs(csa.csa6.sin6_port)); + } } else { close(cfd); } @@ -3695,6 +3755,7 @@ static void usage(char *pname) " -s,--sessions Restrict number of session to 'num' (default=%u) (0 for no limit)\n" " -d,--path Set (colon-separated) search path to 'path' (default=%s)\n" " -q,--quiet Don't print informational messages\n" + " -4,--ipv4 Only use IPv4\n" "LinuxCNC_Options:\n" " -ini (default=%s)\n", pname, @@ -3743,8 +3804,11 @@ int main(int argc, char *argv[]) searchPath.push_back(getDefaultPath()); // process local command line args - while ((opt = getopt_long(argc, argv, "he:n:p:s:w:d:q", longopts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "4he:n:p:s:w:d:q", longopts, NULL)) != -1) { switch (opt) { + case '4': + isipv4 = true; + break; case 'h': usage(argv[0]); return EXIT_FAILURE;