github.com/datadog/cilium@v1.6.12/bpf/lib/icmp6.h (about) 1 /* 2 * Copyright (C) 2016-2019 Authors of Cilium 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 */ 18 #if !defined __LIB_ICMP6__ && defined ENABLE_IPV6 19 #define __LIB_ICMP6__ 20 21 #include <linux/icmpv6.h> 22 #include <linux/in.h> 23 #include "common.h" 24 #include "eth.h" 25 #include "drop.h" 26 27 #define ICMP6_TYPE_OFFSET (sizeof(struct ipv6hdr) + offsetof(struct icmp6hdr, icmp6_type)) 28 #define ICMP6_CSUM_OFFSET (sizeof(struct ipv6hdr) + offsetof(struct icmp6hdr, icmp6_cksum)) 29 #define ICMP6_ND_TARGET_OFFSET (sizeof(struct ipv6hdr) + sizeof(struct icmp6hdr)) 30 #define ICMP6_ND_OPTS (sizeof(struct ipv6hdr) + sizeof(struct icmp6hdr) + sizeof(struct in6_addr)) 31 32 /* If not specific action is specified, drop unknown neighbour solication 33 * messages */ 34 #ifndef ACTION_UNKNOWN_ICMP6_NS 35 #define ACTION_UNKNOWN_ICMP6_NS DROP_UNKNOWN_TARGET 36 #endif 37 38 static inline __u8 icmp6_load_type(struct __sk_buff *skb, int nh_off) 39 { 40 __u8 type; 41 skb_load_bytes(skb, nh_off + ICMP6_TYPE_OFFSET, &type, sizeof(type)); 42 return type; 43 } 44 45 static inline int __inline__ icmp6_send_reply(struct __sk_buff *skb, int nh_off) 46 { 47 union macaddr smac, dmac = NODE_MAC; 48 const int csum_off = nh_off + ICMP6_CSUM_OFFSET; 49 union v6addr sip, dip, router_ip; 50 __be32 sum; 51 52 if (ipv6_load_saddr(skb, nh_off, &sip) < 0 || 53 ipv6_load_daddr(skb, nh_off, &dip) < 0) 54 return DROP_INVALID; 55 56 BPF_V6(router_ip, ROUTER_IP); 57 /* skb->saddr = skb->daddr */ 58 if (ipv6_store_saddr(skb, router_ip.addr, nh_off) < 0) 59 return DROP_WRITE_ERROR; 60 /* skb->daddr = skb->saddr */ 61 if (ipv6_store_daddr(skb, sip.addr, nh_off) < 0) 62 return DROP_WRITE_ERROR; 63 64 /* fixup checksums */ 65 sum = csum_diff(sip.addr, 16, router_ip.addr, 16, 0); 66 if (l4_csum_replace(skb, csum_off, 0, sum, BPF_F_PSEUDO_HDR) < 0) 67 return DROP_CSUM_L4; 68 69 sum = csum_diff(dip.addr, 16, sip.addr, 16, 0); 70 if (l4_csum_replace(skb, csum_off, 0, sum, BPF_F_PSEUDO_HDR) < 0) 71 return DROP_CSUM_L4; 72 73 /* dmac = smac, smac = dmac */ 74 if (eth_load_saddr(skb, smac.addr, 0) < 0) 75 return DROP_INVALID; 76 77 // eth_load_daddr(skb, dmac.addr, 0); 78 if (eth_store_daddr(skb, smac.addr, 0) < 0 || 79 eth_store_saddr(skb, dmac.addr, 0) < 0) 80 return DROP_WRITE_ERROR; 81 82 cilium_dbg_capture(skb, DBG_CAPTURE_DELIVERY, skb->ifindex); 83 84 return redirect_self(skb); 85 } 86 87 static inline int __icmp6_send_echo_reply(struct __sk_buff *skb, int nh_off) 88 { 89 struct icmp6hdr icmp6hdr = {}, icmp6hdr_old; 90 int csum_off = nh_off + ICMP6_CSUM_OFFSET; 91 __be32 sum; 92 93 cilium_dbg(skb, DBG_ICMP6_REQUEST, nh_off, 0); 94 95 if (skb_load_bytes(skb, nh_off + sizeof(struct ipv6hdr), &icmp6hdr_old, 96 sizeof(icmp6hdr_old)) < 0) 97 return DROP_INVALID; 98 99 /* fill icmp6hdr */ 100 icmp6hdr.icmp6_type = 129; 101 icmp6hdr.icmp6_code = 0; 102 icmp6hdr.icmp6_cksum = icmp6hdr_old.icmp6_cksum; 103 icmp6hdr.icmp6_dataun.un_data32[0] = 0; 104 icmp6hdr.icmp6_identifier = icmp6hdr_old.icmp6_identifier; 105 icmp6hdr.icmp6_sequence = icmp6hdr_old.icmp6_sequence; 106 107 if (skb_store_bytes(skb, nh_off + sizeof(struct ipv6hdr), &icmp6hdr, 108 sizeof(icmp6hdr), 0) < 0) 109 return DROP_WRITE_ERROR; 110 111 /* fixup checksum */ 112 sum = csum_diff(&icmp6hdr_old, sizeof(icmp6hdr_old), 113 &icmp6hdr, sizeof(icmp6hdr), 0); 114 115 if (l4_csum_replace(skb, csum_off, 0, sum, BPF_F_PSEUDO_HDR) < 0) 116 return DROP_CSUM_L4; 117 118 return icmp6_send_reply(skb, nh_off); 119 } 120 121 __section_tail(CILIUM_MAP_CALLS, CILIUM_CALL_SEND_ICMP6_ECHO_REPLY) int tail_icmp6_send_echo_reply(struct __sk_buff *skb) 122 { 123 int ret, nh_off = skb->cb[0]; 124 __u8 direction = skb->cb[1]; 125 126 skb->cb[0] = 0; 127 ret = __icmp6_send_echo_reply(skb, nh_off); 128 if (IS_ERR(ret)) 129 return send_drop_notify_error(skb, 0, ret, TC_ACT_SHOT, direction); 130 131 return ret; 132 } 133 134 /* 135 * icmp6_send_echo_reply 136 * @skb: socket buffer 137 * @nh_off: offset to the IPv6 header 138 * 139 * Send an ICMPv6 echo reply in return to an ICMPv6 echo reply. 140 * 141 * NOTE: This is terminal function and will cause the BPF program to exit 142 */ 143 static inline int icmp6_send_echo_reply(struct __sk_buff *skb, int nh_off, 144 __u8 direction) 145 { 146 skb->cb[0] = nh_off; 147 skb->cb[1] = direction; 148 ep_tail_call(skb, CILIUM_CALL_SEND_ICMP6_ECHO_REPLY); 149 150 return DROP_MISSED_TAIL_CALL; 151 } 152 153 static inline int send_icmp6_ndisc_adv(struct __sk_buff *skb, int nh_off, 154 union macaddr *mac) 155 { 156 struct icmp6hdr icmp6hdr = {}, icmp6hdr_old; 157 __u8 opts[8], opts_old[8]; 158 const int csum_off = nh_off + ICMP6_CSUM_OFFSET; 159 __be32 sum; 160 161 if (skb_load_bytes(skb, nh_off + sizeof(struct ipv6hdr), &icmp6hdr_old, 162 sizeof(icmp6hdr_old)) < 0) 163 return DROP_INVALID; 164 165 /* fill icmp6hdr */ 166 icmp6hdr.icmp6_type = 136; 167 icmp6hdr.icmp6_code = 0; 168 icmp6hdr.icmp6_cksum = icmp6hdr_old.icmp6_cksum; 169 icmp6hdr.icmp6_dataun.un_data32[0] = 0; 170 icmp6hdr.icmp6_router = 1; 171 icmp6hdr.icmp6_solicited = 1; 172 icmp6hdr.icmp6_override = 0; 173 174 if (skb_store_bytes(skb, nh_off + sizeof(struct ipv6hdr), &icmp6hdr, sizeof(icmp6hdr), 0) < 0) 175 return DROP_WRITE_ERROR; 176 177 /* fixup checksums */ 178 sum = csum_diff(&icmp6hdr_old, sizeof(icmp6hdr_old), 179 &icmp6hdr, sizeof(icmp6hdr), 0); 180 if (l4_csum_replace(skb, csum_off, 0, sum, BPF_F_PSEUDO_HDR) < 0) 181 return DROP_CSUM_L4; 182 183 /* get old options */ 184 if (skb_load_bytes(skb, nh_off + ICMP6_ND_OPTS, opts_old, sizeof(opts_old)) < 0) 185 return DROP_INVALID; 186 187 opts[0] = 2; 188 opts[1] = 1; 189 opts[2] = mac->addr[0]; 190 opts[3] = mac->addr[1]; 191 opts[4] = mac->addr[2]; 192 opts[5] = mac->addr[3]; 193 opts[6] = mac->addr[4]; 194 opts[7] = mac->addr[5]; 195 196 /* store ND_OPT_TARGET_LL_ADDR option */ 197 if (skb_store_bytes(skb, nh_off + ICMP6_ND_OPTS, opts, sizeof(opts), 0) < 0) 198 return DROP_WRITE_ERROR; 199 200 /* fixup checksum */ 201 sum = csum_diff(opts_old, sizeof(opts_old), opts, sizeof(opts), 0); 202 if (l4_csum_replace(skb, csum_off, 0, sum, BPF_F_PSEUDO_HDR) < 0) 203 return DROP_CSUM_L4; 204 205 return icmp6_send_reply(skb, nh_off); 206 } 207 208 static inline __be32 compute_icmp6_csum(char data[80], __u16 payload_len, 209 struct ipv6hdr *ipv6hdr) 210 { 211 __be32 sum; 212 213 /* compute checksum with new payload length */ 214 sum = csum_diff(NULL, 0, data, payload_len, 0); 215 //printk("csum1 = %x\n", sum); 216 sum = ipv6_pseudohdr_checksum(ipv6hdr, IPPROTO_ICMPV6, payload_len, 217 sum); 218 //printk("csum2 = %x\n", sum); 219 220 return sum; 221 } 222 223 #ifdef HAVE_SKB_CHANGE_TAIL 224 static inline int __icmp6_send_time_exceeded(struct __sk_buff *skb, int nh_off) 225 { 226 /* FIXME: Fix code below to not require this init */ 227 char data[80] = {}; 228 struct icmp6hdr *icmp6hoplim; 229 struct ipv6hdr *ipv6hdr; 230 char *upper; /* icmp6 or tcp or udp */ 231 const int csum_off = nh_off + ICMP6_CSUM_OFFSET; 232 __be32 sum = 0; 233 __u16 payload_len = 0; /* FIXME: Uninit of this causes verifier bug */ 234 __u8 icmp6_nexthdr = IPPROTO_ICMPV6; 235 int trimlen; 236 237 /* initialize pointers to offsets in data */ 238 icmp6hoplim = (struct icmp6hdr *)data; 239 ipv6hdr = (struct ipv6hdr *)(data + 8); 240 upper = (data + 48); 241 242 /* fill icmp6hdr */ 243 icmp6hoplim->icmp6_type = 3; 244 icmp6hoplim->icmp6_code = 0; 245 icmp6hoplim->icmp6_cksum = 0; 246 icmp6hoplim->icmp6_dataun.un_data32[0] = 0; 247 248 cilium_dbg(skb, DBG_ICMP6_TIME_EXCEEDED, 0, 0); 249 250 /* read original v6 hdr into offset 8 */ 251 if (skb_load_bytes(skb, nh_off, ipv6hdr, sizeof(*ipv6hdr)) < 0) 252 return DROP_INVALID; 253 254 if (ipv6_store_nexthdr(skb, &icmp6_nexthdr, nh_off) < 0) 255 return DROP_WRITE_ERROR; 256 257 /* read original v6 payload into offset 48 */ 258 switch (ipv6hdr->nexthdr) { 259 case IPPROTO_ICMPV6: 260 case IPPROTO_UDP: 261 if (skb_load_bytes(skb, nh_off + sizeof(struct ipv6hdr), 262 upper, 8) < 0) 263 return DROP_INVALID; 264 sum = compute_icmp6_csum(data, 56, ipv6hdr); 265 payload_len = bpf_htons(56); 266 trimlen = 56 - bpf_ntohs(ipv6hdr->payload_len); 267 if (skb_change_tail(skb, skb->len + trimlen, 0) < 0) 268 return DROP_WRITE_ERROR; 269 /* trim or expand buffer and copy data buffer after ipv6 header */ 270 if (skb_store_bytes(skb, nh_off + sizeof(struct ipv6hdr), 271 data, 56, 0) < 0) 272 return DROP_WRITE_ERROR; 273 if (ipv6_store_paylen(skb, nh_off, &payload_len) < 0) 274 return DROP_WRITE_ERROR; 275 276 break; 277 /* copy header without options */ 278 case IPPROTO_TCP: 279 if (skb_load_bytes(skb, nh_off + sizeof(struct ipv6hdr), 280 upper, 20) < 0) 281 return DROP_INVALID; 282 sum = compute_icmp6_csum(data, 68, ipv6hdr); 283 payload_len = bpf_htons(68); 284 /* trim or expand buffer and copy data buffer after ipv6 header */ 285 trimlen = 68 - bpf_ntohs(ipv6hdr->payload_len); 286 if (skb_change_tail(skb, skb->len + trimlen, 0) < 0) 287 return DROP_WRITE_ERROR; 288 if (skb_store_bytes(skb, nh_off + sizeof(struct ipv6hdr), 289 data, 68, 0) < 0) 290 return DROP_WRITE_ERROR; 291 if (ipv6_store_paylen(skb, nh_off, &payload_len) < 0) 292 return DROP_WRITE_ERROR; 293 294 break; 295 default: 296 return DROP_UNKNOWN_L4; 297 } 298 299 if (l4_csum_replace(skb, csum_off, 0, sum, BPF_F_PSEUDO_HDR) < 0) 300 return DROP_CSUM_L4; 301 302 return icmp6_send_reply(skb, nh_off); 303 } 304 #endif 305 306 __section_tail(CILIUM_MAP_CALLS, CILIUM_CALL_SEND_ICMP6_TIME_EXCEEDED) int tail_icmp6_send_time_exceeded(struct __sk_buff *skb) 307 { 308 #ifdef HAVE_SKB_CHANGE_TAIL 309 int ret, nh_off = skb->cb[0]; 310 __u8 direction = skb->cb[1]; 311 312 skb->cb[0] = 0; 313 ret = __icmp6_send_time_exceeded(skb, nh_off); 314 if (IS_ERR(ret)) 315 return send_drop_notify_error(skb, 0, ret, TC_ACT_SHOT, direction); 316 317 return ret; 318 #else 319 return 0; 320 #endif 321 } 322 323 /* 324 * icmp6_send_time_exceeded 325 * @skb: socket buffer 326 * @nh_off: offset to the IPv6 header 327 * @direction: direction of packet (can be ingress or egress) 328 * Send a ICMPv6 time exceeded in response to an IPv6 frame. 329 * 330 * NOTE: This is terminal function and will cause the BPF program to exit 331 */ 332 static inline int icmp6_send_time_exceeded(struct __sk_buff *skb, int nh_off, __u8 direction) 333 { 334 skb->cb[0] = nh_off; 335 skb->cb[1] = direction; 336 337 ep_tail_call(skb, CILIUM_CALL_SEND_ICMP6_TIME_EXCEEDED); 338 339 return DROP_MISSED_TAIL_CALL; 340 } 341 342 static inline int __icmp6_handle_ns(struct __sk_buff *skb, int nh_off) 343 { 344 union v6addr target, router; 345 346 if (skb_load_bytes(skb, nh_off + ICMP6_ND_TARGET_OFFSET, target.addr, 347 sizeof(((struct ipv6hdr *)NULL)->saddr)) < 0) 348 return DROP_INVALID; 349 350 cilium_dbg(skb, DBG_ICMP6_NS, target.p3, target.p4); 351 352 BPF_V6(router, ROUTER_IP); 353 if (ipv6_addrcmp(&target, &router) == 0) { 354 union macaddr router_mac = NODE_MAC; 355 356 return send_icmp6_ndisc_adv(skb, nh_off, &router_mac); 357 } else { 358 /* Unknown target address, drop */ 359 return ACTION_UNKNOWN_ICMP6_NS; 360 } 361 } 362 363 __section_tail(CILIUM_MAP_CALLS, CILIUM_CALL_HANDLE_ICMP6_NS) int tail_icmp6_handle_ns(struct __sk_buff *skb) 364 { 365 int ret, nh_off = skb->cb[0]; 366 __u8 direction = skb->cb[1]; 367 368 skb->cb[0] = 0; 369 ret = __icmp6_handle_ns(skb, nh_off); 370 if (IS_ERR(ret)) 371 return send_drop_notify_error(skb, 0, ret, TC_ACT_SHOT, direction); 372 373 return ret; 374 } 375 376 /* 377 * icmp6_handle_ns 378 * @skb: socket buffer 379 * @nh_off: offset to the IPv6 header 380 * @direction: direction of packet(ingress or egress) 381 * 382 * Respond to ICMPv6 Neighbour Solicitation 383 * 384 * NOTE: This is terminal function and will cause the BPF program to exit 385 */ 386 static inline int icmp6_handle_ns(struct __sk_buff *skb, int nh_off, __u8 direction) 387 { 388 skb->cb[0] = nh_off; 389 skb->cb[1] = direction; 390 391 ep_tail_call(skb, CILIUM_CALL_HANDLE_ICMP6_NS); 392 393 return DROP_MISSED_TAIL_CALL; 394 } 395 396 static inline int icmp6_handle(struct __sk_buff *skb, int nh_off, 397 struct ipv6hdr *ip6, __u8 direction) 398 { 399 union v6addr router_ip; 400 __u8 type = icmp6_load_type(skb, nh_off); 401 402 cilium_dbg(skb, DBG_ICMP6_HANDLE, type, 0); 403 BPF_V6(router_ip, ROUTER_IP); 404 405 switch(type) { 406 case 135: 407 return icmp6_handle_ns(skb, nh_off, direction); 408 case ICMPV6_ECHO_REQUEST: 409 if (!ipv6_addrcmp((union v6addr *) &ip6->daddr, &router_ip)) 410 return icmp6_send_echo_reply(skb, nh_off, direction); 411 break; 412 } 413 414 /* All branching above will have issued a tail call, all 415 * remaining traffic is subject to forwarding to containers. 416 */ 417 return 0; 418 } 419 420 #endif