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