1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 /* The SPDX header above is actually correct in claiming this was
4  * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that
5  * compatible with GPL we will claim this to be GPL however, which should be
6  * fine given that LGPL-2.1-or-later downgrades to GPL if needed.
7  */
8 
9 #include "socket-bind-api.bpf.h"
10 /* <linux/types.h> must precede <bpf/bpf_helpers.h> due to
11  * <bpf/bpf_helpers.h> does not depend from type header by design.
12  */
13 #include <linux/types.h>
14 #include <bpf/bpf_endian.h>
15 #include <bpf/bpf_helpers.h>
16 #include <linux/bpf.h>
17 #include <netinet/in.h>
18 #include <stdbool.h>
19 
20 /*
21  * max_entries is set from user space with bpf_map__resize helper.
22  */
23 struct socket_bind_map_t {
24         __uint(type, BPF_MAP_TYPE_ARRAY);
25         __type(key, __u32);
26         __type(value, struct socket_bind_rule);
27 };
28 
29 enum socket_bind_action {
30         SOCKET_BIND_DENY = 0,
31         SOCKET_BIND_ALLOW = 1,
32 };
33 
34 struct socket_bind_map_t sd_bind_allow SEC(".maps");
35 struct socket_bind_map_t sd_bind_deny SEC(".maps");
36 
match_af(__u8 address_family,const struct socket_bind_rule * r)37 static __always_inline bool match_af(
38                 __u8 address_family, const struct socket_bind_rule *r) {
39         return r->address_family == AF_UNSPEC || address_family == r->address_family;
40 }
41 
match_protocol(__u32 protocol,const struct socket_bind_rule * r)42 static __always_inline bool match_protocol(
43                 __u32 protocol, const struct socket_bind_rule *r) {
44         return r->protocol == 0 || r->protocol == protocol;
45 }
46 
match_user_port(__u16 port,const struct socket_bind_rule * r)47 static __always_inline bool match_user_port(
48                 __u16 port, const struct socket_bind_rule *r) {
49         return r->nr_ports == 0 ||
50                 (port >= r->port_min && port < r->port_min + (__u32) r->nr_ports);
51 }
52 
match(__u8 address_family,__u32 protocol,__u16 port,const struct socket_bind_rule * r)53 static __always_inline bool match(
54                 __u8 address_family,
55                 __u32 protocol,
56                 __u16 port,
57                 const struct socket_bind_rule *r) {
58         return match_af(address_family, r) &&
59                 match_protocol(protocol, r) &&
60                 match_user_port(port, r);
61 }
62 
match_rules(struct bpf_sock_addr * ctx,struct socket_bind_map_t * rules)63 static __always_inline bool match_rules(
64                 struct bpf_sock_addr *ctx,
65                 struct socket_bind_map_t *rules) {
66         volatile __u32 user_port = ctx->user_port;
67         __u16 port = (__u16)bpf_ntohs(user_port);
68 
69         for (__u32 i = 0; i < SOCKET_BIND_MAX_RULES; ++i) {
70                 const __u32 key = i;
71                 const struct socket_bind_rule *rule = bpf_map_lookup_elem(rules, &key);
72 
73                 /* Lookup returns NULL if iterator is advanced past the last
74                  * element put in the map. */
75                 if (!rule)
76                         break;
77 
78                 if (match(ctx->user_family, ctx->protocol, port, rule))
79                         return true;
80         }
81 
82         return false;
83 }
84 
bind_socket(struct bpf_sock_addr * ctx)85 static __always_inline int bind_socket(struct bpf_sock_addr *ctx) {
86         if (match_rules(ctx, &sd_bind_allow))
87                 return SOCKET_BIND_ALLOW;
88 
89         if (match_rules(ctx, &sd_bind_deny))
90                 return SOCKET_BIND_DENY;
91 
92         return SOCKET_BIND_ALLOW;
93 }
94 
95 SEC("cgroup/bind4")
sd_bind4(struct bpf_sock_addr * ctx)96 int sd_bind4(struct bpf_sock_addr *ctx) {
97         if (ctx->user_family != AF_INET || ctx->family != AF_INET)
98                 return SOCKET_BIND_ALLOW;
99 
100         return bind_socket(ctx);
101 }
102 
103 SEC("cgroup/bind6")
sd_bind6(struct bpf_sock_addr * ctx)104 int sd_bind6(struct bpf_sock_addr *ctx) {
105         if (ctx->user_family != AF_INET6 || ctx->family != AF_INET6)
106                 return SOCKET_BIND_ALLOW;
107 
108         return bind_socket(ctx);
109 }
110 
111 char _license[] SEC("license") = "GPL";
112