From d71c248d90b62d79dd8073bd107af0cf62be11d8 Mon Sep 17 00:00:00 2001 From: Chris Donohue Date: Tue, 22 Jul 2025 12:22:34 -0400 Subject: [PATCH] ipv6 --- scone_demo.sh | 54 +++++++++++++-- scone_ebpf.c | 182 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 194 insertions(+), 42 deletions(-) diff --git a/scone_demo.sh b/scone_demo.sh index 177a2a4..fd1cfeb 100755 --- a/scone_demo.sh +++ b/scone_demo.sh @@ -48,6 +48,14 @@ CSP2_IP1=10.0.21.2 CSP2_IP2=10.0.31.1 CAP_IP=10.0.31.2 +# IPv6 addresses +USER_IPV6=2001:db8:11::1 +CSP1_IPV6_1=2001:db8:11::2 +CSP1_IPV6_2=2001:db8:21::1 +CSP2_IPV6_1=2001:db8:21::2 +CSP2_IPV6_2=2001:db8:31::1 +CAP_IPV6=2001:db8:31::2 + PLAIN_UDP_PORT=10000 NON_SCONE_PORT=20000 SCONE_PORT=60000 @@ -63,33 +71,51 @@ ip netns add $SCONE_USER_NS ip netns add $SCONE_CSP1_NS ip netns add $SCONE_CSP2_NS ip netns add $SCONE_CAP_NS +echo " Enabling IPv4 and IPv6 forwarding." ip netns exec $SCONE_USER_NS sysctl -q -w net.ipv4.ip_forward=1 ip netns exec $SCONE_CSP1_NS sysctl -q -w net.ipv4.ip_forward=1 ip netns exec $SCONE_CSP2_NS sysctl -q -w net.ipv4.ip_forward=1 ip netns exec $SCONE_CAP_NS sysctl -q -w net.ipv4.ip_forward=1 +ip netns exec $SCONE_USER_NS sysctl -q -w net.ipv6.conf.all.forwarding=1 +ip netns exec $SCONE_CSP1_NS sysctl -q -w net.ipv6.conf.all.forwarding=1 +ip netns exec $SCONE_CSP2_NS sysctl -q -w net.ipv6.conf.all.forwarding=1 +ip netns exec $SCONE_CAP_NS sysctl -q -w net.ipv6.conf.all.forwarding=1 echo " Creating virtual links." ip -n $SCONE_USER_NS link add name $USER_CSP_LINK type veth peer netns $SCONE_CSP1_NS name $CSP_USER_LINK ip -n $SCONE_CSP1_NS link add name $CSP_CSP_1_LINK type veth peer netns $SCONE_CSP2_NS name $CSP_CSP_2_LINK ip -n $SCONE_CSP2_NS link add name $CSP_CAP_LINK type veth peer netns $SCONE_CAP_NS name $CAP_CSP_LINK -echo " Assigning IP addresses and bringing up interfaces." +echo " Assigning IPv4 and IPv6 addresses and bringing up interfaces." ip -n $SCONE_USER_NS address add $USER_IP/30 dev $USER_CSP_LINK ip -n $SCONE_CSP1_NS address add $CSP1_IP1/30 dev $CSP_USER_LINK ip -n $SCONE_CSP1_NS address add $CSP1_IP2/30 dev $CSP_CSP_1_LINK ip -n $SCONE_CSP2_NS address add $CSP2_IP1/30 dev $CSP_CSP_2_LINK ip -n $SCONE_CSP2_NS address add $CSP2_IP2/30 dev $CSP_CAP_LINK ip -n $SCONE_CAP_NS address add $CAP_IP/30 dev $CAP_CSP_LINK +ip -n $SCONE_USER_NS address add $USER_IPV6/64 dev $USER_CSP_LINK +ip -n $SCONE_CSP1_NS address add $CSP1_IPV6_1/64 dev $CSP_USER_LINK +ip -n $SCONE_CSP1_NS address add $CSP1_IPV6_2/64 dev $CSP_CSP_1_LINK +ip -n $SCONE_CSP2_NS address add $CSP2_IPV6_1/64 dev $CSP_CSP_2_LINK +ip -n $SCONE_CSP2_NS address add $CSP2_IPV6_2/64 dev $CSP_CAP_LINK +ip -n $SCONE_CAP_NS address add $CAP_IPV6/64 dev $CAP_CSP_LINK ip -n $SCONE_USER_NS link set $USER_CSP_LINK up ip -n $SCONE_CSP1_NS link set $CSP_USER_LINK up ip -n $SCONE_CSP1_NS link set $CSP_CSP_1_LINK up ip -n $SCONE_CSP2_NS link set $CSP_CSP_2_LINK up ip -n $SCONE_CSP2_NS link set $CSP_CAP_LINK up ip -n $SCONE_CAP_NS link set $CAP_CSP_LINK up -echo " Setting routes." +echo " Setting IPv4 and IPv6 routes." ip -n $SCONE_USER_NS route add $CAP_IP/32 via $CSP1_IP1 ip -n $SCONE_CSP1_NS route add $CAP_IP/32 via $CSP2_IP1 ip -n $SCONE_CSP2_NS route add $USER_IP/32 via $CSP1_IP2 ip -n $SCONE_CAP_NS route add $USER_IP/32 via $CSP2_IP2 +ip -n $SCONE_USER_NS -6 route add $CAP_IPV6/128 via $CSP1_IPV6_1 +ip -n $SCONE_CSP1_NS -6 route add $CAP_IPV6/128 via $CSP2_IPV6_1 +ip -n $SCONE_CSP2_NS -6 route add $USER_IPV6/128 via $CSP1_IPV6_2 +ip -n $SCONE_CAP_NS -6 route add $USER_IPV6/128 via $CSP2_IPV6_2 +echo " Testing IPv4 connectivity." ip netns exec $SCONE_USER_NS ping -c 3 $CAP_IP +echo " Testing IPv6 connectivity." +ip netns exec $SCONE_USER_NS ping6 -c 3 $CAP_IPV6 #ip -n $SCONE_USER_NS stats set dev $USER_CSP_LINK l3_stats on @@ -117,10 +143,15 @@ sleep 10 # Run servers. echo "=== Starting servers." +echo " Starting IPv4 servers." ip netns exec $SCONE_CAP_NS nc -u -l $PLAIN_UDP_PORT & nc_pid=$! ip netns exec $SCONE_CAP_NS ./hq --mode=server --host=$CAP_IP --port=$NON_SCONE_PORT --logtostderr=false & hqs1_pid=$! ip netns exec $SCONE_CAP_NS ./hq --mode=server --host=$CAP_IP --port=$SCONE_PORT --logtostderr=false --h2port=7776 & hqs2_pid=$! -# Note: h2port is set on the 2nd hq server above, so that it doesn't collide with the first. +echo " Starting IPv6 servers." +ip netns exec $SCONE_CAP_NS nc -u -l -6 $((PLAIN_UDP_PORT + 1)) & nc6_pid=$! +ip netns exec $SCONE_CAP_NS ./hq --mode=server --host=$CAP_IPV6 --port=$((NON_SCONE_PORT + 1)) --logtostderr=false & hqs1_ipv6_pid=$! +ip netns exec $SCONE_CAP_NS ./hq --mode=server --host=$CAP_IPV6 --port=$((SCONE_PORT + 1)) --logtostderr=false --h2port=7777 & hqs2_ipv6_pid=$! +# Note: h2port is set differently for IPv6 servers to avoid collisions. echo " Waiting 5 seconds." sleep 5 @@ -129,14 +160,20 @@ sleep 5 echo "=== Running clients." NC_DATA=/tmp/random_nc_data openssl rand -hex 48 >$NC_DATA -#cat $NC_DATA | ip netns exec $SCONE_USER_NS nc -u $CAP_IP $PLAIN_UDP_PORT -#ip netns exec $SCONE_USER_NS nc -u $CAP_IP $PLAIN_UDP_PORT -echo " Doing non-SCONE-enabled fetch." +echo " IPv4 client tests:" +echo " Doing non-SCONE-enabled fetch (IPv4)." ip netns exec $SCONE_USER_NS ./hq --mode=client --host=$CAP_IP --port=$NON_SCONE_PORT --path=/ sleep 2 -echo " Doing SCONE-enabled fetch." +echo " Doing SCONE-enabled fetch (IPv4)." ip netns exec $SCONE_USER_NS ./hq --mode=client --host=$CAP_IP --port=$SCONE_PORT --path=/ sleep 2 +echo " IPv6 client tests:" +echo " Doing non-SCONE-enabled fetch (IPv6)." +ip netns exec $SCONE_USER_NS ./hq --mode=client --host=$CAP_IPV6 --port=$((NON_SCONE_PORT + 1)) --path=/ +sleep 2 +echo " Doing SCONE-enabled fetch (IPv6)." +ip netns exec $SCONE_USER_NS ./hq --mode=client --host=$CAP_IPV6 --port=$((SCONE_PORT + 1)) --path=/ +sleep 2 # Stop and cleanup. echo "=== Cleaning up." @@ -147,9 +184,12 @@ kill -9 $dump_pid4 echo " User network interface stats:" #ip netns exec $SCONE_USER_NS ip stats show dev $USER_CSP_LINK kill -9 $nc_pid +kill -9 $nc6_pid kill -9 $hqs1_pid +kill -9 $hqs1_ipv6_pid sleep 2 kill -9 $hqs2_pid +kill -9 $hqs2_ipv6_pid echo " Stopping User<-CSP1 eBPF program." kill -2 $scone_pid1 sleep 2 diff --git a/scone_ebpf.c b/scone_ebpf.c index 0e60e7b..1590dd9 100644 --- a/scone_ebpf.c +++ b/scone_ebpf.c @@ -51,8 +51,11 @@ const u32 quic_versions[] = { 0x00000000, 0x00000001, 0xfaceb001, 0xfaceb002 }; const int NUM_QUIC_VERSIONS = 9; -// This is a hard-coded destination UDP port number to add SCONE packets for. -const unsigned short SCONE_PORT = 60000; +// These are hard-coded destination UDP port numbers to add SCONE packets for. +const unsigned short SCONE_PORT_IPV4 = 60000; +const unsigned short SCONE_PORT_IPV6 = 60001; +const unsigned short NON_SCONE_PORT_IPV4 = 20000; +const unsigned short NON_SCONE_PORT_IPV6 = 20001; // TODO: Redefine these to the actual SCONE values. #define SCONE_V1 0x53434f4E @@ -108,12 +111,30 @@ static __always_inline u64 check_quic(void *data, void *data_end) { struct ethhdr *eth = data; u16 ipproto = bpf_ntohs(eth->h_proto); struct iphdr *ip = (void *)eth + sizeof(*eth); - struct ipv6hdr *ip6 = (void *)ip; - struct udphdr *udp = (void *)ip + sizeof(*ip); - if (ipproto == ETH_P_IPV6) udp = (void *)ip6 + sizeof(*ip); - char *quic = (char *)udp + sizeof(*udp); + struct ipv6hdr *ip6 = (void *)eth + sizeof(*eth); + struct udphdr *udp; + char *quic; u16 port; - + + // Bounds check for Ethernet header + if ((void *)eth + sizeof(*eth) > data_end) + return TOO_SMALL_COUNTER; + + // Set UDP pointer based on IP version with proper bounds checking + if (ipproto == ETH_P_IPV6) { + // IPv6: Ethernet + IPv6 header + UDP header + if ((void *)ip6 + sizeof(*ip6) + sizeof(struct udphdr) > data_end) + return TOO_SMALL_COUNTER; + udp = (void *)ip6 + sizeof(*ip6); + } else { + // IPv4: Ethernet + IPv4 header + UDP header + if ((void *)ip + sizeof(*ip) + sizeof(struct udphdr) > data_end) + return TOO_SMALL_COUNTER; + udp = (void *)ip + sizeof(*ip); + } + + quic = (char *)udp + sizeof(*udp); + // Pass through if it's too small to be a QUIC packet. if ((void*)quic >= data_end) return TOO_SMALL_COUNTER; @@ -129,8 +150,9 @@ static __always_inline u64 check_quic(void *data, void *data_end) { port = bpf_ntohs(udp->source); ports.increment(port); - // Pass through if the UDP destination port isn't configured for SCONE. - if (port != SCONE_PORT) + // Pass through if the UDP source port isn't configured for SCONE or non-SCONE testing. + if (port != SCONE_PORT_IPV4 && port != SCONE_PORT_IPV6 && + port != NON_SCONE_PORT_IPV4 && port != NON_SCONE_PORT_IPV6) return NOT_SCONE_PORT_COUNTER; // TODO: Remove later, this is just for seeing what is received. @@ -162,7 +184,7 @@ static __always_inline u64 check_quic(void *data, void *data_end) { } // If the checks made it all the way here, it's QUIC. - return QUIC_IPV4_LONG_COUNTER; + return (ipproto == ETH_P_IP) ? QUIC_IPV4_LONG_COUNTER : QUIC_IPV6_LONG_COUNTER; } // This is hooked to receive all packets, so first it needs to check whether @@ -174,14 +196,45 @@ int add_scone_ebpf(struct xdp_md *ctx) { struct ethhdr *eth = data; struct iphdr *ip = (void *)eth + sizeof(*eth); - struct udphdr *udp = (void *)ip + sizeof(*ip); - - size_t lower_hdrlen = sizeof(*eth) + sizeof(*ip) + sizeof(*udp); - u8 *quic = data + lower_hdrlen; + struct ipv6hdr *ip6 = (void *)eth + sizeof(*eth); + struct udphdr *udp; + size_t lower_hdrlen; + u8 *quic; u8 src_conn_id_len, dst_conn_id_len; u64 result = check_quic(data, data_end); - if (result != QUIC_IPV4_LONG_COUNTER) { + if (result != QUIC_IPV4_LONG_COUNTER && result != QUIC_IPV6_LONG_COUNTER) { + counters.increment(result); + return XDP_PASS; + } + + // Set header pointers and lengths based on IP version with bounds checking + if (result == QUIC_IPV6_LONG_COUNTER) { + // IPv6 packet - need to re-verify bounds for IPv6 header access + if ((void *)ip6 + sizeof(*ip6) + sizeof(struct udphdr) > data_end) + return XDP_PASS; + udp = (void*)ip6 + sizeof(*ip6); + lower_hdrlen = sizeof(*eth) + sizeof(*ip6) + sizeof(*udp); + quic = data + lower_hdrlen; + // Verify QUIC payload is accessible + if (quic >= (u8*)data_end) + return XDP_PASS; + } else { + // IPv4 packet + if ((void *)ip + sizeof(*ip) + sizeof(struct udphdr) > data_end) + return XDP_PASS; + udp = (void *)ip + sizeof(*ip); + lower_hdrlen = sizeof(*eth) + sizeof(*ip) + sizeof(*udp); + quic = data + lower_hdrlen; + // Verify QUIC payload is accessible + if (quic >= (u8*)data_end) + return XDP_PASS; + } + + // Only add SCONE to SCONE-enabled ports, pass through non-SCONE test traffic + u16 src_port = bpf_ntohs(udp->source); + if (src_port == NON_SCONE_PORT_IPV4 || src_port == NON_SCONE_PORT_IPV6) { + // Pass through non-SCONE test traffic without modification counters.increment(result); return XDP_PASS; } @@ -233,7 +286,14 @@ int add_scone_ebpf(struct xdp_md *ctx) { u8 *new_data_end = (u8 *)(long)ctx->data_end; if (new_data + lower_hdrlen > new_data_end) return XDP_ABORTED; if (new_data + delta + lower_hdrlen > new_data_end) return XDP_ABORTED; - __builtin_memmove(new_data, new_data+delta, lower_hdrlen); + // Manual byte-by-byte copy to avoid memmove (supports both IPv4 and IPv6) + // IPv4 headers: 14+20+8=42, IPv6 headers: 14+40+8=62 + int max_hdr_len = (result == QUIC_IPV6_LONG_COUNTER) ? 62 : 42; + if (lower_hdrlen <= max_hdr_len && new_data + lower_hdrlen < new_data_end && new_data + delta + lower_hdrlen < new_data_end) { + for (int i = 0; i < max_hdr_len && i < lower_hdrlen; i++) { + new_data[i] = new_data[delta + i]; + } + } // Fill in SCONE packet. eth = (void*)(long)ctx->data; @@ -251,14 +311,32 @@ int add_scone_ebpf(struct xdp_md *ctx) { if ((u8*)&(scone->dst_conn_id) + QUIC_CONN_ID_LEN >= new_data_end) return XDP_ABORTED; u8 *dst_conn_id = new_data + lower_hdrlen + sizeof(*scone) + 7; if ((u8*)dst_conn_id + QUIC_CONN_ID_LEN >= new_data_end) return XDP_ABORTED; - __builtin_memmove(&scone->dst_conn_id, dst_conn_id, QUIC_CONN_ID_LEN); + // Manual copy to avoid memmove issues (QUIC_CONN_ID_LEN = 8) + if ((u8*)&(scone->dst_conn_id[0]) + 8 < new_data_end && dst_conn_id + 8 < new_data_end) { + scone->dst_conn_id[0] = dst_conn_id[0]; + scone->dst_conn_id[1] = dst_conn_id[1]; + scone->dst_conn_id[2] = dst_conn_id[2]; + scone->dst_conn_id[3] = dst_conn_id[3]; + scone->dst_conn_id[4] = dst_conn_id[4]; + scone->dst_conn_id[5] = dst_conn_id[5]; + scone->dst_conn_id[6] = dst_conn_id[6]; + scone->dst_conn_id[7] = dst_conn_id[7]; + } - // Fix up IP header to reflect new length. - if ((u8*)ip + sizeof(*ip) >= new_data_end) return XDP_ABORTED; - ip->tot_len = bpf_htons(bpf_ntohs(ip->tot_len) + sizeof(*scone)); - __u64 cs = ip->check = 0; - ipv4_csum(ip, sizeof(*ip), &cs); - ip->check = cs; + // Fix up IP header to reflect new length (different for IPv4 vs IPv6). + if (result == QUIC_IPV4_LONG_COUNTER) { + if ((u8*)ip + sizeof(*ip) >= new_data_end) return XDP_ABORTED; + ip->tot_len = bpf_htons(bpf_ntohs(ip->tot_len) + sizeof(*scone)); + __u64 cs = ip->check = 0; + ipv4_csum(ip, sizeof(*ip), &cs); + ip->check = cs; + } else { + // IPv6 case + struct ipv6hdr *ip6 = (void*)eth + sizeof(*eth); + if ((u8*)ip6 + sizeof(*ip6) >= new_data_end) return XDP_ABORTED; + ip6->payload_len = bpf_htons(bpf_ntohs(ip6->payload_len) + sizeof(*scone)); + // IPv6 has no header checksum + } // Fix up UDP header to reflect new length. if ((u8*)udp + sizeof(*udp) >= new_data_end) return XDP_ABORTED; @@ -320,23 +398,48 @@ int remove_scone_ebpf(struct xdp_md *ctx) { u64 result = check_quic(data, data_end); // If it doesn't look like SCONE, ignore it. - if (result != SCONE_IPV4_COUNTER) { + if (result != SCONE_IPV4_COUNTER && result != SCONE_IPV6_COUNTER) { counters.increment(result); return XDP_PASS; } struct ethhdr *eth = data; struct iphdr *ip = (void *)eth + sizeof(*eth); - struct udphdr *udp = (void *)ip + sizeof(*ip); + struct ipv6hdr *ip6 = (void *)eth + sizeof(*eth); + struct udphdr *udp; + int lower_hdrs_len; + + // Set header pointers and lengths based on IP version with bounds checking + if (result == SCONE_IPV6_COUNTER) { + // IPv6 packet - need to re-verify bounds for IPv6 header access + if ((void *)ip6 + sizeof(*ip6) + sizeof(struct udphdr) > data_end) + return XDP_PASS; + udp = (void *)ip6 + sizeof(*ip6); + lower_hdrs_len = sizeof(*eth) + sizeof(*ip6) + sizeof(*udp); + } else { + // IPv4 packet + if ((void *)ip + sizeof(*ip) + sizeof(struct udphdr) > data_end) + return XDP_PASS; + udp = (void *)ip + sizeof(*ip); + lower_hdrs_len = sizeof(*eth) + sizeof(*ip) + sizeof(*udp); + } u8 *quic = (u8*)udp + sizeof(*udp); u8 src_conn_id_len, dst_conn_id_len; // Copy the lower headers back, overwriting the SCONE packet. - const int lower_hdrs_len = sizeof(*eth) + sizeof(*ip) + sizeof(*udp); if (data+sizeof(struct sconepkt)+lower_hdrs_len >= data_end) return XDP_ABORTED; - __builtin_memmove(data+sizeof(struct sconepkt), data, lower_hdrs_len); + // Manual byte-by-byte copy for header removal (supports both IPv4 and IPv6) + u8 *src = (u8*)data; + u8 *dst = (u8*)data + sizeof(struct sconepkt); + // IPv4 headers: 14+20+8=42, IPv6 headers: 14+40+8=62 + int max_hdr_len = (result == SCONE_IPV6_COUNTER) ? 62 : 42; + if (lower_hdrs_len <= max_hdr_len && src + lower_hdrs_len < (u8*)data_end && dst + lower_hdrs_len < (u8*)data_end) { + for (int i = 0; i < max_hdr_len && i < lower_hdrs_len; i++) { + dst[i] = src[i]; + } + } // Adjust the packet length to shrink it. if (bpf_xdp_adjust_head(ctx, sizeof(struct sconepkt)) != 0) { // TODO: Some kind of error. @@ -345,18 +448,27 @@ int remove_scone_ebpf(struct xdp_md *ctx) { return XDP_PASS; } - // Adjust IP length and checksum values. + // Adjust IP length and checksum values (different for IPv4 vs IPv6). eth = (void *)(long)ctx->data; - ip = (void *)eth + sizeof(*eth); data_end = (void *)(long)ctx->data_end; - if ((void*)ip + sizeof(*ip) >= data_end) return XDP_ABORTED; - ip->tot_len = bpf_htons(bpf_ntohs(ip->tot_len) - sizeof(struct sconepkt)); - __u64 cs = ip->check = 0; - ipv4_csum(ip, sizeof(*ip), &cs); - ip->check = cs; + + if (result == SCONE_IPV4_COUNTER) { + ip = (void *)eth + sizeof(*eth); + if ((void*)ip + sizeof(*ip) >= data_end) return XDP_ABORTED; + ip->tot_len = bpf_htons(bpf_ntohs(ip->tot_len) - sizeof(struct sconepkt)); + __u64 cs = ip->check = 0; + ipv4_csum(ip, sizeof(*ip), &cs); + ip->check = cs; + udp = (void *)ip + sizeof(*ip); + } else { + // IPv6 case + struct ipv6hdr *ip6 = (void *)eth + sizeof(*eth); + if ((void*)ip6 + sizeof(*ip6) >= data_end) return XDP_ABORTED; + ip6->payload_len = bpf_htons(bpf_ntohs(ip6->payload_len) - sizeof(struct sconepkt)); + udp = (void *)ip6 + sizeof(*ip6); + } // Adjust UDP length and checksum values. - udp = (void *)ip + sizeof(*ip); if ((void*)udp + sizeof(*udp) >= data_end) return XDP_ABORTED; udp->len = bpf_htons(bpf_ntohs(udp->len) - sizeof(struct sconepkt)); udp->check = 0; // TODO: Use real checksum.