From 751413f0503fc4fb97e9032c3ee25e30a8d53dae Mon Sep 17 00:00:00 2001 From: jfsimon1981 Date: Fri, 2 Dec 2022 17:40:46 +0100 Subject: [PATCH] MIP tuntap with dhcp added, enabled gitactions (#1890) Enable DHCP with MIP tests. Separate sources for high and low level stack tests. Fixed assertion. Added dhcpd.conf file. --- .github/workflows/test.yml | 8 +- Makefile | 5 + test/dhcpd.conf | 13 +++ test/mip_tap_test.c | 231 ++++++++++++++++++++++++++++++++++++ test/mip_test.c | 234 +++---------------------------------- test/setup_ga_network.sh | 44 ++++--- 6 files changed, 302 insertions(+), 233 deletions(-) create mode 100644 test/dhcpd.conf create mode 100644 test/mip_tap_test.c diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 663d97d1..21b745ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,12 +25,16 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - #- run: make s390 + - run: sudo apt-get install qemu binfmt-support qemu-user-static + - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - run: make s390 armhf: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - #- run: make armhf +# - run: sudo apt-get install qemu binfmt-support qemu-user-static +# - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes +# - run: make armhf linux2: runs-on: ubuntu-latest steps: diff --git a/Makefile b/Makefile index 078b9a0a..a4886df3 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,11 @@ tall: mg_prefix unamalgamated test mip_test arm examples vc98 vc17 vc22 mingw mi mip_test: test/mip_test.c mongoose.c mongoose.h Makefile $(CC) test/mip_test.c $(INCS) $(WARN) $(OPTS) $(C_WARN) $(ASAN) -o $@ ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@ + $(MAKE) mip_tap_test + +mip_tap_test: test/mip_tap_test.c mongoose.c mongoose.h Makefile + $(CC) test/mip_tap_test.c $(INCS) $(WARN) $(OPTS) $(C_WARN) $(ASAN) -o $@ + ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@ examples: @for X in $(EXAMPLES); do test -f $$X/Makefile || continue; $(MAKE) -C $$X example || exit 1; done diff --git a/test/dhcpd.conf b/test/dhcpd.conf new file mode 100644 index 00000000..ea2c9aad --- /dev/null +++ b/test/dhcpd.conf @@ -0,0 +1,13 @@ +# Network: 192.168.32.0/255.255.255.0 +# Domain name: mos.host +# Name servers: 1.1.1.1 and 8.8.8.8 +# Default router: 192.168.32.1 +# Addresses: 192.168.32.32 - 192.168.1.127 +# +option domain-name "mos.host"; +option domain-name-servers 1.1.1.1, 8.8.8.8; + +subnet 192.168.32.0 netmask 255.255.255.0 { + option routers 192.168.32.1; + range 192.168.32.32 192.168.32.127; +} \ No newline at end of file diff --git a/test/mip_tap_test.c b/test/mip_tap_test.c new file mode 100644 index 00000000..e3f1a659 --- /dev/null +++ b/test/mip_tap_test.c @@ -0,0 +1,231 @@ +#define MG_ENABLE_MIP 1 +#define MG_ENABLE_SOCKET 0 +#define MG_USING_DHCP 1 +#define MG_ENABLE_PACKED_FS 0 +#define MG_ENABLE_LINES 1 + +#include +#include +#include +#include +#include "mongoose.c" +#include "driver_mock.c" + +static int s_num_tests = 0; + +#define ASSERT(expr) \ + do { \ + s_num_tests++; \ + if (!(expr)) { \ + printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \ + abort(); \ + } \ + } while (0) + +// MIP TUNTAP driver +static size_t tap_rx(void *buf, size_t len, void *userdata) { + ssize_t received = read(*(int *) userdata, buf, len); + usleep(1); // This is to avoid 100% CPU + if (received < 0) return 0; + return (size_t) received; +} + +static size_t tap_tx(const void *buf, size_t len, void *userdata) { + ssize_t res = write(*(int *) userdata, buf, len); + if (res < 0) { + MG_ERROR(("tap_tx failed: %d", errno)); + return 0; + } + return (size_t) res; +} + +static bool tap_up(void *userdata) { + return userdata ? true : false; +} + +// HTTP fetches IOs +struct Post_reply { + char *post; // HTTP POST data + void *http_response; // Server response(s) + unsigned int http_responses_received; // Number responses received +}; + +char *fetch(struct mg_mgr *mgr, const char *url, const char *post_data); +static void f_http_fetch_query(struct mg_connection *c, int ev, void *ev_data, + void *fn_data); +int get_response_code(char *); // Returns HTTP status code from full char* msg + +static void f_http_fetch_query(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + static char *http_response = 0; + static bool http_response_allocated = + 0; // So that we will update out parameter + unsigned int http_responses_received = 0; + struct Post_reply *post_reply_l; + post_reply_l = (struct Post_reply *) fn_data; + + if (ev == MG_EV_CONNECT) { + mg_printf(c, post_reply_l->post); + } else if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + http_responses_received++; + if (!http_response_allocated) { + http_response = (char *) mg_strdup(hm->message).ptr; + http_response_allocated = 1; + } + if (http_responses_received > 0) { + post_reply_l->http_response = http_response; + post_reply_l->http_responses_received = http_responses_received; + } + } +} + +// Fetch utility returns message from fetch(..., URL, POST) +char *fetch(struct mg_mgr *mgr, const char *url, const char *fn_data) { + struct Post_reply post_reply; + { + post_reply.post = (char *) fn_data; + post_reply.http_response = 0; + post_reply.http_responses_received = 0; + } + struct mg_connection *conn; + conn = mg_http_connect(mgr, url, f_http_fetch_query, &post_reply); + ASSERT(conn != NULL); // Assertion on initialisation + for (int i = 0; i < 500 && !post_reply.http_responses_received; i++) { + mg_mgr_poll(mgr, 100); + usleep(10000); // 10 ms. Slow down poll loop to ensure packets transit + } + if (mgr->conns != 0) { + conn->is_closing = 1; + mg_mgr_poll(mgr, 0); + } + mg_mgr_poll(mgr, 0); + if (!post_reply.http_responses_received) + return 0; + else + return (char *) post_reply.http_response; +} + +// Returns server's HTTP response code +int get_response_code(char *http_msg_raw) { + int http_status = 0; + struct mg_http_message http_msg_parsed; + if (mg_http_parse(http_msg_raw, strlen(http_msg_raw), &http_msg_parsed)) { + http_status = mg_http_status(&http_msg_parsed); + } else { + printf("Error: mg_http_parse()\n"); + ASSERT(http_status != 0); // Couldn't parse. + } + return http_status; +} + +static void test_http_fetch(void) { + // Setup interface + const char *iface = "tap0"; // Network iface + const char *mac = "00:00:01:02:03:78"; // MAC address + int fd = open("/dev/net/tun", O_RDWR); // Open network interface + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, iface, IFNAMSIZ); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) { + MG_ERROR(("Failed to setup TAP interface: %s", ifr.ifr_name)); + abort(); // return EXIT_FAILURE; + } + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // Non-blocking mode + + MG_INFO(("Opened TAP interface: %s", iface)); + + // Events + struct mg_mgr mgr; // Event manager + mg_mgr_init(&mgr); // Initialise event manager + + // MIP driver + struct mip_driver driver; + memset(&driver, 0, sizeof(driver)); + + driver.tx = tap_tx; + driver.up = tap_up; + driver.rx = tap_rx; + + struct mip_if mif; + memset(&mif, 0, sizeof(mif)); + + mif.driver = &driver; + mif.driver_data = &fd; + +#if MG_USING_DHCP == 1 + mif.use_dhcp = true; // DHCP +#else + mif.use_dhcp = false; // Static IP + mif.ip = 0x0220a8c0; // 192.168.32.2 // Triggering a network failure + mif.mask = 0x00ffffff; // 255.255.255.0 + mif.gw = 0x0120a8c0; // 192.168.32.1 +#endif + + sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mif.mac[0], &mif.mac[1], + &mif.mac[2], &mif.mac[3], &mif.mac[4], &mif.mac[5]); + + mip_init(&mgr, &mif); + MG_INFO(("Init done, starting main loop")); + + // Stack initialization, Network configuration (DHCP lease, ...) + { +#if MG_USING_DHCP == 0 + MG_INFO(("MIF configuration: Static IP")); + ASSERT(mif.ip != 0); // Check we have a satic IP assigned + mg_mgr_poll(&mgr, 100); // For initialisation +#else + MG_INFO(("MIF configuration: DHCP")); + MG_INFO(("Opened TAP interface: %s", iface)); + ASSERT(!mif.ip); // Check we are set for DHCP + int pc = 500; // Timout on DHCP lease 500 ~ approx 5s (typical delay <1s) + while (((pc--) > 0) && !mif.ip) { + mg_mgr_poll(&mgr, 100); + usleep(10000); // 10 ms + } + if (!mif.ip) MG_ERROR(("No ip assigned (DHCP lease may have failed).\n")); + ASSERT(mif.ip); // We have an IP (lease or static) +#endif + } + + // Simple HTTP fetch + { + char *http_feedback = (char *) ""; + const bool ipv6 = 0; + if (ipv6) { + http_feedback = fetch(&mgr, "ipv6.google.com", + "GET/ HTTP/1.0\r\nHost: ipv6.google.com\r\n\r\n"); + } else { + http_feedback = + fetch(&mgr, "http://cesanta.com", + "GET //robots.txt HTTP/1.0\r\nHost: cesanta.com\r\n\r\n"); + } + + ASSERT(http_feedback != NULL && + *http_feedback != '\0'); // HTTP response received ? + + int http_status = get_response_code(http_feedback); + // printf("Server response HTTP status code: %d\n",http_status); + ASSERT(http_status != 0); + ASSERT(http_status == 301); // OK: Permanently moved (HTTP->HTTPS redirect) + + if (http_feedback) { + free(http_feedback); + http_feedback = 0; + } + } + + // Clear + mg_mgr_free(&mgr); + mip_free(&mif); // Release after mg_mgr + ASSERT(mgr.conns == NULL); // Deconstruction OK + close(fd); +} + +int main(void) { + test_http_fetch(); + printf("SUCCESS. Total tests: %d\n", s_num_tests); + return 0; +} diff --git a/test/mip_test.c b/test/mip_test.c index 9be1581c..5dab6bfe 100644 --- a/test/mip_test.c +++ b/test/mip_test.c @@ -4,13 +4,8 @@ #define MG_ENABLE_PACKED_FS 0 #include -#include -#include -#include -#include #include "mongoose.c" - - #include "driver_mock.c" +#include "driver_mock.c" static int s_num_tests = 0; @@ -23,8 +18,6 @@ static int s_num_tests = 0; } \ } while (0) - - static void test_queue(void) { static uint8_t buf[sizeof(size_t) + sizeof(uint16_t) + 3]; // fit 1 element but not 2 @@ -32,27 +25,27 @@ static void test_queue(void) { static struct queue q = {buf, sizeof(buf), 0, 0}; // Write to an empty queue, and read back - assert(q_avail(&q) == 0); - assert(q_write(&q, &val, sizeof(val)) == true); - assert(q_avail(&q) == sizeof(val)); - assert(q.head > q.tail); + ASSERT(q_avail(&q) == 0); + ASSERT(q_write(&q, &val, sizeof(val)) == true); + ASSERT(q_avail(&q) == sizeof(val)); + ASSERT(q.head > q.tail); // Only one element may fit - assert(q_write(&q, &val, sizeof(val)) == false); + ASSERT(q_write(&q, &val, sizeof(val)) == false); val = 0; - assert(q_read(&q, &val) == sizeof(val)); - assert(val == 1234); - assert(q_avail(&q) == 0); + ASSERT(q_read(&q, &val) == sizeof(val)); + ASSERT(val == 1234); + ASSERT(q_avail(&q) == 0); // Second write - wrap over the buffer boundary - assert(q_write(&q, &val, sizeof(val)) == true); - assert(q_avail(&q) == sizeof(val)); - assert(q.head < q.tail); + ASSERT(q_write(&q, &val, sizeof(val)) == true); + ASSERT(q_avail(&q) == sizeof(val)); + ASSERT(q.head < q.tail); // Only one element may fit - assert(q_write(&q, &val, sizeof(val)) == false); + ASSERT(q_write(&q, &val, sizeof(val)) == false); val = 0; - assert(q_read(&q, &val) == sizeof(val)); - assert(val == 1234); - assert(q_avail(&q) == 0); + ASSERT(q_read(&q, &val) == sizeof(val)); + ASSERT(val == 1234); + ASSERT(q_avail(&q) == 0); } static void test_statechange(void) { @@ -66,200 +59,6 @@ static void test_statechange(void) { onstatechange(&iface); } -// MIP TUNTAP driver -static size_t tap_rx(void *buf, size_t len, void *userdata) { - ssize_t received = read(*(int *) userdata, buf, len); - usleep(1); // This is to avoid 100% CPU - if (received < 0) return 0; - return (size_t) received; -} - -static size_t tap_tx(const void *buf, size_t len, void *userdata) { - ssize_t res = write(*(int *) userdata, buf, len); - if (res < 0) { - MG_ERROR(("tap_tx failed: %d", errno)); - return 0; - } - return (size_t) res; -} - -static bool tap_up(void *userdata) { - return userdata ? true : false; -} - -// HTTP fetches IOs -struct Post_reply { - char* post; // HTTP POST data - void* http_response; // Server response(s) - unsigned int http_responses_received; // Number responses received -}; - -char *fetch(struct mg_mgr *mgr, const char *url, const char *post_data); -static void f_http_fetch_query(struct mg_connection *c, int ev, void *ev_data, void *fn_data); -int get_response_code(char *); // Returns HTTP status code from full char* msg - -static void f_http_fetch_query(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { - static char* http_response = 0; - static bool http_response_allocated = 0; // So that we will update out parameter - unsigned int http_responses_received = 0; - struct Post_reply *post_reply_l; - post_reply_l = (struct Post_reply*)fn_data; - - if (ev == MG_EV_CONNECT) { - mg_printf(c, post_reply_l->post); - } else if (ev == MG_EV_HTTP_MSG) { - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - http_responses_received++; - if (!http_response_allocated) { - http_response = (char*)mg_strdup(hm->message).ptr; - http_response_allocated = 1; - } - if (http_responses_received > 0) { - post_reply_l->http_response = http_response; - post_reply_l->http_responses_received = http_responses_received; - } - } -} - -// Fetch utility returns message from fetch(..., URL, POST) -char *fetch(struct mg_mgr *mgr, const char *url, const char *fn_data) { - struct Post_reply post_reply; - { - post_reply.post=(char*)fn_data; - post_reply.http_response=0; - post_reply.http_responses_received=0; - } - struct mg_connection *conn; - conn = mg_http_connect(mgr, url, f_http_fetch_query, &post_reply); - ASSERT(conn != NULL); // Assertion on initialisation - for (int i = 0; i < 500 && !post_reply.http_responses_received; i++) { - mg_mgr_poll(mgr, 100); - usleep(10000); // 10 ms. Slow down poll loop to ensure packets transit - } - conn->is_closing = 1; - mg_mgr_poll(mgr, 0); - if (!post_reply.http_responses_received) - return 0; - else - return (char*)post_reply.http_response; -} - -// Returns server's HTTP response code -int get_response_code(char * http_msg_raw) { - int http_status = 0; - struct mg_http_message http_msg_parsed; - if (mg_http_parse(http_msg_raw, strlen(http_msg_raw), &http_msg_parsed)) { - http_status = mg_http_status(&http_msg_parsed); - } else { - printf("Error: mg_http_parse()\n"); - ASSERT(http_status != 0); // Couldn't parse. - } - return http_status; -} - -static void test_http_fetch(void) { - // Setup interface - const char *iface = "tap0"; // Network iface - const char *mac = "00:00:01:02:03:78"; // MAC address - int fd = open("/dev/net/tun", O_RDWR); // Open network interface - - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, iface, IFNAMSIZ); - ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) { - MG_ERROR(("Failed to setup TAP interface: %s", ifr.ifr_name)); - abort(); // return EXIT_FAILURE; - } - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // Non-blocking mode - - MG_INFO(("Opened TAP interface: %s", iface)); - - // Events - struct mg_mgr mgr; // Event manager - mg_mgr_init(&mgr); // Initialise event manager - - // MIP driver - - // Zero init fields required (C/C++ style diverge) - #ifndef __cplusplus - struct mip_driver driver = {.tx = tap_tx, .up = tap_up, .rx = tap_rx}; - // DHCP - // struct mip_if mif = {.use_dhcp = true, .driver = &driver, .driver_data = &fd}; - // Static - // 192.168.32.2/24 gw 192.168.32.1 - struct mip_if mif = {.use_dhcp = false, \ - .ip=0x0220a8c0 , .mask=0x00ffffff, .gw=0x0120a8c0, \ - .driver = &driver, .driver_data = &fd}; - - #else - struct mip_driver driver {}; - driver.tx = tap_tx; - driver.up = tap_up; - driver.rx = tap_rx; - struct mip_if mif {}; -// mif.use_dhcp = true; // DHCP - mif.use_dhcp = false; // Static IP - mif.ip = 0x0220a8c0; // 192.168.32.2 - mif.mask = 0x00ffffff; // 255.255.255.0 - mif.gw = 0x0120a8c0; // 192.168.32.1 - mif.driver = &driver; - mif.driver_data = &fd; - #endif - - sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mif.mac[0], &mif.mac[1], &mif.mac[2], - &mif.mac[3], &mif.mac[4], &mif.mac[5]); - - mip_init(&mgr, &mif); - MG_INFO(("Init done, starting main loop")); - - // Stack initialization, Network configuration (DHCP lease, ...) - { - if (mif.ip) MG_INFO(("MIF configuration: Static IP")); - else MG_INFO(("MIF configuration: DHCP")); - MG_INFO(("Opened TAP interface: %s", iface)); - // ASSERT(!mif.ip); // Check we are set for DHCP - int pc = 500; // Timout on DHCP lease 500 ~ approx 5s (typical delay <1s) - while (((pc--)>0) /*& !mif.ip*/) { - mg_mgr_poll(&mgr, 100); - usleep(10000); // 10 ms - } - if (!mif.ip) printf("No ip assigned (DHCP lease may have failed).\n"); - ASSERT(mif.ip); // We have an IP (lease or static) - } - - // Simple HTTP fetch - { - char* http_feedback = (char*)""; - const bool ipv6 = 0; - if (ipv6) { - http_feedback = fetch (&mgr, "ipv6.google.com",\ - "GET/ HTTP/1.0\r\nHost: ipv6.google.com\r\n\r\n"); - } else { - http_feedback = fetch (&mgr, "http://cesanta.com",\ - "GET //robots.txt HTTP/1.0\r\nHost: cesanta.com\r\n\r\n"); - } - - ASSERT(*http_feedback != '\0'); // Received HTTP response ? - - int http_status = get_response_code(http_feedback); - // printf("Server response HTTP status code: %d\n",http_status); - ASSERT(http_status != 0); - ASSERT(http_status == 301); // OK: Permanently moved (HTTP->HTTPS redirect) - - if (http_feedback) { - free(http_feedback); - http_feedback = 0; - } - } - - // Clear - mip_free(&mif); - mg_mgr_free(&mgr); - ASSERT(mgr.conns == NULL); // Deconstruction OK - close(fd); -} - static void ph(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_POLL) ++(*(int *) fn_data); (void) c, (void) ev_data; @@ -283,7 +82,6 @@ static void test_poll(void) { int main(void) { test_queue(); test_statechange(); - test_http_fetch(); test_poll(); printf("SUCCESS. Total tests: %d\n", s_num_tests); return 0; diff --git a/test/setup_ga_network.sh b/test/setup_ga_network.sh index 74c5ea2e..8364a2c4 100755 --- a/test/setup_ga_network.sh +++ b/test/setup_ga_network.sh @@ -1,4 +1,11 @@ -#!/bin/bash +#!/bin/sh +BRIDGE=mg_bridge0 +BRIDGE_BROADCAST=192.168.32.255 +BRIDGE_IP=192.168.32.1 +BRIDGE_IP_MASK=192.168.32.0/24 +BRIDGE_MASK=255.255.255.0 +PHY=eth0 +TAP=tap0 # see our network configuration echo "Network configuration:" @@ -8,25 +15,24 @@ timeout 1s bridge link timeout 1s bridge fdb echo -echo "Network configuration script: Bridge" -BRIDGE=mg_bridge0 -BRIDGE_IP=192.168.32.1 -BRIDGE_MASK=255.255.255.0 -BRIDGE_IP_MASK=192.168.32.0/24 -BRIDGE_BROADCAST=192.168.32.255 -PHY=eth0 -TAP=tap0 +# Package installation +echo "Package installation" +sudo apt-get -y install isc-dhcp-server net-tools +# sudo apt-get -y install build-essential sshpassecho "Network configuration script: Bridge" +echo +echo "Network configuration script: TAP" sudo ip link add $BRIDGE type bridge # Create brige sudo ifconfig $BRIDGE $BRIDGE_IP netmask $BRIDGE_MASK up -echo "Network configuration script: TAP" +echo -echo "Create $TAP" +echo "Create $TAP attached to $BRIDGE" sudo ip tuntap add dev $TAP mode tap # Create tuntap sudo ip link set $TAP master $BRIDGE # Link tap-bridge sudo ip link set $TAP up -echo "Network configuration script: NAT" +echo +echo "Network configuration script: NAT" sudo iptables -A FORWARD -d $BRIDGE_IP_MASK -o $BRIDGE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT sudo iptables -A FORWARD -s $BRIDGE_IP_MASK -i $BRIDGE -j ACCEPT sudo iptables -A FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT @@ -38,6 +44,18 @@ sudo iptables -t nat -A POSTROUTING -s $BRIDGE_IP_MASK ! -d $BRIDGE_IP_MASK -p t sudo iptables -t nat -A POSTROUTING -s $BRIDGE_IP_MASK ! -d $BRIDGE_IP_MASK -p udp -j MASQUERADE --to-ports 1024-65535 sudo iptables -t nat -A POSTROUTING -s $BRIDGE_IP_MASK ! -d $BRIDGE_IP_MASK -j MASQUERADE echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward +echo + +# Setup DHCP server +echo "Network configuration script: DHCP server" +echo "Serving from $BRIDGE_IP" +echo "dhcpd.conf:" +cat test/dhcpd.conf +echo +sudo cp test/dhcpd.conf /etc/dhcp/dhcpd.conf +sudo chmod a+w /var/lib/dhcp/* +sudo dhcpd mg_bridge0 & +echo # Do we have connectivity ? echo "Check connectivity:" @@ -52,4 +70,4 @@ echo "Done:" timeout 1s ifconfig timeout 1s sudo route -n timeout 1s bridge fdb -timeout 1s bridge link \ No newline at end of file +timeout 1s bridge link