]>
Commit | Line | Data |
---|---|---|
777ece09 | 1 | /* |
9284baa2 | 2 | * Copyright (c) 2010, 2011, 2012, 2013 Nicira, Inc. |
777ece09 JG |
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 | * | |
6fcfff1b | 10 | * Unless required by applicable law or agreed to in writing, software |
777ece09 JG |
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> | |
2b9d6589 BP |
18 | |
19 | #include "netdev-vport.h" | |
20 | ||
777ece09 JG |
21 | #include <errno.h> |
22 | #include <fcntl.h> | |
ea83a2fc | 23 | #include <sys/socket.h> |
077257b8 | 24 | #include <linux/openvswitch.h> |
ea83a2fc | 25 | #include <linux/rtnetlink.h> |
2b9d6589 | 26 | #include <net/if.h> |
777ece09 JG |
27 | #include <sys/ioctl.h> |
28 | ||
b9298d3f | 29 | #include "byte-order.h" |
5059eff3 JP |
30 | #include "daemon.h" |
31 | #include "dirs.h" | |
c19e6535 | 32 | #include "dpif-linux.h" |
ea83a2fc EJ |
33 | #include "hash.h" |
34 | #include "hmap.h" | |
777ece09 | 35 | #include "list.h" |
d3980822 | 36 | #include "netdev-linux.h" |
2b9d6589 | 37 | #include "netdev-provider.h" |
ea83a2fc | 38 | #include "netlink.h" |
45c8d3a1 | 39 | #include "netlink-notifier.h" |
ea83a2fc EJ |
40 | #include "netlink-socket.h" |
41 | #include "ofpbuf.h" | |
2b9d6589 BP |
42 | #include "openvswitch/tunnel.h" |
43 | #include "packets.h" | |
a132aa96 | 44 | #include "route-table.h" |
777ece09 JG |
45 | #include "shash.h" |
46 | #include "socket-util.h" | |
69ebca1e | 47 | #include "unaligned.h" |
777ece09 JG |
48 | #include "vlog.h" |
49 | ||
d98e6007 | 50 | VLOG_DEFINE_THIS_MODULE(netdev_vport); |
5136ce49 | 51 | |
79f827fa KM |
52 | /* Default to the OTV port, per the VXLAN IETF draft. */ |
53 | #define VXLAN_DST_PORT 8472 | |
54 | ||
f431bf7d EJ |
55 | #define DEFAULT_TTL 64 |
56 | ||
2b9d6589 BP |
57 | struct netdev_dev_vport { |
58 | struct netdev_dev netdev_dev; | |
c19e6535 | 59 | struct ofpbuf *options; |
ac4d3bcb | 60 | unsigned int change_seq; |
35b769cb | 61 | uint8_t etheraddr[ETH_ADDR_LEN]; |
f431bf7d | 62 | struct netdev_tunnel_config tnl_cfg; |
2b9d6589 BP |
63 | }; |
64 | ||
2b9d6589 | 65 | struct vport_class { |
df2c07f4 | 66 | enum ovs_vport_type type; |
c3827f61 | 67 | struct netdev_class netdev_class; |
6d9e6eb4 | 68 | int (*parse_config)(const char *name, const char *type, |
f431bf7d EJ |
69 | const struct smap *args, struct ofpbuf *options, |
70 | struct netdev_tunnel_config *tnl_cfg); | |
6d9e6eb4 | 71 | int (*unparse_config)(const char *name, const char *type, |
c19e6535 | 72 | const struct nlattr *options, size_t options_len, |
79f1cbe9 | 73 | struct smap *args); |
2b9d6589 BP |
74 | }; |
75 | ||
777ece09 JG |
76 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); |
77 | ||
2b9d6589 | 78 | static int netdev_vport_create(const struct netdev_class *, const char *, |
de5cdb90 | 79 | struct netdev_dev **); |
2b9d6589 | 80 | static void netdev_vport_poll_notify(const struct netdev *); |
c19e6535 BP |
81 | static int tnl_port_config_from_nlattr(const struct nlattr *options, |
82 | size_t options_len, | |
df2c07f4 | 83 | struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]); |
2b9d6589 BP |
84 | |
85 | static bool | |
86 | is_vport_class(const struct netdev_class *class) | |
777ece09 | 87 | { |
2b9d6589 BP |
88 | return class->create == netdev_vport_create; |
89 | } | |
777ece09 | 90 | |
2b9d6589 BP |
91 | static const struct vport_class * |
92 | vport_class_cast(const struct netdev_class *class) | |
93 | { | |
94 | assert(is_vport_class(class)); | |
95 | return CONTAINER_OF(class, struct vport_class, netdev_class); | |
96 | } | |
97 | ||
98 | static struct netdev_dev_vport * | |
99 | netdev_dev_vport_cast(const struct netdev_dev *netdev_dev) | |
100 | { | |
101 | assert(is_vport_class(netdev_dev_get_class(netdev_dev))); | |
102 | return CONTAINER_OF(netdev_dev, struct netdev_dev_vport, netdev_dev); | |
103 | } | |
104 | ||
df67d7ae EJ |
105 | static struct netdev_dev_vport * |
106 | netdev_vport_get_dev(const struct netdev *netdev) | |
107 | { | |
108 | return netdev_dev_vport_cast(netdev_get_dev(netdev)); | |
109 | } | |
110 | ||
f431bf7d EJ |
111 | static const struct netdev_tunnel_config * |
112 | get_netdev_tunnel_config(const struct netdev_dev *netdev_dev) | |
113 | { | |
114 | return &netdev_dev_vport_cast(netdev_dev)->tnl_cfg; | |
115 | } | |
116 | ||
c19e6535 | 117 | /* If 'netdev' is a vport netdev, returns an ofpbuf that contains Netlink |
df2c07f4 | 118 | * options to include in OVS_VPORT_ATTR_OPTIONS for configuring that vport. |
c19e6535 BP |
119 | * Otherwise returns NULL. */ |
120 | const struct ofpbuf * | |
121 | netdev_vport_get_options(const struct netdev *netdev) | |
122 | { | |
123 | const struct netdev_dev *dev = netdev_get_dev(netdev); | |
124 | ||
125 | return (is_vport_class(netdev_dev_get_class(dev)) | |
126 | ? netdev_dev_vport_cast(dev)->options | |
127 | : NULL); | |
128 | } | |
129 | ||
df2c07f4 | 130 | enum ovs_vport_type |
c19e6535 | 131 | netdev_vport_get_vport_type(const struct netdev *netdev) |
2b9d6589 | 132 | { |
c3827f61 | 133 | const struct netdev_dev *dev = netdev_get_dev(netdev); |
c19e6535 BP |
134 | const struct netdev_class *class = netdev_dev_get_class(dev); |
135 | ||
136 | return (is_vport_class(class) ? vport_class_cast(class)->type | |
df2c07f4 | 137 | : class == &netdev_internal_class ? OVS_VPORT_TYPE_INTERNAL |
52fa1bcf BP |
138 | : (class == &netdev_linux_class || |
139 | class == &netdev_tap_class) ? OVS_VPORT_TYPE_NETDEV | |
df2c07f4 | 140 | : OVS_VPORT_TYPE_UNSPEC); |
c19e6535 BP |
141 | } |
142 | ||
7f804ea5 JR |
143 | static uint32_t |
144 | get_u32_or_zero(const struct nlattr *a) | |
145 | { | |
146 | return a ? nl_attr_get_u32(a) : 0; | |
147 | } | |
148 | ||
c19e6535 BP |
149 | const char * |
150 | netdev_vport_get_netdev_type(const struct dpif_linux_vport *vport) | |
151 | { | |
df2c07f4 | 152 | struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]; |
c19e6535 BP |
153 | |
154 | switch (vport->type) { | |
df2c07f4 | 155 | case OVS_VPORT_TYPE_UNSPEC: |
c19e6535 BP |
156 | break; |
157 | ||
df2c07f4 | 158 | case OVS_VPORT_TYPE_NETDEV: |
c19e6535 BP |
159 | return "system"; |
160 | ||
df2c07f4 | 161 | case OVS_VPORT_TYPE_INTERNAL: |
c19e6535 | 162 | return "internal"; |
c3827f61 | 163 | |
df2c07f4 | 164 | case OVS_VPORT_TYPE_PATCH: |
c19e6535 BP |
165 | return "patch"; |
166 | ||
df2c07f4 | 167 | case OVS_VPORT_TYPE_GRE: |
c19e6535 BP |
168 | if (tnl_port_config_from_nlattr(vport->options, vport->options_len, |
169 | a)) { | |
170 | break; | |
171 | } | |
7f804ea5 | 172 | return (get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_IPSEC |
c19e6535 BP |
173 | ? "ipsec_gre" : "gre"); |
174 | ||
2de795ad PS |
175 | case OVS_VPORT_TYPE_GRE64: |
176 | if (tnl_port_config_from_nlattr(vport->options, vport->options_len, | |
177 | a)) { | |
178 | break; | |
179 | } | |
7f804ea5 | 180 | return (get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_IPSEC |
2de795ad PS |
181 | ? "ipsec_gre64" : "gre64"); |
182 | ||
df2c07f4 | 183 | case OVS_VPORT_TYPE_CAPWAP: |
c19e6535 BP |
184 | return "capwap"; |
185 | ||
79f827fa | 186 | case OVS_VPORT_TYPE_VXLAN: |
523284fc | 187 | return "vxlan"; |
79f827fa | 188 | |
3c82d068 | 189 | case OVS_VPORT_TYPE_FT_GRE: |
df2c07f4 | 190 | case __OVS_VPORT_TYPE_MAX: |
c19e6535 | 191 | break; |
777ece09 | 192 | } |
c19e6535 BP |
193 | |
194 | VLOG_WARN_RL(&rl, "dp%d: port `%s' has unsupported type %u", | |
254f2dc8 | 195 | vport->dp_ifindex, vport->name, (unsigned int) vport->type); |
c19e6535 | 196 | return "unknown"; |
2b9d6589 | 197 | } |
777ece09 | 198 | |
2b9d6589 | 199 | static int |
c3827f61 | 200 | netdev_vport_create(const struct netdev_class *netdev_class, const char *name, |
c3827f61 | 201 | struct netdev_dev **netdev_devp) |
2b9d6589 | 202 | { |
de5cdb90 | 203 | struct netdev_dev_vport *dev; |
6d9e6eb4 | 204 | |
f431bf7d | 205 | dev = xzalloc(sizeof *dev); |
de5cdb90 | 206 | netdev_dev_init(&dev->netdev_dev, name, netdev_class); |
de5cdb90 | 207 | dev->change_seq = 1; |
35b769cb | 208 | eth_addr_random(dev->etheraddr); |
6d9e6eb4 | 209 | |
de5cdb90 BP |
210 | *netdev_devp = &dev->netdev_dev; |
211 | route_table_register(); | |
6d9e6eb4 | 212 | |
de5cdb90 | 213 | return 0; |
777ece09 JG |
214 | } |
215 | ||
2b9d6589 BP |
216 | static void |
217 | netdev_vport_destroy(struct netdev_dev *netdev_dev_) | |
218 | { | |
219 | struct netdev_dev_vport *netdev_dev = netdev_dev_vport_cast(netdev_dev_); | |
220 | ||
896b3272 | 221 | ofpbuf_delete(netdev_dev->options); |
a132aa96 | 222 | route_table_unregister(); |
2b9d6589 BP |
223 | free(netdev_dev); |
224 | } | |
225 | ||
226 | static int | |
2f8999f5 | 227 | netdev_vport_open(struct netdev_dev *netdev_dev, struct netdev **netdevp) |
2b9d6589 | 228 | { |
2f8999f5 EJ |
229 | *netdevp = xmalloc(sizeof **netdevp); |
230 | netdev_init(*netdevp, netdev_dev); | |
2b9d6589 BP |
231 | return 0; |
232 | } | |
233 | ||
234 | static void | |
2f8999f5 | 235 | netdev_vport_close(struct netdev *netdev) |
2b9d6589 | 236 | { |
2b9d6589 BP |
237 | free(netdev); |
238 | } | |
239 | ||
de5cdb90 | 240 | static int |
79f1cbe9 | 241 | netdev_vport_get_config(struct netdev_dev *dev_, struct smap *args) |
de5cdb90 BP |
242 | { |
243 | const struct netdev_class *netdev_class = netdev_dev_get_class(dev_); | |
244 | const struct vport_class *vport_class = vport_class_cast(netdev_class); | |
245 | struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_); | |
246 | const char *name = netdev_dev_get_name(dev_); | |
247 | int error; | |
248 | ||
249 | if (!dev->options) { | |
250 | struct dpif_linux_vport reply; | |
251 | struct ofpbuf *buf; | |
252 | ||
253 | error = dpif_linux_vport_get(name, &reply, &buf); | |
254 | if (error) { | |
255 | VLOG_ERR_RL(&rl, "%s: vport query failed (%s)", | |
256 | name, strerror(error)); | |
257 | return error; | |
258 | } | |
259 | ||
260 | dev->options = ofpbuf_clone_data(reply.options, reply.options_len); | |
de5cdb90 BP |
261 | ofpbuf_delete(buf); |
262 | } | |
263 | ||
264 | error = vport_class->unparse_config(name, netdev_class->type, | |
265 | dev->options->data, | |
266 | dev->options->size, | |
267 | args); | |
268 | if (error) { | |
269 | VLOG_ERR_RL(&rl, "%s: failed to parse kernel config (%s)", | |
270 | name, strerror(error)); | |
271 | } | |
272 | return error; | |
273 | } | |
274 | ||
2b9d6589 | 275 | static int |
79f1cbe9 | 276 | netdev_vport_set_config(struct netdev_dev *dev_, const struct smap *args) |
2b9d6589 | 277 | { |
c3827f61 BP |
278 | const struct netdev_class *netdev_class = netdev_dev_get_class(dev_); |
279 | const struct vport_class *vport_class = vport_class_cast(netdev_class); | |
280 | struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_); | |
c19e6535 | 281 | const char *name = netdev_dev_get_name(dev_); |
f431bf7d | 282 | struct netdev_tunnel_config tnl_cfg; |
c19e6535 | 283 | struct ofpbuf *options; |
c3827f61 BP |
284 | int error; |
285 | ||
c19e6535 BP |
286 | options = ofpbuf_new(64); |
287 | error = vport_class->parse_config(name, netdev_dev_get_type(dev_), | |
f431bf7d | 288 | args, options, &tnl_cfg); |
c19e6535 | 289 | if (!error |
de5cdb90 BP |
290 | && (!dev->options |
291 | || options->size != dev->options->size | |
c19e6535 BP |
292 | || memcmp(options->data, dev->options->data, options->size))) { |
293 | struct dpif_linux_vport vport; | |
294 | ||
295 | dpif_linux_vport_init(&vport); | |
df2c07f4 | 296 | vport.cmd = OVS_VPORT_CMD_SET; |
c19e6535 BP |
297 | vport.name = name; |
298 | vport.options = options->data; | |
299 | vport.options_len = options->size; | |
300 | error = dpif_linux_vport_transact(&vport, NULL, NULL); | |
c3827f61 BP |
301 | if (!error || error == ENODEV) { |
302 | /* Either reconfiguration succeeded or this vport is not installed | |
303 | * in the kernel (e.g. it hasn't been added to a dpif yet with | |
304 | * dpif_port_add()). */ | |
c19e6535 BP |
305 | ofpbuf_delete(dev->options); |
306 | dev->options = options; | |
f431bf7d | 307 | dev->tnl_cfg = tnl_cfg; |
c19e6535 BP |
308 | options = NULL; |
309 | error = 0; | |
c3827f61 | 310 | } |
2b9d6589 | 311 | } |
c19e6535 BP |
312 | ofpbuf_delete(options); |
313 | ||
c3827f61 | 314 | return error; |
2b9d6589 BP |
315 | } |
316 | ||
317 | static int | |
777ece09 JG |
318 | netdev_vport_set_etheraddr(struct netdev *netdev, |
319 | const uint8_t mac[ETH_ADDR_LEN]) | |
320 | { | |
35b769cb EJ |
321 | memcpy(netdev_vport_get_dev(netdev)->etheraddr, mac, ETH_ADDR_LEN); |
322 | netdev_vport_poll_notify(netdev); | |
323 | return 0; | |
777ece09 JG |
324 | } |
325 | ||
2b9d6589 | 326 | static int |
777ece09 JG |
327 | netdev_vport_get_etheraddr(const struct netdev *netdev, |
328 | uint8_t mac[ETH_ADDR_LEN]) | |
329 | { | |
35b769cb EJ |
330 | memcpy(mac, netdev_vport_get_dev(netdev)->etheraddr, ETH_ADDR_LEN); |
331 | return 0; | |
777ece09 JG |
332 | } |
333 | ||
69ebca1e BP |
334 | /* Copies 'src' into 'dst', performing format conversion in the process. |
335 | * | |
336 | * 'src' is allowed to be misaligned. */ | |
f613a0d7 PS |
337 | static void |
338 | netdev_stats_from_ovs_vport_stats(struct netdev_stats *dst, | |
339 | const struct ovs_vport_stats *src) | |
340 | { | |
69ebca1e BP |
341 | dst->rx_packets = get_unaligned_u64(&src->rx_packets); |
342 | dst->tx_packets = get_unaligned_u64(&src->tx_packets); | |
343 | dst->rx_bytes = get_unaligned_u64(&src->rx_bytes); | |
344 | dst->tx_bytes = get_unaligned_u64(&src->tx_bytes); | |
345 | dst->rx_errors = get_unaligned_u64(&src->rx_errors); | |
346 | dst->tx_errors = get_unaligned_u64(&src->tx_errors); | |
347 | dst->rx_dropped = get_unaligned_u64(&src->rx_dropped); | |
348 | dst->tx_dropped = get_unaligned_u64(&src->tx_dropped); | |
f613a0d7 PS |
349 | dst->multicast = 0; |
350 | dst->collisions = 0; | |
351 | dst->rx_length_errors = 0; | |
352 | dst->rx_over_errors = 0; | |
353 | dst->rx_crc_errors = 0; | |
354 | dst->rx_frame_errors = 0; | |
355 | dst->rx_fifo_errors = 0; | |
356 | dst->rx_missed_errors = 0; | |
357 | dst->tx_aborted_errors = 0; | |
358 | dst->tx_carrier_errors = 0; | |
359 | dst->tx_fifo_errors = 0; | |
360 | dst->tx_heartbeat_errors = 0; | |
361 | dst->tx_window_errors = 0; | |
362 | } | |
363 | ||
777ece09 JG |
364 | int |
365 | netdev_vport_get_stats(const struct netdev *netdev, struct netdev_stats *stats) | |
366 | { | |
c19e6535 BP |
367 | struct dpif_linux_vport reply; |
368 | struct ofpbuf *buf; | |
369 | int error; | |
777ece09 | 370 | |
c19e6535 BP |
371 | error = dpif_linux_vport_get(netdev_get_name(netdev), &reply, &buf); |
372 | if (error) { | |
373 | return error; | |
374 | } else if (!reply.stats) { | |
375 | ofpbuf_delete(buf); | |
376 | return EOPNOTSUPP; | |
377 | } | |
378 | ||
f613a0d7 | 379 | netdev_stats_from_ovs_vport_stats(stats, reply.stats); |
c19e6535 BP |
380 | |
381 | ofpbuf_delete(buf); | |
777ece09 JG |
382 | |
383 | return 0; | |
384 | } | |
385 | ||
ea763e0e | 386 | static int |
275707c3 | 387 | tunnel_get_status(const struct netdev *netdev, struct smap *smap) |
ea763e0e | 388 | { |
275707c3 EJ |
389 | static char iface[IFNAMSIZ]; |
390 | ovs_be32 route; | |
ea763e0e | 391 | |
f431bf7d | 392 | route = netdev_vport_get_dev(netdev)->tnl_cfg.ip_dst; |
275707c3 | 393 | if (route_table_get_name(route, iface)) { |
a404826e AE |
394 | struct netdev *egress_netdev; |
395 | ||
79f1cbe9 | 396 | smap_add(smap, "tunnel_egress_iface", iface); |
a404826e | 397 | |
18812dff | 398 | if (!netdev_open(iface, "system", &egress_netdev)) { |
79f1cbe9 EJ |
399 | smap_add(smap, "tunnel_egress_iface_carrier", |
400 | netdev_get_carrier(egress_netdev) ? "up" : "down"); | |
a404826e AE |
401 | netdev_close(egress_netdev); |
402 | } | |
ea763e0e EJ |
403 | } |
404 | ||
405 | return 0; | |
406 | } | |
407 | ||
2b9d6589 | 408 | static int |
777ece09 JG |
409 | netdev_vport_update_flags(struct netdev *netdev OVS_UNUSED, |
410 | enum netdev_flags off, enum netdev_flags on OVS_UNUSED, | |
411 | enum netdev_flags *old_flagsp) | |
412 | { | |
413 | if (off & (NETDEV_UP | NETDEV_PROMISC)) { | |
414 | return EOPNOTSUPP; | |
415 | } | |
416 | ||
417 | *old_flagsp = NETDEV_UP | NETDEV_PROMISC; | |
418 | return 0; | |
419 | } | |
420 | ||
ac4d3bcb EJ |
421 | static unsigned int |
422 | netdev_vport_change_seq(const struct netdev *netdev) | |
423 | { | |
df67d7ae | 424 | return netdev_vport_get_dev(netdev)->change_seq; |
ac4d3bcb EJ |
425 | } |
426 | ||
ea83a2fc EJ |
427 | static void |
428 | netdev_vport_run(void) | |
429 | { | |
a132aa96 | 430 | route_table_run(); |
ea83a2fc EJ |
431 | } |
432 | ||
433 | static void | |
434 | netdev_vport_wait(void) | |
435 | { | |
a132aa96 | 436 | route_table_wait(); |
ea83a2fc EJ |
437 | } |
438 | \f | |
2b9d6589 | 439 | /* Helper functions. */ |
777ece09 | 440 | |
2b9d6589 | 441 | static void |
777ece09 JG |
442 | netdev_vport_poll_notify(const struct netdev *netdev) |
443 | { | |
df67d7ae | 444 | struct netdev_dev_vport *ndv = netdev_vport_get_dev(netdev); |
777ece09 | 445 | |
ac4d3bcb EJ |
446 | ndv->change_seq++; |
447 | if (!ndv->change_seq) { | |
448 | ndv->change_seq++; | |
449 | } | |
777ece09 | 450 | } |
2b9d6589 BP |
451 | \f |
452 | /* Code specific to individual vport types. */ | |
453 | ||
f431bf7d EJ |
454 | static ovs_be64 |
455 | parse_key(const struct smap *args, const char *name, | |
456 | bool *present, bool *flow) | |
c19e6535 BP |
457 | { |
458 | const char *s; | |
459 | ||
f431bf7d EJ |
460 | *present = false; |
461 | *flow = false; | |
462 | ||
79f1cbe9 | 463 | s = smap_get(args, name); |
c19e6535 | 464 | if (!s) { |
79f1cbe9 | 465 | s = smap_get(args, "key"); |
c19e6535 | 466 | if (!s) { |
f431bf7d | 467 | return 0; |
c19e6535 BP |
468 | } |
469 | } | |
470 | ||
f431bf7d EJ |
471 | *present = true; |
472 | ||
c19e6535 | 473 | if (!strcmp(s, "flow")) { |
f431bf7d EJ |
474 | *flow = true; |
475 | return 0; | |
c19e6535 | 476 | } else { |
f431bf7d | 477 | return htonll(strtoull(s, NULL, 0)); |
c19e6535 BP |
478 | } |
479 | } | |
480 | ||
2b9d6589 | 481 | static int |
6d9e6eb4 | 482 | parse_tunnel_config(const char *name, const char *type, |
f431bf7d EJ |
483 | const struct smap *args, struct ofpbuf *options, |
484 | struct netdev_tunnel_config *tnl_cfg_) | |
2b9d6589 | 485 | { |
f431bf7d EJ |
486 | bool ipsec_mech_set, needs_dst_port, has_csum; |
487 | struct netdev_tunnel_config tnl_cfg; | |
79f1cbe9 | 488 | struct smap_node *node; |
f431bf7d EJ |
489 | uint8_t flags; |
490 | ||
491 | flags = TNL_F_DF_DEFAULT; | |
492 | has_csum = strstr(type, "gre"); | |
493 | ipsec_mech_set = false; | |
494 | memset(&tnl_cfg, 0, sizeof tnl_cfg); | |
2b9d6589 | 495 | |
1280bf0e PS |
496 | if (!strcmp(type, "capwap")) { |
497 | VLOG_WARN_ONCE("CAPWAP tunnel support is deprecated."); | |
498 | } | |
499 | ||
f431bf7d EJ |
500 | needs_dst_port = !strcmp(type, "vxlan"); |
501 | tnl_cfg.ipsec = strstr(type, "ipsec"); | |
502 | if (tnl_cfg.ipsec) { | |
c19e6535 | 503 | flags |= TNL_F_IPSEC; |
e16a28b5 | 504 | } |
f431bf7d | 505 | tnl_cfg.dont_fragment = true; |
e16a28b5 | 506 | |
79f1cbe9 EJ |
507 | SMAP_FOR_EACH (node, args) { |
508 | if (!strcmp(node->key, "remote_ip")) { | |
2b9d6589 | 509 | struct in_addr in_addr; |
79f1cbe9 | 510 | if (lookup_ip(node->value, &in_addr)) { |
c3827f61 | 511 | VLOG_WARN("%s: bad %s 'remote_ip'", name, type); |
2b9d6589 | 512 | } else { |
f431bf7d | 513 | tnl_cfg.ip_dst = in_addr.s_addr; |
2b9d6589 | 514 | } |
79f1cbe9 | 515 | } else if (!strcmp(node->key, "local_ip")) { |
2b9d6589 | 516 | struct in_addr in_addr; |
79f1cbe9 | 517 | if (lookup_ip(node->value, &in_addr)) { |
c3827f61 | 518 | VLOG_WARN("%s: bad %s 'local_ip'", name, type); |
2b9d6589 | 519 | } else { |
f431bf7d | 520 | tnl_cfg.ip_src = in_addr.s_addr; |
2b9d6589 | 521 | } |
79f1cbe9 EJ |
522 | } else if (!strcmp(node->key, "tos")) { |
523 | if (!strcmp(node->value, "inherit")) { | |
c19e6535 | 524 | flags |= TNL_F_TOS_INHERIT; |
f431bf7d | 525 | tnl_cfg.tos_inherit = true; |
2b9d6589 | 526 | } else { |
3fca7064 PS |
527 | char *endptr; |
528 | int tos; | |
79f1cbe9 | 529 | tos = strtol(node->value, &endptr, 0); |
91aff446 | 530 | if (*endptr == '\0' && tos == (tos & IP_DSCP_MASK)) { |
3fca7064 | 531 | nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TOS, tos); |
f431bf7d | 532 | tnl_cfg.tos = tos; |
91aff446 BP |
533 | } else { |
534 | VLOG_WARN("%s: invalid TOS %s", name, node->value); | |
3fca7064 | 535 | } |
2b9d6589 | 536 | } |
79f1cbe9 EJ |
537 | } else if (!strcmp(node->key, "ttl")) { |
538 | if (!strcmp(node->value, "inherit")) { | |
c19e6535 | 539 | flags |= TNL_F_TTL_INHERIT; |
f431bf7d | 540 | tnl_cfg.ttl_inherit = true; |
2b9d6589 | 541 | } else { |
79f1cbe9 | 542 | nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TTL, atoi(node->value)); |
f431bf7d | 543 | tnl_cfg.ttl = atoi(node->value); |
2b9d6589 | 544 | } |
79f827fa | 545 | } else if (!strcmp(node->key, "dst_port") && needs_dst_port) { |
f431bf7d | 546 | tnl_cfg.dst_port = htons(atoi(node->value)); |
79f827fa KM |
547 | nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT, |
548 | atoi(node->value)); | |
f431bf7d | 549 | } else if (!strcmp(node->key, "csum") && has_csum) { |
79f1cbe9 | 550 | if (!strcmp(node->value, "true")) { |
c19e6535 | 551 | flags |= TNL_F_CSUM; |
f431bf7d | 552 | tnl_cfg.csum = true; |
2b9d6589 | 553 | } |
79f1cbe9 EJ |
554 | } else if (!strcmp(node->key, "df_default")) { |
555 | if (!strcmp(node->value, "false")) { | |
66409d1b | 556 | flags &= ~TNL_F_DF_DEFAULT; |
f431bf7d | 557 | tnl_cfg.dont_fragment = false; |
66409d1b | 558 | } |
f431bf7d | 559 | } else if (!strcmp(node->key, "peer_cert") && tnl_cfg.ipsec) { |
79f1cbe9 | 560 | if (smap_get(args, "certificate")) { |
3c52fa7b JP |
561 | ipsec_mech_set = true; |
562 | } else { | |
ef7ee76a JP |
563 | const char *use_ssl_cert; |
564 | ||
565 | /* If the "use_ssl_cert" is true, then "certificate" and | |
566 | * "private_key" will be pulled from the SSL table. The | |
567 | * use of this option is strongly discouraged, since it | |
568 | * will like be removed when multiple SSL configurations | |
569 | * are supported by OVS. | |
570 | */ | |
79f1cbe9 | 571 | use_ssl_cert = smap_get(args, "use_ssl_cert"); |
ef7ee76a | 572 | if (!use_ssl_cert || strcmp(use_ssl_cert, "true")) { |
8283e514 JP |
573 | VLOG_ERR("%s: 'peer_cert' requires 'certificate' argument", |
574 | name); | |
ef7ee76a JP |
575 | return EINVAL; |
576 | } | |
577 | ipsec_mech_set = true; | |
3c52fa7b | 578 | } |
f431bf7d | 579 | } else if (!strcmp(node->key, "psk") && tnl_cfg.ipsec) { |
2b9d6589 | 580 | ipsec_mech_set = true; |
f431bf7d | 581 | } else if (tnl_cfg.ipsec |
79f1cbe9 EJ |
582 | && (!strcmp(node->key, "certificate") |
583 | || !strcmp(node->key, "private_key") | |
584 | || !strcmp(node->key, "use_ssl_cert"))) { | |
3c52fa7b | 585 | /* Ignore options not used by the netdev. */ |
79f1cbe9 EJ |
586 | } else if (!strcmp(node->key, "key") || |
587 | !strcmp(node->key, "in_key") || | |
588 | !strcmp(node->key, "out_key")) { | |
c19e6535 | 589 | /* Handled separately below. */ |
2b9d6589 | 590 | } else { |
79f1cbe9 | 591 | VLOG_WARN("%s: unknown %s argument '%s'", name, type, node->key); |
2b9d6589 BP |
592 | } |
593 | } | |
594 | ||
79f827fa | 595 | /* Add a default destination port for VXLAN if none specified. */ |
f431bf7d | 596 | if (needs_dst_port && !tnl_cfg.dst_port) { |
79f827fa | 597 | nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT, VXLAN_DST_PORT); |
f431bf7d | 598 | tnl_cfg.dst_port = htons(VXLAN_DST_PORT); |
79f827fa KM |
599 | } |
600 | ||
f431bf7d | 601 | if (tnl_cfg.ipsec) { |
2a586a5c | 602 | static pid_t pid = 0; |
900f7601 | 603 | if (pid <= 0) { |
2a586a5c AS |
604 | char *file_name = xasprintf("%s/%s", ovs_rundir(), |
605 | "ovs-monitor-ipsec.pid"); | |
606 | pid = read_pidfile(file_name); | |
607 | free(file_name); | |
608 | } | |
609 | ||
e7009c36 | 610 | if (pid < 0) { |
8283e514 JP |
611 | VLOG_ERR("%s: IPsec requires the ovs-monitor-ipsec daemon", |
612 | name); | |
e7009c36 JP |
613 | return EINVAL; |
614 | } | |
5059eff3 | 615 | |
79f1cbe9 | 616 | if (smap_get(args, "peer_cert") && smap_get(args, "psk")) { |
8283e514 | 617 | VLOG_ERR("%s: cannot define both 'peer_cert' and 'psk'", name); |
3c52fa7b JP |
618 | return EINVAL; |
619 | } | |
620 | ||
621 | if (!ipsec_mech_set) { | |
8283e514 JP |
622 | VLOG_ERR("%s: IPsec requires an 'peer_cert' or psk' argument", |
623 | name); | |
3c52fa7b JP |
624 | return EINVAL; |
625 | } | |
2b9d6589 BP |
626 | } |
627 | ||
f431bf7d | 628 | if (!tnl_cfg.ip_dst) { |
8283e514 JP |
629 | VLOG_ERR("%s: %s type requires valid 'remote_ip' argument", |
630 | name, type); | |
2b9d6589 BP |
631 | return EINVAL; |
632 | } | |
f431bf7d | 633 | nl_msg_put_be32(options, OVS_TUNNEL_ATTR_DST_IPV4, tnl_cfg.ip_dst); |
c19e6535 | 634 | |
f431bf7d EJ |
635 | if (tnl_cfg.ip_src) { |
636 | if (ip_is_multicast(tnl_cfg.ip_dst)) { | |
b37e6334 | 637 | VLOG_WARN("%s: remote_ip is multicast, ignoring local_ip", name); |
f431bf7d | 638 | tnl_cfg.ip_src = 0; |
b37e6334 | 639 | } else { |
f431bf7d | 640 | nl_msg_put_be32(options, OVS_TUNNEL_ATTR_SRC_IPV4, tnl_cfg.ip_src); |
b37e6334 BP |
641 | } |
642 | } | |
643 | ||
f431bf7d EJ |
644 | if (!tnl_cfg.ttl) { |
645 | tnl_cfg.ttl = DEFAULT_TTL; | |
646 | } | |
647 | ||
648 | tnl_cfg.in_key = parse_key(args, "in_key", | |
649 | &tnl_cfg.in_key_present, | |
650 | &tnl_cfg.in_key_flow); | |
651 | if (tnl_cfg.in_key_present && !tnl_cfg.in_key_flow) { | |
652 | nl_msg_put_be64(options, OVS_TUNNEL_ATTR_IN_KEY, tnl_cfg.in_key); | |
653 | } | |
654 | ||
655 | tnl_cfg.out_key = parse_key(args, "out_key", | |
656 | &tnl_cfg.out_key_present, | |
657 | &tnl_cfg.out_key_flow); | |
658 | if (tnl_cfg.out_key_present && !tnl_cfg.out_key_flow) { | |
659 | nl_msg_put_be64(options, OVS_TUNNEL_ATTR_OUT_KEY, tnl_cfg.out_key); | |
660 | } | |
df2c07f4 | 661 | nl_msg_put_u32(options, OVS_TUNNEL_ATTR_FLAGS, flags); |
2b9d6589 | 662 | |
f431bf7d EJ |
663 | *tnl_cfg_ = tnl_cfg; |
664 | ||
2b9d6589 BP |
665 | return 0; |
666 | } | |
667 | ||
c19e6535 BP |
668 | static int |
669 | tnl_port_config_from_nlattr(const struct nlattr *options, size_t options_len, | |
df2c07f4 JP |
670 | struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]) |
671 | { | |
672 | static const struct nl_policy ovs_tunnel_policy[] = { | |
7f804ea5 JR |
673 | [OVS_TUNNEL_ATTR_FLAGS] = { .type = NL_A_U32, .optional = true }, |
674 | [OVS_TUNNEL_ATTR_DST_IPV4] = { .type = NL_A_BE32, .optional = true }, | |
df2c07f4 JP |
675 | [OVS_TUNNEL_ATTR_SRC_IPV4] = { .type = NL_A_BE32, .optional = true }, |
676 | [OVS_TUNNEL_ATTR_IN_KEY] = { .type = NL_A_BE64, .optional = true }, | |
677 | [OVS_TUNNEL_ATTR_OUT_KEY] = { .type = NL_A_BE64, .optional = true }, | |
678 | [OVS_TUNNEL_ATTR_TOS] = { .type = NL_A_U8, .optional = true }, | |
679 | [OVS_TUNNEL_ATTR_TTL] = { .type = NL_A_U8, .optional = true }, | |
79f827fa | 680 | [OVS_TUNNEL_ATTR_DST_PORT] = { .type = NL_A_U16, .optional = true }, |
c19e6535 BP |
681 | }; |
682 | struct ofpbuf buf; | |
683 | ||
684 | ofpbuf_use_const(&buf, options, options_len); | |
df2c07f4 JP |
685 | if (!nl_policy_parse(&buf, 0, ovs_tunnel_policy, |
686 | a, ARRAY_SIZE(ovs_tunnel_policy))) { | |
c19e6535 BP |
687 | return EINVAL; |
688 | } | |
689 | return 0; | |
690 | } | |
691 | ||
692 | static uint64_t | |
693 | get_be64_or_zero(const struct nlattr *a) | |
694 | { | |
695 | return a ? ntohll(nl_attr_get_be64(a)) : 0; | |
696 | } | |
697 | ||
2b9d6589 | 698 | static int |
6d9e6eb4 | 699 | unparse_tunnel_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED, |
c19e6535 | 700 | const struct nlattr *options, size_t options_len, |
79f1cbe9 | 701 | struct smap *args) |
6d9e6eb4 | 702 | { |
df2c07f4 | 703 | struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]; |
c19e6535 BP |
704 | uint32_t flags; |
705 | int error; | |
6d9e6eb4 | 706 | |
c19e6535 BP |
707 | error = tnl_port_config_from_nlattr(options, options_len, a); |
708 | if (error) { | |
709 | return error; | |
710 | } | |
711 | ||
7f804ea5 JR |
712 | if (a[OVS_TUNNEL_ATTR_DST_IPV4]) { |
713 | ovs_be32 daddr = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_DST_IPV4]); | |
714 | smap_add_format(args, "remote_ip", IP_FMT, IP_ARGS(daddr)); | |
715 | } | |
c19e6535 | 716 | |
df2c07f4 JP |
717 | if (a[OVS_TUNNEL_ATTR_SRC_IPV4]) { |
718 | ovs_be32 saddr = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_SRC_IPV4]); | |
ed36537e | 719 | smap_add_format(args, "local_ip", IP_FMT, IP_ARGS(saddr)); |
6d9e6eb4 BP |
720 | } |
721 | ||
df2c07f4 | 722 | if (!a[OVS_TUNNEL_ATTR_IN_KEY] && !a[OVS_TUNNEL_ATTR_OUT_KEY]) { |
6d9e6eb4 | 723 | smap_add(args, "key", "flow"); |
6d9e6eb4 | 724 | } else { |
df2c07f4 JP |
725 | uint64_t in_key = get_be64_or_zero(a[OVS_TUNNEL_ATTR_IN_KEY]); |
726 | uint64_t out_key = get_be64_or_zero(a[OVS_TUNNEL_ATTR_OUT_KEY]); | |
c19e6535 BP |
727 | |
728 | if (in_key && in_key == out_key) { | |
79f1cbe9 | 729 | smap_add_format(args, "key", "%"PRIu64, in_key); |
c19e6535 | 730 | } else { |
df2c07f4 | 731 | if (!a[OVS_TUNNEL_ATTR_IN_KEY]) { |
c19e6535 BP |
732 | smap_add(args, "in_key", "flow"); |
733 | } else if (in_key) { | |
79f1cbe9 | 734 | smap_add_format(args, "in_key", "%"PRIu64, in_key); |
c19e6535 | 735 | } |
6d9e6eb4 | 736 | |
df2c07f4 | 737 | if (!a[OVS_TUNNEL_ATTR_OUT_KEY]) { |
c19e6535 BP |
738 | smap_add(args, "out_key", "flow"); |
739 | } else if (out_key) { | |
79f1cbe9 | 740 | smap_add_format(args, "out_key", "%"PRIu64, out_key); |
c19e6535 | 741 | } |
6d9e6eb4 BP |
742 | } |
743 | } | |
744 | ||
7f804ea5 JR |
745 | flags = get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]); |
746 | ||
c19e6535 | 747 | if (flags & TNL_F_TTL_INHERIT) { |
62827e6a | 748 | smap_add(args, "ttl", "inherit"); |
df2c07f4 JP |
749 | } else if (a[OVS_TUNNEL_ATTR_TTL]) { |
750 | int ttl = nl_attr_get_u8(a[OVS_TUNNEL_ATTR_TTL]); | |
62827e6a | 751 | smap_add_format(args, "ttl", "%d", ttl); |
c19e6535 BP |
752 | } |
753 | ||
754 | if (flags & TNL_F_TOS_INHERIT) { | |
6d9e6eb4 | 755 | smap_add(args, "tos", "inherit"); |
df2c07f4 JP |
756 | } else if (a[OVS_TUNNEL_ATTR_TOS]) { |
757 | int tos = nl_attr_get_u8(a[OVS_TUNNEL_ATTR_TOS]); | |
79f1cbe9 | 758 | smap_add_format(args, "tos", "0x%x", tos); |
6d9e6eb4 BP |
759 | } |
760 | ||
79f827fa KM |
761 | if (a[OVS_TUNNEL_ATTR_DST_PORT]) { |
762 | uint16_t dst_port = nl_attr_get_u16(a[OVS_TUNNEL_ATTR_DST_PORT]); | |
763 | if (dst_port != VXLAN_DST_PORT) { | |
764 | smap_add_format(args, "dst_port", "%d", dst_port); | |
765 | } | |
766 | } | |
767 | ||
c19e6535 | 768 | if (flags & TNL_F_CSUM) { |
6d9e6eb4 BP |
769 | smap_add(args, "csum", "true"); |
770 | } | |
66409d1b | 771 | if (flags & TNL_F_DF_INHERIT) { |
9b9f4d60 EJ |
772 | /* Shouldn't happen as "df_inherit" is no longer supported. However, |
773 | * for completeness we report it if it's there. */ | |
66409d1b AE |
774 | smap_add(args, "df_inherit", "true"); |
775 | } | |
776 | if (!(flags & TNL_F_DF_DEFAULT)) { | |
777 | smap_add(args, "df_default", "false"); | |
778 | } | |
6d9e6eb4 BP |
779 | |
780 | return 0; | |
781 | } | |
782 | ||
783 | static int | |
784 | parse_patch_config(const char *name, const char *type OVS_UNUSED, | |
f431bf7d EJ |
785 | const struct smap *args, struct ofpbuf *options, |
786 | struct netdev_tunnel_config *tnl_cfg) | |
2b9d6589 | 787 | { |
2b9d6589 BP |
788 | const char *peer; |
789 | ||
f431bf7d EJ |
790 | memset(tnl_cfg, 0, sizeof *tnl_cfg); |
791 | ||
79f1cbe9 | 792 | peer = smap_get(args, "peer"); |
2b9d6589 | 793 | if (!peer) { |
8283e514 | 794 | VLOG_ERR("%s: patch type requires valid 'peer' argument", name); |
2b9d6589 BP |
795 | return EINVAL; |
796 | } | |
797 | ||
79f1cbe9 | 798 | if (smap_count(args) > 1) { |
8283e514 | 799 | VLOG_ERR("%s: patch type takes only a 'peer' argument", name); |
2b9d6589 BP |
800 | return EINVAL; |
801 | } | |
802 | ||
c19e6535 | 803 | if (strlen(peer) >= IFNAMSIZ) { |
8283e514 | 804 | VLOG_ERR("%s: patch 'peer' arg too long", name); |
2b9d6589 BP |
805 | return EINVAL; |
806 | } | |
807 | ||
808 | if (!strcmp(name, peer)) { | |
8283e514 | 809 | VLOG_ERR("%s: patch peer must not be self", name); |
2b9d6589 BP |
810 | return EINVAL; |
811 | } | |
812 | ||
df2c07f4 | 813 | nl_msg_put_string(options, OVS_PATCH_ATTR_PEER, peer); |
2b9d6589 BP |
814 | |
815 | return 0; | |
816 | } | |
6d9e6eb4 BP |
817 | |
818 | static int | |
819 | unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED, | |
c19e6535 | 820 | const struct nlattr *options, size_t options_len, |
79f1cbe9 | 821 | struct smap *args) |
6d9e6eb4 | 822 | { |
df2c07f4 JP |
823 | static const struct nl_policy ovs_patch_policy[] = { |
824 | [OVS_PATCH_ATTR_PEER] = { .type = NL_A_STRING, | |
c19e6535 BP |
825 | .max_len = IFNAMSIZ, |
826 | .optional = false } | |
827 | }; | |
828 | ||
df2c07f4 | 829 | struct nlattr *a[ARRAY_SIZE(ovs_patch_policy)]; |
c19e6535 BP |
830 | struct ofpbuf buf; |
831 | ||
832 | ofpbuf_use_const(&buf, options, options_len); | |
df2c07f4 JP |
833 | if (!nl_policy_parse(&buf, 0, ovs_patch_policy, |
834 | a, ARRAY_SIZE(ovs_patch_policy))) { | |
c19e6535 | 835 | return EINVAL; |
6d9e6eb4 BP |
836 | } |
837 | ||
df2c07f4 | 838 | smap_add(args, "peer", nl_attr_get_string(a[OVS_PATCH_ATTR_PEER])); |
6d9e6eb4 BP |
839 | return 0; |
840 | } | |
2b9d6589 | 841 | \f |
f431bf7d | 842 | #define VPORT_FUNCTIONS(GET_TUNNEL_CONFIG, GET_STATUS) \ |
b46ccdf5 | 843 | NULL, \ |
ea83a2fc EJ |
844 | netdev_vport_run, \ |
845 | netdev_vport_wait, \ | |
2b9d6589 BP |
846 | \ |
847 | netdev_vport_create, \ | |
848 | netdev_vport_destroy, \ | |
de5cdb90 | 849 | netdev_vport_get_config, \ |
6d9e6eb4 | 850 | netdev_vport_set_config, \ |
f431bf7d | 851 | GET_TUNNEL_CONFIG, \ |
2b9d6589 BP |
852 | \ |
853 | netdev_vport_open, \ | |
854 | netdev_vport_close, \ | |
855 | \ | |
7b6b0ef4 | 856 | NULL, /* listen */ \ |
2b9d6589 BP |
857 | NULL, /* recv */ \ |
858 | NULL, /* recv_wait */ \ | |
859 | NULL, /* drain */ \ | |
860 | \ | |
552e20d0 | 861 | NULL, /* send */ \ |
2b9d6589 BP |
862 | NULL, /* send_wait */ \ |
863 | \ | |
864 | netdev_vport_set_etheraddr, \ | |
865 | netdev_vport_get_etheraddr, \ | |
14622f22 BP |
866 | NULL, /* get_mtu */ \ |
867 | NULL, /* set_mtu */ \ | |
2b9d6589 | 868 | NULL, /* get_ifindex */ \ |
85da620e | 869 | NULL, /* get_carrier */ \ |
65c3058c | 870 | NULL, /* get_carrier_resets */ \ |
63331829 | 871 | NULL, /* get_miimon */ \ |
2b9d6589 | 872 | netdev_vport_get_stats, \ |
2f31a822 | 873 | NULL, /* set_stats */ \ |
2b9d6589 BP |
874 | \ |
875 | NULL, /* get_features */ \ | |
876 | NULL, /* set_advertisements */ \ | |
2b9d6589 BP |
877 | \ |
878 | NULL, /* set_policing */ \ | |
879 | NULL, /* get_qos_types */ \ | |
880 | NULL, /* get_qos_capabilities */ \ | |
881 | NULL, /* get_qos */ \ | |
882 | NULL, /* set_qos */ \ | |
883 | NULL, /* get_queue */ \ | |
884 | NULL, /* set_queue */ \ | |
885 | NULL, /* delete_queue */ \ | |
886 | NULL, /* get_queue_stats */ \ | |
887 | NULL, /* dump_queues */ \ | |
888 | NULL, /* dump_queue_stats */ \ | |
889 | \ | |
890 | NULL, /* get_in4 */ \ | |
891 | NULL, /* set_in4 */ \ | |
892 | NULL, /* get_in6 */ \ | |
893 | NULL, /* add_router */ \ | |
894 | NULL, /* get_next_hop */ \ | |
ea763e0e | 895 | GET_STATUS, \ |
2b9d6589 BP |
896 | NULL, /* arp_lookup */ \ |
897 | \ | |
898 | netdev_vport_update_flags, \ | |
899 | \ | |
ac4d3bcb | 900 | netdev_vport_change_seq |
2b9d6589 | 901 | |
db078f85 EJ |
902 | #define TUNNEL_CLASS(NAME, VPORT_TYPE) \ |
903 | { VPORT_TYPE, \ | |
f431bf7d EJ |
904 | { NAME, VPORT_FUNCTIONS(get_netdev_tunnel_config, \ |
905 | tunnel_get_status) }, \ | |
db078f85 EJ |
906 | parse_tunnel_config, unparse_tunnel_config } |
907 | ||
2b9d6589 BP |
908 | void |
909 | netdev_vport_register(void) | |
910 | { | |
c3827f61 | 911 | static const struct vport_class vport_classes[] = { |
db078f85 EJ |
912 | TUNNEL_CLASS("gre", OVS_VPORT_TYPE_GRE), |
913 | TUNNEL_CLASS("ipsec_gre", OVS_VPORT_TYPE_GRE), | |
914 | TUNNEL_CLASS("gre64", OVS_VPORT_TYPE_GRE64), | |
915 | TUNNEL_CLASS("ipsec_gre64", OVS_VPORT_TYPE_GRE64), | |
916 | TUNNEL_CLASS("capwap", OVS_VPORT_TYPE_CAPWAP), | |
917 | TUNNEL_CLASS("vxlan", OVS_VPORT_TYPE_VXLAN), | |
79f827fa | 918 | |
df2c07f4 | 919 | { OVS_VPORT_TYPE_PATCH, |
f431bf7d | 920 | { "patch", VPORT_FUNCTIONS(NULL, NULL) }, |
de5cdb90 | 921 | parse_patch_config, unparse_patch_config } |
c3827f61 BP |
922 | }; |
923 | ||
924 | int i; | |
925 | ||
926 | for (i = 0; i < ARRAY_SIZE(vport_classes); i++) { | |
927 | netdev_register_provider(&vport_classes[i].netdev_class); | |
928 | } | |
2b9d6589 | 929 | } |