This series make some substantial simplifications to how we handle the forwarding of "spliced" UDP packets (those that don't go over the tuntap device, but instead are forwarded from one L4 socket to another). This doesn't yet change the existing (arguably broken) assumption that UDP communications are from one port to one port within the pasta namespace, not one to many or many to one. However, the simplifications made here will make it easier to correct that in future. Based on the earlier series for dual stack TCP sockets. David Gibson (11): udp: Also bind() connected ports for "splice" forwarding udp: Separate tracking of inbound and outbound packet flows udp: Always use sendto() rather than send() for forwarding spliced packets udp: Don't connect "forward" sockets for spliced flows udp: Remove the @bound field from union udp_epoll_ref udp: Split splice field in udp_epoll_ref into (mostly) independent bits udp: Don't create double sockets for -U port udp: Re-use fixed bound sockets for packet forwarding when possible udp: Don't explicitly track originating socket for spliced "connections" udp: Update UDP "connection" timestamps in both directions udp: Simplify udp_sock_handler_splice passt.h | 2 + udp.c | 344 ++++++++++++++++++++++---------------------------------- udp.h | 16 ++- 3 files changed, 144 insertions(+), 218 deletions(-) -- 2.38.1
pasta handles "spliced" port forwarding by resending datagrams received on a bound socket in the init namespace to a connected socket in the guest namespace. This means there are actually three ports associated with each "connection". First there's the source and destination ports of the originating datagram. That's also the destination port of the forwarded datagram, but the source port of the forwarded datagram is the kernel allocated bound address of the connected socket. However, by bind()ing as well as connect()ing the forwarding socket we can choose the source port of the forwarded datagrams. By choosing it to match the original source port we remove that surprising third port number and no longer need to store port numbers in struct udp_splice_port. As a bonus this means that the recipient of the packets will see the original source port if they call getpeername(). This rarely matters, but it can't hurt. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 84 +++++++++++++++++++++++------------------------------------ 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/udp.c b/udp.c index 0aa6308..a025a48 100644 --- a/udp.c +++ b/udp.c @@ -51,20 +51,19 @@ * - send packet to udp4_splice_map[5000].ns_conn_sock * - otherwise: * - create new socket udp_splice_map[V4][5000].ns_conn_sock + * - bind in namespace to 127.0.0.1:5000 * - connect in namespace to 127.0.0.1:80 (note: this destination port * might be remapped to another port instead) - * - get source port of new connected socket (10000) with getsockname() - * - add to epoll with reference: index = 10000, splice: UDP_BACK_TO_INIT - * - set udp_splice_map[V4][10000].init_bound_sock to s - * - set udp_splice_map[V4][10000].init_dst_port to 5000 + * - add to epoll with reference: index = 5000, splice: UDP_BACK_TO_INIT + * - set udp_splice_map[V4][5000].init_bound_sock to s * - update udp_splice_map[V4][5000].ns_conn_ts with current time * - * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:10000 in namespace from - * connected socket s, having epoll reference: index = 10000, + * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:5000 in namespace from + * connected socket s, having epoll reference: index = 5000, * splice = UDP_BACK_TO_INIT - * - if udp_splice_map[V4][10000].init_bound_sock: - * - send to udp_splice_map[V4][10000].init_bound_sock, with destination - * port udp_splice_map[V4][10000].init_dst_port (5000) + * - if udp_splice_map[V4][5000].init_bound_sock: + * - send to udp_splice_map[V4][5000].init_bound_sock, with destination + * port 5000 * - otherwise, discard * * - from namespace to init: @@ -75,19 +74,18 @@ * - send packet to udp4_splice_map[2000].init_conn_sock * - otherwise: * - create new socket udp_splice_map[V4][2000].init_conn_sock + * - bind in init to 127.0.0.1:2000 * - connect in init to 127.0.0.1:22 (note: this destination port * might be remapped to another port instead) - * - get source port of new connected socket (4000) with getsockname() - * - add to epoll with reference: index = 4000, splice = UDP_BACK_TO_NS - * - set udp_splice_map[V4][4000].ns_bound_sock to s - * - set udp_splice_map[V4][4000].ns_dst_port to 2000 - * - update udp_splice_map[V4][4000].init_conn_ts with current time + * - add to epoll with reference: index = 2000, splice = UDP_BACK_TO_NS + * - set udp_splice_map[V4][2000].ns_bound_sock to s + * - update udp_splice_map[V4][2000].init_conn_ts with current time * - * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:4000 in init from connected - * socket s, having epoll reference: index = 4000, splice = UDP_BACK_TO_NS - * - if udp_splice_map[V4][4000].ns_bound_sock: - * - send to udp_splice_map[V4][4000].ns_bound_sock, with destination port - * udp_splice_map[4000].ns_dst_port (2000) + * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:2000 in init from connected + * socket s, having epoll reference: index = 2000, splice = UDP_BACK_TO_NS + * - if udp_splice_map[V4][2000].ns_bound_sock: + * - send to udp_splice_map[V4][2000].ns_bound_sock, with destination port + * 2000 * - otherwise, discard */ @@ -145,8 +143,6 @@ struct udp_tap_port { * @init_conn_sock: Socket connected in init for namespace source port * @ns_conn_ts: Timestamp of activity for socket connected in namespace * @init_conn_ts: Timestamp of activity for socket connceted in init - * @ns_dst_port: Destination port in namespace for init source port - * @init_dst_port: Destination port in init for namespace source port * @ns_bound_sock: Bound socket in namespace for this source port in init * @init_bound_sock: Bound socket in init for this source port in namespace */ @@ -157,9 +153,6 @@ struct udp_splice_port { time_t ns_conn_ts; time_t init_conn_ts; - in_port_t ns_dst_port; - in_port_t init_dst_port; - int ns_bound_sock; int init_bound_sock; }; @@ -425,11 +418,10 @@ int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, { struct epoll_event ev = { .events = EPOLLIN | EPOLLRDHUP | EPOLLHUP }; union epoll_ref ref = { .r.proto = IPPROTO_UDP, - .r.p.udp.udp = { .splice = splice, .v6 = v6 } + .r.p.udp.udp = { .splice = splice, .v6 = v6, + .port = src } }; - struct sockaddr_storage sa; - struct udp_splice_port *sp; - socklen_t sl = sizeof(sa); + struct udp_splice_port *sp = &udp_splice_map[v6 ? V6 : V4][src]; int s; s = socket(v6 ? AF_INET6 : AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, @@ -448,46 +440,34 @@ int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, if (v6) { struct sockaddr_in6 addr6 = { .sin6_family = AF_INET6, - .sin6_port = htons(dst), + .sin6_port = htons(src), .sin6_addr = IN6ADDR_LOOPBACK_INIT, }; + if (bind(s, (struct sockaddr *)&addr6, sizeof(addr6))) + goto fail; + addr6.sin6_port = htons(dst); if (connect(s, (struct sockaddr *)&addr6, sizeof(addr6))) goto fail; } else { struct sockaddr_in addr4 = { .sin_family = AF_INET, - .sin_port = htons(dst), + .sin_port = htons(src), .sin_addr = { .s_addr = htonl(INADDR_LOOPBACK) }, }; + if (bind(s, (struct sockaddr *)&addr4, sizeof(addr4))) + goto fail; + addr4.sin_port = htons(dst); if (connect(s, (struct sockaddr *)&addr4, sizeof(addr4))) goto fail; } - if (getsockname(s, (struct sockaddr *)&sa, &sl)) - goto fail; - - if (v6) { - struct sockaddr_in6 sa6; - - memcpy(&sa6, &sa, sizeof(sa6)); - ref.r.p.udp.udp.port = ntohs(sa6.sin6_port); - } else { - struct sockaddr_in sa4; - - memcpy(&sa4, &sa, sizeof(sa4)); - ref.r.p.udp.udp.port = ntohs(sa4.sin_port); - } - - sp = &udp_splice_map[v6 ? V6 : V4][ref.r.p.udp.udp.port]; if (splice == UDP_BACK_TO_INIT) { sp->init_bound_sock = bound_sock; - sp->init_dst_port = src; - udp_splice_map[v6 ? V6 : V4][src].ns_conn_sock = s; + sp->ns_conn_sock = s; bitmap_set(udp_act[v6 ? V6 : V4][UDP_ACT_NS_CONN], src); } else if (splice == UDP_BACK_TO_NS) { sp->ns_bound_sock = bound_sock; - sp->ns_dst_port = src; - udp_splice_map[v6 ? V6 : V4][src].init_conn_sock = s; + sp->init_conn_sock = s; bitmap_set(udp_act[v6 ? V6 : V4][UDP_ACT_INIT_CONN], src); } @@ -591,7 +571,7 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, if (!(s = udp_splice_map[v6][dst].init_bound_sock)) return; - send_dst = udp_splice_map[v6][dst].init_dst_port; + send_dst = dst; break; case UDP_TO_INIT: src += c->udp.fwd_in.rdelta[src]; @@ -608,7 +588,7 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, if (!(s = udp_splice_map[v6][dst].ns_bound_sock)) return; - send_dst = udp_splice_map[v6][dst].ns_dst_port; + send_dst = dst; break; default: return; -- 2.38.1
Each entry udp_splice_map[v6][N] keeps information about two essentially unrelated packet flows. @ns_conn_sock, @ns_conn_ts and @init_bound_sock track a packet flow from port N in the host init namespace to some other port in the pasta namespace (the one @ns_conn_sock is connected to). @init_conn_sock, @init_conn_ts and @ns_bound_sock track packet flow from port N in the pasta namespace to some other port in the host init namespace (the one @init_conn_sock is connected to). Split udp_splice_map[][] into two separate tables for the two directions. Each entry in each table is a 'struct udp_splice_flow' with @orig_sock (previously the bound socket), @target_sock (previously the connected socket) and @ts (the timeout for the target socket). Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 111 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/udp.c b/udp.c index a025a48..4caf73e 100644 --- a/udp.c +++ b/udp.c @@ -47,44 +47,44 @@ * * - forward direction: 127.0.0.1:5000 -> 127.0.0.1:80 in init from bound * socket s, with epoll reference: index = 80, splice = UDP_TO_NS - * - if udp_splice_map[V4][5000].ns_conn_sock: - * - send packet to udp4_splice_map[5000].ns_conn_sock + * - if udp_splice_to_ns[V4][5000].target_sock: + * - send packet to udp_splice_to_ns[V4][5000].target_sock * - otherwise: - * - create new socket udp_splice_map[V4][5000].ns_conn_sock + * - create new socket udp_splice_to_ns[V4][5000].target_sock * - bind in namespace to 127.0.0.1:5000 * - connect in namespace to 127.0.0.1:80 (note: this destination port * might be remapped to another port instead) * - add to epoll with reference: index = 5000, splice: UDP_BACK_TO_INIT - * - set udp_splice_map[V4][5000].init_bound_sock to s - * - update udp_splice_map[V4][5000].ns_conn_ts with current time + * - set udp_splice_to_ns[V4][5000].orig_sock to s + * - update udp_splice_to_ns[V4][5000].ts with current time * * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:5000 in namespace from * connected socket s, having epoll reference: index = 5000, * splice = UDP_BACK_TO_INIT - * - if udp_splice_map[V4][5000].init_bound_sock: - * - send to udp_splice_map[V4][5000].init_bound_sock, with destination - * port 5000 + * - if udp_splice_to_ns[V4][5000].orig_sock: + * - send to udp_splice_to_ns[V4][5000].orig_sock, with destination port + * 5000 * - otherwise, discard * * - from namespace to init: * * - forward direction: 127.0.0.1:2000 -> 127.0.0.1:22 in namespace from bound * socket s, with epoll reference: index = 22, splice = UDP_TO_INIT - * - if udp4_splice_map[V4][2000].init_conn_sock: - * - send packet to udp4_splice_map[2000].init_conn_sock + * - if udp4_splice_to_init[V4][2000].target_sock: + * - send packet to udp_splice_to_init[V4][2000].target_sock * - otherwise: - * - create new socket udp_splice_map[V4][2000].init_conn_sock + * - create new socket udp_splice_to_init[V4][2000].target_sock * - bind in init to 127.0.0.1:2000 * - connect in init to 127.0.0.1:22 (note: this destination port * might be remapped to another port instead) * - add to epoll with reference: index = 2000, splice = UDP_BACK_TO_NS - * - set udp_splice_map[V4][2000].ns_bound_sock to s - * - update udp_splice_map[V4][2000].init_conn_ts with current time + * - set udp_splice_to_init[V4][2000].orig_sock to s + * - update udp_splice_to_init[V4][2000].ts with current time * * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:2000 in init from connected * socket s, having epoll reference: index = 2000, splice = UDP_BACK_TO_NS - * - if udp_splice_map[V4][2000].ns_bound_sock: - * - send to udp_splice_map[V4][2000].ns_bound_sock, with destination port + * - if udp_splice_to_init[V4][2000].orig_sock: + * - send to udp_splice_to_init[V4][2000].orig_sock, with destination port * 2000 * - otherwise, discard */ @@ -138,28 +138,26 @@ struct udp_tap_port { }; /** - * struct udp_splice_port - Source port tracking for traffic between namespaces - * @ns_conn_sock: Socket connected in namespace for init source port - * @init_conn_sock: Socket connected in init for namespace source port - * @ns_conn_ts: Timestamp of activity for socket connected in namespace - * @init_conn_ts: Timestamp of activity for socket connceted in init - * @ns_bound_sock: Bound socket in namespace for this source port in init - * @init_bound_sock: Bound socket in init for this source port in namespace + * struct udp_splice_flow - Spliced "connection" + * @orig_sock: Originating socket, bound to dest port in source ns of + * originating datagram + * @target_sock: Target socket, bound to source port of originating + * datagram in dest ns, connected to dest port of + * originating datagram in dest ns + * @ts: Activity timestamp */ -struct udp_splice_port { - int ns_conn_sock; - int init_conn_sock; - - time_t ns_conn_ts; - time_t init_conn_ts; - - int ns_bound_sock; - int init_bound_sock; +struct udp_splice_flow { + int orig_sock; + int target_sock; + time_t ts; }; /* Port tracking, arrays indexed by packet source port (host order) */ static struct udp_tap_port udp_tap_map [IP_VERSIONS][NUM_PORTS]; -static struct udp_splice_port udp_splice_map [IP_VERSIONS][NUM_PORTS]; + +/* Spliced "connections" indexed by originating source port (host order) */ +static struct udp_splice_flow udp_splice_to_ns [IP_VERSIONS][NUM_PORTS]; +static struct udp_splice_flow udp_splice_to_init[IP_VERSIONS][NUM_PORTS]; enum udp_act_type { UDP_ACT_TAP, @@ -421,8 +419,17 @@ int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, .r.p.udp.udp = { .splice = splice, .v6 = v6, .port = src } }; - struct udp_splice_port *sp = &udp_splice_map[v6 ? V6 : V4][src]; + struct udp_splice_flow *flow; int s; + int act; + + if (splice == UDP_BACK_TO_INIT) { + flow = &udp_splice_to_ns[v6 ? V6 : V4][src]; + act = UDP_ACT_NS_CONN; + } else { + flow = &udp_splice_to_init[v6 ? V6 : V4][src]; + act = UDP_ACT_INIT_CONN; + } s = socket(v6 ? AF_INET6 : AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); @@ -461,15 +468,9 @@ int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, goto fail; } - if (splice == UDP_BACK_TO_INIT) { - sp->init_bound_sock = bound_sock; - sp->ns_conn_sock = s; - bitmap_set(udp_act[v6 ? V6 : V4][UDP_ACT_NS_CONN], src); - } else if (splice == UDP_BACK_TO_NS) { - sp->ns_bound_sock = bound_sock; - sp->init_conn_sock = s; - bitmap_set(udp_act[v6 ? V6 : V4][UDP_ACT_INIT_CONN], src); - } + flow->orig_sock = bound_sock; + flow->target_sock = s; + bitmap_set(udp_act[v6 ? V6 : V4][act], src); ev.data.u64 = ref.u64; epoll_ctl(c->epollfd, EPOLL_CTL_ADD, s, &ev); @@ -556,7 +557,7 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, case UDP_TO_NS: src += c->udp.fwd_out.rdelta[src]; - if (!(s = udp_splice_map[v6][src].ns_conn_sock)) { + if (!(s = udp_splice_to_ns[v6][src].target_sock)) { struct udp_splice_connect_ns_arg arg = { c, v6, ref.r.s, src, dst, -1, }; @@ -565,10 +566,10 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, if ((s = arg.s) < 0) return; } - udp_splice_map[v6][src].ns_conn_ts = now->tv_sec; + udp_splice_to_ns[v6][src].ts = now->tv_sec; break; case UDP_BACK_TO_INIT: - if (!(s = udp_splice_map[v6][dst].init_bound_sock)) + if (!(s = udp_splice_to_ns[v6][dst].orig_sock)) return; send_dst = dst; @@ -576,16 +577,16 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, case UDP_TO_INIT: src += c->udp.fwd_in.rdelta[src]; - if (!(s = udp_splice_map[v6][src].init_conn_sock)) { + if (!(s = udp_splice_to_init[v6][src].target_sock)) { s = udp_splice_connect(c, v6, ref.r.s, src, dst, UDP_BACK_TO_NS); if (s < 0) return; } - udp_splice_map[v6][src].init_conn_ts = now->tv_sec; + udp_splice_to_init[v6][src].ts = now->tv_sec; break; case UDP_BACK_TO_NS: - if (!(s = udp_splice_map[v6][dst].ns_bound_sock)) + if (!(s = udp_splice_to_init[v6][dst].orig_sock)) return; send_dst = dst; @@ -1286,7 +1287,7 @@ int udp_init(struct ctx *c) static void udp_timer_one(struct ctx *c, int v6, enum udp_act_type type, in_port_t port, const struct timespec *ts) { - struct udp_splice_port *sp; + struct udp_splice_flow *flow; struct udp_tap_port *tp; int s = -1; @@ -1301,17 +1302,17 @@ static void udp_timer_one(struct ctx *c, int v6, enum udp_act_type type, break; case UDP_ACT_INIT_CONN: - sp = &udp_splice_map[v6 ? V6 : V4][port]; + flow = &udp_splice_to_init[v6 ? V6 : V4][port]; - if (ts->tv_sec - sp->init_conn_ts > UDP_CONN_TIMEOUT) - s = sp->init_conn_sock; + if (ts->tv_sec - flow->ts > UDP_CONN_TIMEOUT) + s = flow->target_sock; break; case UDP_ACT_NS_CONN: - sp = &udp_splice_map[v6 ? V6 : V4][port]; + flow = &udp_splice_to_ns[v6 ? V6 : V4][port]; - if (ts->tv_sec - sp->ns_conn_ts > UDP_CONN_TIMEOUT) - s = sp->ns_conn_sock; + if (ts->tv_sec - flow->ts > UDP_CONN_TIMEOUT) + s = flow->target_sock; break; default: -- 2.38.1
udp_sock_handler_splice() has two different ways of sending out packets once it has determined the correct destination socket. For the originating sockets (which are not connected) it uses sendto() to specify a specific address. For the forward socket (which is connected) we use send(). However we know the correct destination address even for the forward socket we do also know the correct destination address. We can use this to use sendto() instead of send(), removing the need for two different paths and some staging data structures. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 40 +++++++--------------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/udp.c b/udp.c index 4caf73e..1e78da5 100644 --- a/udp.c +++ b/udp.c @@ -48,7 +48,8 @@ * - forward direction: 127.0.0.1:5000 -> 127.0.0.1:80 in init from bound * socket s, with epoll reference: index = 80, splice = UDP_TO_NS * - if udp_splice_to_ns[V4][5000].target_sock: - * - send packet to udp_splice_to_ns[V4][5000].target_sock + * - send packet to udp_splice_to_ns[V4][5000].target_sock, with + * destination port 80 * - otherwise: * - create new socket udp_splice_to_ns[V4][5000].target_sock * - bind in namespace to 127.0.0.1:5000 @@ -71,7 +72,8 @@ * - forward direction: 127.0.0.1:2000 -> 127.0.0.1:22 in namespace from bound * socket s, with epoll reference: index = 22, splice = UDP_TO_INIT * - if udp4_splice_to_init[V4][2000].target_sock: - * - send packet to udp_splice_to_init[V4][2000].target_sock + * - send packet to udp_splice_to_init[V4][2000].target_sock, with + * destination port 22 * - otherwise: * - create new socket udp_splice_to_init[V4][2000].target_sock * - bind in init to 127.0.0.1:2000 @@ -244,9 +246,6 @@ static struct mmsghdr udp6_l2_mh_tap [UDP_TAP_FRAMES_MEM]; static struct iovec udp_iov_recv [UDP_SPLICE_FRAMES]; static struct mmsghdr udp_mmh_recv [UDP_SPLICE_FRAMES]; -static struct iovec udp_iov_send [UDP_SPLICE_FRAMES]; -static struct mmsghdr udp_mmh_send [UDP_SPLICE_FRAMES]; - static struct iovec udp_iov_sendto [UDP_SPLICE_FRAMES]; static struct mmsghdr udp_mmh_sendto [UDP_SPLICE_FRAMES]; @@ -530,7 +529,7 @@ static int udp_splice_connect_ns(void *arg) static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, uint32_t events, const struct timespec *now) { - in_port_t src, dst = ref.r.p.udp.udp.port, send_dst = 0; + in_port_t src, dst = ref.r.p.udp.udp.port; struct msghdr *mh = &udp_mmh_recv[0].msg_hdr; struct sockaddr_storage *sa_s = mh->msg_name; int s, v6 = ref.r.p.udp.udp.v6, n, i; @@ -571,8 +570,6 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, case UDP_BACK_TO_INIT: if (!(s = udp_splice_to_ns[v6][dst].orig_sock)) return; - - send_dst = dst; break; case UDP_TO_INIT: src += c->udp.fwd_in.rdelta[src]; @@ -588,25 +585,11 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, case UDP_BACK_TO_NS: if (!(s = udp_splice_to_init[v6][dst].orig_sock)) return; - - send_dst = dst; break; default: return; } - if (ref.r.p.udp.udp.splice == UDP_TO_NS || - ref.r.p.udp.udp.splice == UDP_TO_INIT) { - for (i = 0; i < n; i++) { - struct msghdr *mh_s = &udp_mmh_send[i].msg_hdr; - - mh_s->msg_iov->iov_len = udp_mmh_recv[i].msg_len; - } - - sendmmsg(s, udp_mmh_send, n, MSG_NOSIGNAL); - return; - } - for (i = 0; i < n; i++) { struct msghdr *mh_s = &udp_mmh_sendto[i].msg_hdr; @@ -618,7 +601,7 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, ((struct sockaddr_in6) { .sin6_family = AF_INET6, .sin6_addr = IN6ADDR_LOOPBACK_INIT, - .sin6_port = htons(send_dst), + .sin6_port = htons(dst), .sin6_scope_id = 0, }); } else { @@ -626,7 +609,7 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, ((struct sockaddr_in) { .sin_family = AF_INET, .sin_addr = { .s_addr = htonl(INADDR_LOOPBACK) }, - .sin_port = htons(send_dst), + .sin_port = htons(dst), .sin_zero = { 0 }, }); } @@ -1229,15 +1212,6 @@ static void udp_splice_iov_init(void) iov->iov_len = sizeof(udp_splice_buf[i]); } - for (i = 0, h = udp_mmh_send; i < UDP_SPLICE_FRAMES; i++, h++) { - struct msghdr *mh = &h->msg_hdr; - - mh->msg_iov = &udp_iov_send[i]; - mh->msg_iovlen = 1; - } - for (i = 0, iov = udp_iov_send; i < UDP_SPLICE_FRAMES; i++, iov++) - iov->iov_base = udp_splice_buf[i]; - for (i = 0, h = udp_mmh_sendto; i < UDP_SPLICE_FRAMES; i++, h++) { struct msghdr *mh = &h->msg_hdr; -- 2.38.1
Currently we connect() the socket we use to forward spliced UDP flows. However, we now only ever use sendto() rather than send() on this socket so there's not actually any need to connect it. Don't do so. Rename a number of things that referred to "connect" or "conn" since that would now be misleading. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 85 ++++++++++++++++++++++++----------------------------------- 1 file changed, 35 insertions(+), 50 deletions(-) diff --git a/udp.c b/udp.c index 1e78da5..ed095ec 100644 --- a/udp.c +++ b/udp.c @@ -45,23 +45,20 @@ * * - from init to namespace: * - * - forward direction: 127.0.0.1:5000 -> 127.0.0.1:80 in init from bound - * socket s, with epoll reference: index = 80, splice = UDP_TO_NS + * - forward direction: 127.0.0.1:5000 -> 127.0.0.1:80 in init from socket s, + * with epoll reference: index = 80, splice = UDP_TO_NS * - if udp_splice_to_ns[V4][5000].target_sock: * - send packet to udp_splice_to_ns[V4][5000].target_sock, with * destination port 80 * - otherwise: * - create new socket udp_splice_to_ns[V4][5000].target_sock * - bind in namespace to 127.0.0.1:5000 - * - connect in namespace to 127.0.0.1:80 (note: this destination port - * might be remapped to another port instead) * - add to epoll with reference: index = 5000, splice: UDP_BACK_TO_INIT * - set udp_splice_to_ns[V4][5000].orig_sock to s * - update udp_splice_to_ns[V4][5000].ts with current time * - * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:5000 in namespace from - * connected socket s, having epoll reference: index = 5000, - * splice = UDP_BACK_TO_INIT + * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:5000 in namespace socket s, + * having epoll reference: index = 5000, splice = UDP_BACK_TO_INIT * - if udp_splice_to_ns[V4][5000].orig_sock: * - send to udp_splice_to_ns[V4][5000].orig_sock, with destination port * 5000 @@ -69,7 +66,7 @@ * * - from namespace to init: * - * - forward direction: 127.0.0.1:2000 -> 127.0.0.1:22 in namespace from bound + * - forward direction: 127.0.0.1:2000 -> 127.0.0.1:22 in namespace from * socket s, with epoll reference: index = 22, splice = UDP_TO_INIT * - if udp4_splice_to_init[V4][2000].target_sock: * - send packet to udp_splice_to_init[V4][2000].target_sock, with @@ -77,14 +74,12 @@ * - otherwise: * - create new socket udp_splice_to_init[V4][2000].target_sock * - bind in init to 127.0.0.1:2000 - * - connect in init to 127.0.0.1:22 (note: this destination port - * might be remapped to another port instead) * - add to epoll with reference: index = 2000, splice = UDP_BACK_TO_NS * - set udp_splice_to_init[V4][2000].orig_sock to s * - update udp_splice_to_init[V4][2000].ts with current time * - * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:2000 in init from connected - * socket s, having epoll reference: index = 2000, splice = UDP_BACK_TO_NS + * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:2000 in init from socket s, + * having epoll reference: index = 2000, splice = UDP_BACK_TO_NS * - if udp_splice_to_init[V4][2000].orig_sock: * - send to udp_splice_to_init[V4][2000].orig_sock, with destination port * 2000 @@ -144,8 +139,7 @@ struct udp_tap_port { * @orig_sock: Originating socket, bound to dest port in source ns of * originating datagram * @target_sock: Target socket, bound to source port of originating - * datagram in dest ns, connected to dest port of - * originating datagram in dest ns + * datagram in dest ns * @ts: Activity timestamp */ struct udp_splice_flow { @@ -163,8 +157,8 @@ static struct udp_splice_flow udp_splice_to_init[IP_VERSIONS][NUM_PORTS]; enum udp_act_type { UDP_ACT_TAP, - UDP_ACT_NS_CONN, - UDP_ACT_INIT_CONN, + UDP_ACT_SPLICE_NS, + UDP_ACT_SPLICE_INIT, UDP_ACT_TYPE_MAX, }; @@ -398,20 +392,19 @@ static void udp_sock6_iov_init(void) } /** - * udp_splice_connect() - Create and connect socket for "spliced" binding + * udp_splice_new() - Create and prepare socket for "spliced" binding * @c: Execution context - * @v6: Set for IPv6 connections + * @v6: Set for IPv6 sockets * @bound_sock: Originating bound socket * @src: Source port of original connection, host order - * @dst: Destination port of original connection, host order * @splice: UDP_BACK_TO_INIT from init, UDP_BACK_TO_NS from namespace * - * Return: connected socket, negative error code on failure + * Return: Prepared socket, negative error code on failure * * #syscalls:pasta getsockname */ -int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, - in_port_t src, in_port_t dst, int splice) +int udp_splice_new(const struct ctx *c, int v6, int bound_sock, in_port_t src, + int splice) { struct epoll_event ev = { .events = EPOLLIN | EPOLLRDHUP | EPOLLHUP }; union epoll_ref ref = { .r.proto = IPPROTO_UDP, @@ -424,10 +417,10 @@ int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, if (splice == UDP_BACK_TO_INIT) { flow = &udp_splice_to_ns[v6 ? V6 : V4][src]; - act = UDP_ACT_NS_CONN; + act = UDP_ACT_SPLICE_NS; } else { flow = &udp_splice_to_init[v6 ? V6 : V4][src]; - act = UDP_ACT_INIT_CONN; + act = UDP_ACT_SPLICE_INIT; } s = socket(v6 ? AF_INET6 : AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, @@ -451,9 +444,6 @@ int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, }; if (bind(s, (struct sockaddr *)&addr6, sizeof(addr6))) goto fail; - addr6.sin6_port = htons(dst); - if (connect(s, (struct sockaddr *)&addr6, sizeof(addr6))) - goto fail; } else { struct sockaddr_in addr4 = { .sin_family = AF_INET, @@ -462,9 +452,6 @@ int udp_splice_connect(const struct ctx *c, int v6, int bound_sock, }; if (bind(s, (struct sockaddr *)&addr4, sizeof(addr4))) goto fail; - addr4.sin_port = htons(dst); - if (connect(s, (struct sockaddr *)&addr4, sizeof(addr4))) - goto fail; } flow->orig_sock = bound_sock; @@ -481,40 +468,39 @@ fail: } /** - * struct udp_splice_connect_ns_arg - Arguments for udp_splice_connect_ns() + * struct udp_splice_new_ns_arg - Arguments for udp_splice_new_ns() * @c: Execution context - * @v6: Set for inbound IPv6 connection + * @v6: Set for IPv6 * @bound_sock: Originating bound socket - * @src: Source port of original connection, host order - * @dst: Destination port of original connection, host order + * @src: Source port of originating datagram, host order + * @dst: Destination port of originating datagram, host order * @s: Newly created socket or negative error code */ -struct udp_splice_connect_ns_arg { +struct udp_splice_new_ns_arg { const struct ctx *c; int v6; int bound_sock; in_port_t src; - in_port_t dst; int s; }; /** - * udp_splice_connect_ns() - Enter namespace and call udp_splice_connect() - * @arg: See struct udp_splice_connect_ns_arg + * udp_splice_new_ns() - Enter namespace and call udp_splice_new() + * @arg: See struct udp_splice_new_ns_arg * * Return: 0 */ -static int udp_splice_connect_ns(void *arg) +static int udp_splice_new_ns(void *arg) { - struct udp_splice_connect_ns_arg *a; + struct udp_splice_new_ns_arg *a; - a = (struct udp_splice_connect_ns_arg *)arg; + a = (struct udp_splice_new_ns_arg *)arg; if (ns_enter(a->c)) return 0; - a->s = udp_splice_connect(a->c, a->v6, a->bound_sock, a->src, a->dst, - UDP_BACK_TO_INIT); + a->s = udp_splice_new(a->c, a->v6, a->bound_sock, a->src, + UDP_BACK_TO_INIT); return 0; } @@ -557,11 +543,11 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, src += c->udp.fwd_out.rdelta[src]; if (!(s = udp_splice_to_ns[v6][src].target_sock)) { - struct udp_splice_connect_ns_arg arg = { - c, v6, ref.r.s, src, dst, -1, + struct udp_splice_new_ns_arg arg = { + c, v6, ref.r.s, src, -1, }; - NS_CALL(udp_splice_connect_ns, &arg); + NS_CALL(udp_splice_new_ns, &arg); if ((s = arg.s) < 0) return; } @@ -575,8 +561,7 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, src += c->udp.fwd_in.rdelta[src]; if (!(s = udp_splice_to_init[v6][src].target_sock)) { - s = udp_splice_connect(c, v6, ref.r.s, src, dst, - UDP_BACK_TO_NS); + s = udp_splice_new(c, v6, ref.r.s, src, UDP_BACK_TO_NS); if (s < 0) return; } @@ -1275,14 +1260,14 @@ static void udp_timer_one(struct ctx *c, int v6, enum udp_act_type type, } break; - case UDP_ACT_INIT_CONN: + case UDP_ACT_SPLICE_INIT: flow = &udp_splice_to_init[v6 ? V6 : V4][port]; if (ts->tv_sec - flow->ts > UDP_CONN_TIMEOUT) s = flow->target_sock; break; - case UDP_ACT_NS_CONN: + case UDP_ACT_SPLICE_NS: flow = &udp_splice_to_ns[v6 ? V6 : V4][port]; if (ts->tv_sec - flow->ts > UDP_CONN_TIMEOUT) -- 2.38.1
We set this field, but nothing ever checked it. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 8 +++----- udp.h | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/udp.c b/udp.c index ed095ec..2c5e4e9 100644 --- a/udp.c +++ b/udp.c @@ -952,8 +952,7 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr, sl = sizeof(s_in); if (!(s = udp_tap_map[V4][src].sock)) { - union udp_epoll_ref uref = { .udp.bound = 1, - .udp.port = src }; + union udp_epoll_ref uref = { .udp.port = src }; s = sock_l4(c, AF_INET, IPPROTO_UDP, NULL, NULL, src, uref.u32); @@ -1005,8 +1004,7 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr, } if (!(s = udp_tap_map[V6][src].sock)) { - union udp_epoll_ref uref = { .udp.bound = 1, - .udp.v6 = 1, + union udp_epoll_ref uref = { .udp.v6 = 1, .udp.port = src }; s = sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, NULL, @@ -1069,7 +1067,7 @@ int udp_tap_handler(struct ctx *c, int af, const void *addr, void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, const void *addr, const char *ifname, in_port_t port) { - union udp_epoll_ref uref = { .udp.bound = 1 }; + union udp_epoll_ref uref = { .u32 = 0 }; const void *bind_addr; int s; diff --git a/udp.h b/udp.h index 2ac8610..43bd28a 100644 --- a/udp.h +++ b/udp.h @@ -29,8 +29,7 @@ void udp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s, */ union udp_epoll_ref { struct { - uint32_t bound:1, - splice:3, + uint32_t splice:3, #define UDP_TO_NS 1 #define UDP_TO_INIT 2 #define UDP_BACK_TO_NS 3 -- 2.38.1
The @splice field in union udp_epoll_ref can have a number of values for different types of "spliced" packet flows. Split it into several single bit fields with more or less independent meanings. The new @splice field is just a boolean indicating whether the socket is associated with a spliced flow, making it identical to the @splice fiend in tcp_epoll_ref. The new bit @orig, indicates whether this is a socket which can originate new udp packet flows (created with -u or -U) or a socket created on the fly to handle reply socket. @ns indicates whether the socket lives in the init namespace or the pasta namespace. Making these bits more orthogonal to each other will simplify some future cleanups. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- passt.h | 2 ++ udp.c | 53 ++++++++++++++++++++++++++--------------------------- udp.h | 15 +++++++-------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/passt.h b/passt.h index 6649c0a..01d8893 100644 --- a/passt.h +++ b/passt.h @@ -31,6 +31,8 @@ struct tap_l4_msg { union epoll_ref; +#include <stdbool.h> + #include "packet.h" #include "icmp.h" #include "port_fwd.h" diff --git a/udp.c b/udp.c index 2c5e4e9..4c87e66 100644 --- a/udp.c +++ b/udp.c @@ -46,19 +46,20 @@ * - from init to namespace: * * - forward direction: 127.0.0.1:5000 -> 127.0.0.1:80 in init from socket s, - * with epoll reference: index = 80, splice = UDP_TO_NS + * with epoll reference: index = 80, splice = 1, orig = 1, ns = 0 * - if udp_splice_to_ns[V4][5000].target_sock: * - send packet to udp_splice_to_ns[V4][5000].target_sock, with * destination port 80 * - otherwise: * - create new socket udp_splice_to_ns[V4][5000].target_sock * - bind in namespace to 127.0.0.1:5000 - * - add to epoll with reference: index = 5000, splice: UDP_BACK_TO_INIT + * - add to epoll with reference: index = 5000, splice = 1, orig = 0, + * ns = 1 * - set udp_splice_to_ns[V4][5000].orig_sock to s * - update udp_splice_to_ns[V4][5000].ts with current time * * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:5000 in namespace socket s, - * having epoll reference: index = 5000, splice = UDP_BACK_TO_INIT + * having epoll reference: index = 5000, splice = 1, orig = 0, ns = 1 * - if udp_splice_to_ns[V4][5000].orig_sock: * - send to udp_splice_to_ns[V4][5000].orig_sock, with destination port * 5000 @@ -67,19 +68,20 @@ * - from namespace to init: * * - forward direction: 127.0.0.1:2000 -> 127.0.0.1:22 in namespace from - * socket s, with epoll reference: index = 22, splice = UDP_TO_INIT + * socket s, with epoll reference: index = 22, splice = 1, orig = 1, ns = 1 * - if udp4_splice_to_init[V4][2000].target_sock: * - send packet to udp_splice_to_init[V4][2000].target_sock, with * destination port 22 * - otherwise: * - create new socket udp_splice_to_init[V4][2000].target_sock * - bind in init to 127.0.0.1:2000 - * - add to epoll with reference: index = 2000, splice = UDP_BACK_TO_NS + * - add to epoll with reference: index = 2000, splice = 1, orig = 0, + * ns = 0 * - set udp_splice_to_init[V4][2000].orig_sock to s * - update udp_splice_to_init[V4][2000].ts with current time * * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:2000 in init from socket s, - * having epoll reference: index = 2000, splice = UDP_BACK_TO_NS + * having epoll reference: index = 2000, splice = 1, orig = 0, ns = 0 * - if udp_splice_to_init[V4][2000].orig_sock: * - send to udp_splice_to_init[V4][2000].orig_sock, with destination port * 2000 @@ -404,18 +406,18 @@ static void udp_sock6_iov_init(void) * #syscalls:pasta getsockname */ int udp_splice_new(const struct ctx *c, int v6, int bound_sock, in_port_t src, - int splice) + bool ns) { struct epoll_event ev = { .events = EPOLLIN | EPOLLRDHUP | EPOLLHUP }; union epoll_ref ref = { .r.proto = IPPROTO_UDP, - .r.p.udp.udp = { .splice = splice, .v6 = v6, - .port = src } + .r.p.udp.udp = { .splice = true, .ns = ns, + .v6 = v6, .port = src } }; struct udp_splice_flow *flow; int s; int act; - if (splice == UDP_BACK_TO_INIT) { + if (ns) { flow = &udp_splice_to_ns[v6 ? V6 : V4][src]; act = UDP_ACT_SPLICE_NS; } else { @@ -499,8 +501,7 @@ static int udp_splice_new_ns(void *arg) if (ns_enter(a->c)) return 0; - a->s = udp_splice_new(a->c, a->v6, a->bound_sock, a->src, - UDP_BACK_TO_INIT); + a->s = udp_splice_new(a->c, a->v6, a->bound_sock, a->src, true); return 0; } @@ -538,8 +539,8 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, src = ntohs(sa->sin_port); } - switch (ref.r.p.udp.udp.splice) { - case UDP_TO_NS: + + if (ref.r.p.udp.udp.orig && !ref.r.p.udp.udp.ns) { src += c->udp.fwd_out.rdelta[src]; if (!(s = udp_splice_to_ns[v6][src].target_sock)) { @@ -551,27 +552,24 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, if ((s = arg.s) < 0) return; } + udp_splice_to_ns[v6][src].ts = now->tv_sec; - break; - case UDP_BACK_TO_INIT: + } else if (!ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { if (!(s = udp_splice_to_ns[v6][dst].orig_sock)) return; - break; - case UDP_TO_INIT: + } else if (ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { src += c->udp.fwd_in.rdelta[src]; if (!(s = udp_splice_to_init[v6][src].target_sock)) { - s = udp_splice_new(c, v6, ref.r.s, src, UDP_BACK_TO_NS); + s = udp_splice_new(c, v6, ref.r.s, src, false); if (s < 0) return; } udp_splice_to_init[v6][src].ts = now->tv_sec; - break; - case UDP_BACK_TO_NS: + } else if (!ref.r.p.udp.udp.orig && !ref.r.p.udp.udp.ns) { if (!(s = udp_splice_to_init[v6][dst].orig_sock)) return; - break; - default: + } else { return; } @@ -1097,15 +1095,16 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, if (c->mode == MODE_PASTA) { bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) }; - uref.udp.splice = UDP_TO_NS; + uref.udp.splice = uref.udp.orig = true; sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); } if (ns) { + uref.udp.splice = uref.udp.orig = uref.udp.ns = true; + bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) }; - uref.udp.splice = UDP_TO_INIT; sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); @@ -1130,7 +1129,7 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, if (c->mode == MODE_PASTA) { bind_addr = &in6addr_loopback; - uref.udp.splice = UDP_TO_NS; + uref.udp.splice = uref.udp.orig = true; sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); @@ -1138,7 +1137,7 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, if (ns) { bind_addr = &in6addr_loopback; - uref.udp.splice = UDP_TO_INIT; + uref.udp.splice = uref.udp.orig = uref.udp.ns = true; sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); diff --git a/udp.h b/udp.h index 43bd28a..053991e 100644 --- a/udp.h +++ b/udp.h @@ -23,20 +23,19 @@ void udp_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s, * union udp_epoll_ref - epoll reference portion for TCP connections * @bound: Set if this file descriptor is a bound socket * @splice: Set if descriptor is associated to "spliced" connection + * @orig: Set if a spliced socket which can originate "connections" + * @ns: Set if this is a socket in the pasta network namespace * @v6: Set for IPv6 sockets or connections * @port: Source port for connected sockets, bound port otherwise * @u32: Opaque u32 value of reference */ union udp_epoll_ref { struct { - uint32_t splice:3, -#define UDP_TO_NS 1 -#define UDP_TO_INIT 2 -#define UDP_BACK_TO_NS 3 -#define UDP_BACK_TO_INIT 4 - - v6:1, - port:16; + bool splice:1, + orig:1, + ns:1, + v6:1; + uint32_t port:16; } udp; uint32_t u32; }; -- 2.38.1
For each IP version udp_socket() has 3 possible calls to sock_l4(). One is for the "non-spliced" bound socket in the init namespace, one for the "spliced" bound socket in the init namespace and one for the "spliced" bound socket in the pasta namespace. However when this is called to create a socket in the pasta namspeace there is a logic error which causes it to take the path for the init side spliced socket as well as the ns socket. This essentially tries to create two identical sockets on the ns side. Unsurprisingly the second bind() call fails according to strace. Correct this to only attempt to open one socket within the ns. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/udp.c b/udp.c index 4c87e66..f93dd8b 100644 --- a/udp.c +++ b/udp.c @@ -1091,17 +1091,15 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, 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 = uref.udp.orig = true; - sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, - port, uref.u32); - } + if (c->mode == MODE_PASTA) { + bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) }; + uref.udp.splice = uref.udp.orig = true; - if (ns) { + sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, + port, uref.u32); + } + } else { uref.udp.splice = uref.udp.orig = uref.udp.ns = true; bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) }; @@ -1125,17 +1123,15 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, port, uref.u32); udp_tap_map[V6][uref.udp.port].sock = s; - } - - if (c->mode == MODE_PASTA) { - bind_addr = &in6addr_loopback; - uref.udp.splice = uref.udp.orig = true; - sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, - port, uref.u32); - } + if (c->mode == MODE_PASTA) { + bind_addr = &in6addr_loopback; + uref.udp.splice = uref.udp.orig = true; - if (ns) { + sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, + port, uref.u32); + } + } else { bind_addr = &in6addr_loopback; uref.udp.splice = uref.udp.orig = uref.udp.ns = true; -- 2.38.1
When we look up udp_splice_to_ns[v6][src].target_sock in udp_sock_handler_splice, all we really require of the socket is that it be bound to port src in the pasta guest namespace. Similarly for udp_splice_to_init but bound in the init namespace. Usually these sockets are created temporarily by udp_splice_connect() and cleaned up by udp_timer(). However, depending on the -u and -U options its possible we have a permanent socket bound to the relevant port created by udp_sock_init(). If such a socket exists, we could use it instead of creating a temporary one. In fact we *must* use it, because we'll fail trying to bind() a temporary one to the same port. So allow this, store permanently bound sockets into udp_splice_to_{ns,init} in udp_sock_init(). These won't get incorrectly removed by the timer because we don't put a corresponding entry in the udp_act[] structure which directs the timer what to clean up. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/udp.c b/udp.c index f93dd8b..65438de 100644 --- a/udp.c +++ b/udp.c @@ -153,7 +153,7 @@ struct udp_splice_flow { /* Port tracking, arrays indexed by packet source port (host order) */ static struct udp_tap_port udp_tap_map [IP_VERSIONS][NUM_PORTS]; -/* Spliced "connections" indexed by originating source port (host order) */ +/* Spliced "connections" indexed by bound port of target_sock (host order) */ static struct udp_splice_flow udp_splice_to_ns [IP_VERSIONS][NUM_PORTS]; static struct udp_splice_flow udp_splice_to_init[IP_VERSIONS][NUM_PORTS]; @@ -1096,16 +1096,18 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) }; uref.udp.splice = uref.udp.orig = true; - sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, - port, uref.u32); + s = sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, + ifname, port, uref.u32); + udp_splice_to_init[V4][port].target_sock = s; } } else { uref.udp.splice = uref.udp.orig = uref.udp.ns = true; bind_addr = &(uint32_t){ htonl(INADDR_LOOPBACK) }; - sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, - port, uref.u32); + s = sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, + ifname, port, uref.u32); + udp_splice_to_ns[V4][port].target_sock = s; } } @@ -1128,15 +1130,17 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, bind_addr = &in6addr_loopback; uref.udp.splice = uref.udp.orig = true; - sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, - port, uref.u32); + s = sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, + ifname, port, uref.u32); + udp_splice_to_init[V6][port].target_sock = s; } } else { bind_addr = &in6addr_loopback; uref.udp.splice = uref.udp.orig = uref.udp.ns = true; - sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, - port, uref.u32); + s = sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, + ifname, port, uref.u32); + udp_splice_to_ns[V6][port].target_sock = s; } } } -- 2.38.1
When we look up udp_splice_to_ns[][].orig_sock in udp_sock_handler_splice() we're finding the socket on which the originating packet for the "connection" was received on. However, we don't specifically need this socket to be the originating one - we just need one that's bound to the the source port of this reply packet in the init namespace. We can look this up in udp_splice_to_init[v6][src].target_sock, whose defining characteristic is exactly that. The same applies with init and ns swapped. In practice, of course, the port we locate this way will always be the originating port, since we couldn't have started this "connection" if it wasn't. Change this, and we no longer need the @orig_sock field at all. That leaves just @target_sock which we rename to simply @sock. The whole udp_splice_flow structure now more represents a single bound port than a "flow" per se, so rename and recomment it accordingly. Likewise the udp_splice_to_{ns,init} names are now misleading, since the ports in those maps are used in both directions. Rename them to udp_splice_{ns,init} indicating the location where the described socket is bound. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 111 +++++++++++++++++++++++++++------------------------------- 1 file changed, 51 insertions(+), 60 deletions(-) diff --git a/udp.c b/udp.c index 65438de..14e8ff2 100644 --- a/udp.c +++ b/udp.c @@ -47,44 +47,40 @@ * * - forward direction: 127.0.0.1:5000 -> 127.0.0.1:80 in init from socket s, * with epoll reference: index = 80, splice = 1, orig = 1, ns = 0 - * - if udp_splice_to_ns[V4][5000].target_sock: - * - send packet to udp_splice_to_ns[V4][5000].target_sock, with - * destination port 80 + * - if udp_splice_ns[V4][5000].sock: + * - send packet to udp_splice_ns[V4][5000].sock, with destination port + * 80 * - otherwise: - * - create new socket udp_splice_to_ns[V4][5000].target_sock + * - create new socket udp_splice_ns[V4][5000].sock * - bind in namespace to 127.0.0.1:5000 * - add to epoll with reference: index = 5000, splice = 1, orig = 0, * ns = 1 - * - set udp_splice_to_ns[V4][5000].orig_sock to s - * - update udp_splice_to_ns[V4][5000].ts with current time + * - update udp_splice_ns[V4][5000].ts with current time * * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:5000 in namespace socket s, * having epoll reference: index = 5000, splice = 1, orig = 0, ns = 1 - * - if udp_splice_to_ns[V4][5000].orig_sock: - * - send to udp_splice_to_ns[V4][5000].orig_sock, with destination port - * 5000 + * - if udp_splice_init[V4][80].sock: + * - send to udp_splice_init[V4][80].sock, with destination port 5000 * - otherwise, discard * * - from namespace to init: * * - forward direction: 127.0.0.1:2000 -> 127.0.0.1:22 in namespace from * socket s, with epoll reference: index = 22, splice = 1, orig = 1, ns = 1 - * - if udp4_splice_to_init[V4][2000].target_sock: - * - send packet to udp_splice_to_init[V4][2000].target_sock, with - * destination port 22 + * - if udp4_splice_init[V4][2000].sock: + * - send packet to udp_splice_init[V4][2000].sock, with destination + * port 22 * - otherwise: - * - create new socket udp_splice_to_init[V4][2000].target_sock + * - create new socket udp_splice_init[V4][2000].sock * - bind in init to 127.0.0.1:2000 * - add to epoll with reference: index = 2000, splice = 1, orig = 0, * ns = 0 - * - set udp_splice_to_init[V4][2000].orig_sock to s - * - update udp_splice_to_init[V4][2000].ts with current time + * - update udp_splice_init[V4][2000].ts with current time * * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:2000 in init from socket s, * having epoll reference: index = 2000, splice = 1, orig = 0, ns = 0 - * - if udp_splice_to_init[V4][2000].orig_sock: - * - send to udp_splice_to_init[V4][2000].orig_sock, with destination port - * 2000 + * - if udp_splice_ns[V4][22].sock: + * - send to udp_splice_ns[V4][22].sock, with destination port 2000 * - otherwise, discard */ @@ -137,25 +133,21 @@ struct udp_tap_port { }; /** - * struct udp_splice_flow - Spliced "connection" - * @orig_sock: Originating socket, bound to dest port in source ns of - * originating datagram - * @target_sock: Target socket, bound to source port of originating - * datagram in dest ns - * @ts: Activity timestamp + * struct udp_splice_port - Bound socket for spliced communication + * @sock: Socket bound to index port + * @ts: Activity timestamp */ -struct udp_splice_flow { - int orig_sock; - int target_sock; +struct udp_splice_port { + int sock; time_t ts; }; /* Port tracking, arrays indexed by packet source port (host order) */ static struct udp_tap_port udp_tap_map [IP_VERSIONS][NUM_PORTS]; -/* Spliced "connections" indexed by bound port of target_sock (host order) */ -static struct udp_splice_flow udp_splice_to_ns [IP_VERSIONS][NUM_PORTS]; -static struct udp_splice_flow udp_splice_to_init[IP_VERSIONS][NUM_PORTS]; +/* "Spliced" sockets indexed by bound port (host order) */ +static struct udp_splice_port udp_splice_ns [IP_VERSIONS][NUM_PORTS]; +static struct udp_splice_port udp_splice_init[IP_VERSIONS][NUM_PORTS]; enum udp_act_type { UDP_ACT_TAP, @@ -397,7 +389,6 @@ static void udp_sock6_iov_init(void) * udp_splice_new() - Create and prepare socket for "spliced" binding * @c: Execution context * @v6: Set for IPv6 sockets - * @bound_sock: Originating bound socket * @src: Source port of original connection, host order * @splice: UDP_BACK_TO_INIT from init, UDP_BACK_TO_NS from namespace * @@ -405,23 +396,22 @@ static void udp_sock6_iov_init(void) * * #syscalls:pasta getsockname */ -int udp_splice_new(const struct ctx *c, int v6, int bound_sock, in_port_t src, - bool ns) +int udp_splice_new(const struct ctx *c, int v6, in_port_t src, bool ns) { struct epoll_event ev = { .events = EPOLLIN | EPOLLRDHUP | EPOLLHUP }; union epoll_ref ref = { .r.proto = IPPROTO_UDP, .r.p.udp.udp = { .splice = true, .ns = ns, .v6 = v6, .port = src } }; - struct udp_splice_flow *flow; + struct udp_splice_port *sp; int s; int act; if (ns) { - flow = &udp_splice_to_ns[v6 ? V6 : V4][src]; + sp = &udp_splice_ns[v6 ? V6 : V4][src]; act = UDP_ACT_SPLICE_NS; } else { - flow = &udp_splice_to_init[v6 ? V6 : V4][src]; + sp = &udp_splice_init[v6 ? V6 : V4][src]; act = UDP_ACT_SPLICE_INIT; } @@ -456,8 +446,7 @@ int udp_splice_new(const struct ctx *c, int v6, int bound_sock, in_port_t src, goto fail; } - flow->orig_sock = bound_sock; - flow->target_sock = s; + sp->sock = s; bitmap_set(udp_act[v6 ? V6 : V4][act], src); ev.data.u64 = ref.u64; @@ -473,7 +462,6 @@ fail: * struct udp_splice_new_ns_arg - Arguments for udp_splice_new_ns() * @c: Execution context * @v6: Set for IPv6 - * @bound_sock: Originating bound socket * @src: Source port of originating datagram, host order * @dst: Destination port of originating datagram, host order * @s: Newly created socket or negative error code @@ -481,7 +469,6 @@ fail: struct udp_splice_new_ns_arg { const struct ctx *c; int v6; - int bound_sock; in_port_t src; int s; }; @@ -501,7 +488,7 @@ static int udp_splice_new_ns(void *arg) if (ns_enter(a->c)) return 0; - a->s = udp_splice_new(a->c, a->v6, a->bound_sock, a->src, true); + a->s = udp_splice_new(a->c, a->v6, a->src, true); return 0; } @@ -543,9 +530,9 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, if (ref.r.p.udp.udp.orig && !ref.r.p.udp.udp.ns) { src += c->udp.fwd_out.rdelta[src]; - if (!(s = udp_splice_to_ns[v6][src].target_sock)) { + if (!(s = udp_splice_ns[v6][src].sock)) { struct udp_splice_new_ns_arg arg = { - c, v6, ref.r.s, src, -1, + c, v6, src, -1, }; NS_CALL(udp_splice_new_ns, &arg); @@ -553,21 +540,25 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, return; } - udp_splice_to_ns[v6][src].ts = now->tv_sec; + udp_splice_ns[v6][src].ts = now->tv_sec; } else if (!ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { - if (!(s = udp_splice_to_ns[v6][dst].orig_sock)) + src += c->udp.fwd_in.rdelta[src]; + + if (!(s = udp_splice_init[v6][src].sock)) return; } else if (ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { src += c->udp.fwd_in.rdelta[src]; - if (!(s = udp_splice_to_init[v6][src].target_sock)) { - s = udp_splice_new(c, v6, ref.r.s, src, false); + if (!(s = udp_splice_init[v6][src].sock)) { + s = udp_splice_new(c, v6, src, false); if (s < 0) return; } - udp_splice_to_init[v6][src].ts = now->tv_sec; + udp_splice_init[v6][src].ts = now->tv_sec; } else if (!ref.r.p.udp.udp.orig && !ref.r.p.udp.udp.ns) { - if (!(s = udp_splice_to_init[v6][dst].orig_sock)) + src += c->udp.fwd_out.rdelta[src]; + + if (!(s = udp_splice_ns[v6][src].sock)) return; } else { return; @@ -1098,7 +1089,7 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, s = sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); - udp_splice_to_init[V4][port].target_sock = s; + udp_splice_init[V4][port].sock = s; } } else { uref.udp.splice = uref.udp.orig = uref.udp.ns = true; @@ -1107,7 +1098,7 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, s = sock_l4(c, AF_INET, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); - udp_splice_to_ns[V4][port].target_sock = s; + udp_splice_ns[V4][port].sock = s; } } @@ -1132,7 +1123,7 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, s = sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); - udp_splice_to_init[V6][port].target_sock = s; + udp_splice_init[V6][port].sock = s; } } else { bind_addr = &in6addr_loopback; @@ -1140,7 +1131,7 @@ void udp_sock_init(const struct ctx *c, int ns, sa_family_t af, s = sock_l4(c, AF_INET6, IPPROTO_UDP, bind_addr, ifname, port, uref.u32); - udp_splice_to_ns[V6][port].target_sock = s; + udp_splice_ns[V6][port].sock = s; } } } @@ -1243,7 +1234,7 @@ int udp_init(struct ctx *c) static void udp_timer_one(struct ctx *c, int v6, enum udp_act_type type, in_port_t port, const struct timespec *ts) { - struct udp_splice_flow *flow; + struct udp_splice_port *sp; struct udp_tap_port *tp; int s = -1; @@ -1258,17 +1249,17 @@ static void udp_timer_one(struct ctx *c, int v6, enum udp_act_type type, break; case UDP_ACT_SPLICE_INIT: - flow = &udp_splice_to_init[v6 ? V6 : V4][port]; + sp = &udp_splice_init[v6 ? V6 : V4][port]; - if (ts->tv_sec - flow->ts > UDP_CONN_TIMEOUT) - s = flow->target_sock; + if (ts->tv_sec - sp->ts > UDP_CONN_TIMEOUT) + s = sp->sock; break; case UDP_ACT_SPLICE_NS: - flow = &udp_splice_to_ns[v6 ? V6 : V4][port]; + sp = &udp_splice_ns[v6 ? V6 : V4][port]; - if (ts->tv_sec - flow->ts > UDP_CONN_TIMEOUT) - s = flow->target_sock; + if (ts->tv_sec - sp->ts > UDP_CONN_TIMEOUT) + s = sp->sock; break; default: -- 2.38.1
A UDP pseudo-connection between port A in the init namespace and port B in the pasta guest namespace involves two sockets: udp_splice_init[v6][B] and udp_splice_ns[v6][A]. The socket which originated this "connection" will be permanent but the other one will be closed on a timeout. When we get a packet from the originating socket, we update the timeout on the other socket, but we don't do the same when we get a reply packet from the other socket. However any activity on the "connection" probably indicates that it's still in use. Without this we could incorrectly time out a "connection" if it's using a protocol which involves a single initiating packet, but which then gets continuing replies from the target. Correct this by updating the timeout on both sockets for a packet in either direction. This also updates the timestamps for the permanent originating sockets which is unnecessary, but harmless. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/udp.c b/udp.c index 14e8ff2..206e9d3 100644 --- a/udp.c +++ b/udp.c @@ -55,12 +55,15 @@ * - bind in namespace to 127.0.0.1:5000 * - add to epoll with reference: index = 5000, splice = 1, orig = 0, * ns = 1 - * - update udp_splice_ns[V4][5000].ts with current time + * - update udp_splice_init[V4][80].ts and udp_splice_ns[V4][5000].ts with + * current time * * - reverse direction: 127.0.0.1:80 -> 127.0.0.1:5000 in namespace socket s, * having epoll reference: index = 5000, splice = 1, orig = 0, ns = 1 * - if udp_splice_init[V4][80].sock: * - send to udp_splice_init[V4][80].sock, with destination port 5000 + * - update udp_splice_init[V4][80].ts and udp_splice_ns[V4][5000].ts with + * current time * - otherwise, discard * * - from namespace to init: @@ -75,12 +78,15 @@ * - bind in init to 127.0.0.1:2000 * - add to epoll with reference: index = 2000, splice = 1, orig = 0, * ns = 0 - * - update udp_splice_init[V4][2000].ts with current time + * - update udp_splice_ns[V4][22].ts and udp_splice_init[V4][2000].ts with + * current time * * - reverse direction: 127.0.0.1:22 -> 127.0.0.1:2000 in init from socket s, * having epoll reference: index = 2000, splice = 1, orig = 0, ns = 0 * - if udp_splice_ns[V4][22].sock: * - send to udp_splice_ns[V4][22].sock, with destination port 2000 + * - update udp_splice_ns[V4][22].ts and udp_splice_init[V4][2000].ts with + * current time * - otherwise, discard */ @@ -540,12 +546,16 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, return; } + udp_splice_init[v6][dst].ts = now->tv_sec; udp_splice_ns[v6][src].ts = now->tv_sec; } else if (!ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { src += c->udp.fwd_in.rdelta[src]; if (!(s = udp_splice_init[v6][src].sock)) return; + + udp_splice_ns[v6][dst].ts = now->tv_sec; + udp_splice_init[v6][src].ts = now->tv_sec; } else if (ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { src += c->udp.fwd_in.rdelta[src]; @@ -554,12 +564,17 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, if (s < 0) return; } + + udp_splice_ns[v6][dst].ts = now->tv_sec; udp_splice_init[v6][src].ts = now->tv_sec; } else if (!ref.r.p.udp.udp.orig && !ref.r.p.udp.udp.ns) { src += c->udp.fwd_out.rdelta[src]; if (!(s = udp_splice_ns[v6][src].sock)) return; + + udp_splice_init[v6][dst].ts = now->tv_sec; + udp_splice_ns[v6][src].ts = now->tv_sec; } else { return; } -- 2.38.1
Previous cleanups mean that we can now rework some complex ifs in udp_sock_handler_splice() into a simpler set. Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- udp.c | 47 +++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/udp.c b/udp.c index 206e9d3..ec61022 100644 --- a/udp.c +++ b/udp.c @@ -533,50 +533,33 @@ static void udp_sock_handler_splice(const struct ctx *c, union epoll_ref ref, } - if (ref.r.p.udp.udp.orig && !ref.r.p.udp.udp.ns) { - src += c->udp.fwd_out.rdelta[src]; - - if (!(s = udp_splice_ns[v6][src].sock)) { - struct udp_splice_new_ns_arg arg = { - c, v6, src, -1, - }; - - NS_CALL(udp_splice_new_ns, &arg); - if ((s = arg.s) < 0) - return; - } - - udp_splice_init[v6][dst].ts = now->tv_sec; - udp_splice_ns[v6][src].ts = now->tv_sec; - } else if (!ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { + if (ref.r.p.udp.udp.ns) { src += c->udp.fwd_in.rdelta[src]; + s = udp_splice_init[v6][src].sock; + if (!s && ref.r.p.udp.udp.orig) + s = udp_splice_new(c, v6, src, false); - if (!(s = udp_splice_init[v6][src].sock)) + if (s < 0) return; udp_splice_ns[v6][dst].ts = now->tv_sec; udp_splice_init[v6][src].ts = now->tv_sec; - } else if (ref.r.p.udp.udp.orig && ref.r.p.udp.udp.ns) { - src += c->udp.fwd_in.rdelta[src]; - - if (!(s = udp_splice_init[v6][src].sock)) { - s = udp_splice_new(c, v6, src, false); - if (s < 0) - return; - } - - udp_splice_ns[v6][dst].ts = now->tv_sec; - udp_splice_init[v6][src].ts = now->tv_sec; - } else if (!ref.r.p.udp.udp.orig && !ref.r.p.udp.udp.ns) { + } else { src += c->udp.fwd_out.rdelta[src]; + s = udp_splice_ns[v6][src].sock; + if (!s && ref.r.p.udp.udp.orig) { + struct udp_splice_new_ns_arg arg = { + c, v6, src, -1, + }; - if (!(s = udp_splice_ns[v6][src].sock)) + NS_CALL(udp_splice_new_ns, &arg); + s = arg.s; + } + if (s < 0) return; udp_splice_init[v6][dst].ts = now->tv_sec; udp_splice_ns[v6][src].ts = now->tv_sec; - } else { - return; } for (i = 0; i < n; i++) { -- 2.38.1
On Tue, 22 Nov 2022 14:43:51 +1100 David Gibson <david(a)gibson.dropbear.id.au> wrote:This series make some substantial simplifications to how we handle the forwarding of "spliced" UDP packets (those that don't go over the tuntap device, but instead are forwarded from one L4 socket to another). This doesn't yet change the existing (arguably broken) assumption that UDP communications are from one port to one port within the pasta namespace, not one to many or many to one. However, the simplifications made here will make it easier to correct that in future.I'm not done reviewing this yet, but it all makes sense to me -- I just need a bit longer to convince myself of 1/10 to 3/10 despite the fact they're obviously simpler than the original implementation (i.e. of why I didn't implement it like that in the first place ;)). -- Stefano