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