Skip to content

Commit

Permalink
sock/skb: Add IPv6 Support
Browse files Browse the repository at this point in the history
The sock and skb types currently support IPv4 addresses and CIDRs, and
in fact the whole subsystem assumes network operations are only IPv4 or
AF_INET.

This commit adds IPv6 support by allowing IPv6 addresses and CIDRs to be
specified for arg matching for sock and skb types; collects IPv6
addresses from sockets and datagrams where the family is AF_INET6; and
reports said addresses correctly in user space.

Signed-off-by: Kevin Sheldrake <[email protected]>
  • Loading branch information
kevsecurity committed Aug 17, 2023
1 parent c1aed1d commit 3cf9276
Show file tree
Hide file tree
Showing 15 changed files with 679 additions and 108 deletions.
19 changes: 19 additions & 0 deletions bpf/process/addr_lpm_maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,23 @@ struct {
});
} addr4lpm_maps SEC(".maps");

struct addr6_lpm_trie {
__u32 prefix;
__u32 addr[4];
};

struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, ADDR_LPM_MAPS_OUTER_MAX_ENTRIES);
__uint(key_size, sizeof(__u32));
__array(
values, struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__uint(max_entries, 1);
__type(key, __u8[20]); // Need to specify as byte array as wouldn't take struct as key type
__type(value, __u8);
__uint(map_flags, BPF_F_NO_PREALLOC);
});
} addr6lpm_maps SEC(".maps");

#endif // ADDR_LPM_MAPS_H__
50 changes: 35 additions & 15 deletions bpf/process/types/basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -771,20 +771,40 @@ struct ip_ver {
// use the selector value to determine a LPM Trie map, and do a lookup to determine whether the argument
// is in the defined set.
static inline __attribute__((always_inline)) long
filter_addr4_map(struct selector_arg_filter *filter, __u32 addr)
filter_addr_map(struct selector_arg_filter *filter, __u64 *addr, __u16 family)
{
void *addrmap;
__u32 map_idx = filter->value;
struct addr4_lpm_trie arg;

addrmap = map_lookup_elem(&addr4lpm_maps, &map_idx);
if (!addrmap)
__u32 *map_idxs = (__u32 *)&filter->value;
__u32 map_idx;
struct addr4_lpm_trie arg4;
struct addr6_lpm_trie arg6;
void *arg;

switch (family) {
case AF_INET:
map_idx = map_idxs[0];
addrmap = map_lookup_elem(&addr4lpm_maps, &map_idx);
if (!addrmap)
return 0;
arg4.prefix = 32;
arg4.addr = addr[0];
arg = &arg4;
break;
case AF_INET6:
map_idx = map_idxs[1];
addrmap = map_lookup_elem(&addr6lpm_maps, &map_idx);
if (!addrmap)
return 0;
arg6.prefix = 128;
// write the address in as 4 u32s due to alignment
write_ipv6_addr32(arg6.addr, (__u32 *)addr);
arg = &arg6;
break;
default:
return 0;
}

arg.prefix = 32;
arg.addr = addr;

__u8 *pass = map_lookup_elem(addrmap, &arg);
__u8 *pass = map_lookup_elem(addrmap, arg);

switch (filter->op) {
case op_filter_saddr:
Expand All @@ -797,13 +817,13 @@ filter_addr4_map(struct selector_arg_filter *filter, __u32 addr)
return 0;
}

/* filter_inet: runs a comparison between the IPv4 addresses and ports in
/* filter_inet: runs a comparison between the IPv4/6 addresses and ports in
* the sock or skb in the args aginst the filter parameters.
*/
static inline __attribute__((always_inline)) long
filter_inet(struct selector_arg_filter *filter, char *args)
{
__u32 addr = 0;
__u64 addr[2] = { 0, 0 };
__u32 port = 0;
__u32 value = 0;
struct sk_type *sk = 0;
Expand All @@ -826,11 +846,11 @@ filter_inet(struct selector_arg_filter *filter, char *args)
switch (filter->op) {
case op_filter_saddr:
case op_filter_notsaddr:
addr = tuple->saddr;
write_ipv6_addr(addr, tuple->saddr);
break;
case op_filter_daddr:
case op_filter_notdaddr:
addr = tuple->daddr;
write_ipv6_addr(addr, tuple->daddr);
break;
case op_filter_sport:
case op_filter_notsport:
Expand Down Expand Up @@ -874,7 +894,7 @@ filter_inet(struct selector_arg_filter *filter, char *args)
case op_filter_daddr:
case op_filter_notsaddr:
case op_filter_notdaddr:
return filter_addr4_map(filter, addr);
return filter_addr_map(filter, addr, tuple->family);
case op_filter_protocol:
case op_filter_family:
return filter_32ty_map(filter, (char *)&value);
Expand Down
207 changes: 158 additions & 49 deletions bpf/process/types/skb.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,104 @@ struct skb_type {
__u32 secpath_olen;
};

/* The IPv6 specification states that the following headers are valid
* after the fixed header (up to 1 of each, except Destination Options,
* which is up to 2):
* Hop-by-Hop Options (0)
* Routing (43)
* Fragment (44)
* Authentication Header (51)
* Destination Options (60)
* Encapsulation Security Payload Header (50)
* Mobilty Header (135)
* UDP Header (IPPROTO_UDP)
* TCP Header (IPPROTO_TCP)
* ICMP6 (IPPROTO_ICMP6)
*
* We choose to ignore Encapsulating Security Payload (ESP) because
* of complexity (future requirement), Mobility (n/a), Host Identity
* Protocol (replaces IP addresses), Shim6 Protocol (n/a), and the
* Reserved header types. If we come across one of these headers, we
* will return 0 to indicate failure (and no transport header). Otherwise,
* we will skip other headers and return the offset of the transport
* payload.
*/

struct ipv6extension {
u16 ip_off;
u16 byte_len;
u8 header_count;
u8 curr;
u8 next;
u8 len;
};

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, int);
__type(value, struct ipv6extension);
__uint(max_entries, 1);
} tg_ipv6_ext_heap SEC(".maps");

static inline __attribute__((always_inline)) u8
get_ip6_protocol(u16 *payload_off, struct ipv6hdr *ip, u16 network_header_off,
void *skb_head)
{
struct ipv6extension *e;
int zero = 0;
u8 header_count;

e = map_lookup_elem(&tg_ipv6_ext_heap, &zero);
if (!e)
return 0;

e->ip_off = network_header_off;
e->curr = 255;
e->len = 0;
if (probe_read(&e->next, sizeof(e->next), _(&ip->nexthdr)) < 0)
return 0;

// Maximum 7 valid extensions.
#pragma unroll
for (header_count = 0; header_count < 7; header_count++) {
// Correct the length parameter, depending on current extension.
switch (e->curr) {
case 255:
// Fixed header.
e->byte_len = sizeof(struct ipv6hdr);
break;
case 0:
case 43:
case 60:
e->byte_len = (e->len * 8) + 8;
break;
case 44:
e->byte_len = 8;
break;
case 51:
e->byte_len = (e->len * 4) + 8;
break;
}

// Move to next extension.
e->ip_off += e->byte_len;
// If next is transport (or an unhandled header, e.g. ESP or Mobility), return it and the optional offset.
if (e->next != 0 && e->next != 43 && e->next != 44 && e->next != 51 && e->next != 60) {
if (payload_off)
*payload_off = e->ip_off;
return e->next;
}
e->curr = e->next;
// Read next header and current length.
if (probe_read(&e->next, 2,
skb_head + e->ip_off) < 0) {
return 0;
}
}
// Not found transport header.
return 0;
}

/* set_event_from_skb(skb)
*
* Populate the event args with the SKB 5-tuple when supported. Currently,
Expand All @@ -26,6 +124,8 @@ set_event_from_skb(struct skb_type *event, struct sk_buff *skb)
{
unsigned char *skb_head = 0;
u16 l3_off;
typeof(skb->transport_header) l4_off;
u8 protocol;

probe_read(&skb_head, sizeof(skb_head), _(&skb->head));
probe_read(&l3_off, sizeof(l3_off), _(&skb->network_header));
Expand All @@ -36,61 +136,70 @@ set_event_from_skb(struct skb_type *event, struct sk_buff *skb)

u8 ip_ver = iphdr_byte0 >> 4;
if (ip_ver == 4) { // IPv4
u8 v4_prot;
probe_read(&v4_prot, 1, _(&ip->protocol));

event->tuple.protocol = v4_prot;

probe_read(&protocol, 1, _(&ip->protocol));
event->tuple.protocol = protocol;
event->tuple.family = AF_INET;

probe_read(&event->tuple.saddr, sizeof(event->tuple.saddr), _(&ip->saddr));
probe_read(&event->tuple.daddr, sizeof(event->tuple.daddr), _(&ip->daddr));
typeof(skb->transport_header) l4_off;
event->tuple.saddr[0] = 0;
event->tuple.saddr[1] = 0;
event->tuple.daddr[0] = 0;
event->tuple.daddr[1] = 0;
probe_read(&event->tuple.saddr, IPV4LEN, _(&ip->saddr));
probe_read(&event->tuple.daddr, IPV4LEN, _(&ip->daddr));
probe_read(&l4_off, sizeof(l4_off), _(&skb->transport_header));
if (v4_prot == IPPROTO_TCP) { // TCP
struct tcphdr *tcp =
(struct tcphdr *)(skb_head + l4_off);
probe_read(&event->tuple.sport, sizeof(event->tuple.sport),
_(&tcp->source));
probe_read(&event->tuple.dport, sizeof(event->tuple.dport),
_(&tcp->dest));
} else if (v4_prot == IPPROTO_UDP) { // UDP
struct udphdr *udp =
(struct udphdr *)(skb_head + l4_off);
probe_read(&event->tuple.sport, sizeof(event->tuple.sport),
_(&udp->source));
probe_read(&event->tuple.dport, sizeof(event->tuple.dport),
_(&udp->dest));
}
event->tuple.sport = bpf_ntohs(event->tuple.sport);
event->tuple.dport = bpf_ntohs(event->tuple.dport);
} else if (ip_ver == 6) {
struct ipv6hdr *ip6 = (struct ipv6hdr *)(skb_head + l3_off);

if (bpf_core_field_exists(skb->active_extensions)) {
struct sec_path *sp;
struct skb_ext *ext;
u64 offset;
protocol = get_ip6_protocol(&l4_off, ip6, l3_off, skb_head);
event->tuple.protocol = protocol;
event->tuple.family = AF_INET6;
probe_read(&event->tuple.saddr, IPV6LEN, _(&ip6->saddr));
probe_read(&event->tuple.daddr, IPV6LEN, _(&ip6->daddr));
} else {
// This is not IP, so we don't know how to parse further.
return -22;
}

if (protocol == IPPROTO_TCP) { // TCP
struct tcphdr *tcp =
(struct tcphdr *)(skb_head + l4_off);
probe_read(&event->tuple.sport, sizeof(event->tuple.sport),
_(&tcp->source));
probe_read(&event->tuple.dport, sizeof(event->tuple.dport),
_(&tcp->dest));
} else if (protocol == IPPROTO_UDP) { // UDP
struct udphdr *udp =
(struct udphdr *)(skb_head + l4_off);
probe_read(&event->tuple.sport, sizeof(event->tuple.sport),
_(&udp->source));
probe_read(&event->tuple.dport, sizeof(event->tuple.dport),
_(&udp->dest));
} else {
event->tuple.sport = 0;
event->tuple.dport = 0;
}
event->tuple.sport = bpf_ntohs(event->tuple.sport);
event->tuple.dport = bpf_ntohs(event->tuple.dport);

if (bpf_core_field_exists(skb->active_extensions)) {
struct sec_path *sp;
struct skb_ext *ext;
u64 offset;

#define SKB_EXT_SEC_PATH 1 // TBD do this with BTF
probe_read(&ext, sizeof(ext), _(&skb->extensions));
if (ext) {
probe_read(&offset, sizeof(offset),
_(&ext->offset[SKB_EXT_SEC_PATH]));
sp = (void *)ext + (offset << 3);

probe_read(&event->secpath_len,
sizeof(event->secpath_len),
_(&sp->len));
probe_read(&event->secpath_olen,
sizeof(event->secpath_olen),
_(&sp->olen));
}
probe_read(&ext, sizeof(ext), _(&skb->extensions));
if (ext) {
probe_read(&offset, sizeof(offset),
_(&ext->offset[SKB_EXT_SEC_PATH]));
sp = (void *)ext + (offset << 3);

probe_read(&event->secpath_len,
sizeof(event->secpath_len),
_(&sp->len));
probe_read(&event->secpath_olen,
sizeof(event->secpath_olen),
_(&sp->olen));
}
return 0;
} else if (ip_ver == 6) {
return -1;
}

// This is not IP, so we don't know how to parse further.
return -22;
return 0;
}
#endif // __SKB_H__
18 changes: 14 additions & 4 deletions bpf/process/types/sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ set_event_from_sock(struct sk_type *event, struct sock *sk)

event->sockaddr = (__u64)sk;

event->tuple.family = 0;

probe_read(&event->tuple.family, sizeof(event->tuple.family),
_(&common->skc_family));
probe_read(&event->state, sizeof(event->state),
Expand All @@ -49,8 +47,20 @@ set_event_from_sock(struct sk_type *event, struct sock *sk)
probe_read(&event->priority, sizeof(event->priority),
_(&sk->sk_priority));

probe_read(&event->tuple.saddr, sizeof(event->tuple.saddr), _(&common->skc_rcv_saddr));
probe_read(&event->tuple.daddr, sizeof(event->tuple.daddr), _(&common->skc_daddr));
event->tuple.saddr[0] = 0;
event->tuple.saddr[1] = 0;
event->tuple.daddr[0] = 0;
event->tuple.daddr[1] = 0;
switch (event->tuple.family) {
case AF_INET:
probe_read(&event->tuple.saddr, IPV4LEN, _(&common->skc_rcv_saddr));
probe_read(&event->tuple.daddr, IPV4LEN, _(&common->skc_daddr));
break;
case AF_INET6:
probe_read(&event->tuple.saddr, IPV6LEN, _(&common->skc_v6_rcv_saddr));
probe_read(&event->tuple.daddr, IPV6LEN, _(&common->skc_v6_daddr));
}

probe_read(&event->tuple.sport, sizeof(event->tuple.sport), _(&common->skc_num));
probe_read(&event->tuple.dport, sizeof(event->tuple.dport), _(&common->skc_dport));
event->tuple.dport = bpf_ntohs(event->tuple.dport);
Expand Down
Loading

0 comments on commit 3cf9276

Please sign in to comment.