nstun: implement kernel checksum offloading for TUN traffic

This commit is contained in:
Robert Swiecki
2026-04-12 17:35:24 +02:00
parent 078ab1c24c
commit ca4e1654a6
10 changed files with 237 additions and 112 deletions

View File

@@ -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 */

View File

@@ -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];

View File

@@ -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(

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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 = {};

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -21,6 +21,8 @@
#define SO_SNDBUFFORCE 32
#endif
namespace unotify {
enum class SyscallCategory : uint8_t {