netdev-vport: Make 'iface' non-static in tunnel_get_status().
[sliver-openvswitch.git] / lib / netdev-vport.c
1 /*
2  * Copyright (c) 2010, 2011, 2012, 2013 Nicira, Inc.
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>
18
19 #include "netdev-vport.h"
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <sys/socket.h>
24 #include <net/if.h>
25 #include <sys/ioctl.h>
26
27 #include "byte-order.h"
28 #include "daemon.h"
29 #include "dirs.h"
30 #include "dpif.h"
31 #include "hash.h"
32 #include "hmap.h"
33 #include "list.h"
34 #include "netdev-provider.h"
35 #include "ofpbuf.h"
36 #include "packets.h"
37 #include "route-table.h"
38 #include "shash.h"
39 #include "socket-util.h"
40 #include "vlog.h"
41
42 VLOG_DEFINE_THIS_MODULE(netdev_vport);
43
44 #define VXLAN_DST_PORT 4789
45 #define LISP_DST_PORT 4341
46
47 #define DEFAULT_TTL 64
48
49 struct netdev_vport {
50     struct netdev up;
51     unsigned int change_seq;
52     uint8_t etheraddr[ETH_ADDR_LEN];
53     struct netdev_stats stats;
54
55     /* Tunnels. */
56     struct netdev_tunnel_config tnl_cfg;
57
58     /* Patch Ports. */
59     char *peer;
60 };
61
62 struct vport_class {
63     const char *dpif_port;
64     struct netdev_class netdev_class;
65 };
66
67 static int netdev_vport_create(const struct netdev_class *, const char *,
68                                struct netdev **);
69 static int get_patch_config(const struct netdev *, struct smap *args);
70 static int get_tunnel_config(const struct netdev *, struct smap *args);
71 static void netdev_vport_poll_notify(struct netdev_vport *);
72
73 static bool
74 is_vport_class(const struct netdev_class *class)
75 {
76     return class->create == netdev_vport_create;
77 }
78
79 static const struct vport_class *
80 vport_class_cast(const struct netdev_class *class)
81 {
82     ovs_assert(is_vport_class(class));
83     return CONTAINER_OF(class, struct vport_class, netdev_class);
84 }
85
86 static struct netdev_vport *
87 netdev_vport_cast(const struct netdev *netdev)
88 {
89     ovs_assert(is_vport_class(netdev_get_class(netdev)));
90     return CONTAINER_OF(netdev, struct netdev_vport, up);
91 }
92
93 static const struct netdev_tunnel_config *
94 get_netdev_tunnel_config(const struct netdev *netdev)
95 {
96     return &netdev_vport_cast(netdev)->tnl_cfg;
97 }
98
99 bool
100 netdev_vport_is_patch(const struct netdev *netdev)
101 {
102     const struct netdev_class *class = netdev_get_class(netdev);
103
104     return class->get_config == get_patch_config;
105 }
106
107 static bool
108 netdev_vport_needs_dst_port(const struct netdev *dev)
109 {
110     const struct netdev_class *class = netdev_get_class(dev);
111     const char *type = netdev_get_type(dev);
112
113     return (class->get_config == get_tunnel_config &&
114             (!strcmp("vxlan", type) || !strcmp("lisp", type)));
115 }
116
117 const char *
118 netdev_vport_class_get_dpif_port(const struct netdev_class *class)
119 {
120     return is_vport_class(class) ? vport_class_cast(class)->dpif_port : NULL;
121 }
122
123 const char *
124 netdev_vport_get_dpif_port(const struct netdev *netdev)
125 {
126     const char *dpif_port;
127
128     if (netdev_vport_needs_dst_port(netdev)) {
129         const struct netdev_vport *vport = netdev_vport_cast(netdev);
130         const char *type = netdev_get_type(netdev);
131         static char dpif_port_combined[IFNAMSIZ];
132
133         /*
134          * Note: IFNAMSIZ is 16 bytes long. The maximum length of a VXLAN
135          * or LISP port name below is 15 or 14 bytes respectively. Still,
136          * assert here on the size of strlen(type) in case that changes
137          * in the future.
138          */
139         ovs_assert(strlen(type) + 10 < IFNAMSIZ);
140         snprintf(dpif_port_combined, IFNAMSIZ, "%s_sys_%d", type,
141                  ntohs(vport->tnl_cfg.dst_port));
142         return dpif_port_combined;
143     } else {
144         const struct netdev_class *class = netdev_get_class(netdev);
145         dpif_port = netdev_vport_class_get_dpif_port(class);
146     }
147
148     return dpif_port ? dpif_port : netdev_get_name(netdev);
149 }
150
151 static int
152 netdev_vport_create(const struct netdev_class *netdev_class, const char *name,
153                     struct netdev **netdevp)
154 {
155     struct netdev_vport *dev;
156
157     dev = xzalloc(sizeof *dev);
158     netdev_init(&dev->up, name, netdev_class);
159     dev->change_seq = 1;
160     eth_addr_random(dev->etheraddr);
161
162     *netdevp = &dev->up;
163     route_table_register();
164
165     return 0;
166 }
167
168 static void
169 netdev_vport_destroy(struct netdev *netdev_)
170 {
171     struct netdev_vport *netdev = netdev_vport_cast(netdev_);
172
173     route_table_unregister();
174     free(netdev->peer);
175     free(netdev);
176 }
177
178 static int
179 netdev_vport_set_etheraddr(struct netdev *netdev_,
180                            const uint8_t mac[ETH_ADDR_LEN])
181 {
182     struct netdev_vport *netdev = netdev_vport_cast(netdev_);
183     memcpy(netdev->etheraddr, mac, ETH_ADDR_LEN);
184     netdev_vport_poll_notify(netdev);
185     return 0;
186 }
187
188 static int
189 netdev_vport_get_etheraddr(const struct netdev *netdev,
190                            uint8_t mac[ETH_ADDR_LEN])
191 {
192     memcpy(mac, netdev_vport_cast(netdev)->etheraddr, ETH_ADDR_LEN);
193     return 0;
194 }
195
196 static int
197 tunnel_get_status(const struct netdev *netdev, struct smap *smap)
198 {
199     char iface[IFNAMSIZ];
200     ovs_be32 route;
201
202     route = netdev_vport_cast(netdev)->tnl_cfg.ip_dst;
203     if (route_table_get_name(route, iface)) {
204         struct netdev *egress_netdev;
205
206         smap_add(smap, "tunnel_egress_iface", iface);
207
208         if (!netdev_open(iface, "system", &egress_netdev)) {
209             smap_add(smap, "tunnel_egress_iface_carrier",
210                      netdev_get_carrier(egress_netdev) ? "up" : "down");
211             netdev_close(egress_netdev);
212         }
213     }
214
215     return 0;
216 }
217
218 static int
219 netdev_vport_update_flags(struct netdev *netdev OVS_UNUSED,
220                           enum netdev_flags off,
221                           enum netdev_flags on OVS_UNUSED,
222                           enum netdev_flags *old_flagsp)
223 {
224     if (off & (NETDEV_UP | NETDEV_PROMISC)) {
225         return EOPNOTSUPP;
226     }
227
228     *old_flagsp = NETDEV_UP | NETDEV_PROMISC;
229     return 0;
230 }
231
232 static unsigned int
233 netdev_vport_change_seq(const struct netdev *netdev)
234 {
235     return netdev_vport_cast(netdev)->change_seq;
236 }
237
238 static void
239 netdev_vport_run(void)
240 {
241     route_table_run();
242 }
243
244 static void
245 netdev_vport_wait(void)
246 {
247     route_table_wait();
248 }
249 \f
250 /* Helper functions. */
251
252 static void
253 netdev_vport_poll_notify(struct netdev_vport *ndv)
254 {
255     ndv->change_seq++;
256     if (!ndv->change_seq) {
257         ndv->change_seq++;
258     }
259 }
260 \f
261 /* Code specific to tunnel types. */
262
263 static ovs_be64
264 parse_key(const struct smap *args, const char *name,
265           bool *present, bool *flow)
266 {
267     const char *s;
268
269     *present = false;
270     *flow = false;
271
272     s = smap_get(args, name);
273     if (!s) {
274         s = smap_get(args, "key");
275         if (!s) {
276             return 0;
277         }
278     }
279
280     *present = true;
281
282     if (!strcmp(s, "flow")) {
283         *flow = true;
284         return 0;
285     } else {
286         return htonll(strtoull(s, NULL, 0));
287     }
288 }
289
290 static int
291 set_tunnel_config(struct netdev *dev_, const struct smap *args)
292 {
293     struct netdev_vport *dev = netdev_vport_cast(dev_);
294     const char *name = netdev_get_name(dev_);
295     const char *type = netdev_get_type(dev_);
296     bool ipsec_mech_set, needs_dst_port, has_csum;
297     struct netdev_tunnel_config tnl_cfg;
298     struct smap_node *node;
299
300     has_csum = strstr(type, "gre");
301     ipsec_mech_set = false;
302     memset(&tnl_cfg, 0, sizeof tnl_cfg);
303
304     needs_dst_port = netdev_vport_needs_dst_port(dev_);
305     tnl_cfg.ipsec = strstr(type, "ipsec");
306     tnl_cfg.dont_fragment = true;
307
308     SMAP_FOR_EACH (node, args) {
309         if (!strcmp(node->key, "remote_ip")) {
310             struct in_addr in_addr;
311             if (!strcmp(node->value, "flow")) {
312                 tnl_cfg.ip_dst_flow = true;
313                 tnl_cfg.ip_dst = htonl(0);
314             } else if (lookup_ip(node->value, &in_addr)) {
315                 VLOG_WARN("%s: bad %s 'remote_ip'", name, type);
316             } else if (ip_is_multicast(in_addr.s_addr)) {
317                 VLOG_WARN("%s: multicast remote_ip="IP_FMT" not allowed",
318                           name, IP_ARGS(in_addr.s_addr));
319                 return EINVAL;
320             } else {
321                 tnl_cfg.ip_dst = in_addr.s_addr;
322             }
323         } else if (!strcmp(node->key, "local_ip")) {
324             struct in_addr in_addr;
325             if (!strcmp(node->value, "flow")) {
326                 tnl_cfg.ip_src_flow = true;
327                 tnl_cfg.ip_src = htonl(0);
328             } else if (lookup_ip(node->value, &in_addr)) {
329                 VLOG_WARN("%s: bad %s 'local_ip'", name, type);
330             } else {
331                 tnl_cfg.ip_src = in_addr.s_addr;
332             }
333         } else if (!strcmp(node->key, "tos")) {
334             if (!strcmp(node->value, "inherit")) {
335                 tnl_cfg.tos_inherit = true;
336             } else {
337                 char *endptr;
338                 int tos;
339                 tos = strtol(node->value, &endptr, 0);
340                 if (*endptr == '\0' && tos == (tos & IP_DSCP_MASK)) {
341                     tnl_cfg.tos = tos;
342                 } else {
343                     VLOG_WARN("%s: invalid TOS %s", name, node->value);
344                 }
345             }
346         } else if (!strcmp(node->key, "ttl")) {
347             if (!strcmp(node->value, "inherit")) {
348                 tnl_cfg.ttl_inherit = true;
349             } else {
350                 tnl_cfg.ttl = atoi(node->value);
351             }
352         } else if (!strcmp(node->key, "dst_port") && needs_dst_port) {
353             tnl_cfg.dst_port = htons(atoi(node->value));
354         } else if (!strcmp(node->key, "csum") && has_csum) {
355             if (!strcmp(node->value, "true")) {
356                 tnl_cfg.csum = true;
357             }
358         } else if (!strcmp(node->key, "df_default")) {
359             if (!strcmp(node->value, "false")) {
360                 tnl_cfg.dont_fragment = false;
361             }
362         } else if (!strcmp(node->key, "peer_cert") && tnl_cfg.ipsec) {
363             if (smap_get(args, "certificate")) {
364                 ipsec_mech_set = true;
365             } else {
366                 const char *use_ssl_cert;
367
368                 /* If the "use_ssl_cert" is true, then "certificate" and
369                  * "private_key" will be pulled from the SSL table.  The
370                  * use of this option is strongly discouraged, since it
371                  * will like be removed when multiple SSL configurations
372                  * are supported by OVS.
373                  */
374                 use_ssl_cert = smap_get(args, "use_ssl_cert");
375                 if (!use_ssl_cert || strcmp(use_ssl_cert, "true")) {
376                     VLOG_ERR("%s: 'peer_cert' requires 'certificate' argument",
377                              name);
378                     return EINVAL;
379                 }
380                 ipsec_mech_set = true;
381             }
382         } else if (!strcmp(node->key, "psk") && tnl_cfg.ipsec) {
383             ipsec_mech_set = true;
384         } else if (tnl_cfg.ipsec
385                 && (!strcmp(node->key, "certificate")
386                     || !strcmp(node->key, "private_key")
387                     || !strcmp(node->key, "use_ssl_cert"))) {
388             /* Ignore options not used by the netdev. */
389         } else if (!strcmp(node->key, "key") ||
390                    !strcmp(node->key, "in_key") ||
391                    !strcmp(node->key, "out_key")) {
392             /* Handled separately below. */
393         } else {
394             VLOG_WARN("%s: unknown %s argument '%s'", name, type, node->key);
395         }
396     }
397
398     /* Add a default destination port for VXLAN if none specified. */
399     if (!strcmp(type, "vxlan") && !tnl_cfg.dst_port) {
400         tnl_cfg.dst_port = htons(VXLAN_DST_PORT);
401     }
402
403     /* Add a default destination port for LISP if none specified. */
404     if (!strcmp(type, "lisp") && !tnl_cfg.dst_port) {
405         tnl_cfg.dst_port = htons(LISP_DST_PORT);
406     }
407
408     if (tnl_cfg.ipsec) {
409         static pid_t pid = 0;
410         if (pid <= 0) {
411             char *file_name = xasprintf("%s/%s", ovs_rundir(),
412                                         "ovs-monitor-ipsec.pid");
413             pid = read_pidfile(file_name);
414             free(file_name);
415         }
416
417         if (pid < 0) {
418             VLOG_ERR("%s: IPsec requires the ovs-monitor-ipsec daemon",
419                      name);
420             return EINVAL;
421         }
422
423         if (smap_get(args, "peer_cert") && smap_get(args, "psk")) {
424             VLOG_ERR("%s: cannot define both 'peer_cert' and 'psk'", name);
425             return EINVAL;
426         }
427
428         if (!ipsec_mech_set) {
429             VLOG_ERR("%s: IPsec requires an 'peer_cert' or psk' argument",
430                      name);
431             return EINVAL;
432         }
433     }
434
435     if (!tnl_cfg.ip_dst && !tnl_cfg.ip_dst_flow) {
436         VLOG_ERR("%s: %s type requires valid 'remote_ip' argument",
437                  name, type);
438         return EINVAL;
439     }
440     if (tnl_cfg.ip_src_flow && !tnl_cfg.ip_dst_flow) {
441         VLOG_ERR("%s: %s type requires 'remote_ip=flow' with 'local_ip=flow'",
442                  name, type);
443         return EINVAL;
444     }
445     if (!tnl_cfg.ttl) {
446         tnl_cfg.ttl = DEFAULT_TTL;
447     }
448
449     tnl_cfg.in_key = parse_key(args, "in_key",
450                                &tnl_cfg.in_key_present,
451                                &tnl_cfg.in_key_flow);
452
453     tnl_cfg.out_key = parse_key(args, "out_key",
454                                &tnl_cfg.out_key_present,
455                                &tnl_cfg.out_key_flow);
456
457     dev->tnl_cfg = tnl_cfg;
458     netdev_vport_poll_notify(dev);
459
460     return 0;
461 }
462
463 static int
464 get_tunnel_config(const struct netdev *dev, struct smap *args)
465 {
466     const struct netdev_tunnel_config *tnl_cfg =
467         &netdev_vport_cast(dev)->tnl_cfg;
468
469     if (tnl_cfg->ip_dst) {
470         smap_add_format(args, "remote_ip", IP_FMT, IP_ARGS(tnl_cfg->ip_dst));
471     } else if (tnl_cfg->ip_dst_flow) {
472         smap_add(args, "remote_ip", "flow");
473     }
474
475     if (tnl_cfg->ip_src) {
476         smap_add_format(args, "local_ip", IP_FMT, IP_ARGS(tnl_cfg->ip_src));
477     } else if (tnl_cfg->ip_src_flow) {
478         smap_add(args, "local_ip", "flow");
479     }
480
481     if (tnl_cfg->in_key_flow && tnl_cfg->out_key_flow) {
482         smap_add(args, "key", "flow");
483     } else if (tnl_cfg->in_key_present && tnl_cfg->out_key_present
484                && tnl_cfg->in_key == tnl_cfg->out_key) {
485         smap_add_format(args, "key", "%"PRIu64, ntohll(tnl_cfg->in_key));
486     } else {
487         if (tnl_cfg->in_key_flow) {
488             smap_add(args, "in_key", "flow");
489         } else if (tnl_cfg->in_key_present) {
490             smap_add_format(args, "in_key", "%"PRIu64,
491                             ntohll(tnl_cfg->in_key));
492         }
493
494         if (tnl_cfg->out_key_flow) {
495             smap_add(args, "out_key", "flow");
496         } else if (tnl_cfg->out_key_present) {
497             smap_add_format(args, "out_key", "%"PRIu64,
498                             ntohll(tnl_cfg->out_key));
499         }
500     }
501
502     if (tnl_cfg->ttl_inherit) {
503         smap_add(args, "ttl", "inherit");
504     } else if (tnl_cfg->ttl != DEFAULT_TTL) {
505         smap_add_format(args, "ttl", "%"PRIu8, tnl_cfg->ttl);
506     }
507
508     if (tnl_cfg->tos_inherit) {
509         smap_add(args, "tos", "inherit");
510     } else if (tnl_cfg->tos) {
511         smap_add_format(args, "tos", "0x%x", tnl_cfg->tos);
512     }
513
514     if (tnl_cfg->dst_port) {
515         uint16_t dst_port = ntohs(tnl_cfg->dst_port);
516         const char *type = netdev_get_type(dev);
517
518         if ((!strcmp("vxlan", type) && dst_port != VXLAN_DST_PORT) ||
519             (!strcmp("lisp", type) && dst_port != LISP_DST_PORT)) {
520             smap_add_format(args, "dst_port", "%d", dst_port);
521         }
522     }
523
524     if (tnl_cfg->csum) {
525         smap_add(args, "csum", "true");
526     }
527
528     if (!tnl_cfg->dont_fragment) {
529         smap_add(args, "df_default", "false");
530     }
531
532     return 0;
533 }
534 \f
535 /* Code specific to patch ports. */
536
537 const char *
538 netdev_vport_patch_peer(const struct netdev *netdev)
539 {
540     return (netdev_vport_is_patch(netdev)
541             ? netdev_vport_cast(netdev)->peer
542             : NULL);
543 }
544
545 void
546 netdev_vport_inc_rx(const struct netdev *netdev,
547                           const struct dpif_flow_stats *stats)
548 {
549     if (is_vport_class(netdev_get_class(netdev))) {
550         struct netdev_vport *dev = netdev_vport_cast(netdev);
551         dev->stats.rx_packets += stats->n_packets;
552         dev->stats.rx_bytes += stats->n_bytes;
553     }
554 }
555
556 void
557 netdev_vport_inc_tx(const struct netdev *netdev,
558                     const struct dpif_flow_stats *stats)
559 {
560     if (is_vport_class(netdev_get_class(netdev))) {
561         struct netdev_vport *dev = netdev_vport_cast(netdev);
562         dev->stats.tx_packets += stats->n_packets;
563         dev->stats.tx_bytes += stats->n_bytes;
564     }
565 }
566
567 static int
568 get_patch_config(const struct netdev *dev_, struct smap *args)
569 {
570     struct netdev_vport *dev = netdev_vport_cast(dev_);
571
572     if (dev->peer) {
573         smap_add(args, "peer", dev->peer);
574     }
575     return 0;
576 }
577
578 static int
579 set_patch_config(struct netdev *dev_, const struct smap *args)
580 {
581     struct netdev_vport *dev = netdev_vport_cast(dev_);
582     const char *name = netdev_get_name(dev_);
583     const char *peer;
584
585     peer = smap_get(args, "peer");
586     if (!peer) {
587         VLOG_ERR("%s: patch type requires valid 'peer' argument", name);
588         return EINVAL;
589     }
590
591     if (smap_count(args) > 1) {
592         VLOG_ERR("%s: patch type takes only a 'peer' argument", name);
593         return EINVAL;
594     }
595
596     if (!strcmp(name, peer)) {
597         VLOG_ERR("%s: patch peer must not be self", name);
598         return EINVAL;
599     }
600
601     free(dev->peer);
602     dev->peer = xstrdup(peer);
603
604     return 0;
605 }
606
607 static int
608 get_stats(const struct netdev *netdev, struct netdev_stats *stats)
609 {
610     struct netdev_vport *dev = netdev_vport_cast(netdev);
611     memcpy(stats, &dev->stats, sizeof *stats);
612     return 0;
613 }
614 \f
615 #define VPORT_FUNCTIONS(GET_CONFIG, SET_CONFIG,             \
616                         GET_TUNNEL_CONFIG, GET_STATUS)      \
617     NULL,                                                   \
618     netdev_vport_run,                                       \
619     netdev_vport_wait,                                      \
620                                                             \
621     netdev_vport_create,                                    \
622     netdev_vport_destroy,                                   \
623     GET_CONFIG,                                             \
624     SET_CONFIG,                                             \
625     GET_TUNNEL_CONFIG,                                      \
626                                                             \
627     NULL,                       /* rx_open */               \
628                                                             \
629     NULL,                       /* send */                  \
630     NULL,                       /* send_wait */             \
631                                                             \
632     netdev_vport_set_etheraddr,                             \
633     netdev_vport_get_etheraddr,                             \
634     NULL,                       /* get_mtu */               \
635     NULL,                       /* set_mtu */               \
636     NULL,                       /* get_ifindex */           \
637     NULL,                       /* get_carrier */           \
638     NULL,                       /* get_carrier_resets */    \
639     NULL,                       /* get_miimon */            \
640     get_stats,                                              \
641     NULL,                       /* set_stats */             \
642                                                             \
643     NULL,                       /* get_features */          \
644     NULL,                       /* set_advertisements */    \
645                                                             \
646     NULL,                       /* set_policing */          \
647     NULL,                       /* get_qos_types */         \
648     NULL,                       /* get_qos_capabilities */  \
649     NULL,                       /* get_qos */               \
650     NULL,                       /* set_qos */               \
651     NULL,                       /* get_queue */             \
652     NULL,                       /* set_queue */             \
653     NULL,                       /* delete_queue */          \
654     NULL,                       /* get_queue_stats */       \
655     NULL,                       /* dump_queues */           \
656     NULL,                       /* dump_queue_stats */      \
657                                                             \
658     NULL,                       /* get_in4 */               \
659     NULL,                       /* set_in4 */               \
660     NULL,                       /* get_in6 */               \
661     NULL,                       /* add_router */            \
662     NULL,                       /* get_next_hop */          \
663     GET_STATUS,                                             \
664     NULL,                       /* arp_lookup */            \
665                                                             \
666     netdev_vport_update_flags,                              \
667                                                             \
668     netdev_vport_change_seq
669
670 #define TUNNEL_CLASS(NAME, DPIF_PORT)                       \
671     { DPIF_PORT,                                            \
672         { NAME, VPORT_FUNCTIONS(get_tunnel_config,          \
673                                 set_tunnel_config,          \
674                                 get_netdev_tunnel_config,   \
675                                 tunnel_get_status) }}
676
677 void
678 netdev_vport_tunnel_register(void)
679 {
680     static const struct vport_class vport_classes[] = {
681         TUNNEL_CLASS("gre", "gre_system"),
682         TUNNEL_CLASS("ipsec_gre", "gre_system"),
683         TUNNEL_CLASS("gre64", "gre64_system"),
684         TUNNEL_CLASS("ipsec_gre64", "gre64_system"),
685         TUNNEL_CLASS("vxlan", "vxlan_system"),
686         TUNNEL_CLASS("lisp", "lisp_system")
687     };
688     static bool inited;
689
690     int i;
691
692     if (!inited) {
693         inited = true;
694         for (i = 0; i < ARRAY_SIZE(vport_classes); i++) {
695             netdev_register_provider(&vport_classes[i].netdev_class);
696         }
697     }
698 }
699
700 void
701 netdev_vport_patch_register(void)
702 {
703     static const struct vport_class patch_class =
704         { NULL,
705             { "patch", VPORT_FUNCTIONS(get_patch_config,
706                                        set_patch_config,
707                                        NULL,
708                                        NULL) }};
709     netdev_register_provider(&patch_class.netdev_class);
710 }