// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ #include <linux/bpf.h> #include <bpf/bpf_endian.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> #include <linux/in.h> #include <linux/in6.h> #include <linux/ipv6.h> #include <linux/tcp.h> #include <sys/types.h> #include <sys/socket.h> #include "cgroup_tcp_skb.h" char _license[] SEC("license") = "GPL"; __u16 g_sock_port = 0; __u32 g_sock_state = 0; int g_unexpected = 0; __u32 g_packet_count = 0; int needed_tcp_pkt(struct __sk_buff *skb, struct tcphdr *tcph) { struct ipv6hdr ip6h; if (skb->protocol != bpf_htons(ETH_P_IPV6)) return 0; if (bpf_skb_load_bytes(skb, 0, &ip6h, sizeof(ip6h))) return 0; if (ip6h.nexthdr != IPPROTO_TCP) return 0; if (bpf_skb_load_bytes(skb, sizeof(ip6h), tcph, sizeof(*tcph))) return 0; if (tcph->source != bpf_htons(g_sock_port) && tcph->dest != bpf_htons(g_sock_port)) return 0; return 1; } /* Run accept() on a socket in the cgroup to receive a new connection. */ static int egress_accept(struct tcphdr *tcph) { if (g_sock_state == SYN_RECV_SENDING_SYN_ACK) { if (tcph->fin || !tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = SYN_RECV; return 1; } return 0; } static int ingress_accept(struct tcphdr *tcph) { switch (g_sock_state) { case INIT: if (!tcph->syn || tcph->fin || tcph->ack) g_unexpected++; else g_sock_state = SYN_RECV_SENDING_SYN_ACK; break; case SYN_RECV: if (tcph->fin || tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = ESTABLISHED; break; default: return 0; } return 1; } /* Run connect() on a socket in the cgroup to start a new connection. */ static int egress_connect(struct tcphdr *tcph) { if (g_sock_state == INIT) { if (!tcph->syn || tcph->fin || tcph->ack) g_unexpected++; else g_sock_state = SYN_SENT; return 1; } return 0; } static int ingress_connect(struct tcphdr *tcph) { if (g_sock_state == SYN_SENT) { if (tcph->fin || !tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = ESTABLISHED; return 1; } return 0; } /* The connection is closed by the peer outside the cgroup. */ static int egress_close_remote(struct tcphdr *tcph) { switch (g_sock_state) { case ESTABLISHED: break; case CLOSE_WAIT_SENDING_ACK: if (tcph->fin || tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = CLOSE_WAIT; break; case CLOSE_WAIT: if (!tcph->fin) g_unexpected++; else g_sock_state = LAST_ACK; break; default: return 0; } return 1; } static int ingress_close_remote(struct tcphdr *tcph) { switch (g_sock_state) { case ESTABLISHED: if (tcph->fin) g_sock_state = CLOSE_WAIT_SENDING_ACK; break; case LAST_ACK: if (tcph->fin || tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = CLOSED; break; default: return 0; } return 1; } /* The connection is closed by the endpoint inside the cgroup. */ static int egress_close_local(struct tcphdr *tcph) { switch (g_sock_state) { case ESTABLISHED: if (tcph->fin) g_sock_state = FIN_WAIT1; break; case TIME_WAIT_SENDING_ACK: if (tcph->fin || tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = TIME_WAIT; break; default: return 0; } return 1; } static int ingress_close_local(struct tcphdr *tcph) { switch (g_sock_state) { case ESTABLISHED: break; case FIN_WAIT1: if (tcph->fin || tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = FIN_WAIT2; break; case FIN_WAIT2: if (!tcph->fin || tcph->syn || !tcph->ack) g_unexpected++; else g_sock_state = TIME_WAIT_SENDING_ACK; break; default: return 0; } return 1; } /* Check the types of outgoing packets of a server socket to make sure they * are consistent with the state of the server socket. * * The connection is closed by the client side. */ SEC("cgroup_skb/egress") int server_egress(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Egress of the server socket. */ if (egress_accept(&tcph) || egress_close_remote(&tcph)) return 1; g_unexpected++; return 1; } /* Check the types of incoming packets of a server socket to make sure they * are consistent with the state of the server socket. * * The connection is closed by the client side. */ SEC("cgroup_skb/ingress") int server_ingress(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Ingress of the server socket. */ if (ingress_accept(&tcph) || ingress_close_remote(&tcph)) return 1; g_unexpected++; return 1; } /* Check the types of outgoing packets of a server socket to make sure they * are consistent with the state of the server socket. * * The connection is closed by the server side. */ SEC("cgroup_skb/egress") int server_egress_srv(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Egress of the server socket. */ if (egress_accept(&tcph) || egress_close_local(&tcph)) return 1; g_unexpected++; return 1; } /* Check the types of incoming packets of a server socket to make sure they * are consistent with the state of the server socket. * * The connection is closed by the server side. */ SEC("cgroup_skb/ingress") int server_ingress_srv(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Ingress of the server socket. */ if (ingress_accept(&tcph) || ingress_close_local(&tcph)) return 1; g_unexpected++; return 1; } /* Check the types of outgoing packets of a client socket to make sure they * are consistent with the state of the client socket. * * The connection is closed by the server side. */ SEC("cgroup_skb/egress") int client_egress_srv(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Egress of the server socket. */ if (egress_connect(&tcph) || egress_close_remote(&tcph)) return 1; g_unexpected++; return 1; } /* Check the types of incoming packets of a client socket to make sure they * are consistent with the state of the client socket. * * The connection is closed by the server side. */ SEC("cgroup_skb/ingress") int client_ingress_srv(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Ingress of the server socket. */ if (ingress_connect(&tcph) || ingress_close_remote(&tcph)) return 1; g_unexpected++; return 1; } /* Check the types of outgoing packets of a client socket to make sure they * are consistent with the state of the client socket. * * The connection is closed by the client side. */ SEC("cgroup_skb/egress") int client_egress(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Egress of the server socket. */ if (egress_connect(&tcph) || egress_close_local(&tcph)) return 1; g_unexpected++; return 1; } /* Check the types of incoming packets of a client socket to make sure they * are consistent with the state of the client socket. * * The connection is closed by the client side. */ SEC("cgroup_skb/ingress") int client_ingress(struct __sk_buff *skb) { struct tcphdr tcph; if (!needed_tcp_pkt(skb, &tcph)) return 1; g_packet_count++; /* Ingress of the server socket. */ if (ingress_connect(&tcph) || ingress_close_local(&tcph)) return 1; g_unexpected++; return 1; }