Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 52 additions & 13 deletions src/emc/usr_intf/emcrsh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -369,19 +370,41 @@ static int initSocket()
int optval = 1;
int err;
int sockfd;
struct sockaddr_in6 address = {};
union {
struct sockaddr_in6 addr6;
struct sockaddr_in addr4;
} addr;

// Using AF_INET6 will also allow IPv4 to connect (v4-mapped-on-v6)
// We will try IPv6 first and hope it is available
addr.addr6 = {};
addr.addr6.sin6_family = AF_INET6;
addr.addr6.sin6_addr = IN6ADDR_ANY_INIT;
addr.addr6.sin6_port = htons(port);
socklen_t slen = sizeof(addr.addr6);

sockfd = socket(AF_INET6, SOCK_STREAM, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is EAFNOSUPPORT the only way the IPv6 listener can be unavailable in practice?
A restricted netns or an LSM/seccomp policy returns EACCES or EPERM here, and
there are setups where socket() succeeds but bind() to :: is what fails. Since
IPv4 is a strict fallback, any downside to taking it on any IPv6 failure?

if (sockfd < 0) {
xperror("socket()");
return -1;
if(EAFNOSUPPORT == errno) {
// Someone disabled IPv6 on the machine. Try IPv4 instead.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
xperror("IPv4 socket()");
return -1;
}
isipv4 = true;
addr.addr4 = {};
addr.addr4.sin_family = AF_INET;
addr.addr4.sin_addr.s_addr = INADDR_ANY;
addr.addr4.sin_port = htons(port);
slen = sizeof(addr.addr4);
} else {
xperror("IPv6 socket()");
return -1;
}
}
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dual-stack here relies on net.ipv6.bindv6only defaulting to 0. With that sysctl
set to 1, socket(AF_INET6, ...) still succeeds, so the fallback never fires and
IPv4 clients get ECONNREFUSED. Measured:

v6only=0  IPv4 client: connected OK
v6only=1  IPv4 client: connect FAILED (Connection refused)

Worth pinning the assumption rather than inheriting it, on the IPv6 branch only?

int zero = 0;
setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero));

// 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<struct sockaddr *>(&address), sizeof(address));
err = bind(sockfd, reinterpret_cast<struct sockaddr *>(&addr), slen);
if (err) {
close(sockfd);
xperror("bind()");
Expand Down Expand Up @@ -3424,6 +3447,13 @@ static void closeClient(connectionRecType &ctx)
{
if (ctx.sock < 0)
return;
if(ctx.cmdtimeout > 0.0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latch clears, but the NML command is still in flight, so its completion or
error lands on whichever client polls next. Intended trade-off, or would
recording emcCommandSerialNumber at close time and discarding status up to it
be cheap enough to close the desync?

// 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;
Expand Down Expand Up @@ -3493,8 +3523,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 = isipv4 ? sizeof(csa.csa4) : sizeof(csa.csa6);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accept() writes the real length back, so does the family need to be known here?
sizeof(csa) is correct for both.

Broader: would sockaddr_storage plus getnameinfo(..., NI_NUMERICHOST |
NI_NUMERICSERV) let the union, isipv4, and the duplicated logging branch below
all disappear?

cfd = accept(svrfd, reinterpret_cast<struct sockaddr *>(&csa), &csal);
if (cfd < 0) {
switch (errno) {
Expand Down Expand Up @@ -3537,9 +3570,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);
}
Expand Down