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