github.com/cilium/cilium@v1.16.2/bpf/lib/ipv4.h (about) 1 /* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ 2 /* Copyright Authors of Cilium */ 3 4 #pragma once 5 6 #include <linux/ip.h> 7 8 #include "dbg.h" 9 #include "l4.h" 10 #include "metrics.h" 11 12 #define IPV4_SADDR_OFF offsetof(struct iphdr, saddr) 13 #define IPV4_DADDR_OFF offsetof(struct iphdr, daddr) 14 15 struct ipv4_frag_id { 16 __be32 daddr; 17 __be32 saddr; 18 __be16 id; /* L4 datagram identifier */ 19 __u8 proto; 20 __u8 pad; 21 } __packed; 22 23 struct ipv4_frag_l4ports { 24 __be16 sport; 25 __be16 dport; 26 } __packed; 27 28 #ifdef ENABLE_IPV4_FRAGMENTS 29 struct { 30 __uint(type, BPF_MAP_TYPE_LRU_HASH); 31 __type(key, struct ipv4_frag_id); 32 __type(value, struct ipv4_frag_l4ports); 33 __uint(pinning, LIBBPF_PIN_BY_NAME); 34 __uint(max_entries, CILIUM_IPV4_FRAG_MAP_MAX_ENTRIES); 35 } IPV4_FRAG_DATAGRAMS_MAP __section_maps_btf; 36 #endif 37 38 static __always_inline int 39 ipv4_csum_update_by_value(struct __ctx_buff *ctx, int l3_off, __u64 old_val, 40 __u64 new_val, __u32 len) 41 { 42 return l3_csum_replace(ctx, l3_off + offsetof(struct iphdr, check), 43 old_val, new_val, len); 44 } 45 46 static __always_inline int 47 ipv4_csum_update_by_diff(struct __ctx_buff *ctx, int l3_off, __u64 diff) 48 { 49 return l3_csum_replace(ctx, l3_off + offsetof(struct iphdr, check), 50 0, diff, 0); 51 } 52 53 static __always_inline int ipv4_load_daddr(struct __ctx_buff *ctx, int off, 54 __u32 *dst) 55 { 56 return ctx_load_bytes(ctx, off + offsetof(struct iphdr, daddr), dst, 4); 57 } 58 59 static __always_inline int ipv4_dec_ttl(struct __ctx_buff *ctx, int off, 60 struct iphdr *ip4) 61 { 62 __u8 new_ttl, ttl = ip4->ttl; 63 64 if (ttl <= 1) 65 return DROP_TTL_EXCEEDED; 66 67 new_ttl = ttl - 1; 68 ip4->ttl = new_ttl; 69 70 /* l3_csum_replace() takes at min 2 bytes, zero extended. */ 71 if (ipv4_csum_update_by_value(ctx, off, ttl, new_ttl, 2) < 0) 72 return DROP_CSUM_L3; 73 74 return 0; 75 } 76 77 static __always_inline int ipv4_hdrlen(const struct iphdr *ip4) 78 { 79 return ip4->ihl * 4; 80 } 81 82 static __always_inline bool ipv4_is_fragment(const struct iphdr *ip4) 83 { 84 /* The frag_off portion of the header consists of: 85 * 86 * +----+----+----+----------------------------------+ 87 * | RS | DF | MF | ...13 bits of fragment offset... | 88 * +----+----+----+----------------------------------+ 89 * 90 * If "More fragments" or the offset is nonzero, then this is an IP 91 * fragment (RFC791). 92 */ 93 return ip4->frag_off & bpf_htons(0x3FFF); 94 } 95 96 static __always_inline bool ipv4_is_not_first_fragment(const struct iphdr *ip4) 97 { 98 /* Ignore "More fragments" bit to catch all fragments but the first */ 99 return ip4->frag_off & bpf_htons(0x1FFF); 100 } 101 102 /* Simply a reverse of ipv4_is_not_first_fragment to avoid double negative. */ 103 static __always_inline bool ipv4_has_l4_header(const struct iphdr *ip4) 104 { 105 return !ipv4_is_not_first_fragment(ip4); 106 } 107 108 static __always_inline bool ipv4_is_in_subnet(__be32 addr, 109 __be32 subnet, int prefixlen) 110 { 111 return (addr & bpf_htonl(~((1 << (32 - prefixlen)) - 1))) == subnet; 112 } 113 114 #ifdef ENABLE_IPV4_FRAGMENTS 115 static __always_inline int 116 ipv4_frag_get_l4ports(const struct ipv4_frag_id *frag_id, 117 struct ipv4_frag_l4ports *ports) 118 { 119 struct ipv4_frag_l4ports *tmp; 120 121 tmp = map_lookup_elem(&IPV4_FRAG_DATAGRAMS_MAP, frag_id); 122 if (!tmp) 123 return DROP_FRAG_NOT_FOUND; 124 125 /* Do not make ports a pointer to map data, copy from map */ 126 memcpy(ports, tmp, sizeof(*ports)); 127 return 0; 128 } 129 130 static __always_inline int 131 ipv4_handle_fragmentation(struct __ctx_buff *ctx, 132 const struct iphdr *ip4, int l4_off, 133 enum ct_dir ct_dir, 134 struct ipv4_frag_l4ports *ports, 135 bool *has_l4_header) 136 { 137 bool is_fragment, not_first_fragment; 138 int ret; 139 140 struct ipv4_frag_id frag_id = { 141 .daddr = ip4->daddr, 142 .saddr = ip4->saddr, 143 .id = ip4->id, 144 .proto = ip4->protocol, 145 .pad = 0, 146 }; 147 148 is_fragment = ipv4_is_fragment(ip4); 149 150 if (unlikely(is_fragment)) { 151 not_first_fragment = ipv4_is_not_first_fragment(ip4); 152 if (has_l4_header) 153 *has_l4_header = !not_first_fragment; 154 155 if (likely(not_first_fragment)) 156 return ipv4_frag_get_l4ports(&frag_id, ports); 157 } 158 159 /* load sport + dport into tuple */ 160 ret = l4_load_ports(ctx, l4_off, (__be16 *)ports); 161 if (ret < 0) 162 return DROP_CT_INVALID_HDR; 163 164 if (unlikely(is_fragment)) { 165 /* First logical fragment for this datagram (not necessarily the first 166 * we receive). Fragment has L4 header, create an entry in datagrams map. 167 */ 168 if (map_update_elem(&IPV4_FRAG_DATAGRAMS_MAP, &frag_id, ports, BPF_ANY)) 169 update_metrics(ctx_full_len(ctx), ct_to_metrics_dir(ct_dir), 170 REASON_FRAG_PACKET_UPDATE); 171 172 /* Do not return an error if map update failed, as nothing prevents us 173 * to process the current packet normally. 174 */ 175 } 176 177 return 0; 178 } 179 #endif 180 181 static __always_inline int 182 ipv4_load_l4_ports(struct __ctx_buff *ctx, struct iphdr *ip4 __maybe_unused, 183 int l4_off, enum ct_dir dir __maybe_unused, 184 __be16 *ports, bool *has_l4_header __maybe_unused) 185 { 186 #ifdef ENABLE_IPV4_FRAGMENTS 187 return ipv4_handle_fragmentation(ctx, ip4, l4_off, dir, 188 (struct ipv4_frag_l4ports *)ports, 189 has_l4_header); 190 #else 191 if (l4_load_ports(ctx, l4_off, ports) < 0) 192 return DROP_CT_INVALID_HDR; 193 #endif 194 195 return CTX_ACT_OK; 196 }