Changeset View
Changeset View
Standalone View
Standalone View
tests/sys/netgraph/bridge.c
- This file was added.
| /* | |||||
| * SPDX-License-Identifier: BSD-3-Clause | |||||
| * | |||||
| * Copyright 2021 Lutz Donnerhacke | |||||
| * | |||||
| * Redistribution and use in source and binary forms, with or without | |||||
| * modification, are permitted provided that the following conditions | |||||
| * are met: | |||||
| * | |||||
| * 1. Redistributions of source code must retain the above copyright | |||||
| * notice, this list of conditions and the following disclaimer. | |||||
| * 2. Redistributions in binary form must reproduce the above | |||||
| * copyright notice, this list of conditions and the following | |||||
| * disclaimer in the documentation and/or other materials provided | |||||
| * with the distribution. | |||||
| * 3. Neither the name of the copyright holder nor the names of its | |||||
| * contributors may be used to endorse or promote products derived | |||||
| * from this software without specific prior written permission. | |||||
| * | |||||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |||||
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
| * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS | |||||
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||||
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |||||
| * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |||||
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR | |||||
| * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF | |||||
| * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| * SUCH DAMAGE. | |||||
| */ | |||||
| #include <atf-c.h> | |||||
| #include <errno.h> | |||||
| #include <stdio.h> | |||||
| #include <net/ethernet.h> | |||||
| #include <netinet/in.h> | |||||
| #include <netinet/ip.h> | |||||
| #include <netinet/ip6.h> | |||||
| #include "util.h" | |||||
| static void get_data(void *data, size_t len, void *ctx); | |||||
| struct frame4 { | |||||
| struct ether_header eh; | |||||
| struct ip ip; | |||||
| char data[64]; | |||||
| }; | |||||
| struct frame6 { | |||||
| struct ether_header eh; | |||||
| struct ip6_hdr ip; | |||||
| char data[64]; | |||||
| }; | |||||
| static struct frame4 msg4 = { | |||||
| .ip.ip_v = 4, | |||||
| .ip.ip_hl = 5, | |||||
| .ip.ip_ttl = 1, | |||||
| .ip.ip_p = 254, | |||||
| .ip.ip_src = { htonl(0x0a00dead) }, | |||||
| .ip.ip_dst = { htonl(0x0a00beef) }, | |||||
| .ip.ip_len = 32, | |||||
| .eh.ether_type = ETHERTYPE_IP, | |||||
| .eh.ether_shost = { 2,4,6 }, | |||||
| .eh.ether_dhost = { 2,4,6 }, | |||||
| }; | |||||
| ATF_TC_WITH_CLEANUP(basic); | |||||
| ATF_TC_HEAD(basic, conf) | |||||
| { | |||||
| atf_tc_set_md_var(conf, "require.user", "root"); | |||||
| } | |||||
| ATF_TC_BODY(basic, dummy) | |||||
| { | |||||
| int received; | |||||
| ng_init(); | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| ng_errors(FAIL); | |||||
| ng_mkpeer(".", "a", "bridge", "link0"); | |||||
| ng_name("a", "bridge"); | |||||
| ng_connect(".", "b", "bridge:", "link1"); | |||||
| ng_connect(".", "c", "bridge:", "link2"); | |||||
| /* do not bounce back */ | |||||
| ng_register_data("a", get_data); | |||||
| received = 0; | |||||
| msg4.eh.ether_shost[5] = 1; | |||||
| ng_send_data("a", &msg4, sizeof(msg4)); | |||||
| ng_handle_events(50, &received); | |||||
| ATF_CHECK(received == 0); | |||||
| /* send to others */ | |||||
| ng_register_data("b", get_data); | |||||
| ng_register_data("c", get_data); | |||||
| received = 0; | |||||
| msg4.eh.ether_shost[5] = 1; | |||||
| ng_send_data("a", &msg4, sizeof(msg4)); | |||||
| ng_handle_events(50, &received); | |||||
| ATF_CHECK(received == 2); | |||||
| received = 0; | |||||
| msg4.eh.ether_shost[5] = 2; | |||||
| ng_send_data("b", &msg4, sizeof(msg4)); | |||||
| ng_handle_events(50, &received); | |||||
| ATF_CHECK(received == 2); | |||||
| received = 0; | |||||
| msg4.eh.ether_shost[5] = 3; | |||||
| ng_send_data("c", &msg4, sizeof(msg4)); | |||||
| ng_handle_events(50, &received); | |||||
| ATF_CHECK(received == 2); | |||||
| /* remove a link */ | |||||
| ng_rmhook(".", "b"); | |||||
| received = 0; | |||||
| msg4.eh.ether_shost[5] = 1; | |||||
| ng_send_data("a", &msg4, sizeof(msg4)); | |||||
| ng_handle_events(50, &received); | |||||
| ATF_CHECK(received == 1); | |||||
| } | |||||
| ATF_TC_CLEANUP(basic, dummy) | |||||
| { | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| } | |||||
| ATF_TC_WITH_CLEANUP(persistence); | |||||
| ATF_TC_HEAD(persistence, conf) | |||||
| { | |||||
| atf_tc_set_md_var(conf, "require.user", "root"); | |||||
| } | |||||
| ATF_TC_BODY(persistence, dummy) | |||||
| { | |||||
| ng_init(); | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| ng_errors(FAIL); | |||||
| ng_mkpeer(".", "a", "bridge", "link0"); | |||||
| ng_name("a", "bridge"); | |||||
| ng_send_msg("bridge:", "setpersistent"); | |||||
| ng_rmhook(".", "a"); | |||||
| ng_shutdown("bridge:"); | |||||
| } | |||||
| ATF_TC_CLEANUP(persistence, dummy) | |||||
| { | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| } | |||||
| ATF_TC_WITH_CLEANUP(loop); | |||||
| ATF_TC_HEAD(loop, conf) | |||||
| { | |||||
| atf_tc_set_md_var(conf, "require.user", "root"); | |||||
| } | |||||
| ATF_TC_BODY(loop, dummy) | |||||
| { | |||||
| int received, i; | |||||
| ng_init(); | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge1:"); | |||||
| ng_shutdown("bridge2:"); | |||||
| ng_errors(FAIL); | |||||
| ng_mkpeer(".", "a", "bridge", "link0"); | |||||
| ng_name("a", "bridge1"); | |||||
| ng_mkpeer(".", "b", "bridge", "link1"); | |||||
| ng_name("b", "bridge2"); | |||||
| ng_register_data("a", get_data); | |||||
| ng_register_data("b", get_data); | |||||
| /* | |||||
| * Open loop | |||||
| * | |||||
| * /-- bridge1 | |||||
| * . < | | |||||
| * \-- bridge2 | |||||
| */ | |||||
| ng_connect("bridge1:", "link11", "bridge2:", "link11"); | |||||
| received = 0; | |||||
| msg4.eh.ether_shost[5] = 1; | |||||
| ng_send_data("a", &msg4, sizeof(msg4)); | |||||
| ng_handle_events(50, &received); | |||||
| ATF_CHECK(received == 1); | |||||
| /* | |||||
| * Closed loop, DANGEROUS! | |||||
| * | |||||
| * /-- bridge1 -\ | |||||
| * . < | | | |||||
| * \-- bridge2 -/ | |||||
| */ | |||||
| ng_connect("bridge1:", "link12", "bridge2:", "link12"); | |||||
| received = 0; | |||||
| msg4.eh.ether_shost[5] = 1; | |||||
| ng_errors(PASS); | |||||
| ng_send_data("a", &msg4, sizeof(msg4)); | |||||
| ATF_CHECK_ERRNO(ELOOP, errno != 0); /* loop might be detected */ | |||||
| ng_errors(FAIL); | |||||
| for(i = 0; i < 10; i++) /* don't run forever */ | |||||
| if (!ng_handle_event(50, &received)) | |||||
| break; | |||||
| ATF_CHECK(received == 1); | |||||
| } | |||||
| ATF_TC_CLEANUP(loop, dummy) | |||||
| { | |||||
| printf("CALLED: %s\n", __FUNCTION__); | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge1:"); | |||||
| ng_shutdown("bridge2:"); | |||||
| } | |||||
| ATF_TC_WITH_CLEANUP(many_unicasts); | |||||
| ATF_TC_HEAD(many_unicasts, conf) | |||||
| { | |||||
| atf_tc_set_md_var(conf, "require.user", "root"); | |||||
| } | |||||
| ATF_TC_BODY(many_unicasts, dummy) | |||||
| { | |||||
| int received, i; | |||||
| const int HOOKS = 1000; | |||||
| ng_init(); | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| ng_errors(FAIL); | |||||
| ng_mkpeer(".", "a", "bridge", "link0"); | |||||
| ng_name("a", "bridge"); | |||||
| /* learn MAC */ | |||||
| msg4.eh.ether_shost[3] = 0xff; | |||||
| ng_send_data("a", &msg4, sizeof(msg4)); | |||||
| /* use learned MAC */ | |||||
| msg4.eh.ether_shost[3] = 0; | |||||
| msg4.eh.ether_dhost[3] = 0xff; | |||||
| ng_register_data("a", get_data); | |||||
| received = 0; | |||||
| for (i = 1; i <= HOOKS; i++) { | |||||
| char hook[20]; | |||||
| snprintf(hook, sizeof(hook), "link%d", i); | |||||
| ng_connect(".", hook, "bridge:", hook); | |||||
| msg4.eh.ether_shost[4] = i >> 8; | |||||
| msg4.eh.ether_shost[5] = i & 0xff; | |||||
| ng_errors(PASS); | |||||
| ng_send_data(hook, &msg4, sizeof(msg4)); | |||||
| ng_errors(FAIL); | |||||
| if (errno != 0) | |||||
| break; | |||||
| ng_handle_events(50, &received); | |||||
| } | |||||
| ATF_CHECK(received == HOOKS); | |||||
| } | |||||
| ATF_TC_CLEANUP(many_unicasts, dummy) | |||||
| { | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| } | |||||
| ATF_TC_WITH_CLEANUP(many_broadcasts); | |||||
| ATF_TC_HEAD(many_broadcasts, conf) | |||||
| { | |||||
| atf_tc_set_md_var(conf, "require.user", "root"); | |||||
| } | |||||
| ATF_TC_BODY(many_broadcasts, dummy) | |||||
| { | |||||
| int received, i; | |||||
| const int HOOKS = 1000; | |||||
| ng_init(); | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| ng_errors(FAIL); | |||||
| ng_mkpeer(".", "a", "bridge", "link0"); | |||||
| ng_name("a", "bridge"); | |||||
| /* learn MAC */ | |||||
| msg4.eh.ether_shost[3] = 0xff; | |||||
| ng_send_data("a", &msg4, sizeof(msg4)); | |||||
| /* use learned MAC */ | |||||
| msg4.eh.ether_shost[3] = 0; | |||||
| memset(msg4.eh.ether_dhost, 0xff, sizeof(msg4.eh.ether_dhost)); | |||||
| ng_register_data("a", get_data); | |||||
| received = 0; | |||||
| for (i = 1; i <= HOOKS; i++) { | |||||
| char hook[20]; | |||||
| snprintf(hook, sizeof(hook), "link%d", i); | |||||
| ng_connect(".", hook, "bridge:", hook); | |||||
| msg4.eh.ether_shost[4] = i >> 8; | |||||
| msg4.eh.ether_shost[5] = i & 0xff; | |||||
| ng_errors(PASS); | |||||
| ng_send_data(hook, &msg4, sizeof(msg4)); | |||||
| ng_errors(FAIL); | |||||
| if (errno != 0) | |||||
| break; | |||||
| ng_handle_events(50, &received); | |||||
| } | |||||
| ATF_CHECK(received > 100); | |||||
| atf_tc_expect_fail("Implementation limitation (%d)", i); | |||||
| ATF_CHECK(received == HOOKS); | |||||
| atf_tc_expect_pass(); | |||||
| } | |||||
| ATF_TC_CLEANUP(many_broadcasts, dummy) | |||||
| { | |||||
| ng_errors(PASS); | |||||
| ng_shutdown("bridge:"); | |||||
| } | |||||
| ATF_TP_ADD_TCS(bridge) | |||||
| { | |||||
| ATF_TP_ADD_TC(bridge, basic); | |||||
| ATF_TP_ADD_TC(bridge, loop); | |||||
| ATF_TP_ADD_TC(bridge, persistence); | |||||
| ATF_TP_ADD_TC(bridge, many_unicasts); | |||||
| ATF_TP_ADD_TC(bridge, many_broadcasts); | |||||
| return atf_no_error(); | |||||
| } | |||||
| static void | |||||
| get_data(void *data, size_t len, void *ctx) | |||||
| { | |||||
| int *cnt = ctx; | |||||
| (void)data; | |||||
| printf("Got %zu bytes of data.\n", len); | |||||
| (*cnt)++; | |||||
| } | |||||