conf, tcp, udp: Allow address specification for forwarded ports

This feature is available in slirp4netns but was missing in passt and
pasta.

Given that we don't do dynamic memory allocation, we need to bind
sockets while parsing port configuration. This means we need to
process all other options first, as they might affect addressing and
IP version support. It also implies a minor rework of how TCP and UDP
implementations bind sockets.

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
This commit is contained in:
Stefano Brivio 2022-05-01 06:36:34 +02:00
parent df69be379e
commit 3c6ae62510
10 changed files with 274 additions and 156 deletions

120
conf.c
View file

@ -109,14 +109,24 @@ enum conf_port_type {
PORT_ALL, PORT_ALL,
}; };
/**
* conf_ports() - Parse port configuration options, initialise UDP/TCP sockets
* @c: Execution context
* @optname: Short option name, t, T, u, or U
* @optarg: Option argument (port specification)
* @set: Pointer to @conf_port_type to be set (port binding type)
*
* Return: -EINVAL on parsing error, 0 otherwise
*/
static int conf_ports(struct ctx *c, char optname, const char *optarg, static int conf_ports(struct ctx *c, char optname, const char *optarg,
enum conf_port_type *set) enum conf_port_type *set)
{ {
int start_src = -1, end_src = -1, start_dst = -1, end_dst = -1; int start_src = -1, end_src = -1, start_dst = -1, end_dst = -1;
void (*remap)(in_port_t port, in_port_t delta); void (*remap)(in_port_t port, in_port_t delta);
const char *p; char addr_buf[sizeof(struct in6_addr)] = { 0 };
sa_family_t af = AF_UNSPEC;
char buf[BUFSIZ], *sep, *p, *addr = addr_buf;
uint8_t *map; uint8_t *map;
char *sep;
if (optname == 't') { if (optname == 't') {
map = c->tcp.port_to_tap; map = c->tcp.port_to_tap;
@ -149,10 +159,20 @@ static int conf_ports(struct ctx *c, char optname, const char *optarg,
} }
if (!strcmp(optarg, "all")) { if (!strcmp(optarg, "all")) {
int i;
if (*set || c->mode != MODE_PASST) if (*set || c->mode != MODE_PASST)
return -EINVAL; return -EINVAL;
*set = PORT_ALL; *set = PORT_ALL;
memset(map, 0xff, PORT_EPHEMERAL_MIN / 8); memset(map, 0xff, PORT_EPHEMERAL_MIN / 8);
for (i = 0; i < PORT_EPHEMERAL_MIN; i++) {
if (optname == 't')
tcp_sock_init(c, 0, AF_UNSPEC, NULL, i);
else if (optname == 'u')
udp_sock_init(c, 0, AF_UNSPEC, NULL, i);
}
return 0; return 0;
} }
@ -161,12 +181,30 @@ static int conf_ports(struct ctx *c, char optname, const char *optarg,
*set = PORT_SPEC; *set = PORT_SPEC;
if (strspn(optarg, "0123456789-,:") != strlen(optarg)) { strncpy(buf, optarg, sizeof(buf) - 1);
err("Invalid port specifier %s", optarg);
return -EINVAL; if ((p = strchr(buf, '/'))) {
*p = 0;
p++;
if (optname != 't' && optname != 'u')
goto bad;
if (inet_pton(AF_INET, buf, addr))
af = AF_INET;
else if (inet_pton(AF_INET6, buf, addr))
af = AF_INET6;
else
goto bad;
} else {
p = buf;
addr = NULL;
} }
p = optarg; if (strspn(p, "0123456789-,:") != strlen(p))
goto bad;
do { do {
int i, port; int i, port;
@ -242,11 +280,16 @@ static int conf_ports(struct ctx *c, char optname, const char *optarg,
bitmap_set(map, i); bitmap_set(map, i);
if (start_dst == -1) /* 22 or 22-80 */ if (start_dst != -1) {
continue; /* 80:8080 or 22-80:8080:8080 */
remap(i, (in_port_t)(start_dst -
start_src));
}
/* 80:8080 or 22-80:8080:8080 */ if (optname == 't')
remap(i, (in_port_t)(start_dst - start_src)); tcp_sock_init(c, 0, af, addr, i);
else if (optname == 'u')
udp_sock_init(c, 0, af, addr, i);
} }
start_src = end_src = start_dst = end_dst = -1; start_src = end_src = start_dst = end_dst = -1;
@ -655,13 +698,15 @@ static void usage(const char *name)
info( " 'none': don't forward any ports"); info( " 'none': don't forward any ports");
info( " 'all': forward all unbound, non-ephemeral ports"); info( " 'all': forward all unbound, non-ephemeral ports");
info( " a comma-separated list, optionally ranged with '-'"); info( " a comma-separated list, optionally ranged with '-'");
info( " and optional target ports after ':'. Examples:"); info( " and optional target ports after ':', with optional");
info( " address specification suffixed by '/'. Examples:");
info( " -t 22 Forward local port 22 to 22 on guest"); info( " -t 22 Forward local port 22 to 22 on guest");
info( " -t 22:23 Forward local port 22 to 23 on guest"); info( " -t 22:23 Forward local port 22 to 23 on guest");
info( " -t 22,25 Forward ports 22, 25 to ports 22, 25"); info( " -t 22,25 Forward ports 22, 25 to ports 22, 25");
info( " -t 22-80 Forward ports 22 to 80"); info( " -t 22-80 Forward ports 22 to 80");
info( " -t 22-80:32-90 Forward ports 22 to 80 to"); info( " -t 22-80:32-90 Forward ports 22 to 80 to");
info( " corresponding port numbers plus 10"); info( " corresponding port numbers plus 10");
info( " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1 to guest");
info( " default: none"); info( " default: none");
info( " -u, --udp-ports SPEC UDP port forwarding to guest"); info( " -u, --udp-ports SPEC UDP port forwarding to guest");
info( " SPEC is as described for TCP above"); info( " SPEC is as described for TCP above");
@ -676,13 +721,15 @@ pasta_opts:
info( " 'none': don't forward any ports"); info( " 'none': don't forward any ports");
info( " 'auto': forward all ports currently bound in namespace"); info( " 'auto': forward all ports currently bound in namespace");
info( " a comma-separated list, optionally ranged with '-'"); info( " a comma-separated list, optionally ranged with '-'");
info( " and optional target ports after ':'. Examples:"); info( " and optional target ports after ':', with optional");
info( " address specification suffixed by '/'. Examples:");
info( " -t 22 Forward local port 22 to port 22 in netns"); info( " -t 22 Forward local port 22 to port 22 in netns");
info( " -t 22:23 Forward local port 22 to port 23"); info( " -t 22:23 Forward local port 22 to port 23");
info( " -t 22,25 Forward ports 22, 25 to ports 22, 25"); info( " -t 22,25 Forward ports 22, 25 to ports 22, 25");
info( " -t 22-80 Forward ports 22 to 80"); info( " -t 22-80 Forward ports 22 to 80");
info( " -t 22-80:32-90 Forward ports 22 to 80 to"); info( " -t 22-80:32-90 Forward ports 22 to 80 to");
info( " corresponding port numbers plus 10"); info( " corresponding port numbers plus 10");
info( " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1 to namespace");
info( " default: auto"); info( " default: auto");
info( " IPv6 bound ports are also forwarded for IPv4"); info( " IPv6 bound ports are also forwarded for IPv4");
info( " -u, --udp-ports SPEC UDP port forwarding to namespace"); info( " -u, --udp-ports SPEC UDP port forwarding to namespace");
@ -857,7 +904,6 @@ void conf(struct ctx *c, int argc, char **argv)
c->no_dhcp_dns = c->no_dhcp_dns_search = 1; c->no_dhcp_dns = c->no_dhcp_dns_search = 1;
do { do {
enum conf_port_type *set = NULL;
const char *optstring; const char *optstring;
if (c->mode == MODE_PASST) if (c->mode == MODE_PASST)
@ -1242,18 +1288,7 @@ void conf(struct ctx *c, int argc, char **argv)
case 'u': case 'u':
case 'T': case 'T':
case 'U': case 'U':
if (name == 't') /* Handle these later, once addresses are configured */
set = &tcp_tap;
else if (name == 'T')
set = &tcp_init;
else if (name == 'u')
set = &udp_tap;
else if (name == 'U')
set = &udp_init;
if (conf_ports(c, name, optarg, set))
usage(argv[0]);
break; break;
case '?': case '?':
case 'h': case 'h':
@ -1294,6 +1329,41 @@ void conf(struct ctx *c, int argc, char **argv)
conf_ip(c); conf_ip(c);
/* Now we can process port configuration options */
optind = 1;
do {
enum conf_port_type *set = NULL;
const char *optstring;
if (c->mode == MODE_PASST)
optstring = "dqfehs:p::P:m:a:n:M:g:i:D::S::46t:u:";
else
optstring = "dqfehI:p::P:m:a:n:M:g:i:D::S::46t:u:T:U:";
name = getopt_long(argc, argv, optstring, options, NULL);
switch (name) {
case 't':
case 'u':
case 'T':
case 'U':
if (name == 't')
set = &tcp_tap;
else if (name == 'T')
set = &tcp_init;
else if (name == 'u')
set = &udp_tap;
else if (name == 'U')
set = &udp_init;
if (!optarg || conf_ports(c, name, optarg, set))
usage(argv[0]);
break;
default:
break;
}
} while (name != -1);
if (!c->v4) if (!c->v4)
c->no_dhcp = 1; c->no_dhcp = 1;

5
icmp.c
View file

@ -168,7 +168,8 @@ int icmp_tap_handler(const struct ctx *c, int af, const void *addr,
iref.icmp.id = id = ntohs(ih->un.echo.id); iref.icmp.id = id = ntohs(ih->un.echo.id);
if ((s = icmp_id_map[V4][id].sock) <= 0) { if ((s = icmp_id_map[V4][id].sock) <= 0) {
s = sock_l4(c, AF_INET, IPPROTO_ICMP, id, 0, iref.u32); s = sock_l4(c, AF_INET, IPPROTO_ICMP, NULL, id,
iref.u32);
if (s < 0) if (s < 0)
goto fail_sock; goto fail_sock;
if (s > SOCKET_MAX) { if (s > SOCKET_MAX) {
@ -205,7 +206,7 @@ int icmp_tap_handler(const struct ctx *c, int af, const void *addr,
iref.icmp.id = id = ntohs(ih->icmp6_identifier); iref.icmp.id = id = ntohs(ih->icmp6_identifier);
if ((s = icmp_id_map[V6][id].sock) <= 0) { if ((s = icmp_id_map[V6][id].sock) <= 0) {
s = sock_l4(c, AF_INET6, IPPROTO_ICMPV6, id, 0, s = sock_l4(c, AF_INET6, IPPROTO_ICMPV6, NULL, id,
iref.u32); iref.u32);
if (s < 0) if (s < 0)
goto fail_sock; goto fail_sock;

12
passt.1
View file

@ -298,7 +298,8 @@ For low (< 1024) ports, see \fBNOTES\fR.
.TP .TP
.BR ports .BR ports
A comma-separated list of ports, optionally ranged with \fI-\fR, and, A comma-separated list of ports, optionally ranged with \fI-\fR, and,
optionally, with target ports after \fI:\fR, if they differ. Examples: optionally, with target ports after \fI:\fR, if they differ. Specific addresses
can be bound as well, separated by \fI/\fR. Examples:
.RS .RS
.TP .TP
-t 22 -t 22
@ -315,6 +316,9 @@ Forward local ports 22 to 80 to corresponding ports on the guest
.TP .TP
-t 22-80-32:90 -t 22-80-32:90
Forward local ports 22 to 80 to corresponding ports on the guest plus 10 Forward local ports 22 to 80 to corresponding ports on the guest plus 10
.TP
-t 192.0.2.1/22
Forward local port 22, bound to 192.0.2.1, to port 22 on the guest
.RE .RE
Default is \fBnone\fR. Default is \fBnone\fR.
@ -356,7 +360,8 @@ periodically derived (every second) from listening sockets reported by
.TP .TP
.BR ports .BR ports
A comma-separated list of ports, optionally ranged with \fI-\fR, and, A comma-separated list of ports, optionally ranged with \fI-\fR, and,
optionally, with target ports after \fI:\fR, if they differ. Examples: optionally, with target ports after \fI:\fR, if they differ. Specific addresses
can be bound as well, separated by \fI/\fR. Examples:
.RS .RS
.TP .TP
-t 22 -t 22
@ -374,6 +379,9 @@ Forward local ports 22 to 80 to corresponding ports in the target namespace
-t 22-80-32:90 -t 22-80-32:90
Forward local ports 22 to 80 to corresponding ports plus 10 in the target Forward local ports 22 to 80 to corresponding ports plus 10 in the target
namespace namespace
.TP
-t 192.0.2.1/22
Forward local port 22, bound to 192.0.2.1, to port 22 in the target namespace
.RE .RE
IPv6 bound ports are also forwarded for IPv4. IPv6 bound ports are also forwarded for IPv4.

22
passt.c
View file

@ -370,20 +370,13 @@ int main(int argc, char **argv)
__setlogmask(LOG_MASK(LOG_EMERG)); __setlogmask(LOG_MASK(LOG_EMERG));
conf(&c, argc, argv); /* NOLINTNEXTLINE(android-cloexec-epoll-create1): forking in a moment */
trace_init(c.trace); c.epollfd = epoll_create1(0);
if (!c.debug && (c.stderr || isatty(fileno(stdout))))
__openlog(log_name, LOG_PERROR, LOG_DAEMON);
c.epollfd = epoll_create1(c.foreground ? O_CLOEXEC : 0);
if (c.epollfd == -1) { if (c.epollfd == -1) {
perror("epoll_create1"); perror("epoll_create1");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
quit_fd = pasta_netns_quit_init(&c);
if (getrlimit(RLIMIT_NOFILE, &limit)) { if (getrlimit(RLIMIT_NOFILE, &limit)) {
perror("getrlimit"); perror("getrlimit");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -395,13 +388,20 @@ int main(int argc, char **argv)
} }
sock_probe_mem(&c); sock_probe_mem(&c);
conf(&c, argc, argv);
trace_init(c.trace);
if (!c.debug && (c.stderr || isatty(fileno(stdout))))
__openlog(log_name, LOG_PERROR, LOG_DAEMON);
quit_fd = pasta_netns_quit_init(&c);
c.fd_tap = c.fd_tap_listen = -1; c.fd_tap = c.fd_tap_listen = -1;
tap_sock_init(&c); tap_sock_init(&c);
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
if ((!c.no_udp && udp_sock_init(&c)) || if ((!c.no_udp && udp_init(&c)) || (!c.no_tcp && tcp_init(&c)))
(!c.no_tcp && tcp_sock_init(&c)))
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
proto_update_l2_buf(c.mac_guest, c.mac, &c.addr4); proto_update_l2_buf(c.mac_guest, c.mac, &c.addr4);

63
tcp.c
View file

@ -3082,14 +3082,18 @@ void tcp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events,
} }
/** /**
* tcp_sock_init_one() - Initialise listening sockets for a given port * tcp_sock_init() - Initialise listening sockets for a given port
* @c: Execution context * @c: Execution context
* @ns: In pasta mode, if set, bind with loopback address in namespace * @ns: In pasta mode, if set, bind with loopback address in namespace
* @af: Address family to select a specific IP version, or AF_UNSPEC
* @addr: Pointer to address for binding, NULL if not configured
* @port: Port, host order * @port: Port, host order
*/ */
static void tcp_sock_init_one(const struct ctx *c, int ns, in_port_t port) void tcp_sock_init(const struct ctx *c, int ns, sa_family_t af,
const void *addr, in_port_t port)
{ {
union tcp_epoll_ref tref = { .tcp.listen = 1 }; union tcp_epoll_ref tref = { .tcp.listen = 1 };
const void *bind_addr;
int s; int s;
if (ns) { if (ns) {
@ -3100,13 +3104,17 @@ static void tcp_sock_init_one(const struct ctx *c, int ns, in_port_t port)
tcp_port_delta_to_tap[port]); tcp_port_delta_to_tap[port]);
} }
if (c->v4) { if (af == AF_INET || af == AF_UNSPEC) {
tref.tcp.v6 = 0; if (!addr && c->mode == MODE_PASTA)
bind_addr = &c->addr4;
else
bind_addr = addr;
tref.tcp.v6 = 0;
tref.tcp.splice = 0; tref.tcp.splice = 0;
if (!ns) { if (!ns) {
s = sock_l4(c, AF_INET, IPPROTO_TCP, port, s = sock_l4(c, AF_INET, IPPROTO_TCP, bind_addr, port,
c->mode == MODE_PASTA ? BIND_EXT : BIND_ANY,
tref.u32); tref.u32);
if (s >= 0) if (s >= 0)
tcp_sock_set_bufsize(c, s); tcp_sock_set_bufsize(c, s);
@ -3118,9 +3126,11 @@ static void tcp_sock_init_one(const struct ctx *c, int ns, in_port_t port)
} }
if (c->mode == MODE_PASTA) { if (c->mode == MODE_PASTA) {
bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) };
tref.tcp.splice = 1; tref.tcp.splice = 1;
s = sock_l4(c, AF_INET, IPPROTO_TCP, port, s = sock_l4(c, AF_INET, IPPROTO_TCP, bind_addr, port,
BIND_LOOPBACK, tref.u32); tref.u32);
if (s >= 0) if (s >= 0)
tcp_sock_set_bufsize(c, s); tcp_sock_set_bufsize(c, s);
else else
@ -3135,13 +3145,17 @@ static void tcp_sock_init_one(const struct ctx *c, int ns, in_port_t port)
} }
} }
if (c->v6) { if (af == AF_INET6 || af == AF_UNSPEC) {
if (!addr && c->mode == MODE_PASTA)
bind_addr = &c->addr6;
else
bind_addr = addr;
tref.tcp.v6 = 1; tref.tcp.v6 = 1;
tref.tcp.splice = 0; tref.tcp.splice = 0;
if (!ns) { if (!ns) {
s = sock_l4(c, AF_INET6, IPPROTO_TCP, port, s = sock_l4(c, AF_INET6, IPPROTO_TCP, bind_addr, port,
c->mode == MODE_PASTA ? BIND_EXT : BIND_ANY,
tref.u32); tref.u32);
if (s >= 0) if (s >= 0)
tcp_sock_set_bufsize(c, s); tcp_sock_set_bufsize(c, s);
@ -3153,9 +3167,11 @@ static void tcp_sock_init_one(const struct ctx *c, int ns, in_port_t port)
} }
if (c->mode == MODE_PASTA) { if (c->mode == MODE_PASTA) {
bind_addr = &in6addr_loopback;
tref.tcp.splice = 1; tref.tcp.splice = 1;
s = sock_l4(c, AF_INET6, IPPROTO_TCP, port, s = sock_l4(c, AF_INET6, IPPROTO_TCP, bind_addr, port,
BIND_LOOPBACK, tref.u32); tref.u32);
if (s >= 0) if (s >= 0)
tcp_sock_set_bufsize(c, s); tcp_sock_set_bufsize(c, s);
else else
@ -3188,7 +3204,7 @@ static int tcp_sock_init_ns(void *arg)
if (!bitmap_isset(c->tcp.port_to_init, port)) if (!bitmap_isset(c->tcp.port_to_init, port))
continue; continue;
tcp_sock_init_one(c, 1, port); tcp_sock_init(c, 1, AF_UNSPEC, NULL, port);
} }
return 0; return 0;
@ -3259,15 +3275,15 @@ static int tcp_sock_refill(void *arg)
} }
/** /**
* tcp_sock_init() - Bind sockets for inbound connections, get key for sequence * tcp_init() - Get initial sequence, hash secret, initialise per-socket data
* @c: Execution context * @c: Execution context
* *
* Return: 0 on success, -1 on failure * Return: 0, doesn't return on failure
*/ */
int tcp_sock_init(struct ctx *c) int tcp_init(struct ctx *c)
{ {
struct tcp_sock_refill_arg refill_arg = { c, 0 }; struct tcp_sock_refill_arg refill_arg = { c, 0 };
int i, port; int i;
#ifndef HAS_GETRANDOM #ifndef HAS_GETRANDOM
int dev_random = open("/dev/random", O_RDONLY); int dev_random = open("/dev/random", O_RDONLY);
unsigned int random_read = 0; unsigned int random_read = 0;
@ -3296,13 +3312,6 @@ int tcp_sock_init(struct ctx *c)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
for (port = 0; port < USHRT_MAX; port++) {
if (!bitmap_isset(c->tcp.port_to_tap, port))
continue;
tcp_sock_init_one(c, 0, port);
}
for (i = 0; i < ARRAY_SIZE(tcp_l2_mh); i++) for (i = 0; i < ARRAY_SIZE(tcp_l2_mh); i++)
tcp_l2_mh[i] = (struct mmsghdr) { .msg_hdr.msg_iovlen = 1 }; tcp_l2_mh[i] = (struct mmsghdr) { .msg_hdr.msg_iovlen = 1 };
@ -3412,7 +3421,7 @@ static int tcp_port_rebind(void *arg)
if ((a->c->v4 && tcp_sock_ns[port][V4] == -1) || if ((a->c->v4 && tcp_sock_ns[port][V4] == -1) ||
(a->c->v6 && tcp_sock_ns[port][V6] == -1)) (a->c->v6 && tcp_sock_ns[port][V6] == -1))
tcp_sock_init_one(a->c, 1, port); tcp_sock_init(a->c, 1, AF_UNSPEC, NULL, port);
} }
} else { } else {
for (port = 0; port < USHRT_MAX; port++) { for (port = 0; port < USHRT_MAX; port++) {
@ -3445,7 +3454,7 @@ static int tcp_port_rebind(void *arg)
if ((a->c->v4 && tcp_sock_init_ext[port][V4] == -1) || if ((a->c->v4 && tcp_sock_init_ext[port][V4] == -1) ||
(a->c->v6 && tcp_sock_init_ext[port][V6] == -1)) (a->c->v6 && tcp_sock_init_ext[port][V6] == -1))
tcp_sock_init_one(a->c, 0, port); tcp_sock_init(a->c, 0, AF_UNSPEC, NULL, port);
} }
} }

4
tcp.h
View file

@ -20,7 +20,9 @@ void tcp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events,
const struct timespec *now); const struct timespec *now);
int tcp_tap_handler(struct ctx *c, int af, const void *addr, int tcp_tap_handler(struct ctx *c, int af, const void *addr,
const struct pool *p, const struct timespec *now); const struct pool *p, const struct timespec *now);
int tcp_sock_init(struct ctx *c); void tcp_sock_init(const struct ctx *c, int ns, sa_family_t af,
const void *addr, in_port_t port);
int tcp_init(struct ctx *c);
void tcp_timer(struct ctx *c, const struct timespec *ts); void tcp_timer(struct ctx *c, const struct timespec *ts);
void tcp_defer_handler(struct ctx *c); void tcp_defer_handler(struct ctx *c);

162
udp.c
View file

@ -1000,7 +1000,8 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr,
union udp_epoll_ref uref = { .udp.bound = 1, union udp_epoll_ref uref = { .udp.bound = 1,
.udp.port = src }; .udp.port = src };
s = sock_l4(c, AF_INET, IPPROTO_UDP, src, 0, uref.u32); s = sock_l4(c, AF_INET, IPPROTO_UDP, NULL, src,
uref.u32);
if (s < 0) if (s < 0)
return p->count; return p->count;
@ -1026,7 +1027,7 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr,
.sin6_port = uh->dest, .sin6_port = uh->dest,
.sin6_addr = *(struct in6_addr *)addr, .sin6_addr = *(struct in6_addr *)addr,
}; };
enum bind_type bind_to = BIND_ANY; const void *bind_addr = &in6addr_any;
sa = (struct sockaddr *)&s_in6; sa = (struct sockaddr *)&s_in6;
sl = sizeof(s_in6); sl = sizeof(s_in6);
@ -1043,7 +1044,7 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr,
ntohs(s_in6.sin6_port) == 53) { ntohs(s_in6.sin6_port) == 53) {
s_in6.sin6_addr = c->dns6[0]; s_in6.sin6_addr = c->dns6[0];
} else if (IN6_IS_ADDR_LINKLOCAL(&s_in6.sin6_addr)) { } else if (IN6_IS_ADDR_LINKLOCAL(&s_in6.sin6_addr)) {
bind_to = BIND_LL; bind_addr = &c->addr6_ll;
} }
if (!(s = udp_tap_map[V6][src].sock)) { if (!(s = udp_tap_map[V6][src].sock)) {
@ -1051,7 +1052,7 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr,
.udp.v6 = 1, .udp.v6 = 1,
.udp.port = src }; .udp.port = src };
s = sock_l4(c, AF_INET6, IPPROTO_UDP, src, bind_to, s = sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, src,
uref.u32); uref.u32);
if (s < 0) if (s < 0)
return p->count; return p->count;
@ -1092,6 +1093,96 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr,
return count; return count;
} }
/**
* udp_sock_init() - Initialise listening sockets for a given port
* @c: Execution context
* @ns: In pasta mode, if set, bind with loopback address in namespace
* @af: Address family to select a specific IP version, or AF_UNSPEC
* @addr: Pointer to address for binding, NULL if not configured
* @port: Port, host order
*/
void udp_sock_init(const struct ctx *c, int ns, sa_family_t af,
const void *addr, in_port_t port)
{
union udp_epoll_ref uref = { .udp.bound = 1 };
const void *bind_addr;
int s;
if (ns) {
uref.udp.port = (in_port_t)(port +
udp_port_delta_to_init[port]);
} else {
uref.udp.port = (in_port_t)(port +
udp_port_delta_to_tap[port]);
}
if (af == AF_INET || af == AF_UNSPEC) {
if (!addr && c->mode == MODE_PASTA)
bind_addr = &c->addr4;
else
bind_addr = addr;
uref.udp.v6 = 0;
if (!ns) {
uref.udp.splice = 0;
s = sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, port,
uref.u32);
udp_tap_map[V4][uref.udp.port].sock = s;
}
if (c->mode == MODE_PASTA) {
bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) };
uref.udp.splice = UDP_TO_NS;
sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, port,
uref.u32);
}
if (ns) {
bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) };
uref.udp.splice = UDP_TO_INIT;
sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, port,
uref.u32);
}
}
if (af == AF_INET6 || af == AF_UNSPEC) {
if (!addr && c->mode == MODE_PASTA)
bind_addr = &c->addr6;
else
bind_addr = addr;
uref.udp.v6 = 1;
if (!ns) {
uref.udp.splice = 0;
s = sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, port,
uref.u32);
udp_tap_map[V6][uref.udp.port].sock = s;
}
if (c->mode == MODE_PASTA) {
bind_addr = &in6addr_loopback;
uref.udp.splice = UDP_TO_NS;
sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, port,
uref.u32);
}
if (ns) {
bind_addr = &in6addr_loopback;
uref.udp.splice = UDP_TO_INIT;
sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, port,
uref.u32);
}
}
}
/** /**
* udp_sock_init_ns() - Bind sockets in namespace for inbound connections * udp_sock_init_ns() - Bind sockets in namespace for inbound connections
* @arg: Execution context * @arg: Execution context
@ -1100,8 +1191,6 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr,
*/ */
int udp_sock_init_ns(void *arg) int udp_sock_init_ns(void *arg)
{ {
union udp_epoll_ref uref = { .udp.bound = 1,
.udp.splice = UDP_TO_INIT };
struct ctx *c = (struct ctx *)arg; struct ctx *c = (struct ctx *)arg;
int dst; int dst;
@ -1112,19 +1201,7 @@ int udp_sock_init_ns(void *arg)
if (!bitmap_isset(c->udp.port_to_init, dst)) if (!bitmap_isset(c->udp.port_to_init, dst))
continue; continue;
uref.udp.port = dst + udp_port_delta_to_init[dst]; udp_sock_init(c, 1, AF_UNSPEC, NULL, dst);
if (c->v4) {
uref.udp.v6 = 0;
sock_l4(c, AF_INET, IPPROTO_UDP, dst, BIND_LOOPBACK,
uref.u32);
}
if (c->v6) {
uref.udp.v6 = 1;
sock_l4(c, AF_INET6, IPPROTO_UDP, dst, BIND_LOOPBACK,
uref.u32);
}
} }
return 0; return 0;
@ -1178,54 +1255,13 @@ static void udp_splice_iov_init(void)
} }
/** /**
* udp_sock_init() - Create and bind listening sockets for inbound packets * udp_init() - Initialise per-socket data, and sockets in namespace
* @c: Execution context * @c: Execution context
* *
* Return: 0 on success, -1 on failure * Return: 0
*/ */
int udp_sock_init(const struct ctx *c) int udp_init(const struct ctx *c)
{ {
union udp_epoll_ref uref = { .udp.bound = 1 };
int dst, s;
for (dst = 0; dst < USHRT_MAX; dst++) {
if (!bitmap_isset(c->udp.port_to_tap, dst))
continue;
uref.udp.port = dst + udp_port_delta_to_tap[dst];
if (c->v4) {
uref.udp.splice = 0;
uref.udp.v6 = 0;
s = sock_l4(c, AF_INET, IPPROTO_UDP, dst,
c->mode == MODE_PASTA ? BIND_EXT : BIND_ANY,
uref.u32);
if (s > 0)
udp_tap_map[V4][uref.udp.port].sock = s;
if (c->mode == MODE_PASTA) {
uref.udp.splice = UDP_TO_NS;
sock_l4(c, AF_INET, IPPROTO_UDP, dst,
BIND_LOOPBACK, uref.u32);
}
}
if (c->v6) {
uref.udp.splice = 0;
uref.udp.v6 = 1;
s = sock_l4(c, AF_INET6, IPPROTO_UDP, dst,
c->mode == MODE_PASTA ? BIND_EXT : BIND_ANY,
uref.u32);
if (s > 0)
udp_tap_map[V6][uref.udp.port].sock = s;
if (c->mode == MODE_PASTA) {
uref.udp.splice = UDP_TO_NS;
sock_l4(c, AF_INET6, IPPROTO_UDP, dst,
BIND_LOOPBACK, uref.u32);
}
}
}
if (c->v4) if (c->v4)
udp_sock4_iov_init(); udp_sock4_iov_init();

4
udp.h
View file

@ -12,7 +12,9 @@ void udp_sock_handler(const struct ctx *c, union epoll_ref ref, uint32_t events,
const struct timespec *now); const struct timespec *now);
int udp_tap_handler(struct ctx *c, int af, const void *addr, int udp_tap_handler(struct ctx *c, int af, const void *addr,
const struct pool *p, const struct timespec *now); const struct pool *p, const struct timespec *now);
int udp_sock_init(const struct ctx *c); void udp_sock_init(const struct ctx *c, int ns, sa_family_t af,
const void *addr, in_port_t port);
int udp_init(const struct ctx *c);
void udp_timer(struct ctx *c, const struct timespec *ts); void udp_timer(struct ctx *c, const struct timespec *ts);
void udp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s, void udp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s,
const uint32_t *ip_da); const uint32_t *ip_da);

27
util.c
View file

@ -218,14 +218,14 @@ found:
* @c: Execution context * @c: Execution context
* @af: Address family, AF_INET or AF_INET6 * @af: Address family, AF_INET or AF_INET6
* @proto: Protocol number * @proto: Protocol number
* @bind_addr: Address for binding, NULL for any
* @port: Port, host order * @port: Port, host order
* @bind_type: Type of address for binding
* @data: epoll reference portion for protocol handlers * @data: epoll reference portion for protocol handlers
* *
* Return: newly created socket, -1 on error * Return: newly created socket, -1 on error
*/ */
int sock_l4(const struct ctx *c, int af, uint8_t proto, uint16_t port, int sock_l4(const struct ctx *c, int af, uint8_t proto,
enum bind_type bind_addr, uint32_t data) const void *bind_addr, uint16_t port, uint32_t data)
{ {
union epoll_ref ref = { .r.proto = proto, .r.p.data = data }; union epoll_ref ref = { .r.proto = proto, .r.p.data = data };
struct sockaddr_in addr4 = { struct sockaddr_in addr4 = {
@ -264,23 +264,20 @@ int sock_l4(const struct ctx *c, int af, uint8_t proto, uint16_t port,
ref.r.s = fd; ref.r.s = fd;
if (af == AF_INET) { if (af == AF_INET) {
if (bind_addr == BIND_LOOPBACK) if (bind_addr)
addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr4.sin_addr.s_addr = *(in_addr_t *)bind_addr;
else if (bind_addr == BIND_EXT)
addr4.sin_addr.s_addr = c->addr4;
else else
addr4.sin_addr.s_addr = htonl(INADDR_ANY); addr4.sin_addr.s_addr = htonl(INADDR_ANY);
sa = (const struct sockaddr *)&addr4; sa = (const struct sockaddr *)&addr4;
sl = sizeof(addr4); sl = sizeof(addr4);
} else { } else {
if (bind_addr == BIND_LOOPBACK) { if (bind_addr) {
addr6.sin6_addr = in6addr_loopback; addr6.sin6_addr = *(struct in6_addr *)bind_addr;
} else if (bind_addr == BIND_EXT) {
addr6.sin6_addr = c->addr6; if (!memcmp(bind_addr, &c->addr6_ll,
} else if (bind_addr == BIND_LL) { sizeof(c->addr6_ll)))
addr6.sin6_addr = c->addr6_ll; addr6.sin6_scope_id = c->ifi;
addr6.sin6_scope_id = c->ifi;
} else { } else {
addr6.sin6_addr = in6addr_any; addr6.sin6_addr = in6addr_any;
} }
@ -303,7 +300,7 @@ int sock_l4(const struct ctx *c, int af, uint8_t proto, uint16_t port,
*/ */
if (proto != IPPROTO_ICMP && proto != IPPROTO_ICMPV6) { if (proto != IPPROTO_ICMP && proto != IPPROTO_ICMPV6) {
close(fd); close(fd);
return 0; return -1;
} }
} }

11
util.h
View file

@ -170,13 +170,6 @@ enum {
#include "packet.h" #include "packet.h"
enum bind_type {
BIND_ANY = 0,
BIND_LOOPBACK,
BIND_LL,
BIND_EXT,
};
struct ctx; struct ctx;
struct ipv6hdr { struct ipv6hdr {
@ -213,8 +206,8 @@ void passt_vsyslog(int pri, const char *format, va_list ap);
void __setlogmask(int mask); void __setlogmask(int mask);
char *ipv6_l4hdr(const struct pool *p, int index, size_t offset, uint8_t *proto, char *ipv6_l4hdr(const struct pool *p, int index, size_t offset, uint8_t *proto,
size_t *dlen); size_t *dlen);
int sock_l4(const struct ctx *c, int af, uint8_t proto, uint16_t port, int sock_l4(const struct ctx *c, int af, uint8_t proto,
enum bind_type bind_addr, uint32_t data); const void *bind_addr, uint16_t port, uint32_t data);
void sock_probe_mem(struct ctx *c); void sock_probe_mem(struct ctx *c);
int timespec_diff_ms(const struct timespec *a, const struct timespec *b); int timespec_diff_ms(const struct timespec *a, const struct timespec *b);
void bitmap_set(uint8_t *map, int bit); void bitmap_set(uint8_t *map, int bit);