1 /*
2 * Copyright (c) 2018 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /**
8 * @file
9 *
10 * TAP Ethernet driver for the native_sim board. This is meant for network
11 * connectivity between the host and Zephyr.
12 *
13 * Note this driver is divided in two files. This one, built in the embedded code context,
14 * with whichever libC is used in that context, and eth_native_tap_adapt.c built with the host
15 * libC.
16 */
17
18 #define LOG_MODULE_NAME eth_tap
19 #define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL
20
21 #include <zephyr/logging/log.h>
22 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
23
24 #include <stdio.h>
25
26 #include <zephyr/kernel.h>
27 #include <stdbool.h>
28 #include <errno.h>
29 #include <stddef.h>
30 #include <cmdline.h>
31 #include <posix_native_task.h>
32
33 #include <zephyr/net/net_pkt.h>
34 #include <zephyr/net/net_core.h>
35 #include <zephyr/net/net_if.h>
36 #include <zephyr/net/ethernet.h>
37 #include <ethernet/eth_stats.h>
38
39 #include <zephyr/drivers/ptp_clock.h>
40 #include <zephyr/net/gptp.h>
41 #include <zephyr/net/lldp.h>
42
43 #include "eth_native_tap_priv.h"
44 #include "nsi_host_trampolines.h"
45 #include "eth.h"
46
47 #define NET_BUF_TIMEOUT K_MSEC(100)
48
49 #if defined(CONFIG_NET_VLAN)
50 #define ETH_HDR_LEN sizeof(struct net_eth_vlan_hdr)
51 #else
52 #define ETH_HDR_LEN sizeof(struct net_eth_hdr)
53 #endif
54
55 struct eth_context {
56 uint8_t recv[NET_ETH_MTU + ETH_HDR_LEN];
57 uint8_t send[NET_ETH_MTU + ETH_HDR_LEN];
58 uint8_t mac_addr[6];
59 struct net_linkaddr ll_addr;
60 struct net_if *iface;
61 const char *if_name;
62 k_tid_t rx_thread;
63 struct z_thread_stack_element *rx_stack;
64 size_t rx_stack_size;
65 int dev_fd;
66 bool init_done;
67 bool status;
68 bool promisc_mode;
69
70 #if defined(CONFIG_NET_STATISTICS_ETHERNET)
71 struct net_stats_eth stats;
72 #endif
73 #if defined(CONFIG_ETH_NATIVE_TAP_PTP_CLOCK)
74 const struct device *ptp_clock;
75 #endif
76 };
77
78 static const char *if_name_cmd_opt;
79 #ifdef CONFIG_NET_IPV4
80 static const char *ipv4_addr_cmd_opt;
81 static const char *ipv4_nm_cmd_opt;
82 static const char *ipv4_gw_cmd_opt;
83 #endif
84
85
86 #define DEFINE_RX_THREAD(x, _) \
87 K_KERNEL_STACK_DEFINE(rx_thread_stack_##x, \
88 CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE);\
89 static struct k_thread rx_thread_data_##x
90
91 LISTIFY(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT, DEFINE_RX_THREAD, (;), _);
92
93 #if defined(CONFIG_NET_GPTP)
need_timestamping(struct gptp_hdr * hdr)94 static bool need_timestamping(struct gptp_hdr *hdr)
95 {
96 switch (hdr->message_type) {
97 case GPTP_SYNC_MESSAGE:
98 case GPTP_PATH_DELAY_RESP_MESSAGE:
99 return true;
100 default:
101 return false;
102 }
103 }
104
check_gptp_msg(struct net_if * iface,struct net_pkt * pkt,bool is_tx)105 static struct gptp_hdr *check_gptp_msg(struct net_if *iface,
106 struct net_pkt *pkt,
107 bool is_tx)
108 {
109 uint8_t *msg_start = net_pkt_data(pkt);
110 struct gptp_hdr *gptp_hdr;
111 int eth_hlen;
112 struct net_eth_hdr *hdr;
113
114 hdr = (struct net_eth_hdr *)msg_start;
115 if (ntohs(hdr->type) != NET_ETH_PTYPE_PTP) {
116 return NULL;
117 }
118
119 eth_hlen = sizeof(struct net_eth_hdr);
120
121 /* In TX, the first net_buf contains the Ethernet header
122 * and the actual gPTP header is in the second net_buf.
123 * In RX, the Ethernet header + other headers are in the
124 * first net_buf.
125 */
126 if (is_tx) {
127 if (pkt->frags->frags == NULL) {
128 return false;
129 }
130
131 gptp_hdr = (struct gptp_hdr *)pkt->frags->frags->data;
132 } else {
133 gptp_hdr = (struct gptp_hdr *)(pkt->frags->data + eth_hlen);
134 }
135
136 return gptp_hdr;
137 }
138
update_pkt_priority(struct gptp_hdr * hdr,struct net_pkt * pkt)139 static void update_pkt_priority(struct gptp_hdr *hdr, struct net_pkt *pkt)
140 {
141 if (GPTP_IS_EVENT_MSG(hdr->message_type)) {
142 net_pkt_set_priority(pkt, NET_PRIORITY_CA);
143 } else {
144 net_pkt_set_priority(pkt, NET_PRIORITY_IC);
145 }
146 }
147
update_gptp(struct net_if * iface,struct net_pkt * pkt,bool send)148 static void update_gptp(struct net_if *iface, struct net_pkt *pkt,
149 bool send)
150 {
151 struct net_ptp_time timestamp;
152 struct gptp_hdr *hdr;
153 int ret;
154
155 ret = eth_clock_gettime(×tamp.second, ×tamp.nanosecond);
156 if (ret < 0) {
157 return;
158 }
159
160 net_pkt_set_timestamp(pkt, ×tamp);
161
162 hdr = check_gptp_msg(iface, pkt, send);
163 if (!hdr) {
164 return;
165 }
166
167 if (send) {
168 ret = need_timestamping(hdr);
169 if (ret) {
170 net_if_add_tx_timestamp(pkt);
171 }
172 } else {
173 update_pkt_priority(hdr, pkt);
174 }
175 }
176 #else
177 #define update_gptp(iface, pkt, send)
178 #endif /* CONFIG_NET_GPTP */
179
eth_send(const struct device * dev,struct net_pkt * pkt)180 static int eth_send(const struct device *dev, struct net_pkt *pkt)
181 {
182 struct eth_context *ctx = dev->data;
183 int count = net_pkt_get_len(pkt);
184 int ret;
185
186 ret = net_pkt_read(pkt, ctx->send, count);
187 if (ret) {
188 return ret;
189 }
190
191 update_gptp(net_pkt_iface(pkt), pkt, true);
192
193 LOG_DBG("Send pkt %p len %d", pkt, count);
194
195 ret = nsi_host_write(ctx->dev_fd, ctx->send, count);
196 if (ret < 0) {
197 LOG_DBG("Cannot send pkt %p (%d)", pkt, ret);
198 }
199
200 return ret < 0 ? ret : 0;
201 }
202
eth_get_mac(struct eth_context * ctx)203 static struct net_linkaddr *eth_get_mac(struct eth_context *ctx)
204 {
205 (void)net_linkaddr_set(&ctx->ll_addr, ctx->mac_addr,
206 sizeof(ctx->mac_addr));
207
208 return &ctx->ll_addr;
209 }
210
prepare_pkt(struct eth_context * ctx,int count,int * status)211 static struct net_pkt *prepare_pkt(struct eth_context *ctx,
212 int count, int *status)
213 {
214 struct net_pkt *pkt;
215
216 pkt = net_pkt_rx_alloc_with_buffer(ctx->iface, count,
217 AF_UNSPEC, 0, NET_BUF_TIMEOUT);
218 if (!pkt) {
219 *status = -ENOMEM;
220 return NULL;
221 }
222
223 if (net_pkt_write(pkt, ctx->recv, count)) {
224 net_pkt_unref(pkt);
225 *status = -ENOBUFS;
226 return NULL;
227 }
228
229 *status = 0;
230
231 LOG_DBG("Recv pkt %p len %d", pkt, count);
232
233 return pkt;
234 }
235
read_data(struct eth_context * ctx,int fd)236 static int read_data(struct eth_context *ctx, int fd)
237 {
238 struct net_if *iface = ctx->iface;
239 struct net_pkt *pkt = NULL;
240 int status;
241 int count;
242
243 count = nsi_host_read(fd, ctx->recv, sizeof(ctx->recv));
244 if (count <= 0) {
245 return 0;
246 }
247
248 pkt = prepare_pkt(ctx, count, &status);
249 if (!pkt) {
250 return status;
251 }
252
253 update_gptp(iface, pkt, false);
254
255 if (net_recv_data(iface, pkt) < 0) {
256 net_pkt_unref(pkt);
257 }
258
259 return 0;
260 }
261
eth_rx(void * p1,void * p2,void * p3)262 static void eth_rx(void *p1, void *p2, void *p3)
263 {
264 ARG_UNUSED(p2);
265 ARG_UNUSED(p3);
266
267 struct eth_context *ctx = p1;
268 LOG_DBG("Starting ZETH RX thread");
269
270 while (1) {
271 if (net_if_is_up(ctx->iface)) {
272 while (!eth_wait_data(ctx->dev_fd)) {
273 read_data(ctx, ctx->dev_fd);
274 k_yield();
275 }
276 }
277
278 k_sleep(K_MSEC(CONFIG_ETH_NATIVE_TAP_RX_TIMEOUT));
279 }
280 }
281
282 #if defined(CONFIG_THREAD_MAX_NAME_LEN)
283 #define THREAD_MAX_NAME_LEN CONFIG_THREAD_MAX_NAME_LEN
284 #else
285 #define THREAD_MAX_NAME_LEN 1
286 #endif
287
create_rx_handler(struct eth_context * ctx)288 static void create_rx_handler(struct eth_context *ctx)
289 {
290 k_thread_create(ctx->rx_thread,
291 ctx->rx_stack,
292 ctx->rx_stack_size,
293 eth_rx,
294 ctx, NULL, NULL, K_PRIO_COOP(14),
295 0, K_NO_WAIT);
296
297 if (IS_ENABLED(CONFIG_THREAD_NAME)) {
298 char name[THREAD_MAX_NAME_LEN];
299
300 snprintk(name, sizeof(name), "eth_native_tap_rx-%s",
301 ctx->if_name);
302 k_thread_name_set(ctx->rx_thread, name);
303 }
304 }
305
eth_iface_init(struct net_if * iface)306 static void eth_iface_init(struct net_if *iface)
307 {
308 struct eth_context *ctx = net_if_get_device(iface)->data;
309 struct net_linkaddr *ll_addr = eth_get_mac(ctx);
310 #ifdef CONFIG_NET_IPV4
311 struct in_addr addr, netmask;
312 #endif
313
314 ctx->iface = iface;
315
316 ethernet_init(iface);
317
318 if (ctx->init_done) {
319 return;
320 }
321
322 net_lldp_set_lldpdu(iface);
323
324 ctx->init_done = true;
325
326 #if defined(CONFIG_ETH_NATIVE_TAP_RANDOM_MAC)
327 /* 00-00-5E-00-53-xx Documentation RFC 7042 */
328 gen_random_mac(ctx->mac_addr, 0x00, 0x00, 0x5E);
329
330 ctx->mac_addr[3] = 0x00;
331 ctx->mac_addr[4] = 0x53;
332
333 /* The TUN/TAP setup script will by default set the MAC address of host
334 * interface to 00:00:5E:00:53:FF so do not allow that.
335 */
336 if (ctx->mac_addr[5] == 0xff) {
337 ctx->mac_addr[5] = 0x01;
338 }
339 #else
340 /* Difficult to configure MAC addresses any sane way if we have more
341 * than one network interface.
342 */
343 BUILD_ASSERT(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT == 1,
344 "Cannot have static MAC if interface count > 1");
345
346 if (CONFIG_ETH_NATIVE_TAP_MAC_ADDR[0] != 0) {
347 if (net_bytes_from_str(ctx->mac_addr, sizeof(ctx->mac_addr),
348 CONFIG_ETH_NATIVE_TAP_MAC_ADDR) < 0) {
349 LOG_ERR("Invalid MAC address %s",
350 CONFIG_ETH_NATIVE_TAP_MAC_ADDR);
351 }
352 }
353 #endif
354
355 /* If we have only one network interface, then use the name
356 * defined in the Kconfig directly. This way there is no need to
357 * change the documentation etc. and break things.
358 */
359 if (CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT == 1) {
360 ctx->if_name = CONFIG_ETH_NATIVE_TAP_DRV_NAME;
361 }
362
363 if (if_name_cmd_opt != NULL) {
364 ctx->if_name = if_name_cmd_opt;
365 }
366
367 LOG_DBG("Interface %p using \"%s\"", iface, ctx->if_name);
368
369 net_if_set_link_addr(iface, ll_addr->addr, ll_addr->len,
370 NET_LINK_ETHERNET);
371
372 #ifdef CONFIG_NET_IPV4
373 if (ipv4_addr_cmd_opt != NULL) {
374 if (net_addr_pton(AF_INET, ipv4_addr_cmd_opt, &addr) == 0) {
375 net_if_ipv4_addr_add(iface, &addr, NET_ADDR_MANUAL, 0);
376
377 if (ipv4_nm_cmd_opt != NULL) {
378 if (net_addr_pton(AF_INET, ipv4_nm_cmd_opt, &netmask) == 0) {
379 net_if_ipv4_set_netmask_by_addr(iface, &addr, &netmask);
380 } else {
381 NET_ERR("Invalid netmask: %s", ipv4_nm_cmd_opt);
382 }
383 }
384 } else {
385 NET_ERR("Invalid address: %s", ipv4_addr_cmd_opt);
386 }
387 }
388
389 if (ipv4_gw_cmd_opt != NULL) {
390 if (net_addr_pton(AF_INET, ipv4_gw_cmd_opt, &addr) == 0) {
391 net_if_ipv4_set_gw(iface, &addr);
392 } else {
393 NET_ERR("Invalid gateway: %s", ipv4_gw_cmd_opt);
394 }
395 }
396 #endif
397
398 ctx->dev_fd = eth_iface_create(CONFIG_ETH_NATIVE_POSIX_DEV_NAME, ctx->if_name, false);
399 if (ctx->dev_fd < 0) {
400 LOG_ERR("Cannot create %s (%d/%s)", ctx->if_name, ctx->dev_fd,
401 strerror(-ctx->dev_fd));
402 } else {
403 /* Create a thread that will handle incoming data from host */
404 create_rx_handler(ctx);
405 }
406 }
407
eth_native_tap_get_capabilities(const struct device * dev)408 static enum ethernet_hw_caps eth_native_tap_get_capabilities(const struct device *dev)
409 {
410 ARG_UNUSED(dev);
411
412 return ETHERNET_TXTIME
413 #if defined(CONFIG_NET_VLAN)
414 | ETHERNET_HW_VLAN
415 #endif
416 #if defined(CONFIG_ETH_NATIVE_TAP_VLAN_TAG_STRIP)
417 | ETHERNET_HW_VLAN_TAG_STRIP
418 #endif
419 #if defined(CONFIG_ETH_NATIVE_TAP_PTP_CLOCK)
420 | ETHERNET_PTP
421 #endif
422 #if defined(CONFIG_NET_PROMISCUOUS_MODE)
423 | ETHERNET_PROMISC_MODE
424 #endif
425 #if defined(CONFIG_NET_LLDP)
426 | ETHERNET_LLDP
427 #endif
428 ;
429 }
430
431 #if defined(CONFIG_ETH_NATIVE_TAP_PTP_CLOCK)
eth_get_ptp_clock(const struct device * dev)432 static const struct device *eth_get_ptp_clock(const struct device *dev)
433 {
434 struct eth_context *context = dev->data;
435
436 return context->ptp_clock;
437 }
438 #endif
439
440 #if defined(CONFIG_NET_STATISTICS_ETHERNET)
get_stats(const struct device * dev)441 static struct net_stats_eth *get_stats(const struct device *dev)
442 {
443 struct eth_context *context = dev->data;
444
445 return &(context->stats);
446 }
447 #endif
448
set_config(const struct device * dev,enum ethernet_config_type type,const struct ethernet_config * config)449 static int set_config(const struct device *dev,
450 enum ethernet_config_type type,
451 const struct ethernet_config *config)
452 {
453 int ret = 0;
454
455 if (IS_ENABLED(CONFIG_NET_PROMISCUOUS_MODE) &&
456 type == ETHERNET_CONFIG_TYPE_PROMISC_MODE) {
457 struct eth_context *context = dev->data;
458
459 if (config->promisc_mode) {
460 if (context->promisc_mode) {
461 return -EALREADY;
462 }
463
464 context->promisc_mode = true;
465 } else {
466 if (!context->promisc_mode) {
467 return -EALREADY;
468 }
469
470 context->promisc_mode = false;
471 }
472
473 ret = eth_promisc_mode(context->if_name,
474 context->promisc_mode);
475 } else if (type == ETHERNET_CONFIG_TYPE_MAC_ADDRESS) {
476 struct eth_context *context = dev->data;
477
478 memcpy(context->mac_addr, config->mac_address.addr,
479 sizeof(context->mac_addr));
480 }
481
482 return ret;
483 }
484
485 #if defined(CONFIG_NET_VLAN)
vlan_setup(const struct device * dev,struct net_if * iface,uint16_t tag,bool enable)486 static int vlan_setup(const struct device *dev, struct net_if *iface,
487 uint16_t tag, bool enable)
488 {
489 if (enable) {
490 net_lldp_set_lldpdu(iface);
491 } else {
492 net_lldp_unset_lldpdu(iface);
493 }
494
495 return 0;
496 }
497 #endif /* CONFIG_NET_VLAN */
498
499 static const struct ethernet_api eth_if_api = {
500 .iface_api.init = eth_iface_init,
501
502 .get_capabilities = eth_native_tap_get_capabilities,
503 .set_config = set_config,
504 .send = eth_send,
505
506 #if defined(CONFIG_NET_VLAN)
507 .vlan_setup = vlan_setup,
508 #endif
509 #if defined(CONFIG_NET_STATISTICS_ETHERNET)
510 .get_stats = get_stats,
511 #endif
512 #if defined(CONFIG_ETH_NATIVE_TAP_PTP_CLOCK)
513 .get_ptp_clock = eth_get_ptp_clock,
514 #endif
515 };
516
517 #define DEFINE_ETH_DEV_DATA(x, _) \
518 static struct eth_context eth_context_data_##x = { \
519 .if_name = CONFIG_ETH_NATIVE_TAP_DRV_NAME #x, \
520 .rx_thread = &rx_thread_data_##x, \
521 .rx_stack = rx_thread_stack_##x, \
522 .rx_stack_size = K_KERNEL_STACK_SIZEOF(rx_thread_stack_##x), \
523 }
524
525 LISTIFY(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT, DEFINE_ETH_DEV_DATA, (;), _);
526
527 #define DEFINE_ETH_DEVICE(x, _) \
528 ETH_NET_DEVICE_INIT(eth_native_tap_##x, \
529 CONFIG_ETH_NATIVE_TAP_DRV_NAME #x, \
530 NULL, NULL, ð_context_data_##x, NULL, \
531 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
532 ð_if_api, \
533 NET_ETH_MTU)
534
535 LISTIFY(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT, DEFINE_ETH_DEVICE, (;), _);
536
537 #if defined(CONFIG_ETH_NATIVE_TAP_PTP_CLOCK)
538
539 #if defined(CONFIG_NET_GPTP)
540 BUILD_ASSERT( \
541 CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT == CONFIG_NET_GPTP_NUM_PORTS, \
542 "Number of network interfaces must match gPTP port count");
543 #endif
544
545 struct ptp_context {
546 struct eth_context *eth_context;
547 };
548
549 #define DEFINE_PTP_DEV_DATA(x, _) \
550 static struct ptp_context ptp_context_##x
551
552 LISTIFY(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT, DEFINE_PTP_DEV_DATA, (;), _);
553
ptp_clock_set_native_tap(const struct device * clk,struct net_ptp_time * tm)554 static int ptp_clock_set_native_tap(const struct device *clk, struct net_ptp_time *tm)
555 {
556 ARG_UNUSED(clk);
557 ARG_UNUSED(tm);
558
559 /* We cannot set the host device time so this function
560 * does nothing.
561 */
562
563 return 0;
564 }
565
ptp_clock_get_native_tap(const struct device * clk,struct net_ptp_time * tm)566 static int ptp_clock_get_native_tap(const struct device *clk, struct net_ptp_time *tm)
567 {
568 ARG_UNUSED(clk);
569
570 return eth_clock_gettime(&tm->second, &tm->nanosecond);
571 }
572
ptp_clock_adjust_native_tap(const struct device * clk,int increment)573 static int ptp_clock_adjust_native_tap(const struct device *clk, int increment)
574 {
575 ARG_UNUSED(clk);
576 ARG_UNUSED(increment);
577
578 /* We cannot adjust the host device time so this function
579 * does nothing.
580 */
581
582 return 0;
583 }
584
ptp_clock_rate_adjust_native_tap(const struct device * clk,double ratio)585 static int ptp_clock_rate_adjust_native_tap(const struct device *clk, double ratio)
586 {
587 ARG_UNUSED(clk);
588 ARG_UNUSED(ratio);
589
590 /* We cannot adjust the host device time so this function
591 * does nothing.
592 */
593
594 return 0;
595 }
596
597 static DEVICE_API(ptp_clock, api) = {
598 .set = ptp_clock_set_native_tap,
599 .get = ptp_clock_get_native_tap,
600 .adjust = ptp_clock_adjust_native_tap,
601 .rate_adjust = ptp_clock_rate_adjust_native_tap,
602 };
603
604 #define PTP_INIT_FUNC(x, _) \
605 static int ptp_init_##x(const struct device *port) \
606 { \
607 const struct device *const eth_dev = DEVICE_GET(eth_native_tap_##x); \
608 struct eth_context *context = eth_dev->data; \
609 struct ptp_context *ptp_context = port->data; \
610 \
611 context->ptp_clock = port; \
612 ptp_context->eth_context = context; \
613 \
614 return 0; \
615 }
616
617 LISTIFY(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT, PTP_INIT_FUNC, (), _)
618
619 #define DEFINE_PTP_DEVICE(x, _) \
620 DEVICE_DEFINE(eth_native_tap_ptp_clock_##x, \
621 PTP_CLOCK_NAME "_" #x, \
622 ptp_init_##x, \
623 NULL, \
624 &ptp_context_##x, \
625 NULL, \
626 POST_KERNEL, \
627 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
628 &api)
629
630 LISTIFY(CONFIG_ETH_NATIVE_TAP_INTERFACE_COUNT, DEFINE_PTP_DEVICE, (;), _);
631
632 #endif /* CONFIG_ETH_NATIVE_TAP_PTP_CLOCK */
633
add_native_tap_options(void)634 static void add_native_tap_options(void)
635 {
636 static struct args_struct_t eth_native_tap_options[] = {
637 {
638 .is_mandatory = false,
639 .option = "eth-if",
640 .name = "name",
641 .type = 's',
642 .dest = (void *)&if_name_cmd_opt,
643 .descript = "Name of the eth interface to use",
644 },
645 #ifdef CONFIG_NET_IPV4
646 {
647 .is_mandatory = false,
648 .option = "ipv4-addr",
649 .name = "ipv4",
650 .type = 's',
651 .dest = (void *)&ipv4_addr_cmd_opt,
652 .descript = "IPv4 address",
653 },
654 {
655 .is_mandatory = false,
656 .option = "ipv4-gw",
657 .name = "ipv4",
658 .type = 's',
659 .dest = (void *)&ipv4_gw_cmd_opt,
660 .descript = "IPv4 gateway",
661 },
662 {
663 .is_mandatory = false,
664 .option = "ipv4-nm",
665 .name = "ipv4",
666 .type = 's',
667 .dest = (void *)&ipv4_nm_cmd_opt,
668 .descript = "IPv4 netmask",
669 },
670 #endif
671 ARG_TABLE_ENDMARKER,
672 };
673
674 native_add_command_line_opts(eth_native_tap_options);
675 }
676
677 NATIVE_TASK(add_native_tap_options, PRE_BOOT_1, 10);
678