mirror of
https://github.com/torvalds/linux.git
synced 2026-06-02 11:33:28 +02:00
Merge branch 'selftest-extend-tun-virtio-coverage-for-gso-over-udp-tunnel'
Xu Du says: ==================== selftest: Extend tun/virtio coverage for GSO over UDP tunnel The design strategy is to extend the existing tun testing infrastructure to support this new use-case, rather than introducing a new or parallel framework. This allows for better integration and re-use of existing test logic. ==================== Link: https://patch.msgid.link/cover.1768979440.git.xudu@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
56f9058eb3
|
|
@ -183,7 +183,6 @@ TEST_GEN_PROGS := \
|
|||
tap \
|
||||
tcp_port_share \
|
||||
tls \
|
||||
tun \
|
||||
# end of TEST_GEN_PROGS
|
||||
|
||||
TEST_FILES := \
|
||||
|
|
@ -195,7 +194,11 @@ TEST_FILES := \
|
|||
|
||||
# YNL files, must be before "include ..lib.mk"
|
||||
YNL_GEN_FILES := busy_poller
|
||||
YNL_GEN_PROGS := netlink-dumps
|
||||
YNL_GEN_PROGS := \
|
||||
netlink-dumps \
|
||||
tun \
|
||||
# end of YNL_GEN_PROGS
|
||||
|
||||
TEST_GEN_FILES += $(YNL_GEN_FILES)
|
||||
TEST_GEN_PROGS += $(YNL_GEN_PROGS)
|
||||
|
||||
|
|
@ -206,7 +209,14 @@ TEST_INCLUDES := forwarding/lib.sh
|
|||
include ../lib.mk
|
||||
|
||||
# YNL build
|
||||
YNL_GENS := netdev
|
||||
YNL_GENS := \
|
||||
netdev \
|
||||
rt-addr \
|
||||
rt-link \
|
||||
rt-neigh \
|
||||
rt-route \
|
||||
# end of YNL_GENS
|
||||
|
||||
include ynl.mk
|
||||
|
||||
$(OUTPUT)/epoll_busy_poll: LDLIBS += -lcap
|
||||
|
|
|
|||
|
|
@ -8,14 +8,119 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "kselftest_harness.h"
|
||||
#include "tuntap_helpers.h"
|
||||
|
||||
static const char param_dev_geneve_name[] = "geneve1";
|
||||
static unsigned char param_hwaddr_outer_dst[] = { 0x00, 0xfe, 0x98,
|
||||
0x14, 0x22, 0x42 };
|
||||
static unsigned char param_hwaddr_outer_src[] = { 0x00, 0xfe, 0x98,
|
||||
0x94, 0xd2, 0x43 };
|
||||
static unsigned char param_hwaddr_inner_dst[] = { 0x00, 0xfe, 0x98,
|
||||
0x94, 0x22, 0xcc };
|
||||
static unsigned char param_hwaddr_inner_src[] = { 0x00, 0xfe, 0x98,
|
||||
0x94, 0xd2, 0xdd };
|
||||
|
||||
static struct in_addr param_ipaddr4_outer_dst = {
|
||||
__constant_htonl(0xac100001),
|
||||
};
|
||||
|
||||
static struct in_addr param_ipaddr4_outer_src = {
|
||||
__constant_htonl(0xac100002),
|
||||
};
|
||||
|
||||
static struct in_addr param_ipaddr4_inner_dst = {
|
||||
__constant_htonl(0xac100101),
|
||||
};
|
||||
|
||||
static struct in_addr param_ipaddr4_inner_src = {
|
||||
__constant_htonl(0xac100102),
|
||||
};
|
||||
|
||||
static struct in6_addr param_ipaddr6_outer_dst = {
|
||||
{ { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
|
||||
};
|
||||
|
||||
static struct in6_addr param_ipaddr6_outer_src = {
|
||||
{ { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
|
||||
};
|
||||
|
||||
static struct in6_addr param_ipaddr6_inner_dst = {
|
||||
{ { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
|
||||
};
|
||||
|
||||
static struct in6_addr param_ipaddr6_inner_src = {
|
||||
{ { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
|
||||
};
|
||||
|
||||
#ifndef BIT
|
||||
#define BIT(nr) (1UL << (nr))
|
||||
#endif
|
||||
|
||||
#define VN_ID 1
|
||||
#define VN_PORT 4789
|
||||
#define UDP_SRC_PORT 22
|
||||
#define UDP_DST_PORT 48878
|
||||
#define IPPREFIX_LEN 24
|
||||
#define IP6PREFIX_LEN 64
|
||||
#define TIMEOUT_SEC 10
|
||||
#define TIMEOUT_USEC 100000
|
||||
#define MAX_RETRIES 20
|
||||
|
||||
#define UDP_TUNNEL_GENEVE_4IN4 0x01
|
||||
#define UDP_TUNNEL_GENEVE_6IN4 0x02
|
||||
#define UDP_TUNNEL_GENEVE_4IN6 0x04
|
||||
#define UDP_TUNNEL_GENEVE_6IN6 0x08
|
||||
|
||||
#define UDP_TUNNEL_MAX_SEGMENTS BIT(7)
|
||||
|
||||
#define UDP_TUNNEL_OUTER_IPV4 (UDP_TUNNEL_GENEVE_4IN4 | UDP_TUNNEL_GENEVE_6IN4)
|
||||
#define UDP_TUNNEL_INNER_IPV4 (UDP_TUNNEL_GENEVE_4IN4 | UDP_TUNNEL_GENEVE_4IN6)
|
||||
|
||||
#define UDP_TUNNEL_GENEVE_4IN4_HDRLEN \
|
||||
(ETH_HLEN + 2 * sizeof(struct iphdr) + GENEVE_HLEN + \
|
||||
2 * sizeof(struct udphdr))
|
||||
#define UDP_TUNNEL_GENEVE_6IN6_HDRLEN \
|
||||
(ETH_HLEN + 2 * sizeof(struct ipv6hdr) + GENEVE_HLEN + \
|
||||
2 * sizeof(struct udphdr))
|
||||
#define UDP_TUNNEL_GENEVE_4IN6_HDRLEN \
|
||||
(ETH_HLEN + sizeof(struct iphdr) + sizeof(struct ipv6hdr) + \
|
||||
GENEVE_HLEN + 2 * sizeof(struct udphdr))
|
||||
#define UDP_TUNNEL_GENEVE_6IN4_HDRLEN \
|
||||
(ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct iphdr) + \
|
||||
GENEVE_HLEN + 2 * sizeof(struct udphdr))
|
||||
|
||||
#define UDP_TUNNEL_HDRLEN(type) \
|
||||
((type) == UDP_TUNNEL_GENEVE_4IN4 ? UDP_TUNNEL_GENEVE_4IN4_HDRLEN : \
|
||||
(type) == UDP_TUNNEL_GENEVE_6IN6 ? UDP_TUNNEL_GENEVE_6IN6_HDRLEN : \
|
||||
(type) == UDP_TUNNEL_GENEVE_4IN6 ? UDP_TUNNEL_GENEVE_4IN6_HDRLEN : \
|
||||
(type) == UDP_TUNNEL_GENEVE_6IN4 ? UDP_TUNNEL_GENEVE_6IN4_HDRLEN : \
|
||||
0)
|
||||
|
||||
#define UDP_TUNNEL_MSS(type) (ETH_DATA_LEN - UDP_TUNNEL_HDRLEN(type))
|
||||
#define UDP_TUNNEL_MAX(type, is_tap) \
|
||||
(ETH_MAX_MTU - UDP_TUNNEL_HDRLEN(type) - ((is_tap) ? ETH_HLEN : 0))
|
||||
|
||||
#define TUN_VNET_TNL_SIZE sizeof(struct virtio_net_hdr_v1_hash_tunnel)
|
||||
#define MAX_VNET_TUNNEL_PACKET_SZ \
|
||||
(TUN_VNET_TNL_SIZE + ETH_HLEN + UDP_TUNNEL_GENEVE_6IN6_HDRLEN + \
|
||||
ETH_MAX_MTU)
|
||||
|
||||
struct geneve_setup_config {
|
||||
int family;
|
||||
union {
|
||||
struct in_addr r4;
|
||||
struct in6_addr r6;
|
||||
} remote;
|
||||
__be32 vnid;
|
||||
__be16 vnport;
|
||||
unsigned char hwaddr[6];
|
||||
uint8_t csum;
|
||||
};
|
||||
|
||||
static int tun_attach(int fd, char *dev)
|
||||
{
|
||||
|
|
@ -25,7 +130,7 @@ static int tun_attach(int fd, char *dev)
|
|||
strcpy(ifr.ifr_name, dev);
|
||||
ifr.ifr_flags = IFF_ATTACH_QUEUE;
|
||||
|
||||
return ioctl(fd, TUNSETQUEUE, (void *) &ifr);
|
||||
return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
|
||||
}
|
||||
|
||||
static int tun_detach(int fd, char *dev)
|
||||
|
|
@ -36,7 +141,7 @@ static int tun_detach(int fd, char *dev)
|
|||
strcpy(ifr.ifr_name, dev);
|
||||
ifr.ifr_flags = IFF_DETACH_QUEUE;
|
||||
|
||||
return ioctl(fd, TUNSETQUEUE, (void *) &ifr);
|
||||
return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
|
||||
}
|
||||
|
||||
static int tun_alloc(char *dev)
|
||||
|
|
@ -54,7 +159,7 @@ static int tun_alloc(char *dev)
|
|||
strcpy(ifr.ifr_name, dev);
|
||||
ifr.ifr_flags = IFF_TAP | IFF_NAPI | IFF_MULTI_QUEUE;
|
||||
|
||||
err = ioctl(fd, TUNSETIFF, (void *) &ifr);
|
||||
err = ioctl(fd, TUNSETIFF, (void *)&ifr);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "can't TUNSETIFF: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
|
|
@ -66,42 +171,315 @@ static int tun_alloc(char *dev)
|
|||
|
||||
static int tun_delete(char *dev)
|
||||
{
|
||||
struct {
|
||||
struct nlmsghdr nh;
|
||||
struct ifinfomsg ifm;
|
||||
unsigned char data[64];
|
||||
} req;
|
||||
struct rtattr *rta;
|
||||
int ret, rtnl;
|
||||
return ip_link_del(dev);
|
||||
}
|
||||
|
||||
rtnl = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||
if (rtnl < 0) {
|
||||
fprintf(stderr, "can't open rtnl: %s\n", strerror(errno));
|
||||
return 1;
|
||||
static int tun_open(char *dev, const int flags, const int hdrlen,
|
||||
const int features, const unsigned char *mac_addr)
|
||||
{
|
||||
struct ifreq ifr = { 0 };
|
||||
int fd, sk = -1;
|
||||
|
||||
fd = open("/dev/net/tun", O_RDWR);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.nh.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.ifm)));
|
||||
req.nh.nlmsg_flags = NLM_F_REQUEST;
|
||||
req.nh.nlmsg_type = RTM_DELLINK;
|
||||
ifr.ifr_flags = flags;
|
||||
if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) {
|
||||
perror("ioctl(TUNSETIFF)");
|
||||
goto err;
|
||||
}
|
||||
strcpy(dev, ifr.ifr_name);
|
||||
|
||||
req.ifm.ifi_family = AF_UNSPEC;
|
||||
if (hdrlen > 0) {
|
||||
if (ioctl(fd, TUNSETVNETHDRSZ, &hdrlen) < 0) {
|
||||
perror("ioctl(TUNSETVNETHDRSZ)");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nh.nlmsg_len));
|
||||
rta->rta_type = IFLA_IFNAME;
|
||||
rta->rta_len = RTA_LENGTH(IFNAMSIZ);
|
||||
req.nh.nlmsg_len += rta->rta_len;
|
||||
memcpy(RTA_DATA(rta), dev, IFNAMSIZ);
|
||||
if (features) {
|
||||
if (ioctl(fd, TUNSETOFFLOAD, features) < 0) {
|
||||
perror("ioctl(TUNSETOFFLOAD)");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
ret = send(rtnl, &req, req.nh.nlmsg_len, 0);
|
||||
sk = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (sk < 0) {
|
||||
perror("socket");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ioctl(sk, SIOCGIFFLAGS, &ifr) < 0) {
|
||||
perror("ioctl(SIOCGIFFLAGS)");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
|
||||
if (ioctl(sk, SIOCSIFFLAGS, &ifr) < 0) {
|
||||
perror("ioctl(SIOCSIFFLAGS)");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (mac_addr && flags & IFF_TAP) {
|
||||
ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
|
||||
memcpy(ifr.ifr_hwaddr.sa_data, mac_addr, ETH_ALEN);
|
||||
|
||||
if (ioctl(sk, SIOCSIFHWADDR, &ifr) < 0) {
|
||||
perror("ioctl(SIOCSIFHWADDR)");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (sk >= 0)
|
||||
close(sk);
|
||||
return fd;
|
||||
|
||||
err:
|
||||
close(fd);
|
||||
fd = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static size_t sockaddr_len(int family)
|
||||
{
|
||||
return (family == AF_INET) ? sizeof(struct sockaddr_in) :
|
||||
sizeof(struct sockaddr_in6);
|
||||
}
|
||||
|
||||
static int geneve_fill_newlink(struct rt_link_newlink_req *req, void *data)
|
||||
{
|
||||
struct geneve_setup_config *cfg = data;
|
||||
|
||||
#define SET_GENEVE_REMOTE rt_link_newlink_req_set_linkinfo_data_geneve_remote
|
||||
#define SET_GENEVE_REMOTE6 rt_link_newlink_req_set_linkinfo_data_geneve_remote6
|
||||
|
||||
rt_link_newlink_req_set_address(req, cfg->hwaddr, ETH_ALEN);
|
||||
rt_link_newlink_req_set_linkinfo_data_geneve_id(req, cfg->vnid);
|
||||
rt_link_newlink_req_set_linkinfo_data_geneve_port(req, cfg->vnport);
|
||||
rt_link_newlink_req_set_linkinfo_data_geneve_udp_csum(req, cfg->csum);
|
||||
|
||||
if (cfg->family == AF_INET)
|
||||
SET_GENEVE_REMOTE(req, cfg->remote.r4.s_addr);
|
||||
else
|
||||
SET_GENEVE_REMOTE6(req, &cfg->remote.r6,
|
||||
sizeof(cfg->remote.r6));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int geneve_create(const char *dev, int family, void *remote,
|
||||
void *hwaddr)
|
||||
{
|
||||
struct geneve_setup_config geneve;
|
||||
|
||||
memset(&geneve, 0, sizeof(geneve));
|
||||
geneve.vnid = VN_ID;
|
||||
geneve.vnport = htons(VN_PORT);
|
||||
geneve.csum = 1;
|
||||
geneve.family = family;
|
||||
if (family == AF_INET)
|
||||
memcpy(&geneve.remote.r4, remote, sizeof(struct in_addr));
|
||||
else
|
||||
memcpy(&geneve.remote.r6, remote, sizeof(struct in6_addr));
|
||||
memcpy(geneve.hwaddr, hwaddr, ETH_ALEN);
|
||||
|
||||
return ip_link_add(dev, "geneve", geneve_fill_newlink, (void *)&geneve);
|
||||
}
|
||||
|
||||
static int set_pmtu_discover(int fd, bool is_ipv4)
|
||||
{
|
||||
int level, name, val;
|
||||
|
||||
if (is_ipv4) {
|
||||
level = SOL_IP;
|
||||
name = IP_MTU_DISCOVER;
|
||||
val = IP_PMTUDISC_DO;
|
||||
} else {
|
||||
level = SOL_IPV6;
|
||||
name = IPV6_MTU_DISCOVER;
|
||||
val = IPV6_PMTUDISC_DO;
|
||||
}
|
||||
|
||||
return setsockopt(fd, level, name, &val, sizeof(val));
|
||||
}
|
||||
|
||||
static int udp_socket_open(struct sockaddr_storage *ssa, bool do_frag,
|
||||
bool do_connect, struct sockaddr_storage *dsa)
|
||||
{
|
||||
struct timeval to = { .tv_sec = TIMEOUT_SEC };
|
||||
int fd, family = ssa->ss_family;
|
||||
int salen = sockaddr_len(family);
|
||||
|
||||
fd = socket(family, SOCK_DGRAM, 0);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
if (bind(fd, (struct sockaddr *)ssa, salen) < 0) {
|
||||
perror("bind");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (do_connect && connect(fd, (struct sockaddr *)dsa, salen) < 0) {
|
||||
perror("connect");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)) < 0) {
|
||||
perror("setsockopt(SO_RCVTIMEO)");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!do_frag && set_pmtu_discover(fd, family == AF_INET) < 0) {
|
||||
perror("set_pmtu_discover");
|
||||
goto err;
|
||||
}
|
||||
return fd;
|
||||
|
||||
err:
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void parse_route_rsp(struct rt_route_getroute_rsp *rsp, void *rtm_type)
|
||||
{
|
||||
*(uint8_t *)rtm_type = rsp->_hdr.rtm_type;
|
||||
}
|
||||
|
||||
static int ip_route_check(const char *intf, int family, void *addr)
|
||||
{
|
||||
uint8_t rtm_type, table = RT_TABLE_LOCAL;
|
||||
int retries = MAX_RETRIES;
|
||||
|
||||
while (retries-- > 0) {
|
||||
if (ip_route_get(intf, family, table, addr, parse_route_rsp,
|
||||
&rtm_type) == 0 &&
|
||||
rtm_type == RTN_LOCAL)
|
||||
break;
|
||||
|
||||
usleep(TIMEOUT_USEC);
|
||||
}
|
||||
|
||||
if (retries < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_gso_udp_msg(int socket, struct sockaddr_storage *addr,
|
||||
uint8_t *send_buf, int send_len, int gso_size)
|
||||
{
|
||||
char control[CMSG_SPACE(sizeof(uint16_t))] = { 0 };
|
||||
int alen = sockaddr_len(addr->ss_family);
|
||||
struct msghdr msg = { 0 };
|
||||
struct iovec iov = { 0 };
|
||||
int ret;
|
||||
|
||||
iov.iov_base = send_buf;
|
||||
iov.iov_len = send_len;
|
||||
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_name = addr;
|
||||
msg.msg_namelen = alen;
|
||||
|
||||
if (gso_size > 0) {
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_level = SOL_UDP;
|
||||
cmsg->cmsg_type = UDP_SEGMENT;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t));
|
||||
*(uint16_t *)CMSG_DATA(cmsg) = gso_size;
|
||||
}
|
||||
|
||||
ret = sendmsg(socket, &msg, 0);
|
||||
if (ret < 0)
|
||||
fprintf(stderr, "can't send: %s\n", strerror(errno));
|
||||
ret = (unsigned int)ret != req.nh.nlmsg_len;
|
||||
perror("sendmsg");
|
||||
|
||||
close(rtnl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int validate_hdrlen(uint8_t **cur, int *len, int x)
|
||||
{
|
||||
if (*len < x)
|
||||
return -1;
|
||||
*cur += x;
|
||||
*len -= x;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_udp_tunnel_vnet_packet(uint8_t *buf, int len, int tunnel_type,
|
||||
bool is_tap)
|
||||
{
|
||||
struct ipv6hdr *iph6;
|
||||
struct udphdr *udph;
|
||||
struct iphdr *iph4;
|
||||
uint8_t *cur = buf;
|
||||
|
||||
if (validate_hdrlen(&cur, &len, TUN_VNET_TNL_SIZE))
|
||||
return -1;
|
||||
|
||||
if (is_tap) {
|
||||
if (validate_hdrlen(&cur, &len, ETH_HLEN))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (tunnel_type & UDP_TUNNEL_OUTER_IPV4) {
|
||||
iph4 = (struct iphdr *)cur;
|
||||
if (validate_hdrlen(&cur, &len, sizeof(struct iphdr)))
|
||||
return -1;
|
||||
if (iph4->version != 4 || iph4->protocol != IPPROTO_UDP)
|
||||
return -1;
|
||||
} else {
|
||||
iph6 = (struct ipv6hdr *)cur;
|
||||
if (validate_hdrlen(&cur, &len, sizeof(struct ipv6hdr)))
|
||||
return -1;
|
||||
if (iph6->version != 6 || iph6->nexthdr != IPPROTO_UDP)
|
||||
return -1;
|
||||
}
|
||||
|
||||
udph = (struct udphdr *)cur;
|
||||
if (validate_hdrlen(&cur, &len, sizeof(struct udphdr)))
|
||||
return -1;
|
||||
if (ntohs(udph->dest) != VN_PORT)
|
||||
return -1;
|
||||
|
||||
if (validate_hdrlen(&cur, &len, GENEVE_HLEN))
|
||||
return -1;
|
||||
if (validate_hdrlen(&cur, &len, ETH_HLEN))
|
||||
return -1;
|
||||
|
||||
if (tunnel_type & UDP_TUNNEL_INNER_IPV4) {
|
||||
iph4 = (struct iphdr *)cur;
|
||||
if (validate_hdrlen(&cur, &len, sizeof(struct iphdr)))
|
||||
return -1;
|
||||
if (iph4->version != 4 || iph4->protocol != IPPROTO_UDP)
|
||||
return -1;
|
||||
} else {
|
||||
iph6 = (struct ipv6hdr *)cur;
|
||||
if (validate_hdrlen(&cur, &len, sizeof(struct ipv6hdr)))
|
||||
return -1;
|
||||
if (iph6->version != 6 || iph6->nexthdr != IPPROTO_UDP)
|
||||
return -1;
|
||||
}
|
||||
|
||||
udph = (struct udphdr *)cur;
|
||||
if (validate_hdrlen(&cur, &len, sizeof(struct udphdr)))
|
||||
return -1;
|
||||
if (ntohs(udph->dest) != UDP_DST_PORT)
|
||||
return -1;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
FIXTURE(tun)
|
||||
{
|
||||
char ifname[IFNAMSIZ];
|
||||
|
|
@ -127,31 +505,36 @@ FIXTURE_TEARDOWN(tun)
|
|||
close(self->fd2);
|
||||
}
|
||||
|
||||
TEST_F(tun, delete_detach_close) {
|
||||
TEST_F(tun, delete_detach_close)
|
||||
{
|
||||
EXPECT_EQ(tun_delete(self->ifname), 0);
|
||||
EXPECT_EQ(tun_detach(self->fd, self->ifname), -1);
|
||||
EXPECT_EQ(errno, 22);
|
||||
}
|
||||
|
||||
TEST_F(tun, detach_delete_close) {
|
||||
TEST_F(tun, detach_delete_close)
|
||||
{
|
||||
EXPECT_EQ(tun_detach(self->fd, self->ifname), 0);
|
||||
EXPECT_EQ(tun_delete(self->ifname), 0);
|
||||
}
|
||||
|
||||
TEST_F(tun, detach_close_delete) {
|
||||
TEST_F(tun, detach_close_delete)
|
||||
{
|
||||
EXPECT_EQ(tun_detach(self->fd, self->ifname), 0);
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
EXPECT_EQ(tun_delete(self->ifname), 0);
|
||||
}
|
||||
|
||||
TEST_F(tun, reattach_delete_close) {
|
||||
TEST_F(tun, reattach_delete_close)
|
||||
{
|
||||
EXPECT_EQ(tun_detach(self->fd, self->ifname), 0);
|
||||
EXPECT_EQ(tun_attach(self->fd, self->ifname), 0);
|
||||
EXPECT_EQ(tun_delete(self->ifname), 0);
|
||||
}
|
||||
|
||||
TEST_F(tun, reattach_close_delete) {
|
||||
TEST_F(tun, reattach_close_delete)
|
||||
{
|
||||
EXPECT_EQ(tun_detach(self->fd, self->ifname), 0);
|
||||
EXPECT_EQ(tun_attach(self->fd, self->ifname), 0);
|
||||
close(self->fd);
|
||||
|
|
@ -159,4 +542,447 @@ TEST_F(tun, reattach_close_delete) {
|
|||
EXPECT_EQ(tun_delete(self->ifname), 0);
|
||||
}
|
||||
|
||||
FIXTURE(tun_vnet_udptnl)
|
||||
{
|
||||
char ifname[IFNAMSIZ];
|
||||
int fd, sock;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT(tun_vnet_udptnl)
|
||||
{
|
||||
int tunnel_type;
|
||||
int gso_size;
|
||||
int data_size;
|
||||
int r_num_mss;
|
||||
bool is_tap, no_gso;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
#define TUN_VNET_UDPTNL_VARIANT_ADD(type, desc) \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_nogsosz_1byte) { \
|
||||
/* no GSO: send a single byte */ \
|
||||
.tunnel_type = type, \
|
||||
.data_size = 1, \
|
||||
.r_num_mss = 1, \
|
||||
.is_tap = true, \
|
||||
.no_gso = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_nogsosz_1mss) { \
|
||||
/* no GSO: send a single MSS, fall back to no GSO */ \
|
||||
.tunnel_type = type, \
|
||||
.data_size = UDP_TUNNEL_MSS(type), \
|
||||
.r_num_mss = 1, \
|
||||
.is_tap = true, \
|
||||
.no_gso = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_nogsosz_gtmss) { \
|
||||
/* no GSO: send a single MSS + 1B: fail */ \
|
||||
.tunnel_type = type, \
|
||||
.data_size = UDP_TUNNEL_MSS(type) + 1, \
|
||||
.r_num_mss = 1, \
|
||||
.is_tap = true, \
|
||||
.no_gso = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_1byte) { \
|
||||
/* GSO: send 1 byte, gso 1 byte, fall back to no GSO */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = 1, \
|
||||
.data_size = 1, \
|
||||
.r_num_mss = 1, \
|
||||
.is_tap = true, \
|
||||
.no_gso = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_1mss) { \
|
||||
/* send a single MSS: fall back to no GSO */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = UDP_TUNNEL_MSS(type), \
|
||||
.data_size = UDP_TUNNEL_MSS(type), \
|
||||
.r_num_mss = 1, \
|
||||
.is_tap = true, \
|
||||
.no_gso = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_ltgso) { \
|
||||
/* data <= MSS < gso: will fall back to no GSO */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = UDP_TUNNEL_MSS(type) + 1, \
|
||||
.data_size = UDP_TUNNEL_MSS(type), \
|
||||
.r_num_mss = 1, \
|
||||
.is_tap = true, \
|
||||
.no_gso = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_gtgso) { \
|
||||
/* GSO: a single MSS + 1B */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = UDP_TUNNEL_MSS(type), \
|
||||
.data_size = UDP_TUNNEL_MSS(type) + 1, \
|
||||
.r_num_mss = 2, \
|
||||
.is_tap = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_2mss) { \
|
||||
/* no GSO: send exactly 2 MSS */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = UDP_TUNNEL_MSS(type), \
|
||||
.data_size = UDP_TUNNEL_MSS(type) * 2, \
|
||||
.r_num_mss = 2, \
|
||||
.is_tap = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_maxbytes) { \
|
||||
/* GSO: send max bytes */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = UDP_TUNNEL_MSS(type), \
|
||||
.data_size = UDP_TUNNEL_MAX(type, true), \
|
||||
.r_num_mss = UDP_TUNNEL_MAX(type, true) / \
|
||||
UDP_TUNNEL_MSS(type) + 1, \
|
||||
.is_tap = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_over_maxbytes) { \
|
||||
/* GSO: send oversize max bytes: fail */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = UDP_TUNNEL_MSS(type), \
|
||||
.data_size = ETH_MAX_MTU, \
|
||||
.r_num_mss = ETH_MAX_MTU / UDP_TUNNEL_MSS(type) + 1, \
|
||||
.is_tap = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_maxsegs) { \
|
||||
/* GSO: send max number of min sized segments */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = 1, \
|
||||
.data_size = UDP_TUNNEL_MAX_SEGMENTS, \
|
||||
.r_num_mss = UDP_TUNNEL_MAX_SEGMENTS, \
|
||||
.is_tap = true, \
|
||||
}; \
|
||||
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_5byte) { \
|
||||
/* GSO: send 5 bytes, gso 2 bytes */ \
|
||||
.tunnel_type = type, \
|
||||
.gso_size = 2, \
|
||||
.data_size = 5, \
|
||||
.r_num_mss = 3, \
|
||||
.is_tap = true, \
|
||||
} /* clang-format on */
|
||||
|
||||
TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_GENEVE_4IN4, 4in4);
|
||||
TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_GENEVE_6IN4, 6in4);
|
||||
TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_GENEVE_4IN6, 4in6);
|
||||
TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_GENEVE_6IN6, 6in6);
|
||||
|
||||
static void assign_ifaddr_vars(int family, int is_outer, void **srcip,
|
||||
void **dstip, void **srcmac, void **dstmac)
|
||||
{
|
||||
if (is_outer) {
|
||||
if (family == AF_INET) {
|
||||
*srcip = (void *)¶m_ipaddr4_outer_src;
|
||||
*dstip = (void *)¶m_ipaddr4_outer_dst;
|
||||
} else {
|
||||
*srcip = (void *)¶m_ipaddr6_outer_src;
|
||||
*dstip = (void *)¶m_ipaddr6_outer_dst;
|
||||
}
|
||||
*srcmac = param_hwaddr_outer_src;
|
||||
*dstmac = param_hwaddr_outer_dst;
|
||||
} else {
|
||||
if (family == AF_INET) {
|
||||
*srcip = (void *)¶m_ipaddr4_inner_src;
|
||||
*dstip = (void *)¶m_ipaddr4_inner_dst;
|
||||
} else {
|
||||
*srcip = (void *)¶m_ipaddr6_inner_src;
|
||||
*dstip = (void *)¶m_ipaddr6_inner_dst;
|
||||
}
|
||||
*srcmac = param_hwaddr_inner_src;
|
||||
*dstmac = param_hwaddr_inner_dst;
|
||||
}
|
||||
}
|
||||
|
||||
static void assign_sockaddr_vars(int family, int is_outer,
|
||||
struct sockaddr_storage *src,
|
||||
struct sockaddr_storage *dst)
|
||||
{
|
||||
src->ss_family = family;
|
||||
dst->ss_family = family;
|
||||
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in *s4 = (struct sockaddr_in *)src;
|
||||
struct sockaddr_in *d4 = (struct sockaddr_in *)dst;
|
||||
|
||||
s4->sin_addr = is_outer ? param_ipaddr4_outer_src :
|
||||
param_ipaddr4_inner_src;
|
||||
d4->sin_addr = is_outer ? param_ipaddr4_outer_dst :
|
||||
param_ipaddr4_inner_dst;
|
||||
if (!is_outer) {
|
||||
s4->sin_port = htons(UDP_SRC_PORT);
|
||||
d4->sin_port = htons(UDP_DST_PORT);
|
||||
}
|
||||
} else {
|
||||
struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)src;
|
||||
struct sockaddr_in6 *d6 = (struct sockaddr_in6 *)dst;
|
||||
|
||||
s6->sin6_addr = is_outer ? param_ipaddr6_outer_src :
|
||||
param_ipaddr6_inner_src;
|
||||
d6->sin6_addr = is_outer ? param_ipaddr6_outer_dst :
|
||||
param_ipaddr6_inner_dst;
|
||||
if (!is_outer) {
|
||||
s6->sin6_port = htons(UDP_SRC_PORT);
|
||||
d6->sin6_port = htons(UDP_DST_PORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FIXTURE_SETUP(tun_vnet_udptnl)
|
||||
{
|
||||
int ret, family, prefix, flags, features;
|
||||
int tunnel_type = variant->tunnel_type;
|
||||
struct sockaddr_storage ssa, dsa;
|
||||
void *sip, *dip, *smac, *dmac;
|
||||
|
||||
flags = (variant->is_tap ? IFF_TAP : IFF_TUN) | IFF_VNET_HDR |
|
||||
IFF_MULTI_QUEUE | IFF_NO_PI;
|
||||
features = TUN_F_CSUM | TUN_F_UDP_TUNNEL_GSO |
|
||||
TUN_F_UDP_TUNNEL_GSO_CSUM | TUN_F_USO4 | TUN_F_USO6;
|
||||
self->fd = tun_open(self->ifname, flags, TUN_VNET_TNL_SIZE, features,
|
||||
param_hwaddr_outer_src);
|
||||
ASSERT_GE(self->fd, 0);
|
||||
|
||||
family = (tunnel_type & UDP_TUNNEL_OUTER_IPV4) ? AF_INET : AF_INET6;
|
||||
prefix = (family == AF_INET) ? IPPREFIX_LEN : IP6PREFIX_LEN;
|
||||
assign_ifaddr_vars(family, 1, &sip, &dip, &smac, &dmac);
|
||||
|
||||
ret = ip_addr_add(self->ifname, family, sip, prefix);
|
||||
ASSERT_EQ(ret, 0);
|
||||
ret = ip_neigh_add(self->ifname, family, dip, dmac);
|
||||
ASSERT_EQ(ret, 0);
|
||||
ret = ip_route_check(self->ifname, family, sip);
|
||||
ASSERT_EQ(ret, 0);
|
||||
|
||||
ret = geneve_create(param_dev_geneve_name, family, dip,
|
||||
param_hwaddr_inner_src);
|
||||
ASSERT_EQ(ret, 0);
|
||||
|
||||
family = (tunnel_type & UDP_TUNNEL_INNER_IPV4) ? AF_INET : AF_INET6;
|
||||
prefix = (family == AF_INET) ? IPPREFIX_LEN : IP6PREFIX_LEN;
|
||||
assign_ifaddr_vars(family, 0, &sip, &dip, &smac, &dmac);
|
||||
|
||||
ret = ip_addr_add(param_dev_geneve_name, family, sip, prefix);
|
||||
ASSERT_EQ(ret, 0);
|
||||
ret = ip_neigh_add(param_dev_geneve_name, family, dip, dmac);
|
||||
ASSERT_EQ(ret, 0);
|
||||
ret = ip_route_check(param_dev_geneve_name, family, sip);
|
||||
ASSERT_EQ(ret, 0);
|
||||
|
||||
assign_sockaddr_vars(family, 0, &ssa, &dsa);
|
||||
self->sock = udp_socket_open(&ssa, false, true, &dsa);
|
||||
ASSERT_GE(self->sock, 0);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(tun_vnet_udptnl)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (self->sock != -1)
|
||||
close(self->sock);
|
||||
|
||||
ret = ip_link_del(param_dev_geneve_name);
|
||||
EXPECT_EQ(ret, 0);
|
||||
|
||||
ret = tun_delete(self->ifname);
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
|
||||
static int build_gso_packet_into_tun(const FIXTURE_VARIANT(tun_vnet_udptnl) *
|
||||
variant,
|
||||
uint8_t *buf)
|
||||
{
|
||||
int pktlen, hlen, proto, inner_family, outer_family;
|
||||
int tunnel_type = variant->tunnel_type;
|
||||
int payload_len = variant->data_size;
|
||||
int gso_size = variant->gso_size;
|
||||
uint8_t *outer_udph, *cur = buf;
|
||||
void *sip, *dip, *smac, *dmac;
|
||||
bool is_tap = variant->is_tap;
|
||||
|
||||
hlen = (is_tap ? ETH_HLEN : 0) + UDP_TUNNEL_HDRLEN(tunnel_type);
|
||||
inner_family = (tunnel_type & UDP_TUNNEL_INNER_IPV4) ? AF_INET :
|
||||
AF_INET6;
|
||||
outer_family = (tunnel_type & UDP_TUNNEL_OUTER_IPV4) ? AF_INET :
|
||||
AF_INET6;
|
||||
|
||||
cur += build_virtio_net_hdr_v1_hash_tunnel(cur, is_tap, hlen, gso_size,
|
||||
outer_family, inner_family);
|
||||
|
||||
pktlen = hlen + payload_len;
|
||||
assign_ifaddr_vars(outer_family, 1, &sip, &dip, &smac, &dmac);
|
||||
|
||||
if (is_tap) {
|
||||
proto = outer_family == AF_INET ? ETH_P_IP : ETH_P_IPV6;
|
||||
pktlen -= ETH_HLEN;
|
||||
cur += build_eth(cur, proto, dmac, smac);
|
||||
}
|
||||
|
||||
if (outer_family == AF_INET) {
|
||||
pktlen = pktlen - sizeof(struct iphdr);
|
||||
cur += build_ipv4_header(cur, IPPROTO_UDP, pktlen, dip, sip);
|
||||
} else {
|
||||
pktlen = pktlen - sizeof(struct ipv6hdr);
|
||||
cur += build_ipv6_header(cur, IPPROTO_UDP, 0, pktlen, dip, sip);
|
||||
}
|
||||
|
||||
outer_udph = cur;
|
||||
assign_ifaddr_vars(inner_family, 0, &sip, &dip, &smac, &dmac);
|
||||
|
||||
pktlen -= sizeof(struct udphdr);
|
||||
proto = inner_family == AF_INET ? ETH_P_IP : ETH_P_IPV6;
|
||||
cur += build_udp_header(cur, UDP_SRC_PORT, VN_PORT, pktlen);
|
||||
cur += build_geneve_header(cur, VN_ID);
|
||||
cur += build_eth(cur, proto, dmac, smac);
|
||||
|
||||
pktlen = sizeof(struct udphdr) + payload_len;
|
||||
if (inner_family == AF_INET)
|
||||
cur += build_ipv4_header(cur, IPPROTO_UDP, pktlen, dip, sip);
|
||||
else
|
||||
cur += build_ipv6_header(cur, IPPROTO_UDP, 0, pktlen, dip, sip);
|
||||
|
||||
cur += build_udp_packet(cur, UDP_DST_PORT, UDP_SRC_PORT, payload_len,
|
||||
inner_family, false);
|
||||
|
||||
build_udp_packet_csum(outer_udph, outer_family, false);
|
||||
|
||||
return cur - buf;
|
||||
}
|
||||
|
||||
static int
|
||||
receive_gso_packet_from_tunnel(FIXTURE_DATA(tun_vnet_udptnl) * self,
|
||||
const FIXTURE_VARIANT(tun_vnet_udptnl) * variant,
|
||||
int *r_num_mss)
|
||||
{
|
||||
uint8_t packet_buf[MAX_VNET_TUNNEL_PACKET_SZ];
|
||||
int len, total_len = 0, socket = self->sock;
|
||||
int payload_len = variant->data_size;
|
||||
|
||||
while (total_len < payload_len) {
|
||||
len = recv(socket, packet_buf, sizeof(packet_buf), 0);
|
||||
if (len <= 0) {
|
||||
if (len < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
perror("recv");
|
||||
break;
|
||||
}
|
||||
|
||||
(*r_num_mss)++;
|
||||
total_len += len;
|
||||
}
|
||||
|
||||
return total_len;
|
||||
}
|
||||
|
||||
static int send_gso_packet_into_tunnel(FIXTURE_DATA(tun_vnet_udptnl) * self,
|
||||
const FIXTURE_VARIANT(tun_vnet_udptnl) *
|
||||
variant)
|
||||
{
|
||||
int family = (variant->tunnel_type & UDP_TUNNEL_INNER_IPV4) ? AF_INET :
|
||||
AF_INET6;
|
||||
uint8_t buf[MAX_VNET_TUNNEL_PACKET_SZ] = { 0 };
|
||||
int payload_len = variant->data_size;
|
||||
int gso_size = variant->gso_size;
|
||||
struct sockaddr_storage ssa, dsa;
|
||||
|
||||
assign_sockaddr_vars(family, 0, &ssa, &dsa);
|
||||
return send_gso_udp_msg(self->sock, &dsa, buf, payload_len, gso_size);
|
||||
}
|
||||
|
||||
static int
|
||||
receive_gso_packet_from_tun(FIXTURE_DATA(tun_vnet_udptnl) * self,
|
||||
const FIXTURE_VARIANT(tun_vnet_udptnl) * variant,
|
||||
struct virtio_net_hdr_v1_hash_tunnel *vnet_hdr)
|
||||
{
|
||||
struct timeval timeout = { .tv_sec = TIMEOUT_SEC };
|
||||
uint8_t buf[MAX_VNET_TUNNEL_PACKET_SZ];
|
||||
int tunnel_type = variant->tunnel_type;
|
||||
int payload_len = variant->data_size;
|
||||
bool is_tap = variant->is_tap;
|
||||
int ret, len, total_len = 0;
|
||||
int tun_fd = self->fd;
|
||||
fd_set fdset;
|
||||
|
||||
while (total_len < payload_len) {
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(tun_fd, &fdset);
|
||||
|
||||
ret = select(tun_fd + 1, &fdset, NULL, NULL, &timeout);
|
||||
if (ret <= 0) {
|
||||
perror("select");
|
||||
break;
|
||||
}
|
||||
if (!FD_ISSET(tun_fd, &fdset))
|
||||
continue;
|
||||
|
||||
len = read(tun_fd, buf, sizeof(buf));
|
||||
if (len <= 0) {
|
||||
if (len < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
perror("read");
|
||||
break;
|
||||
}
|
||||
|
||||
len = parse_udp_tunnel_vnet_packet(buf, len, tunnel_type,
|
||||
is_tap);
|
||||
if (len < 0)
|
||||
continue;
|
||||
|
||||
if (total_len == 0)
|
||||
memcpy(vnet_hdr, buf, TUN_VNET_TNL_SIZE);
|
||||
|
||||
total_len += len;
|
||||
}
|
||||
|
||||
return total_len;
|
||||
}
|
||||
|
||||
TEST_F(tun_vnet_udptnl, send_gso_packet)
|
||||
{
|
||||
uint8_t pkt[MAX_VNET_TUNNEL_PACKET_SZ];
|
||||
int r_num_mss = 0;
|
||||
int ret, off;
|
||||
|
||||
memset(pkt, 0, sizeof(pkt));
|
||||
off = build_gso_packet_into_tun(variant, pkt);
|
||||
ret = write(self->fd, pkt, off);
|
||||
ASSERT_EQ(ret, off);
|
||||
|
||||
ret = receive_gso_packet_from_tunnel(self, variant, &r_num_mss);
|
||||
ASSERT_EQ(ret, variant->data_size);
|
||||
ASSERT_EQ(r_num_mss, variant->r_num_mss);
|
||||
}
|
||||
|
||||
TEST_F(tun_vnet_udptnl, recv_gso_packet)
|
||||
{
|
||||
struct virtio_net_hdr_v1_hash_tunnel vnet_hdr = { 0 };
|
||||
struct virtio_net_hdr_v1 *vh = &vnet_hdr.hash_hdr.hdr;
|
||||
int ret, gso_type = VIRTIO_NET_HDR_GSO_UDP_L4;
|
||||
|
||||
ret = send_gso_packet_into_tunnel(self, variant);
|
||||
ASSERT_EQ(ret, variant->data_size);
|
||||
|
||||
memset(&vnet_hdr, 0, sizeof(vnet_hdr));
|
||||
ret = receive_gso_packet_from_tun(self, variant, &vnet_hdr);
|
||||
ASSERT_EQ(ret, variant->data_size);
|
||||
|
||||
if (!variant->no_gso) {
|
||||
ASSERT_EQ(vh->gso_size, variant->gso_size);
|
||||
gso_type |= (variant->tunnel_type & UDP_TUNNEL_OUTER_IPV4) ?
|
||||
(VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4) :
|
||||
(VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6);
|
||||
ASSERT_EQ(vh->gso_type, gso_type);
|
||||
}
|
||||
}
|
||||
|
||||
XFAIL_ADD(tun_vnet_udptnl, 4in4_nogsosz_gtmss, recv_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 6in4_nogsosz_gtmss, recv_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 4in6_nogsosz_gtmss, recv_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 6in6_nogsosz_gtmss, recv_gso_packet);
|
||||
|
||||
XFAIL_ADD(tun_vnet_udptnl, 4in4_over_maxbytes, send_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 6in4_over_maxbytes, send_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 4in6_over_maxbytes, send_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 6in6_over_maxbytes, send_gso_packet);
|
||||
|
||||
XFAIL_ADD(tun_vnet_udptnl, 4in4_over_maxbytes, recv_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 6in4_over_maxbytes, recv_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 4in6_over_maxbytes, recv_gso_packet);
|
||||
XFAIL_ADD(tun_vnet_udptnl, 6in6_over_maxbytes, recv_gso_packet);
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
390
tools/testing/selftests/net/tuntap_helpers.h
Normal file
390
tools/testing/selftests/net/tuntap_helpers.h
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef _TUNTAP_HELPERS_H
|
||||
#define _TUNTAP_HELPERS_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <linux/ipv6.h>
|
||||
#include <linux/virtio_net.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/if_ether.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <ynl.h>
|
||||
|
||||
#include "rt-route-user.h"
|
||||
#include "rt-addr-user.h"
|
||||
#include "rt-neigh-user.h"
|
||||
#include "rt-link-user.h"
|
||||
|
||||
#define GENEVE_HLEN 8
|
||||
#define PKT_DATA 0xCB
|
||||
#define TUNTAP_DEFAULT_TTL 8
|
||||
#define TUNTAP_DEFAULT_IPID 1337
|
||||
|
||||
unsigned int if_nametoindex(const char *ifname);
|
||||
|
||||
static inline int ip_addr_len(int family)
|
||||
{
|
||||
return (family == AF_INET) ? sizeof(struct in_addr) :
|
||||
sizeof(struct in6_addr);
|
||||
}
|
||||
|
||||
static inline void fill_ifaddr_msg(struct ifaddrmsg *ifam, int family,
|
||||
int prefix, int flags, const char *dev)
|
||||
{
|
||||
ifam->ifa_family = family;
|
||||
ifam->ifa_prefixlen = prefix;
|
||||
ifam->ifa_index = if_nametoindex(dev);
|
||||
ifam->ifa_flags = flags;
|
||||
ifam->ifa_scope = RT_SCOPE_UNIVERSE;
|
||||
}
|
||||
|
||||
static inline int ip_addr_add(const char *dev, int family, void *addr,
|
||||
uint8_t prefix)
|
||||
{
|
||||
int nl_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
|
||||
int ifa_flags = IFA_F_PERMANENT | IFA_F_NODAD;
|
||||
int ret = -1, ipalen = ip_addr_len(family);
|
||||
struct rt_addr_newaddr_req *req;
|
||||
struct ynl_sock *ys;
|
||||
|
||||
ys = ynl_sock_create(&ynl_rt_addr_family, NULL);
|
||||
if (!ys)
|
||||
return -1;
|
||||
|
||||
req = rt_addr_newaddr_req_alloc();
|
||||
if (!req)
|
||||
goto err_req_alloc;
|
||||
|
||||
fill_ifaddr_msg(&req->_hdr, family, prefix, ifa_flags, dev);
|
||||
rt_addr_newaddr_req_set_nlflags(req, nl_flags);
|
||||
rt_addr_newaddr_req_set_local(req, addr, ipalen);
|
||||
|
||||
ret = rt_addr_newaddr(ys, req);
|
||||
rt_addr_newaddr_req_free(req);
|
||||
err_req_alloc:
|
||||
ynl_sock_destroy(ys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void fill_neigh_req_header(struct ndmsg *ndm, int family,
|
||||
int state, const char *dev)
|
||||
{
|
||||
ndm->ndm_family = family;
|
||||
ndm->ndm_ifindex = if_nametoindex(dev);
|
||||
ndm->ndm_state = state;
|
||||
ndm->ndm_flags = 0;
|
||||
ndm->ndm_type = RTN_UNICAST;
|
||||
}
|
||||
|
||||
static inline int ip_neigh_add(const char *dev, int family, void *addr,
|
||||
unsigned char *lladdr)
|
||||
{
|
||||
int nl_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
|
||||
int ret = -1, ipalen = ip_addr_len(family);
|
||||
struct rt_neigh_newneigh_req *req;
|
||||
struct ynl_sock *ys;
|
||||
|
||||
ys = ynl_sock_create(&ynl_rt_neigh_family, NULL);
|
||||
if (!ys)
|
||||
return -1;
|
||||
|
||||
req = rt_neigh_newneigh_req_alloc();
|
||||
if (!req)
|
||||
goto err_req_alloc;
|
||||
|
||||
fill_neigh_req_header(&req->_hdr, family, NUD_PERMANENT, dev);
|
||||
rt_neigh_newneigh_req_set_nlflags(req, nl_flags);
|
||||
rt_neigh_newneigh_req_set_dst(req, addr, ipalen);
|
||||
rt_neigh_newneigh_req_set_lladdr(req, lladdr, ETH_ALEN);
|
||||
rt_neigh_newneigh_req_set_ifindex(req, if_nametoindex(dev));
|
||||
|
||||
ret = rt_neigh_newneigh(ys, req);
|
||||
rt_neigh_newneigh_req_free(req);
|
||||
err_req_alloc:
|
||||
ynl_sock_destroy(ys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void fill_route_req_header(struct rtmsg *rtm, int family,
|
||||
int table)
|
||||
{
|
||||
rtm->rtm_family = family;
|
||||
rtm->rtm_table = table;
|
||||
}
|
||||
|
||||
static inline int
|
||||
ip_route_get(const char *dev, int family, int table, void *dst,
|
||||
void (*parse_rsp)(struct rt_route_getroute_rsp *rsp, void *out),
|
||||
void *out)
|
||||
{
|
||||
int ret = -1, ipalen = ip_addr_len(family);
|
||||
struct rt_route_getroute_req *req;
|
||||
struct rt_route_getroute_rsp *rsp;
|
||||
struct ynl_sock *ys;
|
||||
|
||||
ys = ynl_sock_create(&ynl_rt_route_family, NULL);
|
||||
if (!ys)
|
||||
return -1;
|
||||
|
||||
req = rt_route_getroute_req_alloc();
|
||||
if (!req)
|
||||
goto err_req_alloc;
|
||||
|
||||
fill_route_req_header(&req->_hdr, family, table);
|
||||
rt_route_getroute_req_set_nlflags(req, NLM_F_REQUEST);
|
||||
rt_route_getroute_req_set_dst(req, dst, ipalen);
|
||||
rt_route_getroute_req_set_oif(req, if_nametoindex(dev));
|
||||
|
||||
rsp = rt_route_getroute(ys, req);
|
||||
if (!rsp)
|
||||
goto err_rsp_get;
|
||||
|
||||
ret = 0;
|
||||
if (parse_rsp)
|
||||
parse_rsp(rsp, out);
|
||||
|
||||
rt_route_getroute_rsp_free(rsp);
|
||||
err_rsp_get:
|
||||
rt_route_getroute_req_free(req);
|
||||
err_req_alloc:
|
||||
ynl_sock_destroy(ys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int
|
||||
ip_link_add(const char *dev, char *link_type,
|
||||
int (*fill_link_attr)(struct rt_link_newlink_req *req, void *data),
|
||||
void *data)
|
||||
{
|
||||
int nl_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
|
||||
struct rt_link_newlink_req *req;
|
||||
struct ynl_sock *ys;
|
||||
int ret = -1;
|
||||
|
||||
ys = ynl_sock_create(&ynl_rt_link_family, NULL);
|
||||
if (!ys)
|
||||
return -1;
|
||||
|
||||
req = rt_link_newlink_req_alloc();
|
||||
if (!req)
|
||||
goto err_req_alloc;
|
||||
|
||||
req->_hdr.ifi_flags = IFF_UP;
|
||||
rt_link_newlink_req_set_nlflags(req, nl_flags);
|
||||
rt_link_newlink_req_set_ifname(req, dev);
|
||||
rt_link_newlink_req_set_linkinfo_kind(req, link_type);
|
||||
|
||||
if (fill_link_attr && fill_link_attr(req, data) < 0)
|
||||
goto err_attr_fill;
|
||||
|
||||
ret = rt_link_newlink(ys, req);
|
||||
err_attr_fill:
|
||||
rt_link_newlink_req_free(req);
|
||||
err_req_alloc:
|
||||
ynl_sock_destroy(ys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int ip_link_del(const char *dev)
|
||||
{
|
||||
struct rt_link_dellink_req *req;
|
||||
struct ynl_sock *ys;
|
||||
int ret = -1;
|
||||
|
||||
ys = ynl_sock_create(&ynl_rt_link_family, NULL);
|
||||
if (!ys)
|
||||
return -1;
|
||||
|
||||
req = rt_link_dellink_req_alloc();
|
||||
if (!req)
|
||||
goto err_req_alloc;
|
||||
|
||||
rt_link_dellink_req_set_nlflags(req, NLM_F_REQUEST);
|
||||
rt_link_dellink_req_set_ifname(req, dev);
|
||||
|
||||
ret = rt_link_dellink(ys, req);
|
||||
rt_link_dellink_req_free(req);
|
||||
err_req_alloc:
|
||||
ynl_sock_destroy(ys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline size_t build_eth(uint8_t *buf, uint16_t proto, unsigned char *src,
|
||||
unsigned char *dest)
|
||||
{
|
||||
struct ethhdr *eth = (struct ethhdr *)buf;
|
||||
|
||||
eth->h_proto = htons(proto);
|
||||
memcpy(eth->h_source, src, ETH_ALEN);
|
||||
memcpy(eth->h_dest, dest, ETH_ALEN);
|
||||
|
||||
return ETH_HLEN;
|
||||
}
|
||||
|
||||
static inline uint32_t add_csum(const uint8_t *buf, int len)
|
||||
{
|
||||
uint16_t *sbuf = (uint16_t *)buf;
|
||||
uint32_t sum = 0;
|
||||
|
||||
while (len > 1) {
|
||||
sum += *sbuf++;
|
||||
len -= 2;
|
||||
}
|
||||
|
||||
if (len)
|
||||
sum += *(uint8_t *)sbuf;
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static inline uint16_t finish_ip_csum(uint32_t sum)
|
||||
{
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
return ~((uint16_t)sum);
|
||||
}
|
||||
|
||||
static inline uint16_t build_ip_csum(const uint8_t *buf, int len, uint32_t sum)
|
||||
{
|
||||
sum += add_csum(buf, len);
|
||||
return finish_ip_csum(sum);
|
||||
}
|
||||
|
||||
static inline int build_ipv4_header(uint8_t *buf, uint8_t proto,
|
||||
int payload_len, struct in_addr *src,
|
||||
struct in_addr *dst)
|
||||
{
|
||||
struct iphdr *iph = (struct iphdr *)buf;
|
||||
|
||||
iph->ihl = 5;
|
||||
iph->version = 4;
|
||||
iph->ttl = TUNTAP_DEFAULT_TTL;
|
||||
iph->tot_len = htons(sizeof(*iph) + payload_len);
|
||||
iph->id = htons(TUNTAP_DEFAULT_IPID);
|
||||
iph->protocol = proto;
|
||||
iph->saddr = src->s_addr;
|
||||
iph->daddr = dst->s_addr;
|
||||
iph->check = build_ip_csum(buf, iph->ihl << 2, 0);
|
||||
|
||||
return iph->ihl << 2;
|
||||
}
|
||||
|
||||
static inline void ipv6_set_dsfield(struct ipv6hdr *ip6h, uint8_t dsfield)
|
||||
{
|
||||
uint16_t val, *ptr = (uint16_t *)ip6h;
|
||||
|
||||
val = ntohs(*ptr);
|
||||
val &= 0xF00F;
|
||||
val |= ((uint16_t)dsfield) << 4;
|
||||
*ptr = htons(val);
|
||||
}
|
||||
|
||||
static inline int build_ipv6_header(uint8_t *buf, uint8_t proto,
|
||||
uint8_t dsfield, int payload_len,
|
||||
struct in6_addr *src, struct in6_addr *dst)
|
||||
{
|
||||
struct ipv6hdr *ip6h = (struct ipv6hdr *)buf;
|
||||
|
||||
ip6h->version = 6;
|
||||
ip6h->payload_len = htons(payload_len);
|
||||
ip6h->nexthdr = proto;
|
||||
ip6h->hop_limit = TUNTAP_DEFAULT_TTL;
|
||||
ipv6_set_dsfield(ip6h, dsfield);
|
||||
memcpy(&ip6h->saddr, src, sizeof(ip6h->saddr));
|
||||
memcpy(&ip6h->daddr, dst, sizeof(ip6h->daddr));
|
||||
|
||||
return sizeof(struct ipv6hdr);
|
||||
}
|
||||
|
||||
static inline int build_geneve_header(uint8_t *buf, uint32_t vni)
|
||||
{
|
||||
uint16_t protocol = htons(ETH_P_TEB);
|
||||
uint32_t geneve_vni = htonl((vni << 8) & 0xffffff00);
|
||||
|
||||
memcpy(buf + 2, &protocol, 2);
|
||||
memcpy(buf + 4, &geneve_vni, 4);
|
||||
return GENEVE_HLEN;
|
||||
}
|
||||
|
||||
static inline int build_udp_header(uint8_t *buf, uint16_t sport, uint16_t dport,
|
||||
int payload_len)
|
||||
{
|
||||
struct udphdr *udph = (struct udphdr *)buf;
|
||||
|
||||
udph->source = htons(sport);
|
||||
udph->dest = htons(dport);
|
||||
udph->len = htons(sizeof(*udph) + payload_len);
|
||||
return sizeof(*udph);
|
||||
}
|
||||
|
||||
static inline void build_udp_packet_csum(uint8_t *buf, int family,
|
||||
bool csum_off)
|
||||
{
|
||||
struct udphdr *udph = (struct udphdr *)buf;
|
||||
size_t ipalen = ip_addr_len(family);
|
||||
uint32_t sum;
|
||||
|
||||
/* No extension IPv4 and IPv6 headers addresses are the last fields */
|
||||
sum = add_csum(buf - 2 * ipalen, 2 * ipalen);
|
||||
sum += htons(IPPROTO_UDP) + udph->len;
|
||||
|
||||
if (!csum_off)
|
||||
sum += add_csum(buf, udph->len);
|
||||
|
||||
udph->check = finish_ip_csum(sum);
|
||||
}
|
||||
|
||||
static inline int build_udp_packet(uint8_t *buf, uint16_t sport, uint16_t dport,
|
||||
int payload_len, int family, bool csum_off)
|
||||
{
|
||||
struct udphdr *udph = (struct udphdr *)buf;
|
||||
|
||||
build_udp_header(buf, sport, dport, payload_len);
|
||||
memset(buf + sizeof(*udph), PKT_DATA, payload_len);
|
||||
build_udp_packet_csum(buf, family, csum_off);
|
||||
|
||||
return sizeof(*udph) + payload_len;
|
||||
}
|
||||
|
||||
static inline int build_virtio_net_hdr_v1_hash_tunnel(uint8_t *buf, bool is_tap,
|
||||
int hdr_len, int gso_size,
|
||||
int outer_family,
|
||||
int inner_family)
|
||||
{
|
||||
struct virtio_net_hdr_v1_hash_tunnel *vh_tunnel = (void *)buf;
|
||||
struct virtio_net_hdr_v1 *vh = &vh_tunnel->hash_hdr.hdr;
|
||||
int outer_iphlen, inner_iphlen, eth_hlen, gso_type;
|
||||
|
||||
eth_hlen = is_tap ? ETH_HLEN : 0;
|
||||
outer_iphlen = (outer_family == AF_INET) ? sizeof(struct iphdr) :
|
||||
sizeof(struct ipv6hdr);
|
||||
inner_iphlen = (inner_family == AF_INET) ? sizeof(struct iphdr) :
|
||||
sizeof(struct ipv6hdr);
|
||||
|
||||
vh_tunnel->outer_th_offset = eth_hlen + outer_iphlen;
|
||||
vh_tunnel->inner_nh_offset = vh_tunnel->outer_th_offset + ETH_HLEN +
|
||||
GENEVE_HLEN + sizeof(struct udphdr);
|
||||
|
||||
vh->csum_start = vh_tunnel->inner_nh_offset + inner_iphlen;
|
||||
vh->csum_offset = __builtin_offsetof(struct udphdr, check);
|
||||
vh->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
vh->hdr_len = hdr_len;
|
||||
vh->gso_size = gso_size;
|
||||
|
||||
if (gso_size) {
|
||||
gso_type = outer_family == AF_INET ?
|
||||
VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4 :
|
||||
VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6;
|
||||
vh->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4 | gso_type;
|
||||
}
|
||||
|
||||
return sizeof(struct virtio_net_hdr_v1_hash_tunnel);
|
||||
}
|
||||
|
||||
#endif /* _TUNTAP_HELPERS_H */
|
||||
Loading…
Reference in New Issue
Block a user