1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <string.h>
4 
5 #include <linux/stddef.h>
6 #include <linux/bpf.h>
7 #include <linux/in.h>
8 #include <linux/in6.h>
9 #include <sys/socket.h>
10 #include <netinet/tcp.h>
11 #include <linux/if.h>
12 #include <errno.h>
13 
14 #include <bpf/bpf_helpers.h>
15 #include <bpf/bpf_endian.h>
16 
17 #define SERV4_IP		0xc0a801feU /* 192.168.1.254 */
18 #define SERV4_PORT		4040
19 #define SERV4_REWRITE_IP	0x7f000001U /* 127.0.0.1 */
20 #define SERV4_REWRITE_PORT	4444
21 
22 #ifndef IFNAMSIZ
23 #define IFNAMSIZ 16
24 #endif
25 
26 static __inline int bind_to_device(struct bpf_sock_addr *ctx)
27 {
28 	char veth1[IFNAMSIZ] = "test_sock_addr1";
29 	char veth2[IFNAMSIZ] = "test_sock_addr2";
30 	char missing[IFNAMSIZ] = "nonexistent_dev";
31 	char del_bind[IFNAMSIZ] = "";
32 	int veth1_idx, veth2_idx;
33 
34 	if (bpf_setsockopt(ctx, SOL_SOCKET, SO_BINDTODEVICE,
35 			   &veth1, sizeof(veth1)))
36 		return 1;
37 	if (bpf_getsockopt(ctx, SOL_SOCKET, SO_BINDTOIFINDEX,
38 			   &veth1_idx, sizeof(veth1_idx)) || !veth1_idx)
39 		return 1;
40 	if (bpf_setsockopt(ctx, SOL_SOCKET, SO_BINDTODEVICE,
41 			   &veth2, sizeof(veth2)))
42 		return 1;
43 	if (bpf_getsockopt(ctx, SOL_SOCKET, SO_BINDTOIFINDEX,
44 			   &veth2_idx, sizeof(veth2_idx)) || !veth2_idx ||
45 	    veth1_idx == veth2_idx)
46 		return 1;
47 	if (bpf_setsockopt(ctx, SOL_SOCKET, SO_BINDTODEVICE,
48 			   &missing, sizeof(missing)) != -ENODEV)
49 		return 1;
50 	if (bpf_setsockopt(ctx, SOL_SOCKET, SO_BINDTOIFINDEX,
51 			   &veth1_idx, sizeof(veth1_idx)))
52 		return 1;
53 	if (bpf_setsockopt(ctx, SOL_SOCKET, SO_BINDTODEVICE,
54 			   &del_bind, sizeof(del_bind)))
55 		return 1;
56 
57 	return 0;
58 }
59 
60 static __inline int bind_reuseport(struct bpf_sock_addr *ctx)
61 {
62 	int val = 1;
63 
64 	if (bpf_setsockopt(ctx, SOL_SOCKET, SO_REUSEPORT,
65 			   &val, sizeof(val)))
66 		return 1;
67 	if (bpf_getsockopt(ctx, SOL_SOCKET, SO_REUSEPORT,
68 			   &val, sizeof(val)) || !val)
69 		return 1;
70 	val = 0;
71 	if (bpf_setsockopt(ctx, SOL_SOCKET, SO_REUSEPORT,
72 			   &val, sizeof(val)))
73 		return 1;
74 	if (bpf_getsockopt(ctx, SOL_SOCKET, SO_REUSEPORT,
75 			   &val, sizeof(val)) || val)
76 		return 1;
77 
78 	return 0;
79 }
80 
81 static __inline int misc_opts(struct bpf_sock_addr *ctx, int opt)
82 {
83 	int old, tmp, new = 0xeb9f;
84 
85 	/* Socket in test case has guarantee that old never equals to new. */
86 	if (bpf_getsockopt(ctx, SOL_SOCKET, opt, &old, sizeof(old)) ||
87 	    old == new)
88 		return 1;
89 	if (bpf_setsockopt(ctx, SOL_SOCKET, opt, &new, sizeof(new)))
90 		return 1;
91 	if (bpf_getsockopt(ctx, SOL_SOCKET, opt, &tmp, sizeof(tmp)) ||
92 	    tmp != new)
93 		return 1;
94 	if (bpf_setsockopt(ctx, SOL_SOCKET, opt, &old, sizeof(old)))
95 		return 1;
96 
97 	return 0;
98 }
99 
100 SEC("cgroup/bind4")
101 int bind_v4_prog(struct bpf_sock_addr *ctx)
102 {
103 	struct bpf_sock *sk;
104 	__u32 user_ip4;
105 	__u16 user_port;
106 
107 	sk = ctx->sk;
108 	if (!sk)
109 		return 0;
110 
111 	if (sk->family != AF_INET)
112 		return 0;
113 
114 	if (ctx->type != SOCK_STREAM && ctx->type != SOCK_DGRAM)
115 		return 0;
116 
117 	if (ctx->user_ip4 != bpf_htonl(SERV4_IP) ||
118 	    ctx->user_port != bpf_htons(SERV4_PORT))
119 		return 0;
120 
121 	// u8 narrow loads:
122 	user_ip4 = 0;
123 	user_ip4 |= ((volatile __u8 *)&ctx->user_ip4)[0] << 0;
124 	user_ip4 |= ((volatile __u8 *)&ctx->user_ip4)[1] << 8;
125 	user_ip4 |= ((volatile __u8 *)&ctx->user_ip4)[2] << 16;
126 	user_ip4 |= ((volatile __u8 *)&ctx->user_ip4)[3] << 24;
127 	if (ctx->user_ip4 != user_ip4)
128 		return 0;
129 
130 	user_port = 0;
131 	user_port |= ((volatile __u8 *)&ctx->user_port)[0] << 0;
132 	user_port |= ((volatile __u8 *)&ctx->user_port)[1] << 8;
133 	if (ctx->user_port != user_port)
134 		return 0;
135 
136 	// u16 narrow loads:
137 	user_ip4 = 0;
138 	user_ip4 |= ((volatile __u16 *)&ctx->user_ip4)[0] << 0;
139 	user_ip4 |= ((volatile __u16 *)&ctx->user_ip4)[1] << 16;
140 	if (ctx->user_ip4 != user_ip4)
141 		return 0;
142 
143 	/* Bind to device and unbind it. */
144 	if (bind_to_device(ctx))
145 		return 0;
146 
147 	/* Test for misc socket options. */
148 	if (misc_opts(ctx, SO_MARK) || misc_opts(ctx, SO_PRIORITY))
149 		return 0;
150 
151 	/* Set reuseport and unset */
152 	if (bind_reuseport(ctx))
153 		return 0;
154 
155 	ctx->user_ip4 = bpf_htonl(SERV4_REWRITE_IP);
156 	ctx->user_port = bpf_htons(SERV4_REWRITE_PORT);
157 
158 	return 1;
159 }
160 
161 char _license[] SEC("license") = "GPL";
162