From 8f4320e76f54703cd68a5812a275850c71775ebf Mon Sep 17 00:00:00 2001 From: =?utf8?q?Remco=20R=C4=B3nders?= Date: Sat, 7 Mar 2026 12:17:36 -0500 Subject: [PATCH] IPv6 Phase 2: Dual-stack socket layer Update inetport() to create AF_INET6 dual-stack listeners (IPV6_V6ONLY=0) for wildcard and IPv6 bind addresses, accepting both v4 and v6 clients on a single socket. IPv4-specific bind addresses still use AF_INET. Switch check_init() and add_connection() from sockaddr_in to sockaddr_storage so getpeername() works for both address families. Rewrite connect_inet() to open AF_INET or AF_INET6 sockets based on the target address family from aconf->ipnum. Fix gethost_byaddr() call to pass &ip.addr.v4 (not &ip) since the resolver is still IPv4-only (Phase 3). Remove unused static mysk. Co-Authored-By: Claude Opus 4.6 --- src/s_bsd.c | 212 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 78 deletions(-) diff --git a/src/s_bsd.c b/src/s_bsd.c index ad2c486..b001a8a 100644 --- a/src/s_bsd.c +++ b/src/s_bsd.c @@ -44,7 +44,6 @@ aClient *local[MAXCONNECTIONS]; int highest_fd = 0, readcalls = 0, udpfd = -1, resfd = -1; -static struct sockaddr_in mysk; static void polludp (); static struct sockaddr *connect_inet (aConfItem *, aClient *, int *); @@ -155,32 +154,56 @@ void report_error (char *text, aClient *cptr) */ int inetport (aClient *cptr, char *name, int port) { - static struct sockaddr_in server; - int ad[4], len = sizeof (server); - char ipname[20]; + struct sockaddr_storage ss; + socklen_t len; + int family; + int opt; if (BadPtr (name)) name = "*"; - ad[0] = ad[1] = ad[2] = ad[3] = 0; - - /* - * do it this way because building ip# from separate values for each - * byte requires endian knowledge or some nasty messing. Also means - * easy conversion of "*" 0.0.0.0 or 134.* to 134.0.0.0 :-) - */ - (void) sscanf (name, "%d.%d.%d.%d", &ad[0], &ad[1], &ad[2], &ad[3]); - (void) sprintf (ipname, "%d.%d.%d.%d", ad[0], ad[1], ad[2], ad[3]); if (cptr != &me) { (void) sprintf (cptr->sockhost, "%-.42s.%u", name, (unsigned int) port); (void) strcpy (cptr->name, me.name); } + + /* + * Determine address family. If the bind address contains ':', + * it is IPv6. "*" or plain IPv4 uses AF_INET6 with dual-stack + * (IPV6_V6ONLY=0) so we can accept both v4 and v6 on one socket. + */ + memset (&ss, 0, sizeof (ss)); + if (strchr (name, ':')) { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &ss; + family = AF_INET6; + s6->sin6_family = AF_INET6; + s6->sin6_port = htons (port); + inet_pton (AF_INET6, name, &s6->sin6_addr); + len = sizeof (struct sockaddr_in6); + } + else if (!strcmp (name, "*") || !strcmp (name, "0.0.0.0")) { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &ss; + family = AF_INET6; + s6->sin6_family = AF_INET6; + s6->sin6_port = htons (port); + s6->sin6_addr = in6addr_any; + len = sizeof (struct sockaddr_in6); + } + else { + struct sockaddr_in *s4 = (struct sockaddr_in *) &ss; + family = AF_INET; + s4->sin_family = AF_INET; + s4->sin_port = htons (port); + inet_pton (AF_INET, name, &s4->sin_addr); + len = sizeof (struct sockaddr_in); + } + /* * At first, open a new socket */ if (cptr->fd == -1) - cptr->fd = socket (AF_INET, SOCK_STREAM, 0); + cptr->fd = socket (family, SOCK_STREAM, 0); if (cptr->fd < 0) { report_error ("opening stream socket %s:%s", cptr); @@ -192,44 +215,48 @@ int inetport (aClient *cptr, char *name, int port) return -1; } set_sock_opts (cptr->fd, cptr); + + /* For dual-stack AF_INET6 listeners, disable IPV6_V6ONLY */ + if (family == AF_INET6) { + opt = 0; + (void) setsockopt (cptr->fd, IPPROTO_IPV6, IPV6_V6ONLY, + (char *) &opt, sizeof (opt)); + } + /* * Bind a port to listen for new connections if port is non-null, * else assume it is already open and try get something from it. */ if (port) { - server.sin_family = AF_INET; - /* per-port bindings, fixes /stats l */ - inet_pton (AF_INET, ipname, &server.sin_addr); - server.sin_port = htons (port); - /* - */ - if (bind (cptr->fd, (struct sockaddr *) &server, sizeof (server)) == - -1) { + if (bind (cptr->fd, (struct sockaddr *) &ss, len) == -1) { report_error ("binding stream socket %s:%s", cptr); (void) close (cptr->fd); return -1; } } - if (getsockname (cptr->fd, (struct sockaddr *) &server, &len)) { + if (getsockname (cptr->fd, (struct sockaddr *) &ss, &len)) { report_error ("getsockname failed for %s:%s", cptr); (void) close (cptr->fd); return -1; } if (cptr == &me) { /* Report port to stdout during startup */ char buf[1024]; + int bound_port; + + if (family == AF_INET6) + bound_port = ntohs (((struct sockaddr_in6 *) &ss)->sin6_port); + else + bound_port = ntohs (((struct sockaddr_in *) &ss)->sin_port); (void) sprintf (buf, rpl_str (RPL_MYPORTIS), me.name, "*", - ntohs (server.sin_port)); + bound_port); (void) write (0, buf, strlen (buf)); } if (cptr->fd > highest_fd) highest_fd = cptr->fd; - if (name) - irc_addr_from_str (&cptr->ip, ipname); - else - IRC_ADDR_COPY (&cptr->ip, &me.ip); - cptr->port = (int) ntohs (server.sin_port); + irc_addr_from_str (&cptr->ip, name); + cptr->port = port; (void) listen (cptr->fd, cfg_listen_size); local[cptr->fd] = cptr; @@ -389,14 +416,13 @@ void write_pidfile () */ static int check_init (aClient *cptr, char *sockn) { - struct sockaddr_in sk; - int len = sizeof (struct sockaddr_in); - + struct sockaddr_storage sk; + socklen_t len = sizeof (sk); /* If descriptor is a tty, special checking... */ if (isatty (cptr->fd)) { strncpyzt (sockn, me.sockhost, HOSTLEN); - memset ((char *) &sk, 0, sizeof (struct sockaddr_in)); + memset (&sk, 0, sizeof (sk)); } else if (getpeername (cptr->fd, (struct sockaddr *) &sk, &len) == -1) { report_error ("connect failure: %s %s", cptr); @@ -408,7 +434,10 @@ static int check_init (aClient *cptr, char *sockn) cptr->hostp = NULL; strncpyzt (sockn, me.sockhost, HOSTLEN); } - cptr->port = (int) ntohs (sk.sin_port); + if (sk.ss_family == AF_INET6) + cptr->port = (int) ntohs (((struct sockaddr_in6 *) &sk)->sin6_port); + else + cptr->port = (int) ntohs (((struct sockaddr_in *) &sk)->sin_port); return 0; } @@ -945,8 +974,8 @@ aClient * add_connection (aClient *cptr, int fd) if (isatty (fd)) /* If descriptor is a tty, special checking... */ get_sockhost (acptr, cptr->sockhost); else { - struct sockaddr_in addr; - int len = sizeof (struct sockaddr_in); + struct sockaddr_storage addr; + socklen_t len = sizeof (addr); if (getpeername (fd, (struct sockaddr *) &addr, &len) == -1) { report_error ("Failed in connecting to %s :%s", cptr); @@ -972,7 +1001,10 @@ aClient * add_connection (aClient *cptr, int fd) send (fd, zlinebuf, strlen (zlinebuf), 0); goto add_con_refuse; } - acptr->port = ntohs (addr.sin_port); + if (addr.ss_family == AF_INET6) + acptr->port = ntohs (((struct sockaddr_in6 *) &addr)->sin6_port); + else + acptr->port = ntohs (((struct sockaddr_in *) &addr)->sin_port); /* Let's do a check here to see if somebody matches a CN pair. * if they don't, we can send them the connection noticed, because @@ -1005,7 +1037,7 @@ aClient * add_connection (aClient *cptr, int fd) lin.flags = ASYNC_CLIENT; lin.value.cptr = acptr; Debug ((DEBUG_DNS, "lookup %s", inetntoa (&acptr->ip))); - acptr->hostp = gethost_byaddr ((char *) &acptr->ip, &lin); + acptr->hostp = gethost_byaddr ((char *) &acptr->ip.addr.v4, &lin); if (!acptr->hostp) SetDNS (acptr); else if (acptr->cc) @@ -1509,70 +1541,94 @@ int connect_server (aConfItem *aconf, aClient *by, struct hostent *hp) static struct sockaddr * connect_inet (aConfItem *aconf, aClient *cptr, int *lenp) { - static struct sockaddr_in server; + static struct sockaddr_storage ss; struct hostent *hp; + int family; + int sport; + + /* + * Resolve the target address if needed. + */ + if (irc_addr_is_zero (&aconf->ipnum)) { + char *s = strchr (aconf->host, '@'); + if (s) s++; + else s = aconf->host; + if (!irc_addr_from_str (&aconf->ipnum, s)) { + IRC_ADDR_ZERO (&aconf->ipnum); + hp = cptr->hostp; + if (!hp) { + Debug ((DEBUG_FATAL, "%s: unknown host", aconf->host)); + return NULL; + } + aconf->ipnum = irc_addr_from_v4 ((struct in_addr *) hp->h_addr); + } + } + + family = aconf->ipnum.family ? aconf->ipnum.family : AF_INET; /* * Might as well get sockhost from here, the connection is attempted * with it so if it fails its useless. */ - cptr->fd = socket (AF_INET, SOCK_STREAM, 0); + cptr->fd = socket (family, SOCK_STREAM, 0); if (cptr->fd >= MAXCLIENTS) { sendto_ops ("No more connections allowed (%s)", cptr->name); return NULL; } - mysk.sin_port = 0; - memset ((char *) &server, 0, sizeof (server)); - server.sin_family = AF_INET; - get_sockhost (cptr, aconf->host); - if (cptr->fd == -1) { report_error ("opening stream socket to server %s:%s", cptr); return NULL; } get_sockhost (cptr, aconf->host); - server.sin_port = 0; - server.sin_addr = me.ip.addr.v4; - server.sin_family = AF_INET; + /* - ** Bind to a local IP# (with unknown port - let unix decide) so - ** we have some chance of knowing the IP# that gets used for a host - ** with more than one IP#. - */ - /* No we don't bind it, not all OS's can handle connecting with - ** an already bound socket, different ip# might occur anyway - ** leading to a freezing select() on this side for some time. - ** I had this on my Linux 1.1.88 --Run + * Bind to local IP if configured (virtual host support). */ - /* We do now. Virtual interface stuff --ns */ - if (!irc_addr_is_zero (&me.ip)) - if (bind (cptr->fd, (struct sockaddr *) &server, sizeof (server)) == - -1) { + if (!irc_addr_is_zero (&me.ip)) { + struct sockaddr_storage bind_ss; + socklen_t bind_len; + memset (&bind_ss, 0, sizeof (bind_ss)); + if (family == AF_INET6) { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &bind_ss; + s6->sin6_family = AF_INET6; + if (me.ip.family == AF_INET6) + memcpy (&s6->sin6_addr, &me.ip.addr.v6, sizeof (struct in6_addr)); + /* else leave as in6addr_any — can't bind v4 addr to v6 socket */ + bind_len = sizeof (struct sockaddr_in6); + } + else { + struct sockaddr_in *s4 = (struct sockaddr_in *) &bind_ss; + s4->sin_family = AF_INET; + s4->sin_addr = me.ip.addr.v4; + bind_len = sizeof (struct sockaddr_in); + } + if (bind (cptr->fd, (struct sockaddr *) &bind_ss, bind_len) == -1) { report_error ("error binding to local port for %s:%s", cptr); return NULL; } - memset ((char *) &server, 0, sizeof (server)); - server.sin_family = AF_INET; + } + /* - * By this point we should know the IP# of the host listed in the - * conf line, whether as a result of the hostname lookup or the ip# - * being present instead. If we dont know it, then the connect fails. + * Build the destination sockaddr. */ - if (isdigit (*aconf->host) && irc_addr_is_zero (&aconf->ipnum)) - irc_addr_from_str (&aconf->ipnum, aconf->host); - if (irc_addr_is_zero (&aconf->ipnum)) { - hp = cptr->hostp; - if (!hp) { - Debug ((DEBUG_FATAL, "%s: unknown host", aconf->host)); - return NULL; - } - aconf->ipnum = irc_addr_from_v4 ((struct in_addr *) hp->h_addr); + memset (&ss, 0, sizeof (ss)); + sport = (aconf->port > 0) ? aconf->port : portnum; + if (family == AF_INET6) { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &ss; + s6->sin6_family = AF_INET6; + memcpy (&s6->sin6_addr, &aconf->ipnum.addr.v6, sizeof (struct in6_addr)); + s6->sin6_port = htons (sport); + *lenp = sizeof (struct sockaddr_in6); + } + else { + struct sockaddr_in *s4 = (struct sockaddr_in *) &ss; + s4->sin_family = AF_INET; + memcpy (&s4->sin_addr, &aconf->ipnum.addr.v4, sizeof (struct in_addr)); + s4->sin_port = htons (sport); + *lenp = sizeof (struct sockaddr_in); } - memcpy ((char *) &server.sin_addr, (char *) &aconf->ipnum.addr.v4, sizeof (struct in_addr)); IRC_ADDR_COPY (&cptr->ip, &aconf->ipnum); - server.sin_port = htons (((aconf->port > 0) ? aconf->port : portnum)); - *lenp = sizeof (server); - return (struct sockaddr *) &server; + return (struct sockaddr *) &ss; } -- 2.30.2