1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <linux/fou.h>
4 #include <net/if.h>
5 #include <netinet/in.h>
6 #include <linux/ip.h>
7
8 #include "conf-parser.h"
9 #include "fou-tunnel.h"
10 #include "ip-protocol-list.h"
11 #include "netlink-util.h"
12 #include "networkd-manager.h"
13 #include "parse-util.h"
14 #include "string-table.h"
15 #include "string-util.h"
16 #include "util.h"
17
18 static const char* const fou_encap_type_table[_NETDEV_FOO_OVER_UDP_ENCAP_MAX] = {
19 [NETDEV_FOO_OVER_UDP_ENCAP_DIRECT] = "FooOverUDP",
20 [NETDEV_FOO_OVER_UDP_ENCAP_GUE] = "GenericUDPEncapsulation",
21 };
22
23 DEFINE_STRING_TABLE_LOOKUP(fou_encap_type, FooOverUDPEncapType);
24 DEFINE_CONFIG_PARSE_ENUM(config_parse_fou_encap_type, fou_encap_type, FooOverUDPEncapType,
25 "Failed to parse Encapsulation=");
26
netdev_fill_fou_tunnel_message(NetDev * netdev,sd_netlink_message * m)27 static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message *m) {
28 FouTunnel *t;
29 uint8_t encap_type;
30 int r;
31
32 assert_se(t = FOU(netdev));
33
34 r = sd_netlink_message_append_u16(m, FOU_ATTR_PORT, htobe16(t->port));
35 if (r < 0)
36 return r;
37
38 if (IN_SET(t->peer_family, AF_INET, AF_INET6)) {
39 r = sd_netlink_message_append_u16(m, FOU_ATTR_PEER_PORT, htobe16(t->peer_port));
40 if (r < 0)
41 return r;
42 }
43
44 switch (t->fou_encap_type) {
45 case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT:
46 encap_type = FOU_ENCAP_DIRECT;
47 break;
48 case NETDEV_FOO_OVER_UDP_ENCAP_GUE:
49 encap_type = FOU_ENCAP_GUE;
50 break;
51 default:
52 assert_not_reached();
53 }
54
55 r = sd_netlink_message_append_u8(m, FOU_ATTR_TYPE, encap_type);
56 if (r < 0)
57 return r;
58
59 r = sd_netlink_message_append_u8(m, FOU_ATTR_AF, AF_INET);
60 if (r < 0)
61 return r;
62
63 r = sd_netlink_message_append_u8(m, FOU_ATTR_IPPROTO, t->fou_protocol);
64 if (r < 0)
65 return r;
66
67 if (t->local_family == AF_INET) {
68 r = sd_netlink_message_append_in_addr(m, FOU_ATTR_LOCAL_V4, &t->local.in);
69 if (r < 0)
70 return r;
71 } else if (t->local_family == AF_INET6) {
72 r = sd_netlink_message_append_in6_addr(m, FOU_ATTR_LOCAL_V6, &t->local.in6);
73 if (r < 0)
74 return r;
75 }
76
77 if (t->peer_family == AF_INET) {
78 r = sd_netlink_message_append_in_addr(m, FOU_ATTR_PEER_V4, &t->peer.in);
79 if (r < 0)
80 return r;
81 } else if (t->peer_family == AF_INET6){
82 r = sd_netlink_message_append_in6_addr(m, FOU_ATTR_PEER_V6, &t->peer.in6);
83 if (r < 0)
84 return r;
85 }
86
87 return 0;
88 }
89
netdev_create_fou_tunnel_message(NetDev * netdev,sd_netlink_message ** ret)90 static int netdev_create_fou_tunnel_message(NetDev *netdev, sd_netlink_message **ret) {
91 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
92 int r;
93
94 assert(netdev);
95
96 r = sd_genl_message_new(netdev->manager->genl, FOU_GENL_NAME, FOU_CMD_ADD, &m);
97 if (r < 0)
98 return log_netdev_error_errno(netdev, r, "Could not allocate netlink message: %m");
99
100 r = netdev_fill_fou_tunnel_message(netdev, m);
101 if (r < 0)
102 return log_netdev_error_errno(netdev, r, "Could not create netlink message: %m");
103
104 *ret = TAKE_PTR(m);
105 return 0;
106 }
107
fou_tunnel_create_handler(sd_netlink * rtnl,sd_netlink_message * m,NetDev * netdev)108 static int fou_tunnel_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
109 int r;
110
111 assert(netdev);
112 assert(netdev->state != _NETDEV_STATE_INVALID);
113
114 r = sd_netlink_message_get_errno(m);
115 if (r == -EEXIST)
116 log_netdev_info(netdev, "netdev exists, using existing without changing its parameters");
117 else if (r < 0) {
118 log_netdev_warning_errno(netdev, r, "netdev could not be created: %m");
119 netdev_enter_failed(netdev);
120
121 return 1;
122 }
123
124 log_netdev_debug(netdev, "FooOverUDP tunnel is created");
125 return 1;
126 }
127
netdev_fou_tunnel_create(NetDev * netdev)128 static int netdev_fou_tunnel_create(NetDev *netdev) {
129 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
130 int r;
131
132 assert(netdev);
133 assert(FOU(netdev));
134
135 r = netdev_create_fou_tunnel_message(netdev, &m);
136 if (r < 0)
137 return r;
138
139 r = netlink_call_async(netdev->manager->genl, NULL, m, fou_tunnel_create_handler,
140 netdev_destroy_callback, netdev);
141 if (r < 0)
142 return log_netdev_error_errno(netdev, r, "Failed to create FooOverUDP tunnel: %m");
143
144 netdev_ref(netdev);
145 return 0;
146 }
147
config_parse_ip_protocol(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)148 int config_parse_ip_protocol(
149 const char *unit,
150 const char *filename,
151 unsigned line,
152 const char *section,
153 unsigned section_line,
154 const char *lvalue,
155 int ltype,
156 const char *rvalue,
157 void *data,
158 void *userdata) {
159
160 uint8_t *ret = data;
161 unsigned protocol;
162 /* linux/fou.h defines the netlink field as one byte, so we need to reject protocols numbers that
163 * don't fit in one byte. */
164 int r;
165
166 assert(filename);
167 assert(section);
168 assert(lvalue);
169 assert(rvalue);
170 assert(data);
171
172 r = parse_ip_protocol(rvalue);
173 if (r >= 0)
174 protocol = r;
175 else {
176 r = safe_atou(rvalue, &protocol);
177 if (r < 0)
178 log_syntax(unit, LOG_WARNING, filename, line, r,
179 "Failed to parse IP protocol '%s' for FooOverUDP tunnel, "
180 "ignoring assignment: %m", rvalue);
181 return 0;
182 }
183
184 if (protocol > UINT8_MAX) {
185 log_syntax(unit, LOG_WARNING, filename, line, 0,
186 "IP protocol '%s' for FooOverUDP tunnel out of range, "
187 "ignoring assignment: %m", rvalue);
188 return 0;
189 }
190
191 *ret = protocol;
192 return 0;
193 }
194
config_parse_fou_tunnel_address(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)195 int config_parse_fou_tunnel_address(
196 const char *unit,
197 const char *filename,
198 unsigned line,
199 const char *section,
200 unsigned section_line,
201 const char *lvalue,
202 int ltype,
203 const char *rvalue,
204 void *data,
205 void *userdata) {
206
207 union in_addr_union *addr = data;
208 FouTunnel *t = userdata;
209 int r, *f;
210
211 assert(filename);
212 assert(lvalue);
213 assert(rvalue);
214 assert(data);
215
216 if (streq(lvalue, "Local"))
217 f = &t->local_family;
218 else
219 f = &t->peer_family;
220
221 r = in_addr_from_string_auto(rvalue, f, addr);
222 if (r < 0)
223 log_syntax(unit, LOG_WARNING, filename, line, r,
224 "FooOverUDP tunnel '%s' address is invalid, ignoring assignment: %s",
225 lvalue, rvalue);
226
227 return 0;
228 }
229
netdev_fou_tunnel_verify(NetDev * netdev,const char * filename)230 static int netdev_fou_tunnel_verify(NetDev *netdev, const char *filename) {
231 FouTunnel *t;
232
233 assert(netdev);
234 assert(filename);
235
236 t = FOU(netdev);
237
238 assert(t);
239
240 switch (t->fou_encap_type) {
241 case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT:
242 if (t->fou_protocol <= 0)
243 return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
244 "FooOverUDP protocol not configured in %s. Rejecting configuration.",
245 filename);
246 break;
247 case NETDEV_FOO_OVER_UDP_ENCAP_GUE:
248 if (t->fou_protocol > 0)
249 return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
250 "FooOverUDP GUE can't be set with protocol configured in %s. Rejecting configuration.",
251 filename);
252 break;
253 default:
254 assert_not_reached();
255 }
256
257 if (t->peer_family == AF_UNSPEC && t->peer_port > 0)
258 return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
259 "FooOverUDP peer port is set but peer address not configured in %s. Rejecting configuration.",
260 filename);
261 else if (t->peer_family != AF_UNSPEC && t->peer_port == 0)
262 return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
263 "FooOverUDP peer port not set but peer address is configured in %s. Rejecting configuration.",
264 filename);
265 return 0;
266 }
267
fou_tunnel_init(NetDev * netdev)268 static void fou_tunnel_init(NetDev *netdev) {
269 FouTunnel *t;
270
271 assert(netdev);
272
273 t = FOU(netdev);
274
275 assert(t);
276
277 t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT;
278 }
279
280 const NetDevVTable foutnl_vtable = {
281 .object_size = sizeof(FouTunnel),
282 .init = fou_tunnel_init,
283 .sections = NETDEV_COMMON_SECTIONS "FooOverUDP\0",
284 .create = netdev_fou_tunnel_create,
285 .create_type = NETDEV_CREATE_INDEPENDENT,
286 .config_verify = netdev_fou_tunnel_verify,
287 };
288