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