diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f660bebb..cfd0d168 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -81,7 +81,6 @@ jobs: cat log test/health.awk < log > json scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json" - mip89: runs-on: ubuntu-latest strategy: @@ -94,6 +93,29 @@ jobs: - uses: actions/checkout@v5 with: { fetch-depth: 2 } - run: make -C test ${{ matrix.target }} IPV6=1 + + l2: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + cc: [gcc, clang, g++, clang++] + target: [eth_test, ppp_test] + ipv6: [0, 1] + name: ${{ matrix.target }} CC=${{ matrix.cc }} IPV6=${{ matrix.ipv6 }} + steps: + - uses: actions/checkout@v5 + with: { fetch-depth: 2 } + - uses: webfactory/ssh-agent@v0.10.0 + with: + ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }} + - run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} CC=${{ matrix.cc }} > log + - if: success() || failure() + run: | + cat log + test/health.awk < log > json + scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ipv6 }}_$(date +"%Y%m%d").json" + s390: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/on_demand.yml b/.github/workflows/on_demand.yml index b89f5f24..f0003ac0 100644 --- a/.github/workflows/on_demand.yml +++ b/.github/workflows/on_demand.yml @@ -4,35 +4,22 @@ on: env: IPV6: 0 jobs: - mip: + l2: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - cc: [gcc] - target: [mip_tap_test] - ssl: ["", BUILTIN, MBEDTLS, OPENSSL, WOLFSSL] - # #3226: built-in TCP is currently not working with WolfSSL (builds fine) - exclude: - - ssl: WOLFSSL - target: mip_tap_test - name: ${{ matrix.target }} CC=${{ matrix.cc }} SSL=${{ matrix.ssl }} - env: - CC: ${{ matrix.cc }} - SSL: ${{ matrix.ssl }} - TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT # -DMQTT_HOST_NAME=\"server\" + cc: [gcc, clang, g++, clang++] + target: [eth_test, ppp_test] + ipv6: [0, 1] + name: ${{ matrix.target }} CC=${{ matrix.cc }} IPV6=${{ matrix.ipv6 }} steps: - uses: actions/checkout@v5 with: { fetch-depth: 2 } - uses: webfactory/ssh-agent@v0.10.0 with: ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }} - - run: if [ "${{ matrix.target }}" == "mip_tap_test" ]; then source ./test/setup_ga_network.sh ; export IPV6=1; sed -i -e "s/127.0.0.1//" test/mosquitto.conf; cat test/mosquitto.conf; ./test/setup_mqtt_server.sh; else export IPV6=1 ; fi && sudo apt -y update ; sudo apt -y install libmbedtls-dev libwolfssl-dev && make -C test ${{ matrix.target }} > log - - if: success() || failure() - run: | - cat log - test/health.awk < log > json - scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json" + - run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} CC=${{ matrix.cc }} > log # macos: diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index a08c0910..a94673f8 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -16,8 +16,13 @@ jobs: fail-fast: false matrix: cc: [gcc, clang++] - target: [test, mip_test] + target: [test, mip_test, eth_test, ppp_test] ssl: ["", BUILTIN] + exclude: + - target: eth_test + ssl: BUILTIN + - target: ppp_test + ssl: BUILTIN name: linux ${{ matrix.target }} CC=${{ matrix.cc }} SSL=${{ matrix.ssl }} env: CC: ${{ matrix.cc }} diff --git a/mongoose.c b/mongoose.c index 70d24351..b9af66e0 100644 --- a/mongoose.c +++ b/mongoose.c @@ -5249,7 +5249,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, } else { // We do, check 802.1Q tag struct qtag *qtag = (struct qtag *) ð->type; if (qtag->tpid != mg_htons(0x8100)) return false; // Untagged frame - if (mg_ntohs(VLAN_ID(qtag->tci)) != VLAN_ID(d->vlan_id)) + if (VLAN_ID(mg_ntohs(qtag->tci)) != VLAN_ID(d->vlan_id)) return false; // Not our VLAN type = MG_LOAD_BE16(qtag + 1); } @@ -5270,7 +5270,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) { if (type == eth_types[i]) break; } - if (i == sizeof(eth_types)) { + if (i == sizeof(eth_types) / sizeof(eth_types[0])) { MG_DEBUG(("Unknown eth type %x", type)); if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(raw->buf, raw->len >= 32 ? 32 : raw->len); diff --git a/src/l2_eth.c b/src/l2_eth.c index ee99a746..86052349 100644 --- a/src/l2_eth.c +++ b/src/l2_eth.c @@ -109,7 +109,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, } else { // We do, check 802.1Q tag struct qtag *qtag = (struct qtag *) ð->type; if (qtag->tpid != mg_htons(0x8100)) return false; // Untagged frame - if (mg_ntohs(VLAN_ID(qtag->tci)) != VLAN_ID(d->vlan_id)) + if (VLAN_ID(mg_ntohs(qtag->tci)) != VLAN_ID(d->vlan_id)) return false; // Not our VLAN type = MG_LOAD_BE16(qtag + 1); } @@ -130,7 +130,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) { if (type == eth_types[i]) break; } - if (i == sizeof(eth_types)) { + if (i == sizeof(eth_types) / sizeof(eth_types[0])) { MG_DEBUG(("Unknown eth type %x", type)); if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(raw->buf, raw->len >= 32 ? 32 : raw->len); diff --git a/test/Makefile b/test/Makefile index 12a133d9..fbefbd06 100644 --- a/test/Makefile +++ b/test/Makefile @@ -29,7 +29,7 @@ COMMON_CFLAGS ?= $(C_WARN) $(WARN) $(INCS) $(DEFS) -DMG_ENABLE_IPV6=$(IPV6) $(TF CFLAGS ?= $(OPTS) $(ASAN) $(COMMON_CFLAGS) VALGRIND_CFLAGS ?= $(OPTS) $(COMMON_CFLAGS) VALGRIND_RUN ?= valgrind --tool=memcheck --gen-suppressions=all --leak-check=full --show-leak-kinds=all --leak-resolution=high --track-origins=yes --error-exitcode=1 --exit-on-first-error=yes --fair-sched=yes -.PHONY: clean_tutorials tutorials mip_test test valgrind +.PHONY: clean_tutorials tutorials mip_test eth_test ppp_test test valgrind ifeq "$(findstring ++,$(CC))" "" # $(CC) does not end with ++, i.e. we're using C. Apply C flags @@ -81,12 +81,20 @@ endif all: $(MAKE) -C ../tutorials/http/http-server -tall: mg_prefix unamalgamated test valgrind fuzz fuzz_tls vc98 vc17 vc22 mingw mingw++ arm armhf riscv s390 tutorials tutorials_win tutorials_embedded mip_test mip_vc98 +tall: mg_prefix unamalgamated test valgrind fuzz fuzz_tls vc98 vc17 vc22 mingw mingw++ arm armhf riscv s390 tutorials tutorials_win tutorials_embedded mip_test eth_test ppp_test mip_vc98 mip_test: mip_test.c mongoose.c mongoose.h packed_fs.c Makefile $(CC) mip_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@ ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@ +eth_test: eth_test.c mongoose.c mongoose.h Makefile + $(CC) eth_test.c $(CFLAGS) $(LDFLAGS) -o $@ + ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@ + +ppp_test: ppp_test.c mongoose.c mongoose.h Makefile + $(CC) ppp_test.c $(CFLAGS) $(LDFLAGS) -o $@ + ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@ + mip_tap_test: mip_x_test.c mip_tap_test.c mongoose.c mongoose.h packed_fs.c Makefile tls_multirec/server $(CC) mip_tap_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@ ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@ diff --git a/test/eth_test.c b/test/eth_test.c new file mode 100644 index 00000000..99fcdd60 --- /dev/null +++ b/test/eth_test.c @@ -0,0 +1,275 @@ +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_TCPIP_DRIVER_INIT 0 + +#include "mongoose.c" + +static int s_num_tests = 0; +static bool s_error = false; + +#ifdef NO_ABORT +static int s_abort = 0; +#define ABORT() ++s_abort, s_error = true +#else +#ifdef NO_SLEEP_ABORT +#define ABORT() abort() +#else +#define ABORT() \ + sleep(2); /* 2s, GH print reason */ \ + abort(); +#endif +#endif + +#define ASSERT(expr) \ + do { \ + s_num_tests++; \ + if (!(expr)) { \ + printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \ + fflush(stdout); \ + ABORT(); \ + } \ + } while (0) + +static const uint8_t s_mac[6] = {2, 3, 4, 5, 6, 7}; +static const uint8_t s_src[6] = {8, 9, 10, 11, 12, 13}; + +static void reset_if(struct mg_tcpip_if *ifp) { + memset(ifp, 0, sizeof(*ifp)); + memcpy(ifp->mac, s_mac, sizeof(s_mac)); + ifp->l2type = MG_TCPIP_L2_ETH; +} + +static size_t mk_eth(uint8_t *buf, const uint8_t *dst, uint16_t type, + const uint8_t *payload, size_t len) { + struct eth *eth = (struct eth *) buf; + memcpy(eth->dst, dst, sizeof(eth->dst)); + memcpy(eth->src, s_src, sizeof(eth->src)); + eth->type = mg_htons(type); + memcpy(eth + 1, payload, len); + return sizeof(*eth) + len; +} + +static size_t mk_vlan(uint8_t *buf, const uint8_t *dst, uint16_t vlan, + uint16_t type, const uint8_t *payload, size_t len) { + struct eth *eth = (struct eth *) buf; + struct qtag *qtag = (struct qtag *) ð->type; + memcpy(eth->dst, dst, sizeof(eth->dst)); + memcpy(eth->src, s_src, sizeof(eth->src)); + qtag->tpid = mg_htons(0x8100); + qtag->tci = mg_htons(vlan); + MG_STORE_BE16(qtag + 1, type); + memcpy((uint8_t *) (qtag + 1) + sizeof(uint16_t), payload, len); + return sizeof(*eth) + sizeof(*qtag) + len; +} + +static size_t add_fcs(uint8_t *buf, size_t len) { + uint32_t crc = mg_crc32(0, (const char *) buf, len); + memcpy(buf + len, &crc, sizeof(crc)); + return len + sizeof(crc); +} + +static void test_plain_accept(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + mg_l2_init(&ifp); + MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize)); + ASSERT(ifp.l2mtu != 0); + ASSERT(ifp.framesize != 0); + len = mk_eth(frame, s_mac, 0x0800, payload, sizeof(payload)); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV4); + ASSERT(pay.len == sizeof(payload)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_plain_discard(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + char sentinel[] = "discard"; + struct mg_tcpip_if ifp; + struct mg_str raw, pay = mg_str_n(sentinel, sizeof(sentinel)); + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + mg_l2_init(&ifp); + len = mk_eth(frame, s_mac, 0x1234, payload, sizeof(payload)); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(proto == MG_TCPIP_L2PROTO_ARP); +} + +static void test_mac_check_accept(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + ifp.enable_mac_check = true; + mg_l2_init(&ifp); + len = mk_eth(frame, s_mac, 0x0800, payload, sizeof(payload)); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV4); + ASSERT(pay.len == sizeof(payload)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_mac_check_discard(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}, dst[6] = {1, 1, 1, 1, 1, 1}; + char sentinel[] = "discard"; + struct mg_tcpip_if ifp; + struct mg_str raw, pay = mg_str_n(sentinel, sizeof(sentinel)); + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + ifp.enable_mac_check = true; + mg_l2_init(&ifp); + len = mk_eth(frame, dst, 0x0800, payload, sizeof(payload)); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(proto == MG_TCPIP_L2PROTO_ARP); + ASSERT(pay.buf == sentinel); + ASSERT(pay.len == sizeof(sentinel)); +} + +static void test_fcs_check_accept(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + ifp.enable_fcs_check = true; + mg_l2_init(&ifp); + len = add_fcs(frame, mk_eth(frame, s_mac, 0x0800, payload, sizeof(payload))); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV4); + ASSERT(pay.len == sizeof(payload)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_fcs_check_discard(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + char sentinel[] = "discard"; + struct mg_tcpip_if ifp; + struct mg_str raw, pay = mg_str_n(sentinel, sizeof(sentinel)); + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + ifp.enable_fcs_check = true; + mg_l2_init(&ifp); + len = add_fcs(frame, mk_eth(frame, s_mac, 0x0800, payload, sizeof(payload))); + frame[len - 1] ^= 1; + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(proto == MG_TCPIP_L2PROTO_ARP); + ASSERT(pay.buf == sentinel); + ASSERT(pay.len == sizeof(sentinel)); +} + +static void test_fcs_check_disabled(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + mg_l2_init(&ifp); + len = add_fcs(frame, mk_eth(frame, s_mac, 0x0800, payload, sizeof(payload))); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV4); + ASSERT(pay.len == sizeof(payload) + sizeof(uint32_t)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_vlan_accept(void) { + uint8_t frame[64], payload[] = {5, 6, 7, 8}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + ifp.l2data.eth.vlan_id = 123; + mg_l2_init(&ifp); + MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize)); + ASSERT(ifp.l2mtu != 0); + ASSERT(ifp.framesize != 0); + len = mk_vlan(frame, s_mac, 123, 0x86dd, payload, sizeof(payload)); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV6); + ASSERT(pay.len == sizeof(payload)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_vlan_discard(void) { + uint8_t frame[64], payload[] = {5, 6, 7, 8}; + char sentinel[] = "discard"; + struct mg_tcpip_if ifp; + struct mg_str raw, pay = mg_str_n(sentinel, sizeof(sentinel)); + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + ifp.l2data.eth.vlan_id = 123; + mg_l2_init(&ifp); + len = mk_vlan(frame, s_mac, 124, 0x86dd, payload, sizeof(payload)); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(proto == MG_TCPIP_L2PROTO_ARP); + ASSERT(pay.buf == sentinel); + ASSERT(pay.len == sizeof(sentinel)); +} + +static void test_tagged_without_vlan_discard(void) { + uint8_t frame[64], payload[] = {5, 6, 7, 8}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + size_t len; + reset_if(&ifp); + mg_l2_init(&ifp); + len = mk_vlan(frame, s_mac, 123, 0x86dd, payload, sizeof(payload)); + raw = mg_str_n((char *) frame, len); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(proto == MG_TCPIP_L2PROTO_ARP); +} + +#define DASHBOARD(x) \ + printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true") + +int main(void) { + s_error = false; + test_plain_accept(); + test_plain_discard(); + DASHBOARD("eth"); + + s_error = false; + test_mac_check_accept(); + test_mac_check_discard(); + DASHBOARD("mac"); + + s_error = false; + test_fcs_check_accept(); + test_fcs_check_discard(); + test_fcs_check_disabled(); + DASHBOARD("fcs"); + + s_error = false; + test_vlan_accept(); + test_vlan_discard(); + test_tagged_without_vlan_discard(); + printf("HEALTH_DASHBOARD\t\"vlan\": %s\n", s_error ? "false" : "true"); + +#ifdef NO_ABORT + if (s_abort != 0) return EXIT_FAILURE; +#endif + + printf("SUCCESS. Total tests: %d\n", s_num_tests); + return EXIT_SUCCESS; +} diff --git a/test/ppp_test.c b/test/ppp_test.c new file mode 100644 index 00000000..8991c724 --- /dev/null +++ b/test/ppp_test.c @@ -0,0 +1,455 @@ +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_TCPIP_DRIVER_INIT 0 + +#include "mongoose.c" +#include "driver_mock.c" + +static int s_num_tests = 0; +static bool s_error = false; + +#ifdef NO_ABORT +static int s_abort = 0; +#define ABORT() ++s_abort, s_error = true +#else +#ifdef NO_SLEEP_ABORT +#define ABORT() abort() +#else +#define ABORT() \ + sleep(2); /* 2s, GH print reason */ \ + abort(); +#endif +#endif + +#define ASSERT(expr) \ + do { \ + s_num_tests++; \ + if (!(expr)) { \ + printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \ + fflush(stdout); \ + ABORT(); \ + } \ + } while (0) + +static const uint8_t s_mac[6] = {2, 3, 4, 5, 6, 7}; +static const uint8_t s_src[6] = {8, 9, 10, 11, 12, 13}; + +static uint8_t s_tx[2048]; + +static void init_if(struct mg_tcpip_if *ifp, enum mg_l2type type) { + memset(ifp, 0, sizeof(*ifp)); + memcpy(ifp->mac, s_mac, sizeof(s_mac)); + ifp->driver = &mg_tcpip_driver_mock; + ifp->l2type = type; + ifp->tx.buf = (char *) s_tx; + ifp->tx.len = sizeof(s_tx); + mg_l2_init(ifp); +} + +static size_t mk_ppp(uint8_t *buf, uint16_t proto, const void *payload, + size_t len) { + struct hdlc_ *hdlc = (struct hdlc_ *) buf; + struct ppp *ppp = (struct ppp *) (hdlc + 1); + hdlc->addr = MG_PPP_ADDR; + hdlc->ctrl = MG_PPP_CTRL; + ppp->proto = mg_htons(proto); + memcpy(ppp + 1, payload, len); + memset((uint8_t *) (ppp + 1) + len, 0, 2); + return sizeof(*hdlc) + sizeof(*ppp) + len + 2; +} + +static void *ppp_payload(uint8_t *frame) { + return (struct ppp *) ((struct hdlc_ *) frame + 1) + 1; +} + +static size_t mk_pppoe_disc(uint8_t *buf, uint8_t code, uint16_t id, + const void *payload, size_t len) { + struct eth *eth = (struct eth *) buf; + struct pppoe *pppoe = (struct pppoe *) (eth + 1); + memcpy(eth->dst, s_mac, sizeof(eth->dst)); + memcpy(eth->src, s_src, sizeof(eth->src)); + eth->type = mg_htons(0x8863); + pppoe->vertype = 0x11; + pppoe->code = code; + pppoe->id = mg_htons(id); + pppoe->len = mg_htons((uint16_t) len); + if (len > 0) memcpy(pppoe + 1, payload, len); + return sizeof(*eth) + sizeof(*pppoe) + len; +} + +static size_t mk_pppoe_sess(uint8_t *buf, uint16_t id, uint16_t proto, + const void *payload, size_t len) { + struct eth *eth = (struct eth *) buf; + struct pppoe *pppoe = (struct pppoe *) (eth + 1); + struct ppp *ppp = (struct ppp *) (pppoe + 1); + memcpy(eth->dst, s_mac, sizeof(eth->dst)); + memcpy(eth->src, s_src, sizeof(eth->src)); + eth->type = mg_htons(0x8864); + pppoe->vertype = 0x11; + pppoe->code = 0; + pppoe->id = mg_htons(id); + pppoe->len = mg_htons((uint16_t) (sizeof(*ppp) + len)); + ppp->proto = mg_htons(proto); + memcpy(ppp + 1, payload, len); + return sizeof(*eth) + sizeof(*pppoe) + sizeof(*ppp) + len; +} + +static void test_ppp_lcp_accept(void) { + uint8_t frame[64]; + struct mg_tcpip_if ifp; + struct lcp lcp = {MG_PPP_LCP_CFG_ACK, 1, mg_htons(sizeof(lcp))}; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = false; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_LCP, &lcp, sizeof(lcp))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_lcpup == true); +} + +static void test_ppp_lcp_discard(void) { + uint8_t frame[64], lcp[] = {MG_PPP_LCP_CFG_ACK, 1}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = false; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_LCP, lcp, sizeof(lcp))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_lcpup == false); +} + +static void test_ppp_ip_accept(void) { + uint8_t frame[64], payload[sizeof(struct ip)]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + memset(payload, 0, sizeof(payload)); + init_if(&ifp, MG_TCPIP_L2_PPP); + MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize)); + ASSERT(ifp.l2mtu != 0); + ASSERT(ifp.framesize != 0); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IP, payload, sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV4); + ASSERT(pay.len == sizeof(payload)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_ppp_ip_discard(void) { + uint8_t frame[64], payload[sizeof(struct ip)]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + memset(payload, 0, sizeof(payload)); + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = false; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IP, payload, sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); +} + +static void test_ppp_ipcp_accept(void) { + uint8_t frame[64], ipcp[] = {MG_PPP_IPCP_CFG_REQ, 1, 0, 10, + MG_PPP_IPCP_OPT_IPADDR, 6, 192, 0, 2, 1}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + uint32_t peer; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = true; + memcpy(&peer, ipcp + 6, sizeof(peer)); + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IPCP, ipcp, sizeof(ipcp))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(ifp.gw == peer); + ASSERT(((struct ipcp *) ppp_payload(frame))->code == MG_PPP_IPCP_CFG_ACK); + ASSERT(ifp.nsent == 2); +} + +static void test_ppp_ipcp_reject(void) { + uint8_t frame[64], ipcp[] = {MG_PPP_IPCP_CFG_REQ, 1, 0, 4}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IPCP, ipcp, sizeof(ipcp))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(ifp.gw == 0); + ASSERT(((struct ipcp *) ppp_payload(frame))->code == MG_PPP_IPCP_CFG_REJECT); + ASSERT(ifp.nsent == 3); +} + +#if MG_ENABLE_IPV6 +static void test_ppp_ipv6_accept(void) { + uint8_t frame[96], payload[sizeof(struct ip6)]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + memset(payload, 0, sizeof(payload)); + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IPV6, payload, sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV6); + ASSERT(pay.len == sizeof(payload)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_ppp_ipv6_discard(void) { + uint8_t frame[96], payload[sizeof(struct ip6)]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + memset(payload, 0, sizeof(payload)); + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = false; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IPV6, payload, sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); +} + +static void test_ppp_ipv6cp_accept(void) { + uint8_t frame[64], ipv6cp[] = {MG_PPP_IPV6CP_CFG_REQ, 1, 0, 14, + MG_PPP_IPV6CP_OPT_IFCID, 10, + 2, 3, 4, 5, 6, 7, 8, 9}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IPV6CP, ipv6cp, sizeof(ipv6cp))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(memcmp(&((struct mg_l2addr *) ifp.gwmac)->addr.ieee64, ipv6cp + 6, + 8) == 0); + ASSERT(((struct ipv6cp *) ppp_payload(frame))->code == + MG_PPP_IPV6CP_CFG_ACK); + ASSERT(ifp.nsent == 2); +} + +static void test_ppp_ipv6cp_reject(void) { + uint8_t frame[64], ipv6cp[] = {MG_PPP_IPV6CP_CFG_REQ, 1, 0, 14, + MG_PPP_IPV6CP_OPT_IFCID, 10, + 0, 0, 0, 0, 0, 0, 0, 0}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_ppp(frame, MG_PPP_PROTO_IPV6CP, ipv6cp, sizeof(ipv6cp))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(((struct ipv6cp *) ppp_payload(frame))->code == + MG_PPP_IPV6CP_CFG_REJECT); + ASSERT(ifp.nsent == 1); +} +#endif + +static void test_ppp_unknown_reject(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_ppp(frame, 0x1234, payload, sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(ifp.nsent == 1); +} + +static void test_ppp_unknown_discard(void) { + uint8_t frame[64], payload[] = {1, 2, 3, 4}; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPP); + s_lcpup = false; + raw = mg_str_n((char *) frame, + mk_ppp(frame, 0x1234, payload, sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(ifp.nsent == 0); +} + +static void test_pppoe_pado_accept(void) { + uint8_t frame[64]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPPoE); + MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize)); + ASSERT(ifp.l2mtu != 0); + ASSERT(ifp.framesize != 0); + s_state = MG_PPPoE_ST_DISC; + raw = mg_str_n((char *) frame, + mk_pppoe_disc(frame, MG_PPPoE_PADO, 0, NULL, 0)); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_state == MG_PPPoE_ST_REQ); + ASSERT(ifp.nsent == 1); +} + +static void test_pppoe_pado_discard(void) { + uint8_t frame[64]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPPoE); + s_state = MG_PPPoE_ST_REQ; + raw = mg_str_n((char *) frame, + mk_pppoe_disc(frame, MG_PPPoE_PADO, 0, NULL, 0)); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_state == MG_PPPoE_ST_REQ); + ASSERT(ifp.nsent == 0); +} + +static void test_pppoe_pads_accept(void) { + uint8_t frame[64]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPPoE); + s_state = MG_PPPoE_ST_REQ; + raw = mg_str_n((char *) frame, + mk_pppoe_disc(frame, MG_PPPoE_PADS, 0x1234, NULL, 0)); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_state == MG_PPPoE_ST_SESS); + ASSERT(s_id == mg_htons(0x1234)); +} + +static void test_pppoe_pads_discard(void) { + uint8_t frame[64]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPPoE); + s_state = MG_PPPoE_ST_DISC; + raw = mg_str_n((char *) frame, + mk_pppoe_disc(frame, MG_PPPoE_PADS, 0x1234, NULL, 0)); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_state == MG_PPPoE_ST_DISC); +} + +static void test_pppoe_padt_accept(void) { + uint8_t frame[64]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPPoE); + s_state = MG_PPPoE_ST_SESS; + s_id = mg_htons(0x1234); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_pppoe_disc(frame, MG_PPPoE_PADT, 0x1234, NULL, 0)); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_state == MG_PPPoE_ST_DISC); + ASSERT(s_id == 0); + ASSERT(s_lcpup == false); +} + +static void test_pppoe_padt_discard(void) { + uint8_t frame[64]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + init_if(&ifp, MG_TCPIP_L2_PPPoE); + s_state = MG_PPPoE_ST_SESS; + s_id = mg_htons(0x4321); + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_pppoe_disc(frame, MG_PPPoE_PADT, 0x1234, NULL, 0)); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); + ASSERT(s_state == MG_PPPoE_ST_SESS); + ASSERT(s_id == mg_htons(0x4321)); + ASSERT(s_lcpup == true); +} + +static void test_pppoe_session_accept(void) { + uint8_t frame[96], payload[sizeof(struct ip)]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + memset(payload, 0, sizeof(payload)); + init_if(&ifp, MG_TCPIP_L2_PPPoE); + s_state = MG_PPPoE_ST_SESS; + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_pppoe_sess(frame, 0x1234, MG_PPP_PROTO_IP, payload, + sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true); + ASSERT(proto == MG_TCPIP_L2PROTO_IPV4); + ASSERT(pay.len == sizeof(payload)); + ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0); +} + +static void test_pppoe_session_discard(void) { + uint8_t frame[96], payload[sizeof(struct ip)]; + struct mg_tcpip_if ifp; + struct mg_str raw, pay; + enum mg_l2proto proto = MG_TCPIP_L2PROTO_ARP; + memset(payload, 0, sizeof(payload)); + init_if(&ifp, MG_TCPIP_L2_PPPoE); + s_state = MG_PPPoE_ST_REQ; + s_lcpup = true; + raw = mg_str_n((char *) frame, + mk_pppoe_sess(frame, 0x1234, MG_PPP_PROTO_IP, payload, + sizeof(payload))); + ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false); +} + +#define DASHBOARD(x) \ + printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true") + +int main(void) { + s_error = false; + test_ppp_lcp_accept(); + test_ppp_lcp_discard(); + DASHBOARD("lcp"); + + s_error = false; + test_ppp_ipcp_accept(); + test_ppp_ipcp_reject(); + DASHBOARD("ipcp"); + +#if MG_ENABLE_IPV6 + s_error = false; + test_ppp_ipv6cp_accept(); + test_ppp_ipv6cp_reject(); + DASHBOARD("ipv6cp"); +#endif + + s_error = false; + test_ppp_ip_accept(); + test_ppp_ip_discard(); +#if MG_ENABLE_IPV6 + test_ppp_ipv6_accept(); + test_ppp_ipv6_discard(); +#endif + test_ppp_unknown_reject(); + test_ppp_unknown_discard(); + DASHBOARD("ppp"); + + s_error = false; + test_pppoe_pado_accept(); + test_pppoe_pado_discard(); + test_pppoe_pads_accept(); + test_pppoe_pads_discard(); + test_pppoe_padt_accept(); + test_pppoe_padt_discard(); + test_pppoe_session_accept(); + test_pppoe_session_discard(); + printf("HEALTH_DASHBOARD\t\"pppoe\": %s\n", s_error ? "false" : "true"); + +#ifdef NO_ABORT + if (s_abort != 0) return EXIT_FAILURE; +#endif + + printf("SUCCESS. Total tests: %d\n", s_num_tests); + return EXIT_SUCCESS; +}