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