From: Kirill A. Korinsky Subject: Re: relayd: support multiple resolveble addresses To: tech@openbsd.org, rafael@sizeofvoid.org Date: Sat, 16 May 2026 16:30:31 +0200 On Mon, 06 Apr 2026 11:33:21 +0200, "Kirill A. Korinsky" wrote: > > tech@, > > relayd supports interface name, DNS hostname or interface group at > listen on but it binds only to the first discovered IP address which is > usually IPv4. > > Here I changed parser to create a dedicated listener for each discovered > and confiugred on a local inerface address. > > This means that listen on egress port 80 binds to all IPv4 and IPv6 > addresses on the egress group. > Ok? Index: etc/examples/relayd.conf =================================================================== RCS file: /home/cvs/src/etc/examples/relayd.conf,v diff -u -p -r1.6 relayd.conf --- etc/examples/relayd.conf 29 Oct 2023 11:27:11 -0000 1.6 +++ etc/examples/relayd.conf 2 May 2026 11:41:39 -0000 @@ -2,7 +2,6 @@ # # Macros # -ext_addr="192.168.1.1" webhost1="10.0.0.1" webhost2="10.0.0.2" sshhost1="10.0.0.3" @@ -24,7 +23,7 @@ table { 127.0.0.1 } # Services will be mapped to a rdr rule. # redirect www { - listen on $ext_addr port http interface trunk0 + listen on egress port http # tag every packet that goes thru the rdr rule with RELAYD pftag RELAYD @@ -51,7 +50,7 @@ http protocol https { relay wwwtls { # Run as a TLS accelerator - listen on $ext_addr port 443 tls + listen on egress port https tls protocol https # Forward to hosts in the webhosts table using a src/dst hash @@ -69,7 +68,7 @@ protocol sshtcp { relay sshgw { # Run as a simple TCP relay - listen on $ext_addr port 2222 + listen on egress port 2222 protocol sshtcp # Forward to the shared carp(4) address of an internal gateway Index: usr.sbin/relayd/parse.y =================================================================== RCS file: /home/cvs/src/usr.sbin/relayd/parse.y,v diff -u -p -r1.263 parse.y --- usr.sbin/relayd/parse.y 15 May 2026 13:57:24 -0000 1.263 +++ usr.sbin/relayd/parse.y 16 May 2026 14:29:10 -0000 @@ -131,8 +131,10 @@ int host_dns(const char *, struct addr int, struct portrange *, const char *, int); int host_if(const char *, struct addresslist *, int, struct portrange *, const char *, int); +int host_ifaddr(struct sockaddr_storage *, struct ifaddrs *); int host(const char *, struct addresslist *, int, struct portrange *, const char *, int); +int host_local(struct addresslist *); void host_free(struct addresslist *); struct table *table_inherit(struct table *); @@ -1995,35 +1997,60 @@ relayopts_l : relayopts_l relayoptsl nl relayoptsl : LISTEN ON STRING port opttls { struct addresslist al; struct address *h; - struct relay *r; + struct relay *nr, *r; + int cnt = 0; if (rlay->rl_conf.ss.ss_family != AF_UNSPEC) { - if ((r = calloc(1, sizeof (*r))) == NULL) + if ((r = calloc(1, sizeof(*r))) == NULL) fatal("out of memory"); - TAILQ_INSERT_TAIL(&relays, r, rl_entry); } else r = rlay; if ($4.op != PF_OP_EQ) { yyerror("invalid port"); free($3); + if (r != rlay) + free(r); YYERROR; } TAILQ_INIT(&al); - if (host($3, &al, 1, &$4, NULL, -1) <= 0) { + if (host($3, &al, SRV_MAX_VIRTS, &$4, NULL, -1) <= 0) { yyerror("invalid listen ip: %s", $3); free($3); + if (r != rlay) + free(r); + YYERROR; + } + if (host_local(&al) == 0) { + yyerror("no local listen ip: %s", $3); + free($3); + host_free(&al); + if (r != rlay) + free(r); YYERROR; } free($3); - h = TAILQ_FIRST(&al); - bcopy(&h->ss, &r->rl_conf.ss, sizeof(r->rl_conf.ss)); - r->rl_conf.port = h->port.val[0]; - if ($5) { - r->rl_conf.flags |= F_TLS; - conf->sc_conf.flags |= F_TLS; + TAILQ_FOREACH(h, &al, entry) { + if (cnt == 0) { + nr = r; + if (nr != rlay) + TAILQ_INSERT_TAIL(&relays, nr, + rl_entry); + } else { + if ((nr = calloc(1, sizeof(*nr))) == NULL) + fatal("out of memory"); + TAILQ_INSERT_TAIL(&relays, nr, rl_entry); + } + bcopy(&h->ss, &nr->rl_conf.ss, + sizeof(nr->rl_conf.ss)); + nr->rl_conf.port = h->port.val[0]; + if ($5) + nr->rl_conf.flags |= F_TLS; + cnt++; } - tableport = h->port.val[0]; + if ($5) + conf->sc_conf.flags |= F_TLS; + tableport = $4.val[0]; host_free(&al); } | forwardmode opttlsclient TO forwardspec dstaf optproxyproto { @@ -3376,6 +3403,64 @@ host(const char *s, struct addresslist * TAILQ_INSERT_HEAD(al, h, entry); return (1); +} + +int +host_ifaddr(struct sockaddr_storage *ss, struct ifaddrs *ifap) +{ + struct ifaddrs *p; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + switch (ss->ss_family) { + case AF_INET: + sin = (struct sockaddr_in *)ss; + if (sin->sin_addr.s_addr == INADDR_ANY) + return (1); + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)ss; + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + return (1); + break; + default: + return (0); + } + + for (p = ifap; p != NULL; p = p->ifa_next) { + if (p->ifa_addr == NULL || + p->ifa_addr->sa_family != ss->ss_family || + sockaddr_cmp((struct sockaddr *)ss, p->ifa_addr, -1) != 0) + continue; + bzero(ss, sizeof(*ss)); + memcpy(ss, p->ifa_addr, p->ifa_addr->sa_len); + return (1); + } + + return (0); +} + +int +host_local(struct addresslist *al) +{ + struct ifaddrs *ifap; + struct address *h, *next; + int cnt = 0; + + if (getifaddrs(&ifap) == -1) + fatal("getifaddrs"); + + TAILQ_FOREACH_SAFE(h, al, entry, next) { + if (host_ifaddr(&h->ss, ifap)) { + cnt++; + continue; + } + TAILQ_REMOVE(al, h, entry); + free(h); + } + + freeifaddrs(ifap); + return (cnt); } void Index: usr.sbin/relayd/relayd.conf.5 =================================================================== RCS file: /home/cvs/src/usr.sbin/relayd/relayd.conf.5,v diff -u -p -r1.216 relayd.conf.5 --- usr.sbin/relayd/relayd.conf.5 15 May 2026 13:57:24 -0000 1.216 +++ usr.sbin/relayd/relayd.conf.5 16 May 2026 14:29:10 -0000 @@ -727,7 +727,19 @@ Like the previous directive, but for red .Op Ic tls .Xc Specify the address and port for the relay to listen on. -The relay will accept incoming connections to the specified address. +The relay will accept incoming connections to the specified address or +addresses. +If +.Ar address +resolves to multiple IPv4 or IPv6 addresses, such as an interface +name, interface group, or DNS hostname, +.Xr relayd 8 +will create a listener for each local address. +For DNS hostnames, all resolved IPv4 and IPv6 addresses are considered, +but only addresses configured on a local interface are used. +Addresses that are not configured on a local interface are ignored. +If none of the resolved addresses are local, the configuration is +invalid. If the .Ic tls keyword is present, the relay will accept connections using the -- wbr, Kirill