Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
Re: relayd: support multiple resolveble addresses
To:
tech@openbsd.org, rafael@sizeofvoid.org
Date:
Sat, 16 May 2026 16:30:31 +0200

Download raw body.

Thread
  • Kirill A. Korinsky:

    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
    
    
  • Kirill A. Korinsky:

    relayd: support multiple resolveble addresses