IPv6 Phase 2: Dual-stack socket layer
authorRemco Rijnders <remmy@serenity-irc.net>
Sat, 7 Mar 2026 17:17:36 +0000 (12:17 -0500)
committerRemco Rijnders <remmy@serenity-irc.net>
Sat, 7 Mar 2026 17:17:36 +0000 (12:17 -0500)
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 <noreply@anthropic.com>
src/s_bsd.c

index ad2c48664e24e735f2b75ae35a26b76f0289c5b3..b001a8af61171d0bc45a5bcd5785da3fb0f26733 100644 (file)
@@ -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;
 }