diff --git a/docs/src/man/man1/halrmt.1.adoc b/docs/src/man/man1/halrmt.1.adoc index aff5d60f7ee..121545df63f 100644 --- a/docs/src/man/man1/halrmt.1.adoc +++ b/docs/src/man/man1/halrmt.1.adoc @@ -42,6 +42,10 @@ halrmt - remote-control interface for LinuxCNC/HAL -q,--quiet:: Don't print informational messages to the console. +-4,--ipv4:: + Listen only for connections using IPv4. + Default is to accept connection on both IPv6 and IPv4. + == DESCRIPTION The application *halrmt* supports six primary commands to manipulate the HAL of a remote LinuxCNC instance. diff --git a/src/hal/utils/halrmt.cc b/src/hal/utils/halrmt.cc index bed6f70103e..2fd33c3e737 100644 --- a/src/hal/utils/halrmt.cc +++ b/src/hal/utils/halrmt.cc @@ -68,6 +68,7 @@ using namespace linuxcnc; static std::string helloPwd = "EMC"; // Connect password static std::string enablePwd = "EMCTOO"; // Enable password static std::string serverName = "EMCNETSVR"; // Server name written in hello response +static bool isipv4 = false; // We try AF_INET6, but can fallback to AF_INET static int port = 5006; static std::string inifilename; static volatile int quitloop = 0; // Signal to main loop to exit @@ -2881,24 +2882,66 @@ static int parseCommand(connectionRecType &ctx, std::string &line) * MAIN PROGRAM * ************************************************************************/ +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 = {}; - - sockfd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (sockfd < 0) { - xperror("socket()"); - return -1; + 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)); + + 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()"); @@ -2985,8 +3028,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 = accept4(svrfd, reinterpret_cast(&csa), &csal, SOCK_CLOEXEC | SOCK_NONBLOCK); if (cfd < 0) { switch (errno) { @@ -3028,9 +3074,15 @@ static int sockMain(int svrfd) cr.echo = true; cr.inifilename = inifilename; // Inherit global default 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); } @@ -3197,6 +3249,7 @@ static struct option longopts[] = { {"sessions", 1, NULL, 's'}, {"ini", 1, NULL, 'i'}, {"quiet", 0, NULL, 'q'}, + {"ipv4", 0, NULL, '4'}, {} }; @@ -3210,8 +3263,11 @@ int main(int argc, char **argv) rtapi_set_msg_level(RTAPI_MSG_ERR); /* set default for other options */ // process halrmt command line args - while(-1 != (optc = getopt_long(argc, argv, "e:hi:n:p:qs:w:", longopts, NULL))) { + while(-1 != (optc = getopt_long(argc, argv, "4e:hi:n:p:qs:w:", longopts, NULL))) { switch(optc) { + case '4': + isipv4 = true; + break; case 'h': usage(); return EXIT_FAILURE;