mirror of
https://github.com/google/nsjail.git
synced 2026-08-30 18:41:30 -07:00
nstun: implement kernel checksum offloading for TUN traffic
This commit is contained in:
@@ -539,4 +539,24 @@ struct rlimit64 {
|
||||
};
|
||||
#endif /* !defined(RLIM64_INFINITY) */
|
||||
|
||||
/* =========================================================================
|
||||
* virtio_net_hdr fallback
|
||||
* ========================================================================= */
|
||||
|
||||
/* linux/virtio_net.h is not C++-safe (uses 'class' as a field name) */
|
||||
#if __has_include(<linux/virtio_net.h>)
|
||||
#include <linux/if_tun.h> /* For TUN_F_CSUM, IFF_VNET_HDR */
|
||||
#endif
|
||||
#ifndef VIRTIO_NET_HDR_F_NEEDS_CSUM
|
||||
#define VIRTIO_NET_HDR_F_NEEDS_CSUM 1
|
||||
struct virtio_net_hdr {
|
||||
uint8_t flags;
|
||||
uint8_t gso_type;
|
||||
uint16_t hdr_len;
|
||||
uint16_t gso_size;
|
||||
uint16_t csum_start;
|
||||
uint16_t csum_offset;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* NS_MISSING_DEFS_H */
|
||||
|
||||
@@ -31,6 +31,7 @@ constexpr size_t TCP_TX_BUF_CAP = 1048576; /* 1 MB - host->guest */
|
||||
constexpr size_t TCP_RX_BUF_CAP = 1048576; /* 1 MB - guest->host */
|
||||
constexpr size_t PROXY_RX_BUF_CAP = 8192; /* 8 KB - proxy handshake */
|
||||
|
||||
|
||||
/* Removed MemcmpLess in favor of C++20 operator<=> */
|
||||
|
||||
struct __attribute__((packed)) FlowKey4 {
|
||||
@@ -201,7 +202,8 @@ struct Context {
|
||||
size_t num_c_host_listener_rules;
|
||||
|
||||
/* Buffer for TUN frames, moved from TLS to avoid stack/TLS pressure */
|
||||
uint8_t tun_buf[NSTUN_MTU + 4];
|
||||
uint8_t tun_buf[VNET_HDR_SIZE + NSTUN_MTU + 4];
|
||||
uint8_t last_vnet_flags; /* virtio_net_hdr.flags from last TUN read */
|
||||
|
||||
/* Buffers for recvmmsg, moved from TLS to avoid stack/TLS pressure */
|
||||
struct mmsghdr recvmmsg_msgs[VLEN];
|
||||
|
||||
@@ -140,6 +140,8 @@ static bool icmp_send_packet4(Context* ctx, uint32_t saddr, uint32_t daddr, uint
|
||||
icmp.seq = seq;
|
||||
icmp.check = 0;
|
||||
|
||||
/* Compute full ICMP4 checksum in userspace (no pseudo-header, and raw
|
||||
* sockets like ping verify it themselves before kernel can complete it) */
|
||||
uint32_t sum = compute_checksum_part(&icmp, sizeof(icmp4_hdr), 0);
|
||||
if (data && len > 0) {
|
||||
sum = compute_checksum_part(data, len, sum);
|
||||
@@ -149,8 +151,11 @@ static bool icmp_send_packet4(Context* ctx, uint32_t saddr, uint32_t daddr, uint
|
||||
memcpy(header_buf, &ip, sizeof(ip));
|
||||
memcpy(header_buf + sizeof(ip), &icmp, sizeof(icmp));
|
||||
|
||||
virtio_net_hdr vh = {};
|
||||
/* flags=0: checksum already complete, no offload needed */
|
||||
|
||||
return send_to_guest_v(
|
||||
ctx, header_buf, sizeof(header_buf), static_cast<const uint8_t*>(data), len);
|
||||
ctx, &vh, header_buf, sizeof(header_buf), static_cast<const uint8_t*>(data), len);
|
||||
}
|
||||
|
||||
static bool icmp_send_packet6(Context* ctx, const uint8_t* saddr, const uint8_t* daddr,
|
||||
@@ -188,6 +193,7 @@ static bool icmp_send_packet6(Context* ctx, const uint8_t* saddr, const uint8_t*
|
||||
memcpy(phdr.saddr, saddr, sizeof(phdr.saddr));
|
||||
memcpy(phdr.daddr, daddr, sizeof(phdr.daddr));
|
||||
|
||||
/* Compute full ICMPv6 checksum in userspace (raw sockets verify it) */
|
||||
uint32_t sum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
sum = compute_checksum_part(&icmp6, sizeof(icmp6_hdr), sum);
|
||||
if (data && len > 0) {
|
||||
@@ -198,8 +204,11 @@ static bool icmp_send_packet6(Context* ctx, const uint8_t* saddr, const uint8_t*
|
||||
memcpy(header_buf, &ip6, sizeof(ip6));
|
||||
memcpy(header_buf + sizeof(ip6), &icmp6, sizeof(icmp6));
|
||||
|
||||
virtio_net_hdr vh = {};
|
||||
/* flags=0: checksum already complete, no offload needed */
|
||||
|
||||
return send_to_guest_v(
|
||||
ctx, header_buf, sizeof(header_buf), static_cast<const uint8_t*>(data), len);
|
||||
ctx, &vh, header_buf, sizeof(header_buf), static_cast<const uint8_t*>(data), len);
|
||||
}
|
||||
|
||||
void send_icmp4_error(
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../missing_defs.h"
|
||||
|
||||
/* From <linux/in.h>, can't include directly due to conflicts with <netinet/in.h> */
|
||||
#ifndef IN_LOOPBACK
|
||||
#define IN_LOOPBACK(a) ((((long int)(a)) & 0xff000000) == 0x7f000000)
|
||||
@@ -106,6 +108,8 @@ struct __attribute__((packed)) pseudo_hdr6 {
|
||||
uint8_t next_header;
|
||||
};
|
||||
|
||||
constexpr size_t VNET_HDR_SIZE = sizeof(struct virtio_net_hdr);
|
||||
|
||||
inline uint8_t ip_version(const uint8_t* ptr) {
|
||||
return ptr[0] >> 4;
|
||||
}
|
||||
@@ -204,9 +208,9 @@ inline uint16_t compute_checksum(const void* buf, size_t len, uint32_t sum = 0)
|
||||
inline struct sockaddr_in init_sockaddr_in(unsigned short family) {
|
||||
#ifdef sin_zero
|
||||
return (struct sockaddr_in){
|
||||
.sin_family = family, .sin_port = 0, .sin_addr = {0}, .sin_zero = {0}};
|
||||
.sin_family = family, .sin_port = 0, .sin_addr = {0}, .sin_zero = {0},};
|
||||
#else
|
||||
return (struct sockaddr_in){.sin_family = family, .sin_port = 0, .sin_addr = {0}};
|
||||
return (struct sockaddr_in){.sin_family = family, .sin_port = 0, .sin_addr = {0},};
|
||||
#endif
|
||||
}
|
||||
inline struct sockaddr_in6 init_sockaddr_in6(unsigned short family) {
|
||||
@@ -214,7 +218,7 @@ inline struct sockaddr_in6 init_sockaddr_in6(unsigned short family) {
|
||||
.sin6_port = 0,
|
||||
.sin6_flowinfo = 0,
|
||||
.sin6_addr = {{{0}}},
|
||||
.sin6_scope_id = 0};
|
||||
.sin6_scope_id = 0,};
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ void host_callback(int fd, uint32_t events, void* data) {
|
||||
};
|
||||
|
||||
struct ifreq ifr = {};
|
||||
ifr.ifr_flags = IFF_TUN | IFF_NO_PI; /* TUN, no packet info */
|
||||
ifr.ifr_flags = IFF_TUN | IFF_NO_PI | IFF_VNET_HDR; /* TUN, no packet info, vnet header */
|
||||
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", nsj->njc.user_net().ns_iface().c_str());
|
||||
|
||||
if (ioctl(tap_fd, TUNSETIFF, &ifr) < 0) {
|
||||
@@ -207,6 +207,13 @@ void host_callback(int fd, uint32_t events, void* data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Enable checksum offload: kernel computes L4 checksums for us */
|
||||
unsigned int offload = TUN_F_CSUM;
|
||||
if (ioctl(tap_fd, TUNSETOFFLOAD, offload) < 0) {
|
||||
PLOG_W("ioctl(TUNSETOFFLOAD, TUN_F_CSUM) failed, checksums will be computed in "
|
||||
"userspace");
|
||||
}
|
||||
|
||||
/* Configure IP, MAC, UP, route. */
|
||||
if (!nstun::configIface(nsj)) {
|
||||
LOG_E("nstun::configIface() failed");
|
||||
@@ -238,7 +245,15 @@ static void tapCb(int fd, uint32_t /* events */, void* data) {
|
||||
ctx->tap_fd = -1;
|
||||
return;
|
||||
}
|
||||
handle_tun_frame(ctx, ctx->tun_buf, n);
|
||||
/* Strip the virtio_net_hdr prefix added by IFF_VNET_HDR */
|
||||
if ((size_t)n <= nstun::VNET_HDR_SIZE) {
|
||||
continue; /* Too small to contain anything after vnet header */
|
||||
}
|
||||
struct virtio_net_hdr vh;
|
||||
memcpy(&vh, ctx->tun_buf, sizeof(vh));
|
||||
ctx->last_vnet_flags = vh.flags;
|
||||
handle_tun_frame(
|
||||
ctx, ctx->tun_buf + nstun::VNET_HDR_SIZE, n - nstun::VNET_HDR_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
197
nstun/tcp.cc
197
nstun/tcp.cc
@@ -166,17 +166,25 @@ static void tcp_send_rst4(Context* ctx, const FlowKey4& key4, uint32_t seq, uint
|
||||
frame.tcp.check = 0;
|
||||
frame.tcp.urg_ptr = 0;
|
||||
|
||||
pseudo_hdr4 phdr = {.saddr = key4.daddr4,
|
||||
pseudo_hdr4 phdr = {
|
||||
.saddr = key4.daddr4,
|
||||
.daddr = key4.saddr4,
|
||||
.zero = 0,
|
||||
.protocol = IPPROTO_TCP,
|
||||
.len = htons(sizeof(tcp_hdr))};
|
||||
.len = htons(sizeof(tcp_hdr)),
|
||||
};
|
||||
|
||||
/* Seed check field with pseudo-header sum only; kernel adds the rest */
|
||||
uint32_t sum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
sum = compute_checksum_part(&frame.tcp, sizeof(tcp_hdr), sum);
|
||||
frame.tcp.check = finalize_checksum(sum);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
frame.tcp.check = (uint16_t)sum;
|
||||
|
||||
if (!send_to_guest_v(ctx, &frame, sizeof(frame), nullptr, 0)) {
|
||||
virtio_net_hdr vh = {};
|
||||
vh.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
vh.csum_start = sizeof(ip4_hdr);
|
||||
vh.csum_offset = offsetof(tcp_hdr, check);
|
||||
|
||||
if (!send_to_guest_v(ctx, &vh, &frame, sizeof(frame), nullptr, 0)) {
|
||||
LOG_W("tcp_send_rst4: failed to send RST to guest");
|
||||
}
|
||||
}
|
||||
@@ -216,11 +224,17 @@ static void tcp_send_rst6(Context* ctx, const FlowKey6& key6, uint32_t seq, uint
|
||||
memcpy(phdr.saddr, key6.daddr6, sizeof(phdr.saddr));
|
||||
memcpy(phdr.daddr, key6.saddr6, sizeof(phdr.daddr));
|
||||
|
||||
/* Seed check field with pseudo-header sum only; kernel adds the rest */
|
||||
uint32_t sum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
sum = compute_checksum_part(&frame.tcp, sizeof(tcp_hdr), sum);
|
||||
frame.tcp.check = finalize_checksum(sum);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
frame.tcp.check = (uint16_t)sum;
|
||||
|
||||
if (!send_to_guest_v(ctx, &frame, sizeof(frame), nullptr, 0)) {
|
||||
virtio_net_hdr vh = {};
|
||||
vh.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
vh.csum_start = sizeof(ip6_hdr);
|
||||
vh.csum_offset = offsetof(tcp_hdr, check);
|
||||
|
||||
if (!send_to_guest_v(ctx, &vh, &frame, sizeof(frame), nullptr, 0)) {
|
||||
LOG_W("tcp_send_rst6: failed to send RST to guest");
|
||||
}
|
||||
}
|
||||
@@ -283,21 +297,19 @@ bool tcp_send_packet4(
|
||||
frame.tcp.check = 0;
|
||||
frame.tcp.urg_ptr = 0;
|
||||
|
||||
pseudo_hdr4 phdr = {.saddr = flow->header.key4.daddr4,
|
||||
pseudo_hdr4 phdr = {
|
||||
.saddr = flow->header.key4.daddr4,
|
||||
.daddr = flow->header.key4.saddr4,
|
||||
.zero = 0,
|
||||
.protocol = IPPROTO_TCP,
|
||||
.len = htons(sizeof(tcp_hdr) + opt_len + len)};
|
||||
.len = htons(sizeof(tcp_hdr) + opt_len + len),
|
||||
};
|
||||
|
||||
/* Seed check field with pseudo-header sum only; kernel adds L4 header + options + payload
|
||||
*/
|
||||
uint32_t sum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
sum = compute_checksum_part(&frame.tcp, sizeof(tcp_hdr), sum);
|
||||
if (opt_len > 0) {
|
||||
sum = compute_checksum_part(options, opt_len, sum);
|
||||
}
|
||||
if (data && len > 0) {
|
||||
sum = compute_checksum_part(data, len, sum);
|
||||
}
|
||||
frame.tcp.check = finalize_checksum(sum);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
frame.tcp.check = (uint16_t)sum;
|
||||
|
||||
if (opt_len > 0) {
|
||||
memcpy(frame.options, options, opt_len);
|
||||
@@ -307,7 +319,13 @@ bool tcp_send_packet4(
|
||||
ip4_to_string(frame.ip.saddr).c_str(), ntohs(frame.tcp.source),
|
||||
ip4_to_string(frame.ip.daddr).c_str(), ntohs(frame.tcp.dest), flags);
|
||||
|
||||
return send_to_guest_v(ctx, &frame, sizeof(ip4_hdr) + sizeof(tcp_hdr) + opt_len, data, len);
|
||||
virtio_net_hdr vh = {};
|
||||
vh.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
vh.csum_start = sizeof(ip4_hdr);
|
||||
vh.csum_offset = offsetof(tcp_hdr, check);
|
||||
|
||||
return send_to_guest_v(
|
||||
ctx, &vh, &frame, sizeof(ip4_hdr) + sizeof(tcp_hdr) + opt_len, data, len);
|
||||
}
|
||||
|
||||
bool tcp_send_packet6(
|
||||
@@ -356,21 +374,23 @@ bool tcp_send_packet6(
|
||||
memcpy(phdr.saddr, flow->header.key6.daddr6, sizeof(phdr.saddr));
|
||||
memcpy(phdr.daddr, flow->header.key6.saddr6, sizeof(phdr.daddr));
|
||||
|
||||
/* Seed check field with pseudo-header sum only; kernel adds L4 header + options + payload
|
||||
*/
|
||||
uint32_t sum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
sum = compute_checksum_part(&frame.tcp, sizeof(tcp_hdr), sum);
|
||||
if (opt_len > 0) {
|
||||
sum = compute_checksum_part(options, opt_len, sum);
|
||||
}
|
||||
if (data && len > 0) {
|
||||
sum = compute_checksum_part(data, len, sum);
|
||||
}
|
||||
frame.tcp.check = finalize_checksum(sum);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
frame.tcp.check = (uint16_t)sum;
|
||||
|
||||
if (opt_len > 0) {
|
||||
memcpy(frame.options, options, opt_len);
|
||||
}
|
||||
|
||||
return send_to_guest_v(ctx, &frame, sizeof(ip6_hdr) + sizeof(tcp_hdr) + opt_len, data, len);
|
||||
virtio_net_hdr vh = {};
|
||||
vh.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
vh.csum_start = sizeof(ip6_hdr);
|
||||
vh.csum_offset = offsetof(tcp_hdr, check);
|
||||
|
||||
return send_to_guest_v(
|
||||
ctx, &vh, &frame, sizeof(ip6_hdr) + sizeof(tcp_hdr) + opt_len, data, len);
|
||||
}
|
||||
|
||||
void tcp_rst_and_destroy(Context* ctx, TcpFlow* flow) {
|
||||
@@ -526,28 +546,55 @@ bool flush_to_host(Context* ctx, TcpFlow* flow) {
|
||||
/* --- state handlers ------------------------------------ */
|
||||
|
||||
static const TcpStateHandlers kStateTable[] = {
|
||||
[static_cast<int>(TcpState::SYN_SENT)] = {.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest},
|
||||
[static_cast<int>(TcpState::SOCKS5_INIT)] = {.on_host_data = handle_socks5_init_host,
|
||||
.on_guest_packet = handle_socks5_init_guest},
|
||||
[static_cast<int>(TcpState::SOCKS5_CONNECTING)] = {.on_host_data =
|
||||
handle_socks5_connecting_host,
|
||||
.on_guest_packet = handle_socks5_init_guest},
|
||||
[static_cast<int>(TcpState::HTTP_CONNECT_WAIT)] = {.on_host_data =
|
||||
handle_http_connect_wait_host,
|
||||
.on_guest_packet = handle_socks5_init_guest},
|
||||
[static_cast<int>(TcpState::ESTABLISHED)] = {.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest},
|
||||
[static_cast<int>(TcpState::FIN_WAIT_1)] = {.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest},
|
||||
[static_cast<int>(TcpState::FIN_WAIT_2)] = {.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest},
|
||||
[static_cast<int>(TcpState::CLOSING)] = {.on_host_data = handle_draining_state_host,
|
||||
.on_guest_packet = handle_draining_state_guest},
|
||||
[static_cast<int>(TcpState::TIME_WAIT)] = {.on_host_data = handle_draining_state_host,
|
||||
.on_guest_packet = handle_draining_state_guest},
|
||||
[static_cast<int>(TcpState::CLOSE_WAIT)] = {.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest},
|
||||
[static_cast<int>(TcpState::SYN_SENT)] =
|
||||
{
|
||||
.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::SOCKS5_INIT)] =
|
||||
{
|
||||
.on_host_data = handle_socks5_init_host,
|
||||
.on_guest_packet = handle_socks5_init_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::SOCKS5_CONNECTING)] =
|
||||
{
|
||||
.on_host_data = handle_socks5_connecting_host,
|
||||
.on_guest_packet = handle_socks5_init_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::HTTP_CONNECT_WAIT)] =
|
||||
{
|
||||
.on_host_data = handle_http_connect_wait_host,
|
||||
.on_guest_packet = handle_socks5_init_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::ESTABLISHED)] =
|
||||
{
|
||||
.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::FIN_WAIT_1)] =
|
||||
{
|
||||
.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::FIN_WAIT_2)] =
|
||||
{
|
||||
.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::CLOSING)] =
|
||||
{
|
||||
.on_host_data = handle_draining_state_host,
|
||||
.on_guest_packet = handle_draining_state_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::TIME_WAIT)] =
|
||||
{
|
||||
.on_host_data = handle_draining_state_host,
|
||||
.on_guest_packet = handle_draining_state_guest,
|
||||
},
|
||||
[static_cast<int>(TcpState::CLOSE_WAIT)] = {
|
||||
.on_host_data = handle_data_transfer_host,
|
||||
.on_guest_packet = handle_data_transfer_guest,
|
||||
},
|
||||
};
|
||||
|
||||
static bool tcp_should_reenable_host_rx(const TcpFlow* flow) {
|
||||
@@ -1149,17 +1196,21 @@ void handle_tcp4(Context* ctx, const ip4_hdr* ip, const uint8_t* payload, size_t
|
||||
return;
|
||||
}
|
||||
|
||||
/* Validate TCP checksum */
|
||||
pseudo_hdr4 phdr = {.saddr = ip->saddr,
|
||||
.daddr = ip->daddr,
|
||||
.zero = 0,
|
||||
.protocol = IPPROTO_TCP,
|
||||
.len = htons(payload_len)};
|
||||
uint32_t csum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
csum = compute_checksum_part(payload, payload_len, csum);
|
||||
if (finalize_checksum(csum) != 0) {
|
||||
LOG_D("Invalid IPv4 TCP checksum, dropping");
|
||||
return;
|
||||
/* Validate TCP checksum (skip if guest used checksum offload) */
|
||||
if (!(ctx->last_vnet_flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
|
||||
pseudo_hdr4 phdr = {
|
||||
.saddr = ip->saddr,
|
||||
.daddr = ip->daddr,
|
||||
.zero = 0,
|
||||
.protocol = IPPROTO_TCP,
|
||||
.len = htons(payload_len),
|
||||
};
|
||||
uint32_t csum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
csum = compute_checksum_part(payload, payload_len, csum);
|
||||
if (finalize_checksum(csum) != 0) {
|
||||
LOG_D("Invalid IPv4 TCP checksum, dropping");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FlowKey4 key4 = {ip->saddr, ip->daddr, tcp.source, tcp.dest};
|
||||
@@ -1582,17 +1633,19 @@ void handle_tcp6(Context* ctx, const ip6_hdr* ip, const uint8_t* payload, size_t
|
||||
return;
|
||||
}
|
||||
|
||||
/* Validate TCP checksum */
|
||||
pseudo_hdr6 phdr = {};
|
||||
phdr.len = htonl(payload_len);
|
||||
phdr.next_header = IPPROTO_TCP;
|
||||
memcpy(phdr.saddr, ip->saddr, sizeof(phdr.saddr));
|
||||
memcpy(phdr.daddr, ip->daddr, sizeof(phdr.daddr));
|
||||
uint32_t csum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
csum = compute_checksum_part(payload, payload_len, csum);
|
||||
if (finalize_checksum(csum) != 0) {
|
||||
LOG_D("Invalid IPv6 TCP checksum, dropping");
|
||||
return;
|
||||
/* Validate TCP checksum (skip if guest used checksum offload) */
|
||||
if (!(ctx->last_vnet_flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
|
||||
pseudo_hdr6 phdr = {};
|
||||
phdr.len = htonl(payload_len);
|
||||
phdr.next_header = IPPROTO_TCP;
|
||||
memcpy(phdr.saddr, ip->saddr, sizeof(phdr.saddr));
|
||||
memcpy(phdr.daddr, ip->daddr, sizeof(phdr.daddr));
|
||||
uint32_t csum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
csum = compute_checksum_part(payload, payload_len, csum);
|
||||
if (finalize_checksum(csum) != 0) {
|
||||
LOG_D("Invalid IPv6 TCP checksum, dropping");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FlowKey6 key6 = {};
|
||||
|
||||
29
nstun/tun.cc
29
nstun/tun.cc
@@ -13,21 +13,32 @@
|
||||
|
||||
namespace nstun {
|
||||
|
||||
bool send_to_guest_v(
|
||||
Context* ctx, const void* header, size_t header_len, const void* payload, size_t payload_len) {
|
||||
bool send_to_guest_v(Context* ctx, const virtio_net_hdr* vh, const void* header, size_t header_len,
|
||||
const void* payload, size_t payload_len) {
|
||||
if (header_len > NSTUN_MTU || payload_len > NSTUN_MTU - header_len) {
|
||||
LOG_W("send_to_guest_v: frame too large (%zu + %zu)", header_len, payload_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = const_cast<void*>(header);
|
||||
iov[0].iov_len = header_len;
|
||||
iov[1].iov_base = const_cast<void*>(payload);
|
||||
iov[1].iov_len = payload_len;
|
||||
struct iovec iov[3];
|
||||
int iovcnt = 0;
|
||||
|
||||
size_t total_len = header_len + payload_len;
|
||||
ssize_t written = TEMP_FAILURE_RETRY(writev(ctx->tap_fd, iov, payload_len > 0 ? 2 : 1));
|
||||
iov[iovcnt].iov_base = const_cast<virtio_net_hdr*>(vh);
|
||||
iov[iovcnt].iov_len = VNET_HDR_SIZE;
|
||||
iovcnt++;
|
||||
|
||||
iov[iovcnt].iov_base = const_cast<void*>(header);
|
||||
iov[iovcnt].iov_len = header_len;
|
||||
iovcnt++;
|
||||
|
||||
if (payload_len > 0) {
|
||||
iov[iovcnt].iov_base = const_cast<void*>(payload);
|
||||
iov[iovcnt].iov_len = payload_len;
|
||||
iovcnt++;
|
||||
}
|
||||
|
||||
size_t total_len = VNET_HDR_SIZE + header_len + payload_len;
|
||||
ssize_t written = TEMP_FAILURE_RETRY(writev(ctx->tap_fd, iov, iovcnt));
|
||||
if (written < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
return false;
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
namespace nstun {
|
||||
|
||||
bool send_to_guest_v(
|
||||
Context* ctx, const void* header, size_t header_len, const void* payload, size_t payload_len);
|
||||
bool send_to_guest_v(Context* ctx, const virtio_net_hdr* vh, const void* header, size_t header_len,
|
||||
const void* payload, size_t payload_len);
|
||||
void handle_tun_frame(Context* ctx, const uint8_t* buf, size_t len);
|
||||
|
||||
} // namespace nstun
|
||||
|
||||
51
nstun/udp.cc
51
nstun/udp.cc
@@ -179,19 +179,18 @@ static bool udp_send_packet4(Context* ctx, uint32_t saddr, uint32_t daddr, uint1
|
||||
udp.len = htons(sizeof(udp_hdr) + len);
|
||||
udp.check = 0;
|
||||
|
||||
pseudo_hdr4 phdr = {.saddr = saddr,
|
||||
pseudo_hdr4 phdr = {
|
||||
.saddr = saddr,
|
||||
.daddr = daddr,
|
||||
.zero = 0,
|
||||
.protocol = IPPROTO_UDP,
|
||||
.len = htons(sizeof(udp_hdr) + len)};
|
||||
.len = htons(sizeof(udp_hdr) + len),
|
||||
};
|
||||
|
||||
/* Seed check field with pseudo-header sum only; kernel adds L4 header + payload */
|
||||
uint32_t sum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
sum = compute_checksum_part(&udp, sizeof(udp_hdr), sum);
|
||||
if (data && len > 0) {
|
||||
sum = compute_checksum_part(data, len, sum);
|
||||
}
|
||||
|
||||
udp.check = finalize_checksum(sum);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
udp.check = (uint16_t)sum;
|
||||
if (udp.check == 0) {
|
||||
udp.check = 0xFFFF;
|
||||
}
|
||||
@@ -199,7 +198,12 @@ static bool udp_send_packet4(Context* ctx, uint32_t saddr, uint32_t daddr, uint1
|
||||
memcpy(header_buf, &ip, sizeof(ip));
|
||||
memcpy(header_buf + sizeof(ip), &udp, sizeof(udp));
|
||||
|
||||
return send_to_guest_v(ctx, header_buf, sizeof(header_buf), data, len);
|
||||
virtio_net_hdr vh = {};
|
||||
vh.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
vh.csum_start = sizeof(ip4_hdr);
|
||||
vh.csum_offset = offsetof(udp_hdr, check);
|
||||
|
||||
return send_to_guest_v(ctx, &vh, header_buf, sizeof(header_buf), data, len);
|
||||
}
|
||||
|
||||
/* Forward declare for use in handle_host_udp */
|
||||
@@ -693,13 +697,15 @@ void handle_udp4_impl(
|
||||
uint16_t guest_port = ntohs(udp.source);
|
||||
uint16_t dest_port = ntohs(udp.dest);
|
||||
|
||||
/* Validate UDP checksum (optional for IPv4 when field is 0) */
|
||||
if (udp.check != 0) {
|
||||
pseudo_hdr4 phdr = {.saddr = ip->saddr,
|
||||
/* Validate UDP checksum (optional for IPv4 when field is 0; skip if offloaded) */
|
||||
if (udp.check != 0 && !(ctx->last_vnet_flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
|
||||
pseudo_hdr4 phdr = {
|
||||
.saddr = ip->saddr,
|
||||
.daddr = ip->daddr,
|
||||
.zero = 0,
|
||||
.protocol = IPPROTO_UDP,
|
||||
.len = htons(payload_size)};
|
||||
.len = htons(payload_size),
|
||||
};
|
||||
uint32_t csum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
csum = compute_checksum_part(payload_data, payload_size, csum);
|
||||
if (finalize_checksum(csum) != 0) {
|
||||
@@ -1071,12 +1077,10 @@ static bool udp_send_packet6(Context* ctx, const uint8_t* saddr, const uint8_t*
|
||||
memcpy(phdr.saddr, saddr, sizeof(phdr.saddr));
|
||||
memcpy(phdr.daddr, daddr, sizeof(phdr.daddr));
|
||||
|
||||
/* Seed check field with pseudo-header sum only; kernel adds L4 header + payload */
|
||||
uint32_t sum = compute_checksum_part(&phdr, sizeof(phdr), 0);
|
||||
sum = compute_checksum_part(&r_udp, sizeof(udp_hdr), sum);
|
||||
if (data && len > 0) {
|
||||
sum = compute_checksum_part(data, len, sum);
|
||||
}
|
||||
r_udp.check = finalize_checksum(sum);
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
r_udp.check = (uint16_t)sum;
|
||||
if (r_udp.check == 0) {
|
||||
r_udp.check = 0xFFFF;
|
||||
}
|
||||
@@ -1084,7 +1088,12 @@ static bool udp_send_packet6(Context* ctx, const uint8_t* saddr, const uint8_t*
|
||||
memcpy(header_buf, &r_ip, sizeof(r_ip));
|
||||
memcpy(header_buf + sizeof(ip6_hdr), &r_udp, sizeof(r_udp));
|
||||
|
||||
return send_to_guest_v(ctx, header_buf, sizeof(header_buf), data, len);
|
||||
virtio_net_hdr vh = {};
|
||||
vh.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
vh.csum_start = sizeof(ip6_hdr);
|
||||
vh.csum_offset = offsetof(udp_hdr, check);
|
||||
|
||||
return send_to_guest_v(ctx, &vh, header_buf, sizeof(header_buf), data, len);
|
||||
}
|
||||
|
||||
static UdpFlow* udp_create_flow6(Context* ctx, const FlowKey6& key6, const RuleResult& rule,
|
||||
@@ -1179,8 +1188,8 @@ void handle_udp6_impl(
|
||||
uint16_t guest_port = ntohs(udp.source);
|
||||
uint16_t dest_port = ntohs(udp.dest);
|
||||
|
||||
/* Validate UDP checksum (mandatory for IPv6 per RFC 8200 §8.1) */
|
||||
{
|
||||
/* Validate UDP checksum (mandatory for IPv6; skip if offloaded) */
|
||||
if (!(ctx->last_vnet_flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
|
||||
pseudo_hdr6 phdr = {};
|
||||
phdr.len = htonl(payload_size);
|
||||
phdr.next_header = IPPROTO_UDP;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#define SO_SNDBUFFORCE 32
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
namespace unotify {
|
||||
|
||||
enum class SyscallCategory : uint8_t {
|
||||
|
||||
Reference in New Issue
Block a user