Commit Diff


commit - 37df23d183de23b74f8a026977b8210dc22701a6
commit + 509d0509a50883a6f8407b63774f40dd1e41dadf
blob - 3f563ed2aba7598f4c4da31068d0ece6d23cb7a9
blob + 82097604764fbbc218ab0d4a03cbd130ac9d8376
--- config.c
+++ config.c
@@ -37,9 +37,8 @@ config_new(void)
 	TAILQ_INIT(&conf->fcgi);
 	TAILQ_INIT(&conf->hosts);
 	TAILQ_INIT(&conf->pkis);
+	TAILQ_INIT(&conf->addrs);
 
-	conf->port = 1965;
-	conf->ipv6 = 0;
 	conf->protos = TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3;
 
 	init_mime(&conf->mime);
@@ -50,9 +49,6 @@ config_new(void)
 	conf->use_privsep_crypto = 1;
 #endif
 
-	conf->sock4 = -1;
-	conf->sock6 = -1;
-
 	return conf;
 }
 
@@ -67,21 +63,12 @@ config_purge(struct conf *conf)
 	struct envlist *e, *te;
 	struct alist *a, *ta;
 	struct pki *pki, *tpki;
+	struct address *addr, *taddr;
 	int use_privsep_crypto;
 
 	ps = conf->ps;
 	use_privsep_crypto = conf->use_privsep_crypto;
 
-	if (conf->sock4 != -1) {
-		event_del(&conf->evsock4);
-		close(conf->sock4);
-	}
-
-	if (conf->sock6 != -1) {
-		event_del(&conf->evsock6);
-		close(conf->sock6);
-	}
-
 	free_mime(&conf->mime);
 	TAILQ_FOREACH_SAFE(f, &conf->fcgi, fcgi, tf) {
 		TAILQ_REMOVE(&conf->fcgi, f, fcgi);
@@ -96,6 +83,11 @@ config_purge(struct conf *conf)
 		free(h->key);
 		free(h->ocsp);
 
+		TAILQ_FOREACH_SAFE(addr, &h->addrs, addrs, taddr) {
+			TAILQ_REMOVE(&h->addrs, addr, addrs);
+			free(addr);
+		}
+
 		TAILQ_FOREACH_SAFE(l, &h->locations, locations, tl) {
 			TAILQ_REMOVE(&h->locations, l, locations);
 
@@ -139,11 +131,19 @@ config_purge(struct conf *conf)
 		free(pki);
 	}
 
+	TAILQ_FOREACH_SAFE(addr, &conf->addrs, addrs, taddr) {
+		TAILQ_REMOVE(&conf->addrs, addr, addrs);
+		if (addr->sock != -1) {
+			close(addr->sock);
+			event_del(&addr->evsock);
+		}
+		free(addr);
+	}
+
 	memset(conf, 0, sizeof(*conf));
 
 	conf->ps = ps;
 	conf->use_privsep_crypto = use_privsep_crypto;
-	conf->sock4 = conf->sock6 = -1;
 	conf->protos = TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3;
 	init_mime(&conf->mime);
 	TAILQ_INIT(&conf->fcgi);
@@ -223,82 +223,47 @@ config_send_kp(struct privsep *ps, int cert_type, int 
 }
 
 static int
-make_socket(int port, int family)
+config_send_socks(struct conf *conf)
 {
-	int sock, v;
-	struct sockaddr_in addr4;
-	struct sockaddr_in6 addr6;
-	struct sockaddr *addr;
-	socklen_t len;
+	struct privsep	*ps = conf->ps;
+	struct address	*addr, a;
+	int		 sock, v;
 
-	switch (family) {
-	case AF_INET:
-		memset(&addr4, 0, sizeof(addr4));
-		addr4.sin_family = family;
-		addr4.sin_port = htons(port);
-		addr4.sin_addr.s_addr = INADDR_ANY;
-		addr = (struct sockaddr*)&addr4;
-		len = sizeof(addr4);
-		break;
+	TAILQ_FOREACH(addr, &conf->addrs, addrs) {
+		sock = socket(addr->ai_family, addr->ai_socktype,
+		    addr->ai_protocol);
+		if (sock == -1)
+			fatal("socket");
 
-	case AF_INET6:
-		memset(&addr6, 0, sizeof(addr6));
-		addr6.sin6_family = AF_INET6;
-		addr6.sin6_port = htons(port);
-		addr6.sin6_addr = in6addr_any;
-		addr = (struct sockaddr*)&addr6;
-		len = sizeof(addr6);
-		break;
+		v = 1;
+		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v))
+		    == -1)
+			fatal("setsockopt(SO_REUSEADDR)");
 
-	default:
-		/* unreachable */
-		abort();
-	}
+		v = 1;
+		if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v))
+		    == -1)
+			fatal("setsockopt(SO_REUSEPORT)");
 
-	if ((sock = socket(family, SOCK_STREAM, 0)) == -1)
-		fatal("socket");
+		mark_nonblock(sock);
 
-	v = 1;
-	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1)
-		fatal("setsockopt(SO_REUSEADDR)");
+		if (bind(sock, (struct sockaddr *)&addr->ss, addr->slen)
+		    == -1)
+			fatal("bind");
+
+		if (listen(sock, 16) == -1)
+			fatal("listen");
 
-	v = 1;
-	if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1)
-		fatal("setsockopt(SO_REUSEPORT)");
+		memcpy(&a, addr, sizeof(a));
+		a.conf = NULL;
+		a.sock = -1;
+		memset(&a.evsock, 0, sizeof(a.evsock));
+		memset(&a.addrs, 0, sizeof(a.addrs));
 
-	mark_nonblock(sock);
-
-	if (bind(sock, addr, len) == -1)
-		fatal("bind");
-
-	if (listen(sock, 16) == -1)
-		fatal("listen");
-
-	return sock;
-}
-
-static int
-config_send_socks(struct conf *conf)
-{
-	struct privsep	*ps = conf->ps;
-	int		 sock;
-
-	if ((sock = make_socket(conf->port, AF_INET)) == -1)
-		return -1;
-
-	if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_SOCK4, sock,
-	    NULL, 0) == -1)
-		return -1;
-
-	if (!conf->ipv6)
-		return 0;
-
-	if ((sock = make_socket(conf->port, AF_INET6)) == -1)
-		return -1;
-
-	if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_SOCK6, sock,
-	    NULL, 0) == -1)
-		return -1;
+		if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_SOCK, sock,
+		    &a, sizeof(a)) == -1)
+			return -1;
+	}
 
 	return 0;
 }
@@ -327,10 +292,6 @@ config_send(struct conf *conf)
 	    &conf->protos, sizeof(conf->protos)) == -1)
 		return -1;
 
-	if (proc_compose(ps, PROC_SERVER, IMSG_RECONF_PORT,
-	    &conf->port, sizeof(conf->port)) == -1)
-		return -1;
-
 	if (proc_flush_imsg(ps, PROC_SERVER, -1) == -1)
 		return -1;
 
@@ -549,6 +510,7 @@ config_recv(struct conf *conf, struct imsg *imsg)
 	struct envlist	*env;
 	struct alist	*alias;
 	struct proxy	*proxy;
+	struct address	*addr;
 	uint8_t		*d;
 	size_t		 len, datalen;
 
@@ -577,31 +539,19 @@ config_recv(struct conf *conf, struct imsg *imsg)
 		memcpy(&conf->protos, imsg->data, datalen);
 		break;
 
-	case IMSG_RECONF_PORT:
-		IMSG_SIZE_CHECK(imsg, &conf->port);
-		memcpy(&conf->port, imsg->data, datalen);
-		break;
-
-	case IMSG_RECONF_SOCK4:
-		if (conf->sock4 != -1)
-			fatalx("socket ipv4 already recv'd");
+	case IMSG_RECONF_SOCK:
+		addr = xcalloc(1, sizeof(*addr));
+		IMSG_SIZE_CHECK(imsg, addr);
+		memcpy(addr, imsg->data, sizeof(*addr));
 		if (imsg->fd == -1)
 			fatalx("missing socket for IMSG_RECONF_SOCK4");
-		conf->sock4 = imsg->fd;
-		event_set(&conf->evsock4, conf->sock4, EV_READ|EV_PERSIST,
-		    do_accept, conf);
+		addr->conf = conf;
+		addr->sock = imsg->fd;
+		event_set(&addr->evsock, addr->sock, EV_READ|EV_PERSIST,
+		    do_accept, addr);
+		TAILQ_INSERT_HEAD(&conf->addrs, addr, addrs);
 		break;
 
-	case IMSG_RECONF_SOCK6:
-		if (conf->sock6 != -1)
-			fatalx("socket ipv6 already recv'd");
-		if (imsg->fd == -1)
-			fatalx("missing socket for IMSG_RECONF_SOCK6");
-		conf->sock6 = imsg->fd;
-		event_set(&conf->evsock6, conf->sock6, EV_READ|EV_PERSIST,
-		    do_accept, conf);
-		break;
-
 	case IMSG_RECONF_FCGI:
 		IMSG_SIZE_CHECK(imsg, fcgi);
 		fcgi = xcalloc(1, sizeof(*fcgi));
blob - bf3cae0f550c74887b455a5befa0fdeb4c821dd6
blob + 1d55b4a5dfde4805a1b1e78511e101f7a371ac1c
--- fcgi.c
+++ fcgi.c
@@ -350,7 +350,7 @@ fcgi_req(struct client *c)
 	struct tm	 tminfo;
 	struct envlist	*p;
 
-	e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
+	e = getnameinfo((struct sockaddr*)&c->raddr, c->raddrlen,
 	    addr, sizeof(addr),
 	    NULL, 0,
 	    NI_NUMERICHOST);
blob - f45d9b2ce431ea1f1af8f847a89e3f8d7bfb9a05
blob + 4978c2cab70a81f1a85fad0ae6cda0cddb76ddc8
--- ge.c
+++ ge.c
@@ -159,10 +159,15 @@ static int
 serve(struct conf *conf, const char *host, int port, const char *dir)
 {
 	struct addrinfo hints, *res, *res0;
+	struct vhost *vh = TAILQ_FIRST(&conf->hosts);
+	struct address *addr, *acp;
 	int r, error, saved_errno, sock = -1;
 	const char *cause = NULL;
 	char service[32];
+	int any = 0;
 
+	event_init();
+
 	r = snprintf(service, sizeof(service), "%d", port);
 	if (r < 0 || (size_t)r >= sizeof(service))
 		fatal("snprintf");
@@ -193,24 +198,34 @@ serve(struct conf *conf, const char *host, int port, c
 		if (listen(sock, 5) == -1)
 			fatal("listen");
 
-		/*
-		 * for the time being, we're happy as soon as
-		 * something binds.
-		 */
-		break;
+		any = 1;
+
+		addr = xcalloc(1, sizeof(*addr));
+		addr->ai_flags = res->ai_flags;
+		addr->ai_family = res->ai_family;
+		addr->ai_socktype = res->ai_socktype;
+		addr->ai_protocol = res->ai_protocol;
+		addr->slen = res->ai_addrlen;
+		memcpy(&addr->ss, res->ai_addr, res->ai_addrlen);
+
+		addr->conf = conf;
+		addr->sock = sock;
+		event_set(&addr->evsock, addr->sock, EV_READ|EV_PERSIST,
+		    do_accept, addr);
+
+		TAILQ_INSERT_HEAD(&conf->addrs, addr, addrs);
+
+		acp = xcalloc(1, sizeof(*acp));
+		memcpy(acp, addr, sizeof(*acp));
+		acp->sock = -1;
+		memset(&acp->evsock, 0, sizeof(acp->evsock));
+		TAILQ_INSERT_HEAD(&vh->addrs, addr, addrs);
 	}
 
-	if (sock == -1)
+	if (!any)
 		fatal("%s", cause);
 	freeaddrinfo(res0);
 
-	event_init();
-
-	/* cheating */
-	conf->sock4 = sock;
-	event_set(&conf->evsock4, conf->sock4, EV_READ|EV_PERSIST,
-	    do_accept, conf);
-
 	server_init(NULL, NULL, NULL);
 	if (server_configure_done(conf) == -1)
 		fatalx("server configuration failed");
@@ -239,7 +254,7 @@ main(int argc, char **argv)
 	struct location *loc;
 	const char *errstr, *certs_dir = NULL, *hostname = "localhost";
 	char path[PATH_MAX];
-	int ch;
+	int ch, port = 1965;
 
 	setlocale(LC_CTYPE, "");
 
@@ -262,7 +277,7 @@ main(int argc, char **argv)
 			usage();
 			break;
 		case 'p':
-			conf->port = strtonum(optarg, 0, UINT16_MAX, &errstr);
+			port = strtonum(optarg, 0, UINT16_MAX, &errstr);
 			if (errstr)
 				fatalx("port number is %s: %s", errstr,
 				    optarg);
@@ -316,5 +331,5 @@ main(int argc, char **argv)
 	/* start the server */
 	signal(SIGPIPE, SIG_IGN);
 	setproctitle("%s", loc->dir);
-	return serve(conf, hostname, conf->port, loc->dir);
+	return serve(conf, hostname, port, loc->dir);
 }
blob - d3e4cb2d0d3f0d855fa6f743a4e11b55ac7a9ea3
blob + b85fd4a1bb6bc4ed3d4c9d671593916e04b71832
--- gmid.c
+++ gmid.c
@@ -484,9 +484,7 @@ main_print_conf(struct conf *conf)
 
 	if (*conf->chroot != '\0')
 		printf("chroot \"%s\"\n", conf->chroot);
-	printf("ipv6 %s\n", conf->ipv6 ? "on" : "off");
 	/* XXX: defined mimes? */
-	printf("port %d\n", conf->port);
 	printf("prefork %d\n", conf->prefork);
 	/* XXX: protocols? */
 	if (*conf->user != '\0')
blob - 644788bcdf6c52441e6cb6ed9a768ff3187332b1
blob + 8947e68a144d2bc7174694d25f5ccd307ba7b198
--- gmid.h
+++ gmid.h
@@ -102,6 +102,25 @@ struct parser {
 	char		*iri;
 	struct iri	*parsed;
 	const char	*err;
+};
+
+struct conf;
+TAILQ_HEAD(addrhead, address);
+struct address {
+	int			 ai_flags;
+	int			 ai_family;
+	int			 ai_socktype;
+	int			 ai_protocol;
+	struct sockaddr_storage	 ss;
+	socklen_t		 slen;
+	int16_t			 port;
+
+	/* used in the server */
+	struct conf		*conf;
+	int			 sock;
+	struct event		 evsock; /* set if sock != -1 */
+
+	TAILQ_ENTRY(address)	 addrs;
 };
 
 TAILQ_HEAD(fcgihead, fcgi);
@@ -188,6 +207,8 @@ struct vhost {
 
 	TAILQ_ENTRY(vhost) vhosts;
 
+	struct addrhead	 addrs;
+
 	/*
 	 * the first location rule is always '*' and holds the default
 	 * settings for the vhost, then follows the "real" location
@@ -220,8 +241,6 @@ struct pki {
 
 struct conf {
 	struct privsep	*ps;
-	int		 port;
-	int		 ipv6;
 	uint32_t	 protos;
 	struct mime	 mime;
 	char		 chroot[PATH_MAX];
@@ -229,15 +248,11 @@ struct conf {
 	int		 prefork;
 	int		 reload;
 	int		 use_privsep_crypto;
-
-	int		 sock4;
-	struct event	 evsock4;
-	int		 sock6;
-	struct event	 evsock6;
 
 	struct fcgihead	 fcgi;
 	struct vhosthead hosts;
 	struct pkihead	 pkis;
+	struct addrhead	 addrs;
 };
 
 extern const char *config_path;
@@ -258,6 +273,7 @@ enum {
 
 struct client {
 	struct conf	*conf;
+	struct address	*addr;
 	uint32_t	 id;
 	struct tls	*ctx;
 	char		*req;
@@ -324,9 +340,7 @@ enum imsg_type {
 	IMSG_RECONF_START,	/* 7 */
 	IMSG_RECONF_MIME,
 	IMSG_RECONF_PROTOS,
-	IMSG_RECONF_PORT,
-	IMSG_RECONF_SOCK4,
-	IMSG_RECONF_SOCK6,
+	IMSG_RECONF_SOCK,
 	IMSG_RECONF_FCGI,
 	IMSG_RECONF_HOST,
 	IMSG_RECONF_CERT,
blob - 8ba06d3931c0035ae9d7310680e632944ca4c861
blob + 2f85a660d177ee24bd720b00b0195502c7766fc8
--- parse.y
+++ parse.y
@@ -37,6 +37,9 @@
 
 struct conf *conf;
 
+static const char	*default_host = "*";
+static uint16_t		 default_port = 1965;
+
 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
 static struct file {
 	TAILQ_ENTRY(file)	 entry;
@@ -96,6 +99,7 @@ void		 parsehp(char *, char **, const char **, const c
 int		 fastcgi_conf(const char *, const char *);
 void		 add_param(char *, char *);
 int		 getservice(const char *);
+void		 listen_on(const char *, const char *);
 
 static struct vhost		*host;
 static struct location		*loc;
@@ -125,7 +129,7 @@ typedef struct {
 %token	FASTCGI FOR_HOST
 %token	INCLUDE INDEX IPV6
 %token	KEY
-%token	LANG LOCATION LOG
+%token	LANG LISTEN LOCATION LOG
 %token	OCSP OFF ON
 %token	PARAM PORT PREFORK PROTO PROTOCOLS PROXY
 %token	RELAY_TO REQUIRE RETURN ROOT
@@ -220,8 +224,19 @@ option		: CHROOT string	{
 				yyerror("chroot path too long");
 			free($2);
 		}
-		| IPV6 bool		{ conf->ipv6 = $2; }
-		| PORT NUM		{ conf->port = check_port_num($2); }
+		| IPV6 bool {
+			yywarn("option `ipv6' is deprecated,"
+			    " please use `listen on'");
+			if ($2)
+				default_host = "*";
+			else
+				default_host = "0.0.0.0";
+		}
+		| PORT NUM {
+			yywarn("option `port' is deprecated,"
+			    " please use `listen on'");
+			default_port = $2;
+		}
 		| PREFORK NUM		{ conf->prefork = check_prefork_num($2); }
 		| PROTOCOLS string {
 			if (tls_config_parse_protocols(&conf->protos, $2) == -1)
@@ -259,6 +274,20 @@ vhost		: SERVER string {
 			    host->key_path == NULL)
 				yyerror("invalid vhost definition: %s",
 				    host->domain);
+			if (TAILQ_EMPTY(&host->addrs)) {
+				char portno[32];
+				int r;
+
+				r = snprintf(portno, sizeof(portno), "%d",
+				    default_port);
+				if (r < 0 || (size_t)r >= sizeof(portno))
+					fatal("snprintf");
+
+				yywarn("missing `listen on' in server %s,"
+				    " assuming %s port %d", $2, default_host,
+				    default_port);
+				listen_on(default_host, portno);
+			}
 		}
 		| error '}'		{ yyerror("bad server directive"); }
 		;
@@ -294,6 +323,22 @@ servopt		: ALIAS string {
 		}
 		| PARAM string '=' string {
 			add_param($2, $4);
+		}
+		| LISTEN ON STRING PORT STRING {
+			listen_on($3, $5);
+			free($3);
+			free($5);
+		}
+		| LISTEN ON STRING PORT NUM {
+			char portno[32];
+			int r;
+
+			r = snprintf(portno, sizeof(portno), "%d", $5);
+			if (r < 0 || (size_t)r >= sizeof(portno))
+				fatal("snprintf");
+
+			listen_on($3, portno);
+			free($3);
 		}
 		| locopt
 		;
@@ -515,6 +560,7 @@ static const struct keyword {
 	{"ipv6", IPV6},
 	{"key", KEY},
 	{"lang", LANG},
+	{"listen", LISTEN},
 	{"location", LOCATION},
 	{"log", LOG},
 	{"ocsp", OCSP},
@@ -913,6 +959,9 @@ parse_conf(struct conf *c, const char *filename)
 {
 	struct sym		*sym, *next;
 
+	default_host = "*";
+	default_port = 1965;
+
 	conf = c;
 
 	file = pushfile(filename, 0);
@@ -1152,4 +1201,79 @@ getservice(const char *n)
 	}
 
 	return ((unsigned short)llval);
+}
+
+static void
+add_to_addr_queue(struct addrhead *a, struct addrinfo *ai)
+{
+	struct address		*addr;
+	struct sockaddr_in	*sin;
+	struct sockaddr_in6	*sin6;
+
+	if (ai->ai_addrlen > sizeof(addr->ss))
+		fatalx("ai_addrlen larger than a sockaddr_storage");
+
+	TAILQ_FOREACH(addr, a, addrs) {
+		if (addr->ai_flags == ai->ai_flags &&
+		    addr->ai_family == ai->ai_family &&
+		    addr->ai_socktype == ai->ai_socktype &&
+		    addr->ai_protocol == ai->ai_protocol &&
+		    addr->slen == ai->ai_addrlen &&
+		    !memcmp(&addr->ss, ai->ai_addr, addr->slen))
+			return;
+	}
+
+	addr = xcalloc(1, sizeof(*addr));
+	addr->ai_flags = ai->ai_flags;
+	addr->ai_family = ai->ai_family;
+	addr->ai_socktype = ai->ai_socktype;
+	addr->ai_protocol = ai->ai_protocol;
+	addr->slen = ai->ai_addrlen;
+	memcpy(&addr->ss, ai->ai_addr, ai->ai_addrlen);
+
+	/* for commodity */
+	switch (addr->ai_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *)&addr->ss;
+		addr->port = ntohs(sin->sin_port);
+		break;
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)&addr->ss;
+		addr->port = ntohs(sin6->sin6_port);
+		break;
+	default:
+		fatalx("unknown socket family %d", addr->ai_family);
+	}
+
+	addr->sock = -1;
+
+	TAILQ_INSERT_HEAD(a, addr, addrs);
 }
+
+void
+listen_on(const char *hostname, const char *servname)
+{
+	struct addrinfo hints, *res, *res0;
+	int error;
+
+	if (!strcmp(hostname, "*"))
+		hostname = NULL;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_PASSIVE;
+	error = getaddrinfo(hostname, servname, &hints, &res0);
+	if (error) {
+		yyerror("listen on \"%s\" port %s: %s", hostname, servname,
+		    gai_strerror(errno));
+		return;
+	}
+
+	for (res = res0; res; res = res->ai_next) {
+		add_to_addr_queue(&host->addrs, res);
+		add_to_addr_queue(&conf->addrs, res);
+	}
+
+	freeaddrinfo(res0);
+}
blob - c61285b5f3837c4417ff98a79127247733bb8dab
blob + 06cb7a7762eaabf6718673fa06c8ede41211fce2
--- regress/lib.sh
+++ regress/lib.sh
@@ -10,9 +10,7 @@ current_test=
 run_test() {
 	ggflags=
 	port=10965
-	config_common="ipv6 off
-port $port
-"
+	config_common=""
 	hdr=
 	body=
 	dont_check_server_alive=no
@@ -64,6 +62,7 @@ server "localhost" {
 	cert "$PWD/cert.pem"
 	key  "$PWD/key.pem"
 	root "$PWD/testdata"
+	listen on localhost port $port
 	$2
 }
 EOF
@@ -78,6 +77,7 @@ set_proxy() {
 server "localhost.local" {
 	cert "$PWD/cert.pem"
 	key "$PWD/key.pem"
+	listen on localhost port $port
 	proxy {
 		relay-to localhost port $port
 		$1
blob - 5ebc33bdd15f4ea9e6c987995d6c00c0eb5ba344
blob + a38e4ba03a75bf901a4ef6e2a48ab9a6e9c64df3
--- regress/tests.sh
+++ regress/tests.sh
@@ -239,13 +239,13 @@ test_fastcgi() {
 test_macro_expansion() {
 	cat <<EOF > reg.conf
 pwd = "$PWD"
-$config_common
 
 server "localhost" {
 	# the quoting of \$ is for sh
 	cert \$pwd "/cert.pem"
 	key  \$pwd "/key.pem"
 	root \$pwd "/testdata"
+	listen on localhost port $port
 }
 EOF
 
blob - 05c1d4bf94ed0e278f6b0013480346ad54dd4094
blob + 18e8c91350428e9f2187966fcc6e62397da4a3ca
--- server.c
+++ server.c
@@ -489,7 +489,6 @@ strip_path(const char *path, int strip)
 static void
 fmt_sbuf(const char *fmt, struct client *c, const char *path)
 {
-	struct conf *conf = c->conf;
 	size_t i;
 	char buf[32];
 
@@ -519,7 +518,7 @@ fmt_sbuf(const char *fmt, struct client *c, const char
 			strlcat(c->sbuf, c->iri.query, sizeof(c->sbuf));
 			break;
 		case 'P':
-			snprintf(buf, sizeof(buf), "%d", conf->port);
+			snprintf(buf, sizeof(buf), "%d", c->addr->port);
 			strlcat(c->sbuf, buf, sizeof(c->sbuf));
 			memset(buf, 0, sizeof(buf));
 			break;
@@ -1313,7 +1312,7 @@ client_close(struct client *c)
 void
 do_accept(int sock, short et, void *d)
 {
-	struct conf *conf = d;
+	struct address *addr = d;
 	struct client *c;
 	struct sockaddr_storage raddr;
 	struct sockaddr *sraddr;
@@ -1332,7 +1331,8 @@ do_accept(int sock, short et, void *d)
 	mark_nonblock(fd);
 
 	c = xcalloc(1, sizeof(*c));
-	c->conf = conf;
+	c->conf = addr->conf;
+	c->addr = addr;
 	c->id = ++server_client_id;
 	c->fd = fd;
 	c->pfd = -1;
@@ -1486,16 +1486,19 @@ server_init(struct privsep *ps, struct privsep_proc *p
 int
 server_configure_done(struct conf *conf)
 {
+	struct address *addr;
+
 	if (load_default_mime(&conf->mime) == -1)
 		fatal("can't load default mime");
 	sort_mime(&conf->mime);
 	setup_tls(conf);
 	load_vhosts(conf);
-	if (conf->sock4 != -1)
-		event_add(&conf->evsock4, NULL);
-	if (conf->sock6 != -1)
-		event_add(&conf->evsock6, NULL);
 
+	TAILQ_FOREACH(addr, &conf->addrs, addrs) {
+		if (addr->sock != -1)
+			event_add(&addr->evsock, NULL);
+	}
+
 	return 0;
 }
 
@@ -1509,9 +1512,7 @@ server_dispatch_parent(int fd, struct privsep_proc *p,
 	case IMSG_RECONF_START:
 	case IMSG_RECONF_MIME:
 	case IMSG_RECONF_PROTOS:
-	case IMSG_RECONF_PORT:
-	case IMSG_RECONF_SOCK4:
-	case IMSG_RECONF_SOCK6:
+	case IMSG_RECONF_SOCK:
 	case IMSG_RECONF_FCGI:
 	case IMSG_RECONF_HOST:
 	case IMSG_RECONF_CERT:
blob - 43c1f4d86e007ab4d57004d52162954ce91b70d2
blob + bf28329eca544e589bb3ee70ceb7c34d78ae237b
--- utils.c
+++ utils.c
@@ -346,6 +346,7 @@ new_vhost(void)
 	struct vhost *h;
 
 	h = xcalloc(1, sizeof(*h));
+	TAILQ_INIT(&h->addrs);
 	TAILQ_INIT(&h->locations);
 	TAILQ_INIT(&h->params);
 	TAILQ_INIT(&h->aliases);