Download raw body.
relayd: support multiple resolveble addresses
On Mon, 06 Apr 2026 11:33:21 +0200,
"Kirill A. Korinsky" <kirill@korins.ky> 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 <fallback> { 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
relayd: support multiple resolveble addresses