diff --git a/ioctl/Makefile b/ioctl/Makefile index 04f7fc09c..6efc2a313 100644 --- a/ioctl/Makefile +++ b/ioctl/Makefile @@ -3,3 +3,4 @@ LOCAL_LDFLAGS := -lpthread LOCAL_LDFLAGS += -z stack-size=12288 $(eval $(call add_unity_test, test-ioctl)) +$(eval $(call add_unity_test, test-ioctl-special)) diff --git a/ioctl/test-ioctl-special.c b/ioctl/test-ioctl-special.c new file mode 100644 index 000000000..dd569ee58 --- /dev/null +++ b/ioctl/test-ioctl-special.c @@ -0,0 +1,286 @@ +/* + * Phoenix-RTOS + * + * Tests for special ioctls: + * - SIOCIFCONF + * - SIOCETHTOOL + * + * (special - passed structure has a pointer + * to arbitrary memory -> needs flattening + * in userspace) + * + * Copyright 2025 Phoenix Systems + * Author: Julian UziembÅ‚o + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "unity_fixture.h" + +#define ERR_MSG_LEN 64 + + +static int fd = -1; +static struct ifconf ifc; +static struct ifconf current_ifc; +static struct ifreq current_ifr; + + +static int get_ifconf(int fd, struct ifconf *ifc) +{ + int err = ioctl(fd, SIOCGIFCONF, ifc); + if (err < 0) { + return -errno; + } + + ifc->ifc_req = malloc(ifc->ifc_len); + if (ifc->ifc_req == NULL) { + return -errno; + } + + err = ioctl(fd, SIOCGIFCONF, ifc); + if (err < 0) { + return -errno; + } + + return 0; +} + + +/* returns: 0 or errno on success, -1 on fail */ +static int ethtool_ioctl(struct ifreq *ifr, void *ethtool_struct, uint32_t cmd, char *err_msg_buf) +{ + *((uint32_t *)ethtool_struct) = cmd; + ifr->ifr_data = (void *)ethtool_struct; + if (ioctl(fd, SIOCETHTOOL, ifr) < 0) { + if (errno == ENXIO) { + snprintf(err_msg_buf, ERR_MSG_LEN, "Interface '%.*s', not found", IFNAMSIZ, ifr->ifr_name); + return -1; + } + if (errno == EOPNOTSUPP) { + return EOPNOTSUPP; + } + snprintf(err_msg_buf, ERR_MSG_LEN, "Interface '%.*s': %s", IFNAMSIZ, ifr->ifr_name, strerror(errno)); + return -1; + } + return 0; +} + + +TEST_GROUP(test_ioctl_special); + + +TEST_SETUP(test_ioctl_special) +{ +} + + +TEST_TEAR_DOWN(test_ioctl_special) +{ + if (current_ifc.ifc_req != NULL) { + free(current_ifc.ifc_req); + memset(¤t_ifc, 0, sizeof(current_ifc)); + } +} + + +TEST(test_ioctl_special, ifconf) +{ + int res = get_ifconf(fd, ¤t_ifc); + TEST_ASSERT_EQUAL_MESSAGE(0, res, strerror(errno)); +} + + +TEST(test_ioctl_special, ifconf_not_enough_space) +{ + struct ifreq ifr = { 0 }; + struct ifconf ifconf = { + .ifc_req = &ifr, + .ifc_len = sizeof(ifr), + }; + + int res = ioctl(fd, SIOCGIFCONF, &ifconf); + TEST_ASSERT_EQUAL_MESSAGE(0, res, strerror(errno)); + TEST_ASSERT_EQUAL(sizeof(ifr), ifconf.ifc_len); + + /* ifr_name should be 3 characters in lwip. + if net stack is ever changed - this should change too */ + TEST_ASSERT_EQUAL(3, strnlen(ifconf.ifc_req->ifr_name, IFNAMSIZ)); +} + + +TEST(test_ioctl_special, ethtool_gset) +{ + struct ethtool_cmd cmd = { 0 }; + char err_msg_buf[ERR_MSG_LEN] = { 0 }; + int res = ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_GSET, err_msg_buf); + TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(0, res, err_msg_buf); +} + + +TEST(test_ioctl_special, ethtool_sset) +{ + int err = 0; + int last_port = -1; + struct ethtool_cmd cmd = { 0 }; + char err_msg_buf[ERR_MSG_LEN] = { 0 }; + do { + err = ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_GSET, err_msg_buf); + if (err < 0) { + break; + } + + last_port = cmd.port; + cmd.port = 123; + err = ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_SSET, err_msg_buf); + if (err < 0) { + break; + } + TEST_ASSERT_EQUAL(123, cmd.port); + + } while (0); + + if (last_port != -1) { + cmd.port = last_port; + (void)ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_SSET, err_msg_buf); + } + if (err < 0) { + TEST_FAIL_MESSAGE(err_msg_buf); + } +} + + +TEST(test_ioctl_special, ethtool_test) +{ + struct ethtool_test cmd = { 0 }; + char err_msg_buf[ERR_MSG_LEN] = { 0 }; + cmd.flags = ETH_TEST_FL_OFFLINE; + if (ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_TEST, err_msg_buf) < 0) { + TEST_FAIL_MESSAGE(err_msg_buf); + } + TEST_ASSERT_EQUAL_MESSAGE(0, cmd.flags & ETH_TEST_FL_FAILED, "driver PHYSELFTEST failed"); +} + + +TEST(test_ioctl_special, ethtool_gloopback) +{ + struct ethtool_value cmd = { 0 }; + char err_msg_buf[ERR_MSG_LEN] = { 0 }; + if (ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_GLOOPBACK, err_msg_buf) < 0) { + TEST_FAIL_MESSAGE(err_msg_buf); + } +} + + +TEST(test_ioctl_special, ethtool_sloopback) +{ + int err = 0; + int last_loopback = -1; + struct ethtool_value cmd = { 0 }; + char err_msg_buf[ERR_MSG_LEN] = { 0 }; + + do { + err = ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_GLOOPBACK, err_msg_buf); + if (err < 0) { + break; + } + last_loopback = cmd.data; + uint32_t expected = (cmd.data != 0) ? ETH_PHY_LOOPBACK_DISABLED : ETH_PHY_LOOPBACK_ENABLED; + + cmd.data = expected; + if (ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_SLOOPBACK, err_msg_buf) < 0) { + break; + } + + if (cmd.data == ETH_PHY_LOOPBACK_SET_FAILED) { + snprintf(err_msg_buf, ERR_MSG_LEN, "Interface %.*s: couldn't set loopback", IFNAMSIZ, current_ifr.ifr_name); + err = -1; + break; + } + + cmd.data = 0; + err = ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_GLOOPBACK, err_msg_buf); + if (err < 0) { + break; + } + if (err != EOPNOTSUPP && cmd.data != expected) { + snprintf(err_msg_buf, ERR_MSG_LEN, "Interface %.*s: loopback set incorrectly", IFNAMSIZ, current_ifr.ifr_name); + err = -1; + break; + } + } while (0); + + if (last_loopback != -1) { + cmd.data = last_loopback; + (void)ethtool_ioctl(¤t_ifr, &cmd, ETHTOOL_SLOOPBACK, err_msg_buf); + } + + if (err < 0) { + TEST_FAIL_MESSAGE(err_msg_buf); + } +} + + +TEST_GROUP_RUNNER(test_ioctl_special) +{ + RUN_TEST_CASE(test_ioctl_special, ifconf); + RUN_TEST_CASE(test_ioctl_special, ifconf_not_enough_space); + + for (int i = 0; i < (ifc.ifc_len / sizeof(struct ifreq)); i++) { + current_ifr = ifc.ifc_req[i]; + fprintf(stderr, "IF: %.*s\n", IFNAMSIZ, current_ifr.ifr_name); + RUN_TEST_CASE(test_ioctl_special, ethtool_gset); + RUN_TEST_CASE(test_ioctl_special, ethtool_sset); + RUN_TEST_CASE(test_ioctl_special, ethtool_test); + RUN_TEST_CASE(test_ioctl_special, ethtool_gloopback); + RUN_TEST_CASE(test_ioctl_special, ethtool_sloopback); + } +} + + +void runner(void) +{ + RUN_TEST_GROUP(test_ioctl_special); +} + + +int main(int argc, char *argv[]) +{ + int err; + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("Couldn't open socket"); + return EXIT_FAILURE; + } + + if (get_ifconf(fd, &ifc) < 0) { + perror("Couldn't get ifconf"); + return EXIT_FAILURE; + } + + err = UnityMain(argc, (const char **)argv, runner); + if (ifc.ifc_req != NULL) { + free(ifc.ifc_req); + } + if (fd != -1) { + close(fd); + } + + return (err == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/ioctl/test-route.py b/ioctl/test-route.py new file mode 100644 index 000000000..fb49dccdb --- /dev/null +++ b/ioctl/test-route.py @@ -0,0 +1,89 @@ +import psh.tools.psh as psh + + +ROUTE_ROW0 = "%-15s %-15s %-15s %-5s %-6s %-6s %3s %s" % ( + "Destination", + "Gateway", + "Genmask", + "Flags", + "Metric", + "Ref", + "Use", + "Iface", +) +DEFAULT_ROUTE_ARGS_LO0 = ( + "127.0.0.0", + "127.0.0.1", + "255.0.0.0", + "UG", + 0, + 0, + 0, + "lo0", +) + + +def format_route_row( + destination_addr, gateway_addr, mask, sFlags, metric, refcnt, use, iface +): + return "%-15s %-15s %-15s %-5s %-6d %-6d %3d %s" % ( + destination_addr, + gateway_addr, + mask, + sFlags, + metric, + refcnt, + use, + iface, + ) + + +def match_literal_line(line): + return rf"{line}[\r\n]+" + + +def match_anything_until(until): + return rf"[\s\S]*?({until}){{1}}" + + +def route_assert_matches(pexpect_proc, expected_lines): + pexpect_proc.sendline("route") + pexpect_proc.expect(r"".join(expected_lines)) + pexpect_proc.expect_exact("(psh)%") + + +@psh.run +def harness(p): + expected_lines = [ + match_literal_line("route"), + match_literal_line(ROUTE_ROW0), + match_anything_until(format_route_row(*DEFAULT_ROUTE_ARGS_LO0)), + ] + route_assert_matches(p, expected_lines) + + # lo0 should be always available + psh.assert_cmd( + p, "route add 123.123.123.123 netmask 255.255.255.255 dev lo0" + ) + expected_lines.insert( + 2, + match_literal_line( + format_route_row( + "123.123.123.123", + "127.0.0.1", + "255.255.255.255", + "U", + 100, + 0, + 0, + "lo0", + ) + ), + ) + route_assert_matches(p, expected_lines) + + psh.assert_cmd( + p, "route del 123.123.123.123 netmask 255.255.255.255 dev lo0" + ) + expected_lines.pop(2) + route_assert_matches(p, expected_lines) diff --git a/ioctl/test.yaml b/ioctl/test.yaml index 31ba22740..f5177de5a 100644 --- a/ioctl/test.yaml +++ b/ioctl/test.yaml @@ -1,7 +1,21 @@ test: - type: unity - tests: - - name: unit - execute: test-ioctl - targets: - exclude: [host-generic-pc] + # type: unity + tests: + - name: test-ioctl + type: unity + execute: test-ioctl + targets: + exclude: [host-generic-pc] + + # special ioctls include only those from sockios for now, + # so test only on targets with lwIP enabled + - name: test-ioctl-special + type: unity + execute: test-ioctl-special + targets: + value: [ia32-generic-qemu, armv7a7-imx6ull-evk, armv7m7-imxrt106x-evk] + + - name: test-route + harness: test-route.py + targets: + value: [ia32-generic-qemu, armv7a7-imx6ull-evk, armv7m7-imxrt106x-evk]