]>
Commit | Line | Data |
---|---|---|
a36de779 | 1 | /* |
f9ac0f03 | 2 | * Copyright (c) 2014, 2015, 2017 Nicira, Inc. |
a36de779 PS |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
3655f492 TLSC |
18 | |
19 | #include "tnl-ports.h" | |
20 | ||
a36de779 PS |
21 | #include <stddef.h> |
22 | #include <stdint.h> | |
4f6e5a69 | 23 | #include <string.h> |
a36de779 PS |
24 | |
25 | #include "classifier.h" | |
3e8a2ad1 | 26 | #include "openvswitch/dynamic-string.h" |
a36de779 | 27 | #include "hash.h" |
b19bab5b | 28 | #include "openvswitch/list.h" |
4f6e5a69 | 29 | #include "netdev.h" |
64c96779 | 30 | #include "openvswitch/ofpbuf.h" |
a36de779 PS |
31 | #include "ovs-thread.h" |
32 | #include "odp-util.h" | |
fccd7c09 | 33 | #include "ovs-thread.h" |
a36de779 PS |
34 | #include "unixctl.h" |
35 | #include "util.h" | |
36 | ||
fccd7c09 | 37 | static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; |
a36de779 PS |
38 | static struct classifier cls; /* Tunnel ports. */ |
39 | ||
7f9b8504 PS |
40 | struct ip_device { |
41 | struct netdev *dev; | |
42 | struct eth_addr mac; | |
a8704b50 PS |
43 | struct in6_addr *addr; |
44 | int n_addr; | |
7f9b8504 PS |
45 | uint64_t change_seq; |
46 | struct ovs_list node; | |
47 | char dev_name[IFNAMSIZ]; | |
48 | }; | |
49 | ||
50 | static struct ovs_list addr_list; | |
51 | ||
52 | struct tnl_port { | |
53 | odp_port_t port; | |
e7c9ff0e | 54 | struct ovs_refcount ref_cnt; |
98e3f58e PS |
55 | ovs_be16 tp_port; |
56 | uint8_t nw_proto; | |
7f9b8504 PS |
57 | char dev_name[IFNAMSIZ]; |
58 | struct ovs_list node; | |
59 | }; | |
60 | ||
61 | static struct ovs_list port_list; | |
62 | ||
a36de779 PS |
63 | struct tnl_port_in { |
64 | struct cls_rule cr; | |
65 | odp_port_t portno; | |
66 | struct ovs_refcount ref_cnt; | |
67 | char dev_name[IFNAMSIZ]; | |
68 | }; | |
69 | ||
70 | static struct tnl_port_in * | |
71 | tnl_port_cast(const struct cls_rule *cr) | |
72 | { | |
73 | BUILD_ASSERT_DECL(offsetof(struct tnl_port_in, cr) == 0); | |
74 | ||
75 | return CONTAINER_OF(cr, struct tnl_port_in, cr); | |
76 | } | |
77 | ||
78 | static void | |
79 | tnl_port_free(struct tnl_port_in *p) | |
80 | { | |
81 | cls_rule_destroy(&p->cr); | |
82 | free(p); | |
83 | } | |
84 | ||
85 | static void | |
7f9b8504 | 86 | tnl_port_init_flow(struct flow *flow, struct eth_addr mac, |
98e3f58e | 87 | struct in6_addr *addr, uint8_t nw_proto, ovs_be16 tp_port) |
a36de779 PS |
88 | { |
89 | memset(flow, 0, sizeof *flow); | |
7f9b8504 | 90 | |
7f9b8504 | 91 | flow->dl_dst = mac; |
293a104b TLSC |
92 | if (IN6_IS_ADDR_V4MAPPED(addr)) { |
93 | flow->dl_type = htons(ETH_TYPE_IP); | |
94 | flow->nw_dst = in6_addr_get_mapped_ipv4(addr); | |
95 | } else { | |
96 | flow->dl_type = htons(ETH_TYPE_IPV6); | |
97 | flow->ipv6_dst = *addr; | |
98 | } | |
7f9b8504 | 99 | |
98e3f58e PS |
100 | flow->nw_proto = nw_proto; |
101 | flow->tp_dst = tp_port; | |
a36de779 PS |
102 | } |
103 | ||
7f9b8504 | 104 | static void |
293a104b | 105 | map_insert(odp_port_t port, struct eth_addr mac, struct in6_addr *addr, |
98e3f58e | 106 | uint8_t nw_proto, ovs_be16 tp_port, const char dev_name[]) |
a36de779 PS |
107 | { |
108 | const struct cls_rule *cr; | |
109 | struct tnl_port_in *p; | |
110 | struct match match; | |
111 | ||
112 | memset(&match, 0, sizeof match); | |
98e3f58e | 113 | tnl_port_init_flow(&match.flow, mac, addr, nw_proto, tp_port); |
a36de779 PS |
114 | |
115 | do { | |
44e0c35d | 116 | cr = classifier_lookup(&cls, OVS_VERSION_MAX, &match.flow, NULL); |
a36de779 PS |
117 | p = tnl_port_cast(cr); |
118 | /* Try again if the rule was released before we get the reference. */ | |
119 | } while (p && !ovs_refcount_try_ref_rcu(&p->ref_cnt)); | |
120 | ||
fccd7c09 JR |
121 | if (!p) { |
122 | p = xzalloc(sizeof *p); | |
123 | p->portno = port; | |
a36de779 | 124 | |
fccd7c09 JR |
125 | match.wc.masks.dl_type = OVS_BE16_MAX; |
126 | match.wc.masks.nw_proto = 0xff; | |
5be5370d DDP |
127 | /* XXX: No fragments support. */ |
128 | match.wc.masks.nw_frag = FLOW_NW_FRAG_MASK; | |
129 | ||
98e3f58e | 130 | /* 'tp_port' is zero for GRE tunnels. In this case it |
5be5370d | 131 | * doesn't make sense to match on UDP port numbers. */ |
98e3f58e | 132 | if (tp_port) { |
5be5370d DDP |
133 | match.wc.masks.tp_dst = OVS_BE16_MAX; |
134 | } | |
293a104b TLSC |
135 | if (IN6_IS_ADDR_V4MAPPED(addr)) { |
136 | match.wc.masks.nw_dst = OVS_BE32_MAX; | |
137 | } else { | |
138 | match.wc.masks.ipv6_dst = in6addr_exact; | |
139 | } | |
f0fb825a | 140 | match.wc.masks.vlans[0].tci = OVS_BE16_MAX; |
7f9b8504 | 141 | memset(&match.wc.masks.dl_dst, 0xff, sizeof (struct eth_addr)); |
a36de779 | 142 | |
bd53aa17 | 143 | cls_rule_init(&p->cr, &match, 0); /* Priority == 0. */ |
fccd7c09 | 144 | ovs_refcount_init(&p->ref_cnt); |
8742957c | 145 | ovs_strlcpy(p->dev_name, dev_name, sizeof p->dev_name); |
a36de779 | 146 | |
44e0c35d | 147 | classifier_insert(&cls, &p->cr, OVS_VERSION_MIN, NULL, 0); |
fccd7c09 | 148 | } |
7f9b8504 PS |
149 | } |
150 | ||
a8704b50 PS |
151 | static void |
152 | map_insert_ipdev__(struct ip_device *ip_dev, char dev_name[], | |
98e3f58e | 153 | odp_port_t port, uint8_t nw_proto, ovs_be16 tp_port) |
a8704b50 PS |
154 | { |
155 | if (ip_dev->n_addr) { | |
156 | int i; | |
157 | ||
158 | for (i = 0; i < ip_dev->n_addr; i++) { | |
159 | map_insert(port, ip_dev->mac, &ip_dev->addr[i], | |
98e3f58e | 160 | nw_proto, tp_port, dev_name); |
a8704b50 PS |
161 | } |
162 | } | |
163 | } | |
164 | ||
98e3f58e PS |
165 | static uint8_t |
166 | tnl_type_to_nw_proto(const char type[]) | |
167 | { | |
168 | if (!strcmp(type, "geneve")) { | |
169 | return IPPROTO_UDP; | |
170 | } | |
171 | if (!strcmp(type, "stt")) { | |
172 | return IPPROTO_TCP; | |
173 | } | |
174 | if (!strcmp(type, "gre")) { | |
175 | return IPPROTO_GRE; | |
176 | } | |
177 | if (!strcmp(type, "vxlan")) { | |
178 | return IPPROTO_UDP; | |
179 | } | |
180 | return 0; | |
181 | } | |
182 | ||
7f9b8504 | 183 | void |
98e3f58e PS |
184 | tnl_port_map_insert(odp_port_t port, ovs_be16 tp_port, |
185 | const char dev_name[], const char type[]) | |
7f9b8504 PS |
186 | { |
187 | struct tnl_port *p; | |
188 | struct ip_device *ip_dev; | |
98e3f58e PS |
189 | uint8_t nw_proto; |
190 | ||
191 | nw_proto = tnl_type_to_nw_proto(type); | |
192 | if (!nw_proto) { | |
193 | return; | |
194 | } | |
7f9b8504 PS |
195 | |
196 | ovs_mutex_lock(&mutex); | |
197 | LIST_FOR_EACH(p, node, &port_list) { | |
98e3f58e | 198 | if (tp_port == p->tp_port && p->nw_proto == nw_proto) { |
e7c9ff0e | 199 | ovs_refcount_ref(&p->ref_cnt); |
200 | goto out; | |
7f9b8504 PS |
201 | } |
202 | } | |
203 | ||
204 | p = xzalloc(sizeof *p); | |
205 | p->port = port; | |
98e3f58e PS |
206 | p->tp_port = tp_port; |
207 | p->nw_proto = nw_proto; | |
7f9b8504 | 208 | ovs_strlcpy(p->dev_name, dev_name, sizeof p->dev_name); |
e7c9ff0e | 209 | ovs_refcount_init(&p->ref_cnt); |
417e7e66 | 210 | ovs_list_insert(&port_list, &p->node); |
7f9b8504 PS |
211 | |
212 | LIST_FOR_EACH(ip_dev, node, &addr_list) { | |
98e3f58e | 213 | map_insert_ipdev__(ip_dev, p->dev_name, p->port, p->nw_proto, p->tp_port); |
7f9b8504 PS |
214 | } |
215 | ||
216 | out: | |
fccd7c09 | 217 | ovs_mutex_unlock(&mutex); |
a36de779 PS |
218 | } |
219 | ||
220 | static void | |
221 | tnl_port_unref(const struct cls_rule *cr) | |
222 | { | |
223 | struct tnl_port_in *p = tnl_port_cast(cr); | |
224 | ||
225 | if (cr && ovs_refcount_unref_relaxed(&p->ref_cnt) == 1) { | |
226 | if (classifier_remove(&cls, cr)) { | |
227 | ovsrcu_postpone(tnl_port_free, p); | |
228 | } | |
229 | } | |
230 | } | |
231 | ||
7f9b8504 | 232 | static void |
98e3f58e PS |
233 | map_delete(struct eth_addr mac, struct in6_addr *addr, |
234 | ovs_be16 tp_port, uint8_t nw_proto) | |
a36de779 PS |
235 | { |
236 | const struct cls_rule *cr; | |
237 | struct flow flow; | |
238 | ||
98e3f58e | 239 | tnl_port_init_flow(&flow, mac, addr, nw_proto, tp_port); |
a36de779 | 240 | |
44e0c35d | 241 | cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL); |
a36de779 PS |
242 | tnl_port_unref(cr); |
243 | } | |
244 | ||
a8704b50 | 245 | static void |
98e3f58e | 246 | ipdev_map_delete(struct ip_device *ip_dev, ovs_be16 tp_port, uint8_t nw_proto) |
a8704b50 PS |
247 | { |
248 | if (ip_dev->n_addr) { | |
249 | int i; | |
250 | ||
251 | for (i = 0; i < ip_dev->n_addr; i++) { | |
98e3f58e | 252 | map_delete(ip_dev->mac, &ip_dev->addr[i], tp_port, nw_proto); |
a8704b50 PS |
253 | } |
254 | } | |
255 | } | |
256 | ||
7f9b8504 | 257 | void |
98e3f58e | 258 | tnl_port_map_delete(ovs_be16 tp_port, const char type[]) |
7f9b8504 PS |
259 | { |
260 | struct tnl_port *p, *next; | |
261 | struct ip_device *ip_dev; | |
98e3f58e PS |
262 | uint8_t nw_proto; |
263 | ||
264 | nw_proto = tnl_type_to_nw_proto(type); | |
7f9b8504 PS |
265 | |
266 | ovs_mutex_lock(&mutex); | |
267 | LIST_FOR_EACH_SAFE(p, next, node, &port_list) { | |
e7c9ff0e | 268 | if (p->tp_port == tp_port && p->nw_proto == nw_proto && |
269 | ovs_refcount_unref_relaxed(&p->ref_cnt) == 1) { | |
417e7e66 | 270 | ovs_list_remove(&p->node); |
e7c9ff0e | 271 | LIST_FOR_EACH(ip_dev, node, &addr_list) { |
272 | ipdev_map_delete(ip_dev, p->tp_port, p->nw_proto); | |
273 | } | |
274 | free(p); | |
7f9b8504 PS |
275 | break; |
276 | } | |
277 | } | |
7f9b8504 PS |
278 | ovs_mutex_unlock(&mutex); |
279 | } | |
280 | ||
2e0bded4 BP |
281 | /* 'flow' is non-const to allow for temporary modifications during the lookup. |
282 | * Any changes are restored before returning. */ | |
a36de779 | 283 | odp_port_t |
2e0bded4 | 284 | tnl_port_map_lookup(struct flow *flow, struct flow_wildcards *wc) |
a36de779 | 285 | { |
44e0c35d | 286 | const struct cls_rule *cr = classifier_lookup(&cls, OVS_VERSION_MAX, flow, |
2b7b1427 | 287 | wc); |
a36de779 PS |
288 | |
289 | return (cr) ? tnl_port_cast(cr)->portno : ODPP_NONE; | |
290 | } | |
291 | ||
292 | static void | |
7f9b8504 | 293 | tnl_port_show_v(struct ds *ds) |
a36de779 | 294 | { |
a36de779 PS |
295 | const struct tnl_port_in *p; |
296 | ||
a36de779 PS |
297 | CLS_FOR_EACH(p, cr, &cls) { |
298 | struct odputil_keybuf keybuf; | |
299 | struct odputil_keybuf maskbuf; | |
300 | struct flow flow; | |
301 | const struct nlattr *key, *mask; | |
302 | size_t key_len, mask_len; | |
303 | struct flow_wildcards wc; | |
304 | struct ofpbuf buf; | |
5262eea1 JG |
305 | struct odp_flow_key_parms odp_parms = { |
306 | .flow = &flow, | |
307 | .mask = &wc.masks, | |
308 | }; | |
a36de779 | 309 | |
7f9b8504 | 310 | ds_put_format(ds, "%s (%"PRIu32") : ", p->dev_name, p->portno); |
8fd47924 JR |
311 | minimask_expand(p->cr.match.mask, &wc); |
312 | miniflow_expand(p->cr.match.flow, &flow); | |
a36de779 PS |
313 | |
314 | /* Key. */ | |
2494ccd7 | 315 | odp_parms.support.recirc = true; |
a36de779 | 316 | ofpbuf_use_stack(&buf, &keybuf, sizeof keybuf); |
5262eea1 | 317 | odp_flow_key_from_flow(&odp_parms, &buf); |
6fd6ed71 PS |
318 | key = buf.data; |
319 | key_len = buf.size; | |
5262eea1 | 320 | |
a36de779 | 321 | /* mask*/ |
2494ccd7 | 322 | odp_parms.support.recirc = false; |
a36de779 | 323 | ofpbuf_use_stack(&buf, &maskbuf, sizeof maskbuf); |
5262eea1 | 324 | odp_flow_key_from_mask(&odp_parms, &buf); |
6fd6ed71 PS |
325 | mask = buf.data; |
326 | mask_len = buf.size; | |
a36de779 PS |
327 | |
328 | /* build string. */ | |
7f9b8504 PS |
329 | odp_flow_format(key, key_len, mask, mask_len, NULL, ds, false); |
330 | ds_put_format(ds, "\n"); | |
331 | } | |
332 | } | |
333 | ||
334 | static void | |
335 | tnl_port_show(struct unixctl_conn *conn, int argc OVS_UNUSED, | |
336 | const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) | |
337 | { | |
338 | struct ds ds = DS_EMPTY_INITIALIZER; | |
339 | struct tnl_port *p; | |
340 | ||
341 | ds_put_format(&ds, "Listening ports:\n"); | |
342 | ovs_mutex_lock(&mutex); | |
343 | if (argc > 1) { | |
344 | if (!strcasecmp(argv[1], "-v")) { | |
345 | tnl_port_show_v(&ds); | |
346 | goto out; | |
347 | } | |
348 | } | |
349 | ||
350 | LIST_FOR_EACH(p, node, &port_list) { | |
351 | ds_put_format(&ds, "%s (%"PRIu32")\n", p->dev_name, p->port); | |
a36de779 | 352 | } |
7f9b8504 PS |
353 | |
354 | out: | |
355 | ovs_mutex_unlock(&mutex); | |
a36de779 PS |
356 | unixctl_command_reply(conn, ds_cstr(&ds)); |
357 | ds_destroy(&ds); | |
358 | } | |
359 | ||
7f9b8504 PS |
360 | static void |
361 | map_insert_ipdev(struct ip_device *ip_dev) | |
362 | { | |
363 | struct tnl_port *p; | |
364 | ||
365 | LIST_FOR_EACH(p, node, &port_list) { | |
98e3f58e | 366 | map_insert_ipdev__(ip_dev, p->dev_name, p->port, p->nw_proto, p->tp_port); |
7f9b8504 PS |
367 | } |
368 | } | |
369 | ||
370 | static void | |
a8704b50 PS |
371 | insert_ipdev__(struct netdev *dev, |
372 | struct in6_addr *addr, int n_addr) | |
7f9b8504 PS |
373 | { |
374 | struct ip_device *ip_dev; | |
375 | enum netdev_flags flags; | |
7f9b8504 | 376 | int error; |
7f9b8504 PS |
377 | |
378 | error = netdev_get_flags(dev, &flags); | |
379 | if (error || (flags & NETDEV_LOOPBACK)) { | |
a8704b50 | 380 | goto err; |
7f9b8504 PS |
381 | } |
382 | ||
383 | ip_dev = xzalloc(sizeof *ip_dev); | |
a8704b50 | 384 | ip_dev->dev = netdev_ref(dev); |
7f9b8504 PS |
385 | ip_dev->change_seq = netdev_get_change_seq(dev); |
386 | error = netdev_get_etheraddr(ip_dev->dev, &ip_dev->mac); | |
387 | if (error) { | |
a8704b50 | 388 | goto err_free_ipdev; |
7f9b8504 | 389 | } |
a8704b50 PS |
390 | ip_dev->addr = addr; |
391 | ip_dev->n_addr = n_addr; | |
7f9b8504 | 392 | ovs_strlcpy(ip_dev->dev_name, netdev_get_name(dev), sizeof ip_dev->dev_name); |
417e7e66 | 393 | ovs_list_insert(&addr_list, &ip_dev->node); |
7f9b8504 | 394 | map_insert_ipdev(ip_dev); |
a8704b50 PS |
395 | return; |
396 | ||
397 | err_free_ipdev: | |
398 | netdev_close(ip_dev->dev); | |
399 | free(ip_dev); | |
400 | err: | |
401 | free(addr); | |
402 | } | |
403 | ||
404 | static void | |
405 | insert_ipdev(const char dev_name[]) | |
406 | { | |
407 | struct in6_addr *addr, *mask; | |
408 | struct netdev *dev; | |
409 | int error, n_in6; | |
410 | ||
411 | error = netdev_open(dev_name, NULL, &dev); | |
412 | if (error) { | |
413 | return; | |
414 | } | |
415 | ||
416 | error = netdev_get_addr_list(dev, &addr, &mask, &n_in6); | |
417 | if (error) { | |
418 | netdev_close(dev); | |
419 | return; | |
420 | } | |
421 | free(mask); | |
422 | insert_ipdev__(dev, addr, n_in6); | |
423 | netdev_close(dev); | |
7f9b8504 PS |
424 | } |
425 | ||
426 | static void | |
427 | delete_ipdev(struct ip_device *ip_dev) | |
428 | { | |
429 | struct tnl_port *p; | |
430 | ||
431 | LIST_FOR_EACH(p, node, &port_list) { | |
98e3f58e | 432 | ipdev_map_delete(ip_dev, p->tp_port, p->nw_proto); |
7f9b8504 PS |
433 | } |
434 | ||
417e7e66 | 435 | ovs_list_remove(&ip_dev->node); |
7f9b8504 | 436 | netdev_close(ip_dev->dev); |
a8704b50 | 437 | free(ip_dev->addr); |
7f9b8504 PS |
438 | free(ip_dev); |
439 | } | |
440 | ||
441 | void | |
442 | tnl_port_map_insert_ipdev(const char dev_name[]) | |
443 | { | |
c465f75f | 444 | struct ip_device *ip_dev, *next; |
7f9b8504 PS |
445 | |
446 | ovs_mutex_lock(&mutex); | |
447 | ||
c465f75f | 448 | LIST_FOR_EACH_SAFE(ip_dev, next, node, &addr_list) { |
7f9b8504 PS |
449 | if (!strcmp(netdev_get_name(ip_dev->dev), dev_name)) { |
450 | if (ip_dev->change_seq == netdev_get_change_seq(ip_dev->dev)) { | |
451 | goto out; | |
452 | } | |
453 | /* Address changed. */ | |
454 | delete_ipdev(ip_dev); | |
7f9b8504 PS |
455 | } |
456 | } | |
457 | insert_ipdev(dev_name); | |
458 | ||
459 | out: | |
460 | ovs_mutex_unlock(&mutex); | |
461 | } | |
462 | ||
463 | void | |
464 | tnl_port_map_delete_ipdev(const char dev_name[]) | |
465 | { | |
466 | struct ip_device *ip_dev, *next; | |
467 | ||
468 | ovs_mutex_lock(&mutex); | |
469 | LIST_FOR_EACH_SAFE(ip_dev, next, node, &addr_list) { | |
470 | if (!strcmp(netdev_get_name(ip_dev->dev), dev_name)) { | |
471 | delete_ipdev(ip_dev); | |
472 | } | |
473 | } | |
474 | ovs_mutex_unlock(&mutex); | |
475 | } | |
476 | ||
477 | void | |
478 | tnl_port_map_run(void) | |
479 | { | |
c465f75f | 480 | struct ip_device *ip_dev, *next; |
7f9b8504 PS |
481 | |
482 | ovs_mutex_lock(&mutex); | |
c465f75f | 483 | LIST_FOR_EACH_SAFE(ip_dev, next, node, &addr_list) { |
7f9b8504 PS |
484 | char dev_name[IFNAMSIZ]; |
485 | ||
486 | if (ip_dev->change_seq == netdev_get_change_seq(ip_dev->dev)) { | |
487 | continue; | |
488 | } | |
489 | ||
490 | /* Address changed. */ | |
f9ac0f03 | 491 | ovs_strlcpy_arrays(dev_name, ip_dev->dev_name); |
7f9b8504 PS |
492 | delete_ipdev(ip_dev); |
493 | insert_ipdev(dev_name); | |
494 | } | |
495 | ovs_mutex_unlock(&mutex); | |
496 | } | |
497 | ||
a36de779 PS |
498 | void |
499 | tnl_port_map_init(void) | |
500 | { | |
d70e8c28 | 501 | classifier_init(&cls, flow_segment_u64s); |
417e7e66 BW |
502 | ovs_list_init(&addr_list); |
503 | ovs_list_init(&port_list); | |
7f9b8504 | 504 | unixctl_command_register("tnl/ports/show", "-v", 0, 1, tnl_port_show, NULL); |
a36de779 | 505 | } |