From 31223b104983a1a356a999aae397c0cb29b08cd1 Mon Sep 17 00:00:00 2001 From: VasilyZa <450270247@qq.com> Date: Fri, 13 Nov 2026 05:28:38 +0800 Subject: [PATCH] feat(net): support multiple independent network interfaces --- Documentation/core-api/drivers.rst | 8 +- Documentation/core-api/e1000e.rst | 9 +- Documentation/core-api/index.rst | 1 + Documentation/core-api/network-devices.rst | 83 +++ Documentation/core-api/realtek.rst | 8 +- drivers/net/e1000/e1000_main.c | 548 +++++++++--------- drivers/net/e1000e/hardware.c | 9 +- drivers/net/e1000e/internal.h | 5 +- drivers/net/e1000e/main.c | 174 +++--- drivers/net/r8169/hardware.c | 14 +- drivers/net/r8169/internal.h | 5 +- drivers/net/r8169/main.c | 206 +++---- include/reliefnt/e1000.h | 24 +- include/reliefnt/net.h | 34 +- include/reliefnt/net_device.h | 73 +++ include/reliefnt/net_interface.h | 76 +++ include/reliefnt/net_ipv6.h | 24 +- include/reliefnt/net_packet.h | 9 + include/reliefnt/net_udp.h | 11 + include/uapi/linux/if.h | 2 + include/uapi/linux/rtnetlink.h | 1 + kernel/device/manager_internal.h | 16 +- kernel/device/registry.c | 11 +- kernel/device/services.c | 398 ++++++++----- kernel/syscall/ipc/socket.c | 9 + net/core.c | 67 ++- net/input.c | 31 +- net/interface/Kbuild | 2 +- net/interface/config.c | 186 +----- net/interface/ioctl.c | 258 +++++++++ net/interface/routes.c | 186 ++++++ net/interface/socket.c | 54 ++ net/interface/state.c | 159 +++++ net/internal.h | 74 ++- net/ipv4/core.c | 41 +- net/ipv4/udp.c | 71 ++- net/ipv6/core.c | 493 +++++++++------- net/link/arp.c | 302 ++++------ net/link/ethernet.c | 21 +- net/proc.c | 50 +- net/socket/netlink.c | 328 +++++++++-- net/socket/packet.c | 84 ++- net/socket/udp.c | 100 ++-- net/tcp/input.c | 38 +- net/tcp/output.c | 63 +- net/tcp/socket.c | 82 ++- tools/test_net_device.py | 19 + tools/test_net_multi_interface.py | 16 + tools/test_net_multinic_data.py | 26 + tools/tests/driver_modules_test.c | 4 + tools/tests/e1000_init_test.c | 97 +++- tools/tests/e1000e_test.c | 113 ++-- tools/tests/host-suites.list | 3 + tools/tests/ipv6_control_test.c | 19 +- tools/tests/ipv6_protocol_test.c | 313 +++++++++- tools/tests/ipv6_tcp_test.c | 25 +- tools/tests/ipv6_transport_test.c | 11 + tools/tests/module_legacy_audio_owner_test.c | 2 + tools/tests/modules/network.h | 8 + tools/tests/multinic_guest.c | 137 +++++ tools/tests/net_device_test.c | 89 +++ tools/tests/net_interface_test.c | 23 +- tools/tests/net_multi_interface_test.c | 197 +++++++ tools/tests/net_multinic_data_test.c | 97 ++++ tools/tests/net_packet_test.c | 45 ++ tools/tests/r8169_test.c | 68 ++- ...didate-3-net-ioctl-user-candidate-3.stub.c | 18 + tools/tests/task8_net_ioctl_user_test.c | 4 +- tools/tests/tcp_state_test.c | 163 +++++- tools/tests/udp_socket_test.c | 51 ++ 70 files changed, 4475 insertions(+), 1521 deletions(-) create mode 100644 Documentation/core-api/network-devices.rst create mode 100644 include/reliefnt/net_device.h create mode 100644 include/reliefnt/net_interface.h create mode 100644 net/interface/ioctl.c create mode 100644 net/interface/routes.c create mode 100644 net/interface/socket.c create mode 100644 net/interface/state.c create mode 100644 tools/test_net_device.py create mode 100644 tools/test_net_multi_interface.py create mode 100644 tools/test_net_multinic_data.py create mode 100644 tools/tests/multinic_guest.c create mode 100644 tools/tests/net_device_test.c create mode 100644 tools/tests/net_multi_interface_test.c create mode 100644 tools/tests/net_multinic_data_test.c diff --git a/Documentation/core-api/drivers.rst b/Documentation/core-api/drivers.rst index 5d21f777..58a3a645 100644 --- a/Documentation/core-api/drivers.rst +++ b/Documentation/core-api/drivers.rst @@ -30,12 +30,12 @@ drivers/ 按 console、tty、video、storage、net、input、usb、sound 等设 - PRO/1000 设备;网络协议栈和回调入口保留内置。 * - ``E1000E`` - ``e1000e`` - - Intel 82574L/82574IT、82583V PCIe 千兆网卡;共用单 eth0 服务, - 不替换已有网络 owner。设备范围和验证边界见 :doc:`e1000e`。 + - Intel 82574L/82574IT、82583V PCIe 千兆网卡;每个 PCI 功能独立注册 + 网络接口。设备范围和验证边界见 :doc:`e1000e`。 * - ``R8169`` - ``r8169`` - - Realtek RTL8111/8168 千兆与 RTL8125 2.5GbE PCIe 网卡;共用单 eth0 - 服务。芯片版本、固件来源和验证范围见 :doc:`realtek`。 + - Realtek RTL8111/8168 千兆与 RTL8125 2.5GbE PCIe 网卡;每个 PCI 功能 + 独立注册网络接口。芯片版本、固件来源和验证范围见 :doc:`realtek`。 * - ``MOUSE_PS2`` / ``USB_UHCI_HCD`` - ``mouse`` / ``uhci`` - PS/2、VMware 鼠标与 UHCI HID;键盘基础输入和分发入口内置。 diff --git a/Documentation/core-api/e1000e.rst b/Documentation/core-api/e1000e.rst index f81fe713..f25ba4ad 100644 --- a/Documentation/core-api/e1000e.rst +++ b/Documentation/core-api/e1000e.rst @@ -32,11 +32,10 @@ I217、I218、I219、82571/82572/82573 以及其他未列出的设备不在本 Kbuild/modpost 生成 ``drivers/net/e1000e/e1000e.ko``;``n`` 不编译该驱动。 模块携带精确 PCI 别名,ReliefOS 按实际硬件通过普通 modprobe 加载。 -网络继续使用现有 ``eth0`` 和 Linux socket、ioctl、netlink、proc 网络接口。 -内核当前只有一个外部 Ethernet 提供者;后来的不同 owner 注册返回 -``-EBUSY``,不替换先前的网卡或回调。内部历史名称 ``register_e1000`` 和 -``e1000_*`` 服务函数也承载 e1000e,不代表绑定硬件仍是 8254x;本次没有改变 -这些服务结构的布局、Linux UAPI 或 BusyBox。 +每个受支持的 PCI 功能拥有独立的上下文、DMA 环和 ``ethN`` 网络接口。 +Intel e1000、e1000e 与 Realtek 网卡可以同时注册。用户程序使用 Linux +socket、ioctl、netlink 和 proc 接口,接口状态和选路由 :doc:`network-devices` +描述的设备层管理。旧 ``e1000_*`` 内部入口查询首个已发布接口。 接口存在性不依赖网线或 PHY 协商:无载波时仍能查询名称、index、MAC、MTU 和已配置地址,也能设置 ``IFF_UP``。ioctl 与 rtnetlink 分别报告管理状态和 diff --git a/Documentation/core-api/index.rst b/Documentation/core-api/index.rst index b1e856de..a79c824e 100644 --- a/Documentation/core-api/index.rst +++ b/Documentation/core-api/index.rst @@ -16,6 +16,7 @@ path-cache resource-limits drivers + network-devices e1000e realtek usb diff --git a/Documentation/core-api/network-devices.rst b/Documentation/core-api/network-devices.rst new file mode 100644 index 00000000..45ebbe0d --- /dev/null +++ b/Documentation/core-api/network-devices.rst @@ -0,0 +1,83 @@ +======================== +Multiple network devices +======================== + +ReliefNT 为每个受支持的 Ethernet PCI 功能分配独立网络接口。e1000、e1000e +和 r8169 可以同时注册,多张相同型号网卡也分别拥有设备状态和 DMA 环。 + +Device identity +=============== + +``lo`` 的 ifindex 为 1。硬件接口从 ifindex 2 开始递增,名称为 +``eth``。接口在一次启动内保持名称和索引,移除后保留索引空洞。 +例如卸载拥有 eth0、eth1 的驱动后,仍存在的 eth2 继续使用原来的编号。 +重新加载驱动时,新设备获得新的索引和名称。 + +私有 ``include/reliefnt/net_device.h`` 的 ``driver_register_netdev`` 接收 +版本化回调表、每设备上下文和索引输出。管理器复制回调表,将其与原驱动 owner +绑定,回调前固定 owner,释放服务锁后执行回调。初始化成功后发布接口。 +驱动关闭回调接入、排空引用并停止自身全部设备后,管理器移除对应接口。 +停止失败的设备保留 DMA 和模块代码,后续卸载可以重试。 + +旧 ``e1000_*`` 服务入口和 ReliefOS 旧网络管理请求查询首个存活接口。 +旧驱动 API 表和公开用户结构继续使用原布局。新设备接口属于 ReliefNT 私有 +内核接口,原生模块通过精确构建 ABI 关联。 + +Configuration and routing +========================= + +每张网卡拥有独立的行政状态、IPv4 地址、掩码、广播地址和默认网关。 +IPv4 路由优先使用最长前缀,再比较 metric 和接口索引。绑定本机源地址和 +``SO_BINDTODEVICE`` 的 socket 按其约束选路。ARP 缓存按接口和下一跳地址 +区分。TCP 路由变化时重新检查下一跳 MAC,向本机 NIC 地址发送的 TCP、UDP +保留目标接口身份。 + +IPv6 地址、路由、邻居、多播成员、待发包、重组和 PMTU 携带 ifindex。 +逐链路状态通过动态分配管理,RA、RS、DAD、MLD 和发现定时分别推进。 +不同链路可以使用相同 link-local 地址,socket 的作用域与设备绑定须一致。 +现有 IPv6 地址、路由和邻居等共享表仍有各自的资源容量,耗尽时返回错误。 + +Linux 网络管理入口覆盖按名称、索引查询,IPv4/IPv6 地址和路由操作,以及 +行政状态开关。接口配置修改需要 ``CAP_NET_ADMIN``。首次设置 +``SO_BINDTODEVICE`` 允许普通进程,更改已有设备绑定需要 ``CAP_NET_RAW``。 +AF_PACKET 和 raw socket 的创建权限继续由对应协议入口检查。 + +设备直接接收软件提交的帧,发送队列交由各设备的有界 DMA 环管理。 +``SIOCGIFTXQLEN`` 报告的软件队列长度为 0。硬件 MTU 为 1500。 + +.. code-block:: sh + + ip link show + ip addr show dev eth1 + ip link set dev eth1 up + ip addr add 192.0.2.10/24 dev eth1 + ip route add default via 192.0.2.1 dev eth1 metric 100 + ip -6 addr add 2001:db8:1::10/64 dev eth1 + ip -6 route add default via fe80::1 dev eth1 + udhcpc -i eth1 + cat /proc/net/dev /proc/net/route /proc/net/arp /proc/net/if_inet6 + +``/proc/net/dev`` 列举所有已登记接口,包括未分配 IPv4 地址或行政关闭的设备。 +``SIOCGIFCONF`` 按 Linux 的 IPv4 地址列表语义输出 loopback 和有地址的接口。 +枚举硬件接口可使用 rtnetlink 或 ``/proc/net/dev``,并按名称查询 ifindex。 + +Validation +========== + +``tools/test_net_device.py`` 验证多个 owner、独立回调、计数和索引生命周期。 +``tools/test_net_multi_interface.py`` 验证 ioctl、rtnetlink 和逐接口选路。 +``tools/test_net_multinic_data.py`` 验证 ARP、UDP 输出和 AF_PACKET 隔离。 +既有 TCP、UDP 和 IPv6 定向套件覆盖对应传输的设备绑定、作用域与资源清理。 + +``tools/tests/multinic_guest.c`` 是标准 socket 用户程序探针,由 ReliefOS 的 +``tools/test_multi_nic_qemu.py`` 放入私有来宾执行。来宾使用两张 e1000 和 +一张 e1000e,验证独立 DHCP、IPv4/IPv6、并发 TCP/UDP,以及模块卸载期间 +另一 owner 的活动 TCP 连接。完整桌面镜像与实体硬件的运行验证分别记录。 +VMware 验证未完成。 + +References +========== + +* `Linux network device socket interface `_ +* `Linux packet sockets `_ +* `IPv6 scoped address architecture, RFC 4007 `_ diff --git a/Documentation/core-api/realtek.rst b/Documentation/core-api/realtek.rst index 31ac55c6..56130c7e 100644 --- a/Documentation/core-api/realtek.rst +++ b/Documentation/core-api/realtek.rst @@ -25,10 +25,10 @@ tristate,``make defconfig`` 默认选择 ``y``。选择 ``m`` 生成 - RTL8125A ``0x60900000``、B ``0x64100000``、D-1 ``0x68800000``、 D-2 ``0x68900000``。 -未知 MAC 家族返回 ``-ENODEV``。现有网络服务仅提供一个外部 Ethernet -接口,先注册的网卡拥有 ``eth0``,后来的 owner 收到 ``-EBUSY``。 -驱动复用内部历史名称 ``register_e1000`` 的服务,网络用户程序继续使用 -现有 Linux socket、ioctl、netlink 和 proc 接口。 +未知 MAC 家族返回 ``-ENODEV``。驱动枚举全部匹配的 PCI 功能,为每张网卡 +分配独立 DMA 环和上下文,通过私有 ``driver_register_netdev`` 注册 +``eth0``、``eth1`` 等接口。网络用户程序使用 Linux socket、ioctl、netlink +和 proc 接口。接口生命周期和多网卡选路见 :doc:`network-devices`。 .. code-block:: sh diff --git a/drivers/net/e1000/e1000_main.c b/drivers/net/e1000/e1000_main.c index 15672088..2bc12f53 100644 --- a/drivers/net/e1000/e1000_main.c +++ b/drivers/net/e1000/e1000_main.c @@ -1,10 +1,13 @@ #include #include +#include +#include static const struct reliefos_driver_kernel_api *kernel_api; -static void e1000_release_rings(void); +struct e1000_state; +static void e1000_release_rings(struct e1000_state *d); static void e1000_log(enum console_log_level level, const char *message) { @@ -113,6 +116,7 @@ struct module_e1000_info { }; struct e1000_state { + struct e1000_state *next; uint32_t present; uint32_t active; uint32_t pci_command_valid; @@ -132,7 +136,7 @@ struct e1000_state { uint8_t mac[6]; }; -static struct e1000_state g_e1000; +static struct e1000_state *devices; static void e1000_cpu_relax(void) { @@ -156,44 +160,14 @@ static void e1000_memcpy(void *dst, const void *src, uint32_t len) } } -static int e1000_find(struct pci_device *out) +static uint32_t e1000_reg_read(struct e1000_state *d, uint32_t offset) { - static const uint16_t supported[] = { - E1000_DEVICE_82540EM, - E1000_DEVICE_82545EM, - E1000_DEVICE_82543GC, - E1000_DEVICE_82544GC, - }; - for (uint32_t index = 0; index < sizeof(supported) / sizeof(supported[0]); ++index) { - struct reliefos_driver_pci_device found; - if (kernel_api->pci_find(E1000_VENDOR_INTEL, supported[index], &found) == 0) { - if (out) { - *out = (struct pci_device){ - .bus = found.bus, - .slot = found.slot, - .function = found.function, - .vendor_id = found.vendor_id, - .device_id = found.device_id, - .class_code = found.class_code, - .subclass = found.subclass, - .prog_if = found.prog_if, - .revision = 0, - }; - } - return 0; - } - } - return -1; + return *(volatile uint32_t *)(d->mmio_base + offset); } -static uint32_t e1000_reg_read(uint32_t offset) +static void e1000_reg_write(struct e1000_state *d, uint32_t offset, uint32_t value) { - return *(volatile uint32_t *)(g_e1000.mmio_base + offset); -} - -static void e1000_reg_write(uint32_t offset, uint32_t value) -{ - *(volatile uint32_t *)(g_e1000.mmio_base + offset) = value; + *(volatile uint32_t *)(d->mmio_base + offset) = value; } /** @brief Read the full physical address of an assigned memory BAR0. @@ -215,41 +189,41 @@ static uint64_t e1000_bar0_mmio(const struct pci_device *dev) return addr; } -static int e1000_read_mac(void) +static int e1000_read_mac(struct e1000_state *d) { - uint32_t ral = e1000_reg_read(E1000_REG_RAL); - uint32_t rah = e1000_reg_read(E1000_REG_RAH); - g_e1000.mac[0] = (uint8_t)ral; - g_e1000.mac[1] = (uint8_t)(ral >> 8); - g_e1000.mac[2] = (uint8_t)(ral >> 16); - g_e1000.mac[3] = (uint8_t)(ral >> 24); - g_e1000.mac[4] = (uint8_t)rah; - g_e1000.mac[5] = (uint8_t)(rah >> 8); - if (!(rah & E1000_RAH_AV) || (g_e1000.mac[0] & 1u) || !(ral | (rah & 0xffff))) { + uint32_t ral = e1000_reg_read(d, E1000_REG_RAL); + uint32_t rah = e1000_reg_read(d, E1000_REG_RAH); + d->mac[0] = (uint8_t)ral; + d->mac[1] = (uint8_t)(ral >> 8); + d->mac[2] = (uint8_t)(ral >> 16); + d->mac[3] = (uint8_t)(ral >> 24); + d->mac[4] = (uint8_t)rah; + d->mac[5] = (uint8_t)(rah >> 8); + if (!(rah & E1000_RAH_AV) || (d->mac[0] & 1u) || !(ral | (rah & 0xffff))) { for (unsigned word = 0; word < 3; ++word) { - e1000_reg_write(E1000_REG_EERD, 1u | (word << 8)); + e1000_reg_write(d, E1000_REG_EERD, 1u | (word << 8)); uint32_t value = 0; for (unsigned spin = 0; spin < E1000_TX_WAIT_SPINS; ++spin) { - value = e1000_reg_read(E1000_REG_EERD); + value = e1000_reg_read(d, E1000_REG_EERD); if (value & (1u << 4)) break; e1000_cpu_relax(); } if (!(value & (1u << 4))) return -1; - g_e1000.mac[word * 2] = (uint8_t)(value >> 16); - g_e1000.mac[word * 2 + 1] = (uint8_t)(value >> 24); + d->mac[word * 2] = (uint8_t)(value >> 16); + d->mac[word * 2 + 1] = (uint8_t)(value >> 24); } } - if ((g_e1000.mac[0] & 1u) || !(g_e1000.mac[0] | g_e1000.mac[1] | - g_e1000.mac[2] | g_e1000.mac[3] | g_e1000.mac[4] | g_e1000.mac[5])) return -1; - ral = (uint32_t)g_e1000.mac[0] | - ((uint32_t)g_e1000.mac[1] << 8) | - ((uint32_t)g_e1000.mac[2] << 16) | - ((uint32_t)g_e1000.mac[3] << 24); - rah = (uint32_t)g_e1000.mac[4] | - ((uint32_t)g_e1000.mac[5] << 8) | + if ((d->mac[0] & 1u) || !(d->mac[0] | d->mac[1] | + d->mac[2] | d->mac[3] | d->mac[4] | d->mac[5])) return -1; + ral = (uint32_t)d->mac[0] | + ((uint32_t)d->mac[1] << 8) | + ((uint32_t)d->mac[2] << 16) | + ((uint32_t)d->mac[3] << 24); + rah = (uint32_t)d->mac[4] | + ((uint32_t)d->mac[5] << 8) | E1000_RAH_AV; - e1000_reg_write(E1000_REG_RAL, ral); - e1000_reg_write(E1000_REG_RAH, rah); + e1000_reg_write(d, E1000_REG_RAL, ral); + e1000_reg_write(d, E1000_REG_RAH, rah); return 0; } @@ -258,122 +232,114 @@ static int e1000_read_mac(void) * after releasing every partially allocated page. * Task-context initialization before DMA is enabled on the PCI function. */ -static int e1000_alloc_rings(void) +static int e1000_alloc_rings(struct e1000_state *d) { - g_e1000.rx_phys = mm_alloc_pages(1); - g_e1000.tx_phys = mm_alloc_pages(1); - if (!g_e1000.rx_phys || !g_e1000.tx_phys) { - e1000_release_rings(); + d->rx_phys = mm_alloc_pages(1); + d->tx_phys = mm_alloc_pages(1); + if (!d->rx_phys || !d->tx_phys) { + e1000_release_rings(d); return -1; } - g_e1000.rx = (volatile struct e1000_rx_desc *)mm_phys_to_virt(g_e1000.rx_phys); - g_e1000.tx = (volatile struct e1000_tx_desc *)mm_phys_to_virt(g_e1000.tx_phys); - if (!g_e1000.rx || !g_e1000.tx) { - e1000_release_rings(); + d->rx = (volatile struct e1000_rx_desc *)mm_phys_to_virt(d->rx_phys); + d->tx = (volatile struct e1000_tx_desc *)mm_phys_to_virt(d->tx_phys); + if (!d->rx || !d->tx) { + e1000_release_rings(d); return -1; } - e1000_memzero((void *)g_e1000.rx, 4096); - e1000_memzero((void *)g_e1000.tx, 4096); + e1000_memzero((void *)d->rx, 4096); + e1000_memzero((void *)d->tx, 4096); for (uint32_t i = 0; i < E1000_RX_COUNT; ++i) { uint64_t phys = mm_alloc_pages(1); if (!phys) { - e1000_release_rings(); + e1000_release_rings(d); return -1; } - g_e1000.rx_buf[i] = (uint8_t *)mm_phys_to_virt(phys); - if (!g_e1000.rx_buf[i]) { + d->rx_buf[i] = (uint8_t *)mm_phys_to_virt(phys); + if (!d->rx_buf[i]) { mm_free_pages(phys, 1); - e1000_release_rings(); + e1000_release_rings(d); return -1; } - g_e1000.rx_buf_phys[i] = phys; - e1000_memzero(g_e1000.rx_buf[i], E1000_BUFFER_SIZE); - g_e1000.rx[i].addr = phys; - g_e1000.rx[i].status = 0; + d->rx_buf_phys[i] = phys; + e1000_memzero(d->rx_buf[i], E1000_BUFFER_SIZE); + d->rx[i].addr = phys; + d->rx[i].status = 0; } for (uint32_t i = 0; i < E1000_TX_COUNT; ++i) { uint64_t phys = mm_alloc_pages(1); if (!phys) { - e1000_release_rings(); + e1000_release_rings(d); return -1; } - g_e1000.tx_buf[i] = (uint8_t *)mm_phys_to_virt(phys); - if (!g_e1000.tx_buf[i]) { + d->tx_buf[i] = (uint8_t *)mm_phys_to_virt(phys); + if (!d->tx_buf[i]) { mm_free_pages(phys, 1); - e1000_release_rings(); + e1000_release_rings(d); return -1; } - g_e1000.tx_buf_phys[i] = phys; - e1000_memzero(g_e1000.tx_buf[i], E1000_BUFFER_SIZE); - g_e1000.tx[i].addr = phys; - g_e1000.tx[i].status = E1000_TX_STATUS_DD; + d->tx_buf_phys[i] = phys; + e1000_memzero(d->tx_buf[i], E1000_BUFFER_SIZE); + d->tx[i].addr = phys; + d->tx[i].status = E1000_TX_STATUS_DD; } return 0; } -static void e1000_configure_rx_tx(void) +static void e1000_configure_rx_tx(struct e1000_state *d) { - e1000_reg_write(E1000_REG_RCTL, 0); - e1000_reg_write(E1000_REG_TCTL, 0); - e1000_reg_write(E1000_REG_IMC, 0xffffffffu); - (void)e1000_reg_read(E1000_REG_ICR); + e1000_reg_write(d, E1000_REG_RCTL, 0); + e1000_reg_write(d, E1000_REG_TCTL, 0); + e1000_reg_write(d, E1000_REG_IMC, 0xffffffffu); + (void)e1000_reg_read(d, E1000_REG_ICR); for (uint32_t i = 0; i < 128; ++i) { - e1000_reg_write(E1000_REG_MTA + i * 4u, 0); + e1000_reg_write(d, E1000_REG_MTA + i * 4u, 0); } - e1000_reg_write(E1000_REG_RDBAL, (uint32_t)g_e1000.rx_phys); - e1000_reg_write(E1000_REG_RDBAH, (uint32_t)(g_e1000.rx_phys >> 32)); - e1000_reg_write(E1000_REG_RDLEN, E1000_RX_COUNT * sizeof(struct e1000_rx_desc)); - e1000_reg_write(E1000_REG_RDH, 0); - e1000_reg_write(E1000_REG_RDT, E1000_RX_COUNT - 1u); - g_e1000.rx_index = 0; + e1000_reg_write(d, E1000_REG_RDBAL, (uint32_t)d->rx_phys); + e1000_reg_write(d, E1000_REG_RDBAH, (uint32_t)(d->rx_phys >> 32)); + e1000_reg_write(d, E1000_REG_RDLEN, E1000_RX_COUNT * sizeof(struct e1000_rx_desc)); + e1000_reg_write(d, E1000_REG_RDH, 0); + e1000_reg_write(d, E1000_REG_RDT, E1000_RX_COUNT - 1u); + d->rx_index = 0; - e1000_reg_write(E1000_REG_TDBAL, (uint32_t)g_e1000.tx_phys); - e1000_reg_write(E1000_REG_TDBAH, (uint32_t)(g_e1000.tx_phys >> 32)); - e1000_reg_write(E1000_REG_TDLEN, E1000_TX_COUNT * sizeof(struct e1000_tx_desc)); - e1000_reg_write(E1000_REG_TDH, 0); - e1000_reg_write(E1000_REG_TDT, 0); - g_e1000.tx_tail = 0; + e1000_reg_write(d, E1000_REG_TDBAL, (uint32_t)d->tx_phys); + e1000_reg_write(d, E1000_REG_TDBAH, (uint32_t)(d->tx_phys >> 32)); + e1000_reg_write(d, E1000_REG_TDLEN, E1000_TX_COUNT * sizeof(struct e1000_tx_desc)); + e1000_reg_write(d, E1000_REG_TDH, 0); + e1000_reg_write(d, E1000_REG_TDT, 0); + d->tx_tail = 0; - e1000_reg_write(E1000_REG_TIPG, 10u | (8u << 10) | (6u << 20)); - e1000_reg_write(E1000_REG_TCTL, + e1000_reg_write(d, E1000_REG_TIPG, 10u | (8u << 10) | (6u << 20)); + e1000_reg_write(d, E1000_REG_TCTL, E1000_TCTL_EN | E1000_TCTL_PSP | (0x10u << 4) | (0x40u << 12)); - e1000_reg_write(E1000_REG_RCTL, + e1000_reg_write(d, E1000_REG_RCTL, E1000_RCTL_EN | E1000_RCTL_UPE | E1000_RCTL_MPE | E1000_RCTL_BAM | E1000_RCTL_BSIZE_2048 | E1000_RCTL_SECRC); } -/** @brief Initialize the controller through an owned kernel MMIO alias. - * @return None. On success, g_e1000 owns the mapping until fini; on failure, +/** @brief Initialize a selected controller through an owned kernel MMIO alias. + * @param d Per-function state with the PCI identity already selected. + * @return None. On success, the device owns its mapping until fini; on failure, * the partially initialized state remains available for fini cleanup. * Task-context driver initialization; all PCI decode and bus-master bits are * disabled while the BAR is mapped. DMA buffer addresses remain physical for * device programming. */ -static void e1000_hardware_init(void) +static void e1000_hardware_init(struct e1000_state *d) { - struct pci_device dev; + struct pci_device dev = d->pci; uint64_t mmio_phys; void *mmio_alias; uint16_t command; - e1000_memzero(&g_e1000, sizeof(g_e1000)); - - if (e1000_find(&dev) < 0) { - struct reliefos_driver_pci_device other; - if (kernel_api->pci_find(0x1022, 0x2000, &other) == 0) - e1000_log(CONSOLE_LOG_WARN, "VMware PCnet detected; select ethernet0.virtualDev = e1000 in the powered-off VM configuration"); - else e1000_log(CONSOLE_LOG_INFO, "no supported Intel PCI device found"); - return; - } - g_e1000.present = 1; - g_e1000.pci = dev; + d->present = 1; + d->pci = dev; command = pci_config_read16(dev.bus, dev.slot, dev.function, 0x04); - g_e1000.pci_command_original = command; - g_e1000.pci_command_valid = 1; + d->pci_command_original = command; + d->pci_command_valid = 1; command &= (uint16_t)~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER); pci_config_write16(dev.bus, dev.slot, dev.function, 0x04, command); @@ -388,31 +354,31 @@ static void e1000_hardware_init(void) e1000_log(CONSOLE_LOG_ERROR, "BAR0 MMIO mapping failed"); return; } - g_e1000.mmio_base = (uintptr_t)mmio_alias; + d->mmio_base = (uintptr_t)mmio_alias; command = pci_config_read16(dev.bus, dev.slot, dev.function, 0x04); command |= PCI_COMMAND_MEMORY; pci_config_write16(dev.bus, dev.slot, dev.function, 0x04, command); - e1000_reg_write(E1000_REG_IMC, 0xffffffffu); - e1000_reg_write(E1000_REG_RCTL, 0); - e1000_reg_write(E1000_REG_TCTL, 0); - (void)e1000_reg_read(E1000_REG_STATUS); + e1000_reg_write(d, E1000_REG_IMC, 0xffffffffu); + e1000_reg_write(d, E1000_REG_RCTL, 0); + e1000_reg_write(d, E1000_REG_TCTL, 0); + (void)e1000_reg_read(d, E1000_REG_STATUS); kernel_api->sleep_ms(10); - e1000_reg_write(E1000_REG_CTRL, e1000_reg_read(E1000_REG_CTRL) | E1000_CTRL_RST); + e1000_reg_write(d, E1000_REG_CTRL, e1000_reg_read(d, E1000_REG_CTRL) | E1000_CTRL_RST); kernel_api->sleep_ms(20); - if (e1000_reg_read(E1000_REG_CTRL) & E1000_CTRL_RST) { + if (e1000_reg_read(d, E1000_REG_CTRL) & E1000_CTRL_RST) { e1000_log(CONSOLE_LOG_ERROR, "controller reset timed out"); return; } - e1000_reg_write(E1000_REG_IMC, 0xffffffffu); - (void)e1000_reg_read(E1000_REG_ICR); - if (e1000_read_mac() < 0) { + e1000_reg_write(d, E1000_REG_IMC, 0xffffffffu); + (void)e1000_reg_read(d, E1000_REG_ICR); + if (e1000_read_mac(d) < 0) { e1000_log(CONSOLE_LOG_ERROR, "no valid hardware MAC in RAR or EEPROM"); return; } - e1000_reg_write(E1000_REG_CTRL, e1000_reg_read(E1000_REG_CTRL) | E1000_CTRL_SLU); - if (e1000_alloc_rings() < 0) { + e1000_reg_write(d, E1000_REG_CTRL, e1000_reg_read(d, E1000_REG_CTRL) | E1000_CTRL_SLU); + if (e1000_alloc_rings(d) < 0) { e1000_log(CONSOLE_LOG_ERROR, "ring allocation failed"); return; } @@ -420,31 +386,34 @@ static void e1000_hardware_init(void) command |= PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER; command &= (uint16_t)~PCI_COMMAND_IO; pci_config_write16(dev.bus, dev.slot, dev.function, 0x04, command); - e1000_configure_rx_tx(); - g_e1000.active = 1; + e1000_configure_rx_tx(d); + d->active = 1; e1000_log(CONSOLE_LOG_INFO, "descriptor rings initialized; hardware MAC loaded"); } -static int e1000_is_ready(void) +static int e1000_device_is_ready(void *context) { - return g_e1000.active && (e1000_reg_read(E1000_REG_STATUS) & (1u << 1)); + struct e1000_state *d=context; + return d->active && (e1000_reg_read(d, E1000_REG_STATUS) & (1u << 1)); } -static const uint8_t *e1000_mac(void) +static const uint8_t *e1000_device_mac(void *context) { - return g_e1000.mac; + struct e1000_state *d=context; + return d->mac; } -static int e1000_send(const void *frame, uint32_t len) +static int e1000_device_send(void *context, const void *frame, uint32_t len) { + struct e1000_state *d=context; uint32_t index; uint32_t next; - if (!g_e1000.active || !frame || len == 0 || len > E1000_BUFFER_SIZE) { + if (!d->active || !frame || len == 0 || len > E1000_BUFFER_SIZE) { return -1; } - index = g_e1000.tx_tail; + index = d->tx_tail; for (uint32_t spin = 0; spin < E1000_TX_WAIT_SPINS; ++spin) { - if (g_e1000.tx[index].status & E1000_TX_STATUS_DD) { + if (d->tx[index].status & E1000_TX_STATUS_DD) { break; } if (spin + 1u == E1000_TX_WAIT_SPINS) { @@ -452,19 +421,19 @@ static int e1000_send(const void *frame, uint32_t len) } e1000_cpu_relax(); } - e1000_memcpy(g_e1000.tx_buf[index], frame, len); - g_e1000.tx[index].length = (uint16_t)len; - g_e1000.tx[index].cso = 0; - g_e1000.tx[index].cmd = E1000_TX_CMD_EOP | E1000_TX_CMD_IFCS | E1000_TX_CMD_RS; - g_e1000.tx[index].status = 0; - g_e1000.tx[index].css = 0; - g_e1000.tx[index].special = 0; + e1000_memcpy(d->tx_buf[index], frame, len); + d->tx[index].length = (uint16_t)len; + d->tx[index].cso = 0; + d->tx[index].cmd = E1000_TX_CMD_EOP | E1000_TX_CMD_IFCS | E1000_TX_CMD_RS; + d->tx[index].status = 0; + d->tx[index].css = 0; + d->tx[index].special = 0; __atomic_thread_fence(__ATOMIC_RELEASE); next = (index + 1u) % E1000_TX_COUNT; - g_e1000.tx_tail = next; - e1000_reg_write(E1000_REG_TDT, next); + d->tx_tail = next; + e1000_reg_write(d, E1000_REG_TDT, next); for (uint32_t spin = 0; spin < E1000_TX_WAIT_SPINS; ++spin) { - if (g_e1000.tx[index].status & E1000_TX_STATUS_DD) { + if (d->tx[index].status & E1000_TX_STATUS_DD) { return 0; } e1000_cpu_relax(); @@ -472,8 +441,9 @@ static int e1000_send(const void *frame, uint32_t len) return -1; } -static int e1000_poll(void *frame, uint32_t capacity, uint32_t *out_len) +static int e1000_device_poll(void *context, void *frame, uint32_t capacity, uint32_t *out_len) { + struct e1000_state *d=context; uint32_t index; uint32_t len; uint8_t status; @@ -481,17 +451,17 @@ static int e1000_poll(void *frame, uint32_t capacity, uint32_t *out_len) if (out_len) { *out_len = 0; } - if (!g_e1000.active || !frame || !capacity) { + if (!d->active || !frame || !capacity) { return -1; } - index = g_e1000.rx_index; - status = g_e1000.rx[index].status; + index = d->rx_index; + status = d->rx[index].status; if ((status & E1000_RX_STATUS_DD) == 0) { return 0; } __atomic_thread_fence(__ATOMIC_ACQUIRE); - len = g_e1000.rx[index].length; - if ((status & E1000_RX_STATUS_EOP) == 0 || g_e1000.rx[index].errors || + len = d->rx[index].length; + if ((status & E1000_RX_STATUS_EOP) == 0 || d->rx[index].errors || len > E1000_BUFFER_SIZE || len > capacity) { receive_error = len > capacity ? -90 : -74; len = 0; @@ -500,62 +470,53 @@ static int e1000_poll(void *frame, uint32_t capacity, uint32_t *out_len) len = capacity; } if (len) { - e1000_memcpy(frame, g_e1000.rx_buf[index], len); + e1000_memcpy(frame, d->rx_buf[index], len); } - g_e1000.rx[index].length = 0; - g_e1000.rx[index].checksum = 0; - g_e1000.rx[index].status = 0; - g_e1000.rx[index].errors = 0; - g_e1000.rx[index].special = 0; + d->rx[index].length = 0; + d->rx[index].checksum = 0; + d->rx[index].status = 0; + d->rx[index].errors = 0; + d->rx[index].special = 0; __atomic_thread_fence(__ATOMIC_RELEASE); - e1000_reg_write(E1000_REG_RDT, index); - g_e1000.rx_index = (index + 1u) % E1000_RX_COUNT; + e1000_reg_write(d, E1000_REG_RDT, index); + d->rx_index = (index + 1u) % E1000_RX_COUNT; if (out_len) { *out_len = len; } return receive_error ? receive_error : len ? 1 : 0; } -static void e1000_get_info(struct module_e1000_info *info) +static void e1000_device_get_info(struct e1000_state *d, struct module_e1000_info *info) { if (!info) { return; } *info = (struct module_e1000_info){ - .present = g_e1000.present, - .active = e1000_is_ready() != 0, - .vendor_id = g_e1000.pci.vendor_id, - .device_id = g_e1000.pci.device_id, - .bus = g_e1000.pci.bus, - .slot = g_e1000.pci.slot, - .function = g_e1000.pci.function, + .present = d->present, + .active = e1000_device_is_ready(d) != 0, + .vendor_id = d->pci.vendor_id, + .device_id = d->pci.device_id, + .bus = d->pci.bus, + .slot = d->pci.slot, + .function = d->pci.function, }; for (uint32_t i = 0; i < 6; ++i) { - info->mac[i] = g_e1000.mac[i]; + info->mac[i] = d->mac[i]; } } -static void e1000_driver_get_info(struct reliefos_driver_e1000_info *out) +/** @brief Copy this pinned device's identity for the network registry. + * @param context Pinned controller. @param out Caller-owned nullable output. + */ +static void e1000_driver_get_info(void *context, struct e1000_info *out) { struct module_e1000_info info; - if (!out) { - return; - } - e1000_get_info(&info); - *out = (struct reliefos_driver_e1000_info){ - .present = info.present, - .active = info.active, - .vendor_id = info.vendor_id, - .device_id = info.device_id, - .bus = info.bus, - .slot = info.slot, - .function = info.function, - .reserved = 0, - .reserved2 = {0, 0}, - }; - for (uint32_t index = 0; index < 6; ++index) { - out->mac[index] = info.mac[index]; - } + if (!out) return; + e1000_device_get_info(context, &info); + *out=(struct e1000_info){.present=info.present,.active=info.active, + .vendor_id=info.vendor_id,.device_id=info.device_id, + .bus=info.bus,.slot=info.slot,.function=info.function}; + for (uint32_t i=0;i<6;++i) out->mac[i]=info.mac[i]; } /** @brief Release the physical DMA rings and buffers without dropping device state. @@ -563,113 +524,132 @@ static void e1000_driver_get_info(struct reliefos_driver_e1000_info *out) * alias remain valid for initialization rollback or ordered teardown. * Task-context cleanup after active ring DMA has not started or has been stopped. */ -static void e1000_release_rings(void) +static void e1000_release_rings(struct e1000_state *d) { for (uint32_t index = 0; index < E1000_RX_COUNT; ++index) { - if (g_e1000.rx_buf[index]) { - mm_free_pages(g_e1000.rx_buf_phys[index], 1); + if (d->rx_buf[index]) { + mm_free_pages(d->rx_buf_phys[index], 1); } } for (uint32_t index = 0; index < E1000_TX_COUNT; ++index) { - if (g_e1000.tx_buf[index]) { - mm_free_pages(g_e1000.tx_buf_phys[index], 1); + if (d->tx_buf[index]) { + mm_free_pages(d->tx_buf_phys[index], 1); } } - if (g_e1000.rx_phys) { - mm_free_pages(g_e1000.rx_phys, 1); - } - if (g_e1000.tx_phys) { - mm_free_pages(g_e1000.tx_phys, 1); - } - g_e1000.rx = 0; - g_e1000.tx = 0; - g_e1000.rx_phys = 0; - g_e1000.tx_phys = 0; - e1000_memzero(g_e1000.rx_buf, sizeof(g_e1000.rx_buf)); - e1000_memzero(g_e1000.tx_buf, sizeof(g_e1000.tx_buf)); - e1000_memzero(g_e1000.rx_buf_phys, sizeof(g_e1000.rx_buf_phys)); - e1000_memzero(g_e1000.tx_buf_phys, sizeof(g_e1000.tx_buf_phys)); - g_e1000.rx_index = 0; - g_e1000.tx_tail = 0; - g_e1000.active = 0; + if (d->rx_phys) { + mm_free_pages(d->rx_phys, 1); + } + if (d->tx_phys) { + mm_free_pages(d->tx_phys, 1); + } + d->rx = 0; + d->tx = 0; + d->rx_phys = 0; + d->tx_phys = 0; + e1000_memzero(d->rx_buf, sizeof(d->rx_buf)); + e1000_memzero(d->tx_buf, sizeof(d->tx_buf)); + e1000_memzero(d->rx_buf_phys, sizeof(d->rx_buf_phys)); + e1000_memzero(d->tx_buf_phys, sizeof(d->tx_buf_phys)); + d->rx_index = 0; + d->tx_tail = 0; + d->active = 0; } -static void e1000_driver_fini(void); +/** @brief Stop one controller and release resources only after confirmed reset. + * @param d Exclusive device after callback drain, including partial probes. + * @return Zero when safe, or -ETIMEDOUT while retaining memory and MMIO. + */ +static int e1000_stop_device(struct e1000_state *d) +{ + uintptr_t mmio_alias=d->mmio_base; + if (mmio_alias) { + e1000_reg_write(d,E1000_REG_RCTL,0); + e1000_reg_write(d,E1000_REG_TCTL,0); + e1000_reg_write(d,E1000_REG_IMC,0xffffffffu); + (void)e1000_reg_read(d,E1000_REG_STATUS); + kernel_api->sleep_ms(10); + } + if (d->pci_command_valid) { + uint16_t command=pci_config_read16(d->pci.bus,d->pci.slot,d->pci.function,4); + pci_config_write16(d->pci.bus,d->pci.slot,d->pci.function,4, + command & (uint16_t)~PCI_COMMAND_BUS_MASTER); + (void)pci_config_read16(d->pci.bus,d->pci.slot,d->pci.function,4); + } + if (mmio_alias) { + e1000_reg_write(d,E1000_REG_CTRL,e1000_reg_read(d,E1000_REG_CTRL)|E1000_CTRL_RST); + kernel_api->sleep_ms(20); + if (e1000_reg_read(d,E1000_REG_CTRL)&E1000_CTRL_RST) return -110; + } + e1000_release_rings(d); + if (mmio_alias) kernel_api->unmap_mmio((void *)mmio_alias,E1000_REGISTER_BYTES); + if (d->pci_command_valid) + pci_config_write16(d->pci.bus,d->pci.slot,d->pci.function,4,d->pci_command_original); + return 0; +} -/** @brief Validate the v2 services and register an initialized e1000 device. - * @param api Size-versioned kernel service table borrowed for the module lifetime. - * @return Zero on registration, or a negative errno after releasing partial resources. - * Task-context module initialization. +/** @brief Probe each supported PCI function into an independent device state. + * @param api Size-versioned service table retained for driver lifetime. + * @return Zero with at least one NIC, else a negative errno. */ static int e1000_driver_init(const struct reliefos_driver_kernel_api *api) { - static const struct reliefos_driver_e1000_ops ops = { - .is_ready = e1000_is_ready, - .mac = e1000_mac, - .send = e1000_send, - .poll = e1000_poll, - .get_info = e1000_driver_get_info, + static const struct reliefnt_netdev_ops ops={ + RELIEFNT_NETDEV_OPS_VERSION,sizeof(ops),e1000_device_is_ready,e1000_device_mac, + e1000_device_send,e1000_device_poll,e1000_driver_get_info }; - if (!api || api->abi_version != RELIEFOS_DRIVER_ABI_VERSION || - api->struct_size < RELIEFOS_DRIVER_PHYS_MAP_API_SIZE || !api->console_log || + if (!api || api->abi_version!=RELIEFOS_DRIVER_ABI_VERSION || + api->struct_sizeconsole_log || !api->phys_to_virt || !api->map_mmio || !api->unmap_mmio || - !api->alloc_pages || !api->free_pages || !api->pci_find || + !api->alloc_pages || !api->free_pages || !api->pci_enumerate || !api->pci_read16 || !api->pci_write16 || !api->pci_read32 || - !api->sleep_ms || !api->register_e1000 || !api->report_teardown_failure) { - return -22; - } - kernel_api = api; - e1000_hardware_init(); - if (!g_e1000.active) { - e1000_driver_fini(); - return -19; - } - int ret = kernel_api->register_e1000(&ops); - if (ret < 0) e1000_driver_fini(); - return ret; + !api->sleep_ms || !api->report_teardown_failure) return -22; + kernel_api=api; + int first_error=0; unsigned registered=0; + for (uint32_t index=0;;++index) { + struct reliefos_driver_pci_device pci; + int rc=api->pci_enumerate(index,&pci); + if (rc==-19) break; + if (rc) return rc; + if (pci.vendor_id!=E1000_VENDOR_INTEL || pci.class_code!=2 || pci.subclass!=0 || + (pci.device_id!=E1000_DEVICE_82540EM && pci.device_id!=E1000_DEVICE_82545EM && + pci.device_id!=E1000_DEVICE_82543GC && pci.device_id!=E1000_DEVICE_82544GC)) continue; + struct e1000_state *d=kernel_malloc(sizeof(*d)); + if (!d) { + api->console_log(CONSOLE_LOG_WARN,"[e1000] %02x:%02x.%u: no device state memory\n", + pci.bus,pci.slot,pci.function); + if (!first_error) first_error=-12; + break; + } + *d=(struct e1000_state){.next=devices,.pci={.bus=pci.bus,.slot=pci.slot, + .function=pci.function,.vendor_id=pci.vendor_id,.device_id=pci.device_id, + .class_code=pci.class_code,.subclass=pci.subclass,.prog_if=pci.prog_if}}; + devices=d; e1000_hardware_init(d); + rc=d->active ? 0 : -19; + if (!rc) { uint32_t ifindex; rc=driver_register_netdev(&ops,d,&ifindex); } + if (rc) { + api->console_log(CONSOLE_LOG_WARN,"[e1000] %02x:%02x.%u initialization failed: %d\n", + pci.bus,pci.slot,pci.function,rc); + if (!first_error) first_error=rc; + if (e1000_stop_device(d)) return rc; + devices=d->next; kernel_free(d); continue; + } + ++registered; + } + return registered ? 0 : first_error ? first_error : -19; } -/** @brief Stop device access, release DMA pages, then unmap its BAR. - * @return None. All MMIO access ends before the owned mapping is returned. - * Task-context module teardown after the driver manager has stopped admissions. +/** @brief Stop all controllers and retain failed STOP resources for retry. + * Called after all callbacks owned by this driver have drained. */ static void e1000_driver_fini(void) { - uintptr_t mmio_alias = g_e1000.mmio_base; - if (g_e1000.mmio_base) { - e1000_reg_write(E1000_REG_RCTL, 0); - e1000_reg_write(E1000_REG_TCTL, 0); - e1000_reg_write(E1000_REG_IMC, 0xffffffffu); - (void)e1000_reg_read(E1000_REG_STATUS); - kernel_api->sleep_ms(10); - } - if (g_e1000.pci_command_valid) { - uint16_t command = pci_config_read16(g_e1000.pci.bus, g_e1000.pci.slot, - g_e1000.pci.function, 0x04); - command &= (uint16_t)~PCI_COMMAND_BUS_MASTER; - pci_config_write16(g_e1000.pci.bus, g_e1000.pci.slot, - g_e1000.pci.function, 0x04, command); - (void)pci_config_read16(g_e1000.pci.bus,g_e1000.pci.slot,g_e1000.pci.function,0x04); - } - if(mmio_alias) { - e1000_reg_write(E1000_REG_CTRL,e1000_reg_read(E1000_REG_CTRL)|E1000_CTRL_RST); - kernel_api->sleep_ms(20); - if(e1000_reg_read(E1000_REG_CTRL)&E1000_CTRL_RST) { - /* DMA memory and code must survive an unacknowledged reset. */ - kernel_api->report_teardown_failure(-110); - return; - } - } - e1000_release_rings(); - if (mmio_alias) { - kernel_api->unmap_mmio((void *)mmio_alias, E1000_REGISTER_BYTES); - } - if (g_e1000.pci_command_valid) { - pci_config_write16(g_e1000.pci.bus, g_e1000.pci.slot, - g_e1000.pci.function, 0x04, - g_e1000.pci_command_original); + struct e1000_state **link=&devices; + while (*link) { + struct e1000_state *d=*link; + int rc=e1000_stop_device(d); + if (rc) { kernel_api->report_teardown_failure(rc); link=&d->next; continue; } + *link=d->next; kernel_free(d); } - e1000_memzero(&g_e1000, sizeof(g_e1000)); } const struct reliefos_driver_module e1000_driver_module = { diff --git a/drivers/net/e1000e/hardware.c b/drivers/net/e1000e/hardware.c index 835b1e23..c043ead0 100644 --- a/drivers/net/e1000e/hardware.c +++ b/drivers/net/e1000e/hardware.c @@ -16,16 +16,13 @@ static int wait_bits(struct e1k_device *d, unsigned reg, uint32_t mask, } } -/** @brief Discover, map and reset an explicitly supported PCIe controller. - * @param d Zeroed state with validated services; caller retains partial state. +/** @brief Map and reset one already selected supported PCIe controller. + * @param d Selected PCI identity with validated services; retains partial state. * @return Zero, -ENODEV, -EINVAL, -ENOMEM or -ETIMEDOUT; no DMA buffers are allocated. */ int e1k_hardware_start(struct e1k_device *d) { - static const uint16_t devices[]={0x10d3,0x150c}; - for (unsigned i=0;iapi->pci_find(0x8086,devices[i],&d->pci)) { d->present=true; break; } - if (!d->present) return -19; + d->present=true; const struct reliefos_driver_pci_device *p=&d->pci; d->original_command=d->api->pci_read16(p->bus,p->slot,p->function,4); d->command_saved=true; diff --git a/drivers/net/e1000e/internal.h b/drivers/net/e1000e/internal.h index b070cf5e..b3a7f646 100644 --- a/drivers/net/e1000e/internal.h +++ b/drivers/net/e1000e/internal.h @@ -61,6 +61,7 @@ _Static_assert(E1K_RX_COUNT*sizeof(struct e1k_rx_descriptor)==4096, "RX ring pag _Static_assert(E1K_TX_COUNT*sizeof(struct e1k_tx_descriptor)<=4096, "TX ring page"); struct e1k_device { + struct e1k_device *next; const struct reliefos_driver_kernel_api *api; struct reliefos_driver_pci_device pci; volatile uint8_t *registers; @@ -92,8 +93,8 @@ static inline void e1k_write(struct e1k_device *d, unsigned offset, uint32_t val static inline void e1k_flush(struct e1k_device *d) { (void)e1k_read(d,E1K_STATUS); } -/** @brief Discover, map and reset an explicitly supported PCIe controller. - * @param d Zeroed state with validated kernel services, retained for rollback. +/** @brief Map and reset one already selected supported PCIe controller. + * @param d Selected PCI identity and validated services, retained for rollback. * @return Zero, or negative Linux errno; all acquired resources remain owned by d. */ int e1k_hardware_start(struct e1k_device *d); diff --git a/drivers/net/e1000e/main.c b/drivers/net/e1000e/main.c index 3b1b9025..18811dbe 100644 --- a/drivers/net/e1000e/main.c +++ b/drivers/net/e1000e/main.c @@ -1,97 +1,135 @@ /* SPDX-License-Identifier: Apache-2.0 */ #include "internal.h" +#include +#include -static struct e1k_device device; +static struct e1k_device *devices; -/** @brief Return actual link readiness without waiting or restarting negotiation. - * @return Nonzero only for an initialized controller whose hardware link is up. +/** @brief Report carrier of the specific controller. + * @param context Pinned controller. @return Nonzero when the link is up. */ -static int ready(void) -{ return device.active && (e1k_read(&device,E1K_STATUS)&E1K_STATUS_LINK); } - -/** @brief Borrow the immutable station address during a retained callback. - * @return Six device-owned bytes, valid until the caller releases its owner pin. +static int ready(void *context) +{ + struct e1k_device *d=context; + return d->active && (e1k_read(d,E1K_STATUS)&E1K_STATUS_LINK); +} +/** @brief Borrow the selected controller's address while pinned. + * @param context Pinned controller. @return Six immutable device bytes. */ -static const uint8_t *mac(void) { return device.mac; } - -/** @brief Enqueue one frame through the retained hardware provider. - * @param frame Borrowed Ethernet bytes. @param length Frame size excluding FCS. - * @return Zero on admission or a negative Linux errno; no hardware wait. +static const uint8_t *mac(void *context) { return ((struct e1k_device *)context)->mac; } +/** @brief Enqueue a frame to the selected controller. + * @param context Pinned controller. @param frame Borrowed frame. + * @param length Frame bytes. @return Zero or negative errno. */ -static int send(const void *frame, uint32_t length) -{ return e1k_send(&device,frame,length); } - -/** @brief Consume at most one received Ethernet frame. - * @param frame Output buffer. @param capacity Buffer size. @param length Optional length output. - * @return One for a complete frame, zero if empty, or a negative Linux errno. +static int send(void *context, const void *frame, uint32_t length) +{ return e1k_send(context,frame,length); } +/** @brief Receive one frame from the selected controller. + * @param context Pinned controller. @param frame Output buffer. + * @param capacity Buffer size. @param length Optional output size. + * @return One frame, zero or negative errno. */ -static int receive(void *frame, uint32_t capacity, uint32_t *length) -{ return e1k_receive(&device,frame,capacity,length); } - -/** @brief Return the actual bound PCI identity, MAC and link state. - * @param out Caller-owned metadata output, nullable; contains no kernel pointers. +static int receive(void *context, void *frame, uint32_t capacity, uint32_t *length) +{ return e1k_receive(context,frame,capacity,length); } +/** @brief Copy this controller's identity and link state. + * @param context Pinned controller. @param out Nullable caller-owned output. */ -static void info(struct reliefos_driver_e1000_info *out) +static void info(void *context, struct e1000_info *out) { + struct e1k_device *d=context; if (!out) return; - *out=(struct reliefos_driver_e1000_info){.present=device.present, - .active=ready()!=0, .vendor_id=device.pci.vendor_id,.device_id=device.pci.device_id, - .bus=device.pci.bus,.slot=device.pci.slot,.function=device.pci.function}; - for (unsigned i=0;i<6;++i) out->mac[i]=device.mac[i]; + *out=(struct e1000_info){.present=d->present,.active=ready(d)!=0, + .vendor_id=d->pci.vendor_id,.device_id=d->pci.device_id, + .bus=d->pci.bus,.slot=d->pci.slot,.function=d->pci.function}; + for (unsigned i=0;i<6;++i) out->mac[i]=d->mac[i]; } +static const struct reliefnt_netdev_ops ops={ + RELIEFNT_NETDEV_OPS_VERSION,sizeof(ops),ready,mac,send,receive,info +}; -/** @brief Initialize a supported controller and publish its retained network callbacks. - * @param api Size-versioned kernel services borrowed for this binding's lifetime. - * @return Zero or negative Linux errno. Partial resources remain for the manager's - * mandatory fini rollback; no self-reference or private user ABI is introduced. +/** @brief Stop and release one complete or partially initialized controller. + * @param d Exclusively owned controller with callback admission closed. + * @return Zero after safe release, or negative errno retaining all resources. + */ +static int stop_device(struct e1k_device *d) +{ + int rc=e1k_hardware_stop(d); + if (rc) return rc; + e1k_free_rings(d); + if (d->registers) d->api->unmap_mmio((void *)d->registers,E1K_MMIO_BYTES); + if (d->command_saved) { + const struct reliefos_driver_pci_device *p=&d->pci; + d->api->pci_write16(p->bus,p->slot,p->function,4,d->original_command); + } + return 0; +} + +/** @brief Probe every supported PCI function and register independent NICs. + * @param api Validated kernel service table retained for the driver lifetime. + * @return Zero if at least one registered, or the first probe error/-ENODEV. */ static int start(const struct reliefos_driver_kernel_api *api) { - static const struct reliefos_driver_e1000_ops ops={ready,mac,send,receive,info}; if (!api || api->abi_version!=RELIEFOS_DRIVER_ABI_VERSION || api->struct_sizepci_find || !api->pci_read16 || !api->pci_write16 || !api->pci_read32 || + !api->pci_enumerate || !api->pci_read16 || !api->pci_write16 || !api->pci_read32 || !api->map_mmio || !api->unmap_mmio || !api->phys_to_virt || !api->alloc_dma || !api->free_dma || !api->sleep_ms || - !api->register_e1000 || !api->console_log || !api->report_teardown_failure) return -22; - device=(struct e1k_device){.api=api}; - int rc=e1k_hardware_start(&device); - if (rc) return rc; - rc=e1k_allocate_rings(&device); - if (rc) return rc; - const struct reliefos_driver_pci_device *p=&device.pci; - uint16_t command=api->pci_read16(p->bus,p->slot,p->function,4); - api->pci_write16(p->bus,p->slot,p->function,4,command|6u); - e1k_enable_rings(&device); - device.active=true; - rc=api->register_e1000(&ops); - if (rc) return rc; - e1k_write(&device,E1K_CTRL_EXT,e1k_read(&device,E1K_CTRL_EXT)|E1K_DRV_LOAD); - e1k_flush(&device); - api->console_log(CONSOLE_LOG_INFO, - "[e1000e] %02x:%02x.%u Intel %04x:%04x MAC=%02x:%02x:%02x:%02x:%02x:%02x legacy DMA rings ready; link=%s\n", - p->bus,p->slot,p->function,p->vendor_id,p->device_id, - device.mac[0],device.mac[1],device.mac[2],device.mac[3],device.mac[4],device.mac[5], - ready()?"up":"negotiating/down"); - return 0; + !api->console_log || !api->report_teardown_failure) return -22; + int first_error=0; unsigned registered=0; + for (uint32_t index=0;;++index) { + struct reliefos_driver_pci_device pci; + int rc=api->pci_enumerate(index,&pci); + if (rc==-19) break; + if (rc) return rc; + if (pci.vendor_id!=0x8086 || (pci.device_id!=0x10d3 && pci.device_id!=0x150c) || + pci.class_code!=2 || pci.subclass!=0) continue; + struct e1k_device *d=kernel_malloc(sizeof(*d)); + if (!d) { + api->console_log(CONSOLE_LOG_WARN,"[e1000e] %02x:%02x.%u: no device state memory\n", + pci.bus,pci.slot,pci.function); + if (!first_error) first_error=-12; + break; + } + *d=(struct e1k_device){.api=api,.pci=pci,.next=devices}; devices=d; + rc=e1k_hardware_start(d); + if (!rc) rc=e1k_allocate_rings(d); + if (!rc) { + uint16_t command=api->pci_read16(pci.bus,pci.slot,pci.function,4); + api->pci_write16(pci.bus,pci.slot,pci.function,4,command|6u); + e1k_enable_rings(d); d->active=true; + uint32_t ifindex; + rc=driver_register_netdev(&ops,d,&ifindex); + } + if (rc) { + api->console_log(CONSOLE_LOG_WARN,"[e1000e] %02x:%02x.%u initialization failed: %d\n", + pci.bus,pci.slot,pci.function,rc); + if (!first_error) first_error=rc; + if (stop_device(d)) return rc; /* manager retries mandatory fini */ + devices=d->next; kernel_free(d); continue; + } + e1k_write(d,E1K_CTRL_EXT,e1k_read(d,E1K_CTRL_EXT)|E1K_DRV_LOAD); + e1k_flush(d); ++registered; + api->console_log(CONSOLE_LOG_INFO, + "[e1000e] %02x:%02x.%u Intel %04x:%04x MAC=%02x:%02x:%02x:%02x:%02x:%02x; link=%s\n", + pci.bus,pci.slot,pci.function,pci.vendor_id,pci.device_id, + d->mac[0],d->mac[1],d->mac[2],d->mac[3],d->mac[4],d->mac[5], + ready(d)?"up":"negotiating/down"); + } + return registered ? 0 : first_error ? first_error : -19; } -/** @brief Stop hardware before releasing DMA backing and MMIO ownership. - * Callback admission is closed and pins drained by the manager. Failed STOP - * reports an error and retains every resource and the module for a safe retry. +/** @brief Stop every owned controller, retaining unsafe ones for retry. + * Called after callback admission closes and all owner callbacks drain. */ static void stop(void) { - if (!device.api) return; - int rc=e1k_hardware_stop(&device); - if (rc) { device.api->report_teardown_failure(rc); return; } - e1k_free_rings(&device); - if (device.registers) device.api->unmap_mmio((void *)device.registers,E1K_MMIO_BYTES); - if (device.command_saved) { - const struct reliefos_driver_pci_device *p=&device.pci; - device.api->pci_write16(p->bus,p->slot,p->function,4,device.original_command); + struct e1k_device **link=&devices; + while (*link) { + struct e1k_device *d=*link; + int rc=stop_device(d); + if (rc) { d->api->report_teardown_failure(rc); link=&d->next; continue; } + *link=d->next; kernel_free(d); } - device=(struct e1k_device){0}; } const struct reliefos_driver_module e1000e_driver_module={ diff --git a/drivers/net/r8169/hardware.c b/drivers/net/r8169/hardware.c index 79556668..29edbdf3 100644 --- a/drivers/net/r8169/hardware.c +++ b/drivers/net/r8169/hardware.c @@ -97,22 +97,14 @@ static int map_registers(struct rtl_device *d) return rc; } -/** @brief Discover a supported Realtek function and retain its MMIO/PCI state. - * @param d Zeroed device containing validated kernel services. +/** @brief Probe a selected Realtek function and retain its MMIO/PCI state. + * @param d Selected PCI identity with validated kernel services. * @return Zero with identified hardware and valid station address, or negative errno. * Bus mastering remains disabled until rings have been fully allocated. */ int rtl_hardware_probe(struct rtl_device *d) { - static const uint16_t ids[] = {0x8168, 0x8125}; - for (unsigned i = 0; i < sizeof(ids) / sizeof(ids[0]); ++i) { - if (!d->api->pci_find(0x10ec, ids[i], &d->pci)) { - d->present = true; - break; - } - } - if (!d->present) - return -19; + d->present = true; const struct reliefos_driver_pci_device *p = &d->pci; if (p->class_code != 2 || p->subclass != 0) return -19; diff --git a/drivers/net/r8169/internal.h b/drivers/net/r8169/internal.h index 1313a9c2..89fb25cc 100644 --- a/drivers/net/r8169/internal.h +++ b/drivers/net/r8169/internal.h @@ -30,6 +30,7 @@ _Static_assert(RTL_RX_COUNT * sizeof(struct rtl_descriptor) == 4096, "RX ring pa _Static_assert(RTL_TX_COUNT * sizeof(struct rtl_descriptor) == 4096, "TX ring page"); struct rtl_device { + struct rtl_device *next; const struct reliefos_driver_kernel_api *api; struct reliefos_driver_pci_device pci; volatile uint8_t *registers; @@ -121,8 +122,8 @@ int rtl8125_start(struct rtl_device *d); */ int rtl8125_stop(struct rtl_device *d); -/** @brief Probe a supported PCI identity, enter D0, claim a BAR and identify the MAC. - * @param d Zeroed state with validated services; retained for mandatory rollback. +/** @brief Probe a selected PCI identity, enter D0, claim a BAR and identify the MAC. + * @param d Selected PCI identity with validated services; retained for rollback. * @return Zero or negative Linux errno. Leaves bus mastering disabled. */ int rtl_hardware_probe(struct rtl_device *d); diff --git a/drivers/net/r8169/main.c b/drivers/net/r8169/main.c index a2bd84c9..a388b802 100644 --- a/drivers/net/r8169/main.c +++ b/drivers/net/r8169/main.c @@ -1,130 +1,142 @@ /* SPDX-License-Identifier: Apache-2.0 */ #include "internal.h" +#include +#include -static struct rtl_device device; +static struct rtl_device *devices; -/** @brief Observe the hardware's current link indication without MDIO waits. - * @return Nonzero only for an active bound controller with a copper link. +/** @brief Observe this controller's copper link without MDIO waits. + * @param context Pinned device. @return Nonzero when carrier is up. */ -static int ready(void) +static int ready(void *context) { - return device.active && (rtl_read8(&device, 0x6c) & 2); + struct rtl_device *d=context; + return d->active && (rtl_read8(d, 0x6c) & 2); } - -/** @brief Borrow the immutable station address while the caller holds its owner pin. - * @return Device-owned six-byte station address, retained until callback drain. +/** @brief Borrow this controller's immutable station address. + * @param context Pinned device. @return Six device-owned bytes. */ -static const uint8_t *mac(void) -{ - return device.mac; -} - -/** @brief Submit a frame through the retained single-interface network service. - * @param frame Borrowed Ethernet bytes. @param length Size excluding FCS. - * @return Zero on admission or negative Linux errno without waiting. +static const uint8_t *mac(void *context) { return ((struct rtl_device *)context)->mac; } +/** @brief Submit a frame to one controller. + * @param context Pinned device. @param frame Borrowed frame bytes. + * @param length Number of bytes. @return Zero or negative errno. */ -static int send(const void *frame, uint32_t length) -{ - return rtl_send(&device, frame, length); -} - -/** @brief Consume one descriptor through the retained network service. - * @param frame Writable output. @param capacity Buffer capacity. @param length Optional size output. - * @return One for a frame, zero if empty, or negative Linux errno. +static int send(void *context, const void *frame, uint32_t length) +{ return rtl_send(context,frame,length); } +/** @brief Consume a descriptor from one controller. + * @param context Pinned device. @param frame Writable buffer. + * @param capacity Buffer bytes. @param length Optional length output. + * @return One frame, zero or negative errno. */ -static int receive(void *frame, uint32_t capacity, uint32_t *length) +static int receive(void *context, void *frame, uint32_t capacity, uint32_t *length) +{ return rtl_receive(context,frame,capacity,length); } +/** @brief Copy this controller's PCI identity, link and station address. + * @param context Pinned device. @param out Nullable caller-owned output. + */ +static void info(void *context, struct e1000_info *out) { - return rtl_receive(&device, frame, capacity, length); + struct rtl_device *d=context; + if (!out) return; + *out=(struct e1000_info){.present=d->present,.active=ready(d)!=0, + .vendor_id=d->pci.vendor_id,.device_id=d->pci.device_id, + .bus=d->pci.bus,.slot=d->pci.slot,.function=d->pci.function}; + for (unsigned i=0;i<6;++i) out->mac[i]=d->mac[i]; } +static const struct reliefnt_netdev_ops ops={ + RELIEFNT_NETDEV_OPS_VERSION,sizeof(ops),ready,mac,send,receive,info +}; -/** @brief Report the actual PCI identity, link state and station address. - * @param out Nullable caller-owned output; no private pointer crosses the interface. +/** @brief Stop one complete or partial controller before releasing its resources. + * @param d Exclusively owned device with callbacks drained. + * @return Zero on safe release, negative errno retaining resources for retry. */ -static void info(struct reliefos_driver_e1000_info *out) +static int stop_device(struct rtl_device *d) { - if (!out) - return; - *out = (struct reliefos_driver_e1000_info){ - .present = device.present, .active = ready() != 0, - .vendor_id = device.pci.vendor_id, .device_id = device.pci.device_id, - .bus = device.pci.bus, .slot = device.pci.slot, .function = device.pci.function - }; - for (unsigned i = 0; i < 6; ++i) - out->mac[i] = device.mac[i]; + int rc=rtl_hardware_stop(d); + if (rc) return rc; + rtl_free_rings(d); + if (d->registers) d->api->unmap_mmio((void *)d->registers,d->mmio_bytes); + if (d->command_saved) { + const struct reliefos_driver_pci_device *p=&d->pci; + d->api->pci_write16(p->bus,p->slot,p->function,4,d->original_command & ~4u); + } + return 0; } -/** @brief Initialize a supported Realtek MAC and publish its owner-pinned callbacks. - * @param api Size/version-qualified services borrowed for the binding's lifetime. - * @return Zero or negative Linux errno; partial resources remain for mandatory fini. +/** @brief Enumerate every supported Realtek function and publish separate contexts. + * @param api Kernel services retained for driver lifetime. + * @return Zero for at least one success, or first probe errno/-ENODEV. */ static int start(const struct reliefos_driver_kernel_api *api) { - static const struct reliefos_driver_e1000_ops ops = {ready, mac, send, receive, info}; if (!api || api->abi_version != RELIEFOS_DRIVER_ABI_VERSION || api->struct_size < RELIEFOS_DRIVER_PHYS_MAP_API_SIZE || - !api->pci_find || !api->pci_read16 || !api->pci_write16 || !api->pci_read32 || + !api->pci_enumerate || !api->pci_read16 || !api->pci_write16 || !api->pci_read32 || !api->map_mmio || !api->unmap_mmio || !api->alloc_dma || !api->free_dma || - !api->phys_to_virt || !api->sleep_ms || !api->register_e1000 || - !api->console_log || !api->report_teardown_failure) + !api->phys_to_virt || !api->sleep_ms || !api->console_log || !api->report_teardown_failure) return -22; - device = (struct rtl_device){.api = api}; - int rc = rtl_hardware_probe(&device); - if (rc) - return rc; - rc = rtl_hardware_stop(&device); - if (rc) - return rc; - rc = rtl_allocate_rings(&device); - if (rc) - return rc; - const struct reliefos_driver_pci_device *p = &device.pci; - uint16_t command = api->pci_read16(p->bus, p->slot, p->function, 4); - api->pci_write16(p->bus, p->slot, p->function, 4, command | 4u); - if (!(api->pci_read16(p->bus, p->slot, p->function, 4) & 4)) - return -5; - rc = device.family == RTL_FAMILY_8125 ? rtl8125_start(&device) : rtl8168_start(&device); - if (rc) - return rc; - device.active = true; - rc = api->register_e1000(&ops); - if (rc) - return rc; - api->console_log(CONSOLE_LOG_INFO, - "[r8169] %02x:%02x.%u %s %04x:%04x XID=%08x MAC=%02x:%02x:%02x:%02x:%02x:%02x; link=%s\n", - p->bus, p->slot, p->function, device.chip_name, p->vendor_id, p->device_id, device.xid, - device.mac[0], device.mac[1], device.mac[2], device.mac[3], device.mac[4], device.mac[5], - ready() ? "up" : "negotiating/down"); - return 0; + int first_error=0; unsigned registered=0; + for (uint32_t index=0;;++index) { + struct reliefos_driver_pci_device pci; + int rc=api->pci_enumerate(index,&pci); + if (rc==-19) break; + if (rc) return rc; + if (pci.vendor_id!=0x10ec || (pci.device_id!=0x8168 && pci.device_id!=0x8125) || + pci.class_code!=2 || pci.subclass!=0) continue; + struct rtl_device *d=kernel_malloc(sizeof(*d)); + if (!d) { + api->console_log(CONSOLE_LOG_WARN,"[r8169] %02x:%02x.%u: no device state memory\n", + pci.bus,pci.slot,pci.function); + if (!first_error) first_error=-12; + break; + } + *d=(struct rtl_device){.api=api,.pci=pci,.next=devices}; devices=d; + rc=rtl_hardware_probe(d); + if (!rc) rc=rtl_hardware_stop(d); + if (!rc) rc=rtl_allocate_rings(d); + if (!rc) { + uint16_t command=api->pci_read16(pci.bus,pci.slot,pci.function,4); + api->pci_write16(pci.bus,pci.slot,pci.function,4,command|4u); + if (!(api->pci_read16(pci.bus,pci.slot,pci.function,4)&4)) rc=-5; + } + if (!rc) rc=d->family==RTL_FAMILY_8125 ? rtl8125_start(d) : rtl8168_start(d); + if (!rc) { + d->active=true; uint32_t ifindex; + rc=driver_register_netdev(&ops,d,&ifindex); + } + if (rc) { + api->console_log(CONSOLE_LOG_WARN,"[r8169] %02x:%02x.%u initialization failed: %d\n", + pci.bus,pci.slot,pci.function,rc); + if (!first_error) first_error=rc; + if (stop_device(d)) return rc; /* manager retries mandatory fini */ + devices=d->next; kernel_free(d); continue; + } + ++registered; + api->console_log(CONSOLE_LOG_INFO, + "[r8169] %02x:%02x.%u %s %04x:%04x XID=%08x MAC=%02x:%02x:%02x:%02x:%02x:%02x; link=%s\n", + pci.bus,pci.slot,pci.function,d->chip_name,pci.vendor_id,pci.device_id,d->xid, + d->mac[0],d->mac[1],d->mac[2],d->mac[3],d->mac[4],d->mac[5], + ready(d)?"up":"negotiating/down"); + } + return registered ? 0 : first_error ? first_error : -19; } -/** @brief Stop and drain hardware before relinquishing owned MMIO and DMA backing. - * A failed reset retains the resources and module through the manager's retry contract. - * Called only after callback admission closes and all outstanding users drain. +/** @brief Stop each controller; failed resets retain their resources for retry. + * Called after callback admission closes and outstanding users drain. */ static void stop(void) { - if (!device.api) - return; - int rc = rtl_hardware_stop(&device); - if (rc) { - device.api->report_teardown_failure(rc); - return; - } - rtl_free_rings(&device); - if (device.registers) - device.api->unmap_mmio((void *)device.registers, device.mmio_bytes); - if (device.command_saved) { - const struct reliefos_driver_pci_device *p = &device.pci; - device.api->pci_write16(p->bus, p->slot, p->function, 4, - device.original_command & ~4u); + struct rtl_device **link=&devices; + while (*link) { + struct rtl_device *d=*link; + int rc=stop_device(d); + if (rc) { d->api->report_teardown_failure(rc); link=&d->next; continue; } + *link=d->next; kernel_free(d); } - device = (struct rtl_device){0}; } -const struct reliefos_driver_module r8169_driver_module = { - .magic = RELIEFOS_DRIVER_MODULE_MAGIC, - .abi_version = RELIEFOS_DRIVER_ABI_VERSION, - .struct_size = sizeof(struct reliefos_driver_module), - .kind = RELIEFOS_DRIVER_KIND_NETWORK, - .name = "r8169", .version = 1, .init = start, .fini = stop, +const struct reliefos_driver_module r8169_driver_module={ + .magic=RELIEFOS_DRIVER_MODULE_MAGIC,.abi_version=RELIEFOS_DRIVER_ABI_VERSION, + .struct_size=sizeof(struct reliefos_driver_module),.kind=RELIEFOS_DRIVER_KIND_NETWORK, + .name="r8169",.version=1,.init=start,.fini=stop, }; diff --git a/include/reliefnt/e1000.h b/include/reliefnt/e1000.h index 05f7b5fd..2a4d50cd 100644 --- a/include/reliefnt/e1000.h +++ b/include/reliefnt/e1000.h @@ -1,6 +1,6 @@ /* - * ReliefOS e1000 driver interface: declares Intel e1000 network operations. - * Connects the PCI-backed NIC driver to the kernel networking layer. + * Compatibility wrappers for the first published Ethernet controller. + * New networking code should address a specific NIC through net_device.h. */ #ifndef RELIEFNT_E1000_H #define RELIEFNT_E1000_H @@ -20,24 +20,34 @@ struct e1000_info { }; /** - * @brief Probe and initialize the Intel e1000 NIC, if one is present. + * @brief Retained startup hook; drivers initialize their own controllers. */ void e1000_init(void); /** - * @brief Return non-zero when the e1000 driver is present and usable. + * @brief Return the first published NIC's current carrier status. + * @return Nonzero when carrier is available; zero otherwise. */ int e1000_is_ready(void); +/** @brief Borrow the first published NIC's core-owned station address. + * @return Six bytes or the zero address. Caller holds network execution ownership. + */ const uint8_t *e1000_mac(void); /** - * @brief Transmit len bytes of frame over the NIC; returns 0, or -ENODEV when absent. + * @brief Transmit through the first published NIC. + * @param frame Borrowed frame bytes. @param len Number of bytes. + * @return Zero or a negative Linux errno, including -ENODEV when absent. */ int e1000_send(const void *frame, uint32_t len); /** - * @brief Receive one packet into frame (up to capacity bytes), writing its size to out_len. + * @brief Receive a packet from the first published NIC. + * @param frame Writable buffer. @param capacity Buffer bytes. + * @param out_len Optional frame length output. + * @return One frame, zero if empty, or a negative Linux errno. */ int e1000_poll(void *frame, uint32_t capacity, uint32_t *out_len); /** - * @brief Fill info with the NIC's presence, PCI location, and MAC address. + * @brief Copy the first published NIC's identity, or zeros when absent. + * @param info Nullable caller-owned identity output. */ void e1000_get_info(struct e1000_info *info); diff --git a/include/reliefnt/net.h b/include/reliefnt/net.h index 232d77ac..e841b307 100644 --- a/include/reliefnt/net.h +++ b/include/reliefnt/net.h @@ -280,7 +280,7 @@ int net_socket_address(int32_t handle, uint32_t owner_pid, */ void net_close_owner_sockets(uint32_t owner_pid); /** - * @brief Drop network state after the NIC driver is removed so callers stop using stale sockets. + * @brief Retire the primary compatibility interface's network state. */ void net_driver_detached(void); /** @@ -291,7 +291,7 @@ void net_device_info(uint32_t *flags, uint64_t *mac_value, uint32_t *local_ip); /** @brief Execute a native Linux interface/route ioctl with checked user memory. */ int net_interface_ioctl(uint32_t request, uint64_t address); -/** @brief Report whether eth0 is administratively up and ready. */ +/** @brief Report whether the primary compatibility NIC is administratively up and ready. */ bool net_interface_ready(void); /** @brief Report a registered Ethernet device independently of carrier state. @@ -301,8 +301,36 @@ bool net_interface_ready(void); bool net_interface_present(void); /** @brief Report the administrative IFF_UP setting independently of carrier. - * @return Current eth0 administrative flag; caller owns network execution. + * @return Current primary-interface administrative flag; caller owns network execution. */ bool net_interface_admin_up(void); +/** @brief Transmit raw IPv4 with an optional bound output device. + * @param ifindex Bound device or zero for route selection. @param destination Peer IPv4. + * @param source Bound source or zero. @param protocol IP protocol. + * @param header_included Payload contains an IPv4 header. @param data Borrowed bytes. + * @param length Byte count. @return Bytes accepted or negative Linux errno. */ +int net_ipv4_send_raw_on(uint32_t ifindex, uint32_t destination, uint32_t source, + uint8_t protocol, bool header_included, const void *data, uint32_t length); +/** @brief Transmit a UDP message through the selected interface and route. + * @param ifindex Bound output device or zero. @param source Optional bound source. + * @param destination Peer IPv4. @param source_port Local port. @param destination_port Peer port. + * @param data Borrowed payload. @param length Byte count. @return Zero or negative errno. */ +int net_ipv4_send_udp_on(uint32_t ifindex, uint32_t source, uint32_t destination, + uint16_t source_port, uint16_t destination_port, const void *data, uint32_t length); + + +/** @brief Deliver a checked ICMPv6 error only to TCP state on the matching link. + * @param ifindex Link or zero. @param source Local. @param destination Peer. + * @param source_port Local port. @param destination_port Peer port. + * @param error Positive errno. @param mtu Reported MTU. */ +void net_tcp6_error_on(uint32_t ifindex, const struct in6_addr *source, + const struct in6_addr *destination, uint16_t source_port, + uint16_t destination_port, int error, uint32_t mtu); + +/** @brief Read or update a TCP socket's SO_BINDTODEVICE interface. + * @param handle Internal socket. @param set Update when true. @param index Input/output. + * @return Zero or negative errno; caller validates privileges and user memory. */ +int net_socket_bound_device(int32_t handle, bool set, uint32_t *index); + #endif diff --git a/include/reliefnt/net_device.h b/include/reliefnt/net_device.h new file mode 100644 index 00000000..b17a98cb --- /dev/null +++ b/include/reliefnt/net_device.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef RELIEFNT_NET_DEVICE_H +#define RELIEFNT_NET_DEVICE_H + +#include + +/* Private kernel/module contract. This is independent of the user ABI. */ +#define RELIEFNT_NETDEV_OPS_VERSION 1u + +struct net_proc_counters; + +struct reliefnt_netdev_ops { + uint32_t version, struct_size; + int (*is_ready)(void *context); + const uint8_t *(*mac)(void *context); + int (*send)(void *context, const void *frame, uint32_t length); + int (*poll)(void *context, void *frame, uint32_t capacity, uint32_t *out_length); + void (*get_info)(void *context, struct e1000_info *out); +}; + +/** @brief Register one NIC owned by the currently initializing driver. + * @param ops Immutable v1 callbacks retained until the owner is stopped. + * @param context Driver-owned per-device state retained through callback drain. + * @param out_ifindex Receives a positive, never-reused interface index. + * @return Zero, -EINVAL, -ENOMEM or -EOVERFLOW. Init task context; the + * registration becomes visible only after successful owner initialization. + */ +int driver_register_netdev(const struct reliefnt_netdev_ops *ops, void *context, + uint32_t *out_ifindex); + +/** @brief Return the smallest live NIC index greater than after. + * @param after Previous index, or zero. @return The next index or zero. + */ +uint32_t netdev_next(uint32_t after); +/** @brief Query carrier readiness of the exact NIC. + * @param ifindex Stable NIC index. @return Nonzero only when ready. + */ +int netdev_is_ready(uint32_t ifindex); +/** @brief Return the NIC MAC in core-owned storage. + * @param ifindex Stable NIC index. @return Six bytes, or a zero address when + * absent. Network execution ownership keeps the returned bytes stable. + */ +const uint8_t *netdev_mac(uint32_t ifindex); +/** @brief Send one Ethernet frame through the exact NIC. + * @param ifindex Stable NIC index. @param frame Borrowed bytes. + * @param length Number of bytes. @return Zero or negative Linux errno. + */ +int netdev_send(uint32_t ifindex, const void *frame, uint32_t length); +/** @brief Receive one frame from the exact NIC. + * @param ifindex Stable NIC index. @param frame Writable buffer. + * @param capacity Buffer bytes. @param out_length Optional returned size. + * @return One frame, zero if empty, or negative Linux errno. + */ +int netdev_poll(uint32_t ifindex, void *frame, uint32_t capacity, uint32_t *out_length); +/** @brief Copy an exact NIC identity, clearing the output if absent. + * @param ifindex Stable NIC index. @param out Caller-owned nullable output. + */ +void netdev_get_info(uint32_t ifindex, struct e1000_info *out); +/** @brief Copy counters observed for the exact NIC, or zeros if absent. + * @param ifindex Stable NIC index. @param out Caller-owned nullable output. + */ +void netdev_counters(uint32_t ifindex, struct net_proc_counters *out); + +/** @brief Notify the network stack after a NIC becomes visible. + * @param ifindex Newly published stable index. Caller owns network execution. + */ +void net_device_attached(uint32_t ifindex); +/** @brief Notify the network stack after a NIC has been removed. + * @param ifindex Removed stable index. Caller owns network execution. + */ +void net_device_detached(uint32_t ifindex); + +#endif diff --git a/include/reliefnt/net_interface.h b/include/reliefnt/net_interface.h new file mode 100644 index 00000000..ae2bfd33 --- /dev/null +++ b/include/reliefnt/net_interface.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef RELIEFNT_NET_INTERFACE_H +#define RELIEFNT_NET_INTERFACE_H + +#include +#include + +/* IPv4 values in this interface are in host byte order. Calls run under the + * network execution owner; pointers returned by get are borrowed until the + * interface is removed. Interface indices are never reused. */ +#define NET_IF_LOOPBACK 1u +#define NET_IF_NAME_LEN 16u + +struct net_interface { + struct net_interface *next; + uint32_t index; + struct reliefos_net_config config; + uint32_t broadcast; + uint32_t default_metric; + bool up; +}; + +struct net_ipv4_route { + uint32_t destination, mask, gateway, ifindex, metric, flags; +}; + +struct net_ipv4_path { + uint32_t source, destination, next_hop, ifindex; +}; + +/** @brief Register one hardware interface; zero or a negative Linux errno. */ +int net_interface_register(uint32_t index); +/** @brief Remove a hardware interface and its routes; no borrowed pointer survives. */ +void net_interface_remove(uint32_t index); +/** @brief Return the live hardware state for index, or NULL; loopback is virtual. */ +struct net_interface *net_interface_get(uint32_t index); +/** @brief Return the next live index after index, including loopback, or zero. */ +uint32_t net_interface_next(uint32_t after); +/** @brief Resolve an exact interface name to its index, or zero if absent. */ +uint32_t net_interface_index(const char *name); +/** @brief Write a NUL-terminated live interface name; false when absent. */ +bool net_interface_name(uint32_t index, char out[NET_IF_NAME_LEN]); +/** @brief Test for a live hardware interface or loopback. */ +bool net_interface_exists(uint32_t index); +/** @brief Test administrative and carrier readiness for an interface. */ +bool net_interface_is_ready(uint32_t index); +/** @brief Test the administrative state of an interface. */ +bool net_interface_is_up(uint32_t index); +/** @brief Set administrative state after authorization; zero or a negative errno. */ +int net_interface_set_up(uint32_t index, bool up); +/** @brief Resolve an assigned IPv4 address to its interface, or zero. */ +uint32_t net_interface_for_ipv4(uint32_t ip); +/** @brief Copy the interface's IPv4 configuration, or return a negative errno. */ +int net_interface_get_config(uint32_t index, struct reliefos_net_config *out); +/** @brief Replace one NIC's primary IPv4 address, mask, and broadcast. */ +int net_interface_configure_ipv4(uint32_t index, uint32_t address, + uint32_t mask, uint32_t broadcast); +/** @brief Access the first live NIC's compatibility state, or stable fallback. */ +struct reliefos_net_config *net_interface_legacy_config(void); +/** @brief Access the first live NIC's administrative flag, or stable fallback. */ +bool *net_interface_legacy_up(void); + +/** @brief Select a live IPv4 route and source; source/scope zero mean automatic. */ +int net_ipv4_route_lookup(uint32_t destination, uint32_t source, uint32_t scope, + struct net_ipv4_path *out); +/** @brief Add, replace, or remove a checked IPv4 route. */ +int net_ipv4_route_update(const struct net_ipv4_route *route, bool remove, bool replace); +/** @brief Copy a route snapshot and return the total number of routes. */ +uint32_t net_ipv4_routes(struct net_ipv4_route *out, uint32_t capacity); + +/** @brief Set/query a bound device with Linux socket permissions and usercopy. + * @param set Update when true. @param address User name. @param length Size or socklen pointer. + * @param index Current/output bound index. @return Zero or negative errno. */ +int net_interface_device_option(bool set, uint64_t address, uint64_t length, uint32_t *index); + +#endif diff --git a/include/reliefnt/net_ipv6.h b/include/reliefnt/net_ipv6.h index ba42c76d..d49771b8 100644 --- a/include/reliefnt/net_ipv6.h +++ b/include/reliefnt/net_ipv6.h @@ -5,7 +5,7 @@ #include struct text_stream; #define NET_IPV6_IF_LOOPBACK 1u -#define NET_IPV6_IF_ETHERNET 2u +#define NET_IPV6_IF_ETHERNET 2u /* Legacy default when no NIC is registered. */ #define NET_IPV6_ADDR_TENTATIVE 0x40u #define NET_IPV6_ADDR_DADFAILED 0x08u #define NET_IPV6_ADDR_DEPRECATED 0x20u @@ -36,19 +36,31 @@ struct net_ipv6_path { }; /** @brief Initialize loopback and Ethernet IPv6 host state without blocking. */ void net_ipv6_init(void); -/** @brief Retire NIC-specific IPv6 state and queued packets after detach. */ +/** @brief Retire state of the first live NIC, or the legacy default when none exists. */ void net_ipv6_detached(void); -/** @brief Apply administrative Ethernet state; never touch driver IRQ ownership. +/** @brief Retire state owned by the specified NIC, including after registry removal. + * @param ifindex Stable interface index. */ +void net_ipv6_detached_on(uint32_t ifindex); +/** @brief Apply Ethernet state to the first live NIC or the legacy default. * @param up New administrative state. */ void net_ipv6_link_changed(bool up); +/** @brief Apply administrative state to one NIC under network execution ownership. + * @param ifindex Stable interface index. @param up New administrative state. + * @return Zero, -ENOMEM when link state cannot be allocated, or -ENODEV for an invalid interface. + * Before initialization, state is applied later by the initialization scan. */ +int net_ipv6_link_changed_on(uint32_t ifindex,bool up); /** @brief Advance bounded DAD, router/neighbor discovery and queued loopback work. */ void net_ipv6_poll(void); /** @brief Indicate receive/timer work requiring the task-context network pump. * @return True when bounded maintenance should remain enabled. */ bool net_ipv6_maintenance_pending(void); -/** @brief Validate and dispatch an IPv6 Ethernet frame. +/** @brief Validate a frame on the first live NIC or the legacy default. * @param frame Borrowed complete Ethernet frame. @param length Available bytes. */ void net_ipv6_input(const uint8_t *frame,uint32_t length); +/** @brief Validate and dispatch a frame received on the specified NIC. + * @param ifindex Stable receiving interface index. @param frame Borrowed Ethernet frame. + * @param length Available bytes. */ +void net_ipv6_input_on(uint32_t ifindex,const uint8_t *frame,uint32_t length); /** @brief Select a route and usable source without borrowing mutable state. * @param destination Required destination. @param scope Explicit interface or zero. * @param bound Optional local source, zero-address means automatic. @param out_path Output. @@ -121,6 +133,10 @@ void net_raw6_input(const struct in6_addr *source,const struct in6_addr *destina * @param packet Quoted original IPv6 packet. @param length Available quote bytes. * @param error Positive Linux errno. @param mtu Reported MTU or zero. */ void net_ipv6_socket_error(const uint8_t *packet,uint32_t length,int error,uint32_t mtu); +/** @brief Deliver an asynchronous error for a specific receiving or transmitting interface. + * @param ifindex Interface index, zero to skip interface filtering. @param packet Quoted packet. + * @param length Available quote bytes. @param error Positive Linux errno. @param mtu MTU or zero. */ +void net_ipv6_socket_error_on(uint32_t ifindex,const uint8_t *packet,uint32_t length,int error,uint32_t mtu); /** @brief Render tcp6/udp6/raw6 socket state without driving I/O. * @param stream Slice output. @param leaf Requested standard proc basename. */ void net_ipv6_transport_proc_emit(struct text_stream *stream,const char *leaf); diff --git a/include/reliefnt/net_packet.h b/include/reliefnt/net_packet.h index e5d642d8..75f7c480 100644 --- a/include/reliefnt/net_packet.h +++ b/include/reliefnt/net_packet.h @@ -78,4 +78,13 @@ void task_packet_release(struct task_file *file); * @return Length, zero when empty, or negative errno. */ int task_packet_available(struct task_file *file); +/** @brief Deliver one Ethernet frame while retaining its original ingress link. + * @param ifindex Stable ingress interface. @param frame Borrowed frame. + * @param length Complete Ethernet frame length. */ +void net_packet_input_on(uint32_t ifindex, const void *frame, uint32_t length); + + +/** @brief Wake raw sockets bound to a disappearing device. @param ifindex Device. */ +void net_packet_device_detached(uint32_t ifindex); + #endif diff --git a/include/reliefnt/net_udp.h b/include/reliefnt/net_udp.h index b16ed91f..26bc0a77 100644 --- a/include/reliefnt/net_udp.h +++ b/include/reliefnt/net_udp.h @@ -50,4 +50,15 @@ void net_udp6_error(const struct in6_addr *source,const struct in6_addr *destina void net_udp6_proc_emit(struct text_stream *stream); void net_udp_input(uint32_t source, uint32_t destination, uint16_t source_port, uint16_t destination_port, const void *data, uint32_t length, int ifindex); + +/** @brief Deliver a checked ICMPv6 error only to UDP state on the matching link. + * @param ifindex Link or zero. @param source Local. @param destination Peer. + * @param source_port Local port. @param destination_port Peer port. + * @param error Positive errno. @param mtu Reported MTU. */ +void net_udp6_error_on(uint32_t ifindex, const struct in6_addr *source, + const struct in6_addr *destination, uint16_t source_port, + uint16_t destination_port, int error, uint32_t mtu); +/** @brief Wake UDP sockets bound to a disappearing device. @param ifindex Device. */ +void net_udp_device_detached(uint32_t ifindex); + #endif diff --git a/include/uapi/linux/if.h b/include/uapi/linux/if.h index 419d2f1f..89db8a57 100644 --- a/include/uapi/linux/if.h +++ b/include/uapi/linux/if.h @@ -37,6 +37,8 @@ struct rtentry { #define SIOCSIFNETMASK 0x891c #define SIOCGIFMTU 0x8921 #define SIOCSIFMTU 0x8922 +#define SIOCGIFTXQLEN 0x8942 +#define SIOCSIFTXQLEN 0x8943 #define SIOCGIFHWADDR 0x8927 #define SIOCGIFINDEX 0x8933 #define SIOCDIFADDR 0x8936 diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index c67bcdb6..0164a91a 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -13,6 +13,7 @@ struct rtmsg { uint8_t rtm_family, rtm_dst_len, rtm_src_len, rtm_tos; #define RTM_NEWLINK 16 #define RTM_DELLINK 17 #define RTM_GETLINK 18 +#define RTM_SETLINK 19 #define RTM_NEWADDR 20 #define RTM_DELADDR 21 #define RTM_GETADDR 22 diff --git a/kernel/device/manager_internal.h b/kernel/device/manager_internal.h index 21dfdbe1..2ff25d36 100644 --- a/kernel/device/manager_internal.h +++ b/kernel/device/manager_internal.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -99,13 +100,22 @@ int driver_register_mouse(const struct reliefos_driver_mouse_ops *ops); int driver_register_serial(const struct reliefos_driver_serial_ops *ops); /** - * @brief Bind the loading driver's Ethernet ops without replacing another owner. + * @brief Adapt the loading driver's legacy singleton Ethernet callbacks. * @param ops Validated callbacks retained by the current loading driver slot. - * @return Zero, -EINVAL for invalid registration, or -EBUSY when eth0 already - * belongs to another provider. Task init; IRQ-safe service lock, no callback. + * @return Zero, -EINVAL, allocation/index error, or -EBUSY when another legacy + * provider exists. Task init; no callback under the service lock. */ int driver_register_e1000(const struct reliefos_driver_e1000_ops *ops); +/** @brief Publish completed NIC registrations and notify the network stack. + * @param owner Loaded driver slot. Caller owns network execution. + */ +void driver_netdev_publish_owner(uint32_t owner); +/** @brief Remove an owner's drained NICs and notify the network stack. + * @param owner Closed and drained driver slot. Caller owns network execution. + */ +void driver_netdev_remove_owner(uint32_t owner); + /** * @brief Bind the loading driver's v1 backend with a non-reused generation. * @param ops Borrowed validated callbacks, owned by the loaded image. diff --git a/kernel/device/registry.c b/kernel/device/registry.c index d6d34c42..76137023 100644 --- a/kernel/device/registry.c +++ b/kernel/device/registry.c @@ -42,7 +42,8 @@ static int driver_run_fini(struct driver_slot *slot, const struct reliefos_driver_module *module); /** - * @brief Detach any device ops owned by slot_id and reset their state (notifying net on e1000). + * @brief Detach callbacks and all network devices belonging to one driver slot. + * @param slot_id Closed and drained driver slot. Caller owns network execution. */ static void driver_clear_services(uint32_t slot_id); @@ -180,11 +181,11 @@ static int driver_run_fini(struct driver_slot *slot, } /** - * @brief Detach any device ops owned by slot_id and reset their state (notifying net on e1000). + * @brief Detach callbacks and all network devices belonging to one driver slot. + * @param slot_id Closed and drained driver slot. Caller owns network execution. */ static void driver_clear_services(uint32_t slot_id) { - bool detach_network = false; driver_manager_service_disable(slot_id); uint64_t flags; kernel_spin_lock_irqsave(&manager_service_lock, &flags); @@ -198,7 +199,6 @@ static void driver_clear_services(uint32_t slot_id) serial_owner = 0; } if (e1000_ops && e1000_owner == slot_id) { - detach_network = true; e1000_ops = 0; e1000_owner = 0; } @@ -207,7 +207,7 @@ static void driver_clear_services(uint32_t slot_id) audio_owner = 0; } kernel_spin_unlock_irqrestore(&manager_service_lock, flags); - if (detach_network) net_driver_detached(); + driver_netdev_remove_owner(slot_id); } /** @@ -307,6 +307,7 @@ static int driver_load_registered(struct driver_slot *slot, slot->info.state = RELIEFOS_DRIVER_STATE_LOADED; slot->info.error[0] = 0; driver_manager_service_enable(slot->info.id); + driver_netdev_publish_owner(slot->info.id); console_info("[driver] loaded %s abi=%u\n", module->name, slot->info.abi_version); return 0; diff --git a/kernel/device/services.c b/kernel/device/services.c index 7d7d39eb..3cfe7550 100644 --- a/kernel/device/services.c +++ b/kernel/device/services.c @@ -1,4 +1,6 @@ #include +#include +#include #include "manager_internal.h" /** @@ -15,17 +17,61 @@ static const struct reliefos_driver_mouse_ops *driver_mouse_service_pin(uint32_t */ static const struct reliefos_driver_serial_ops *driver_serial_service_pin(uint32_t *out_owner); -/** - * @brief Capture and pin one published e1000 callback table with its matching owner. - * @param out_owner Receives the slot whose callback pin must be released. - * @return Consistent local ops snapshot, or NULL while absent/closing. - */ -static const struct reliefos_driver_e1000_ops *driver_e1000_service_pin(uint32_t *out_owner); - static bool mouse_visible = true; -static uint8_t e1000_empty_mac[6]; -static uint8_t e1000_mac_cache[6]; -static struct kernel_spinlock e1000_mac_cache_lock = KERNEL_SPINLOCK_INIT; +static const uint8_t netdev_empty_mac[6]; + +struct netdev_binding { + struct netdev_binding *next; + struct reliefnt_netdev_ops ops; + void *context; + uint32_t owner, ifindex; + bool published; + uint8_t mac_cache[6]; + struct net_proc_counters counters; +}; +static struct netdev_binding *netdev_bindings; +static uint32_t netdev_next_ifindex = 2; +static struct net_proc_counters proc_nic; + +/** @brief Adapt a legacy single-instance provider to the context contract. + * @param context Original immutable callback table. @return Carrier state. + */ +static int legacy_netdev_ready(void *context) +{ return ((const struct reliefos_driver_e1000_ops *)context)->is_ready(); } +/** @brief Borrow the legacy provider's station address. + * @param context Original callback table. @return Six borrowed MAC bytes. + */ +static const uint8_t *legacy_netdev_mac(void *context) +{ return ((const struct reliefos_driver_e1000_ops *)context)->mac(); } +/** @brief Dispatch a frame to the original single-instance provider. + * @param context Original callback table. @param frame Borrowed bytes. + * @param length Frame bytes. @return Provider result. + */ +static int legacy_netdev_send(void *context, const void *frame, uint32_t length) +{ return ((const struct reliefos_driver_e1000_ops *)context)->send(frame, length); } +/** @brief Poll the original single-instance provider. + * @param context Original table. @param frame Output bytes. + * @param capacity Output capacity. @param length Output length. @return Provider result. + */ +static int legacy_netdev_poll(void *context, void *frame, uint32_t capacity, uint32_t *length) +{ return ((const struct reliefos_driver_e1000_ops *)context)->poll(frame, capacity, length); } +/** @brief Convert the legacy identity to the private network identity. + * @param context Original callback table. @param out Caller-owned identity. + */ +static void legacy_netdev_info(void *context, struct e1000_info *out) +{ + struct reliefos_driver_e1000_info source = {0}; + ((const struct reliefos_driver_e1000_ops *)context)->get_info(&source); + *out = (struct e1000_info){.present=source.present, .active=source.active, + .vendor_id=source.vendor_id, .device_id=source.device_id, + .bus=source.bus, .slot=source.slot, .function=source.function}; + driver_memcpy(out->mac, source.mac, sizeof(out->mac)); +} +static const struct reliefnt_netdev_ops legacy_netdev_ops = { + RELIEFNT_NETDEV_OPS_VERSION, sizeof(struct reliefnt_netdev_ops), + legacy_netdev_ready, legacy_netdev_mac, legacy_netdev_send, + legacy_netdev_poll, legacy_netdev_info +}; /** * @brief Bind the currently-loading driver's mouse ops as the active mouse backend. */ @@ -59,10 +105,10 @@ int driver_register_serial(const struct reliefos_driver_serial_ops *ops) } /** - * @brief Bind the loading driver's Ethernet ops without replacing another owner. + * @brief Adapt the loading driver's legacy singleton Ethernet callbacks. * @param ops Validated callbacks retained by the current loading driver slot. - * @return Zero, -EINVAL for invalid registration, or -EBUSY when eth0 already - * belongs to another provider. Task init; IRQ-safe service lock, no callback. + * @return Zero, -EINVAL, allocation/index error, or -EBUSY when another legacy + * provider exists. Task init; no callback under the service lock. */ int driver_register_e1000(const struct reliefos_driver_e1000_ops *ops) { @@ -72,10 +118,15 @@ int driver_register_e1000(const struct reliefos_driver_e1000_ops *ops) } uint64_t flags; kernel_spin_lock_irqsave(&manager_service_lock, &flags); - if (e1000_ops && e1000_owner != (uint32_t)loading_slot) { + if (e1000_ops) { kernel_spin_unlock_irqrestore(&manager_service_lock, flags); return -16; } + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + uint32_t ifindex; + int ret = driver_register_netdev(&legacy_netdev_ops, (void *)ops, &ifindex); + if (ret) return ret; + kernel_spin_lock_irqsave(&manager_service_lock, &flags); e1000_ops = ops; e1000_owner = (uint32_t)loading_slot; kernel_spin_unlock_irqrestore(&manager_service_lock, flags); @@ -142,24 +193,6 @@ static const struct reliefos_driver_serial_ops *driver_serial_service_pin(uint32 return ops; } -/** - * @brief Capture and pin one published e1000 callback table with its matching owner. - * @param out_owner Receives the slot whose callback pin must be released. - * @return Consistent local ops snapshot, or NULL while absent/closing. - */ -static const struct reliefos_driver_e1000_ops *driver_e1000_service_pin(uint32_t *out_owner) -{ - if (!out_owner) return NULL; - uint64_t flags; - kernel_spin_lock_irqsave(&manager_service_lock, &flags); - const struct reliefos_driver_e1000_ops *ops = e1000_ops; - uint32_t owner = e1000_owner; - if (!ops || !driver_manager_service_try_pin(owner)) ops = NULL; - else *out_owner = owner; - kernel_spin_unlock_irqrestore(&manager_service_lock, flags); - return ops; -} - /** * @brief Capture and pin one published audio callback table with its matching owner. * @param generation Exact OSS lease generation, or zero for native compatibility. @@ -370,140 +403,233 @@ void serial_write(const char *text) early_serial_write(text); } -/** - * @brief Reserved startup hook; the NIC is brought up by its driver's init(). +/** @brief Register a provider context without exposing it before init succeeds. + * @param ops Immutable v1 callback table. @param context Driver-owned device. + * @param out_ifindex Receives the never-reused interface identity. + * @return Zero or -EINVAL/-ENOMEM/-EOVERFLOW; init task context. */ -void e1000_init(void) +int driver_register_netdev(const struct reliefnt_netdev_ops *ops, void *context, + uint32_t *out_ifindex) { + if (loading_slot < 0 || (uint32_t)loading_slot >= RELIEFOS_DRIVER_MAX || + !out_ifindex || !context || !ops || + ops->version != RELIEFNT_NETDEV_OPS_VERSION || ops->struct_size != sizeof(*ops) || + !ops->is_ready || !ops->mac || !ops->send || !ops->poll || !ops->get_info) + return -22; + struct netdev_binding *binding = kernel_malloc(sizeof(*binding)); + if (!binding) return -12; + *binding = (struct netdev_binding){.ops=*ops, .context=context, .owner=(uint32_t)loading_slot}; + uint64_t flags; + kernel_spin_lock_irqsave(&manager_service_lock, &flags); + if (netdev_next_ifindex > 0x7fffffffu) { + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + kernel_free(binding); + return -75; + } + binding->ifindex = netdev_next_ifindex++; + struct netdev_binding **tail = &netdev_bindings; + while (*tail) tail = &(*tail)->next; + *tail = binding; + *out_ifindex = binding->ifindex; + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + return 0; } +EXPORT_SYMBOL(driver_register_netdev); -/** - * @brief Return 1 when an e1000 driver is registered and reports ready. - * @return Driver result or 0 while absent/closing; the matching ops/owner stays - * pinned until is_ready returns. +/** @brief Publish one completed owner's NICs before issuing unlocked notifications. + * @param owner Loaded driver slot. Caller holds network execution ownership. */ -int e1000_is_ready(void) +void driver_netdev_publish_owner(uint32_t owner) { - uint32_t owner; - const struct reliefos_driver_e1000_ops *ops = driver_e1000_service_pin(&owner); - if (!ops) return 0; - int ready = ops->is_ready ? ops->is_ready() : 0; - driver_manager_service_unpin(owner); - return ready; + for (;;) { + uint64_t flags; uint32_t id = 0; + kernel_spin_lock_irqsave(&manager_service_lock, &flags); + for (struct netdev_binding *b=netdev_bindings; b; b=b->next) + if (b->owner == owner && !b->published) { b->published=true; id=b->ifindex; break; } + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + if (!id) break; + net_device_attached(id); + } } -/** - * @brief Return a kernel-owned copy of the NIC MAC, or a zeroed placeholder when absent. - * @return Manager cache pointer; the module's borrowed pointer is copied while its - * matching ops/owner snapshot remains pinned. +/** @brief Remove a drained owner's NICs and notify after each registry unlink. + * @param owner Closed and drained driver slot. Caller holds network execution. */ -const uint8_t *e1000_mac(void) +void driver_netdev_remove_owner(uint32_t owner) { - uint32_t owner; - const struct reliefos_driver_e1000_ops *ops = driver_e1000_service_pin(&owner); - if (!ops || !ops->mac) { - if (ops) driver_manager_service_unpin(owner); - return e1000_empty_mac; + for (;;) { + uint64_t flags; + kernel_spin_lock_irqsave(&manager_service_lock, &flags); + struct netdev_binding **link=&netdev_bindings; + while (*link && (*link)->owner != owner) link=&(*link)->next; + struct netdev_binding *b=*link; + if (b) *link=b->next; + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + if (!b) break; + uint32_t id=b->ifindex; bool published=b->published; + kernel_free(b); + if (published) net_device_detached(id); } - const uint8_t *borrowed = ops->mac(); +} + +/** @brief Pin the exact published NIC and its original module callback owner. + * @param ifindex Stable interface index. @return Retained binding or NULL; + * caller releases the owner pin before allowing registry removal. + */ +static struct netdev_binding *netdev_pin(uint32_t ifindex) +{ uint64_t flags; - kernel_spin_lock_irqsave(&e1000_mac_cache_lock, &flags); - if (borrowed) driver_memcpy(e1000_mac_cache, borrowed, sizeof(e1000_mac_cache)); - else driver_memzero(e1000_mac_cache, sizeof(e1000_mac_cache)); - kernel_spin_unlock_irqrestore(&e1000_mac_cache_lock, flags); - driver_manager_service_unpin(owner); - return e1000_mac_cache; + kernel_spin_lock_irqsave(&manager_service_lock, &flags); + struct netdev_binding *b=netdev_bindings; + while (b && (!b->published || b->ifindex != ifindex)) b=b->next; + if (b && !driver_manager_service_try_pin(b->owner)) b=NULL; + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + return b; } -static struct net_proc_counters proc_nic; +/** @brief Find the next published NIC in stable index order. + * @param after Previous index or zero. @return Next index or zero. + */ +uint32_t netdev_next(uint32_t after) +{ + uint32_t result=0; uint64_t flags; + kernel_spin_lock_irqsave(&manager_service_lock, &flags); + for (struct netdev_binding *b=netdev_bindings; b; b=b->next) + if (b->published && b->ifindex > after && (!result || b->ifindex < result)) result=b->ifindex; + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + return result; +} -/** - * @brief Send an Ethernet frame through the driver, or -19 when no driver is ready. - * @param frame Kernel-owned frame borrowed through send callback return. - * @param len Number of bytes available at frame. - * @return Driver result or -19 while absent/closing; one pin spans readiness and send. +/** @brief Report the current carrier for one retained NIC. + * @param ifindex Stable index. @return Nonzero when its provider reports ready. */ -int e1000_send(const void *frame, uint32_t len) +int netdev_is_ready(uint32_t ifindex) { - uint32_t owner; - const struct reliefos_driver_e1000_ops *ops = driver_e1000_service_pin(&owner); - if (!ops) return -19; - int ret = ops->is_ready && ops->send && ops->is_ready() - ? ops->send(frame, len) : -19; - if (!ret) { - proc_nic.tx_bytes += len; - ++proc_nic.tx_packets; - } else - ++proc_nic.tx_errors; - driver_manager_service_unpin(owner); - return ret; + struct netdev_binding *b=netdev_pin(ifindex); + if (!b) return 0; + uint32_t owner=b->owner; int ready=b->ops.is_ready(b->context); + driver_manager_service_unpin(owner); return ready; } -/** - * @brief Poll received frames from the driver, or -19 when no driver is ready. - * @param frame Kernel-owned receive buffer. - * @param capacity Bytes available at frame. - * @param out_len Receives the copied frame length when supplied by the driver. - * @return Driver result or -19 while absent/closing; one pin spans readiness and poll. +/** @brief Copy a pinned NIC's address into core-owned per-NIC storage. + * @param ifindex Stable index. @return Six bytes stable under network execution. */ -int e1000_poll(void *frame, uint32_t capacity, uint32_t *out_len) +const uint8_t *netdev_mac(uint32_t ifindex) { - uint32_t owner; - const struct reliefos_driver_e1000_ops *ops = driver_e1000_service_pin(&owner); - if (!ops) return -19; - uint32_t received = 0; - int ret = - ops->is_ready && ops->poll && ops->is_ready() ? ops->poll(frame, capacity, &received) : -19; - if (ret > 0) { - proc_nic.rx_bytes += received; - ++proc_nic.rx_packets; - } else if (ret < 0) { - ++proc_nic.rx_errors; - if (ret == -90 || ret == -74) - ++proc_nic.rx_drops; - } - if (out_len) - *out_len = received; - driver_manager_service_unpin(owner); - return ret; + struct netdev_binding *b=netdev_pin(ifindex); + if (!b) return netdev_empty_mac; + uint32_t owner=b->owner; + const uint8_t *mac=b->ops.mac(b->context); + uint64_t flags; kernel_spin_lock_irqsave(&manager_service_lock, &flags); + if (mac) driver_memcpy(b->mac_cache, mac, 6); else driver_memzero(b->mac_cache, 6); + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + const uint8_t *result=b->mac_cache; + driver_manager_service_unpin(owner); return result; } -/** - * @brief Copy NIC identity independently of carrier state. - * @param info Receives kernel-owned identity fields and MAC bytes. - * @return None. Driver output is copied to a stack snapshot before unpin. +/** @brief Send a frame and record its actual interface's transport counters. + * @param ifindex Stable index. @param frame Borrowed frame bytes. + * @param length Frame size. @return Provider result or -ENODEV. */ -void e1000_get_info(struct e1000_info *info) +int netdev_send(uint32_t ifindex, const void *frame, uint32_t length) { - struct reliefos_driver_e1000_info source; - if (!info) { - return; - } - *info = (struct e1000_info){0}; - uint32_t owner; - const struct reliefos_driver_e1000_ops *ops = driver_e1000_service_pin(&owner); - if (!ops || !ops->get_info) { - if (ops) driver_manager_service_unpin(owner); - return; + struct netdev_binding *b=netdev_pin(ifindex); + if (!b) return -19; + uint32_t owner=b->owner; + int ret=b->ops.is_ready(b->context) ? b->ops.send(b->context,frame,length) : -19; + uint64_t flags; kernel_spin_lock_irqsave(&manager_service_lock, &flags); + if (!ret) { b->counters.tx_bytes+=length; ++b->counters.tx_packets; + proc_nic.tx_bytes+=length; ++proc_nic.tx_packets; } + else { ++b->counters.tx_errors; ++proc_nic.tx_errors; } + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + driver_manager_service_unpin(owner); return ret; +} + +/** @brief Receive one frame and account it to its actual interface. + * @param ifindex Stable index. @param frame Writable buffer. + * @param capacity Buffer bytes. @param out_length Optional returned bytes. + * @return One frame, zero for empty, or a negative Linux errno. + */ +int netdev_poll(uint32_t ifindex, void *frame, uint32_t capacity, uint32_t *out_length) +{ + if (out_length) *out_length=0; + struct netdev_binding *b=netdev_pin(ifindex); + if (!b) return -19; + uint32_t owner=b->owner, length=0; + if (!b->ops.is_ready(b->context)) { + /* No receive descriptor was inspected while carrier is unavailable. */ + driver_manager_service_unpin(owner); + return -19; } - ops->get_info(&source); + int ret=b->ops.poll(b->context,frame,capacity,&length); + uint64_t flags; kernel_spin_lock_irqsave(&manager_service_lock, &flags); + if (ret>0) { b->counters.rx_bytes+=length; ++b->counters.rx_packets; + proc_nic.rx_bytes+=length; ++proc_nic.rx_packets; } + else if (ret<0) { ++b->counters.rx_errors; ++proc_nic.rx_errors; + if (ret==-90 || ret==-74) { ++b->counters.rx_drops; ++proc_nic.rx_drops; } } + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); + if (out_length) *out_length=length; + driver_manager_service_unpin(owner); return ret; +} + +/** @brief Copy one provider's PCI identity and carrier state while pinned. + * @param ifindex Stable index. @param out Caller-owned nullable output. + */ +void netdev_get_info(uint32_t ifindex, struct e1000_info *out) +{ + if (!out) return; + *out=(struct e1000_info){0}; + struct netdev_binding *b=netdev_pin(ifindex); + if (!b) return; + uint32_t owner=b->owner; + b->ops.get_info(b->context,out); driver_manager_service_unpin(owner); - info->present = source.present; - info->active = source.active; - info->vendor_id = source.vendor_id; - info->device_id = source.device_id; - info->bus = source.bus; - info->slot = source.slot; - info->function = source.function; - for (uint32_t index = 0; index < 6; ++index) { - info->mac[index] = source.mac[index]; - } } -/** @brief Read core-observed Ethernet transport completions. - * @param out Required scalar output; execution ownership, no provider callbacks. +/** @brief Copy counters for a live exact identity without entering provider code. + * @param ifindex Stable index. @param out Caller-owned nullable output. + */ +void netdev_counters(uint32_t ifindex, struct net_proc_counters *out) +{ + if (!out) return; + *out=(struct net_proc_counters){0}; + uint64_t flags; kernel_spin_lock_irqsave(&manager_service_lock, &flags); + for (struct netdev_binding *b=netdev_bindings; b; b=b->next) + if (b->published && b->ifindex==ifindex) { *out=b->counters; break; } + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); +} + +/** @brief Reserved startup hook; drivers initialize their own controllers. */ +void e1000_init(void) { } +/** @brief Report carrier on the earliest published interface. + * @return Nonzero while that interface has carrier. + */ +int e1000_is_ready(void) { return netdev_is_ready(netdev_next(0)); } +/** @brief Borrow the earliest published interface's core-owned MAC. + * @return Six bytes valid while the caller owns network execution. + */ +const uint8_t *e1000_mac(void) { return netdev_mac(netdev_next(0)); } +/** @brief Send on the earliest published interface. + * @param frame Borrowed bytes. @param len Frame length. @return Provider result. + */ +int e1000_send(const void *frame, uint32_t len) { return netdev_send(netdev_next(0),frame,len); } +/** @brief Poll the earliest published interface. + * @param frame Writable buffer. @param capacity Buffer bytes. @param out_len Optional length. + * @return One frame, zero or a negative errno. + */ +int e1000_poll(void *frame, uint32_t capacity, uint32_t *out_len) +{ return netdev_poll(netdev_next(0),frame,capacity,out_len); } +/** @brief Copy identity for the earliest published interface. + * @param info Caller-owned nullable output. + */ +void e1000_get_info(struct e1000_info *info) { netdev_get_info(netdev_next(0),info); } +/** @brief Copy aggregate core-observed NIC counters. + * @param out Required caller-owned output. */ void net_device_counters(struct net_proc_counters *out) { - *out = proc_nic; + if (!out) return; + uint64_t flags; kernel_spin_lock_irqsave(&manager_service_lock, &flags); + *out=proc_nic; + kernel_spin_unlock_irqrestore(&manager_service_lock, flags); } diff --git a/kernel/syscall/ipc/socket.c b/kernel/syscall/ipc/socket.c index 9839ecd2..a0d913fe 100644 --- a/kernel/syscall/ipc/socket.c +++ b/kernel/syscall/ipc/socket.c @@ -3,6 +3,7 @@ #include #include "unix_path.h" #include +#include #include #include #include @@ -1239,6 +1240,14 @@ void task_inet_release(struct task_file *file) static int inet_socket_option(bool setting, int level, int option, uint64_t address, uint64_t length, int32_t handle) { + if (level == SOL_SOCKET && option == SO_BINDTODEVICE) { + uint32_t index = 0; + int result = net_socket_bound_device(handle, false, &index); + if (result < 0) return result; + result = net_interface_device_option(setting, address, length, &index); + if (result < 0) return result; + return setting ? net_socket_bound_device(handle, true, &index) : 0; + } if (level == SOL_IPV6 || (level == SOL_SOCKET && option == SO_REUSEADDR)) { int value = 0; uint32_t size = (uint32_t)length; if (setting) { diff --git a/net/core.c b/net/core.c index 279f2354..0a2861dd 100644 --- a/net/core.c +++ b/net/core.c @@ -13,11 +13,10 @@ uint32_t net_sequence = 1; uint16_t net_ipv4_id = 1; -struct reliefos_net_config net_config; -bool net_interface_up = true; struct net_socket *net_sockets; +static bool net_registration_pending; /** * Net log config. @@ -63,7 +62,9 @@ void net_init(void) */ int net_is_ready(void) { - return e1000_is_ready(); + for (uint32_t index = netdev_next(0); index; index = netdev_next(index)) + if (net_interface_is_ready(index)) return 1; + return 0; } /** @@ -71,15 +72,65 @@ int net_is_ready(void) * @note Every socket waiter is woken before the network stack stops using its state. */ void net_driver_detached(void) +{ + /* Compatibility entry point for callers retiring the primary NIC. */ + uint32_t index = netdev_next(0); + if (index) net_device_detached(index); +} + +/** @brief Publish network configuration for a newly admitted hardware device. + * @param ifindex Stable index owned by the device registry. Allocation failure + * leaves the device unconfigured and is reported in the kernel log. */ +void net_device_attached(uint32_t ifindex) +{ + int result = net_interface_register(ifindex); + if (result < 0) { + net_registration_pending = true; + console_error("[reliefnt] network interface %u registration failed: %d\n", ifindex, result); + return; + } + net_ipv6_link_changed_on(ifindex, net_interface_is_up(ifindex)); + netlink_ipv6_changed(); +} + +/** @brief Retire sockets and discovery state belonging to one removed device. + * @param ifindex Non-reused hardware interface index. Other interfaces retain + * their addresses, routes, neighbors and connections. Execution ownership held. */ +void net_device_detached(uint32_t ifindex) { for (struct net_socket *s = net_sockets; s; s = s->registry_next) { - if (s->used) { + if (s->used && s->ifindex == ifindex) { s->error = LINUX_ENETDOWN; net_socket_mark_closed(s, RELIEFOS_NET_STATUS_NO_DEVICE); } } - net_ipv6_detached(); - net_arp_cache_clear(); - net_set_static_fallback(); - net_update_config_flags(); + net_udp_device_detached(ifindex); + net_packet_device_detached(ifindex); + net_ipv6_detached_on(ifindex); + net_arp_cache_clear_on(ifindex); + net_interface_remove(ifindex); + netlink_ipv6_changed(); +} + +/** @brief Report whether a failed device-state allocation needs another pass. + * @return True while the bounded maintenance pump must retry registration. */ +bool net_device_registration_pending(void) +{ + return net_registration_pending; +} +/** @brief Retry one missing device's network state after transient allocation failure. + * Failed retries remain silent. Success enables discovery, and a subsequent + * scan clears the retry hint after every published NIC has network state. */ +void net_device_retry_registration(void) +{ + if (!net_registration_pending) return; + for (uint32_t index = netdev_next(0); index; index = netdev_next(index)) { + if (net_interface_get(index)) continue; + if (!net_interface_register(index)) { + net_ipv6_link_changed_on(index, net_interface_is_up(index)); + netlink_ipv6_changed(); + } + return; + } + net_registration_pending = false; } diff --git a/net/input.c b/net/input.c index f8f6a4d6..1a0283ed 100644 --- a/net/input.c +++ b/net/input.c @@ -15,7 +15,7 @@ static bool net_tcp_maintenance_tick_valid; * @param udp_wait Value supplied by the caller. * @param tcp_wait Value supplied by the caller. */ -static void net_process_frame(const uint8_t *frame, uint32_t len, +static void net_process_frame(uint32_t ifindex, const uint8_t *frame, uint32_t len, struct net_arp_wait *arp_wait, struct net_ping_wait *ping_wait, struct net_udp_wait *udp_wait, @@ -31,23 +31,23 @@ static void net_process_frame(const uint8_t *frame, uint32_t len, * @param udp_wait Value supplied by the caller. * @param tcp_wait Value supplied by the caller. */ -static void net_process_frame(const uint8_t *frame, uint32_t len, +static void net_process_frame(uint32_t ifindex, const uint8_t *frame, uint32_t len, struct net_arp_wait *arp_wait, struct net_ping_wait *ping_wait, struct net_udp_wait *udp_wait, struct net_tcp_wait *tcp_wait) { uint16_t type; - if (len < 14 || !net_frame_for_us(frame)) { + if (len < 14 || !net_frame_for_us_on(ifindex, frame)) { return; } type = net_get_u16(frame + 12); if (type == ETH_TYPE_ARP) { - net_handle_arp(frame, len, arp_wait); + net_handle_arp_on(ifindex, frame, len, arp_wait); } else if (type == ETH_TYPE_IPV4) { - net_handle_ipv4(frame, len, ping_wait, udp_wait, tcp_wait); + net_handle_ipv4_on(ifindex, frame, len, ping_wait, udp_wait, tcp_wait); } else if (type == 0x86dd) { - net_ipv6_input(frame, len); + net_ipv6_input_on(ifindex, frame, len); } } @@ -65,12 +65,19 @@ void net_poll_once(struct net_arp_wait *arp_wait, struct net_udp_wait *udp_wait, struct net_tcp_wait *tcp_wait) { + net_device_retry_registration(); uint8_t frame[NET_FRAME_MAX]; uint32_t len = 0; - int ret = e1000_poll(frame, sizeof(frame), &len); - if (ret > 0 && len && net_interface_up) { - net_packet_input(frame, len); - net_process_frame(frame, len, arp_wait, ping_wait, udp_wait, tcp_wait); + static uint32_t previous; + uint32_t ifindex = netdev_next(previous); + if (!ifindex) ifindex = netdev_next(0); + previous = ifindex; + if (ifindex && net_interface_is_up(ifindex)) { + int ret = netdev_poll(ifindex, frame, sizeof(frame), &len); + if (ret > 0 && len) { + net_packet_input_on(ifindex, frame, len); + net_process_frame(ifindex, frame, len, arp_wait, ping_wait, udp_wait, tcp_wait); + } } net_tcp_service_sockets(); net_ipv6_poll(); @@ -89,7 +96,7 @@ void net_poll_packets(void) */ bool net_tcp_maintenance_due(void) { - return ((net_tcp_maintenance_pending() && net_interface_up) || + return (net_device_registration_pending() || net_tcp_maintenance_pending() || net_ipv6_maintenance_pending()) && (!net_tcp_maintenance_tick_valid || time_ticks() != net_tcp_maintenance_last_tick); @@ -108,7 +115,7 @@ void net_tcp_maintenance(void) net_tcp_maintenance_last_tick = time_ticks(); for (uint32_t i = 0; i < NET_TCP_MAINTENANCE_POLL_BUDGET && - (net_tcp_maintenance_pending() || net_ipv6_maintenance_pending()); ++i) { + (net_device_registration_pending() || net_tcp_maintenance_pending() || net_ipv6_maintenance_pending()); ++i) { net_poll_once(0, 0, 0, 0); } } diff --git a/net/interface/Kbuild b/net/interface/Kbuild index 92650392..9ee13f13 100644 --- a/net/interface/Kbuild +++ b/net/interface/Kbuild @@ -1,4 +1,4 @@ # SPDX-License-Identifier: Apache-2.0 -obj-y := config.o +obj-y := config.o state.o routes.o ioctl.o socket.o include $(srctree)/scripts/Makefile.module diff --git a/net/interface/config.c b/net/interface/config.c index abe7a40a..eb1966db 100644 --- a/net/interface/config.c +++ b/net/interface/config.c @@ -51,7 +51,6 @@ static uint32_t net_effective_dns_ip(uint32_t dhcp_dns_ip); static void net_expire_lease(void); -static uint32_t net_interface_broadcast; static uint32_t net_dns_mode = RELIEFOS_NET_DNS_MODE_DHCP; static uint32_t net_dns_custom_ip; static uint32_t net_dhcp_dns_ip; @@ -235,7 +234,10 @@ void net_update_config_flags(void) */ void net_set_static_fallback(void) { - net_arp_cache_clear(); + uint32_t index = net_interface_next(NET_IF_LOOPBACK); + struct net_interface *iface = net_interface_get(index); + net_arp_cache_clear_on(index); + if (iface) { iface->broadcast = 0; iface->default_metric = 0; } net_dhcp_dns_ip = 0; net_dhcp_acquired_ms = 0; net_config = (struct reliefos_net_config){ @@ -264,9 +266,15 @@ static void net_expire_lease(void) */ void net_apply_dhcp_offer(const struct net_dhcp_offer *offer) { - net_arp_cache_clear(); + uint32_t index = net_interface_next(NET_IF_LOOPBACK); + struct net_interface *iface = net_interface_get(index); + net_arp_cache_clear_on(index); net_config.local_ip = offer->yiaddr; net_config.subnet_mask = offer->subnet_mask ? offer->subnet_mask : RELIEFOS_NET_DEFAULT_SUBNET_MASK; + if (iface) { + iface->broadcast = net_config.local_ip | ~net_config.subnet_mask; + iface->default_metric = 0; + } net_config.gateway_ip = offer->router_ip; net_dhcp_dns_ip = offer->dns_ip; net_config.dns_ip = net_effective_dns_ip(net_dhcp_dns_ip); @@ -441,178 +449,6 @@ void net_device_info(uint32_t *flags, uint64_t *mac_value, uint32_t *local_ip) } } -/** @brief Apply/query network interfaces through the Linux native ioctl ABI. - * Loopback name/index and the real Ethernet interface/default route are exposed. - * Unsupported routing/features return an error rather than retaining inert state. - */ -int net_interface_ioctl(uint32_t request, uint64_t address) -{ - bool change = request == SIOCSIFFLAGS || request == SIOCSIFADDR || - request == SIOCSIFNETMASK || request == SIOCSIFBRDADDR || - request == SIOCSIFMTU || request == SIOCADDRT || request == SIOCDELRT; - struct task *task = sched_current_task(); - if (change && (!task || !(task->cap_effective & (1ULL << CAP_NET_ADMIN)))) - return -LINUX_EPERM; - if (request == SIOCADDRT || request == SIOCDELRT) { - struct rtentry route; - int rc=socket_user_import(task,address,&route,sizeof(route));if(rc)return rc; - if (route.rt_dst.sa_family != AF_INET) return -LINUX_EAFNOSUPPORT; - uint32_t route_mask = net_get_u32((uint8_t *)&route.rt_genmask + 4); - if (route.rt_genmask.sa_family != AF_INET && (route.rt_genmask.sa_family || route_mask)) - return -LINUX_EAFNOSUPPORT; - if (route.rt_dev) { - char name[IFNAMSIZ] = {0}; - for (unsigned i = 0; i < sizeof(name); ++i) { - if(route.rt_dev>UINT64_MAX-i)return -LINUX_EFAULT; - rc=socket_user_import(task,route.rt_dev+i,&name[i],1);if(rc)return rc; - if (!name[i]) break; - } - if (__builtin_memcmp(name, "eth0", 5)) return -LINUX_ENODEV; - } - if (!net_interface_present()) return -LINUX_ENODEV; - uint32_t dst = net_get_u32((uint8_t *)&route.rt_dst + 4); - uint32_t mask = net_get_u32((uint8_t *)&route.rt_genmask + 4); - uint32_t gateway = route.rt_gateway.sa_family == AF_INET ? - net_get_u32((uint8_t *)&route.rt_gateway + 4) : 0; - if (dst || mask || (route.rt_flags & ~3u) || route.rt_metric > 1) - return -LINUX_EOPNOTSUPP; - if (request == SIOCDELRT) { - if (!net_config.gateway_ip || (gateway && gateway != net_config.gateway_ip)) - return -LINUX_ESRCH; - net_config.gateway_ip = 0; - } else { - if ((route.rt_flags & 2) && !gateway) return -LINUX_EINVAL; - if (!(route.rt_flags & 2)) return -LINUX_EOPNOTSUPP; - if (!net_interface_up || !net_config.local_ip || - (gateway & net_config.subnet_mask) != (net_config.local_ip & net_config.subnet_mask)) - return -LINUX_ENETUNREACH; - if (net_config.gateway_ip) return -LINUX_EEXIST; - net_config.gateway_ip = gateway; - } - net_arp_cache_clear(); - return 0; - } - if (request == SIOCGIFCONF) { - struct ifconf conf; - int rc=socket_user_import(task,address,&conf,sizeof(conf));if(rc)return rc; - rc=socket_user_prepare(task,address,sizeof(conf),true);if(rc)return rc; - if(!user_range_writable(address,sizeof(conf)))return -LINUX_EFAULT; - if (conf.ifc_len < 0) return -LINUX_EINVAL; - int available = net_interface_present() && net_config.local_ip ? sizeof(struct ifreq) : 0; - if (!conf.ifc_buf) conf.ifc_len = available; - else { - conf.ifc_len = conf.ifc_len >= available ? available : 0; - if (conf.ifc_len) { - rc=socket_user_prepare(task,conf.ifc_buf,sizeof(struct ifreq),true);if(rc)return rc; - if (!user_range_writable(conf.ifc_buf, sizeof(struct ifreq))) return -LINUX_EFAULT; - struct ifreq result = { .ifr_name = "eth0", .data.addr.sa_family = AF_INET }; - net_put_u32((uint8_t *)&result.data.addr + 4, net_config.local_ip); - net_memcpy((void *)(uintptr_t)conf.ifc_buf, &result, sizeof(result)); - } - } - net_memcpy((void *)(uintptr_t)address, &conf, sizeof(conf)); - return 0; - } - switch (request) { - case SIOCGIFNAME: case SIOCGIFINDEX: case SIOCGIFHWADDR: case SIOCGIFFLAGS: - case SIOCSIFFLAGS: case SIOCGIFADDR: case SIOCSIFADDR: case SIOCGIFNETMASK: - case SIOCSIFNETMASK: case SIOCGIFBRDADDR: case SIOCSIFBRDADDR: case SIOCGIFMTU: - case SIOCSIFMTU: break; - default: return -LINUX_ENOTTY; - } - struct ifreq req; - int rc=socket_user_import(task,address,&req,sizeof(req));if(rc)return rc; - if(!change) { - rc=socket_user_prepare(task,address,sizeof(req),true);if(rc)return rc; - if(!user_range_writable(address,sizeof(req)))return -LINUX_EFAULT; - } - if (request == SIOCGIFNAME) { - const char *name; - uint32_t size; - if (req.data.value == 1) { name = "lo"; size = 3; } - else if (req.data.value == 2 && net_interface_present()) { name = "eth0"; size = 5; } - else return -LINUX_ENODEV; - net_memzero(req.ifr_name, sizeof(req.ifr_name)); - net_memcpy(req.ifr_name, name, size); - net_memcpy((void *)(uintptr_t)address, &req, sizeof(req)); - return 0; - } - req.ifr_name[IFNAMSIZ - 1] = 0; - if (request == SIOCGIFINDEX && !__builtin_memcmp(req.ifr_name, "lo", 3)) { - req.data.value = 1; - net_memcpy((void *)(uintptr_t)address, &req, sizeof(req)); - return 0; - } - if (!net_interface_present() || __builtin_memcmp(req.ifr_name, "eth0", 5)) return -LINUX_ENODEV; - switch (request) { - case SIOCGIFINDEX: req.data.value = 2; break; - case SIOCGIFHWADDR: - net_memzero(&req.data, sizeof(req.data)); - req.data.addr.sa_family = 1; /* ARPHRD_ETHER */ - net_memcpy(req.data.addr.sa_data, e1000_mac(), 6); break; - case SIOCGIFFLAGS: - req.data.flags = IFF_BROADCAST | IFF_MULTICAST | - (net_interface_up ? IFF_UP : 0) | - (net_interface_ready() ? IFF_RUNNING : 0); break; - case SIOCSIFFLAGS: - if (req.data.flags & ~(IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST)) - return -LINUX_EOPNOTSUPP; - if (net_interface_up != !!(req.data.flags & IFF_UP)) { - net_interface_up = !!(req.data.flags & IFF_UP); - net_ipv6_link_changed(net_interface_up); - netlink_ipv6_changed(); - } - break; - case SIOCGIFMTU: req.data.value = 1500; break; - case SIOCSIFMTU: - if (req.data.value != 1500) return -LINUX_EOPNOTSUPP; - break; - default: { - uint32_t *value = (request == SIOCGIFADDR || request == SIOCSIFADDR) ? &net_config.local_ip : - (request == SIOCGIFNETMASK || request == SIOCSIFNETMASK) ? &net_config.subnet_mask : &net_interface_broadcast; - if (change) { - if (req.data.addr.sa_family != AF_INET) return -LINUX_EAFNOSUPPORT; - uint32_t ip = net_get_u32((uint8_t *)&req.data.addr + 4); - if (request == SIOCSIFNETMASK && ((~ip) & ((~ip) + 1))) return -LINUX_EINVAL; - if (request == SIOCSIFADDR && ip >= 0xe0000000u) return -LINUX_EINVAL; - *value = ip; - if (request == SIOCSIFADDR || request == SIOCSIFNETMASK) - net_interface_broadcast = net_config.local_ip | ~net_config.subnet_mask; - if (!net_config.local_ip) net_config.gateway_ip = 0; - net_config.source = net_config.local_ip ? RELIEFOS_NET_CONFIG_SOURCE_STATIC : RELIEFOS_NET_CONFIG_SOURCE_NONE; - net_config.lease_seconds = net_config.dhcp_server_ip = 0; - net_arp_cache_clear(); - } else { - if (!net_config.local_ip) return -LINUX_EADDRNOTAVAIL; - net_memzero(&req.data, sizeof(req.data)); - req.data.addr.sa_family = AF_INET; - net_put_u32((uint8_t *)&req.data.addr + 4, *value); - } - } break; - } - if (!change) net_memcpy((void *)(uintptr_t)address, &req, sizeof(req)); - return 0; -} - -/** @brief Report the real administrative and hardware state of eth0. */ -bool net_interface_ready(void) { return net_interface_up && e1000_is_ready(); } - -/** @brief Report a registered Ethernet device independently of carrier state. - * @return True while a retained provider publishes a present PCI device; - * false before registration or after detach. Caller owns network execution. - */ -bool net_interface_present(void) -{ - struct e1000_info info; - e1000_get_info(&info); - return info.present!=0; -} - -/** @brief Report the administrative IFF_UP setting independently of carrier. - * @return Current eth0 administrative flag; caller owns network execution. - */ -bool net_interface_admin_up(void) { return net_interface_up; } - /** @brief Apply Linux IPv6 address and route ioctls for an IPv6 socket. * @param request Supported address or route mutation command. * @param address User pointer to a native in6_ifreq or in6_rtmsg. diff --git a/net/interface/ioctl.c b/net/interface/ioctl.c new file mode 100644 index 00000000..fbeb48b8 --- /dev/null +++ b/net/interface/ioctl.c @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "../internal.h" +#include +#include + +/** @brief Export whole IPv4 ifreq records for all configured interfaces. */ +static int interface_conf(struct task *task, uint64_t address) +{ + struct ifconf conf; + int rc = socket_user_import(task, address, &conf, sizeof(conf)); + if (rc) return rc; + rc = socket_user_prepare(task, address, sizeof(conf), true); + if (rc) return rc; + if (!user_range_writable(address, sizeof(conf))) return -LINUX_EFAULT; + if (conf.ifc_len < 0) return -LINUX_EINVAL; + uint32_t count = 0; + for (uint32_t index = net_interface_next(0); index; index = net_interface_next(index)) { + struct reliefos_net_config config; + if (!net_interface_get_config(index, &config) && config.local_ip) ++count; + } + uint32_t available = count * sizeof(struct ifreq); + if (!conf.ifc_buf) conf.ifc_len = (int32_t)available; + else { + uint32_t room = (uint32_t)conf.ifc_len / sizeof(struct ifreq); + if (room > count) room = count; + uint32_t bytes = room * sizeof(struct ifreq); + if (bytes) { + rc = socket_user_prepare(task, conf.ifc_buf, bytes, true); + if (rc) return rc; + if (!user_range_writable(conf.ifc_buf, bytes)) return -LINUX_EFAULT; + uint32_t written = 0; + for (uint32_t index = net_interface_next(0); index && written < room; + index = net_interface_next(index)) { + struct reliefos_net_config config; + if (net_interface_get_config(index, &config) || !config.local_ip) continue; + struct ifreq result = {0}; + (void)net_interface_name(index, result.ifr_name); + result.data.addr.sa_family = AF_INET; + net_put_u32((uint8_t *)&result.data.addr + 4, config.local_ip); + net_memcpy((void *)(uintptr_t)(conf.ifc_buf + written * sizeof(result)), + &result, sizeof(result)); + ++written; + } + } + conf.ifc_len = (int32_t)bytes; + } + net_memcpy((void *)(uintptr_t)address, &conf, sizeof(conf)); + return 0; +} + +/** @brief Select the best connected NIC for a legacy gateway-only request. */ +static uint32_t interface_for_gateway(uint32_t gateway) +{ + uint32_t selected = 0, best_mask = 0; + for (uint32_t index = net_interface_next(NET_IF_LOOPBACK); index; + index = net_interface_next(index)) { + struct net_interface *iface = net_interface_get(index); + if (!iface || !iface->config.local_ip || + (gateway & iface->config.subnet_mask) != + (iface->config.local_ip & iface->config.subnet_mask)) continue; + if (!selected || iface->config.subnet_mask > best_mask) { + selected = index; best_mask = iface->config.subnet_mask; + } + } + return selected; +} + +/** @brief Apply an authorized IPv4 route ioctl without retaining user pointers. */ +static int interface_route(struct task *task, uint32_t request, uint64_t address) +{ + struct rtentry input; + int rc = socket_user_import(task, address, &input, sizeof(input)); + if (rc) return rc; + if (input.rt_dst.sa_family != AF_INET) return -LINUX_EAFNOSUPPORT; + uint32_t mask = net_get_u32((uint8_t *)&input.rt_genmask + 4); + if (input.rt_genmask.sa_family != AF_INET && (input.rt_genmask.sa_family || mask)) + return -LINUX_EAFNOSUPPORT; + if (input.rt_gateway.sa_family && input.rt_gateway.sa_family != AF_INET) + return -LINUX_EAFNOSUPPORT; + if ((input.rt_flags & ~(RTF_UP | RTF_GATEWAY | RTF_HOST)) || input.rt_metric < 0) + return -LINUX_EOPNOTSUPP; + struct net_ipv4_route route = { + .destination = net_get_u32((uint8_t *)&input.rt_dst + 4), .mask = mask, + .gateway = input.rt_gateway.sa_family == AF_INET ? + net_get_u32((uint8_t *)&input.rt_gateway + 4) : 0, + .metric = (uint32_t)input.rt_metric, .flags = input.rt_flags, + }; + if ((route.flags & RTF_HOST) && !route.mask) route.mask = UINT32_MAX; + if (input.rt_dev) { + char name[NET_IF_NAME_LEN] = {0}; + bool terminated = false; + for (uint32_t i = 0; i < sizeof(name); ++i) { + if (input.rt_dev > UINT64_MAX - i) return -LINUX_EFAULT; + rc = socket_user_import(task, input.rt_dev + i, &name[i], 1); + if (rc) return rc; + if (!name[i]) { terminated = true; break; } + } + if (!terminated) return -LINUX_EINVAL; + route.ifindex = net_interface_index(name); + if (!route.ifindex) return -LINUX_ENODEV; + } else if (route.gateway) route.ifindex = interface_for_gateway(route.gateway); + else if (request == SIOCDELRT && !route.mask) { + for (uint32_t index = net_interface_next(NET_IF_LOOPBACK); index; + index = net_interface_next(index)) { + struct net_interface *iface = net_interface_get(index); + if (iface && iface->config.gateway_ip) { route.ifindex = index; break; } + } + } else { + struct net_ipv4_path path; + if (!net_ipv4_route_lookup(route.destination, 0, 0, &path)) route.ifindex = path.ifindex; + } + if (!route.ifindex) return -LINUX_ENETUNREACH; + if (request == SIOCDELRT && !route.mask && !route.gateway) { + struct net_interface *iface = net_interface_get(route.ifindex); + if (!iface || !iface->config.gateway_ip) return -LINUX_ESRCH; + route.gateway = iface->config.gateway_ip; + route.flags |= RTF_GATEWAY; + } + rc = net_ipv4_route_update(&route, request == SIOCDELRT, false); + if (!rc) { + net_arp_cache_clear_on(route.ifindex); + netlink_ipv6_changed(); + } + return rc; +} + +int net_interface_configure_ipv4(uint32_t index, uint32_t address, + uint32_t mask, uint32_t broadcast) +{ + struct net_interface *iface = net_interface_get(index); + if (!iface) return index == NET_IF_LOOPBACK ? -LINUX_EOPNOTSUPP : -LINUX_ENODEV; + if (((~mask) & ((~mask) + 1u)) || address >= 0xe0000000u) return -LINUX_EINVAL; + iface->config.local_ip = address; + iface->config.subnet_mask = mask; + iface->broadcast = broadcast; + if (!address) iface->config.gateway_ip = 0; + iface->config.source = address ? RELIEFOS_NET_CONFIG_SOURCE_STATIC : RELIEFOS_NET_CONFIG_SOURCE_NONE; + iface->config.lease_seconds = iface->config.dhcp_server_ip = 0; + net_arp_cache_clear_on(index); + netlink_ipv6_changed(); + return 0; +} + +int net_interface_ioctl(uint32_t request, uint64_t address) +{ + bool change = request == SIOCSIFFLAGS || request == SIOCSIFADDR || + request == SIOCSIFNETMASK || request == SIOCSIFBRDADDR || + request == SIOCSIFMTU || request == SIOCADDRT || request == SIOCDELRT; + struct task *task = sched_current_task(); + if (change && (!task || !(task->cap_effective & (1ULL << CAP_NET_ADMIN)))) + return -LINUX_EPERM; + if (request == SIOCADDRT || request == SIOCDELRT) + return interface_route(task, request, address); + if (request == SIOCGIFCONF) return interface_conf(task, address); + switch (request) { + case SIOCGIFNAME: case SIOCGIFINDEX: case SIOCGIFHWADDR: case SIOCGIFFLAGS: + case SIOCSIFFLAGS: case SIOCGIFADDR: case SIOCSIFADDR: case SIOCGIFNETMASK: + case SIOCSIFNETMASK: case SIOCGIFBRDADDR: case SIOCSIFBRDADDR: case SIOCGIFMTU: + case SIOCSIFMTU: case SIOCGIFTXQLEN: break; + default: return -LINUX_ENOTTY; + } + struct ifreq req; + int rc = socket_user_import(task, address, &req, sizeof(req)); + if (rc) return rc; + if (!change) { + rc = socket_user_prepare(task, address, sizeof(req), true); + if (rc) return rc; + if (!user_range_writable(address, sizeof(req))) return -LINUX_EFAULT; + } + uint32_t index; + if (request == SIOCGIFNAME) { + index = (uint32_t)req.data.value; + if (!net_interface_name(index, req.ifr_name)) return -LINUX_ENODEV; + net_memcpy((void *)(uintptr_t)address, &req, sizeof(req)); + return 0; + } + req.ifr_name[IFNAMSIZ - 1] = 0; + index = net_interface_index(req.ifr_name); + if (!index) return -LINUX_ENODEV; + bool loop = index == NET_IF_LOOPBACK; + struct net_interface *iface = net_interface_get(index); + switch (request) { + case SIOCGIFINDEX: req.data.value = (int32_t)index; break; + case SIOCGIFHWADDR: + net_memzero(&req.data, sizeof(req.data)); + req.data.addr.sa_family = loop ? 772 : 1; + if (!loop) net_memcpy(req.data.addr.sa_data, netdev_mac(index), 6); + break; + case SIOCGIFFLAGS: + req.data.flags = (loop ? IFF_LOOPBACK : IFF_BROADCAST | IFF_MULTICAST) | + (net_interface_is_up(index) ? IFF_UP : 0) | + (net_interface_is_ready(index) ? IFF_RUNNING : 0); + break; + case SIOCSIFFLAGS: { + uint32_t allowed = IFF_UP | IFF_RUNNING | (loop ? IFF_LOOPBACK : IFF_BROADCAST | IFF_MULTICAST); + if ((uint16_t)req.data.flags & ~allowed) return -LINUX_EOPNOTSUPP; + bool up = !!(req.data.flags & IFF_UP); + if (net_interface_is_up(index) != up) { + rc = net_interface_set_up(index, up); + if (rc) return rc; + if (!loop) net_ipv6_link_changed_on(index, up); + netlink_ipv6_changed(); + } + break; + } + /* Frames go directly to the driver's bounded DMA ring. There is no + * separate software transmit backlog to report through this ioctl. */ + case SIOCGIFTXQLEN: req.data.value = 0; break; + case SIOCGIFMTU: req.data.value = loop ? 65536 : 1500; break; + case SIOCSIFMTU: + if (req.data.value != (loop ? 65536 : 1500)) return -LINUX_EOPNOTSUPP; + break; + default: { + struct reliefos_net_config config; + rc = net_interface_get_config(index, &config); + if (rc) return rc; + if (change) { + if (loop) return -LINUX_EOPNOTSUPP; + if (req.data.addr.sa_family != AF_INET) return -LINUX_EAFNOSUPPORT; + uint32_t ip = net_get_u32((uint8_t *)&req.data.addr + 4); + if (request == SIOCSIFNETMASK && ((~ip) & ((~ip) + 1u))) return -LINUX_EINVAL; + if (request == SIOCSIFADDR && ip >= 0xe0000000u) return -LINUX_EINVAL; + uint32_t local = request == SIOCSIFADDR ? ip : iface->config.local_ip; + uint32_t mask = request == SIOCSIFNETMASK ? ip : iface->config.subnet_mask; + uint32_t broadcast = request == SIOCSIFBRDADDR ? ip : local | ~mask; + rc = net_interface_configure_ipv4(index,local,mask,broadcast); + if (rc) return rc; + } else { + if (!config.local_ip) return -LINUX_EADDRNOTAVAIL; + uint32_t value = request == SIOCGIFADDR ? config.local_ip : + request == SIOCGIFNETMASK ? config.subnet_mask : loop ? 0 : iface->broadcast; + net_memzero(&req.data, sizeof(req.data)); + req.data.addr.sa_family = AF_INET; + net_put_u32((uint8_t *)&req.data.addr + 4, value); + } + break; + } + } + if (!change) net_memcpy((void *)(uintptr_t)address, &req, sizeof(req)); + return 0; +} + +bool net_interface_ready(void) +{ + uint32_t index = net_interface_next(NET_IF_LOOPBACK); + return index && net_interface_is_ready(index); +} + +bool net_interface_present(void) +{ + return net_interface_next(NET_IF_LOOPBACK) != 0; +} + +bool net_interface_admin_up(void) +{ + uint32_t index = net_interface_next(NET_IF_LOOPBACK); + return index && net_interface_is_up(index); +} diff --git a/net/interface/routes.c b/net/interface/routes.c new file mode 100644 index 00000000..f908c3b5 --- /dev/null +++ b/net/interface/routes.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include + +struct ipv4_route_node { + struct ipv4_route_node *next; + struct net_ipv4_route route; +}; +static struct ipv4_route_node *ipv4_routes; + +/** @brief Remove all explicitly configured routes belonging to a detached NIC. */ +void net_ipv4_routes_remove_interface(uint32_t index) +{ + struct ipv4_route_node **slot = &ipv4_routes; + while (*slot) { + struct ipv4_route_node *entry = *slot; + if (entry->route.ifindex != index) { slot = &entry->next; continue; } + *slot = entry->next; + kernel_free(entry); + } +} + +/** @brief Append a route to an optional bounded snapshot. */ +static void ipv4_snapshot_add(struct net_ipv4_route *out, uint32_t capacity, + uint32_t *count, struct net_ipv4_route route) +{ + if (out && *count < capacity) out[*count] = route; + ++*count; +} + +uint32_t net_ipv4_routes(struct net_ipv4_route *out, uint32_t capacity) +{ + uint32_t count = 0; + ipv4_snapshot_add(out, capacity, &count, (struct net_ipv4_route){ + .destination = 0x7f000000u, .mask = 0xff000000u, + .ifindex = NET_IF_LOOPBACK, .flags = RTF_UP}); + for (uint32_t index = net_interface_next(NET_IF_LOOPBACK); index; + index = net_interface_next(index)) { + struct net_interface *iface = net_interface_get(index); + if (!iface || !iface->config.local_ip) continue; + ipv4_snapshot_add(out, capacity, &count, (struct net_ipv4_route){ + .destination = iface->config.local_ip & iface->config.subnet_mask, + .mask = iface->config.subnet_mask, .ifindex = index, .flags = RTF_UP}); + if (iface->config.gateway_ip) + ipv4_snapshot_add(out, capacity, &count, (struct net_ipv4_route){ + .gateway = iface->config.gateway_ip, .ifindex = index, + .metric = iface->default_metric, .flags = RTF_UP | RTF_GATEWAY}); + } + for (struct ipv4_route_node *entry = ipv4_routes; entry; entry = entry->next) + ipv4_snapshot_add(out, capacity, &count, entry->route); + return count; +} + +/** @brief Replace the current candidate when the route has a better priority. */ +static void ipv4_consider(const struct net_ipv4_route *candidate, uint32_t destination, + uint32_t scope, struct net_ipv4_route *best, bool *found) +{ + if ((scope && candidate->ifindex != scope) || + (destination & candidate->mask) != candidate->destination || + !net_interface_is_ready(candidate->ifindex)) return; + if (!*found || candidate->mask > best->mask || + (candidate->mask == best->mask && candidate->metric < best->metric) || + (candidate->mask == best->mask && candidate->metric == best->metric && + candidate->ifindex < best->ifindex)) { + *best = *candidate; + *found = true; + } +} + +int net_ipv4_route_lookup(uint32_t destination, uint32_t source, uint32_t scope, + struct net_ipv4_path *out) +{ + if (!out) return -LINUX_EINVAL; + if (scope && !net_interface_exists(scope)) return -LINUX_ENODEV; + if (source && !net_interface_for_ipv4(source)) return -LINUX_EADDRNOTAVAIL; + + uint32_t local = net_interface_for_ipv4(destination); + if (local) { + if (scope && scope != local) return -LINUX_ENETUNREACH; + if (!net_interface_is_up(local)) return -LINUX_ENETDOWN; + *out = (struct net_ipv4_path){.source = source ? source : + (local == NET_IF_LOOPBACK ? 0x7f000001u : destination), + .destination = destination, .next_hop = destination, .ifindex = local}; + return 0; + } + if (destination == UINT32_MAX) { + uint32_t index = scope; + if (!index) { + for (index = net_interface_next(NET_IF_LOOPBACK); index; + index = net_interface_next(index)) + if (net_interface_is_ready(index)) break; + } + if (!index || index == NET_IF_LOOPBACK) return -LINUX_ENETUNREACH; + if (!net_interface_is_ready(index)) return -LINUX_ENETDOWN; + struct net_interface *iface = net_interface_get(index); + *out = (struct net_ipv4_path){.source = source ? source : iface->config.local_ip, + .destination = destination, .next_hop = destination, .ifindex = index}; + return 0; + } + + struct net_ipv4_route best = {0}; + bool found = false; + for (uint32_t index = net_interface_next(NET_IF_LOOPBACK); index; + index = net_interface_next(index)) { + struct net_interface *iface = net_interface_get(index); + if (!iface || !iface->config.local_ip) continue; + struct net_ipv4_route connected = { + .destination = iface->config.local_ip & iface->config.subnet_mask, + .mask = iface->config.subnet_mask, .ifindex = index, .flags = RTF_UP}; + ipv4_consider(&connected, destination, scope, &best, &found); + if (iface->config.gateway_ip) { + struct net_ipv4_route def = {.gateway = iface->config.gateway_ip, + .ifindex = index, .metric = iface->default_metric, .flags = RTF_UP | RTF_GATEWAY}; + ipv4_consider(&def, destination, scope, &best, &found); + } + } + for (struct ipv4_route_node *entry = ipv4_routes; entry; entry = entry->next) + ipv4_consider(&entry->route, destination, scope, &best, &found); + if (!found) return -LINUX_ENETUNREACH; + struct net_interface *iface = net_interface_get(best.ifindex); + if (!source && (!iface || !iface->config.local_ip)) return -LINUX_EADDRNOTAVAIL; + *out = (struct net_ipv4_path){.source = source ? source : iface->config.local_ip, + .destination = destination, .next_hop = best.gateway ? best.gateway : destination, + .ifindex = best.ifindex}; + return 0; +} + +int net_ipv4_route_update(const struct net_ipv4_route *input, bool remove, bool replace) +{ + if (!input || ((~input->mask) & ((~input->mask) + 1u)) || + (input->flags & ~(RTF_UP | RTF_GATEWAY | RTF_HOST))) return -LINUX_EINVAL; + struct net_ipv4_route route = *input; + route.destination &= route.mask; + if (!!route.gateway != !!(route.flags & RTF_GATEWAY)) return -LINUX_EINVAL; + if ((route.flags & RTF_HOST) && route.mask != UINT32_MAX) return -LINUX_EINVAL; + struct net_interface *iface = net_interface_get(route.ifindex); + if (!iface) return -LINUX_ENODEV; + if (!remove && route.gateway && (!iface->config.local_ip || + (route.gateway & iface->config.subnet_mask) != + (iface->config.local_ip & iface->config.subnet_mask))) return -LINUX_ENETUNREACH; + route.flags |= RTF_UP; + + /* The compatibility configuration owns one default gateway per NIC. + * This also keeps DHCP's existing default-route updates observable. */ + if (!route.mask && route.gateway) { + if (remove) { + if (!iface->config.gateway_ip || iface->config.gateway_ip != route.gateway) + return -LINUX_ESRCH; + iface->config.gateway_ip = 0; + iface->default_metric = 0; + return 0; + } + if (iface->config.gateway_ip && !replace) return -LINUX_EEXIST; + iface->config.gateway_ip = route.gateway; + iface->default_metric = route.metric; + return 0; + } + + struct ipv4_route_node **slot = &ipv4_routes; + while (*slot) { + struct net_ipv4_route *r = &(*slot)->route; + if (r->destination == route.destination && r->mask == route.mask && + r->ifindex == route.ifindex && r->gateway == route.gateway) break; + slot = &(*slot)->next; + } + if (remove) { + if (!*slot) return -LINUX_ESRCH; + struct ipv4_route_node *entry = *slot; + *slot = entry->next; + kernel_free(entry); + return 0; + } + if (*slot) { + if (!replace) return -LINUX_EEXIST; + (*slot)->route = route; + return 0; + } + struct ipv4_route_node *entry = kernel_malloc(sizeof(*entry)); + if (!entry) return -LINUX_ENOMEM; + entry->route = route; + entry->next = NULL; + *slot = entry; + return 0; +} diff --git a/net/interface/socket.c b/net/interface/socket.c new file mode 100644 index 00000000..44dc427c --- /dev/null +++ b/net/interface/socket.c @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include +#include +#include + +/** @brief Apply Linux SO_BINDTODEVICE validation for one socket's bound index. + * @param set Update when true, query otherwise. @param address User name buffer. + * @param length Input byte count for updates, user socklen_t pointer for queries. + * @param index Current bound device; changed only after a successful update. + * @return Zero or negative errno. Execution-owned, user memory copied with the + * socket helpers, CAP_NET_RAW required when changing an existing binding. No provider pointer escapes. */ +int net_interface_device_option(bool set, uint64_t address, uint64_t length, uint32_t *index) +{ + if (!index) return -LINUX_EINVAL; + struct task *task = sched_current_task(); + char name[NET_IF_NAME_LEN] = {0}; + if (set) { + uint32_t size = (uint32_t)length; + if ((int32_t)size < 0) return -LINUX_EINVAL; + if (!task) return -LINUX_EPERM; + if (size >= sizeof(name)) size = sizeof(name) - 1; + if (size) { + int result = socket_user_import(task, address, name, size); + if (result) return result; + } + uint32_t selected = name[0] ? net_interface_index(name) : 0; + if (name[0] && !selected) return -LINUX_ENODEV; + if (*index && !(task->cap_effective & (1ULL << CAP_NET_RAW))) return -LINUX_EPERM; + *index = selected; + return 0; + } + uint32_t capacity; + int result = socket_user_length(task, length, &capacity); + if (result) return result; + if ((int32_t)capacity < 0) return -LINUX_EINVAL; + uint32_t size = 0; + if (*index) { + if (capacity < sizeof(name)) return -LINUX_EINVAL; + if (!net_interface_name(*index, name)) return -LINUX_ENODEV; + while (size < sizeof(name) && name[size]) ++size; + ++size; + } + if (size) { + result = socket_user_prepare(task, address, size, true); + if (result) return result; + if (!user_range_writable(address, size)) return -LINUX_EFAULT; + __builtin_memcpy((void *)(uintptr_t)address, name, size); + } + *(uint32_t *)(uintptr_t)length = size; + return 0; +} diff --git a/net/interface/state.c b/net/interface/state.c new file mode 100644 index 00000000..db055752 --- /dev/null +++ b/net/interface/state.c @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include + +/* All state is serialized by the existing network execution owner. */ +static struct net_interface *interfaces; +static struct reliefos_net_config fallback_config; +static bool fallback_up = true; +static bool loopback_up = true; + +/* Defined by the IPv4 routing table; remove before releasing interface state. */ +void net_ipv4_routes_remove_interface(uint32_t index); + +int net_interface_register(uint32_t index) +{ + if (index <= NET_IF_LOOPBACK) return -LINUX_EINVAL; + struct net_interface **slot = &interfaces; + while (*slot && (*slot)->index < index) slot = &(*slot)->next; + if (*slot && (*slot)->index == index) return 0; + struct net_interface *iface = kernel_malloc(sizeof(*iface)); + if (!iface) return -LINUX_ENOMEM; + __builtin_memset(iface, 0, sizeof(*iface)); + iface->index = index; + iface->up = true; + iface->next = *slot; + *slot = iface; + return 0; +} + +void net_interface_remove(uint32_t index) +{ + struct net_interface **slot = &interfaces; + while (*slot && (*slot)->index != index) slot = &(*slot)->next; + if (!*slot) return; + struct net_interface *iface = *slot; + net_ipv4_routes_remove_interface(index); + *slot = iface->next; + kernel_free(iface); +} + +struct net_interface *net_interface_get(uint32_t index) +{ + for (struct net_interface *iface = interfaces; iface; iface = iface->next) + if (iface->index == index) return iface; + return NULL; +} + +uint32_t net_interface_next(uint32_t after) +{ + if (after < NET_IF_LOOPBACK) return NET_IF_LOOPBACK; + for (struct net_interface *iface = interfaces; iface; iface = iface->next) + if (iface->index > after) return iface->index; + return 0; +} + +bool net_interface_exists(uint32_t index) +{ + return index == NET_IF_LOOPBACK || net_interface_get(index) != NULL; +} + +bool net_interface_name(uint32_t index, char out[NET_IF_NAME_LEN]) +{ + if (!out || !net_interface_exists(index)) return false; + __builtin_memset(out, 0, NET_IF_NAME_LEN); + if (index == NET_IF_LOOPBACK) { + out[0] = 'l'; out[1] = 'o'; + return true; + } + out[0] = 'e'; out[1] = 't'; out[2] = 'h'; + uint32_t value = index - 2, count = 0; + char digits[10]; + do { digits[count++] = (char)('0' + value % 10); value /= 10; } while (value); + for (uint32_t i = 0; i < count; ++i) out[3 + i] = digits[count - i - 1]; + return true; +} + +uint32_t net_interface_index(const char *name) +{ + if (!name) return 0; + if (!__builtin_strcmp(name, "lo")) return NET_IF_LOOPBACK; + if (name[0] != 'e' || name[1] != 't' || name[2] != 'h' || + name[3] < '0' || name[3] > '9') return 0; + if (name[3] == '0' && name[4]) return 0; + uint32_t value = 0, i = 3; + for (; name[i]; ++i) { + if (i >= NET_IF_NAME_LEN - 1 || name[i] < '0' || name[i] > '9' || + value > (UINT32_MAX - 2u - (uint32_t)(name[i] - '0')) / 10u) + return 0; + value = value * 10u + (uint32_t)(name[i] - '0'); + } + uint32_t index = value + 2u; + return net_interface_get(index) ? index : 0; +} + +bool net_interface_is_up(uint32_t index) +{ + if (index == NET_IF_LOOPBACK) return loopback_up; + struct net_interface *iface = net_interface_get(index); + return iface && iface->up; +} + +int net_interface_set_up(uint32_t index, bool up) +{ + if (index == NET_IF_LOOPBACK) { loopback_up = up; return 0; } + struct net_interface *iface = net_interface_get(index); + if (!iface) return -LINUX_ENODEV; + iface->up = up; + return 0; +} + +bool net_interface_is_ready(uint32_t index) +{ + if (index == NET_IF_LOOPBACK) return loopback_up; + struct net_interface *iface = net_interface_get(index); + return iface && iface->up && netdev_is_ready(index); +} + +uint32_t net_interface_for_ipv4(uint32_t ip) +{ + if ((ip >> 24) == 127) return NET_IF_LOOPBACK; + if (!ip) return 0; + for (struct net_interface *iface = interfaces; iface; iface = iface->next) + if (iface->config.local_ip == ip) return iface->index; + return 0; +} + +int net_interface_get_config(uint32_t index, struct reliefos_net_config *out) +{ + if (!out) return -LINUX_EINVAL; + if (index == NET_IF_LOOPBACK) { + *out = (struct reliefos_net_config){.local_ip = 0x7f000001u, + .subnet_mask = 0xff000000u}; + return 0; + } + struct net_interface *iface = net_interface_get(index); + if (!iface) return -LINUX_ENODEV; + struct e1000_info info = {0}; + netdev_get_info(index, &info); + *out = iface->config; + out->flags &= ~(RELIEFOS_NET_CONFIG_FLAG_PRESENT | RELIEFOS_NET_CONFIG_FLAG_ACTIVE | + RELIEFOS_NET_CONFIG_FLAG_DHCP); + if (info.present) out->flags |= RELIEFOS_NET_CONFIG_FLAG_PRESENT; + if (info.active && iface->up) out->flags |= RELIEFOS_NET_CONFIG_FLAG_ACTIVE; + if (out->source == RELIEFOS_NET_CONFIG_SOURCE_DHCP) out->flags |= RELIEFOS_NET_CONFIG_FLAG_DHCP; + __builtin_memcpy(out->mac, info.mac, sizeof(out->mac)); + return 0; +} + +struct reliefos_net_config *net_interface_legacy_config(void) +{ + return interfaces ? &interfaces->config : &fallback_config; +} + +bool *net_interface_legacy_up(void) +{ + return interfaces ? &interfaces->up : &fallback_up; +} diff --git a/net/internal.h b/net/internal.h index 826ecda7..e9a0badb 100644 --- a/net/internal.h +++ b/net/internal.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -84,12 +86,14 @@ #define NET_NTP_UNIX_EPOCH_OFFSET 2208988800ULL struct net_arp_wait { + uint32_t ifindex; uint32_t ip; uint8_t mac[6]; uint32_t done; }; struct net_ping_wait { + uint32_t ifindex; uint32_t target_ip; uint16_t ident; uint16_t sequence; @@ -97,6 +101,7 @@ struct net_ping_wait { }; struct net_udp_wait { + uint32_t ifindex; uint32_t src_ip; uint16_t src_port; uint16_t dst_port; @@ -107,6 +112,7 @@ struct net_udp_wait { }; struct net_tcp_wait { + uint32_t ifindex; uint32_t src_ip; uint16_t src_port; uint16_t dst_port; @@ -133,6 +139,7 @@ struct net_dhcp_offer { }; struct net_arp_cache_entry { + uint32_t ifindex; uint32_t ip; uint8_t mac[6]; }; @@ -159,6 +166,10 @@ struct net_socket { uint32_t status; uint32_t local_ip; uint32_t remote_ip; + uint32_t ifindex; + uint32_t bound_ifindex; + uint32_t next_hop; + bool resolving_neighbor; struct in6_addr local6, remote6; uint32_t scope6, remote_scope6, mtu6; uint16_t peer_mss; @@ -438,8 +449,8 @@ struct net_socket { extern const uint8_t net_broadcast_mac[6]; extern uint32_t net_sequence; extern uint16_t net_ipv4_id; -extern struct reliefos_net_config net_config; -extern bool net_interface_up; +#define net_config (*net_interface_legacy_config()) +#define net_interface_up (*net_interface_legacy_up()) extern struct net_socket *net_sockets; /** @@ -1096,4 +1107,63 @@ int net_interface_ioctl(uint32_t request, uint64_t address); /** @brief Report the real administrative and hardware state of eth0. */ bool net_interface_ready(void); + +/** @brief Clear only one interface's ARP entries; zero clears every entry. + * @param ifindex Stable interface index or zero. No driver callback or wait. */ +void net_arp_cache_clear_on(uint32_t ifindex); +/** @brief Resolve an IPv4 neighbor on one specific Ethernet link. + * @param ifindex Egress link. @param ip Next-hop IPv4, host order. + * @param timeout_ms Bounded wait. @param out_mac Six writable bytes. + * @return Zero or negative errno/legacy timeout status. */ +int net_resolve_mac_on(uint32_t ifindex, uint32_t ip, uint32_t timeout_ms, uint8_t out_mac[6]); +/** @brief Construct the link header with the selected device's source MAC. + * @param ifindex Egress link. @param frame Fourteen writable bytes. + * @param dst_mac Six destination bytes. @param type Host-order EtherType. */ +void net_write_eth_on(uint32_t ifindex, uint8_t *frame, const uint8_t *dst_mac, uint16_t type); +/** @brief Match the frame destination against a specific link and multicast policy. + * @param ifindex Ingress link. @param frame Fourteen readable bytes. + * @return Nonzero for an accepted link-layer destination. */ +int net_frame_for_us_on(uint32_t ifindex, const uint8_t *frame); +/** @brief Validate and process ARP while preserving ingress link identity. + * @param ifindex Ingress link. @param frame Borrowed Ethernet frame. + * @param len Frame bytes. @param wait Optional matching synchronous waiter. */ +void net_handle_arp_on(uint32_t ifindex, const uint8_t *frame, uint32_t len, struct net_arp_wait *wait); +/** @brief Validate and dispatch IPv4 using the receiving interface. + * @param ifindex Ingress link. @param frame Borrowed Ethernet frame. @param len Bytes. + * @param ping Optional ICMP waiter. @param udp Optional UDP waiter. @param tcp Optional TCP waiter. */ +void net_handle_ipv4_on(uint32_t ifindex, const uint8_t *frame, uint32_t len, + struct net_ping_wait *ping, struct net_udp_wait *udp, struct net_tcp_wait *tcp); +/** @brief Deliver checked IPv4 UDP with the original ingress interface. + * @param ifindex Ingress link. @param ip Packet. @param total_len IP bytes. + * @param ihl Header bytes. @param src_ip Host-order source. @param wait Optional waiter. */ +void net_handle_udp_on(uint32_t ifindex, const uint8_t *ip, uint32_t total_len, + uint32_t ihl, uint32_t src_ip, struct net_udp_wait *wait); + + +/** @brief Process TCP with a stable ingress device identity. + * @param ifindex Ingress. @param ip IP packet. @param total_len Bytes. @param ihl Header size. + * @param src_ip Peer address. @param source_mac Peer MAC or NULL for loopback. + * @param wait Optional synchronous waiter. */ +void net_handle_tcp_on(uint32_t ifindex, const uint8_t *ip, uint32_t total_len, + uint32_t ihl, uint32_t src_ip, const uint8_t *source_mac, struct net_tcp_wait *wait); +/** @brief Create an incoming IPv4 child associated with its ingress link. + * @param ifindex Ingress. @param listener Parent. @param source Peer. @param destination Local. + * @param source_port Peer port. @param source_mac Peer MAC. @param seq Sequence. + * @param window Peer window. @param mss MSS. @return Child or NULL. */ +struct net_socket *net_tcp_passive4_on(uint32_t ifindex, struct net_socket *listener, + uint32_t source, uint32_t destination, uint16_t source_port, const uint8_t *source_mac, + uint32_t seq, uint16_t window, uint16_t mss); +/** @brief Build and send a UDP frame on an explicit link. + * @param ifindex Egress. @param mac Peer MAC. @param source Local. @param destination Peer. + * @param source_port Local port. @param destination_port Peer port. @param payload Bytes. + * @param length Payload length. @return Driver result or negative errno. */ +int net_send_udp_to_mac_on(uint32_t ifindex, const uint8_t *mac, uint32_t source, + uint32_t destination, uint16_t source_port, uint16_t destination_port, + const uint8_t *payload, uint32_t length); + +/** @brief Query whether device-state allocation retries remain pending. @return Retry hint. */ +bool net_device_registration_pending(void); +/** @brief Retry one failed interface registration under execution ownership. */ +void net_device_retry_registration(void); + #endif diff --git a/net/ipv4/core.c b/net/ipv4/core.c index c2b066db..ae35a847 100644 --- a/net/ipv4/core.c +++ b/net/ipv4/core.c @@ -15,7 +15,7 @@ static uint16_t net_packet_ip_id; */ static int net_ip_for_us(uint32_t dst_ip) { - return dst_ip == net_config.local_ip || + return net_interface_for_ipv4(dst_ip) != 0 || dst_ip == 0xffffffffu || dst_ip == 0; } @@ -28,7 +28,7 @@ static int net_ip_for_us(uint32_t dst_ip) * @param udp_wait Optional legacy UDP waiter. * @param tcp_wait Optional legacy TCP waiter. */ -void net_handle_ipv4(const uint8_t *frame, uint32_t len, +void net_handle_ipv4_on(uint32_t ifindex, const uint8_t *frame, uint32_t len, struct net_ping_wait *ping_wait, struct net_udp_wait *udp_wait, struct net_tcp_wait *tcp_wait) @@ -64,11 +64,11 @@ void net_handle_ipv4(const uint8_t *frame, uint32_t len, } src_ip = net_get_u32(ip + 12); if (ip[9] == IPV4_PROTO_UDP) { - net_handle_udp(ip, total_len, ihl, src_ip, udp_wait); + net_handle_udp_on(ifindex, ip, total_len, ihl, src_ip, udp_wait); return; } if (ip[9] == IPV4_PROTO_TCP) { - net_handle_tcp(ip, total_len, ihl, src_ip, frame + 6, tcp_wait); + net_handle_tcp_on(ifindex, ip, total_len, ihl, src_ip, frame + 6, tcp_wait); return; } if (ip[9] != IPV4_PROTO_ICMP || total_len < ihl + 8u) { @@ -80,6 +80,7 @@ void net_handle_ipv4(const uint8_t *frame, uint32_t len, return; } if (icmp[0] == ICMP_ECHO_REPLY && ping_wait && + (!ping_wait->ifindex || ping_wait->ifindex == ifindex) && src_ip == ping_wait->target_ip && net_get_u16(icmp + 4) == ping_wait->ident && net_get_u16(icmp + 6) == ping_wait->sequence) { @@ -97,7 +98,7 @@ void net_handle_ipv4(const uint8_t *frame, uint32_t len, * @param length Payload bytes. * @return Bytes accepted or negative errno; no fragmentation is implemented. */ -int net_ipv4_send_raw(uint32_t destination, uint32_t source, uint8_t protocol, +int net_ipv4_send_raw_on(uint32_t ifindex, uint32_t destination, uint32_t source, uint8_t protocol, bool header_included, const void *data, uint32_t length) { uint32_t header = header_included ? 0 : 20; @@ -105,7 +106,11 @@ int net_ipv4_send_raw(uint32_t destination, uint32_t source, uint8_t protocol, if (header_included && (length < 20 || ((const uint8_t *)data)[0] >> 4 != 4)) return -LINUX_EINVAL; uint8_t frame[1514] = {0}; uint8_t *ip = frame + 14; - if (!source) source = net_config.local_ip; + struct net_ipv4_path path; + int route = net_ipv4_route_lookup(destination, source, ifindex, &path); + if (route < 0) return route; + ifindex = path.ifindex; + source = path.source; if (header_included) { net_memcpy(ip, data, length); uint32_t ihl = (ip[0] & 15) * 4; @@ -124,9 +129,27 @@ int net_ipv4_send_raw(uint32_t destination, uint32_t source, uint8_t protocol, net_memcpy(ip + 20, data, length); } if (destination == UINT32_MAX) net_memcpy(frame, net_broadcast_mac, 6); - else if (net_resolve_mac(net_route_arp_ip(destination), 1000, frame) < 0) return -LINUX_EHOSTUNREACH; - net_memcpy(frame + 6, e1000_mac(), 6); + else if (net_resolve_mac_on(ifindex, path.next_hop, 1000, frame) < 0) return -LINUX_EHOSTUNREACH; + net_memcpy(frame + 6, netdev_mac(ifindex), 6); net_put_u16(frame + 12, 0x0800); - int ret = net_interface_up ? e1000_send(frame, length + header + 14) : -LINUX_ENETDOWN; + int ret = net_interface_is_ready(ifindex) ? netdev_send(ifindex, frame, length + header + 14) : -LINUX_ENETDOWN; return ret < 0 ? ret : (int)length; } + +/** @brief Receive IPv4 on the primary compatibility device. + * @param frame Ethernet packet. @param length Byte count. @param ping Optional waiter. + * @param udp Optional waiter. @param tcp Optional waiter. */ +void net_handle_ipv4(const uint8_t *frame, uint32_t length, struct net_ping_wait *ping, + struct net_udp_wait *udp, struct net_tcp_wait *tcp) +{ + net_handle_ipv4_on(netdev_next(0), frame, length, ping, udp, tcp); +} +/** @brief Send raw IPv4 using normal destination-based routing. + * @param destination Peer. @param source Optional source. @param protocol Protocol. + * @param header_included Header present. @param data Payload. @param length Bytes. + * @return Accepted bytes or negative errno. */ +int net_ipv4_send_raw(uint32_t destination, uint32_t source, uint8_t protocol, + bool header_included, const void *data, uint32_t length) +{ + return net_ipv4_send_raw_on(0, destination, source, protocol, header_included, data, length); +} diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index f12477e3..8b100dd6 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -13,7 +13,7 @@ * @param payload_len Value supplied by the caller. * @return The value or status produced by the operation. */ -int net_send_udp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, +int net_send_udp_to_mac_on(uint32_t ifindex, const uint8_t *dst_mac, uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, const uint8_t *payload, uint32_t payload_len) @@ -29,7 +29,7 @@ int net_send_udp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, udp_len = (uint16_t)(8u + payload_len); total_len = (uint16_t)(20u + udp_len); net_memzero(frame, 42u + payload_len); - net_write_eth(frame, dst_mac, ETH_TYPE_IPV4); + net_write_eth_on(ifindex, frame, dst_mac, ETH_TYPE_IPV4); ip[0] = 0x45; ip[1] = 0; net_put_u16(ip + 2, total_len); @@ -45,7 +45,7 @@ int net_send_udp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, net_put_u16(udp + 4, udp_len); net_put_u16(udp + 6, 0); net_memcpy(udp + 8, payload, payload_len); - return net_interface_up ? e1000_send(frame, 42u + payload_len) : -LINUX_ENETDOWN; + return net_interface_is_ready(ifindex) ? netdev_send(ifindex, frame, 42u + payload_len) : -LINUX_ENETDOWN; } /** @@ -56,7 +56,7 @@ int net_send_udp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, * @param src_ip Value supplied by the caller. * @param udp_wait Value supplied by the caller. */ -void net_handle_udp(const uint8_t *ip, uint32_t total_len, +void net_handle_udp_on(uint32_t ifindex, const uint8_t *ip, uint32_t total_len, uint32_t ihl, uint32_t src_ip, struct net_udp_wait *udp_wait) { @@ -80,8 +80,8 @@ void net_handle_udp(const uint8_t *ip, uint32_t total_len, sum += IPV4_PROTO_UDP + udp_len; if (net_checksum_finish(net_checksum_partial(sum, udp, udp_len))) return; } - net_udp_input(src_ip, net_get_u32(ip + 16), src_port, dst_port, udp + 8, udp_len - 8, 2); - if (!udp_wait || udp_wait->done) return; + net_udp_input(src_ip, net_get_u32(ip + 16), src_port, dst_port, udp + 8, udp_len - 8, (int)ifindex); + if (!udp_wait || udp_wait->done || (udp_wait->ifindex && udp_wait->ifindex != ifindex)) return; if (udp_wait->src_port && src_port != udp_wait->src_port) { return; } @@ -100,15 +100,58 @@ void net_handle_udp(const uint8_t *ip, uint32_t total_len, udp_wait->done = 1; } +/** @brief Receive UDP on the primary compatibility interface. + * @param ip Packet. @param total_len IP size. @param ihl Header bytes. + * @param src_ip Host-order source. @param wait Optional waiter. */ +void net_handle_udp(const uint8_t *ip, uint32_t total_len, uint32_t ihl, + uint32_t src_ip, struct net_udp_wait *wait) +{ + net_handle_udp_on(netdev_next(0), ip, total_len, ihl, src_ip, wait); +} + +/** @brief Send an IPv4 datagram with an explicit bound interface or route selection. + * @param ifindex Bound device or zero. @param source Optional source address. + * @param destination Peer address. @param source_port Local port. @param destination_port Peer port. + * @param data Borrowed payload. @param length Bytes. @return Zero or negative errno. */ +int net_ipv4_send_udp_on(uint32_t ifindex, uint32_t source, uint32_t destination, + uint16_t source_port, uint16_t destination_port, + const void *data, uint32_t length) +{ + if (length > 1472 || (length && !data)) return -LINUX_EMSGSIZE; + struct net_ipv4_path path; + int result = net_ipv4_route_lookup(destination, source, ifindex, &path); + if (result < 0) return result; + uint8_t mac[6]; + struct net_interface *interface = net_interface_get(path.ifindex); + if (destination == UINT32_MAX || (interface && destination == interface->broadcast)) + net_memcpy(mac, net_broadcast_mac, 6); + else if (net_resolve_mac_on(path.ifindex, path.next_hop, 1000, mac) < 0) + return -LINUX_EHOSTUNREACH; + result = net_send_udp_to_mac_on(path.ifindex, mac, path.source, destination, + source_port, destination_port, data, length); + return result < 0 ? result : 0; +} + +/** @brief Send UDP with automatic output device selection. + * @param source Optional source. @param destination Peer. @param source_port Local port. + * @param destination_port Peer port. @param data Payload. @param length Bytes. + * @return Zero or negative errno. */ int net_ipv4_send_udp(uint32_t source, uint32_t destination, uint16_t source_port, uint16_t destination_port, const void *data, uint32_t length) { - if (!e1000_is_ready()) return -LINUX_ENETDOWN; - if (net_ensure_ipv4_config(3000, 0) != RELIEFOS_NET_STATUS_OK) return -LINUX_ENETUNREACH; - uint8_t mac[6]; - if (destination == 0xffffffffu) net_memcpy(mac, net_broadcast_mac, 6); - else if (net_resolve_mac(net_route_arp_ip(destination), 1000, mac) < 0) return -LINUX_EHOSTUNREACH; - if (!source) source = net_config.local_ip; - return net_send_udp_to_mac(mac, source, destination, source_port, destination_port, - data, length) < 0 ? -LINUX_EIO : 0; + return net_ipv4_send_udp_on(0, source, destination, source_port, destination_port, data, length); +} + +/** @brief Build a legacy UDP frame using destination-based route selection. + * @param mac Destination MAC. @param source IPv4 source. @param destination Peer. + * @param source_port Local port. @param destination_port Peer port. + * @param data Payload. @param length Bytes. @return Driver result or negative errno. */ +int net_send_udp_to_mac(const uint8_t *mac, uint32_t source, uint32_t destination, + uint16_t source_port, uint16_t destination_port, const uint8_t *data, uint32_t length) +{ + struct net_ipv4_path path; + int result = net_ipv4_route_lookup(destination, source, 0, &path); + if (result < 0) return result; + return net_send_udp_to_mac_on(path.ifindex, mac, source, destination, + source_port, destination_port, data, length); } diff --git a/net/ipv6/core.c b/net/ipv6/core.c index 24651f00..bcab80c4 100644 --- a/net/ipv6/core.c +++ b/net/ipv6/core.c @@ -2,6 +2,8 @@ /** @file IPv6 host, neighbor discovery, bounded reassembly and MLD. */ #include "../internal.h" #include +#include +#include #include #include @@ -63,7 +65,7 @@ struct ipv6_pending { struct ipv6_reassembly { struct in6_addr source, destination; uint8_t *data, *bitmap, *prefix; - uint32_t identification, covered, total, highwater, prefix_len, prev_next; + uint32_t identification, ifindex, covered, total, highwater, prefix_len, prev_next; uint64_t deadline; uint8_t next; bool used, poisoned, have_first, have_last; @@ -82,44 +84,68 @@ static struct ipv6_group_slot ipv6_groups[IPV6_GROUP_MAX]; static struct ipv6_pending ipv6_pending[IPV6_PENDING_MAX]; static struct ipv6_reassembly ipv6_reassembly[IPV6_REASM_MAX]; static struct ipv6_pmtu ipv6_pmtu[IPV6_PMTU_MAX]; -static bool ipv6_initialized, ipv6_link_up; -static uint32_t ipv6_link_mtu = IPV6_LINK_MTU, ipv6_frag_id, ipv6_rng_state; -static uint32_t ipv6_base_reachable_ms = 30000, ipv6_retrans_ms = 1000; -static uint64_t ipv6_reachable_ms = 30000, ipv6_reachable_refresh; -static uint64_t ipv6_rs_at, ipv6_error_at, ipv6_mld_v1_until; -static uint8_t ipv6_rs_sent, ipv6_error_count; +/* Indexes are stable identifiers, not dense array positions. */ +struct ipv6_link { + struct ipv6_link *next; + uint32_t ifindex, mtu, rng_state, base_reachable_ms, retrans_ms; + uint64_t reachable_ms, reachable_refresh, rs_at, mld_v1_until; + uint8_t rs_sent; + bool up; +}; +static struct ipv6_link *ipv6_links; +static bool ipv6_initialized; +/* Fragment IDs must not collide when the same endpoints use different NICs. */ +static uint32_t ipv6_frag_id; +static uint64_t ipv6_error_at; +static uint8_t ipv6_error_count; static const struct in6_addr ipv6_zero = {0}; static const struct in6_addr ipv6_loop = {.s6_addr={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}; static const struct in6_addr ipv6_all_nodes = {.s6_addr={0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}; static const struct in6_addr ipv6_all_routers = {.s6_addr={0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}; static const struct in6_addr ipv6_mld_routers = {.s6_addr={0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0x16}}; -/** @brief Obtain a discovery jitter word without requiring a hardware generator. - * @return Hardware random value when available, otherwise a non-cryptographic interface sequence. */ -static uint32_t ipv6_jitter_word(void) +/** @brief Look up link state by stable interface index, optionally allocating it. */ +static struct ipv6_link *ipv6_link(uint32_t ifindex,bool create) +{ + for(struct ipv6_link *link=ipv6_links;link;link=link->next) + if(link->ifindex==ifindex)return link; + if(!create)return 0; + struct ipv6_link *link=kernel_malloc(sizeof(*link));if(!link)return 0; + net_memzero(link,sizeof(*link));link->ifindex=ifindex; + link->mtu=IPV6_LINK_MTU;link->base_reachable_ms=30000;link->retrans_ms=1000; + link->next=ipv6_links;ipv6_links=link; + return link; +} +/** @brief Select the historical default NIC, including in legacy white-box fixtures. */ +static uint32_t ipv6_default_interface(void) +{ + uint32_t ifindex=netdev_next(0); + return ifindex?ifindex:NET_IPV6_IF_ETHERNET; +} +/** @brief Obtain per-interface discovery jitter without requiring hardware RNG. */ +static uint32_t ipv6_jitter_word(struct ipv6_link *link) { uint32_t value; if(kernel_random_fill(&value,sizeof(value))==0)return value; - if(!ipv6_rng_state){const uint8_t *mac=e1000_mac();uint64_t now=time_uptime_ms(); - ipv6_rng_state=(uint32_t)now^(uint32_t)(now>>32)^0x9e3779b9u; - for(uint32_t i=0;i<6;i++)ipv6_rng_state=(ipv6_rng_state^mac[i])*16777619u; - if(!ipv6_rng_state)ipv6_rng_state=0x6d2b79f5u;} - value=ipv6_rng_state;value^=value<<13;value^=value>>17;value^=value<<5; - ipv6_rng_state=value;return value; + if(!link->rng_state){const uint8_t *mac=netdev_mac(link->ifindex);uint64_t now=time_uptime_ms(); + link->rng_state=(uint32_t)now^(uint32_t)(now>>32)^0x9e3779b9u^link->ifindex; + if(mac)for(uint32_t i=0;i<6;i++)link->rng_state=(link->rng_state^mac[i])*16777619u; + if(!link->rng_state)link->rng_state=0x6d2b79f5u;} + value=link->rng_state;value^=value<<13;value^=value>>17;value^=value<<5; + link->rng_state=value;return value; } -/** @brief Choose a bounded, uniformly scaled discovery delay. - * @param maximum Largest accepted delay, at most UINT32_MAX. @return Milliseconds in the inclusive range. */ -static uint32_t ipv6_jitter(uint32_t maximum) -{ return (uint32_t)(((uint64_t)ipv6_jitter_word()*((uint64_t)maximum+1u))>>32); } -/** @brief Recompute randomized neighbor reachability using the current advertised base time. */ -static void ipv6_refresh_reachable(void) +/** @brief Choose a bounded, uniformly scaled discovery delay on one link. */ +static uint32_t ipv6_jitter(struct ipv6_link *link,uint32_t maximum) +{ return (uint32_t)(((uint64_t)ipv6_jitter_word(link)*((uint64_t)maximum+1u))>>32); } +/** @brief Recompute one link's randomized neighbor reachability time. */ +static void ipv6_refresh_reachable(struct ipv6_link *link) { - ipv6_reachable_ms=(uint64_t)ipv6_base_reachable_ms/2u+ipv6_jitter(ipv6_base_reachable_ms); - ipv6_reachable_refresh=time_uptime_ms()+7200000u; + link->reachable_ms=(uint64_t)link->base_reachable_ms/2u+ipv6_jitter(link,link->base_reachable_ms); + link->reachable_refresh=time_uptime_ms()+7200000u; } -/** @brief Restore interface discovery timing defaults and choose a fresh reachable interval. */ -static void ipv6_reset_timing(void) -{ ipv6_base_reachable_ms=30000;ipv6_retrans_ms=1000;ipv6_refresh_reachable(); } +/** @brief Restore one interface's discovery timing defaults. */ +static void ipv6_reset_timing(struct ipv6_link *link) +{ link->base_reachable_ms=30000;link->retrans_ms=1000;ipv6_refresh_reachable(link); } /** @brief Compare IPv6 addresses. @param a First. @param b Second. @return True if identical. */ static bool ipv6_equal(const struct in6_addr *a,const struct in6_addr *b) { return net_memeq(a->s6_addr,b->s6_addr,16)!=0; } @@ -244,7 +270,7 @@ uint32_t net_ipv6_neighbors(struct net_ipv6_neighbor *out,uint32_t capacity) * @param ifindex Interface. @param delta One to join or minus one to leave. @return Zero or errno. */ int net_ipv6_multicast_change(const struct in6_addr *address,uint32_t ifindex,int delta) { - if(!address || !ipv6_multicast(address) || (ifindex!=1 && ifindex!=2) || (delta!=1 && delta!=-1))return -LINUX_EINVAL; + if(!address || !ipv6_multicast(address) || !net_interface_exists(ifindex) || (delta!=1 && delta!=-1))return -LINUX_EINVAL; struct ipv6_group_slot *g=ipv6_find_group(address,ifindex); if(delta<0){if(!g)return -LINUX_EADDRNOTAVAIL;if(--g->refs==0){ /* Keep the group briefly to send an MLD state-change record. */ @@ -259,7 +285,7 @@ int net_ipv6_multicast_change(const struct in6_addr *address,uint32_t ifindex,in * @param remove Remove rather than add. @return Zero or negative errno. */ int net_ipv6_route_update(const struct net_ipv6_route *route,bool remove) { - if(!route || route->prefix_length>128 || (route->ifindex!=1 && route->ifindex!=2) || + if(!route || route->prefix_length>128 || !net_interface_exists(route->ifindex) || ipv6_multicast(&route->gateway))return -LINUX_EINVAL; struct net_ipv6_route normalized=*route;ipv6_mask(&normalized.destination,normalized.prefix_length); struct ipv6_route_slot *free_slot=0; @@ -277,12 +303,12 @@ int net_ipv6_route_update(const struct net_ipv6_route *route,bool remove) /** @brief Recompute an automatic connected route from manually assigned addresses. * @param address An address in the prefix. @param bits Prefix width. * @return Zero or negative capacity errno. */ -static int ipv6_connected_route(const struct in6_addr *address,uint32_t bits) +static int ipv6_connected_route(const struct in6_addr *address,uint32_t bits,uint32_t ifindex) { struct in6_addr prefix=*address;ipv6_mask(&prefix,bits);uint64_t deadline=0,now=time_uptime_ms(); for(uint32_t i=0;iused && !a->autonomous && a->value.ifindex==2 && a->value.prefix_length==bits && + if(a->used && !a->autonomous && a->value.ifindex==ifindex && a->value.prefix_length==bits && ipv6_prefix_equal(&a->value.address,&prefix,bits) && !ipv6_expired(a->value.valid_until,now) && a->value.valid_until>deadline)deadline=a->value.valid_until; } @@ -290,12 +316,12 @@ static int ipv6_connected_route(const struct in6_addr *address,uint32_t bits) for(uint32_t i=0;iused){if(!empty)empty=r;continue;} - if(r->value.ifindex==2 && r->value.prefix_length==bits && ipv6_unspecified(&r->value.gateway) && + if(r->value.ifindex==ifindex && r->value.prefix_length==bits && ipv6_unspecified(&r->value.gateway) && ipv6_equal(&r->value.destination,&prefix)){slot=r;break;} } if(slot && !slot->automatic)return 0; if(!slot){if(!deadline)return 0;if(!empty)return -LINUX_ENOBUFS;slot=empty;net_memzero(slot,sizeof(*slot)); - slot->used=true;slot->automatic=true;slot->value.destination=prefix;slot->value.ifindex=2; + slot->used=true;slot->automatic=true;slot->value.destination=prefix;slot->value.ifindex=ifindex; slot->value.prefix_length=bits;slot->value.metric=256; slot->value.flags=RTF_UP | (bits==128?RTF_HOST:0);} slot->address_until=deadline; @@ -309,40 +335,42 @@ static int ipv6_connected_route(const struct in6_addr *address,uint32_t bits) * @param autonomous True for router-derived addresses. @return Zero or negative errno. */ static int ipv6_address_set(const struct net_ipv6_address *address,bool remove,bool autonomous) { - if(!address || address->prefix_length>128 || (address->ifindex!=1 && address->ifindex!=2) || + if(!address || address->prefix_length>128 || !net_interface_exists(address->ifindex) || ipv6_multicast(&address->address) || ipv6_unspecified(&address->address) || address->preferred_until>address->valid_until)return -LINUX_EINVAL; struct ipv6_address_slot *slot=ipv6_find_address(&address->address,address->ifindex); if(remove){if(!slot)return -LINUX_EADDRNOTAVAIL; if(slot->value.ifindex==1 && ipv6_equal(&slot->value.address,&ipv6_loop))return -LINUX_EPERM; struct in6_addr group;ipv6_solicited(&group,&slot->value.address); - if(slot->value.ifindex==2)(void)net_ipv6_multicast_change(&group,slot->value.ifindex,-1); - bool connected=slot->value.ifindex==2 && !slot->autonomous; + if(slot->value.ifindex!=1)(void)net_ipv6_multicast_change(&group,slot->value.ifindex,-1); + bool connected=slot->value.ifindex!=1 && !slot->autonomous; struct net_ipv6_address old=slot->value;slot->used=false; - if(connected)(void)ipv6_connected_route(&old.address,old.prefix_length); + if(connected)(void)ipv6_connected_route(&old.address,old.prefix_length,old.ifindex); netlink_ipv6_changed();return 0;} if(slot){struct net_ipv6_address old=slot->value;bool old_auto=slot->autonomous; uint32_t state=slot->value.flags & (NET_IPV6_ADDR_TENTATIVE|NET_IPV6_ADDR_DADFAILED); slot->value=*address;slot->value.flags|=state;slot->value.scope=ipv6_scope(&address->address); slot->autonomous=autonomous; - if(old.ifindex==2 && !old_auto)(void)ipv6_connected_route(&old.address,old.prefix_length); - if(address->ifindex==2 && !autonomous){int rc=ipv6_connected_route(&address->address,address->prefix_length); + if(old.ifindex!=1 && !old_auto)(void)ipv6_connected_route(&old.address,old.prefix_length,old.ifindex); + if(address->ifindex!=1 && !autonomous){int rc=ipv6_connected_route(&address->address,address->prefix_length,address->ifindex); if(rc<0){slot->value=old;slot->autonomous=old_auto; - if(old.ifindex==2 && !old_auto)(void)ipv6_connected_route(&old.address,old.prefix_length); + if(old.ifindex!=1 && !old_auto)(void)ipv6_connected_route(&old.address,old.prefix_length,old.ifindex); return rc;}} netlink_ipv6_changed();return 0;} for(uint32_t i=0;iaddress); - if(address->ifindex==2){int ret=net_ipv6_multicast_change(&group,address->ifindex,1);if(ret<0)return ret;} + if(address->ifindex!=1){int ret=net_ipv6_multicast_change(&group,address->ifindex,1);if(ret<0)return ret;} slot->used=true;slot->value=*address;slot->value.scope=ipv6_scope(&address->address); slot->autonomous=autonomous;slot->dad_sent=false;slot->dad_wait_mld=false;slot->dad_at=time_uptime_ms(); - if(address->ifindex==2 && !(address->flags&NET_IPV6_ADDR_NODAD)){ - slot->value.flags|=NET_IPV6_ADDR_TENTATIVE;slot->dad_at+=ipv6_jitter(1000); + if(address->ifindex!=1 && !(address->flags&NET_IPV6_ADDR_NODAD)){ + slot->value.flags|=NET_IPV6_ADDR_TENTATIVE;struct ipv6_link *link=ipv6_link(address->ifindex,true); + if(!link){slot->used=false;(void)net_ipv6_multicast_change(&group,address->ifindex,-1);return -LINUX_ENOMEM;} + slot->dad_at+=ipv6_jitter(link,1000); struct ipv6_group_slot *g=ipv6_find_group(&group,address->ifindex); if(g && g->refs==1 && g->reports==2){g->report_at=slot->dad_at;slot->dad_wait_mld=true;} } - if(address->ifindex==2 && !autonomous){int ret=ipv6_connected_route(&address->address,address->prefix_length); + if(address->ifindex!=1 && !autonomous){int ret=ipv6_connected_route(&address->address,address->prefix_length,address->ifindex); if(ret<0){slot->used=false;(void)net_ipv6_multicast_change(&group,address->ifindex,-1);return ret;}} netlink_ipv6_changed();return 0; } @@ -358,34 +386,47 @@ int net_ipv6_address_update(const struct net_ipv6_address *address,bool remove) int net_ipv6_route(const struct in6_addr *destination,uint32_t scope, const struct in6_addr *bound,struct net_ipv6_path *out_path) { - if(!destination || !out_path || ipv6_unspecified(destination) || (scope && scope!=1 && scope!=2))return -LINUX_EINVAL; + if(!destination || !out_path || ipv6_unspecified(destination) || (scope && !net_interface_exists(scope)))return -LINUX_EINVAL; uint64_t now=time_uptime_ms();uint32_t ifindex=0,mtu=IPV6_LINK_MTU; struct in6_addr next=*destination; - struct ipv6_address_slot *local=ipv6_find_address(destination,0); + struct ipv6_address_slot *local=ipv6_find_address(destination,scope); if(ipv6_equal(destination,&ipv6_loop)){ifindex=1;mtu=65575;} else if(local && !(local->value.flags&(NET_IPV6_ADDR_TENTATIVE|NET_IPV6_ADDR_DADFAILED))){ ifindex=local->value.ifindex;mtu=65575; } else if(ipv6_multicast(destination)){ uint8_t ms=destination->s6_addr[1]&15; if(ms==0)return -LINUX_EINVAL; - ifindex=scope?scope:(ms==1?1:2);mtu=ifindex==1?65575:ipv6_link_mtu; - if(ms==1 && ifindex!=1)return -LINUX_ENETUNREACH; + ifindex=scope?scope:(ms==1?1:ipv6_default_interface()); + if(!scope && bound && !ipv6_unspecified(bound)){ + struct ipv6_address_slot *binding=ipv6_find_address(bound,0); + if(binding)ifindex=binding->value.ifindex; + } } else { - struct ipv6_route_slot *best=0; + struct ipv6_route_slot *best=0;bool matched_route=false; for(uint32_t i=0;iused || ipv6_expired(r->value.valid_until,now) || (scope && r->value.ifindex!=scope) || !ipv6_prefix_equal(destination,&r->value.destination,r->value.prefix_length))continue; + matched_route=true; + if(bound && !ipv6_unspecified(bound)){ + struct ipv6_address_slot *binding=ipv6_find_address(bound,r->value.ifindex); + if(!binding || binding->value.flags&(NET_IPV6_ADDR_TENTATIVE|NET_IPV6_ADDR_DADFAILED) || + ipv6_expired(binding->value.valid_until,now))continue; + } if(!best || r->value.prefix_length>best->value.prefix_length || (r->value.prefix_length==best->value.prefix_length && r->value.metricvalue.metric))best=r; } - if(!best)return -LINUX_ENETUNREACH; - ifindex=best->value.ifindex;mtu=ifindex==1?65575:ipv6_link_mtu; + if(!best)return matched_route?-LINUX_EADDRNOTAVAIL:-LINUX_ENETUNREACH; + ifindex=best->value.ifindex; if(!ipv6_unspecified(&best->value.gateway))next=best->value.gateway; } if(scope && scope!=ifindex)return -LINUX_ENETUNREACH; - if(ifindex==2 && (!ipv6_link_up || !e1000_is_ready()))return -LINUX_ENETDOWN; - if(ifindex==2 && ipv6_equal(destination,&ipv6_loop))return -LINUX_ENETUNREACH; + if(ifindex!=1){ + struct ipv6_link *link=ipv6_link(ifindex,false); + if(!link || !link->up || !net_interface_is_ready(ifindex))return -LINUX_ENETDOWN; + if(mtu!=65575)mtu=link->mtu; + if(ipv6_equal(destination,&ipv6_loop))return -LINUX_ENETUNREACH; + } struct ipv6_address_slot *source=0;uint32_t best_score=0; for(uint32_t i=0;ivalue.address))continue; bool link_destination=ipv6_linklocal(destination) || (ipv6_multicast(destination) && (destination->s6_addr[1]&15)<=2); - if(ifindex==2 && !link_destination && ipv6_linklocal(&a->value.address))continue; + if(ifindex!=1 && !link_destination && ipv6_linklocal(&a->value.address))continue; uint32_t score=ipv6_match_bits(destination,&a->value.address)+1; - if(ifindex==2 && link_destination && ipv6_linklocal(&a->value.address))score+=512; + if(ifindex!=1 && link_destination && ipv6_linklocal(&a->value.address))score+=512; if(!(a->value.flags&NET_IPV6_ADDR_DEPRECATED) && !ipv6_expired(a->value.preferred_until,now))score+=256; if(ipv6_equal(destination,&a->value.address))score+=512; if(!source || score>best_score){source=a;best_score=score;} @@ -412,35 +453,36 @@ int net_ipv6_route(const struct in6_addr *destination,uint32_t scope, * @param source IPv6 source. @param destination IPv6 destination. @param next Next header. * @param hops Hop limit. @param flowinfo Network-order flow bits. @param payload Bytes. * @param length Payload length no greater than Ethernet MTU minus IPv6 header. @return Driver status. */ -static int ipv6_send_frame(const uint8_t mac[6],const struct in6_addr *source, +static int ipv6_send_frame(uint32_t ifindex,const uint8_t mac[6],const struct in6_addr *source, const struct in6_addr *destination,uint8_t next,uint8_t hops, uint32_t flowinfo,const uint8_t *payload,uint32_t length) { - if(!ipv6_link_up || !e1000_is_ready())return -LINUX_ENETDOWN; + struct ipv6_link *link=ipv6_link(ifindex,false); + if(!link || !link->up || !net_interface_is_ready(ifindex))return -LINUX_ENETDOWN; if(length>IPV6_LINK_MTU-40)return -LINUX_EMSGSIZE; uint8_t frame[14+IPV6_LINK_MTU];uint8_t *ip=frame+14;const uint8_t *flow=(const uint8_t *)&flowinfo; - net_write_eth(frame,mac,IPV6_ETHERTYPE);ip[0]=(uint8_t)(0x60|(flow[0]&15)); + net_memcpy(frame,mac,6);net_memcpy(frame+6,netdev_mac(ifindex),6);net_put_u16(frame+12,IPV6_ETHERTYPE);ip[0]=(uint8_t)(0x60|(flow[0]&15)); ip[1]=flow[1];ip[2]=flow[2];ip[3]=flow[3];net_put_u16(ip+4,(uint16_t)length); ip[6]=next;ip[7]=hops;net_memcpy(ip+8,source->s6_addr,16);net_memcpy(ip+24,destination->s6_addr,16); if(length)net_memcpy(ip+40,payload,length); - return e1000_send(frame,14+40+length); + return netdev_send(ifindex,frame,14+40+length); } /** @brief Derive an Ethernet destination from an IPv6 multicast group. @param mac Six-byte output. * @param address Multicast destination. */ static void ipv6_multicast_mac(uint8_t mac[6],const struct in6_addr *address) { mac[0]=0x33;mac[1]=0x33;net_memcpy(mac+2,address->s6_addr+12,4); } /** @brief Find one neighbor. @param address Neighbor address. @return Entry or NULL. */ -static struct ipv6_neighbor_slot *ipv6_find_neighbor(const struct in6_addr *address) +static struct ipv6_neighbor_slot *ipv6_find_neighbor(uint32_t ifindex,const struct in6_addr *address) { - for(uint32_t i=0;ivalue.state!=IPV6_NUD_FAILED)return slot; if(slot){net_memzero(slot->value.mac,6);slot->value.state=IPV6_NUD_INCOMPLETE; slot->deadline=time_uptime_ms();slot->probes=0;netlink_ipv6_changed();return slot;} @@ -448,28 +490,28 @@ static struct ipv6_neighbor_slot *ipv6_create_neighbor(const struct in6_addr *ad ipv6_neighbors[i].value.state==IPV6_NUD_FAILED){slot=&ipv6_neighbors[i];break;} if(!slot)return 0; net_memzero(slot,sizeof(*slot));slot->used=true;slot->value.address=*address; - slot->value.ifindex=2;slot->value.state=IPV6_NUD_INCOMPLETE;slot->deadline=time_uptime_ms(); + slot->value.ifindex=ifindex;slot->value.state=IPV6_NUD_INCOMPLETE;slot->deadline=time_uptime_ms(); netlink_ipv6_changed();return slot; } /** @brief Send a Neighbor Solicitation for DAD or address resolution. @param target Target. * @param source Usable source or unspecified for DAD. @param unicast Optional neighbor MAC. * @return Driver status. */ -static int ipv6_send_ns(const struct in6_addr *target,const struct in6_addr *source,const uint8_t *unicast) +static int ipv6_send_ns(uint32_t ifindex,const struct in6_addr *target,const struct in6_addr *source,const uint8_t *unicast) { struct in6_addr dest;uint8_t mac[6],ns[32]={0};uint32_t len=24; if(unicast){dest=*target;net_memcpy(mac,unicast,6);}else{ipv6_solicited(&dest,target);ipv6_multicast_mac(mac,&dest);} ns[0]=135;net_memcpy(ns+8,target->s6_addr,16); - if(!ipv6_unspecified(source)){ns[24]=1;ns[25]=1;net_memcpy(ns+26,e1000_mac(),6);len=32;} + if(!ipv6_unspecified(source)){ns[24]=1;ns[25]=1;net_memcpy(ns+26,netdev_mac(ifindex),6);len=32;} net_put_u16(ns+2,net_ipv6_checksum(source,&dest,58,ns,len)); - return ipv6_send_frame(mac,source,&dest,58,255,0,ns,len); + return ipv6_send_frame(ifindex,mac,source,&dest,58,255,0,ns,len); } /** @brief Send a Router Solicitation after link-local DAD. @param source Link-local source. * @return Driver status. */ -static int ipv6_send_rs(const struct in6_addr *source) +static int ipv6_send_rs(uint32_t ifindex,const struct in6_addr *source) { uint8_t mac[6],rs[16]={133,0,0,0,0,0,0,0,1,1};ipv6_multicast_mac(mac,&ipv6_all_routers); - net_memcpy(rs+10,e1000_mac(),6);net_put_u16(rs+2,net_ipv6_checksum(source,&ipv6_all_routers,58,rs,16)); - return ipv6_send_frame(mac,source,&ipv6_all_routers,58,255,0,rs,16); + net_memcpy(rs+10,netdev_mac(ifindex),6);net_put_u16(rs+2,net_ipv6_checksum(source,&ipv6_all_routers,58,rs,16)); + return ipv6_send_frame(ifindex,mac,source,&ipv6_all_routers,58,255,0,rs,16); } /** @brief Transmit one datagram, fragmenting at the source when necessary. @param path Route snapshot. * @param protocol Upper next-header. @param data Borrowed bytes. @param length Length. @@ -478,17 +520,19 @@ static int ipv6_send_rs(const struct in6_addr *source) static int ipv6_transmit(const struct net_ipv6_path *path,uint8_t protocol,const uint8_t *data, uint32_t length,int hops,uint32_t flowinfo,bool dont_fragment,const uint8_t mac[6]) { - uint32_t mtu=path->mtu;if(mtu>ipv6_link_mtu)mtu=ipv6_link_mtu; + struct ipv6_link *link=ipv6_link(path->ifindex,false); + if(!link || !link->up)return -LINUX_ENETDOWN; + uint32_t mtu=path->mtu;if(mtu>link->mtu)mtu=link->mtu; if(mtusource,&path->destination,protocol,hop,flowinfo,data,length); + if(length+40<=mtu){int rc=ipv6_send_frame(path->ifindex,mac,&path->source,&path->destination,protocol,hop,flowinfo,data,length); return rc<0?rc:(int)length;} if(dont_fragment)return -LINUX_EMSGSIZE; uint32_t chunk=(mtu-48)&~7u,ident=++ipv6_frag_id,offset=0;uint8_t fragment[IPV6_LINK_MTU-40]; while(offsetchunk)n=chunk; fragment[0]=protocol;fragment[1]=0;net_put_u16(fragment+2,(uint16_t)(offset|(offset+nsource,&path->destination,44,hop,flowinfo,fragment,n+8); + int rc=ipv6_send_frame(path->ifindex,mac,&path->source,&path->destination,44,hop,flowinfo,fragment,n+8); if(rc<0)return rc; offset+=n;} return (int)length; @@ -519,14 +563,21 @@ int net_ipv6_output(const struct net_ipv6_path *path,uint8_t protocol,const void { if(length>65535)return -LINUX_EMSGSIZE; if(!path || (!data && length) || hops < -1 || hops>255 || - (path->ifindex!=1 && path->ifindex!=2))return -LINUX_EINVAL; - if(path->ifindex==2 && (!ipv6_link_up || !e1000_is_ready()))return -LINUX_ENETDOWN; + !net_interface_exists(path->ifindex))return -LINUX_EINVAL; + struct ipv6_link *link=path->ifindex==1?0:ipv6_link(path->ifindex,false); + if(path->ifindex!=1 && (!link || !link->up || !net_interface_is_ready(path->ifindex)))return -LINUX_ENETDOWN; if(dont_fragment && length+40>path->mtu)return -LINUX_EMSGSIZE; struct ipv6_address_slot *dst=ipv6_find_address(&path->destination,path->ifindex); if(path->ifindex==1 || (dst && !(dst->value.flags&(NET_IPV6_ADDR_TENTATIVE|NET_IPV6_ADDR_DADFAILED)))) return ipv6_queue(path,protocol,data,length,hops,flowinfo,dont_fragment,true); if(ipv6_multicast(&path->destination)){ struct ipv6_group_slot *group=ipv6_find_group(&path->destination,path->ifindex); + /* Interface-local multicast is delivered only inside this host. */ + if((path->destination.s6_addr[1]&15)==1){ + if(multicast_loop && group && group->refs) + return ipv6_queue(path,protocol,data,length,hops,flowinfo,dont_fragment,true); + return (int)length; + } struct ipv6_pending *queued=0; if(multicast_loop && (ipv6_equal(&path->destination,&ipv6_all_nodes) || (group && group->refs))){ for(uint32_t i=0;idata);queued->used=false;} return rc; } - struct ipv6_neighbor_slot *neighbor=ipv6_create_neighbor(&path->next_hop); + struct ipv6_neighbor_slot *neighbor=ipv6_create_neighbor(path->ifindex,&path->next_hop); if(!neighbor)return -LINUX_ENOBUFS; if(neighbor->value.state!=IPV6_NUD_INCOMPLETE && neighbor->value.state!=IPV6_NUD_FAILED){ if(neighbor->value.state==IPV6_NUD_STALE){neighbor->value.state=IPV6_NUD_DELAY;neighbor->deadline=time_uptime_ms()+5000;} return ipv6_transmit(path,protocol,data,length,hops,flowinfo,dont_fragment,neighbor->value.mac); } int rc=ipv6_queue(path,protocol,data,length,hops,flowinfo,dont_fragment,false); - if(rc>=0 && neighbor->probes==0){(void)ipv6_send_ns(&path->next_hop,&path->source,0); - neighbor->probes=1;neighbor->deadline=time_uptime_ms()+ipv6_retrans_ms;} + if(rc>=0 && neighbor->probes==0){(void)ipv6_send_ns(path->ifindex,&path->next_hop,&path->source,0); + neighbor->probes=1;neighbor->deadline=time_uptime_ms()+link->retrans_ms;} return rc; } @@ -557,14 +608,14 @@ static void ipv6_pending_error(const struct ipv6_pending *pending,int error) uint8_t quote[128]={0};uint32_t n=pending->length;if(n>sizeof(quote)-40)n=sizeof(quote)-40; quote[0]=0x60;net_put_u16(quote+4,(uint16_t)pending->length);quote[6]=pending->protocol; net_memcpy(quote+8,pending->path.source.s6_addr,16);net_memcpy(quote+24,pending->path.destination.s6_addr,16); - net_memcpy(quote+40,pending->data,n);net_ipv6_socket_error(quote,n+40,error,0); + net_memcpy(quote+40,pending->data,n);net_ipv6_socket_error_on(pending->path.ifindex,quote,n+40,error,0); } /** @brief Release queued packets for a newly resolved neighbor. @param neighbor Resolved entry. */ static void ipv6_flush_neighbor(struct ipv6_neighbor_slot *neighbor) { for(uint32_t i=0;iused || p->loopback || - !ipv6_equal(&p->path.next_hop,&neighbor->value.address))continue; + p->path.ifindex!=neighbor->value.ifindex || !ipv6_equal(&p->path.next_hop,&neighbor->value.address))continue; int rc=ipv6_transmit(&p->path,p->protocol,p->data,p->length,p->hops,p->flowinfo,p->dont_fragment,neighbor->value.mac); if(rc<0)ipv6_pending_error(p,-rc); kernel_free(p->data);p->used=false; @@ -572,14 +623,15 @@ static void ipv6_flush_neighbor(struct ipv6_neighbor_slot *neighbor) } /** @brief Update neighbor state from a validated link-layer option. @param address Neighbor address. * @param mac Link address. @param solicited Valid solicited advertisement. @param override Replace changed address. */ -static void ipv6_learn_neighbor(const struct in6_addr *address,const uint8_t mac[6],bool solicited,bool override) +static void ipv6_learn_neighbor(uint32_t ifindex,const struct in6_addr *address,const uint8_t mac[6],bool solicited,bool override) { if(ipv6_unspecified(address) || ipv6_multicast(address) || (mac[0]&1))return; - struct ipv6_neighbor_slot *n=ipv6_create_neighbor(address);if(!n)return; + struct ipv6_neighbor_slot *n=ipv6_create_neighbor(ifindex,address);if(!n)return; bool changed=n->value.state!=IPV6_NUD_INCOMPLETE && !net_memeq(n->value.mac,mac,6); if(changed && !override){if(n->value.state==IPV6_NUD_REACHABLE)n->value.state=IPV6_NUD_STALE;return;} net_memcpy(n->value.mac,mac,6);n->value.state=solicited?IPV6_NUD_REACHABLE:IPV6_NUD_STALE; - n->deadline=time_uptime_ms()+ipv6_reachable_ms;n->probes=0;netlink_ipv6_changed();ipv6_flush_neighbor(n); + struct ipv6_link *link=ipv6_link(ifindex,false);if(!link)return; + n->deadline=time_uptime_ms()+link->reachable_ms;n->probes=0;netlink_ipv6_changed();ipv6_flush_neighbor(n); } /** @brief Find and validate an ND link-layer option. @param options Option bytes. @param length Byte length. * @param kind Wanted option type. @param mac Output pointer into input or NULL. @return False for malformed options. */ @@ -594,13 +646,13 @@ static bool ipv6_nd_options(const uint8_t *options,uint32_t length,uint8_t kind, } /** @brief Respond to a valid solicitation for one of our addresses. @param target Local address. * @param destination Recipient address. @param mac Ethernet recipient. @param solicited Solicited response. */ -static void ipv6_send_na(const struct in6_addr *target,const struct in6_addr *destination, +static void ipv6_send_na(uint32_t ifindex,const struct in6_addr *target,const struct in6_addr *destination, const uint8_t mac[6],bool solicited) { uint8_t na[32]={136,0,0,0,0,0,0,0};na[4]=(uint8_t)(0x20|(solicited?0x40:0)); - net_memcpy(na+8,target->s6_addr,16);na[24]=2;na[25]=1;net_memcpy(na+26,e1000_mac(),6); + net_memcpy(na+8,target->s6_addr,16);na[24]=2;na[25]=1;net_memcpy(na+26,netdev_mac(ifindex),6); net_put_u16(na+2,net_ipv6_checksum(target,destination,58,na,32)); - (void)ipv6_send_frame(mac,target,destination,58,255,0,na,32); + (void)ipv6_send_frame(ifindex,mac,target,destination,58,255,0,na,32); } /** @brief Mark an address as a failed duplicate. @param slot Tentative local address. */ static void ipv6_dad_failed(struct ipv6_address_slot *slot) @@ -611,20 +663,20 @@ static void ipv6_dad_failed(struct ipv6_address_slot *slot) /** @brief Install or refresh an automatically learned prefix route. @param prefix Prefix address. * @param bits Prefix width. @param gateway Gateway or unspecified. @param deadline Lifetime. * @param metric Selection metric. @param addrconf True for an RA-derived route. */ -static void ipv6_auto_route(const struct in6_addr *prefix,uint32_t bits,const struct in6_addr *gateway, +static void ipv6_auto_route(uint32_t ifindex,const struct in6_addr *prefix,uint32_t bits,const struct in6_addr *gateway, uint64_t deadline,uint32_t metric,bool addrconf) { struct in6_addr network=*prefix;ipv6_mask(&network,bits);uint64_t now=time_uptime_ms(); struct ipv6_route_slot *slot=0,*empty=0; for(uint32_t i=0;iused){if(!empty)empty=r;continue;} - if(r->value.ifindex==2 && r->value.prefix_length==bits && ipv6_equal(&r->value.destination,&network) && + if(r->value.ifindex==ifindex && r->value.prefix_length==bits && ipv6_equal(&r->value.destination,&network) && ipv6_equal(&r->value.gateway,gateway)){slot=r;break;} } if(slot && !slot->automatic)return; if(!slot){if(ipv6_expired(deadline,now) || !empty)return;slot=empty;net_memzero(slot,sizeof(*slot)); slot->used=true;slot->automatic=true;slot->value.destination=network;slot->value.gateway=*gateway; - slot->value.prefix_length=bits;slot->value.ifindex=2;slot->value.metric=metric; + slot->value.prefix_length=bits;slot->value.ifindex=ifindex;slot->value.metric=metric; slot->value.flags=RTF_UP | (bits==128?RTF_HOST:0) | (!ipv6_unspecified(gateway)?RTF_GATEWAY:0);} if(addrconf)slot->value.flags|=RTF_ADDRCONF; slot->ra_until=ipv6_expired(deadline,now)?0:deadline; @@ -636,12 +688,12 @@ static void ipv6_auto_route(const struct in6_addr *prefix,uint32_t bits,const st /** @brief Apply a validated autonomous /64 prefix with RFC 4862 lifetime protection. * @param prefix Prefix information option. @param valid Valid lifetime seconds. * @param preferred Preferred lifetime seconds. */ -static void ipv6_slaac(const uint8_t *prefix,uint32_t valid,uint32_t preferred) +static void ipv6_slaac(uint32_t ifindex,const uint8_t *prefix,uint32_t valid,uint32_t preferred) { struct in6_addr address;net_memcpy(address.s6_addr,prefix,8); - const uint8_t *mac=e1000_mac();address.s6_addr[8]=mac[0]^2;address.s6_addr[9]=mac[1];address.s6_addr[10]=mac[2]; + const uint8_t *mac=netdev_mac(ifindex);address.s6_addr[8]=mac[0]^2;address.s6_addr[9]=mac[1];address.s6_addr[10]=mac[2]; address.s6_addr[11]=0xff;address.s6_addr[12]=0xfe;address.s6_addr[13]=mac[3];address.s6_addr[14]=mac[4];address.s6_addr[15]=mac[5]; - struct ipv6_address_slot *slot=ipv6_find_address(&address,2);uint64_t now=time_uptime_ms(); + struct ipv6_address_slot *slot=ipv6_find_address(&address,ifindex);uint64_t now=time_uptime_ms(); uint64_t valid_until=ipv6_deadline(valid),preferred_until=ipv6_deadline(preferred); if(slot){if(!slot->autonomous)return; uint64_t remaining=slot->value.valid_until==IPV6_FOREVER?IPV6_FOREVER: @@ -657,44 +709,45 @@ static void ipv6_slaac(const uint8_t *prefix,uint32_t valid,uint32_t preferred) netlink_ipv6_changed();return; } if(!valid)return; - struct net_ipv6_address entry={0};entry.address=address;entry.ifindex=2;entry.prefix_length=64; + struct net_ipv6_address entry={0};entry.address=address;entry.ifindex=ifindex;entry.prefix_length=64; entry.preferred_until=preferred_until;entry.valid_until=valid_until; if(preferred==0)entry.flags|=NET_IPV6_ADDR_DEPRECATED; (void)ipv6_address_set(&entry,false,true); } /** @brief Process a validated router advertisement and its bounded options. * @param source Link-local router. @param icmp Message. @param length Bytes. */ -static void ipv6_router_advertisement(const struct in6_addr *source,const uint8_t *icmp,uint32_t length) +static void ipv6_router_advertisement(uint32_t ifindex,const struct in6_addr *source,const uint8_t *icmp,uint32_t length) { - if(!ipv6_linklocal(source) || length<16 || icmp[1])return; + struct ipv6_link *link=ipv6_link(ifindex,false); + if(!link || !ipv6_linklocal(source) || length<16 || icmp[1])return; /* Validate the entire option sequence before applying any changes. */ for(uint32_t off=16;offlength-off)return;off+=n;} uint32_t reachable=net_get_u32(icmp+8),retrans=net_get_u32(icmp+12); - if(reachable && reachable!=ipv6_base_reachable_ms){ipv6_base_reachable_ms=reachable;ipv6_refresh_reachable();} - if(retrans)ipv6_retrans_ms=retrans; - ipv6_rs_sent=3; - uint16_t life=net_get_u16(icmp+6);ipv6_auto_route(&ipv6_zero,0,source,ipv6_deadline(life),1024,true); + if(reachable && reachable!=link->base_reachable_ms){link->base_reachable_ms=reachable;ipv6_refresh_reachable(link);} + if(retrans)link->retrans_ms=retrans; + link->rs_sent=3; + uint16_t life=net_get_u16(icmp+6);ipv6_auto_route(ifindex,&ipv6_zero,0,source,ipv6_deadline(life),1024,true); for(uint32_t off=16;off=IPV6_MIN_MTU && mtu<=IPV6_LINK_MTU)ipv6_link_mtu=mtu;} + if(kind==1 && n==8)ipv6_learn_neighbor(ifindex,source,p+2,false,true); + else if(kind==5 && n==8){uint32_t mtu=net_get_u32(p+4);if(mtu>=IPV6_MIN_MTU && mtu<=IPV6_LINK_MTU)link->mtu=mtu;} else if(kind==3 && n==32){uint8_t bits=p[2],flags=p[3];uint32_t valid=net_get_u32(p+4),preferred=net_get_u32(p+8); struct in6_addr prefix;net_memcpy(prefix.s6_addr,p+16,16); if(bits<=128 && !ipv6_linklocal(&prefix) && !ipv6_multicast(&prefix)){ - if(flags&0x80)ipv6_auto_route(&prefix,bits,&ipv6_zero,ipv6_deadline(valid),256,true); - if((flags&0x40) && bits==64 && preferred<=valid)ipv6_slaac(prefix.s6_addr,valid,preferred); + if(flags&0x80)ipv6_auto_route(ifindex,&prefix,bits,&ipv6_zero,ipv6_deadline(valid),256,true); + if((flags&0x40) && bits==64 && preferred<=valid)ipv6_slaac(ifindex,prefix.s6_addr,valid,preferred); }} off+=n; } } /** @brief Remember a validated path MTU update from an ICMPv6 quote. @param quote IPv6 header. * @param length Quote length. @param mtu Reported MTU. */ -static void ipv6_update_pmtu(const uint8_t *quote,uint32_t length,uint32_t mtu) +static void ipv6_update_pmtu(uint32_t ifindex,const uint8_t *quote,uint32_t length,uint32_t mtu) { if(length<40 || quote[0]>>4!=6)return; struct in6_addr src,dst;net_memcpy(src.s6_addr,quote+8,16);net_memcpy(dst.s6_addr,quote+24,16); - struct ipv6_address_slot *a=ipv6_find_address(&src,0);if(!a || ipv6_multicast(&dst))return; + struct ipv6_address_slot *a=ipv6_find_address(&src,ifindex);if(!a || ipv6_multicast(&dst))return; if(mtuIPV6_LINK_MTU)return; struct ipv6_pmtu *slot=0; @@ -722,7 +775,7 @@ static bool ipv6_error_suppressed(const uint8_t *ip,uint32_t length) /** @brief Generate a rate-limited ICMPv6 error for a locally received packet. * @param ip Original IPv6 packet. @param length Available bytes. @param type Error type. * @param code Error code. @param word Type-specific field. @param allow_multicast Permit RFC option exception. */ -static void ipv6_error(const uint8_t *ip,uint32_t length,uint8_t type,uint8_t code,uint32_t word,bool allow_multicast) +static void ipv6_error(const uint8_t *ip,uint32_t length,uint8_t type,uint8_t code,uint32_t word,bool allow_multicast,uint32_t ifindex) { if(length<40)return; struct in6_addr src,dst;net_memcpy(src.s6_addr,ip+8,16);net_memcpy(dst.s6_addr,ip+24,16); @@ -730,7 +783,7 @@ static void ipv6_error(const uint8_t *ip,uint32_t length,uint8_t type,uint8_t co if(ipv6_error_suppressed(ip,length))return; uint64_t now=time_uptime_ms();if(now-ipv6_error_at>=1000){ipv6_error_at=now;ipv6_error_count=0;} if(ipv6_error_count>=10)return; - struct net_ipv6_path path;if(net_ipv6_route(&src,ipv6_linklocal(&src)?2:0, + struct net_ipv6_path path;if(net_ipv6_route(&src,ipv6_linklocal(&src)?ifindex:0, ipv6_multicast(&dst)?0:&dst,&path)<0)return; uint8_t error[1240]={0};uint32_t n=length;if(n>1232)n=1232; error[0]=type;error[1]=code;net_put_u32(error+4,word);net_memcpy(error+8,ip,n); @@ -757,29 +810,29 @@ static void ipv6_icmp(const uint8_t *ip,uint32_t ip_length,const uint8_t *icmp,u int error=icmp[0]==1?(icmp[1]==4?LINUX_ECONNREFUSED:LINUX_EHOSTUNREACH): (icmp[0]==2?LINUX_EMSGSIZE:(icmp[0]==3?LINUX_ETIMEDOUT:LINUX_EPROTO)); uint32_t mtu=icmp[0]==2?net_get_u32(icmp+4):0; - if(icmp[0]==2)ipv6_update_pmtu(icmp+8,length-8,mtu); - net_ipv6_socket_error(icmp+8,length-8,error,mtu);return; + if(icmp[0]==2)ipv6_update_pmtu(ifindex,icmp+8,length-8,mtu); + net_ipv6_socket_error_on(ifindex,icmp+8,length-8,error,mtu);return; } - if(ifindex!=2 || fragmented || ip[7]!=255 || icmp[1]!=0 || !ethernet_mac)return; - if(icmp[0]==134){ipv6_router_advertisement(&src,icmp,length);return;} + if(ifindex==1 || fragmented || ip[7]!=255 || icmp[1]!=0 || !ethernet_mac)return; + if(icmp[0]==134){ipv6_router_advertisement(ifindex,&src,icmp,length);return;} if((icmp[0]==135 || icmp[0]==136) && length>=24){ struct in6_addr target;net_memcpy(target.s6_addr,icmp+8,16);if(ipv6_multicast(&target))return; const uint8_t *mac=0;if(!ipv6_nd_options(icmp+24,length-24,icmp[0]==135?1:2,&mac))return; if(icmp[0]==135){ if(ipv6_unspecified(&src)){struct in6_addr group;ipv6_solicited(&group,&target); if(mac || !ipv6_equal(&dst,&group))return;} - else if(mac)ipv6_learn_neighbor(&src,mac,false,true); + else if(mac)ipv6_learn_neighbor(ifindex,&src,mac,false,true); struct ipv6_address_slot *slot=ipv6_find_address(&target,ifindex);if(!slot)return; - if(slot->value.flags&NET_IPV6_ADDR_TENTATIVE){if(!net_memeq(ethernet_mac,e1000_mac(),6))ipv6_dad_failed(slot);return;} + if(slot->value.flags&NET_IPV6_ADDR_TENTATIVE){if(!net_memeq(ethernet_mac,netdev_mac(ifindex),6))ipv6_dad_failed(slot);return;} if(slot->value.flags&NET_IPV6_ADDR_DADFAILED)return; if(ipv6_unspecified(&src)){uint8_t allmac[6];ipv6_multicast_mac(allmac,&ipv6_all_nodes); - ipv6_send_na(&target,&ipv6_all_nodes,allmac,false);} - else ipv6_send_na(&target,&src,ethernet_mac,true); + ipv6_send_na(ifindex,&target,&ipv6_all_nodes,allmac,false);} + else ipv6_send_na(ifindex,&target,&src,ethernet_mac,true); }else{ if(ipv6_unspecified(&src) || (ipv6_multicast(&dst) && (icmp[4]&0x40)))return; struct ipv6_address_slot *slot=ipv6_find_address(&target,ifindex); - if(slot && slot->value.flags&NET_IPV6_ADDR_TENTATIVE){if(!net_memeq(ethernet_mac,e1000_mac(),6))ipv6_dad_failed(slot);return;} - if(mac && ipv6_find_neighbor(&target))ipv6_learn_neighbor(&target,mac,(icmp[4]&0x40)!=0,(icmp[4]&0x20)!=0); + if(slot && slot->value.flags&NET_IPV6_ADDR_TENTATIVE){if(!net_memeq(ethernet_mac,netdev_mac(ifindex),6))ipv6_dad_failed(slot);return;} + if(mac && ipv6_find_neighbor(ifindex,&target))ipv6_learn_neighbor(ifindex,&target,mac,(icmp[4]&0x40)!=0,(icmp[4]&0x20)!=0); } } (void)ip_length; @@ -821,7 +874,7 @@ static void ipv6_reassembly_expire(struct ipv6_reassembly *r) quote[n++]=r->data[i]; } } - ipv6_error(quote,n,3,1,0,false); + ipv6_error(quote,n,3,1,0,false,r->ifindex); } ipv6_reassembly_clear(r); } @@ -844,19 +897,19 @@ static void ipv6_fragment(const uint8_t *ip,uint32_t ip_length,uint32_t off,uint uint32_t offset=field&0xfff8u,n=ip_length-off-8u,ident=net_get_u32(fragment+4); bool more=(field&1)!=0;if((more && (!n || (n&7u))) || offset+n>65535u || off-40u+offset+n>65535u)return; if(offset==0 && !ipv6_first_complete(fragment[0],fragment+8,n)){ - ipv6_error(ip,ip_length,4,3,0,false);return;} + ipv6_error(ip,ip_length,4,3,0,false,ifindex);return;} struct in6_addr src,dst;net_memcpy(src.s6_addr,ip+8,16);net_memcpy(dst.s6_addr,ip+24,16); struct ipv6_reassembly *r=0,*free_slot=0; for(uint32_t i=0;iused && ipv6_expired(e->deadline,time_uptime_ms()))ipv6_reassembly_expire(e); - if(e->used && e->identification==ident && ipv6_equal(&e->source,&src) && ipv6_equal(&e->destination,&dst)){ + if(e->used && e->ifindex==ifindex && e->identification==ident && ipv6_equal(&e->source,&src) && ipv6_equal(&e->destination,&dst)){ r=e;break;}if(!e->used && !free_slot)free_slot=e; } if(!r){if(!free_slot)return;r=free_slot;r->data=kernel_malloc(65535);r->bitmap=kernel_malloc(8192); if(!r->data || !r->bitmap){ipv6_reassembly_clear(r);return;} net_memzero(r->bitmap,8192);r->used=true;r->source=src;r->destination=dst; - r->identification=ident;r->next=fragment[0];r->deadline=time_uptime_ms()+IPV6_REASM_TIMEOUT; + r->ifindex=ifindex;r->identification=ident;r->next=fragment[0];r->deadline=time_uptime_ms()+IPV6_REASM_TIMEOUT; } if(r->poisoned)return; if(r->next!=fragment[0] || (r->have_last && offset+n>r->total) || @@ -881,15 +934,15 @@ static void ipv6_fragment(const uint8_t *ip,uint32_t ip_length,uint32_t off,uint * @param ip Packet. @param total Packet bytes. @param off Extension offset. * @param length Extension length. @param router_alert Set when the MLD router alert is present. * @return True when processing may continue. */ -static bool ipv6_options(const uint8_t *ip,uint32_t total,uint32_t off,uint32_t length,bool *router_alert) +static bool ipv6_options(const uint8_t *ip,uint32_t total,uint32_t off,uint32_t length,bool *router_alert,uint32_t ifindex) { uint32_t pos=off+2,end=off+length; while(posend-pos){ipv6_error(ip,total,4,0,pos,false);return false;} + if(end-pos<2 || (uint32_t)ip[pos+1]+2u>end-pos){ipv6_error(ip,total,4,0,pos,false,ifindex);return false;} uint32_t n=(uint32_t)ip[pos+1]+2u; if(type==5 && n==4 && ip[pos+2]==0 && ip[pos+3]==0)*router_alert=true; else if(type!=1){uint8_t action=type>>6;if(action){ - if(action==2 || action==3){ipv6_error(ip,total,4,2,pos,action==2);} + if(action==2 || action==3){ipv6_error(ip,total,4,2,pos,action==2,ifindex);} return false;}} pos+=n; } @@ -904,19 +957,20 @@ static uint32_t ipv6_mld_response_ms(uint16_t code,bool v2) } /** @brief Schedule listener reports in response to a valid MLD query. @param ip IPv6 packet. * @param icmp Query. @param length Bytes. @param router_alert Hop-by-hop router alert was present. */ -static void ipv6_mld_query(const uint8_t *ip,const uint8_t *icmp,uint32_t length,bool router_alert) +static void ipv6_mld_query(uint32_t ifindex,const uint8_t *ip,const uint8_t *icmp,uint32_t length,bool router_alert) { if(ip[7]!=1 || !router_alert || length<24 || icmp[1])return; struct in6_addr source,group;net_memcpy(source.s6_addr,ip+8,16);net_memcpy(group.s6_addr,icmp+8,16); if(!ipv6_unspecified(&source) && !ipv6_linklocal(&source))return; if(!ipv6_unspecified(&group) && !ipv6_multicast(&group))return; bool v2=length!=24; - if(!v2)ipv6_mld_v1_until=time_uptime_ms()+260000; + struct ipv6_link *link=ipv6_link(ifindex,false);if(!link)return; + if(!v2)link->mld_v1_until=time_uptime_ms()+260000; else if(length<28 || (uint32_t)net_get_u16(icmp+26)*16u>length-28)return; uint32_t maximum=ipv6_mld_response_ms(net_get_u16(icmp+4),v2); for(uint32_t i=0;i>4!=6)return; uint32_t total=40u+net_get_u16(ip+4);if(total>length)return; struct in6_addr src,dst;net_memcpy(src.s6_addr,ip+8,16);net_memcpy(dst.s6_addr,ip+24,16); - if(ipv6_multicast(&src) || (ifindex==2 && (ipv6_equal(&src,&ipv6_loop) || ipv6_equal(&dst,&ipv6_loop))) || - (ifindex==2 && ipv6_multicast(&dst) && (dst.s6_addr[1]&15)<2) || !ipv6_destination_local(&dst,ifindex))return; + if(ipv6_multicast(&src) || (ifindex!=1 && (ipv6_equal(&src,&ipv6_loop) || ipv6_equal(&dst,&ipv6_loop))) || + (ifindex!=1 && mac && ipv6_multicast(&dst) && (dst.s6_addr[1]&15)<2) || + !ipv6_destination_local(&dst,ifindex))return; uint32_t off=40,previous=6,count=0;uint8_t next=ip[6];bool fragmented=reassembled,router_alert=false; while(next==0 || next==43 || next==44 || next==60){ if(++count>IPV6_HEADER_COUNT || off>IPV6_HEADER_LIMIT || total-off<8)return; @@ -939,10 +994,10 @@ static void ipv6_receive_ip(const uint8_t *ip,uint32_t length,uint32_t ifindex,c if((field&0xfff9u)!=0){ipv6_fragment(ip,total,off,previous,ifindex);return;} next=ip[off];previous=off;off+=8;continue; } - if(next==0 && off!=40){ipv6_error(ip,total,4,1,previous,false);return;} + if(next==0 && off!=40){ipv6_error(ip,total,4,1,previous,false,ifindex);return;} uint32_t n=((uint32_t)ip[off+1]+1u)*8u;if(n>total-off || off+n>IPV6_HEADER_LIMIT)return; - if(next==43 && ip[off+3]){ipv6_error(ip,total,4,0,off+2,false);return;} - if((next==0 || next==60) && !ipv6_options(ip,total,off,n,&router_alert))return; + if(next==43 && ip[off+3]){ipv6_error(ip,total,4,0,off+2,false,ifindex);return;} + if((next==0 || next==60) && !ipv6_options(ip,total,off,n,&router_alert,ifindex))return; next=ip[off];previous=off;off+=n; } uint32_t payload=total-off;const uint8_t *data=ip+off; @@ -953,7 +1008,7 @@ static void ipv6_receive_ip(const uint8_t *ip,uint32_t length,uint32_t ifindex,c net_ipv6_checksum(&src,&dst,17,data,payload))return; net_raw6_input(&src,&dst,next,data,payload,ifindex,ip[7]); if(!net_udp6_input(&src,&dst,net_get_u16(data),net_get_u16(data+2),data+8,payload-8,ifindex,ip[7])) - ipv6_error(ip,total,1,4,0,false); + ipv6_error(ip,total,1,4,0,false,ifindex); }else if(next==6){ if(payload<20 || (data[12]>>4)<5 || (uint32_t)(data[12]>>4)*4u>payload || net_ipv6_checksum(&src,&dst,6,data,payload))return; @@ -963,104 +1018,124 @@ static void ipv6_receive_ip(const uint8_t *ip,uint32_t length,uint32_t ifindex,c /* RFC 6980: fragmented Neighbor Discovery must not reach raw consumers either. */ if(fragmented && data[0]>=133 && data[0]<=137)return; net_raw6_input(&src,&dst,next,data,payload,ifindex,ip[7]); - if(data[0]==130 && ifindex==2 && !fragmented)ipv6_mld_query(ip,data,payload,router_alert); + if(data[0]==130 && ifindex!=1 && !fragmented)ipv6_mld_query(ifindex,ip,data,payload,router_alert); ipv6_icmp(ip,total,data,payload,ifindex,mac,fragmented); }else{ - net_raw6_input(&src,&dst,next,data,payload,ifindex,ip[7]);ipv6_error(ip,total,4,1,previous,false); + net_raw6_input(&src,&dst,next,data,payload,ifindex,ip[7]);ipv6_error(ip,total,4,1,previous,false,ifindex); } } -/** @brief Process one complete IPv6 Ethernet frame. @param frame Borrowed frame. - * @param length Ethernet bytes including header. */ -void net_ipv6_input(const uint8_t *frame,uint32_t length) +/** @brief Process one complete IPv6 Ethernet frame on its receiving interface. */ +void net_ipv6_input_on(uint32_t ifindex,const uint8_t *frame,uint32_t length) { - if(!frame || length<54 || net_get_u16(frame+12)!=IPV6_ETHERTYPE || !ipv6_link_up)return; - ipv6_receive_ip(frame+14,length-14,2,frame+6,false); + struct ipv6_link *link=ipv6_link(ifindex,false); + if(!frame || length<54 || net_get_u16(frame+12)!=IPV6_ETHERTYPE || + !link || !link->up || !net_interface_is_ready(ifindex))return; + ipv6_receive_ip(frame+14,length-14,ifindex,frame+6,false); } +void net_ipv6_input(const uint8_t *frame,uint32_t length) +{ net_ipv6_input_on(ipv6_default_interface(),frame,length); } -/** @brief Return a usable Ethernet link-local source. @return Borrowed address or NULL. */ -static const struct in6_addr *ipv6_link_source(void) +/** @brief Return a usable link-local source on one Ethernet interface. */ +static const struct in6_addr *ipv6_link_source(uint32_t ifindex) { - for(uint32_t i=0;iifindex!=2 || ipv6_equal(&group->address,&ipv6_all_nodes))return true; - const struct in6_addr *source=ipv6_link_source();if(!source)source=&ipv6_zero; - bool v1=time_uptime_ms()ifindex==1 || (group->address.s6_addr[1]&15)<=1 || + ipv6_equal(&group->address,&ipv6_all_nodes))return true; + struct ipv6_link *link=ipv6_link(group->ifindex,false); + if(!link || !link->up)return false; + const struct in6_addr *source=ipv6_link_source(group->ifindex);if(!source)source=&ipv6_zero; + bool v1=time_uptime_ms()mld_v1_until; struct in6_addr dest=v1?(group->refs?group->address:ipv6_all_routers):ipv6_mld_routers; uint8_t payload[36]={58,0,5,2,0,0,1,0};uint8_t *icmp=payload+8;uint32_t n; if(v1){n=24;icmp[0]=(uint8_t)(group->refs?131:132);net_memcpy(icmp+8,group->address.s6_addr,16);} else{n=28;icmp[0]=143;net_put_u16(icmp+6,1);icmp[8]=(uint8_t)(group->refs?(query?2:4):3); net_memcpy(icmp+12,group->address.s6_addr,16);} net_put_u16(icmp+2,net_ipv6_checksum(source,&dest,58,icmp,n));uint8_t mac[6];ipv6_multicast_mac(mac,&dest); - return ipv6_send_frame(mac,source,&dest,0,1,0,payload,n+8)>=0; + return ipv6_send_frame(group->ifindex,mac,source,&dest,0,1,0,payload,n+8)>=0; } -/** @brief Tear down owned reassembly and queued payload state for one link. */ -static void ipv6_clear_link_state(void) +/** @brief Tear down only the state owned by one link. */ +static void ipv6_clear_link_state(uint32_t ifindex) { - for(uint32_t i=0;iup)return 0; + link->up=enabled; + if(!enabled){ipv6_clear_link_state(ifindex);return 0;} + link->mtu=IPV6_LINK_MTU;link->rng_state=0;link->mld_v1_until=0; + ipv6_reset_timing(link); + const uint8_t *mac=netdev_mac(ifindex);struct net_ipv6_address a={0};a.address.s6_addr[0]=0xfe;a.address.s6_addr[1]=0x80; a.address.s6_addr[8]=mac[0]^2;a.address.s6_addr[9]=mac[1];a.address.s6_addr[10]=mac[2]; a.address.s6_addr[11]=0xff;a.address.s6_addr[12]=0xfe;a.address.s6_addr[13]=mac[3]; - a.address.s6_addr[14]=mac[4];a.address.s6_addr[15]=mac[5];a.ifindex=2;a.prefix_length=64; + a.address.s6_addr[14]=mac[4];a.address.s6_addr[15]=mac[5];a.ifindex=ifindex;a.prefix_length=64; a.flags=NET_IPV6_ADDR_PERMANENT;a.preferred_until=a.valid_until=IPV6_FOREVER; (void)net_ipv6_address_update(&a,false); - struct in6_addr prefix={.s6_addr={0xfe,0x80}};ipv6_auto_route(&prefix,64,&ipv6_zero,IPV6_FOREVER,256,false); - ipv6_rs_at=IPV6_FOREVER;ipv6_rs_sent=0; + struct in6_addr prefix={.s6_addr={0xfe,0x80}}; + ipv6_auto_route(ifindex,&prefix,64,&ipv6_zero,IPV6_FOREVER,256,false); + link->rs_at=IPV6_FOREVER;link->rs_sent=0; + return 0; } -/** @brief Retire NIC-specific addresses, routes, neighbors, fragments and queued packets. */ -void net_ipv6_detached(void) +void net_ipv6_link_changed(bool up) +{ (void)net_ipv6_link_changed_on(ipv6_default_interface(),up); } +/** @brief Retire all state for the detached interface, even after registry removal. */ +void net_ipv6_detached_on(uint32_t ifindex) { - if(ipv6_link_up){ipv6_link_up=false;ipv6_clear_link_state();} + if(!ipv6_initialized || ifindex==1)return; + ipv6_clear_link_state(ifindex); + struct ipv6_link **link=&ipv6_links; + while(*link && (*link)->ifindex!=ifindex)link=&(*link)->next; + if(*link){struct ipv6_link *removed=*link;*link=removed->next;kernel_free(removed);} } -/** @brief Initialize loopback and Ethernet host state without waiting for discovery. */ +void net_ipv6_detached(void) +{ net_ipv6_detached_on(ipv6_default_interface()); } +/** @brief Initialize loopback and every currently published Ethernet interface. */ void net_ipv6_init(void) { if(ipv6_initialized){ for(uint32_t i=0;inext;kernel_free(old);} net_memzero(ipv6_addrs,sizeof(ipv6_addrs));net_memzero(ipv6_routes,sizeof(ipv6_routes)); net_memzero(ipv6_neighbors,sizeof(ipv6_neighbors));net_memzero(ipv6_groups,sizeof(ipv6_groups)); net_memzero(ipv6_pending,sizeof(ipv6_pending));net_memzero(ipv6_reassembly,sizeof(ipv6_reassembly)); - net_memzero(ipv6_pmtu,sizeof(ipv6_pmtu));ipv6_link_up=false;ipv6_initialized=true; - ipv6_link_mtu=IPV6_LINK_MTU;ipv6_frag_id=0;ipv6_rng_state=0;ipv6_error_at=0;ipv6_error_count=0; - ipv6_mld_v1_until=0;ipv6_base_reachable_ms=30000;ipv6_retrans_ms=1000; + net_memzero(ipv6_pmtu,sizeof(ipv6_pmtu)); + ipv6_initialized=true;ipv6_frag_id=0;ipv6_error_at=0;ipv6_error_count=0; struct net_ipv6_address loop={0};loop.address=ipv6_loop;loop.ifindex=1;loop.prefix_length=128; loop.flags=NET_IPV6_ADDR_PERMANENT|NET_IPV6_ADDR_NODAD;loop.preferred_until=loop.valid_until=IPV6_FOREVER; (void)net_ipv6_address_update(&loop,false); struct net_ipv6_route route={0};route.destination=ipv6_loop;route.ifindex=1;route.prefix_length=128; route.valid_until=IPV6_FOREVER;route.flags=RTF_UP|RTF_HOST;(void)net_ipv6_route_update(&route,false); - net_ipv6_link_changed(net_interface_up); + for(uint32_t i=netdev_next(0);i;i=netdev_next(i))(void)net_ipv6_link_changed_on(i,net_interface_is_up(i)); } -/** @brief Check if task-context receive or timer service should continue. - * @return True while an Ethernet interface or deferred loopback packet is active. */ +/** @brief Check if task-context receive or timer service should continue. */ bool net_ipv6_maintenance_pending(void) { - if(ipv6_initialized && net_interface_up && e1000_is_ready())return true; + if(ipv6_initialized)for(uint32_t i=netdev_next(0);i;i=netdev_next(i)) + if(net_interface_is_ready(i))return true; for(uint32_t i=0;iup)!=up)(void)net_ipv6_link_changed_on(i,up); + } uint64_t now=time_uptime_ms(); for(uint32_t i=0;i=ipv6_reachable_refresh)ipv6_refresh_reachable(); - /* Report a new solicited-node membership before sending its DAD probe. */ - for(uint32_t i=0;iused)continue; - if(g->query_pending && now>=g->query_at && ipv6_send_mld(g,true))g->query_pending=false; - if(g->reports && now>=g->report_at && ipv6_send_mld(g,false)){ - --g->reports;g->report_at=now+ipv6_jitter(1000); - if(!g->reports && !g->refs)g->used=false; - } + for(struct ipv6_link *link=ipv6_links;link;link=link->next) + if(link->up && now>=link->reachable_refresh)ipv6_refresh_reachable(link); + /* Report a new solicited-node membership before sending its DAD probe. */ + for(uint32_t i=0;iused)continue; + struct ipv6_link *link=ipv6_link(g->ifindex,false); + if(g->ifindex!=1 && (!link || !link->up))continue; + if(g->query_pending && now>=g->query_at && ipv6_send_mld(g,true))g->query_pending=false; + if(g->reports && now>=g->report_at && ipv6_send_mld(g,false)){ + --g->reports;g->report_at=now+(link?ipv6_jitter(link,1000):0); + if(!g->reports && !g->refs)g->used=false; } } for(uint32_t i=0;ivalue.flags&NET_IPV6_ADDR_DEPRECATED) && ipv6_expired(a->value.preferred_until,now)){ a->value.flags|=NET_IPV6_ADDR_DEPRECATED;netlink_ipv6_changed();} - if(ipv6_link_up && (a->value.flags&NET_IPV6_ADDR_TENTATIVE) && now>=a->dad_at){ + struct ipv6_link *link=ipv6_link(a->value.ifindex,false); + if(link && link->up && (a->value.flags&NET_IPV6_ADDR_TENTATIVE) && now>=a->dad_at){ if(!a->dad_sent && a->dad_wait_mld){struct in6_addr group;ipv6_solicited(&group,&a->value.address); struct ipv6_group_slot *g=ipv6_find_group(&group,a->value.ifindex); if(g && g->reports==2)continue; a->dad_wait_mld=false;} - if(!a->dad_sent){if(ipv6_send_ns(&a->value.address,&ipv6_zero,0)>=0){a->dad_sent=true;a->dad_at=now+ipv6_retrans_ms;}} + if(!a->dad_sent){if(ipv6_send_ns(a->value.ifindex,&a->value.address,&ipv6_zero,0)>=0){ + a->dad_sent=true;a->dad_at=now+link->retrans_ms;}} else{a->value.flags&=~NET_IPV6_ADDR_TENTATIVE;netlink_ipv6_changed();} } } @@ -1113,22 +1195,27 @@ void net_ipv6_poll(void) ipv6_routes[i].used=false;netlink_ipv6_changed();} for(uint32_t i=0;i=ipv6_rs_at){ - if(ipv6_send_rs(link_source)>=0){++ipv6_rs_sent;ipv6_rs_at=now+4000;}} + for(struct ipv6_link *link=ipv6_links;link;link=link->next){ + if(!link->up)continue; + const struct in6_addr *source=ipv6_link_source(link->ifindex); + if(source && !link->rs_sent && link->rs_at==IPV6_FOREVER)link->rs_at=now+ipv6_jitter(link,1000); + if(source && link->rs_sent<3 && now>=link->rs_at && ipv6_send_rs(link->ifindex,source)>=0){ + ++link->rs_sent;link->rs_at=now+4000;} + } for(uint32_t i=0;iused || nowdeadline)continue; + struct ipv6_link *link=ipv6_link(n->value.ifindex,false);if(!link || !link->up)continue; + const struct in6_addr *source=ipv6_link_source(n->value.ifindex); if(n->value.state==IPV6_NUD_REACHABLE){n->value.state=IPV6_NUD_STALE;netlink_ipv6_changed();} else if(n->value.state==IPV6_NUD_DELAY){n->value.state=IPV6_NUD_PROBE;n->probes=0;n->deadline=now;} if(n->value.state==IPV6_NUD_INCOMPLETE || n->value.state==IPV6_NUD_PROBE){ if(n->probes>=3){n->value.state=IPV6_NUD_FAILED;netlink_ipv6_changed(); for(uint32_t j=0;jvalue.ifindex && ipv6_equal(&ipv6_pending[j].path.next_hop,&n->value.address)){ ipv6_pending_error(&ipv6_pending[j],LINUX_EHOSTUNREACH);kernel_free(ipv6_pending[j].data);ipv6_pending[j].used=false;} - }else if(link_source){(void)ipv6_send_ns(&n->value.address,link_source, - n->value.state==IPV6_NUD_PROBE?n->value.mac:0);++n->probes;n->deadline=now+ipv6_retrans_ms;} + }else if(source){(void)ipv6_send_ns(n->value.ifindex,&n->value.address,source, + n->value.state==IPV6_NUD_PROBE?n->value.mac:0);++n->probes;n->deadline=now+link->retrans_ms;} } } } diff --git a/net/link/arp.c b/net/link/arp.c index 397bd0f6..630d4561 100644 --- a/net/link/arp.c +++ b/net/link/arp.c @@ -1,224 +1,137 @@ #include "../internal.h" #include -/** - * Net arp cache lookup. - * @param ip Value supplied by the caller. - * @param mac Value supplied by the caller. - * @return The value or status produced by the operation. - */ -static int net_arp_cache_lookup(uint32_t ip, uint8_t mac[6]); - -/** - * Net arp cache store. - * @param ip Value supplied by the caller. - * @param mac Value supplied by the caller. - */ -static void net_arp_cache_store(uint32_t ip, const uint8_t mac[6]); - -/** - * Net write arp ipv4. - * @param frame Value supplied by the caller. - * @param op Identifier or flags controlling the operation. - * @param dst_eth_mac Value supplied by the caller. - * @param target_mac Value supplied by the caller. - * @param target_ip Value supplied by the caller. - */ -static void net_write_arp_ipv4(uint8_t *frame, uint16_t op, - const uint8_t *dst_eth_mac, - const uint8_t *target_mac, - uint32_t target_ip); - -/** - * Net send arp request. - * @param target_ip Value supplied by the caller. - * @return The value or status produced by the operation. - */ -static int net_send_arp_request(uint32_t target_ip); - -/** - * Net send arp reply. - * @param target_mac Value supplied by the caller. - * @param target_ip Value supplied by the caller. - * @return The value or status produced by the operation. - */ -static int net_send_arp_reply(const uint8_t *target_mac, uint32_t target_ip); - static struct net_arp_cache_entry net_arp_cache[NET_ARP_CACHE_SIZE]; static uint32_t net_arp_cache_next; -/** - * Net arp cache clear. - */ + +/** @brief Invalidate neighbors for one link without disturbing other links. + * @param ifindex Interface, or zero for a complete reset. */ +void net_arp_cache_clear_on(uint32_t ifindex) +{ + for (uint32_t i = 0; i < NET_ARP_CACHE_SIZE; ++i) + if (!ifindex || net_arp_cache[i].ifindex == ifindex) + net_arp_cache[i] = (struct net_arp_cache_entry){0}; +} + +/** @brief Reset the primary interface's compatibility ARP cache. */ void net_arp_cache_clear(void) { - net_memzero(net_arp_cache, sizeof(net_arp_cache)); - net_arp_cache_next = 0; + net_arp_cache_clear_on(netdev_next(0)); } -/** - * Net arp cache lookup. - * @param ip Value supplied by the caller. - * @param mac Value supplied by the caller. - * @return The value or status produced by the operation. - */ -static int net_arp_cache_lookup(uint32_t ip, uint8_t mac[6]) +/** @brief Copy a resolved neighbor on the specified Ethernet link. + * @param ifindex Link. @param ip Host-order neighbor. @param mac Six output bytes. + * @return One for a resolved entry, zero for a miss. */ +static int net_arp_lookup_on(uint32_t ifindex, uint32_t ip, uint8_t mac[6]) { - if (!ip || !mac) { - return 0; - } + if (!ip || !mac) return 0; for (uint32_t i = 0; i < NET_ARP_CACHE_SIZE; ++i) { - if (net_arp_cache[i].ip == ip && - !net_mac_is_zero(net_arp_cache[i].mac)) { - net_memcpy(mac, net_arp_cache[i].mac, 6); + const struct net_arp_cache_entry *entry = &net_arp_cache[i]; + if (entry->ifindex == ifindex && entry->ip == ip && !net_mac_is_zero(entry->mac)) { + net_memcpy(mac, entry->mac, 6); return 1; } } return 0; } -/** - * Net arp cache store. - * @param ip Value supplied by the caller. - * @param mac Value supplied by the caller. - */ -static void net_arp_cache_store(uint32_t ip, const uint8_t mac[6]) +/** @brief Cache a unicast mapping with its ingress link identity. + * @param ifindex Link. @param ip Host-order neighbor. @param mac Six input bytes. */ +static void net_arp_store_on(uint32_t ifindex, uint32_t ip, const uint8_t mac[6]) { - uint32_t slot; - if (!ip || !mac || net_mac_is_zero(mac) || - ip == 0xffffffffu || net_memeq(mac, net_broadcast_mac, 6)) { - return; - } + if (!ip || !mac || net_mac_is_zero(mac) || ip == UINT32_MAX || (mac[0] & 1)) return; + uint32_t slot = NET_ARP_CACHE_SIZE; for (uint32_t i = 0; i < NET_ARP_CACHE_SIZE; ++i) { - if (net_arp_cache[i].ip == ip || net_arp_cache[i].ip == 0) { - net_arp_cache[i].ip = ip; - net_memcpy(net_arp_cache[i].mac, mac, 6); - return; + if ((net_arp_cache[i].ifindex == ifindex && net_arp_cache[i].ip == ip) || !net_arp_cache[i].ip) { + slot = i; + break; } } - slot = net_arp_cache_next++ % NET_ARP_CACHE_SIZE; + if (slot == NET_ARP_CACHE_SIZE) slot = net_arp_cache_next++ % NET_ARP_CACHE_SIZE; + net_arp_cache[slot].ifindex = ifindex; net_arp_cache[slot].ip = ip; net_memcpy(net_arp_cache[slot].mac, mac, 6); } -/** - * Net write arp ipv4. - * @param frame Value supplied by the caller. - * @param op Identifier or flags controlling the operation. - * @param dst_eth_mac Value supplied by the caller. - * @param target_mac Value supplied by the caller. - * @param target_ip Value supplied by the caller. - */ -static void net_write_arp_ipv4(uint8_t *frame, uint16_t op, - const uint8_t *dst_eth_mac, - const uint8_t *target_mac, - uint32_t target_ip) +/** @brief Construct one ARP request/reply from the selected link's configuration. + * @param ifindex Link. @param frame Forty-two output bytes. @param op ARP opcode. + * @param dst_mac Ethernet destination. @param target_mac ARP destination. + * @param target_ip Host-order target IPv4 address. */ +static void net_write_arp_on(uint32_t ifindex, uint8_t frame[42], uint16_t op, + const uint8_t dst_mac[6], const uint8_t target_mac[6], uint32_t target_ip) { - const uint8_t *src_mac = e1000_mac(); - net_write_eth(frame, dst_eth_mac, ETH_TYPE_ARP); + struct reliefos_net_config config = {0}; + net_interface_get_config(ifindex, &config); + net_write_eth_on(ifindex, frame, dst_mac, ETH_TYPE_ARP); net_put_u16(frame + 14, ARP_HTYPE_ETHERNET); net_put_u16(frame + 16, ETH_TYPE_IPV4); frame[18] = 6; frame[19] = 4; net_put_u16(frame + 20, op); - net_memcpy(frame + 22, src_mac, 6); - net_put_u32(frame + 28, net_config.local_ip); + net_memcpy(frame + 22, netdev_mac(ifindex), 6); + net_put_u32(frame + 28, config.local_ip); net_memcpy(frame + 32, target_mac, 6); net_put_u32(frame + 38, target_ip); } -/** - * Net send arp request. - * @param target_ip Value supplied by the caller. - * @return The value or status produced by the operation. - */ -static int net_send_arp_request(uint32_t target_ip) -{ - uint8_t frame[64]; - uint8_t zero_mac[6] = {0, 0, 0, 0, 0, 0}; - net_memzero(frame, sizeof(frame)); - net_write_arp_ipv4(frame, ARP_OPER_REQUEST, net_broadcast_mac, - zero_mac, target_ip); - return net_interface_up ? e1000_send(frame, 42) : -LINUX_ENETDOWN; -} - -/** - * Net send arp reply. - * @param target_mac Value supplied by the caller. - * @param target_ip Value supplied by the caller. - * @return The value or status produced by the operation. - */ -static int net_send_arp_reply(const uint8_t *target_mac, uint32_t target_ip) +/** @brief Process ARP learning and replies on exactly the receiving link. + * @param ifindex Ingress link. @param frame Borrowed Ethernet/ARP packet. + * @param len Bytes available. @param arp_wait Optional link-scoped waiter. */ +void net_handle_arp_on(uint32_t ifindex, const uint8_t *frame, uint32_t len, + struct net_arp_wait *arp_wait) { - uint8_t frame[64]; - net_memzero(frame, sizeof(frame)); - net_write_arp_ipv4(frame, ARP_OPER_REPLY, target_mac, target_mac, target_ip); - return net_interface_up ? e1000_send(frame, 42) : -LINUX_ENETDOWN; -} - -/** - * Net handle arp. - * @param frame Value supplied by the caller. - * @param len Maximum number of elements available in the related buffer. - * @param arp_wait Value supplied by the caller. - */ -void net_handle_arp(const uint8_t *frame, uint32_t len, - struct net_arp_wait *arp_wait) -{ - uint16_t op; - uint32_t sender_ip; - uint32_t target_ip; - const uint8_t *sender_mac; - if (len < 42 || - net_get_u16(frame + 14) != ARP_HTYPE_ETHERNET || - net_get_u16(frame + 16) != ETH_TYPE_IPV4 || - frame[18] != 6 || frame[19] != 4) { - return; - } - op = net_get_u16(frame + 20); - sender_mac = frame + 22; - sender_ip = net_get_u32(frame + 28); - target_ip = net_get_u32(frame + 38); - net_arp_cache_store(sender_ip, sender_mac); - if (op == ARP_OPER_REQUEST && target_ip == net_config.local_ip) { - (void)net_send_arp_reply(sender_mac, sender_ip); - return; - } - if (op == ARP_OPER_REPLY && arp_wait && sender_ip == arp_wait->ip) { + if (len < 42 || net_get_u16(frame + 14) != ARP_HTYPE_ETHERNET || + net_get_u16(frame + 16) != ETH_TYPE_IPV4 || frame[18] != 6 || frame[19] != 4) return; + uint16_t op = net_get_u16(frame + 20); + if (op != ARP_OPER_REQUEST && op != ARP_OPER_REPLY) return; + uint32_t sender_ip = net_get_u32(frame + 28), target_ip = net_get_u32(frame + 38); + const uint8_t *sender_mac = frame + 22; + net_arp_store_on(ifindex, sender_ip, sender_mac); + if (arp_wait && (!arp_wait->ifindex || arp_wait->ifindex == ifindex) && arp_wait->ip == sender_ip) { net_memcpy(arp_wait->mac, sender_mac, 6); arp_wait->done = 1; } + struct reliefos_net_config config = {0}; + if (op == ARP_OPER_REQUEST && !net_interface_get_config(ifindex, &config) && + config.local_ip && target_ip == config.local_ip && net_interface_is_ready(ifindex)) { + uint8_t reply[42]; + net_write_arp_on(ifindex, reply, ARP_OPER_REPLY, sender_mac, sender_mac, sender_ip); + (void)netdev_send(ifindex, reply, sizeof(reply)); + } } -/** - * Net resolve mac. - * @param ip Value supplied by the caller. - * @param timeout_ms Value supplied by the caller. - * @param out_mac Value supplied by the caller. - * @return The value or status produced by the operation. - */ -int net_resolve_mac(uint32_t ip, uint32_t timeout_ms, uint8_t *out_mac) +/** @brief Process ARP on the primary compatibility link. + * @param frame Borrowed packet. @param len Byte count. @param wait Optional waiter. */ +void net_handle_arp(const uint8_t *frame, uint32_t len, struct net_arp_wait *wait) { - struct net_arp_wait wait; - uint64_t start = time_uptime_ms(); - uint64_t next_request = start; + net_handle_arp_on(netdev_next(0), frame, len, wait); +} + +/** @brief Resolve a neighbor while polling all links with an interface-specific waiter. + * @param ifindex Egress device. @param ip Host-order next hop. + * @param timeout_ms Maximum uptime wait. @param out_mac Six output bytes. + * @return Zero, negative driver error, or legacy minus two on timeout. */ +int net_resolve_mac_on(uint32_t ifindex, uint32_t ip, uint32_t timeout_ms, uint8_t out_mac[6]) +{ + if (!out_mac || !ip) return -LINUX_EINVAL; + if (!net_interface_is_ready(ifindex)) return -LINUX_ENETDOWN; + if (net_arp_lookup_on(ifindex, ip, out_mac)) return 0; + struct net_arp_wait wait = {.ifindex = ifindex, .ip = ip}; + uint64_t start = time_uptime_ms(), next_request = start; uint32_t spins = 0; - wait = (struct net_arp_wait){0}; - wait.ip = ip; - if (net_arp_cache_lookup(ip, out_mac)) { - return 0; - } while (!net_timeout_expired(start, timeout_ms, spins++)) { + if (!net_interface_is_ready(ifindex)) return -LINUX_ENETDOWN; uint64_t now = time_uptime_ms(); if (now >= next_request) { - if (net_send_arp_request(ip) < 0) { - return -1; - } - next_request = now + 250u; + uint8_t frame[42], zero[6] = {0}; + net_write_arp_on(ifindex, frame, ARP_OPER_REQUEST, net_broadcast_mac, zero, ip); + int result = netdev_send(ifindex, frame, sizeof(frame)); + if (result < 0) return result; + next_request = now + 250; } - net_poll_once(&wait, 0, 0, 0); + net_poll_once(&wait, NULL, NULL, NULL); if (wait.done) { net_memcpy(out_mac, wait.mac, 6); - net_arp_cache_store(ip, wait.mac); return 0; } net_cpu_relax(); @@ -226,28 +139,37 @@ int net_resolve_mac(uint32_t ip, uint32_t timeout_ms, uint8_t *out_mac) return -2; } -/** @brief Emit only populated ARP cache entries without resolving neighbours. - * @param s Required output slice, serialized by execution ownership. - */ -void net_arp_proc_emit(struct text_stream *s) +/** @brief Resolve a next hop using the route selected for its address. + * @param ip Host-order next hop. @param timeout_ms Bound. @param out_mac Output. + * @return Zero or negative failure/timeout. */ +int net_resolve_mac(uint32_t ip, uint32_t timeout_ms, uint8_t out_mac[6]) +{ + struct net_ipv4_path path; + int result = net_ipv4_route_lookup(ip, 0, 0, &path); + if (result < 0) return result; + return net_resolve_mac_on(path.ifindex, ip, timeout_ms, out_mac); +} + +/** @brief Emit live ARP entries with their actual interface names. + * @param stream Execution-owned output slice. */ +void net_arp_proc_emit(struct text_stream *stream) { - text_string(s, - "IP address HW type Flags HW address Mask Device\n"); + text_string(stream, "IP address HW type Flags HW address Mask Device\n"); for (uint32_t i = 0; i < NET_ARP_CACHE_SIZE; ++i) { - const struct net_arp_cache_entry *a = &net_arp_cache[i]; - if (!a->ip) - continue; + const struct net_arp_cache_entry *entry = &net_arp_cache[i]; + char name[NET_IF_NAME_LEN]; + if (!entry->ip || !net_interface_name(entry->ifindex, name)) continue; for (int byte = 3; byte >= 0; --byte) { - text_unsigned(s, (a->ip >> (byte * 8)) & 255); - if (byte) - text_string(s, "."); + text_unsigned(stream, (entry->ip >> (byte * 8)) & 255); + if (byte) text_string(stream, "."); } - text_string(s, " 0x1 0x2 "); - for (unsigned b = 0; b < 6; ++b) { - if (b) - text_string(s, ":"); - text_hex(s, a->mac[b], 2); + text_string(stream, " 0x1 0x2 "); + for (unsigned byte = 0; byte < 6; ++byte) { + if (byte) text_string(stream, ":"); + text_hex(stream, entry->mac[byte], 2); } - text_string(s, " * eth0\n"); + text_string(stream, " * "); + text_string(stream, name); + text_string(stream, "\n"); } } diff --git a/net/link/ethernet.c b/net/link/ethernet.c index fbfdcdf4..7403d68f 100644 --- a/net/link/ethernet.c +++ b/net/link/ethernet.c @@ -8,10 +8,10 @@ * @param dst_mac Value supplied by the caller. * @param type Value supplied by the caller. */ -void net_write_eth(uint8_t *frame, const uint8_t *dst_mac, +void net_write_eth_on(uint32_t ifindex, uint8_t *frame, const uint8_t *dst_mac, uint16_t type) { - const uint8_t *src_mac = e1000_mac(); + const uint8_t *src_mac = netdev_mac(ifindex); net_memcpy(frame, dst_mac, 6); net_memcpy(frame + 6, src_mac, 6); net_put_u16(frame + 12, type); @@ -22,10 +22,23 @@ void net_write_eth(uint8_t *frame, const uint8_t *dst_mac, * @param frame Borrowed Ethernet header of at least 14 bytes. * @return Nonzero for a locally receivable destination. */ -int net_frame_for_us(const uint8_t *frame) +int net_frame_for_us_on(uint32_t ifindex, const uint8_t *frame) { - return net_memeq(frame, e1000_mac(), 6) || + return net_memeq(frame, netdev_mac(ifindex), 6) || net_memeq(frame, net_broadcast_mac, 6) || (frame[0] == 0x33 && frame[1] == 0x33 && net_get_u16(frame + 12) == 0x86dd); } + +/** @brief Construct an Ethernet header for the primary compatibility interface. + * @param frame Writable header. @param dst_mac Destination. @param type EtherType. */ +void net_write_eth(uint8_t *frame, const uint8_t *dst_mac, uint16_t type) +{ + net_write_eth_on(netdev_next(0), frame, dst_mac, type); +} +/** @brief Match a link destination on the primary compatibility interface. + * @param frame Readable header. @return Nonzero for an accepted destination. */ +int net_frame_for_us(const uint8_t *frame) +{ + return net_frame_for_us_on(netdev_next(0), frame); +} diff --git a/net/proc.c b/net/proc.c index fbf591ea..edaa026a 100644 --- a/net/proc.c +++ b/net/proc.c @@ -33,8 +33,8 @@ static int proc_ipv6_addresses(struct text_stream *s) if (net_ipv6_addresses(entries,count)>count) { kernel_free(entries); return -LINUX_EAGAIN; } for (uint32_t i=0;iifindex==1 ? "lo" : a->ifindex==2 ? "eth0" : NULL; - if (!name) continue; + char name[NET_IF_NAME_LEN]; + if (!net_interface_name(a->ifindex,name)) continue; proc_ipv6(s,&a->address); text_string(s," "); text_hex(s,a->ifindex,2); text_string(s," "); text_hex(s,a->prefix_length,2); text_string(s," "); text_hex(s,proc_ipv6_scope(a->scope),2); @@ -57,8 +57,8 @@ static int proc_ipv6_routes(struct text_stream *s) static const struct in6_addr zero; for (uint32_t i=0;iifindex==1 ? "lo" : r->ifindex==2 ? "eth0" : NULL; - if (!name) continue; + char name[NET_IF_NAME_LEN]; + if (!net_interface_name(r->ifindex,name)) continue; proc_ipv6(s,&r->destination); text_string(s," "); text_hex(s,r->prefix_length,2); text_string(s," "); proc_ipv6(s,&zero); text_string(s," 00 "); proc_ipv6(s,&r->gateway); text_string(s," "); text_hex(s,r->metric,8); @@ -91,17 +91,18 @@ int net_proc_emit(struct text_stream *s, const char *leaf) return 0; } if (!__builtin_strcmp(leaf, "dev")) { - struct net_proc_counters c; - net_device_counters(&c); text_string(s, "Inter-| Receive | " "Transmit\n face |bytes packets errs drop fifo frame compressed " "multicast|bytes packets errs drop fifo colls carrier compressed\n"); /* Loopback datagrams are accounted by their owner rather than the NIC. */ net_loopback_proc_emit(s); - struct e1000_info info; - e1000_get_info(&info); - if (info.present) { - text_string(s, " eth0:"); + for (uint32_t index=net_interface_next(NET_IF_LOOPBACK);index; + index=net_interface_next(index)) { + struct net_proc_counters c; + char name[NET_IF_NAME_LEN]; + if (!net_interface_name(index,name)) continue; + netdev_counters(index,&c); + text_string(s, " "); text_string(s,name); text_string(s, ":"); uint64_t fields[] = {c.rx_bytes, c.rx_packets, c.rx_errors, c.rx_drops, 0, 0, 0, 0, c.tx_bytes, c.tx_packets, c.tx_errors, 0, 0, 0, 0, 0}; for (unsigned i = 0; i < 16; ++i) { @@ -113,21 +114,22 @@ int net_proc_emit(struct text_stream *s, const char *leaf) } else if (!__builtin_strcmp(leaf, "route")) { text_string(s, "Iface\tDestination\tGateway " "\tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n"); - /* No IPv4 forwarding/routing table exists beyond loopback, connected - * subnet and the configured default gateway. Emit only those routes. */ - text_string(s, "lo\t0000007f\t00000000\t0001\t0\t0\t0\t000000ff\t0\t0\t0\n"); - if (net_interface_up && net_config.local_ip) { - text_string(s, "eth0\t"); - proc_ip(s, net_config.local_ip & net_config.subnet_mask); - text_string(s, "\t00000000\t0001\t0\t0\t0\t"); - proc_ip(s, net_config.subnet_mask); - text_string(s, "\t0\t0\t0\n"); - if (net_config.gateway_ip) { - text_string(s, "eth0\t00000000\t"); - proc_ip(s, net_config.gateway_ip); - text_string(s, "\t0003\t0\t0\t0\t00000000\t0\t0\t0\n"); - } + uint32_t count=net_ipv4_routes(NULL,0); + if (count > UINT32_MAX / sizeof(struct net_ipv4_route)) return -LINUX_ENOMEM; + struct net_ipv4_route *routes=count ? kernel_malloc((size_t)count*sizeof(*routes)) : NULL; + if (count && !routes) return -LINUX_ENOMEM; + if (net_ipv4_routes(routes,count)>count) { kernel_free(routes); return -LINUX_EAGAIN; } + for (uint32_t i=0;i #include #include -#include +#include +#include #include #include #include @@ -42,8 +43,15 @@ static uint64_t netlink_ino; static struct net_ipv6_address *nl_previous_addresses; static struct net_ipv6_route *nl_previous_routes; static uint32_t nl_previous_address_count, nl_previous_route_count; -static struct e1000_info nl_previous_link; -static bool nl_previous_link_up, nl_snapshot_ready; +struct nl_link_state { + uint32_t index; + char name[NET_IF_NAME_LEN]; + uint8_t mac[6]; + bool up, active; +}; +static struct nl_link_state *nl_previous_links; +static uint32_t nl_previous_link_count; +static bool nl_snapshot_ready; #define NL_SUPPORTED_GROUPS (RTMGRP_LINK|RTMGRP_IPV6_IFADDR|RTMGRP_IPV6_ROUTE) /** @brief Resolve the route socket attached to an open description. @@ -177,27 +185,28 @@ static uint8_t nl_prefix4(uint32_t mask) /** @brief Emit one interface from a copied link state. * @param b Builder. @param seq Sequence. @param index Interface index. - * @param multi Multipart flag. @param type Native event. @param info Ethernet snapshot. - * @param up Administrative IFF_UP flag, independent of carrier. @return Zero or ENOBUFS. */ -static int nl_link_snapshot(struct nl_builder *b, uint32_t seq, int index, bool multi, - uint16_t type, const struct e1000_info *info, bool up) + * @param multi Multipart flag. @param type Native event. @param link Copied link. + * @return Zero or ENOBUFS. */ +static int nl_link_snapshot(struct nl_builder *b, uint32_t seq, bool multi, + uint16_t type, const struct nl_link_state *link) { + uint32_t index = link->index; uint32_t start = b->used; struct ifinfomsg *m = nl_begin(b, type, multi ? NLM_F_MULTI : 0, seq, sizeof(*m)); if (!m) return -LINUX_ENOBUFS; m->ifi_index = index; m->ifi_type = index == 1 ? 772 : 1; /* ARPHRD_LOOPBACK / ARPHRD_ETHER */ m->ifi_flags = index == 1 ? IFF_UP | IFF_LOOPBACK | IFF_RUNNING | 0x10000u : - IFF_BROADCAST | IFF_MULTICAST | (up ? IFF_UP : 0) | - (up && info->active ? IFF_RUNNING : 0) | - (info->active ? 0x10000u : 0); + IFF_BROADCAST | IFF_MULTICAST | (link->up ? IFF_UP : 0) | + (link->up && link->active ? IFF_RUNNING : 0) | + (link->active ? 0x10000u : 0); + if (index == NET_IF_LOOPBACK && !link->up) m->ifi_flags = IFF_LOOPBACK; m->ifi_change = UINT32_MAX; uint32_t mtu = index == 1 ? 65536 : 1500; uint8_t zero[6] = {0}, broadcast[6] = {255,255,255,255,255,255}; - const char *name = index == 1 ? "lo" : "eth0"; - if (nl_attr(b,start,IFLA_IFNAME,name,index == 1 ? 3 : 5) || + if (nl_attr(b,start,IFLA_IFNAME,link->name,__builtin_strlen(link->name)+1) || nl_attr(b,start,IFLA_MTU,&mtu,sizeof(mtu)) || - nl_attr(b,start,IFLA_ADDRESS,index == 1 ? zero : info->mac,6) || + nl_attr(b,start,IFLA_ADDRESS,index == 1 ? zero : link->mac,6) || nl_attr(b,start,IFLA_BROADCAST,index == 1 ? zero : broadcast,6)) return -LINUX_ENOBUFS; return 0; } @@ -207,9 +216,15 @@ static int nl_link_snapshot(struct nl_builder *b, uint32_t seq, int index, bool * @param multi Multipart flag. @return Zero or errno. */ static int nl_link(struct nl_builder *b, uint32_t seq, int index, bool multi) { - struct e1000_info info={0}; - if (index==2) { e1000_get_info(&info); if (!info.present) return -LINUX_ENODEV; } - return nl_link_snapshot(b,seq,index,multi,RTM_NEWLINK,&info,net_interface_admin_up()); + struct nl_link_state link = {.index=(uint32_t)index}; + if (!net_interface_name(link.index,link.name)) return -LINUX_ENODEV; + link.up = net_interface_is_up(link.index); + if (link.index != NET_IF_LOOPBACK) { + struct e1000_info info = {0}; netdev_get_info(link.index,&info); + link.active = info.active != 0; + __builtin_memcpy(link.mac,info.mac,sizeof(link.mac)); + } + return nl_link_snapshot(b,seq,multi,RTM_NEWLINK,&link); } /** @brief Emit one IPv4 address using its actual configured mask. @@ -223,10 +238,11 @@ static int nl_address4(struct nl_builder *b, uint32_t seq, uint32_t index, if (!m) return -LINUX_ENOBUFS; *m = (struct ifaddrmsg){.ifa_family=AF_INET,.ifa_prefixlen=prefix,.ifa_flags=IFA_F_PERMANENT, .ifa_scope=index==1 ? RT_SCOPE_HOST : RT_SCOPE_UNIVERSE,.ifa_index=index}; - const char *name = index == 1 ? "lo" : "eth0"; + char name[NET_IF_NAME_LEN]; + if (!net_interface_name(index,name)) return -LINUX_ENODEV; if (nl_attr(b,start,IFA_ADDRESS,&ip,4) || nl_attr(b,start,IFA_LOCAL,&ip,4) || - nl_attr(b,start,IFA_LABEL,name,index==1 ? 3 : 5) || - (index==2 && nl_attr(b,start,IFA_BROADCAST,&br,4))) return -LINUX_ENOBUFS; + nl_attr(b,start,IFA_LABEL,name,__builtin_strlen(name)+1) || + (index!=NET_IF_LOOPBACK && nl_attr(b,start,IFA_BROADCAST,&br,4))) return -LINUX_ENOBUFS; return 0; } @@ -275,7 +291,7 @@ static int nl_route6(struct nl_builder *b, uint32_t seq, const struct net_ipv6_r * @param prefix Prefix length. @param gateway Host-order gateway. @param index Interface. * @return Zero or ENOBUFS. */ static int nl_route4(struct nl_builder *b, uint32_t seq, uint32_t destination, - uint8_t prefix, uint32_t gateway, uint32_t index) + uint8_t prefix, uint32_t gateway, uint32_t index, uint32_t metric) { uint32_t start=b->used, dst=__builtin_bswap32(destination), gw=__builtin_bswap32(gateway); struct rtmsg *m=nl_begin(b,RTM_NEWROUTE,NLM_F_MULTI,seq,sizeof(*m)); @@ -284,7 +300,8 @@ static int nl_route4(struct nl_builder *b, uint32_t seq, uint32_t destination, .rtm_protocol=RTPROT_KERNEL,.rtm_scope=gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK, .rtm_type=RTN_UNICAST}; if ((prefix && nl_attr(b,start,RTA_DST,&dst,4)) || nl_attr(b,start,RTA_OIF,&index,4) || - (gateway && nl_attr(b,start,RTA_GATEWAY,&gw,4))) return -LINUX_ENOBUFS; + (gateway && nl_attr(b,start,RTA_GATEWAY,&gw,4)) || + nl_attr(b,start,RTA_PRIORITY,&metric,4)) return -LINUX_ENOBUFS; return 0; } @@ -316,12 +333,37 @@ static int nl_routes(struct net_ipv6_route **out, uint32_t *count) return 0; } +/** @brief Copy every live link for dumps and subsequent change detection. */ +static int nl_links(struct nl_link_state **out, uint32_t *count) +{ + *out = NULL; *count = 0; + for (uint32_t index=net_interface_next(0);index;index=net_interface_next(index)) ++*count; + if (!*count) return 0; + if (*count > NL_MESSAGE_MAX / sizeof(**out)) return -LINUX_ENOBUFS; + struct nl_link_state *links=kernel_malloc((size_t)*count*sizeof(*links)); + if (!links) return -LINUX_ENOMEM; + uint32_t used=0; + for (uint32_t index=net_interface_next(0);index;index=net_interface_next(index)) { + if (used==*count) { kernel_free(links); return -LINUX_EAGAIN; } + struct nl_link_state *link=&links[used++]; + __builtin_memset(link,0,sizeof(*link)); link->index=index; + if (!net_interface_name(index,link->name)) { kernel_free(links); return -LINUX_EAGAIN; } + link->up=net_interface_is_up(index); + if (index!=NET_IF_LOOPBACK) { + struct e1000_info info={0}; netdev_get_info(index,&info); + link->active=info.active!=0; __builtin_memcpy(link->mac,info.mac,sizeof(link->mac)); + } + } + *out=links; *count=used; return 0; +} + /** @brief Drop the owned change-detection baseline after the last subscriber leaves. */ static void nl_forget_snapshot(void) { - kernel_free(nl_previous_addresses); kernel_free(nl_previous_routes); + kernel_free(nl_previous_addresses); kernel_free(nl_previous_routes); kernel_free(nl_previous_links); nl_previous_addresses=NULL; nl_previous_routes=NULL; - nl_previous_address_count=nl_previous_route_count=0; nl_snapshot_ready=false; + nl_previous_links=NULL; + nl_previous_address_count=nl_previous_route_count=nl_previous_link_count=0; nl_snapshot_ready=false; } /** @brief Capture a baseline before admitting the first subscription. @@ -330,12 +372,14 @@ static int nl_initial_snapshot(void) { if (nl_snapshot_ready) return 0; struct net_ipv6_address *addresses; struct net_ipv6_route *routes; - uint32_t acount, rcount; + struct nl_link_state *links; + uint32_t acount, rcount, lcount; int rc=nl_addresses(&addresses,&acount); if (rc) return rc; rc=nl_routes(&routes,&rcount); if (rc) { kernel_free(addresses); return rc; } + rc=nl_links(&links,&lcount); if (rc) { kernel_free(addresses); kernel_free(routes); return rc; } nl_previous_addresses=addresses; nl_previous_address_count=acount; nl_previous_routes=routes; nl_previous_route_count=rcount; - e1000_get_info(&nl_previous_link); nl_previous_link_up=net_interface_admin_up(); + nl_previous_links=links; nl_previous_link_count=lcount; nl_snapshot_ready=true; return 0; } @@ -395,6 +439,7 @@ static bool nl_equal_route(const struct net_ipv6_route *a,const struct net_ipv6_ * route or Ethernet snapshot according to group. @param up Link readiness for link events. */ static void nl_publish(uint32_t group,uint16_t type,const void *value,bool up) { + (void)up; bool interested=false; for (struct netlink_socket *s=netlink_sockets;s;s=s->next) if (s->groups & group) { interested=true; break; } if (!interested) return; @@ -403,7 +448,7 @@ static void nl_publish(uint32_t group,uint16_t type,const void *value,bool up) struct nl_builder b={.message=message}; int rc=group==RTMGRP_IPV6_IFADDR ? nl_address6(&b,0,value,type,false) : group==RTMGRP_IPV6_ROUTE ? nl_route6(&b,0,value,type,false) : - nl_link_snapshot(&b,0,2,false,type,value,up); + nl_link_snapshot(&b,0,false,type,value); if (rc) { kernel_free(message); nl_overrun(group); return; } for (struct netlink_socket *s=netlink_sockets;s;s=s->next) { if (!(s->groups & group)) continue; @@ -425,11 +470,14 @@ void netlink_ipv6_changed(void) { if (!nl_snapshot_ready) return; struct net_ipv6_address *addresses; struct net_ipv6_route *routes; - uint32_t acount, rcount; + struct nl_link_state *links; + uint32_t acount, rcount, lcount; int rc=nl_addresses(&addresses,&acount); if (rc) { nl_overrun(NL_SUPPORTED_GROUPS); return; } rc=nl_routes(&routes,&rcount); if (rc) { kernel_free(addresses); nl_overrun(NL_SUPPORTED_GROUPS); return; } + rc=nl_links(&links,&lcount); + if (rc) { kernel_free(addresses); kernel_free(routes); nl_overrun(NL_SUPPORTED_GROUPS); return; } for (uint32_t i=0;iup!=links[i].up || old->active!=links[i].active || + __builtin_memcmp(old->mac,links[i].mac,sizeof(old->mac)) || + __builtin_strcmp(old->name,links[i].name)) + nl_publish(RTMGRP_LINK,RTM_NEWLINK,&links[i],false); + } + kernel_free(nl_previous_addresses); kernel_free(nl_previous_routes); kernel_free(nl_previous_links); nl_previous_addresses=addresses; nl_previous_address_count=acount; nl_previous_routes=routes; nl_previous_route_count=rcount; - nl_previous_link=link; nl_previous_link_up=up; + nl_previous_links=links; nl_previous_link_count=lcount; } /** @brief Render a link, address or route dump from current network state. @@ -477,11 +533,12 @@ static int nl_dump(struct nl_builder *b, const struct nlmsghdr *h) if (req.ifi_family != AF_UNSPEC && req.ifi_family != AF_PACKET && req.ifi_family != AF_INET && req.ifi_family != AF_INET6) return -LINUX_EAFNOSUPPORT; if (!multi) { - if (req.ifi_index != 1 && req.ifi_index != 2) return -LINUX_ENODEV; + if (req.ifi_index <= 0 || !net_interface_exists((uint32_t)req.ifi_index)) return -LINUX_ENODEV; return nl_link(b,h->nlmsg_seq,req.ifi_index,false); } - int rc=nl_link(b,h->nlmsg_seq,1,true); if (rc) return rc; - if (net_interface_present()) { rc=nl_link(b,h->nlmsg_seq,2,true); if (rc) return rc; } + for (uint32_t index=net_interface_next(0);index;index=net_interface_next(index)) { + int rc=nl_link(b,h->nlmsg_seq,(int)index,true); if (rc) return rc; + } return nl_done(b,h->nlmsg_seq); } if (!multi) return -LINUX_EOPNOTSUPP; @@ -493,12 +550,15 @@ static int nl_dump(struct nl_builder *b, const struct nlmsghdr *h) return -LINUX_EAFNOSUPPORT; int rc=0; if (req.ifa_family != AF_INET6) { - if ((!req.ifa_index || req.ifa_index==1) && - (rc=nl_address4(b,h->nlmsg_seq,1,0x7f000001u,8,0))) return rc; - struct reliefos_net_config config; net_get_config(&config); - if ((!req.ifa_index || req.ifa_index==2) && net_interface_present() && config.local_ip && - (rc=nl_address4(b,h->nlmsg_seq,2,config.local_ip,nl_prefix4(config.subnet_mask), - config.local_ip | ~config.subnet_mask))) return rc; + for (uint32_t index=net_interface_next(0);index;index=net_interface_next(index)) { + struct reliefos_net_config config; + if ((req.ifa_index && req.ifa_index!=index) || + net_interface_get_config(index,&config) || !config.local_ip) continue; + struct net_interface *iface=net_interface_get(index); + uint32_t broadcast=iface ? iface->broadcast : 0; + rc=nl_address4(b,h->nlmsg_seq,index,config.local_ip,nl_prefix4(config.subnet_mask),broadcast); + if (rc) return rc; + } } if (req.ifa_family != AF_INET) { struct net_ipv6_address *entries; uint32_t count; @@ -517,13 +577,15 @@ static int nl_dump(struct nl_builder *b, const struct nlmsghdr *h) return -LINUX_EAFNOSUPPORT; int rc=0; if (req.rtm_family != AF_INET6) { - rc=nl_route4(b,h->nlmsg_seq,0x7f000000u,8,0,1); if (rc) return rc; - struct reliefos_net_config config; net_get_config(&config); - if (net_interface_ready() && config.local_ip) { - rc=nl_route4(b,h->nlmsg_seq,config.local_ip & config.subnet_mask, - nl_prefix4(config.subnet_mask),0,2); if (rc) return rc; - if (config.gateway_ip) { rc=nl_route4(b,h->nlmsg_seq,0,0,config.gateway_ip,2); if (rc) return rc; } - } + uint32_t count=net_ipv4_routes(NULL,0); + if (count > NL_MESSAGE_MAX / sizeof(struct net_ipv4_route)) return -LINUX_ENOBUFS; + struct net_ipv4_route *entries=count ? kernel_malloc((size_t)count*sizeof(*entries)) : NULL; + if (count && !entries) return -LINUX_ENOMEM; + if (net_ipv4_routes(entries,count)>count) { kernel_free(entries); return -LINUX_EAGAIN; } + for (uint32_t i=0;inlmsg_seq,entries[i].destination,nl_prefix4(entries[i].mask), + entries[i].gateway,entries[i].ifindex,entries[i].metric); + kernel_free(entries); if (rc) return rc; } if (req.rtm_family != AF_INET) { struct net_ipv6_route *entries; uint32_t count; @@ -564,12 +626,112 @@ static int nl_copy_attr(const struct rtattr *a, void *out, uint32_t size) __builtin_memcpy(out,a+1,size); return 0; } +/** @brief Change administrative state on one existing interface. */ +static int nl_change_link(const struct nlmsghdr *h) +{ + if (h->nlmsg_len < sizeof(*h)+sizeof(struct ifinfomsg)) return -LINUX_EINVAL; + struct ifinfomsg req; + __builtin_memcpy(&req,h+1,sizeof(req)); + if (req.ifi_family!=AF_UNSPEC && req.ifi_family!=AF_PACKET && + req.ifi_family!=AF_INET && req.ifi_family!=AF_INET6) return -LINUX_EAFNOSUPPORT; + if (req.ifi_index<0) return -LINUX_EINVAL; + if (req.ifi_change & ~IFF_UP) return -LINUX_EOPNOTSUPP; + const struct rtattr *attrs[17]={0}; + int rc=nl_parse_attrs((const uint8_t *)(h+1)+sizeof(req), + h->nlmsg_len-sizeof(*h)-sizeof(req),attrs,17); + if (rc) return rc; + for (unsigned i=0;i<17;++i) + if (attrs[i] && i!=IFLA_IFNAME && i!=IFLA_MTU) return -LINUX_EOPNOTSUPP; + uint32_t index=(uint32_t)req.ifi_index; + if (attrs[IFLA_IFNAME]) { + uint32_t n=attrs[IFLA_IFNAME]->rta_len-sizeof(struct rtattr); + const char *input=(const char *)(attrs[IFLA_IFNAME]+1); + if (!n || n>NET_IF_NAME_LEN) return -LINUX_EINVAL; + bool terminated=false; + for (uint32_t i=0;iifa_index || req->ifa_prefixlen>32) return -LINUX_EINVAL; + struct net_interface *iface=net_interface_get(req->ifa_index); + if (!iface) return req->ifa_index==NET_IF_LOOPBACK ? -LINUX_EOPNOTSUPP : -LINUX_ENODEV; + const struct rtattr *a[9]={0}; + int rc=nl_parse_attrs((const uint8_t *)(h+1)+sizeof(*req), + h->nlmsg_len-sizeof(*h)-sizeof(*req),a,9); + if (rc) return rc; + for (unsigned i=0;i<9;++i) + if (a[i] && i!=IFA_LOCAL && i!=IFA_ADDRESS && i!=IFA_FLAGS && + i!=IFA_BROADCAST && i!=IFA_LABEL && i!=IFA_CACHEINFO) return -LINUX_EOPNOTSUPP; + if (!a[IFA_LOCAL] && !a[IFA_ADDRESS]) return -LINUX_EINVAL; + uint32_t ip=0, peer=0, broadcast=0, flags=req->ifa_flags; + rc=nl_copy_attr(a[IFA_LOCAL] ? a[IFA_LOCAL] : a[IFA_ADDRESS],&ip,4); if (rc) return rc; + if (a[IFA_LOCAL] && a[IFA_ADDRESS]) { + rc=nl_copy_attr(a[IFA_ADDRESS],&peer,4); if (rc) return rc; + if (peer!=ip) return -LINUX_EOPNOTSUPP; + } + if (a[IFA_FLAGS]) { rc=nl_copy_attr(a[IFA_FLAGS],&flags,4); if (rc) return rc; } + if (flags & ~IFA_F_PERMANENT) return -LINUX_EOPNOTSUPP; + if (a[IFA_LABEL]) { + char name[NET_IF_NAME_LEN]; + if (!net_interface_name(req->ifa_index,name)) return -LINUX_ENODEV; + uint32_t n=a[IFA_LABEL]->rta_len-sizeof(struct rtattr); + if (n!=__builtin_strlen(name)+1 || __builtin_memcmp(a[IFA_LABEL]+1,name,n)) + return -LINUX_EOPNOTSUPP; + } + if (a[IFA_CACHEINFO]) { + struct ifa_cacheinfo cache; + rc=nl_copy_attr(a[IFA_CACHEINFO],&cache,sizeof(cache)); if (rc) return rc; + if (cache.ifa_prefered!=UINT32_MAX || cache.ifa_valid!=UINT32_MAX) return -LINUX_EOPNOTSUPP; + } + ip=__builtin_bswap32(ip); + if (!ip || ip>=0xe0000000u) return -LINUX_EINVAL; + uint32_t mask=req->ifa_prefixlen ? UINT32_MAX << (32u-req->ifa_prefixlen) : 0; + if (a[IFA_BROADCAST]) { rc=nl_copy_attr(a[IFA_BROADCAST],&broadcast,4); if (rc) return rc; + broadcast=__builtin_bswap32(broadcast); } + else broadcast=ip | ~mask; + bool found=iface->config.local_ip==ip && iface->config.subnet_mask==mask; + if (remove) { + if (!found) return -LINUX_EADDRNOTAVAIL; + return net_interface_configure_ipv4(req->ifa_index,0,0,0); + } + if (iface->config.local_ip && !(h->nlmsg_flags & NLM_F_REPLACE)) return -LINUX_EEXIST; + if (!iface->config.local_ip && (h->nlmsg_flags & NLM_F_REPLACE) && + !(h->nlmsg_flags & NLM_F_CREATE)) return -LINUX_EADDRNOTAVAIL; + return net_interface_configure_ipv4(req->ifa_index,ip,mask,broadcast); +} + /** @brief Apply an authorized IPv6 address request using copied snapshots. * @param h Checked request. @param remove True for delete. @return Zero or errno. */ static int nl_change_address(const struct nlmsghdr *h, bool remove) { if (h->nlmsg_len < sizeof(*h)+sizeof(struct ifaddrmsg)) return -LINUX_EINVAL; struct ifaddrmsg req; __builtin_memcpy(&req,h+1,sizeof(req)); + if (req.ifa_family == AF_INET) return nl_change_address4(h,&req,remove); if (req.ifa_family != AF_INET6) return -LINUX_EAFNOSUPPORT; if (!req.ifa_index || req.ifa_prefixlen > 128) return -LINUX_EINVAL; const struct rtattr *a[9]={0}; @@ -591,8 +753,9 @@ static int nl_change_address(const struct nlmsghdr *h, bool remove) if (a[IFA_LABEL]) { const char *label=(const char *)(a[IFA_LABEL]+1); uint32_t n=a[IFA_LABEL]->rta_len-sizeof(struct rtattr); - const char *expected=entry.ifindex==1 ? "lo" : entry.ifindex==2 ? "eth0" : ""; - uint32_t expected_n=entry.ifindex==1 ? 3 : entry.ifindex==2 ? 5 : 1; + char expected[NET_IF_NAME_LEN]; + if (!net_interface_name(entry.ifindex,expected)) return -LINUX_ENODEV; + uint32_t expected_n=__builtin_strlen(expected)+1; if (n!=expected_n || __builtin_memcmp(label,expected,n)) return -LINUX_EOPNOTSUPP; } if (a[IFA_CACHEINFO]) { @@ -617,12 +780,68 @@ static int nl_change_address(const struct nlmsghdr *h, bool remove) return net_ipv6_address_update(&entry,remove); } +/** @brief Apply an IPv4 route request to the shared route table. */ +static int nl_change_route4(const struct nlmsghdr *h, const struct rtmsg *req, bool remove) +{ + if (req->rtm_dst_len>32 || req->rtm_src_len || req->rtm_tos || req->rtm_flags || + (req->rtm_table && req->rtm_table!=RT_TABLE_MAIN) || + (req->rtm_type && req->rtm_type!=RTN_UNICAST)) return -LINUX_EOPNOTSUPP; + const struct rtattr *a[16]={0}; + int rc=nl_parse_attrs((const uint8_t *)(h+1)+sizeof(*req), + h->nlmsg_len-sizeof(*h)-sizeof(*req),a,16); + if (rc) return rc; + for (unsigned i=0;i<16;++i) + if (a[i] && i!=RTA_DST && i!=RTA_GATEWAY && i!=RTA_OIF && + i!=RTA_PRIORITY && i!=RTA_TABLE) return -LINUX_EOPNOTSUPP; + if (req->rtm_dst_len && !a[RTA_DST]) return -LINUX_EINVAL; + struct net_ipv4_route route={.mask=req->rtm_dst_len ? UINT32_MAX << (32u-req->rtm_dst_len) : 0, + .flags=RTF_UP}; + if ((rc=nl_copy_attr(a[RTA_DST],&route.destination,4)) || + (rc=nl_copy_attr(a[RTA_GATEWAY],&route.gateway,4)) || + (rc=nl_copy_attr(a[RTA_OIF],&route.ifindex,4)) || + (rc=nl_copy_attr(a[RTA_PRIORITY],&route.metric,4))) return rc; + route.destination=__builtin_bswap32(route.destination) & route.mask; + route.gateway=__builtin_bswap32(route.gateway); + if (a[RTA_TABLE]) { uint32_t table; rc=nl_copy_attr(a[RTA_TABLE],&table,4); if (rc) return rc; + if (table!=RT_TABLE_MAIN) return -LINUX_EOPNOTSUPP; } + if (a[RTA_GATEWAY]) { if (!route.gateway) return -LINUX_EINVAL; route.flags|=RTF_GATEWAY; } + if (route.mask==UINT32_MAX) route.flags|=RTF_HOST; + if (!route.ifindex) { + struct net_ipv4_path path; + rc=net_ipv4_route_lookup(route.gateway ? route.gateway : route.destination,0,0,&path); + if (rc) return rc; + route.ifindex=path.ifindex; + } + if (remove && !route.mask && !route.gateway) { + struct net_interface *iface=net_interface_get(route.ifindex); + if (!iface || !iface->config.gateway_ip) return -LINUX_ESRCH; + route.gateway=iface->config.gateway_ip; route.flags|=RTF_GATEWAY; + } + bool replace=(h->nlmsg_flags & NLM_F_REPLACE)!=0; + if (!remove && replace && !(h->nlmsg_flags & NLM_F_CREATE)) { + uint32_t count=net_ipv4_routes(NULL,0); + if (count>NL_MESSAGE_MAX/sizeof(struct net_ipv4_route)) return -LINUX_ENOBUFS; + struct net_ipv4_route *entries=count ? kernel_malloc((size_t)count*sizeof(*entries)) : NULL; + if (count && !entries) return -LINUX_ENOMEM; + if (net_ipv4_routes(entries,count)>count) { kernel_free(entries); return -LINUX_EAGAIN; } + bool found=false; + for (uint32_t i=0;inlmsg_len < sizeof(*h)+sizeof(struct rtmsg)) return -LINUX_EINVAL; struct rtmsg req; __builtin_memcpy(&req,h+1,sizeof(req)); + if (req.rtm_family == AF_INET) return nl_change_route4(h,&req,remove); if (req.rtm_family != AF_INET6) return -LINUX_EAFNOSUPPORT; if (req.rtm_dst_len > 128 || req.rtm_src_len || req.rtm_tos || req.rtm_flags || (req.rtm_table && req.rtm_table!=RT_TABLE_MAIN) || @@ -671,11 +890,14 @@ static int nl_request(struct nl_builder *b, const struct nlmsghdr *h) if (!(h->nlmsg_flags & NLM_F_REQUEST)) rc=-LINUX_EINVAL; else if (h->nlmsg_type==RTM_GETLINK || h->nlmsg_type==RTM_GETADDR || h->nlmsg_type==RTM_GETROUTE) rc=nl_dump(b,h); - else if (h->nlmsg_type==RTM_NEWADDR || h->nlmsg_type==RTM_DELADDR || + else if (h->nlmsg_type==RTM_NEWLINK || h->nlmsg_type==RTM_SETLINK || + h->nlmsg_type==RTM_NEWADDR || h->nlmsg_type==RTM_DELADDR || h->nlmsg_type==RTM_NEWROUTE || h->nlmsg_type==RTM_DELROUTE) { struct task *task=sched_current_task(); if (!task || !(task->cap_effective & (1ULL<nlmsg_flags & (NLM_F_ECHO|NLM_F_APPEND)) rc=-LINUX_EOPNOTSUPP; + else if (h->nlmsg_type==RTM_NEWLINK || h->nlmsg_type==RTM_SETLINK) + rc=nl_change_link(h); else if (h->nlmsg_type==RTM_NEWADDR || h->nlmsg_type==RTM_DELADDR) rc=nl_change_address(h,h->nlmsg_type==RTM_DELADDR); else rc=nl_change_route(h,h->nlmsg_type==RTM_DELROUTE); diff --git a/net/socket/packet.c b/net/socket/packet.c index ad15d734..af20b077 100644 --- a/net/socket/packet.c +++ b/net/socket/packet.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -81,7 +83,7 @@ static struct packet_socket *packet_of(const struct task_file *file) * @param input Ethernet frame under the execution lock. * @param length Complete frame length. */ -void net_packet_input(const void *input, uint32_t length) +void net_packet_input_on(uint32_t ifindex, const void *input, uint32_t length) { if (length < 14 || length > 1514) return; const uint8_t *frame = input; @@ -95,7 +97,7 @@ void net_packet_input(const void *input, uint32_t length) uint32_t source = 0, destination = 0; if (s->domain == AF_PACKET) { if (!s->protocol || (s->protocol != (int)__builtin_bswap16(3) && s->protocol != protocol)) continue; - if (s->ifindex && s->ifindex != 2) continue; + if (s->ifindex && (uint32_t)s->ifindex != ifindex) continue; } else { if (s->domain != AF_INET) continue; if (protocol != __builtin_bswap16(0x0800) || length < 34 || frame[14] >> 4 != 4 || @@ -111,7 +113,7 @@ void net_packet_input(const void *input, uint32_t length) struct packet_message *m = kernel_malloc(sizeof(*m) + size); if (!m) continue; *m = (struct packet_message){.length = size, - .link = {.sll_family = AF_PACKET, .sll_protocol = protocol, .sll_ifindex = 2, + .link = {.sll_family = AF_PACKET, .sll_protocol = protocol, .sll_ifindex = (int)ifindex, .sll_hatype = 1, .sll_halen = 6, .sll_pkttype = frame[0] == 255 ? PACKET_BROADCAST : frame[0] & 1 ? PACKET_MULTICAST : PACKET_HOST}, .ip = {.sin_family = AF_INET, .sin_addr.s_addr = source}}; @@ -188,6 +190,8 @@ static int packet6_send(struct packet_socket *s, const void *data, uint32_t leng if (address) { int ret = packet6_import(address, address_length, &to); if (ret) return ret; } else if (!s->connected6) return -LINUX_EDESTADDRREQ; if (length > 65535) return -LINUX_EMSGSIZE; + if (to.sin6_scope_id && s->ifindex && to.sin6_scope_id != (uint32_t)s->ifindex) + return -LINUX_EINVAL; uint32_t scope = to.sin6_scope_id ? to.sin6_scope_id : (uint32_t)s->ifindex; if (!scope && to.sin6_addr.s6_addr[0] == 0xff) scope = s->multicast_if; struct net_ipv6_path path; @@ -231,7 +235,6 @@ int task_packet_send(struct task_file *file, const void *data, uint32_t length, if (s->error) { int error=s->error; s->error=0; return -error; } if (flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL)) return -LINUX_EOPNOTSUPP; if (s->domain == AF_INET6) return packet6_send(s, data, length, address, address_length); - if (!net_interface_ready()) return -LINUX_ENETDOWN; if (s->domain == AF_INET) { uint32_t destination = s->remote_ip; if (address) { @@ -246,7 +249,7 @@ int task_packet_send(struct task_file *file, const void *data, uint32_t length, } if (!destination) return -LINUX_EDESTADDRREQ; if (destination == UINT32_MAX && !s->broadcast) return -LINUX_EACCES; - return net_ipv4_send_raw(__builtin_bswap32(destination), __builtin_bswap32(s->local_ip), + return net_ipv4_send_raw_on((uint32_t)s->ifindex, __builtin_bswap32(destination), __builtin_bswap32(s->local_ip), (uint8_t)s->protocol, s->hdrincl, data, length); } struct sockaddr_ll to = {.sll_family = AF_PACKET, .sll_protocol = (uint16_t)s->protocol, .sll_ifindex = s->ifindex}; @@ -258,14 +261,16 @@ int task_packet_send(struct task_file *file, const void *data, uint32_t length, __builtin_memcpy(&to, (void *)(uintptr_t)address, sizeof(to)); if (to.sll_family != AF_PACKET) return -LINUX_EINVAL; } - if (to.sll_ifindex != 2) return -LINUX_ENXIO; + if (to.sll_ifindex <= 0 || !net_interface_exists((uint32_t)to.sll_ifindex)) return -LINUX_ENXIO; + if (to.sll_ifindex == 1) return -LINUX_EOPNOTSUPP; + if (!net_interface_is_ready((uint32_t)to.sll_ifindex)) return -LINUX_ENETDOWN; uint8_t frame[1514]; uint32_t total = length; if (s->type == SOCK_DGRAM) { if (length > 1500) return -LINUX_EMSGSIZE; if (to.sll_halen < 6 || to.sll_halen > 8) return -LINUX_EINVAL; __builtin_memcpy(frame, to.sll_addr, 6); - __builtin_memcpy(frame + 6, e1000_mac(), 6); + __builtin_memcpy(frame + 6, netdev_mac((uint32_t)to.sll_ifindex), 6); __builtin_memcpy(frame + 12, &to.sll_protocol, 2); __builtin_memcpy(frame + 14, data, length); total += 14; @@ -274,7 +279,7 @@ int task_packet_send(struct task_file *file, const void *data, uint32_t length, if (length > sizeof(frame)) return -LINUX_EMSGSIZE; __builtin_memcpy(frame, data, length); } - int ret = e1000_send(frame, total); + int ret = netdev_send((uint32_t)to.sll_ifindex, frame, total); return ret < 0 ? ret : (int)length; } @@ -442,7 +447,7 @@ static int packet6_option(struct packet_socket *s, bool set, uint32_t level, uin __builtin_memcpy(&group, (void *)(uintptr_t)pointer, sizeof(group)); if (group.ipv6mr_multiaddr.s6_addr[0] != 0xff || group.ipv6mr_ifindex < 0) return -LINUX_EINVAL; uint32_t ifindex = group.ipv6mr_ifindex ? (uint32_t)group.ipv6mr_ifindex : s->multicast_if; - if (!ifindex) ifindex = s->ifindex ? (uint32_t)s->ifindex : 2; + if (!ifindex) ifindex = s->ifindex ? (uint32_t)s->ifindex : netdev_next(0); unsigned slot = 16, empty = 16; for (unsigned i = 0; i < 16; ++i) { if (!s->groups[i].used && empty == 16) empty = i; @@ -471,7 +476,7 @@ static int packet6_option(struct packet_socket *s, bool set, uint32_t level, uin s->checksum_offset = value < 0 ? -1 : value; break; case IPV6_UNICAST_HOPS: if (value < -1 || value > 255) return -LINUX_EINVAL; s->unicast_hops = value; break; case IPV6_MULTICAST_HOPS: if (value < -1 || value > 255) return -LINUX_EINVAL; s->multicast_hops = value; break; - case IPV6_MULTICAST_IF: if (value < 0 || value > 2) return -LINUX_ENODEV; s->multicast_if = (uint32_t)value; break; + case IPV6_MULTICAST_IF: if (value < 0 || (value && !net_interface_exists((uint32_t)value))) return -LINUX_ENODEV; s->multicast_if = (uint32_t)value; break; case IPV6_MULTICAST_LOOP: if (value != 0 && value != 1) return -LINUX_EINVAL; s->multicast_loop = value != 0; break; case IPV6_RECVPKTINFO: s->recv_pktinfo = value != 0; break; case IPV6_RECVHOPLIMIT: s->recv_hoplimit = value != 0; break; @@ -498,7 +503,7 @@ static int packet6_option(struct packet_socket *s, bool set, uint32_t level, uin case IPV6_MTU: { if (!s->connected6) return -LINUX_ENOTCONN; struct net_ipv6_path path; - int ret = net_ipv6_route(&s->remote6, s->remote_scope6, &s->local6, &path); + int ret = net_ipv6_route(&s->remote6, s->remote_scope6 ? s->remote_scope6 : (uint32_t)s->ifindex, &s->local6, &path); if (ret < 0) return ret; value = (int)path.mtu; break; } @@ -576,6 +581,14 @@ int64_t syscall_packet(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, #endif struct packet_socket *s = packet_of(file); if (!s) return -LINUX_EBADF; + if ((number == __NR_setsockopt || number == __NR_getsockopt) && + a1 == SOL_SOCKET && a2 == SO_BINDTODEVICE) { + uint32_t index = (uint32_t)s->ifindex; + bool set = number == __NR_setsockopt; + int result = net_interface_device_option(set, a3, a4, &index); + if (!result && set) s->ifindex = (int)index; + return result; + } if (number == __NR_bind || number == __NR_connect) { if (s->domain == AF_PACKET) { if (number == __NR_connect) return -LINUX_EOPNOTSUPP; @@ -585,15 +598,20 @@ int64_t syscall_packet(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, struct sockaddr_ll address; __builtin_memcpy(&address, (void *)(uintptr_t)a1, sizeof(address)); if (address.sll_family != AF_PACKET) return -LINUX_EINVAL; - if (address.sll_ifindex && address.sll_ifindex != 2) return -LINUX_ENODEV; + if (address.sll_ifindex < 0 || (address.sll_ifindex && + !net_interface_exists((uint32_t)address.sll_ifindex))) return -LINUX_ENODEV; s->ifindex = address.sll_ifindex; s->protocol = address.sll_protocol; } else if (s->domain == AF_INET6) { struct sockaddr_in6 address; int ret = packet6_import(a1, (uint32_t)a2, &address); if (ret < 0) return ret; - if (address.sin6_scope_id > 2) return -LINUX_ENODEV; + if (address.sin6_scope_id && !net_interface_exists(address.sin6_scope_id)) return -LINUX_ENODEV; + if (address.sin6_scope_id && s->ifindex && address.sin6_scope_id != (uint32_t)s->ifindex) + return -LINUX_EINVAL; if (number == __NR_connect) { + if (address.sin6_scope_id && s->ifindex && address.sin6_scope_id != (uint32_t)s->ifindex) + return -LINUX_EINVAL; s->remote6 = address.sin6_addr; s->remote_scope6 = address.sin6_scope_id; s->connected6 = true; } else { if (s->type == SOCK_DGRAM && address.sin6_port) { @@ -622,9 +640,7 @@ int64_t syscall_packet(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, if (address.sin_family != AF_INET) return -LINUX_EAFNOSUPPORT; if (number == __NR_connect) s->remote_ip = address.sin_addr.s_addr; else { - struct reliefos_net_config config; - net_get_config(&config); - if (address.sin_addr.s_addr && address.sin_addr.s_addr != __builtin_bswap32(config.local_ip)) + if (address.sin_addr.s_addr && !net_interface_for_ipv4(__builtin_bswap32(address.sin_addr.s_addr))) return -LINUX_EADDRNOTAVAIL; s->local_ip = address.sin_addr.s_addr; } @@ -641,7 +657,7 @@ int64_t syscall_packet(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, if (number == __NR_getsockname && s->type == SOCK_DGRAM) address.sin6_port = __builtin_bswap16(s->ping_id); if (number == __NR_getsockname && s->connected6 && packet6_any(&address.sin6_addr)) { struct net_ipv6_path path; - if (!net_ipv6_route(&s->remote6, s->remote_scope6, &s->local6, &path)) address.sin6_addr = path.source; + if (!net_ipv6_route(&s->remote6, s->remote_scope6 ? s->remote_scope6 : (uint32_t)s->ifindex, &s->local6, &path)) address.sin6_addr = path.source; } if (length > sizeof(address)) length = sizeof(address); ret = socket_user_prepare(task, a1, length, true); if (ret) return ret; @@ -734,7 +750,7 @@ int64_t syscall_packet(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, /** @brief Report ICMPv6 errors to the socket owning the quoted protocol tuple. * @param packet Quoted original IPv6 packet. @param length Quote size. * @param error Positive Linux errno. @param mtu Reported path MTU or zero. */ -void net_ipv6_socket_error(const uint8_t *packet, uint32_t length, int error, uint32_t mtu) +void net_ipv6_socket_error_on(uint32_t ifindex, const uint8_t *packet, uint32_t length, int error, uint32_t mtu) { if (!packet || length < 40 || packet[0] >> 4 != 6) return; struct in6_addr source, destination; @@ -743,12 +759,15 @@ void net_ipv6_socket_error(const uint8_t *packet, uint32_t length, int error, ui if ((protocol == 6 || protocol == 17) && length >= 48) { uint16_t source_port = ((uint16_t)packet[40] << 8) | packet[41]; uint16_t destination_port = ((uint16_t)packet[42] << 8) | packet[43]; - if (protocol == 6) net_tcp6_error(&source, &destination, source_port, destination_port, error, mtu); - else net_udp6_error(&source, &destination, source_port, destination_port, error, mtu); + if (protocol == 6) net_tcp6_error_on(ifindex, &source, &destination, source_port, destination_port, error, mtu); + else net_udp6_error_on(ifindex, &source, &destination, source_port, destination_port, error, mtu); } for (unsigned i = 0; i < PACKET_LIMIT; ++i) { struct packet_socket *s = sockets[i]; if (!s || s->domain != AF_INET6 || s->protocol != protocol || + (ifindex && ((s->ifindex && (uint32_t)s->ifindex != ifindex) || + (s->scope6 && s->scope6 != ifindex) || + (s->remote_scope6 && s->remote_scope6 != ifindex))) || (!packet6_any(&s->local6) && __builtin_memcmp(&s->local6, &source, sizeof(source))) || (s->connected6 && __builtin_memcmp(&s->remote6, &destination, sizeof(destination)))) continue; s->error = error; @@ -784,3 +803,28 @@ void net_ipv6_transport_proc_emit(struct text_stream *stream, const char *leaf) else if (!__builtin_strcmp(leaf,"udp6")) net_udp6_proc_emit(stream); else if (!__builtin_strcmp(leaf,"raw6")) net_raw6_proc_emit(stream); } + +/** @brief Deliver a frame for the primary compatibility interface. + * @param input Borrowed Ethernet frame. @param length Complete frame length. */ +void net_packet_input(const void *input, uint32_t length) +{ + net_packet_input_on(netdev_next(0), input, length); +} + +/** @brief Forward a compatibility ICMPv6 error without a link filter. + * @param packet Quote. @param length Bytes. @param error Errno. @param mtu MTU. */ +void net_ipv6_socket_error(const uint8_t *packet, uint32_t length, int error, uint32_t mtu) +{ + net_ipv6_socket_error_on(0, packet, length, error, mtu); +} +/** @brief Wake raw endpoints explicitly bound to a removed NIC. + * @param ifindex Removed interface index. */ +void net_packet_device_detached(uint32_t ifindex) +{ + for (unsigned i = 0; i < PACKET_LIMIT; ++i) { + struct packet_socket *socket = sockets[i]; + if (socket && ((uint32_t)socket->ifindex == ifindex || socket->scope6 == ifindex || + socket->remote_scope6 == ifindex)) socket->error = LINUX_ENETDOWN; + } + kernel_poll_readiness_notify(); +} diff --git a/net/socket/udp.c b/net/socket/udp.c index 7715f502..4a091e94 100644 --- a/net/socket/udp.c +++ b/net/socket/udp.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -124,7 +126,7 @@ static int bind_port(struct udp_socket *s, uint32_t ip, uint16_t port) if (s->bound) return -LINUX_EINVAL; struct reliefos_net_config config; net_get_config(&config); - if (ip && (ip >> 24) != 127 && ip != config.local_ip) return -LINUX_EADDRNOTAVAIL; + if (ip && !net_interface_for_ipv4(ip)) return -LINUX_EADDRNOTAVAIL; struct task *task = sched_current_task(); if (port && port < 1024 && !(task->cap_effective & (1ULL << CAP_NET_BIND_SERVICE))) return -LINUX_EACCES; @@ -135,7 +137,8 @@ static int bind_port(struct udp_socket *s, uint32_t ip, uint16_t port) for (struct udp_socket *other = sockets; other; other = other->registry_next) { if (other == s || !other->bound || other->port != candidate || - (s->reuse && other->reuse)) continue; + (s->reuse && other->reuse) || + (s->ifindex && other->ifindex && s->ifindex != other->ifindex)) continue; if (udp_ipv4_overlap(s, ip, other)) conflict = true; if (s->ipv6 && other->ipv6 && !udp6_mapped(&s->local6) && !udp6_mapped(&other->local6) && @@ -292,7 +295,9 @@ static int udp6_bind(struct udp_socket *s, const struct sockaddr_in6 *address) if (s->bound) return -LINUX_EINVAL; const struct in6_addr *ip = &address->sin6_addr; if (udp6_mapped(ip) && s->v6only) return -LINUX_EINVAL; - if (address->sin6_scope_id > 2) return -LINUX_ENODEV; + if (address->sin6_scope_id && !net_interface_exists(address->sin6_scope_id)) return -LINUX_ENODEV; + if (address->sin6_scope_id && s->ifindex && address->sin6_scope_id != (uint32_t)s->ifindex) + return -LINUX_EINVAL; if (ip->s6_addr[0] == 0xfe && (ip->s6_addr[1] & 0xc0) == 0x80 && !address->sin6_scope_id) return -LINUX_EINVAL; if (!udp6_any(ip) && !udp6_mapped(ip) && ip->s6_addr[0] != 0xff) { @@ -333,17 +338,20 @@ static int udp6_send(struct udp_socket *s, const void *data, uint32_t length, if (!s->bound) { int ret = bind_port(s, 0, 0); if (ret) return ret; } struct reliefos_net_config config; net_get_config(&config); - if ((ip >> 24) == 127 || (ip && ip == config.local_ip)) { - if (s->ifindex && s->ifindex != 1) return -LINUX_ENETUNREACH; + if ((ip >> 24) == 127 || (ip && net_interface_for_ipv4(ip))) { + uint32_t local_index = net_interface_for_ipv4(ip); + if (s->ifindex && (uint32_t)s->ifindex != local_index) return -LINUX_ENETUNREACH; loopback_bytes += length + 28; ++loopback_packets; - net_udp_input(s->local ? s->local : ip, ip, s->port, port, data, length, 1); + net_udp_input(s->local ? s->local : ip, ip, s->port, port, data, length, (int)local_index); return (int)length; } if (s->ifindex == 1) return -LINUX_ENETUNREACH; - int ret = net_ipv4_send_udp(s->local, ip, s->port, port, data, length); + int ret = net_ipv4_send_udp_on((uint32_t)s->ifindex, s->local, ip, s->port, port, data, length); return ret < 0 ? ret : (int)length; } if (udp6_mapped(&s->local6)) return -LINUX_ENETUNREACH; + if (to.sin6_scope_id && s->ifindex && to.sin6_scope_id != (uint32_t)s->ifindex) + return -LINUX_EINVAL; uint32_t scope = to.sin6_scope_id ? to.sin6_scope_id : (uint32_t)s->ifindex; if (!scope && to.sin6_addr.s6_addr[0] == 0xff) scope = s->multicast_if; struct net_ipv6_path path; @@ -394,15 +402,16 @@ int task_udp_send(struct task_file *file, const void *data, uint32_t length, if (!s->bound) { int ret = bind_port(s, 0, 0); if (ret < 0) return ret; } struct reliefos_net_config config; net_get_config(&config); - if ((ip >> 24) == 127 || (ip && ip == config.local_ip)) { - if (s->ifindex && s->ifindex != 1) return -LINUX_ENETUNREACH; + if ((ip >> 24) == 127 || (ip && net_interface_for_ipv4(ip))) { + uint32_t local_index = net_interface_for_ipv4(ip); + if (s->ifindex && (uint32_t)s->ifindex != local_index) return -LINUX_ENETUNREACH; loopback_bytes += length + 28; ++loopback_packets; - net_udp_input(s->local ? s->local : ip, ip, s->port, port, data, length, 1); + net_udp_input(s->local ? s->local : ip, ip, s->port, port, data, length, (int)local_index); return (int)length; } if (s->ifindex == 1) return -LINUX_ENETUNREACH; - int ret = net_ipv4_send_udp(s->local, ip, s->port, port, data, length); + int ret = net_ipv4_send_udp_on((uint32_t)s->ifindex, s->local, ip, s->port, port, data, length); return ret < 0 ? ret : (int)length; } @@ -576,7 +585,7 @@ static int udp6_option(struct udp_socket *s, bool set, uint32_t option, __builtin_memcpy(&group, (void *)(uintptr_t)pointer, sizeof(group)); if (group.ipv6mr_multiaddr.s6_addr[0] != 0xff || group.ipv6mr_ifindex < 0) return -LINUX_EINVAL; uint32_t ifindex = group.ipv6mr_ifindex ? (uint32_t)group.ipv6mr_ifindex : s->multicast_if; - if (!ifindex) ifindex = s->ifindex ? (uint32_t)s->ifindex : 2; + if (!ifindex) ifindex = s->ifindex ? (uint32_t)s->ifindex : netdev_next(0); unsigned slot = UDP6_GROUP_LIMIT, empty = UDP6_GROUP_LIMIT; for (unsigned i = 0; i < UDP6_GROUP_LIMIT; ++i) { if (!s->groups[i].used && empty == UDP6_GROUP_LIMIT) empty = i; @@ -603,7 +612,7 @@ static int udp6_option(struct udp_socket *s, bool set, uint32_t option, case IPV6_V6ONLY: if (s->bound) return -LINUX_EINVAL; s->v6only = value != 0; break; case IPV6_UNICAST_HOPS: if (value < -1 || value > 255) return -LINUX_EINVAL; s->unicast_hops = value; break; case IPV6_MULTICAST_HOPS: if (value < -1 || value > 255) return -LINUX_EINVAL; s->multicast_hops = value; break; - case IPV6_MULTICAST_IF: if (value < 0 || value > 2) return -LINUX_ENODEV; s->multicast_if = (uint32_t)value; break; + case IPV6_MULTICAST_IF: if (value < 0 || (value && !net_interface_exists((uint32_t)value))) return -LINUX_ENODEV; s->multicast_if = (uint32_t)value; break; case IPV6_MULTICAST_LOOP: if (value != 0 && value != 1) return -LINUX_EINVAL; s->multicast_loop = value != 0; break; case IPV6_RECVPKTINFO: s->recv_pktinfo = value != 0; break; case IPV6_RECVHOPLIMIT: s->recv_hoplimit = value != 0; break; @@ -631,7 +640,7 @@ static int udp6_option(struct udp_socket *s, bool set, uint32_t option, case IPV6_MTU: { struct net_ipv6_path path; if (!s->connected || udp6_mapped(&s->remote6)) return -LINUX_ENOTCONN; - int ret = net_ipv6_route(&s->remote6, s->remote_scope6, &s->local6, &path); + int ret = net_ipv6_route(&s->remote6, s->remote_scope6 ? s->remote_scope6 : (uint32_t)s->ifindex, &s->local6, &path); if (ret < 0) return ret; value = (int)path.mtu; break; @@ -712,6 +721,8 @@ int64_t syscall_udp(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, if (number == __NR_bind) return udp6_bind(s, &address); if (udp6_mapped(&address.sin6_addr) && s->v6only) return -LINUX_ENETUNREACH; if (!s->bound && (ret = bind_port(s, 0, 0)) < 0) return ret; + if (address.sin6_scope_id && s->ifindex && address.sin6_scope_id != (uint32_t)s->ifindex) + return -LINUX_EINVAL; s->remote6 = address.sin6_addr; s->remote_scope6 = address.sin6_scope_id; s->remote = udp6_mapped(&address.sin6_addr) ? udp6_unmap(&address.sin6_addr) : 0; s->remote_port = swap16(address.sin6_port); s->connected = true; @@ -740,7 +751,7 @@ int64_t syscall_udp(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, address.sin6_addr = udp6_map(s->remote >> 24 == 127 ? 0x7f000001 : config.local_ip); } else { struct net_ipv6_path path; - if (!net_ipv6_route(&s->remote6, s->remote_scope6, &s->local6, &path)) address.sin6_addr = path.source; + if (!net_ipv6_route(&s->remote6, s->remote_scope6 ? s->remote_scope6 : (uint32_t)s->ifindex, &s->local6, &path)) address.sin6_addr = path.source; } } return udp6_export(a1, a2, &address); @@ -748,7 +759,13 @@ int64_t syscall_udp(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, struct reliefos_net_config config; net_get_config(&config); uint32_t local = s->local; - if (!local && s->connected) local = s->remote >> 24 == 127 ? 0x7f000001 : config.local_ip; + if (!local && s->connected) { + if (net_interface_for_ipv4(s->remote)) local = s->remote; + else { + struct net_ipv4_path path; + if (!net_ipv4_route_lookup(s->remote, 0, (uint32_t)s->ifindex, &path)) local = path.source; + } + } return export_address(a1, a2, number == __NR_getsockname ? local : s->remote, number == __NR_getsockname ? s->port : s->remote_port); } @@ -764,27 +781,10 @@ int64_t syscall_udp(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, } if ((int32_t)length < 0) return -LINUX_EINVAL; if (a2 == SO_BINDTODEVICE) { - if (set) { - struct task *task = sched_current_task(); - if (!(task->cap_effective & (1ULL << CAP_NET_RAW))) return -LINUX_EPERM; - char name[16] = {0}; - uint32_t size = length < sizeof(name) - 1 ? length : sizeof(name) - 1; - int prep=socket_user_prepare(task,a3,size,false);if(prep)return prep; - if (size && !user_range_ok(a3, size)) return -LINUX_EFAULT; - if (size) __builtin_memcpy(name, (void *)(uintptr_t)a3, size); - int index = !name[0] ? 0 : !__builtin_strcmp(name, "eth0") ? 2 : !__builtin_strcmp(name, "lo") ? 1 : -1; - if (index < 0) return -LINUX_ENODEV; - s->ifindex = index; - return 0; - } - const char *name = s->ifindex == 2 ? "eth0" : s->ifindex == 1 ? "lo" : ""; - uint32_t size = s->ifindex == 2 ? 5 : s->ifindex == 1 ? 3 : 0; - if (size && length < 16) return -LINUX_EINVAL; - int prep=socket_user_prepare(task,a3,size,true);if(prep)return prep; - if (size && !user_range_writable(a3, size)) return -LINUX_EFAULT; - if (size) __builtin_memcpy((void *)(uintptr_t)a3, name, size); - *(uint32_t *)(uintptr_t)a4 = size; - return 0; + uint32_t index = (uint32_t)s->ifindex; + int result = net_interface_device_option(set, a3, a4, &index); + if (!result && set) s->ifindex = (int)index; + return result; } int value = 0; if (a2 == SO_RCVTIMEO || a2 == SO_RCVTIMEO_NEW) { @@ -849,12 +849,15 @@ int64_t syscall_udp(uint64_t number, uint64_t a0, uint64_t a1, uint64_t a2, * @param source Original local address. @param destination Original peer address. * @param source_port Original local port. @param destination_port Original peer port. * @param error Positive errno. @param mtu Reported path MTU or zero. */ -void net_udp6_error(const struct in6_addr *source,const struct in6_addr *destination, +void net_udp6_error_on(uint32_t ifindex, const struct in6_addr *source,const struct in6_addr *destination, uint16_t source_port,uint16_t destination_port,int error,uint32_t mtu) { (void)mtu; for (struct udp_socket *s = sockets; s; s = s->registry_next) { if (!s->ipv6 || !s->bound || s->port != source_port || + (ifindex && ((s->ifindex && (uint32_t)s->ifindex != ifindex) || + (s->scope6 && s->scope6 != ifindex) || + (s->remote_scope6 && s->remote_scope6 != ifindex))) || (!udp6_any(&s->local6) && __builtin_memcmp(&s->local6, source, sizeof(*source))) || (s->connected && (s->remote_port != destination_port || __builtin_memcmp(&s->remote6, destination, sizeof(*destination))))) continue; @@ -930,3 +933,24 @@ void net_loopback_proc_emit(struct text_stream *s) text_unsigned(s, loopback_packets); text_string(s, " 0 0 0 0 0 0\n"); } + +/** @brief Deliver a compatibility ICMPv6 quote without an explicit ingress filter. + * @param source Quoted local address. @param destination Quoted peer. + * @param source_port Local port. @param destination_port Peer port. + * @param error Positive errno. @param mtu Reported MTU. */ +void net_udp6_error(const struct in6_addr *source, const struct in6_addr *destination, + uint16_t source_port, uint16_t destination_port, int error, uint32_t mtu) +{ + net_udp6_error_on(0, source, destination, source_port, destination_port, error, mtu); +} + +/** @brief Wake only UDP endpoints explicitly tied to a removed interface. + * @param ifindex Removed non-reused interface index. */ +void net_udp_device_detached(uint32_t ifindex) +{ + for (struct udp_socket *s = sockets; s; s = s->registry_next) { + if ((uint32_t)s->ifindex == ifindex || s->scope6 == ifindex || s->remote_scope6 == ifindex) + s->error = LINUX_ENETDOWN; + } + kernel_poll_readiness_notify(); +} diff --git a/net/tcp/input.c b/net/tcp/input.c index 38efc347..5144b38c 100644 --- a/net/tcp/input.c +++ b/net/tcp/input.c @@ -8,7 +8,7 @@ * @param dst_port Local destination port. * @return Matching live socket, or null when the tuple is not registered. */ -static struct net_socket *net_socket_match(uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, +static struct net_socket *net_socket_match(uint32_t ifindex, uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port); /** @@ -75,7 +75,8 @@ void net_tcp6_input(const struct in6_addr *source, const struct in6_addr *destin uint8_t flags = tcp[13]; struct net_socket *match = NULL, *listener = NULL; for (struct net_socket *s = net_sockets; s; s = s->registry_next) { - if (!s->used || !s->ipv6 || s->mapped || s->local_port != destination_port) continue; + if (!s->used || !s->ipv6 || s->mapped || s->local_port != destination_port || + (s->bound_ifindex && s->bound_ifindex != ifindex)) continue; if (s->state == NET_TCP_LISTEN) { static const struct in6_addr zero = {0}; if ((!s->scope6 || s->scope6 == ifindex) && @@ -93,7 +94,7 @@ void net_tcp6_input(const struct in6_addr *source, const struct in6_addr *destin } if (!match) { if (!(flags & TCP_FLAG_RST) && destination->s6_addr[0] != 0xff) { - struct net_socket reply = {.ipv6=true,.local6=*destination,.remote6=*source, + struct net_socket reply = {.ifindex=ifindex,.ipv6=true,.local6=*destination,.remote6=*source, .local_port=destination_port,.remote_port=source_port,.unicast_hops=-1, .remote_scope6=source->s6_addr[0] == 0xfe && (source->s6_addr[1] & 0xc0) == 0x80 ? ifindex : 0}; uint32_t reply_seq = (flags & TCP_FLAG_ACK) ? ack : 0; @@ -148,7 +149,7 @@ static bool net_tcp_segment_acceptable(const struct net_socket *socket, * @param tcp_wait Optional synchronous legacy TCP exchange waiter. * @note Socket dispatch is serialized by the network execution transaction. */ -void net_handle_tcp(const uint8_t *ip, uint32_t total_len, +void net_handle_tcp_on(uint32_t ifindex, const uint8_t *ip, uint32_t total_len, uint32_t ihl, uint32_t src_ip, const uint8_t *source_mac, struct net_tcp_wait *tcp_wait) @@ -181,19 +182,20 @@ void net_handle_tcp(const uint8_t *ip, uint32_t total_len, window = net_get_u16(tcp + 14); payload_len = tcp_len - hdr_len; uint32_t dst_ip = net_get_u32(ip + 16); - struct net_socket *matched = net_socket_match(src_ip, dst_ip, src_port, dst_port); + struct net_socket *matched = net_socket_match(ifindex, src_ip, dst_ip, src_port, dst_port); bool listening = false; if (!matched && (flags & (TCP_FLAG_SYN | TCP_FLAG_ACK | TCP_FLAG_RST)) == TCP_FLAG_SYN) { static const struct in6_addr any = {0}; static const uint8_t mapped_prefix[12] = {0,0,0,0,0,0,0,0,0,0,255,255}; for (struct net_socket *listener = net_sockets; listener; listener = listener->registry_next) { if (!listener->used || listener->state != NET_TCP_LISTEN || listener->local_port != dst_port || - (listener->local_ip && listener->local_ip != dst_ip)) continue; + (listener->local_ip && listener->local_ip != dst_ip) || + (listener->bound_ifindex && listener->bound_ifindex != ifindex)) continue; if (listener->ipv6 && (listener->v6only || (__builtin_memcmp(&listener->local6, &any, sizeof(any)) && __builtin_memcmp(listener->local6.s6_addr, mapped_prefix, sizeof(mapped_prefix))))) continue; listening = true; - (void)net_tcp_passive4(listener, src_ip, dst_ip, src_port, source_mac, seq, window, + (void)net_tcp_passive4_on(ifindex, listener, src_ip, dst_ip, src_port, source_mac, seq, window, net_tcp6_syn_mss(tcp, hdr_len)); break; } @@ -202,21 +204,21 @@ void net_handle_tcp(const uint8_t *ip, uint32_t total_len, if (matched && (flags & TCP_FLAG_SYN)) matched->peer_mss = net_tcp6_syn_mss(tcp, hdr_len); net_socket_handle_tcp(matched, seq, ack, window, flags, tcp + hdr_len, payload_len); } else if (!listening && !(flags & TCP_FLAG_RST) && source_mac) { - struct net_socket reply = {.local_ip=dst_ip,.remote_ip=src_ip,.local_port=dst_port,.remote_port=src_port}; + struct net_socket reply = {.ifindex=ifindex,.local_ip=dst_ip,.remote_ip=src_ip,.local_port=dst_port,.remote_port=src_port}; net_memcpy(reply.dst_mac, source_mac, 6); uint32_t response_seq = (flags & TCP_FLAG_ACK) ? ack : 0; uint32_t response_ack = seq + payload_len + !!(flags & TCP_FLAG_SYN) + !!(flags & TCP_FLAG_FIN); (void)net_send_tcp_socket(&reply, response_seq, response_ack, (flags & TCP_FLAG_ACK) ? TCP_FLAG_RST : TCP_FLAG_RST | TCP_FLAG_ACK, NULL, 0); } else if (!listening && !(flags & TCP_FLAG_RST) && - ((src_ip >> 24) == 127 || (src_ip && src_ip == net_config.local_ip))) { - struct net_socket reply = {.local_ip=dst_ip,.remote_ip=src_ip,.local_port=dst_port,.remote_port=src_port}; + ((src_ip >> 24) == 127 || (src_ip && net_interface_for_ipv4(src_ip)))) { + struct net_socket reply = {.ifindex=ifindex,.local_ip=dst_ip,.remote_ip=src_ip,.local_port=dst_port,.remote_port=src_port}; uint32_t response_seq = (flags & TCP_FLAG_ACK) ? ack : 0; uint32_t response_ack = seq + payload_len + !!(flags & TCP_FLAG_SYN) + !!(flags & TCP_FLAG_FIN); (void)net_send_tcp_socket(&reply, response_seq, response_ack, (flags & TCP_FLAG_ACK) ? TCP_FLAG_RST : TCP_FLAG_RST | TCP_FLAG_ACK, NULL, 0); } - if (!tcp_wait) { + if (!tcp_wait || (tcp_wait->ifindex && tcp_wait->ifindex != ifindex)) { return; } if (tcp_wait->src_port && src_port != tcp_wait->src_port) { @@ -280,13 +282,14 @@ void net_handle_tcp(const uint8_t *ip, uint32_t total_len, * @param dst_port Local destination port. * @return Matching live socket, or null when the tuple is not registered. */ -static struct net_socket *net_socket_match(uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, +static struct net_socket *net_socket_match(uint32_t ifindex, uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port) { for (struct net_socket *socket = net_sockets; socket; socket = socket->registry_next) { if (!socket->used || socket->state == RELIEFOS_NET_TCP_CLOSED || - socket->state == NET_TCP_LISTEN || (socket->ipv6 && !socket->mapped)) { + socket->state == NET_TCP_LISTEN || (socket->ipv6 && !socket->mapped) || + (socket->bound_ifindex && socket->bound_ifindex != ifindex)) { continue; } if (socket->remote_ip == src_ip && (!socket->local_ip || socket->local_ip == dst_ip) && @@ -459,3 +462,12 @@ static void net_socket_handle_tcp(struct net_socket *socket, uint32_t seq, } } } + +/** @brief Process TCP for legacy callers, inferring local delivery from a missing MAC. + * @param ip Packet. @param total_len Bytes. @param ihl Header. @param src_ip Peer. + * @param source_mac Source MAC, NULL for local. @param wait Optional waiter. */ +void net_handle_tcp(const uint8_t *ip, uint32_t total_len, uint32_t ihl, uint32_t src_ip, + const uint8_t *source_mac, struct net_tcp_wait *wait) +{ + net_handle_tcp_on(source_mac ? netdev_next(0) : 1, ip, total_len, ihl, src_ip, source_mac, wait); +} diff --git a/net/tcp/output.c b/net/tcp/output.c index f0f4422f..7d5a6ccb 100644 --- a/net/tcp/output.c +++ b/net/tcp/output.c @@ -1,6 +1,6 @@ #include "../internal.h" -struct tcp_loop_frame { uint32_t length; uint8_t bytes[NET_FRAME_MAX]; }; +struct tcp_loop_frame { uint32_t length, ifindex; uint8_t bytes[NET_FRAME_MAX]; }; static struct tcp_loop_frame tcp_loop_frames[32]; static uint32_t tcp_loop_head, tcp_loop_count; @@ -16,7 +16,9 @@ void net_tcp_loop_service(void) struct tcp_loop_frame frame = tcp_loop_frames[tcp_loop_head]; tcp_loop_head = (tcp_loop_head + 1u) % 32u; --tcp_loop_count; const uint8_t *ip = frame.bytes + 14; - net_handle_tcp(ip, frame.length - 14u, 20u, net_get_u32(ip + 12), NULL, NULL); + if (net_interface_exists(frame.ifindex) && net_interface_is_up(frame.ifindex)) + net_handle_tcp_on(frame.ifindex, ip, frame.length - 14u, 20u, + net_get_u32(ip + 12), NULL, NULL); } } @@ -77,7 +79,7 @@ uint32_t net_socket_select_window(const struct net_socket *socket) * @note The receiver window is read from the matching stable socket under the * network execution transaction, then published only after successful transmit. */ -int net_send_tcp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, +static int net_send_tcp_to_mac_on(uint32_t ifindex, const uint8_t *dst_mac, uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, uint32_t seq, uint32_t ack, uint8_t flags, @@ -97,7 +99,7 @@ int net_send_tcp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, tcp_len = (uint16_t)(tcp_header_len + payload_len); total_len = (uint16_t)(20u + tcp_len); net_memzero(frame, 34u + tcp_header_len + payload_len); - net_write_eth(frame, dst_mac, ETH_TYPE_IPV4); + net_write_eth_on(ifindex, frame, dst_mac, ETH_TYPE_IPV4); ip[0] = 0x45; ip[1] = 0; net_put_u16(ip + 2, total_len); @@ -119,7 +121,7 @@ int net_send_tcp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, struct net_socket *receiver = 0; for (struct net_socket *s = net_sockets; s; s = s->registry_next) { if (s->used && s->local_port == src_port && s->remote_port == dst_port && - s->remote_ip == dst_ip) { + s->remote_ip == dst_ip && s->local_ip == src_ip) { receiver = s; window = net_socket_select_window(s); break; @@ -138,13 +140,14 @@ int net_send_tcp_to_mac(const uint8_t *dst_mac, uint32_t src_ip, } net_put_u16(tcp + 16, net_tcp_checksum(src_ip, dst_ip, tcp, tcp_len)); int result; - if ((dst_ip >> 24) == 127 || (dst_ip && dst_ip == net_config.local_ip)) { + if ((dst_ip >> 24) == 127 || (dst_ip && net_interface_for_ipv4(dst_ip))) { if (tcp_loop_count == 32) return -LINUX_ENOBUFS; struct tcp_loop_frame *queued = &tcp_loop_frames[(tcp_loop_head + tcp_loop_count) % 32u]; queued->length = 14u + total_len; + queued->ifindex = ifindex; net_memcpy(queued->bytes, frame, queued->length); ++tcp_loop_count; result = (int)queued->length; - } else result = net_interface_up ? e1000_send(frame, 14u + total_len) : -LINUX_ENETDOWN; + } else result = net_interface_is_ready(ifindex) ? netdev_send(ifindex, frame, 14u + total_len) : -LINUX_ENETDOWN; if (result >= 0 && receiver && (flags & TCP_FLAG_ACK)) { receiver->rx_window_ack = ack; receiver->rx_window = window; @@ -174,13 +177,35 @@ uint32_t net_socket_mss(const struct net_socket *socket) int net_send_tcp_socket(struct net_socket *socket, uint32_t seq, uint32_t ack, uint8_t flags, const uint8_t *payload, uint32_t length) { - if (!socket->ipv6 || socket->mapped) - return net_send_tcp_to_mac(socket->dst_mac, socket->local_ip, socket->remote_ip, - socket->local_port, socket->remote_port, seq, ack, flags, payload, length); + if (socket->resolving_neighbor) return -LINUX_EAGAIN; + if (!socket->ipv6 || socket->mapped) { + bool local = net_interface_for_ipv4(socket->remote_ip) != 0; + if (!local) { + struct net_ipv4_path path; + int result = net_ipv4_route_lookup(socket->remote_ip, socket->local_ip, + socket->bound_ifindex, &path); + if (result < 0) return result; + if (socket->ifindex != path.ifindex || socket->next_hop != path.next_hop || + net_mac_is_zero(socket->dst_mac)) { + socket->resolving_neighbor = true; + result = net_resolve_mac_on(path.ifindex, path.next_hop, 1000, socket->dst_mac); + socket->resolving_neighbor = false; + if (result < 0) return result; + } + socket->ifindex = path.ifindex; + socket->next_hop = path.next_hop; + } + return net_send_tcp_to_mac_on(local ? net_interface_for_ipv4(socket->remote_ip) : socket->ifindex, socket->dst_mac, + socket->local_ip, socket->remote_ip, socket->local_port, socket->remote_port, + seq, ack, flags, payload, length); + } + if (socket->remote_scope6 && socket->bound_ifindex && socket->remote_scope6 != socket->bound_ifindex) + return -LINUX_EINVAL; struct net_ipv6_path path; - int ret = net_ipv6_route(&socket->remote6, socket->remote_scope6, &socket->local6, &path); + int ret = net_ipv6_route(&socket->remote6, socket->remote_scope6 ? socket->remote_scope6 : socket->bound_ifindex, &socket->local6, &path); if (ret < 0) return ret; socket->mtu6 = path.mtu; + socket->ifindex = path.ifindex; uint32_t header = (flags & TCP_FLAG_SYN) ? 24u : 20u; if (length > net_socket_mss(socket) || (length && !payload)) return -LINUX_EMSGSIZE; uint8_t tcp[NET_TCP_MSS + 24]; @@ -205,3 +230,19 @@ int net_send_tcp_socket(struct net_socket *socket, uint32_t seq, uint32_t ack, } return ret; } + +/** @brief Send a legacy TCP segment with normal route selection. + * @param mac Peer MAC. @param source Local IP. @param destination Peer IP. + * @param source_port Local port. @param destination_port Peer port. + * @param seq Sequence. @param ack Acknowledgment. @param flags Control bits. + * @param data Payload. @param length Bytes. @return Driver result or negative errno. */ +int net_send_tcp_to_mac(const uint8_t *mac, uint32_t source, uint32_t destination, + uint16_t source_port, uint16_t destination_port, uint32_t seq, uint32_t ack, + uint8_t flags, const uint8_t *data, uint32_t length) +{ + struct net_ipv4_path path; + int result = net_ipv4_route_lookup(destination, source, 0, &path); + if (result < 0) return result; + return net_send_tcp_to_mac_on(path.ifindex, mac, source, destination, source_port, + destination_port, seq, ack, flags, data, length); +} diff --git a/net/tcp/socket.c b/net/tcp/socket.c index 732a85c1..2625d5ce 100644 --- a/net/tcp/socket.c +++ b/net/tcp/socket.c @@ -397,6 +397,7 @@ static int tcp_bind_port(struct net_socket *socket, uint32_t ip, uint16_t port) for (struct net_socket *other = net_sockets; other; other = other->registry_next) { if (other == socket || !other->used || !other->bound || other->listener || other->local_port != candidate) continue; if (socket->reuse && other->reuse && other->state != NET_TCP_LISTEN) continue; + if (socket->bound_ifindex && other->bound_ifindex && socket->bound_ifindex != other->bound_ifindex) continue; if (tcp_uses_ipv4(socket) && tcp_uses_ipv4(other) && (!ip || !other->local_ip || ip == other->local_ip)) conflict = true; if (socket->ipv6 && other->ipv6 && !tcp6_mapped(&socket->local6) && !tcp6_mapped(&other->local6) && (tcp6_any(&socket->local6) || tcp6_any(&other->local6) || @@ -533,7 +534,7 @@ static void net_socket_service(struct net_socket *socket) uint32_t flight_limit; uint32_t unsent; uint32_t send_window; - if (!socket || !socket->used) return; + if (!socket || !socket->used || socket->resolving_neighbor) return; now = time_uptime_ms(); if (socket->state == RELIEFOS_NET_TCP_SYN_SENT || socket->state == NET_TCP_SYN_RECEIVED) { if (socket->syn_deadline && now >= socket->syn_deadline) { @@ -753,10 +754,18 @@ static uint32_t net_socket_connect_ip(struct net_socket *socket, if (!socket || !remote_ip || !remote_port) { return RELIEFOS_NET_STATUS_BAD_ARGUMENT; } - if ((remote_ip >> 24) == 127 || (remote_ip && remote_ip == net_config.local_ip)) net_memzero(dst_mac, sizeof(dst_mac)); + struct net_ipv4_path path; + ret = net_ipv4_route_lookup(remote_ip, socket->local_ip, socket->bound_ifindex, &path); + if (ret < 0) { + socket->error = -ret; + return RELIEFOS_NET_STATUS_NO_ADDRESS; + } + socket->ifindex = path.ifindex; + socket->next_hop = path.next_hop; + if (net_interface_for_ipv4(remote_ip)) net_memzero(dst_mac, sizeof(dst_mac)); else { - arp_ip = net_route_arp_ip(remote_ip); - ret = net_resolve_mac(arp_ip, timeout_ms, dst_mac); + arp_ip = path.next_hop; + ret = net_resolve_mac_on(path.ifindex, arp_ip, timeout_ms, dst_mac); if (ret < 0) return ret == -1 ? RELIEFOS_NET_STATUS_TX_FAILED : RELIEFOS_NET_STATUS_ARP_TIMEOUT; } @@ -770,7 +779,7 @@ static uint32_t net_socket_connect_ip(struct net_socket *socket, socket->fin_pending = false; socket->fin_sent = false; socket->fin_acked = false; - if (!socket->local_ip) socket->local_ip = (remote_ip >> 24) == 127 ? 0x7f000001 : net_config.local_ip; + if (!socket->local_ip) socket->local_ip = path.source; socket->remote_ip = remote_ip; socket->remote_port = remote_port; socket->local_seq = 0x4c4e0000u ^ (net_sequence++ << 8) ^ @@ -1306,10 +1315,10 @@ int net_socket_connect_fd(int32_t handle, uint32_t ip, uint16_t port, bool nonbl if (!s) return -LINUX_EBADF; if (s->state == RELIEFOS_NET_TCP_SYN_SENT) return -LINUX_EALREADY; if (s->state != RELIEFOS_NET_TCP_CLOSED) return -LINUX_EISCONN; - bool local = (ip >> 24) == 127 || (ip && ip == net_config.local_ip); - if (!local && !e1000_is_ready()) return -LINUX_ENETDOWN; if (!ip || !port) return -LINUX_EINVAL; - if (!local && net_ensure_ipv4_config(3000, 0) != RELIEFOS_NET_STATUS_OK) return -LINUX_ENETUNREACH; + struct net_ipv4_path path; + int route = net_ipv4_route_lookup(ip, s->local_ip, s->bound_ifindex, &path); + if (route < 0) return route; s->error = 0; uint32_t status = net_socket_connect_ip(s, ip, port, 10000, nonblock); if (s->state == RELIEFOS_NET_TCP_SYN_SENT) return -LINUX_EINPROGRESS; @@ -1346,14 +1355,16 @@ int net_socket_bind6_fd(int32_t handle, const struct sockaddr_in6 *address) struct net_socket *s = net_socket_find(handle, 0, 1); if (!s) return -LINUX_EBADF; if (!address || s->bound || s->state != RELIEFOS_NET_TCP_CLOSED) return -LINUX_EINVAL; - if (address->sin6_scope_id > 2) return -LINUX_ENODEV; + if (address->sin6_scope_id && !net_interface_exists(address->sin6_scope_id)) return -LINUX_ENODEV; + if (address->sin6_scope_id && s->bound_ifindex && address->sin6_scope_id != s->bound_ifindex) + return -LINUX_EINVAL; bool mapped = tcp6_mapped(&address->sin6_addr); if (mapped && s->ipv6 && s->v6only) return -LINUX_EINVAL; uint32_t ip = mapped ? tcp6_unmap(&address->sin6_addr) : 0; if (mapped) { struct reliefos_net_config config; net_get_config(&config); - if (ip && (ip >> 24) != 127 && ip != config.local_ip) return -LINUX_EADDRNOTAVAIL; + if (ip && !net_interface_for_ipv4(ip)) return -LINUX_EADDRNOTAVAIL; } else if (!tcp6_any(&address->sin6_addr)) { if (!s->ipv6) return -LINUX_EAFNOSUPPORT; if (address->sin6_addr.s6_addr[0] == 0xfe && (address->sin6_addr.s6_addr[1] & 0xc0) == 0x80 && !address->sin6_scope_id) @@ -1395,12 +1406,14 @@ int net_socket_connect6_fd(int32_t handle, const struct sockaddr_in6 *address, b s->mapped = true; s->remote6 = address->sin6_addr; return net_socket_connect_fd(handle, tcp6_unmap(&address->sin6_addr), port, nonblock); } + if (address->sin6_scope_id && s->bound_ifindex && address->sin6_scope_id != s->bound_ifindex) + return -LINUX_EINVAL; if (tcp6_mapped(&s->local6)) return -LINUX_ENETUNREACH; struct net_ipv6_path path; - int ret = net_ipv6_route(&address->sin6_addr, address->sin6_scope_id, &s->local6, &path); + int ret = net_ipv6_route(&address->sin6_addr, address->sin6_scope_id ? address->sin6_scope_id : s->bound_ifindex, &s->local6, &path); if (ret < 0) return ret; if (!s->bound) { ret = tcp_bind_port(s, 0, 0); if (ret < 0) return ret; } - s->local6 = path.source; s->remote6 = address->sin6_addr; s->remote_scope6 = address->sin6_scope_id; + s->ifindex = path.ifindex; s->local6 = path.source; s->remote6 = address->sin6_addr; s->remote_scope6 = address->sin6_scope_id; s->mtu6 = path.mtu; s->remote_port = port; s->error = 0; s->mapped = false; s->state = RELIEFOS_NET_TCP_SYN_SENT; s->status = RELIEFOS_NET_STATUS_TCP_TIMEOUT; s->local_seq = 0x4c4e0000u ^ (net_sequence++ << 8) ^ (uint32_t)time_uptime_ms(); @@ -1493,11 +1506,12 @@ int net_socket_reuse(int32_t handle, bool set, int *value) * @param source Original local address. @param destination Original peer address. * @param source_port Local port. @param destination_port Peer port. * @param error Positive errno. @param mtu Reported path MTU or zero. */ -void net_tcp6_error(const struct in6_addr *source,const struct in6_addr *destination, +void net_tcp6_error_on(uint32_t ifindex, const struct in6_addr *source,const struct in6_addr *destination, uint16_t source_port,uint16_t destination_port,int error,uint32_t mtu) { for (struct net_socket *s = net_sockets; s; s = s->registry_next) { if (!s->used || !s->ipv6 || s->mapped || s->local_port != source_port || s->remote_port != destination_port || + (ifindex && s->ifindex && s->ifindex != ifindex) || __builtin_memcmp(&s->local6, source, sizeof(*source)) || __builtin_memcmp(&s->remote6, destination, sizeof(*destination))) continue; if (mtu) { if (mtu < 1280) mtu = 1280; if (!s->mtu6 || mtu < s->mtu6) s->mtu6 = mtu; } @@ -1578,6 +1592,7 @@ struct net_socket *net_tcp_passive6(struct net_socket *listener,const struct in6 if (net_ipv6_route(source, scope, destination, &path) < 0) return NULL; struct net_socket *s = net_socket_alloc(listener->owner_pid, listener->owner_uid); if (!s) return NULL; + s->ifindex = ifindex; s->bound_ifindex = listener->bound_ifindex; s->ipv6 = true; s->v6only = listener->v6only; s->bound = true; s->listener = listener; s->local6 = *destination; s->remote6 = *source; s->remote_scope6 = scope; s->mtu6 = path.mtu; s->local_port = listener->local_port; s->remote_port = source_port; s->peer_mss = mss; @@ -1598,15 +1613,17 @@ struct net_socket *net_tcp_passive6(struct net_socket *listener,const struct in6 * @param destination Local IPv4 host order. @param source_port Peer port. * @param source_mac Borrowed next-hop MAC, null for loopback. @param seq Peer sequence. * @param window Peer window. @param mss Advertised MSS. @return Child or null on failure. */ -struct net_socket *net_tcp_passive4(struct net_socket *listener,uint32_t source,uint32_t destination, +struct net_socket *net_tcp_passive4_on(uint32_t ifindex, struct net_socket *listener,uint32_t source,uint32_t destination, uint16_t source_port,const uint8_t *source_mac, uint32_t seq,uint16_t window,uint16_t mss) { if (!listener || listener->state != NET_TCP_LISTEN || tcp_pending(listener, false) >= listener->backlog) return NULL; - if (!source_mac && (source >> 24) != 127 && source != net_config.local_ip) return NULL; + if (!source_mac && !net_interface_for_ipv4(source)) return NULL; struct net_socket *s = net_socket_alloc(listener->owner_pid, listener->owner_uid); if (!s) return NULL; + s->ifindex = ifindex; s->bound_ifindex = listener->bound_ifindex; s->ipv6 = listener->ipv6; s->mapped = listener->ipv6; s->v6only = listener->v6only; + s->next_hop = source; s->bound = true; s->listener = listener; s->local_ip = destination; s->remote_ip = source; s->local6 = tcp6_map(destination); s->remote6 = tcp6_map(source); s->local_port = listener->local_port; s->remote_port = source_port; s->peer_mss = mss; @@ -1817,3 +1834,38 @@ int net_socket_set_timeout_ticks(int32_t handle, bool send, uint64_t ticks) else socket->receive_timeout = ticks; return 0; } + +/** @brief Preserve the legacy passive-open entry point using the destination link. + * @param listener Parent. @param source Peer. @param destination Local address. + * @param source_port Peer port. @param source_mac Next-hop MAC. @param seq Sequence. + * @param window Window. @param mss MSS. @return Child or NULL. */ +struct net_socket *net_tcp_passive4(struct net_socket *listener, uint32_t source, + uint32_t destination, uint16_t source_port, const uint8_t *source_mac, + uint32_t seq, uint16_t window, uint16_t mss) +{ + uint32_t index = source_mac ? net_interface_for_ipv4(destination) : 1; + return net_tcp_passive4_on(index, listener, source, destination, source_port, + source_mac, seq, window, mss); +} +/** @brief Forward an old ICMPv6 error call without an interface filter. + * @param source Local. @param destination Peer. @param source_port Local port. + * @param destination_port Peer port. @param error Errno. @param mtu Path MTU. */ +void net_tcp6_error(const struct in6_addr *source, const struct in6_addr *destination, + uint16_t source_port, uint16_t destination_port, int error, uint32_t mtu) +{ + net_tcp6_error_on(0, source, destination, source_port, destination_port, error, mtu); +} +/** @brief Read or change a stream endpoint's bound output device. + * @param handle Socket handle. @param set Select update. @param index Input/output index. + * @return Zero or negative errno. Caller authorizes and validates user memory. */ +int net_socket_bound_device(int32_t handle, bool set, uint32_t *index) +{ + struct net_socket *socket = net_socket_find(handle, 0, 1); + if (!socket) return -LINUX_EBADF; + if (!index) return -LINUX_EINVAL; + if (set) { + if (*index && !net_interface_exists(*index)) return -LINUX_ENODEV; + socket->bound_ifindex = *index; + } else *index = socket->bound_ifindex; + return 0; +} diff --git a/tools/test_net_device.py b/tools/test_net_device.py new file mode 100644 index 00000000..fe4c813a --- /dev/null +++ b/tools/test_net_device.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"""Exercise independent NIC identities against real service admission.""" +from pathlib import Path +import os +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[1] +temporary = Path(os.environ.get("TMPDIR", str(root / "out/tmp"))) +temporary.mkdir(parents=True, exist_ok=True) +with tempfile.TemporaryDirectory(prefix="reliefnt-netdev-", dir=temporary) as work: + binary = Path(work) / "netdev" + subprocess.run(["clang", "-std=c11", "-O1", "-g", "-Wall", "-Wextra", "-Werror", + "-ffunction-sections", "-fdata-sections", "-Wl,--gc-sections", + "-fsanitize=address,undefined", "-fno-sanitize-recover=all", + "-fno-pie", "-no-pie", "-Iinclude", "-Iinclude/uapi", + "tools/tests/net_device_test.c", "kernel/device/services.c", + "kernel/device/driver_manager_phase.c", "-o", str(binary)], cwd=root, check=True) + subprocess.run([str(binary)], check=True, timeout=20) diff --git a/tools/test_net_multi_interface.py b/tools/test_net_multi_interface.py new file mode 100644 index 00000000..76701707 --- /dev/null +++ b/tools/test_net_multi_interface.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Run the two-interface ioctl and IPv4 routing fixture.""" +from pathlib import Path +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[1] +with tempfile.TemporaryDirectory(prefix="reliefnt-multi-nic-") as work: + binary = Path(work) / "net_multi_interface" + subprocess.run(["cc", "-std=c11", "-O1", "-g", "-fsanitize=address,undefined", + "-fno-sanitize-recover=all", "-fno-pie", "-no-pie", "-ffunction-sections", + "-fdata-sections", "-Wl,--gc-sections", "-Iinclude", "-Iinclude/uapi", + "tools/tests/net_multi_interface_test.c", "net/interface/state.c", + "net/interface/routes.c", "net/interface/ioctl.c", "net/utility.c", + "-o", str(binary)], cwd=ROOT, check=True) + subprocess.run([str(binary)], check=True, timeout=15) diff --git a/tools/test_net_multinic_data.py b/tools/test_net_multinic_data.py new file mode 100644 index 00000000..89ec33c6 --- /dev/null +++ b/tools/test_net_multinic_data.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Exercise IPv4 link isolation and AF_PACKET device binding with sanitizers.""" +from pathlib import Path +import os +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[1] + + +def main(): + temporary = Path(os.environ.get("TMPDIR", str(ROOT / "out/tmp"))).resolve() + temporary.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="reliefnt-multinic-data-", dir=temporary) as work: + for name in ("net_multinic_data", "net_packet"): + binary = Path(work) / name + subprocess.run(["cc", "-std=c11", "-O1", "-g", "-DRELIEFNT_HOST_TEST", + "-fsanitize=address,undefined", "-fno-sanitize-recover=all", + "-ffunction-sections", "-fdata-sections", "-Wl,--gc-sections", + "-Iinclude", "-Iinclude/uapi", f"tools/tests/{name}_test.c", + "-o", str(binary)], cwd=ROOT, check=True) + subprocess.run([str(binary)], check=True, timeout=15) + + +if __name__ == "__main__": + main() diff --git a/tools/tests/driver_modules_test.c b/tools/tests/driver_modules_test.c index d058b8f7..ef050741 100644 --- a/tools/tests/driver_modules_test.c +++ b/tools/tests/driver_modules_test.c @@ -37,6 +37,10 @@ int sound_service_unregister_owner(uint32_t id,uint32_t force) { (void)id; (void void driver_resources_release(uint32_t id,bool irq_only) { (void)id; if(irq_only){assert(!execution_depth);++irq_releases;} else {assert(execution_depth);++data_releases;} } void net_driver_detached(void) { ++detaches; } +void net_device_attached(uint32_t ifindex) { assert(ifindex >= 2); } +void net_device_detached(uint32_t ifindex) { assert(ifindex >= 2); ++detaches; } +void *kernel_malloc(size_t size) { return calloc(1, size); } +void kernel_free(void *p) { free(p); } int module_add_resource(struct module *code,int (*stop)(void *),void (*free_fn)(void *),void *context) { assert(code==&owner && stop && free_fn && !resource.stop); diff --git a/tools/tests/e1000_init_test.c b/tools/tests/e1000_init_test.c index 86291298..fbd9e242 100644 --- a/tools/tests/e1000_init_test.c +++ b/tools/tests/e1000_init_test.c @@ -2,8 +2,12 @@ #include #include #include +#include #include #include "../../drivers/net/e1000/e1000_main.c" +#define g_e1000 (*devices) +void *kernel_malloc(size_t size) { return calloc(1, size); } +void kernel_free(void *p) { free(p); } static unsigned allocations, allocation_limit = 1024; static uint64_t next_physical = 0x100000ULL; @@ -13,10 +17,16 @@ static uint64_t fixture_bar = TEST_E1000_BAR_PHYS; static struct { uint64_t phys; uint32_t pages; void *host; } allocation_map[1024]; static int registration_error; static int stuck_reset, teardown_error; +static int multi_mode, stuck_second; +static const struct reliefnt_netdev_ops *networks[2]; +static void *contexts[2]; +static unsigned network_count; static int fail_mmio_map; static unsigned mmio_map_calls, mmio_maps, mmio_unmaps; static uint16_t command_bits; static uint8_t *registers; +static uint16_t command_bits_second; +static uint8_t *registers_second; static uint64_t allocate(uint32_t count) { if (allocations == allocation_limit) return 0; @@ -62,50 +72,70 @@ static int find(uint16_t vendor, uint16_t device, struct reliefos_driver_pci_dev *out = (struct reliefos_driver_pci_device){.vendor_id = vendor, .device_id = device, .class_code = 2}; return 0; } +static int enumerate(uint32_t index, struct reliefos_driver_pci_device *out) +{ + if (index > (unsigned)multi_mode) return -19; + int rc=find(0x8086, 0x100f, out); + if (!rc) out->slot=(uint8_t)index; + return rc; +} /** @brief Return the configured low or 64-bit fixture BAR configuration word. * @param b Bus. @param s Slot. @param f Function. @param offset Config byte offset. * @return Selected BAR register value, or zero for other offsets. */ static uint32_t read32(uint8_t b, uint8_t s, uint8_t f, uint8_t offset) { - (void)b; (void)s; (void)f; - if (offset == 0x10) return (uint32_t)fixture_bar | (fixture_bar > UINT32_MAX ? 4u : 0); - return offset == 0x14 ? (uint32_t)(fixture_bar >> 32) : 0; + (void)b; (void)f; + uint64_t device_bar=fixture_bar+(s ? 0x100000u : 0); + if (offset == 0x10) return (uint32_t)device_bar | (device_bar > UINT32_MAX ? 4u : 0); + return offset == 0x14 ? (uint32_t)(device_bar >> 32) : 0; } static uint16_t read16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset) -{ (void)b; (void)s; (void)f; (void)offset; return command_bits; } +{ (void)b; (void)f; (void)offset; return s ? command_bits_second : command_bits; } static void write16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset, uint16_t value) -{ (void)b; (void)s; (void)f; (void)offset; command_bits = value; } +{ (void)b; (void)f; (void)offset; if (s) command_bits_second=value; else command_bits=value; } /** @brief Lease the configured fixture BAR through the driver API. * @param phys Exact BAR physical address. @param bytes Register aperture bytes. * @return Borrowed emulated register alias or NULL on injected failure. */ static void *map_mmio(uint64_t phys, uint64_t bytes) { - assert(phys == fixture_bar); + assert(phys == fixture_bar || (multi_mode && phys == fixture_bar+0x100000u)); assert(bytes == TEST_E1000_REGISTER_BYTES); - assert(!(command_bits & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY | + uint16_t command=phys==fixture_bar ? command_bits : command_bits_second; + assert(!(command & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER))); ++mmio_map_calls; if (fail_mmio_map) return NULL; ++mmio_maps; - return registers; + return phys==fixture_bar ? registers : registers_second; } static void unmap_mmio(void *pointer, uint64_t bytes) { - assert(pointer == registers && bytes == TEST_E1000_REGISTER_BYTES); - assert(!(command_bits & PCI_COMMAND_BUS_MASTER)); + assert((pointer == registers || pointer == registers_second) && bytes == TEST_E1000_REGISTER_BYTES); + uint16_t command=pointer==registers ? command_bits : command_bits_second; + assert(!(command & PCI_COMMAND_BUS_MASTER)); assert(mmio_maps > mmio_unmaps); ++mmio_unmaps; } static void pause_ms(uint64_t ms) -{ (void)ms; if(!stuck_reset) *(uint32_t *)(registers + E1000_REG_CTRL) &= ~(1u << 26); } +{ + (void)ms; + if(!stuck_reset) *(uint32_t *)(registers + E1000_REG_CTRL) &= ~(1u << 26); + if(registers_second && !stuck_reset && !stuck_second) + *(uint32_t *)(registers_second + E1000_REG_CTRL) &= ~(1u << 26); +} static void report_teardown(int error) { teardown_error=error; } static void log_text(const char *text) { (void)text; } static void log_record(enum console_log_level level, const char *format, ...) { (void)level; (void)format; } -static int register_ops(const struct reliefos_driver_e1000_ops *ops) -{ assert(ops->is_ready()); return registration_error; } +int driver_register_netdev(const struct reliefnt_netdev_ops *ops, void *context, uint32_t *id) +{ + assert(ops->is_ready(context)); + if (registration_error) return registration_error; + if (multi_mode) { assert(network_count<2); networks[network_count]=ops; contexts[network_count++]=context; } + *id=2; return 0; +} /** @brief Exercise complete e1000 lifecycle, high BAR and high DMA descriptors. * @return Zero after all fixture assertions succeed. @@ -123,8 +153,8 @@ int main(void) .alloc_pages = allocate, .free_pages = release, .console_write = log_text, .console_log = log_record, .phys_to_virt = physical_alias, .map_mmio = map_mmio, .unmap_mmio = unmap_mmio, - .pci_find = find, .pci_read16 = read16, .pci_write16 = write16, - .pci_read32 = read32, .sleep_ms = pause_ms, .register_e1000 = register_ops, + .pci_find = find, .pci_enumerate = enumerate, .pci_read16 = read16, .pci_write16 = write16, + .pci_read32 = read32, .sleep_ms = pause_ms, .report_teardown_failure = report_teardown, }; struct reliefos_driver_kernel_api old_api = api; @@ -160,7 +190,7 @@ int main(void) allocation_limit = 1024; registration_error = -16; assert(e1000_driver_init(&api) == -16); - assert(allocations == 0 && !g_e1000.active && mmio_maps == mmio_unmaps); + assert(allocations == 0 && !devices && mmio_maps == mmio_unmaps); registration_error = 0; assert(e1000_driver_init(&api) == 0); assert(g_e1000.mmio_base == (uintptr_t)registers); @@ -173,15 +203,15 @@ int main(void) assert(!memcmp(g_e1000.mac, "\x00\x0c\x29\x06\x29\x97", 6)); *(uint32_t *)(registers + E1000_REG_STATUS) = 0; struct module_e1000_info info; - e1000_get_info(&info); - assert(info.present && !info.active && !e1000_is_ready()); + e1000_device_get_info(devices, &info); + assert(info.present && !info.active && !e1000_device_is_ready(devices)); *(uint32_t *)(registers + E1000_REG_STATUS) = 1u << 1; - assert(e1000_is_ready()); + assert(e1000_device_is_ready(devices)); unsigned char frame[2048]; uint32_t length; g_e1000.rx[0].length = 65535; g_e1000.rx[0].status = E1000_RX_STATUS_DD | E1000_RX_STATUS_EOP; - assert(e1000_poll(frame, sizeof(frame), &length) == -90 && length == 0); + assert(e1000_device_poll(devices, frame, sizeof(frame), &length) == -90 && length == 0); /* A full TCP window arrives before the next poll, including ring wrap. */ for (unsigned burst = 0; burst < 5; ++burst) { unsigned queued = 0; @@ -195,10 +225,10 @@ int main(void) } assert(queued == 64); for (unsigned packet = 0; packet < queued; ++packet) { - assert(e1000_poll(frame, sizeof(frame), &length) == 1 && length == 1460); + assert(e1000_device_poll(devices, frame, sizeof(frame), &length) == 1 && length == 1460); for (unsigned byte = 0; byte < length; ++byte) assert(frame[byte] == packet); } - assert(e1000_poll(frame, sizeof(frame), &length) == 0 && length == 0); + assert(e1000_device_poll(devices, frame, sizeof(frame), &length) == 0 && length == 0); } stuck_reset=1; e1000_driver_fini(); @@ -207,7 +237,7 @@ int main(void) stuck_reset=0; teardown_error=0; e1000_driver_fini(); assert(!teardown_error); - assert(!allocations && !g_e1000.active && mmio_maps == mmio_unmaps); + assert(!allocations && !devices && mmio_maps == mmio_unmaps); assert(command_bits == (PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER)); fixture_bar = 0xc000000000ULL; next_physical = 5ULL << 30; @@ -216,6 +246,27 @@ int main(void) assert(g_e1000.rx_phys > UINT32_MAX && g_e1000.rx[0].addr > UINT32_MAX); e1000_driver_fini(); assert(!allocations && mmio_maps == mmio_unmaps); + /* Two identical PCI IDs have separate BARs, rings and RX callbacks. */ + registers_second=mmap(NULL, 0x20000, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(registers_second != MAP_FAILED); + *(uint32_t *)(registers_second+E1000_REG_STATUS)=1u<<1; + *(uint32_t *)(registers_second+E1000_REG_RAL)=0x12345602; + *(uint32_t *)(registers_second+E1000_REG_RAH)=E1000_RAH_AV|0x9876; + command_bits_second=7; multi_mode=1; + assert(!e1000_driver_init(&api) && network_count==2 && contexts[0]!=contexts[1]); + struct e1000_state *first=contexts[0], *second=contexts[1]; + assert(first->pci.slot==0 && second->pci.slot==1 && first->rx_phys!=second->rx_phys); + first->rx_buf[0][0]=0xa1; first->rx[0].length=1; first->rx[0].status=3; + second->rx_buf[0][0]=0xb2; second->rx[0].length=1; second->rx[0].status=3; + assert(networks[1]->poll(contexts[1],frame,sizeof(frame),&length)==1 && frame[0]==0xb2); + assert(networks[0]->poll(contexts[0],frame,sizeof(frame),&length)==1 && frame[0]==0xa1); + stuck_second=1; e1000_driver_fini(); + assert(teardown_error==-110 && devices==second && !second->next && allocations); + assert(!(command_bits_second&PCI_COMMAND_BUS_MASTER)); + stuck_second=0; teardown_error=0; e1000_driver_fini(); + assert(!devices && !allocations && mmio_maps==mmio_unmaps); + assert(munmap(registers_second,0x20000)==0); assert(munmap(registers, 0x20000) == 0); puts("PASS e1000 initialization, VMware MAC, link setup, RX bounds and failure cleanup"); } diff --git a/tools/tests/e1000e_test.c b/tools/tests/e1000e_test.c index f88ff80c..f1645a3f 100644 --- a/tools/tests/e1000e_test.c +++ b/tools/tests/e1000e_test.c @@ -6,6 +6,7 @@ #include #include #include +#include #include extern const struct reliefos_driver_module e1000e_driver_module __attribute__((weak)); @@ -16,14 +17,22 @@ struct descriptor { uint8_t byte10, byte11, status, byte13; uint16_t special; }; -static uint32_t registers[0x8000 / 4]; -#define REG(offset) registers[(offset) / 4] -static uint16_t device_id = 0x10d3, command, phy[32]; +static uint32_t registers[2][0x8000 / 4]; +static unsigned active_registers; +#define REG(offset) registers[active_registers][(offset) / 4] +static uint16_t device_id = 0x10d3, command, command_second, phy[32]; +static int multi_mode; +static const struct reliefnt_netdev_ops *networks[2]; +static void *contexts[2]; +static unsigned network_count; static uint64_t bar = 0xc000000000ULL, physical_next = 0x140000000ULL; static struct allocation { uint64_t address; unsigned pages; void *bytes; } memory[8]; static unsigned live, allocation_limit = 8, maps, unmaps, nvm_reads, mdio_accesses; static int mapping_failure, reset_stuck, nvm_stuck, phy_stuck, registration_error, stop_error; -static const struct reliefos_driver_e1000_ops *network; +static const struct reliefnt_netdev_ops *network; +static void *network_context; +void *kernel_malloc(size_t size) { return calloc(1, size); } +void kernel_free(void *p) { free(p); } void kernel_spin_lock_irqsave(struct kernel_spinlock *lock, uint64_t *flags) { *flags = 0; assert(!lock->state); lock->state = 1; } @@ -47,7 +56,7 @@ static uint64_t allocate(unsigned pages, uint64_t mask) } static void release(uint64_t address, unsigned pages) { - assert(!(command & 4)); + assert(multi_mode || !(command & 4)); for (unsigned i = 0; i < 8; ++i) if (memory[i].address == address) { assert(memory[i].pages == pages); free(memory[i].bytes); @@ -72,32 +81,42 @@ static int find(uint16_t vendor, uint16_t device, struct reliefos_driver_pci_dev .bus=2, .slot=3, .function=0, .class_code=2}; return 0; } +static int enumerate(uint32_t index, struct reliefos_driver_pci_device *out) +{ + if (index > (unsigned)multi_mode) return -19; + int rc=find(0x8086, device_id, out); + if (!rc) out->slot=(uint8_t)(3+index); + return rc; +} static uint16_t read16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset) -{ assert(b==2 && s==3 && !f && offset==4); return command; } +{ assert(b==2 && (s==3 || (multi_mode && s==4)) && !f && offset==4); return s==3 ? command : command_second; } static void write16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset, uint16_t value) -{ assert(b==2 && s==3 && !f && offset==4); command=value; } +{ assert(b==2 && (s==3 || (multi_mode && s==4)) && !f && offset==4); if(s==3) command=value; else command_second=value; } static uint32_t read32(uint8_t b, uint8_t s, uint8_t f, uint8_t offset) { - assert(b==2 && s==3 && !f); - return offset==0x10 ? (uint32_t)bar | 4 : offset==0x14 ? (uint32_t)(bar >> 32) : 0; + assert(b==2 && (s==3 || (multi_mode && s==4)) && !f); + uint64_t address=bar+(s==4 ? 0x100000u : 0); + return offset==0x10 ? (uint32_t)address | 4 : offset==0x14 ? (uint32_t)(address >> 32) : 0; } static void *map(uint64_t address, uint64_t bytes) { - assert(address==bar && bytes>=0x5b04 && bytes<=sizeof(registers)); - assert(!(command & 7)); + assert((address==bar || (multi_mode && address==bar+0x100000u)) && bytes>=0x5b04 && bytes<=sizeof(registers[0])); + assert(!((address==bar ? command : command_second) & 7)); if (mapping_failure) return NULL; ++maps; - return registers; + return registers[address==bar ? 0 : 1]; } static void unmap(void *pointer, uint64_t bytes) { - assert(pointer==registers && bytes>=0x5b04 && bytes<=sizeof(registers)); - assert(!(command & 4) && !live && maps==unmaps+1); + assert((pointer==registers[0] || (multi_mode && pointer==registers[1])) && bytes>=0x5b04 && bytes<=sizeof(registers[0])); + assert(!((pointer==registers[0] ? command : command_second) & 4)); + assert(multi_mode || (!live && maps==unmaps+1)); ++unmaps; } static void sleep_ms(uint64_t milliseconds) { (void)milliseconds; + for (active_registers=0; active_registers<(unsigned)(multi_mode+1); ++active_registers) { if ((REG(0) & (1u<<26)) && !reset_stuck) { REG(0) &= ~(1u<<26); REG(0x10) |= 1u<<9; /* EEPROM autoload complete. */ @@ -119,11 +138,14 @@ static void sleep_ms(uint64_t milliseconds) REG(0x20)=mdic|(1u<<28); ++mdio_accesses; } + } + active_registers=0; } -static int register_network(const struct reliefos_driver_e1000_ops *ops) +int driver_register_netdev(const struct reliefnt_netdev_ops *ops, void *context, uint32_t *id) { if (registration_error) return registration_error; - network=ops; + network=ops; network_context=context; *id=2; + if (multi_mode) { assert(network_count<2); networks[network_count]=ops; contexts[network_count++]=context; } return 0; } static void report_failure(int error) { stop_error=error; } @@ -131,15 +153,16 @@ static void log_text(enum console_log_level level, const char *format, ...) { (void)level; (void)format; } static const struct reliefos_driver_kernel_api api = { .abi_version=RELIEFOS_DRIVER_ABI_VERSION, .struct_size=sizeof(api), - .pci_find=find, .pci_read16=read16, .pci_write16=write16, .pci_read32=read32, + .pci_find=find, .pci_enumerate=enumerate, .pci_read16=read16, .pci_write16=write16, .pci_read32=read32, .map_mmio=map, .unmap_mmio=unmap, .phys_to_virt=alias, .alloc_dma=allocate, .free_dma=release, .sleep_ms=sleep_ms, - .register_e1000=register_network, .console_log=log_text, + .console_log=log_text, .report_teardown_failure=report_failure, }; static void reset_fixture(void) { assert(!live && maps==unmaps); + multi_mode=0; network_count=0; active_registers=0; memset(registers,0,sizeof(registers)); memset(phy,0,sizeof(phy)); REG(0x8)=2; @@ -196,28 +219,28 @@ int main(void) reset_fixture(); registration_error=-16; assert(e1000e_driver_module.init(&api)==-16); finish(); reset_fixture(); REG(0x8)=0; /* No cable is not a probe failure. */ - assert(!e1000e_driver_module.init(&api) && network && !network->is_ready()); finish(); + assert(!e1000e_driver_module.init(&api) && network && !network->is_ready(network_context)); finish(); reset_fixture(); REG(0x5404)=0; - assert(!e1000e_driver_module.init(&api) && network && network->is_ready()); + assert(!e1000e_driver_module.init(&api) && network && network->is_ready(network_context)); assert(nvm_reads==3 && mdio_accesses); - assert(!memcmp(network->mac(),"\x52\x54\x00\x12\x34\x56",6)); + assert(!memcmp(network->mac(network_context),"\x52\x54\x00\x12\x34\x56",6)); assert(!(phy[0]&(1u<<11)) && (phy[0]&(1u<<12))); assert((REG(0x5b00)&(1u<<22)) && (REG(0x18)&(1u<<28))); - struct reliefos_driver_e1000_info info={0}; network->get_info(&info); + struct e1000_info info={0}; network->get_info(network_context, &info); assert(info.present && info.active && info.device_id==0x10d3 && info.bus==2); struct descriptor *rx=ring(0x2800), *tx=ring(0x3800); unsigned rx_count=REG(0x2808)/16, tx_count=REG(0x3808)/16; assert(rx_count>=64 && tx_count>=16); unsigned char bytes[2048], output[2048]; for(unsigned i=0;isend(NULL,64)==-22); - assert(network->send(bytes,0)==-22); - assert(network->send(bytes,2049)==-90); + assert(network->send(network_context, NULL,64)==-22); + assert(network->send(network_context, bytes,0)==-22); + assert(network->send(network_context, bytes,2049)==-90); for(unsigned cycle=0;cycle<3;++cycle) { unsigned start=REG(0x3818); - for(unsigned n=0;nsend(bytes,1500)); - assert(network->send(bytes,1500)==-11); /* Full ring must never spin. */ + for(unsigned n=0;nsend(network_context, bytes,1500)); + assert(network->send(network_context, bytes,1500)==-11); /* Full ring must never spin. */ for(unsigned n=0;nlength==1500 && (d->byte11&0x0b)==0x0b && !(d->status&1)); @@ -230,22 +253,22 @@ int main(void) memcpy(alias(rx[i].address),bytes,1500); rx[i].length=1500; rx[i].status=3; uint32_t length=99; - assert(network->poll(output,sizeof(output),&length)==1 && length==1500); + assert(network->poll(network_context, output,sizeof(output),&length)==1 && length==1500); assert(!memcmp(output,bytes,1500) && !rx[i].status && REG(0x2818)==i); } unsigned i=(rx_count*2+3)%rx_count; rx[i].length=65535; rx[i].status=3; uint32_t length=99; - assert(network->poll(output,sizeof(output),&length)==-90 && !length); + assert(network->poll(network_context, output,sizeof(output),&length)==-90 && !length); i=(i+1)%rx_count; rx[i].length=64; rx[i].status=1; /* Fragment without EOP. */ - assert(network->poll(output,sizeof(output),&length)==-74 && !length); + assert(network->poll(network_context, output,sizeof(output),&length)==-74 && !length); i=(i+1)%rx_count; rx[i].length=64; rx[i].status=3; /* The final fragment must not be returned as an independent frame. */ - assert(network->poll(output,sizeof(output),&length)==-74 && !length); + assert(network->poll(network_context, output,sizeof(output),&length)==-74 && !length); i=(i+1)%rx_count; rx[i].length=64; rx[i].status=3; rx[i].byte13=1; - assert(network->poll(output,sizeof(output),&length)==-74 && !length); - REG(0x8)=0; assert(!network->is_ready()); - assert(network->send(bytes,64)==-100); + assert(network->poll(network_context, output,sizeof(output),&length)==-74 && !length); + REG(0x8)=0; assert(!network->is_ready(network_context)); + assert(network->send(network_context, bytes,64)==-100); reset_stuck=1; e1000e_driver_module.fini(); assert(stop_error==-110 && live && maps==unmaps+1 && !(command&4)); @@ -253,7 +276,27 @@ int main(void) finish(); reset_fixture(); device_id=0x150c; assert(!e1000e_driver_module.init(&api) && network); - network->get_info(&info); assert(info.device_id==0x150c); finish(); + network->get_info(network_context, &info); assert(info.device_id==0x150c); finish(); + /* Identical models on distinct BDFs own distinct rings and callbacks. */ + reset_fixture(); multi_mode=1; command_second=7; + active_registers=1; + REG(0x8)=2; REG(0x10)=1u<<9; + REG(0x5400)=0x44332202; REG(0x5404)=(1u<<31)|0x6655; + active_registers=0; + assert(!e1000e_driver_module.init(&api) && network_count==2 && live==8); + assert(contexts[0]!=contexts[1]); + struct e1000_info first_info, second_info; + networks[0]->get_info(contexts[0],&first_info); + networks[1]->get_info(contexts[1],&second_info); + assert(first_info.slot==3 && second_info.slot==4 && memcmp(first_info.mac,second_info.mac,6)); + active_registers=0; struct descriptor *first_rx=ring(0x2800); + active_registers=1; struct descriptor *second_rx=ring(0x2800); + assert(first_rx!=second_rx); + *(uint8_t *)alias(first_rx[0].address)=0xa1; first_rx[0].length=64; first_rx[0].status=3; + *(uint8_t *)alias(second_rx[0].address)=0xb2; second_rx[0].length=64; second_rx[0].status=3; + assert(networks[1]->poll(contexts[1],output,sizeof(output),&length)==1 && output[0]==0xb2); + assert(networks[0]->poll(contexts[0],output,sizeof(output),&length)==1 && output[0]==0xa1); + active_registers=0; finish(); assert(command_second==7); puts("PASS e1000e: PCI IDs, high BAR/DMA, MAC/NVM, PHY, bounded TX, RX wrap/errors, rollback and retained failed STOP"); return 0; } diff --git a/tools/tests/host-suites.list b/tools/tests/host-suites.list index f585cde1..3f596b23 100644 --- a/tools/tests/host-suites.list +++ b/tools/tests/host-suites.list @@ -215,3 +215,6 @@ vmwgfx_ppn_bo tools/test_vmwgfx_ppn_bo.py physical_metadata tools/test_physical_metadata.py heap_slab tools/test_heap_slab.py vfs_lookup_cache tools/test_vfs_lookup_cache.py +net_device tools/test_net_device.py +net_multi_interface tools/test_net_multi_interface.py +net_multinic_data tools/test_net_multinic_data.py diff --git a/tools/tests/ipv6_control_test.c b/tools/tests/ipv6_control_test.c index 1239838f..100820b4 100644 --- a/tools/tests/ipv6_control_test.c +++ b/tools/tests/ipv6_control_test.c @@ -5,6 +5,9 @@ #include #if __has_include("../../net/socket/netlink.c") #include "../../net/socket/netlink.c" +#include "../../net/interface/state.c" +#include "../../net/interface/routes.c" +#include "../../net/interface/ioctl.c" #include "../../net/interface/config.c" #include "../../net/proc.c" @@ -14,8 +17,6 @@ static unsigned allocations, updates, link_changes; static bool additional_address; static bool nic_ready=true; static bool nic_present=true; -struct reliefos_net_config net_config = {.local_ip=0x0a00020f,.subnet_mask=0xffffff00}; -bool net_interface_up=true; struct net_socket *net_sockets; struct task *sched_current_task(void) { return ¤t; } void *kernel_malloc(size_t bytes) { ++allocations; return calloc(1, bytes); } @@ -39,12 +40,21 @@ struct task_file *task_file_for_fd(struct task *task, int fd) int e1000_is_ready(void) { return nic_ready; } const uint8_t *e1000_mac(void) { static const uint8_t mac[6] = {2,3,4,5,6,7}; return mac; } void e1000_get_info(struct e1000_info *info) { *info = (struct e1000_info){.present=nic_present,.active=nic_ready,.mac={2,3,4,5,6,7}}; } +uint32_t netdev_next(uint32_t after) { return nic_present && after<2 ? 2 : 0; } +int netdev_is_ready(uint32_t index) { return index==2 && nic_present && nic_ready; } +const uint8_t *netdev_mac(uint32_t index) { (void)index; return e1000_mac(); } +void netdev_get_info(uint32_t index,struct e1000_info *info) +{ if (index==2) e1000_get_info(info); else memset(info,0,sizeof(*info)); } +void netdev_counters(uint32_t index,struct net_proc_counters *out) +{ (void)index; memset(out,0,sizeof(*out)); } void net_memcpy(void *dst,const void *src,uint32_t size) { memcpy(dst,src,size); } void net_memzero(void *dst,uint32_t size) { memset(dst,0,size); } uint32_t net_get_u32(const uint8_t *p) { return ((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3]; } void net_put_u32(uint8_t *p,uint32_t value) { p[0]=value>>24;p[1]=value>>16;p[2]=value>>8;p[3]=value; } void net_arp_cache_clear(void) {} +void net_arp_cache_clear_on(uint32_t index) { (void)index; } void net_ipv6_link_changed(bool up) { (void)up; ++link_changes; } +int net_ipv6_link_changed_on(uint32_t index,bool up) { (void)index; (void)up; ++link_changes; return 0; } void net_device_counters(struct net_proc_counters *out) { memset(out,0,sizeof(*out)); } void net_loopback_proc_emit(struct text_stream *stream) { (void)stream; } void net_arp_proc_emit(struct text_stream *stream) { (void)stream; } @@ -163,6 +173,8 @@ static void check_dump_boundaries(int fd) int main(void) { + assert(!net_interface_register(2)); + net_config.local_ip=0x0a00020f; net_config.subnet_mask=0xffffff00; int fd = (int)call(__NR_socket,AF_NETLINK,SOCK_RAW|SOCK_NONBLOCK,NETLINK_ROUTE,0,0); assert(fd >= 0 && files[fd].socket_ino); check_route_completion(fd); @@ -292,6 +304,7 @@ int main(void) memset(&ifr,0,sizeof(ifr)); ifr.data.value=1; assert(net_interface_ioctl(SIOCGIFNAME,(uintptr_t)&ifr)==0 && !strcmp(ifr.ifr_name,"lo")); nic_ready=nic_present=false; + net_interface_remove(2); strcpy(ifr.ifr_name,"lo"); assert(net_interface_ioctl(SIOCGIFINDEX,(uintptr_t)&ifr)==0 && ifr.data.value==1); memset(&ifr,0,sizeof(ifr)); ifr.data.value=1; @@ -299,6 +312,7 @@ int main(void) strcpy(ifr.ifr_name,"eth0"); assert(net_interface_ioctl(SIOCGIFINDEX,(uintptr_t)&ifr)==-LINUX_ENODEV); nic_ready=nic_present=true; + assert(!net_interface_register(2)); memset(&ifr,0,sizeof(ifr)); ifr.data.value=2; assert(net_interface_ioctl(SIOCGIFNAME,(uintptr_t)&ifr)==0 && !strcmp(ifr.ifr_name,"eth0")); strcpy(ifr.ifr_name,"eth0"); @@ -313,6 +327,7 @@ int main(void) memset(text,0,sizeof(text)); stream=(struct text_stream){.buffer=text,.capacity=sizeof(text)-1}; assert(net_proc_emit(&stream,"tcp6")==0 && !strcmp(text,"tcp6\n")); task_netlink_release(&files[fd]); + net_interface_remove(2); assert(!allocations); puts("PASS route-netlink dumps/updates, musl requests, multicast/overflow, IPv6 ioctl and proc"); return 0; diff --git a/tools/tests/ipv6_protocol_test.c b/tools/tests/ipv6_protocol_test.c index af6ae47d..f7912924 100644 --- a/tools/tests/ipv6_protocol_test.c +++ b/tools/tests/ipv6_protocol_test.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: Apache-2.0 */ /** @file Packet-level IPv6 fixture: only NIC, clock, heap and transport are mocked. */ #include +#include +#include #include #include "../../net/internal.h" #include @@ -9,15 +11,25 @@ #include #include -bool net_interface_up = true; +static bool fixture_interface_up = true; +bool *net_interface_legacy_up(void) { return &fixture_interface_up; } static uint64_t now_ms; static uint32_t random_word; static bool random_unavailable; static bool nic_ready = true; +static bool first_nic = true; static uint8_t nic_mac[6] = {0x52,0x54,0x00,0x12,0x34,0x56}; +static bool second_nic; +static uint32_t second_index=3; +static uint32_t extra_nics; +static unsigned failed_allocations; +static uint8_t second_mac[6] = {0x52,0x54,0x00,0x12,0x34,0x57}; static uint8_t sent[128][1536]; static uint32_t sent_len[128]; +static uint32_t sent_ifindex[128]; static unsigned sent_count, udp_count, raw_count; +static uint32_t last_udp_ifindex, last_error_ifindex; +static unsigned socket_error_count; static uint8_t queued_frame[1536]; static uint32_t queued_length; const uint8_t net_broadcast_mac[6]={255,255,255,255,255,255}; @@ -38,8 +50,30 @@ const uint8_t *e1000_mac(void) { return nic_mac; } * @return Zero when captured. */ int e1000_send(const void *frame, uint32_t len) { assert(sent_count < 128 && len <= 1536); memcpy(sent[sent_count],frame,len); sent_len[sent_count++]=len; return 0; } +uint32_t netdev_next(uint32_t after) +{ + uint32_t next=0; + if(after<2 && first_nic)next=2; + if(second_nic && after=100 && extra-100=100 && index-100=100 && index-100=78 && sent[i][20]==58 && sent[i][54]==135 && + !memcmp(sent[i]+62,target->s6_addr,16))++count; + return count; +} + +/** @brief Find an MLD report for a group and interface. */ +static bool saw_mld(uint32_t ifindex,const struct in6_addr *group,uint8_t type) +{ + for(unsigned i=0;i=group_offset+16 && !memcmp(sent[i]+group_offset,group->s6_addr,16))return true; + } + return false; +} + +/** @brief Exercise the real IPv6 core with duplicate scoped addresses on two NICs. */ +static void test_multiple_interfaces(void) +{ + const uint8_t text[]="hello"; + const uint8_t mac2[6]={0x02,0,0,0,0,2},mac3[6]={0x02,0,0,0,0,3}; + struct in6_addr shared={.s6_addr={0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0x12,0x34}}; + struct in6_addr peer={.s6_addr={0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0x42}}; + struct in6_addr peer2=peer;peer2.s6_addr[15]=0x43; + struct in6_addr all={.s6_addr={0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}; + uint8_t frame[1536],message[64],na[32]={136,0,0,0,0x60},fragment[32]; + struct net_ipv6_path path2,path3; + second_nic=true;first_nic=true;nic_ready=true;random_word=0; + net_ipv6_init();sent_count=0;net_ipv6_poll(); + uint8_t early_ra[16]={134};net_put_u16(early_ra+2,net_ipv6_checksum(&peer,&all,58,early_ra,sizeof(early_ra))); + uint32_t len=packet(frame,&peer,&all,58,255,early_ra,sizeof(early_ra));net_ipv6_input_on(2,frame,len); + now_ms+=1000;net_ipv6_poll(); + + /* DAD and MLD frames must use each NIC's own source MAC. */ + bool saw2=false,saw3=false,rs2=false,rs3=false; + for(unsigned i=0;i=55 && sent[i][20]==58 && sent[i][54]==133){ + if(sent_ifindex[i]==2)rs2=true; + if(sent_ifindex[i]==3)rs3=true; + } + } + assert(saw2 && saw3 && !rs2 && rs3); + struct net_ipv6_address initial[16];uint32_t initial_count=net_ipv6_addresses(initial,16); + struct in6_addr auto3={0};bool found_auto3=false; + for(uint32_t i=0;i=0 && now_ms-start<=1010);random_unavailable=false; + /* Address configuration must accept a second real Ethernet interface. */ + test_multiple_interfaces(); + test_many_interfaces(); + test_link_allocation_retry(); puts("PASS IPv6 protocol: loopback, DAD/NDP, checksums, fragments, RA/SLAAC, MLD and timer pump"); #endif return 0; diff --git a/tools/tests/ipv6_tcp_test.c b/tools/tests/ipv6_tcp_test.c index a15d5f86..aaf0f442 100644 --- a/tools/tests/ipv6_tcp_test.c +++ b/tools/tests/ipv6_tcp_test.c @@ -6,10 +6,10 @@ #include "../../net/internal.h" struct net_socket *net_sockets; -struct reliefos_net_config net_config; +static struct reliefos_net_config fixture_config; +static bool fixture_interface_up; uint32_t net_sequence; uint16_t net_ipv4_id; -bool net_interface_up; static struct task current; static uint64_t now = 1000; static const struct in6_addr loop6 = {.s6_addr = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}; @@ -30,15 +30,34 @@ uint32_t kernel_wait_queue_wake_all(struct kernel_wait_queue *q) { (void)q; retu void kernel_poll_readiness_notify(void) {} void console_log(enum console_log_level level, const char *format, ...) { (void)level; (void)format; } int net_get_config(struct reliefos_net_config *out) { *out = net_config; return 0; } +struct reliefos_net_config *net_interface_legacy_config(void) { return &fixture_config; } +bool *net_interface_legacy_up(void) { return &fixture_interface_up; } +bool net_interface_exists(uint32_t index) { return index==1; } +bool net_interface_is_ready(uint32_t index) { return index==1; } +uint32_t net_interface_for_ipv4(uint32_t ip) { return (ip>>24)==127?1:0; } +uint32_t netdev_next(uint32_t after) { (void)after;return 0; } +int netdev_send(uint32_t index,const void *data,uint32_t length) +{ (void)index;(void)data;(void)length;return -LINUX_ENETDOWN; } +int net_ipv4_route_lookup(uint32_t destination,uint32_t source,uint32_t scope,struct net_ipv4_path *out) +{ + if((destination>>24)!=127 || (scope && scope!=1))return -LINUX_ENETUNREACH; + *out=(struct net_ipv4_path){.source=source?source:0x7f000001u,.destination=destination, + .next_hop=destination,.ifindex=1}; + return 0; +} int e1000_is_ready(void) { return 0; } int e1000_send(const void *data, uint32_t length) { (void)data; (void)length; return -LINUX_ENETDOWN; } int net_resolve_mac(uint32_t ip, uint32_t timeout, uint8_t *out) { (void)ip; (void)timeout; (void)out; return -1; } +int net_resolve_mac_on(uint32_t index,uint32_t ip,uint32_t timeout,uint8_t *out) +{ (void)index;return net_resolve_mac(ip,timeout,out); } uint32_t net_route_arp_ip(uint32_t ip) { return ip; } uint32_t net_ensure_ipv4_config(uint32_t timeout, int dns) { (void)timeout; (void)dns; return RELIEFOS_NET_STATUS_OK; } void net_poll_once(struct net_arp_wait *a, struct net_ping_wait *p, struct net_udp_wait *u, struct net_tcp_wait *t) { (void)a; (void)p; (void)u; (void)t; } void net_write_eth(uint8_t *frame, const uint8_t *mac, uint16_t type) { (void)frame; (void)mac; (void)type; } +void net_write_eth_on(uint32_t index,uint8_t *frame,const uint8_t *mac,uint16_t type) +{ (void)index;net_write_eth(frame,mac,type); } uint32_t net_ipv6_addresses(struct net_ipv6_address *out,uint32_t cap) { if (cap) out[0] = (struct net_ipv6_address){.address=loop6,.ifindex=1}; return 1; } int net_ipv6_route(const struct in6_addr *destination,uint32_t scope, @@ -142,3 +161,5 @@ int main(void) while (net_sockets) { struct net_socket *s=net_sockets; net_sockets=s->registry_next; free(s->rx); free(s->tx); free(s); } puts("PASS IPv6 and IPv4-mapped TCP deferred loopback handshake, accept, payload and FIN"); } + +bool net_interface_is_up(uint32_t index) { return net_interface_is_ready(index); } diff --git a/tools/tests/ipv6_transport_test.c b/tools/tests/ipv6_transport_test.c index 518418c9..68571fb9 100644 --- a/tools/tests/ipv6_transport_test.c +++ b/tools/tests/ipv6_transport_test.c @@ -78,6 +78,9 @@ int main(void) assert(call(__NR_bind, receiver, (uintptr_t)&local, sizeof(local), 0, 0) == 0); assert(call(__NR_connect, fd, (uintptr_t)&local, sizeof(local), 0, 0) == 0); assert(task_udp_send(&files[fd], "hello6", 6, 0, 0, 0) == 6); + struct udp_socket conflict = {.ipv6=true,.ifindex=2,.connected=true, + .remote6=loop6,.remote_scope6=1,.remote_port=55001}; + assert(udp6_send(&conflict, "scope", 5, 0, 0) == -LINUX_EINVAL); char payload[16] = {0}; struct sockaddr_in6 source = {0}; uint32_t source_length = sizeof(source); @@ -178,3 +181,11 @@ int main(void) loopback_bytes = loopback_packets = 0; return ipv4_udp_regression_main(); } + +bool net_interface_is_ready(uint32_t index) { return index >= 1 && index <= 3; } +const uint8_t *netdev_mac(uint32_t index) { (void)index; return e1000_mac(); } +int netdev_send(uint32_t index, const void *frame, uint32_t length) +{ (void)index; return e1000_send(frame, length); } +int net_ipv4_send_raw_on(uint32_t index, uint32_t destination, uint32_t source, + uint8_t protocol, bool hdrincl, const void *data, uint32_t length) +{ (void)index; return net_ipv4_send_raw(destination,source,protocol,hdrincl,data,length); } diff --git a/tools/tests/module_legacy_audio_owner_test.c b/tools/tests/module_legacy_audio_owner_test.c index 9f3c3c91..4cdf2672 100644 --- a/tools/tests/module_legacy_audio_owner_test.c +++ b/tools/tests/module_legacy_audio_owner_test.c @@ -21,6 +21,8 @@ __attribute__((noreturn)) void panic(const char *message) { fprintf(stderr,"pani int sound_service_unregister_owner(uint32_t owner,uint32_t force) { (void)owner;(void)force;return 0; } void driver_resources_release(uint32_t owner,bool irq_only) { (void)owner;(void)irq_only; } void net_driver_detached(void) { } +void net_device_attached(uint32_t ifindex) { (void)ifindex; } +void net_device_detached(uint32_t ifindex) { (void)ifindex; } static int ready(void) { return 1; } static int configure_audio(const struct reliefos_audio_format *format) { (void)format;return 0; } static long write_audio(const void *bytes,uint32_t length,uint32_t *status) { (void)bytes;*status=0;return length; } diff --git a/tools/tests/modules/network.h b/tools/tests/modules/network.h index 006dceec..d0a2db13 100644 --- a/tools/tests/modules/network.h +++ b/tools/tests/modules/network.h @@ -1,7 +1,13 @@ +#ifndef RELIEFNT_TEST_NETWORK_BODIES_H +#define RELIEFNT_TEST_NETWORK_BODIES_H /* White-box test of independently compiled original network bodies. */ #include "../../../net/core.c" #include "../../../net/input.c" #include "../../../net/interface/config.c" +#include "../../../net/interface/state.c" +#include "../../../net/interface/routes.c" +#include "../../../net/interface/ioctl.c" +#include "../../../net/interface/socket.c" #include "../../../net/ipv4/core.c" #include "../../../net/ipv4/icmp.c" #include "../../../net/ipv4/udp.c" @@ -15,3 +21,5 @@ #include "../../../net/tcp/output.c" #include "../../../net/tcp/socket.c" #include "../../../net/utility.c" + +#endif diff --git a/tools/tests/multinic_guest.c b/tools/tests/multinic_guest.c new file mode 100644 index 00000000..168a7c32 --- /dev/null +++ b/tools/tests/multinic_guest.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Native Linux socket probe for independently configured network interfaces. */ +#define _DEFAULT_SOURCE +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *device; + +static void require(int condition, const char *operation) +{ + if (!condition) { + fprintf(stderr, "MULTINIC_FAIL device=%s operation=%s errno=%d (%s)\n", + device, operation, errno, strerror(errno)); + exit(1); + } +} + +static int connect_peer(int family, int type, const char *peer, unsigned port) +{ + int fd = socket(family, type, 0); + require(fd >= 0, "socket"); + require(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) == 0, + "bind socket to interface"); + char bound[IFNAMSIZ] = {0}; + socklen_t size = sizeof(bound); + require(getsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bound, &size) == 0 && + size == strlen(device) + 1 && strcmp(bound, device) == 0, + "read bound interface"); + struct timeval timeout = {8, 0}; + require(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == 0, + "receive timeout"); + if (type == SOCK_STREAM) + require(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == 0, + "send timeout"); + struct sockaddr_storage storage = {0}; + if (family == AF_INET) { + struct sockaddr_in *address = (void *)&storage; + address->sin_family = AF_INET; + address->sin_port = htons((uint16_t)port); + require(inet_pton(family, peer, &address->sin_addr) == 1, "parse IPv4 peer"); + size = sizeof(*address); + } else { + struct sockaddr_in6 *address = (void *)&storage; + address->sin6_family = AF_INET6; + address->sin6_port = htons((uint16_t)port); + require(inet_pton(family, peer, &address->sin6_addr) == 1, "parse IPv6 peer"); + size = sizeof(*address); + } + require(connect(fd, (void *)&storage, size) == 0, "connect echo peer"); + if (family == AF_INET) { + struct ifreq request = {0}; + snprintf(request.ifr_name, sizeof(request.ifr_name), "%s", device); + require(ioctl(fd, SIOCGIFADDR, &request) == 0, "query interface IPv4"); + struct sockaddr_in local; + socklen_t local_size = sizeof(local); + require(getsockname(fd, (void *)&local, &local_size) == 0 && + local.sin_addr.s_addr == ((struct sockaddr_in *)&request.ifr_addr)->sin_addr.s_addr, + "TCP source belongs to selected device"); + } + return fd; +} + +static void transfer(int fd, unsigned char *data, size_t size, int sending) +{ + while (size) { + ssize_t count = sending ? send(fd, data, size, 0) : recv(fd, data, size, 0); + if (count < 0 && errno == EINTR) continue; + require(count > 0, sending ? "TCP send" : "TCP receive"); + data += count; + size -= (size_t)count; + } +} + +static void check_stream(int family, const char *peer, unsigned port, int hold) +{ + int fd = connect_peer(family, SOCK_STREAM, peer, port); + unsigned char sent[4096], received[4096]; + unsigned rounds = hold ? 50 : 3; + for (unsigned round = 0; round < rounds; ++round) { + for (unsigned i = 0; i < sizeof(sent); ++i) + sent[i] = (unsigned char)(i * 37 + round * 19 + (unsigned)family); + transfer(fd, sent, sizeof(sent), 1); + transfer(fd, received, sizeof(received), 0); + require(memcmp(sent, received, sizeof(sent)) == 0, "TCP payload integrity"); + if (hold && round == 0) { + FILE *marker = fopen("/run/multinic-hold-ready", "w"); + require(marker != NULL && fclose(marker) == 0, "publish active stream marker"); + } + if (hold) { + struct timespec delay = {0, 100000000}; + while (nanosleep(&delay, &delay) < 0 && errno == EINTR) {} + } + } + require(close(fd) == 0, "close TCP socket"); +} + +static void check_datagrams(int family, const char *peer, unsigned port) +{ + int fd = connect_peer(family, SOCK_DGRAM, peer, port); + unsigned char sent[333], received[334]; + for (unsigned round = 0; round < 4; ++round) { + for (unsigned i = 0; i < sizeof(sent); ++i) + sent[i] = (unsigned char)(i * 17 + round * 23 + (unsigned)family); + require(send(fd, sent, sizeof(sent), 0) == sizeof(sent), "UDP send"); + require(recv(fd, received, sizeof(received), 0) == sizeof(sent), "UDP receive size"); + require(memcmp(sent, received, sizeof(sent)) == 0, "UDP payload integrity"); + } + require(close(fd) == 0, "close UDP socket"); +} + +int main(int argc, char **argv) +{ + device = argc > 1 ? argv[1] : "?"; + require(argc == 5 || argc == 6, "arguments device IPv4 IPv6 port [hold]"); + char *end; + unsigned long number = strtoul(argv[4], &end, 10); + require(argv[4][0] && !*end && number > 0 && number <= 65535, "echo port"); + require(strlen(device) < IFNAMSIZ && if_nametoindex(device) > 0, "interface identity"); + for (unsigned version = 0; version < 2; ++version) { + int family = version ? AF_INET6 : AF_INET; + check_stream(family, argv[version + 2], (unsigned)number, argc == 6 && !version); + check_datagrams(family, argv[version + 2], (unsigned)number); + printf("MULTINIC_PASS device=%s family=%u\n", device, version ? 6 : 4); + fflush(stdout); + } + return 0; +} diff --git a/tools/tests/net_device_test.c b/tools/tests/net_device_test.c new file mode 100644 index 00000000..26554b68 --- /dev/null +++ b/tools/tests/net_device_test.c @@ -0,0 +1,89 @@ +/* Verify that independent NIC contexts retain distinct identities and owners. */ +#include +#include +#include +#include +#include "../../kernel/device/manager_internal.h" +#include +#include + +int32_t loading_slot = -1; +struct driver_slot driver_slots[RELIEFOS_DRIVER_MAX]; +struct kernel_spinlock manager_service_lock = KERNEL_SPINLOCK_INIT; +static unsigned attached[64], detached[64], attaches, detaches; +void net_device_attached(uint32_t id) { attached[attaches++] = id; } +void net_device_detached(uint32_t id) { detached[detaches++] = id; } +void kernel_spin_lock_irqsave(struct kernel_spinlock *lock, uint64_t *flags) +{ assert(!lock->state); lock->state = 1; *flags = 0; } +void kernel_spin_unlock_irqrestore(struct kernel_spinlock *lock, uint64_t flags) +{ (void)flags; assert(lock->state); lock->state = 0; } +void driver_memcpy(void *out, const void *in, uint64_t size) { memcpy(out, in, size); } +void driver_memzero(void *out, uint64_t size) { memset(out, 0, size); } +void *kernel_malloc(size_t size) { return calloc(1, size); } +void kernel_free(void *p) { free(p); } + +struct nic { uint8_t mac[6]; unsigned tx; uint8_t rx; unsigned link_down; }; +static int ready(void *arg) { return arg && !((struct nic *)arg)->link_down; } +static const uint8_t *mac(void *arg) { return ((struct nic *)arg)->mac; } +static int send(void *arg, const void *frame, uint32_t length) +{ assert(frame && length == 1); ++((struct nic *)arg)->tx; return 0; } +static int poll(void *arg, void *frame, uint32_t capacity, uint32_t *length) +{ assert(capacity); *(uint8_t *)frame = ((struct nic *)arg)->rx; *length = 1; return 1; } +static void info(void *arg, struct e1000_info *out) +{ *out = (struct e1000_info){.present = 1, .active = 1}; memcpy(out->mac, mac(arg), 6); } +static const struct reliefnt_netdev_ops ops = {1, sizeof(ops), ready, mac, send, poll, info}; + +int main(void) +{ + struct nic a = {{2,0,0,0,0,1},0,0xa1,0}, b = {{2,0,0,0,0,2},0,0xb2,0}; + struct nic c = {{2,0,0,0,0,3},0,0xc3,0}; + uint32_t ia, ib, ic; + loading_slot = 0; + assert(!driver_register_netdev(&ops, &a, &ia)); + assert(!driver_register_netdev(&ops, &b, &ib)); + assert(ia == 2 && ib == 3 && netdev_next(0) == 0 && !attaches); + driver_manager_service_enable(0); driver_netdev_publish_owner(0); + loading_slot = 1; + assert(!driver_register_netdev(&ops, &c, &ic) && ic == 4); + driver_manager_service_enable(1); driver_netdev_publish_owner(1); + loading_slot = -1; + assert(attaches == 3 && attached[0] == ia && attached[1] == ib && attached[2] == ic); + assert(netdev_next(0) == ia && netdev_next(ia) == ib && netdev_next(ib) == ic); + assert(netdev_is_ready(ib) && !memcmp(netdev_mac(ib), b.mac, 6)); + uint8_t byte = 9; uint32_t length = 0; + assert(!netdev_send(ib, &byte, 1) && b.tx == 1 && !a.tx && !c.tx); + assert(netdev_poll(ic, &byte, 1, &length) == 1 && length == 1 && byte == 0xc3); + struct net_proc_counters counts; + netdev_counters(ib, &counts); assert(counts.tx_packets == 1 && !counts.rx_packets); + netdev_counters(ic, &counts); assert(!counts.tx_packets && counts.rx_packets == 1); + /* Carrier negotiation has no received packet to classify as an RX error. */ + c.link_down = 1; + assert(netdev_poll(ic, &byte, 1, &length) == -19 && length == 0); + netdev_counters(ic, &counts); + assert(counts.rx_packets == 1 && !counts.rx_errors && !counts.rx_drops); + c.link_down = 0; + driver_manager_service_close(0); driver_manager_service_drain(0); + driver_netdev_remove_owner(0); + assert(detaches == 2 && detached[0] == ia && detached[1] == ib); + assert(netdev_next(0) == ic && !netdev_is_ready(ia)); + struct e1000_info absent = {.present = 1}; netdev_get_info(ia, &absent); + assert(!absent.present && netdev_send(ia, &byte, 1) < 0); + assert(!netdev_send(ic, &byte, 1) && c.tx == 1); + loading_slot = 0; assert(!driver_register_netdev(&ops, &a, &ia) && ia == 5); + driver_manager_service_enable(0); driver_netdev_publish_owner(0); + assert(netdev_next(ic) == ia); + /* The core must not impose a fixed 16-NIC registry limit. */ + for (unsigned n=0; n<20; ++n) { + uint32_t more; + assert(!driver_register_netdev(&ops, &a, &more) && more == 6+n); + } + driver_netdev_publish_owner(0); + assert(netdev_next(24) == 25 && netdev_next(25) == 0); + driver_manager_service_close(0); driver_manager_service_drain(0); + driver_netdev_remove_owner(0); + driver_manager_service_close(1); driver_manager_service_drain(1); + driver_netdev_remove_owner(1); + assert(!netdev_next(0)); + puts("PASS netdev owner isolation, context dispatch, publication and stable ifindex"); + return 0; +} diff --git a/tools/tests/net_interface_test.c b/tools/tests/net_interface_test.c index 258978bf..3e26f718 100644 --- a/tools/tests/net_interface_test.c +++ b/tools/tests/net_interface_test.c @@ -1,22 +1,34 @@ #include #include +#include #include #include "modules/network.h" static struct task current; +#ifndef NET_INTERFACE_TEST_EXTERNAL_ALLOC +void *kernel_malloc(size_t size) { return malloc(size); } +void kernel_free(void *memory) { free(memory); } +#endif struct task *sched_current_task(void) { return ¤t; } static bool nic_present=true, nic_carrier=true; int e1000_is_ready(void) { return nic_present && nic_carrier; } void e1000_get_info(struct e1000_info *info) { *info=(struct e1000_info){.present=nic_present,.active=nic_present && nic_carrier}; } void net_ipv6_link_changed(bool up) { (void)up; } +int net_ipv6_link_changed_on(uint32_t index, bool up) { (void)index; (void)up; return 0; } void netlink_ipv6_changed(void) { } const uint8_t *e1000_mac(void) { static const uint8_t mac[6] = {2,3,4,5,6,7}; return mac; } +uint32_t netdev_next(uint32_t after) { return nic_present && after < 2 ? 2 : 0; } +int netdev_is_ready(uint32_t index) { return index == 2 && e1000_is_ready(); } +const uint8_t *netdev_mac(uint32_t index) { (void)index; return e1000_mac(); } +void netdev_get_info(uint32_t index, struct e1000_info *info) +{ if (index == 2) { e1000_get_info(info); memcpy(info->mac,e1000_mac(),6); } else memset(info,0,sizeof(*info)); } bool user_range_ok(uint64_t address, uint64_t size) { return address >= 4096 && size <= 4096; } bool user_range_writable(uint64_t address, uint64_t size) { return user_range_ok(address, size); } static int call(unsigned command, struct ifreq *req) { return net_interface_ioctl(command, (uintptr_t)req); } int main(void) { + assert(!net_interface_register(2)); struct ifreq req = {.ifr_name = "eth0"}; assert(call(SIOCGIFINDEX, &req) == 0 && req.data.value == 2); assert(call(SIOCGIFHWADDR, &req) == 0 && req.data.addr.sa_family == 1 && req.data.addr.sa_data[0] == 2); @@ -39,11 +51,12 @@ int main(void) assert(net_interface_ioctl(SIOCADDRT, (uintptr_t)&rt) == -LINUX_EEXIST); assert(net_interface_ioctl(SIOCDELRT, (uintptr_t)&rt) == 0); assert(net_interface_ioctl(SIOCDELRT, (uintptr_t)&rt) == -LINUX_ESRCH); - struct ifreq output; + struct ifreq output[2]; struct ifconf conf = {.ifc_len = sizeof(output), .ifc_buf = (uintptr_t)&output}; assert(net_interface_ioctl(SIOCGIFCONF, (uintptr_t)&conf) == 0); - assert(conf.ifc_len == sizeof(output) && !strcmp(output.ifr_name, "eth0")); - assert(net_get_u32((uint8_t *)&output.data.addr + 4) == 0x0a00020f); + assert(conf.ifc_len == sizeof(output) && !strcmp(output[0].ifr_name, "lo") && + !strcmp(output[1].ifr_name, "eth0")); + assert(net_get_u32((uint8_t *)&output[1].data.addr + 4) == 0x0a00020f); req.data.flags = 0; assert(call(SIOCSIFFLAGS, &req) == 0 && !net_interface_ready()); req.data.flags = IFF_UP; @@ -61,10 +74,12 @@ int main(void) conf.ifc_len=sizeof(output); assert(!net_interface_ioctl(SIOCGIFCONF,(uintptr_t)&conf) && conf.ifc_len==sizeof(output)); nic_present=false; + net_interface_remove(2); assert(call(SIOCGIFINDEX,&req)==-LINUX_ENODEV); conf.ifc_len=sizeof(output); - assert(!net_interface_ioctl(SIOCGIFCONF,(uintptr_t)&conf) && !conf.ifc_len); + assert(!net_interface_ioctl(SIOCGIFCONF,(uintptr_t)&conf) && conf.ifc_len==sizeof(struct ifreq)); nic_present=nic_carrier=true; + assert(!net_interface_register(2)); strcpy(req.ifr_name, "absent"); assert(call(SIOCGIFINDEX, &req) == -LINUX_ENODEV); assert(net_interface_ioctl(SIOCGIFINDEX, 0) == -LINUX_EFAULT); diff --git a/tools/tests/net_multi_interface_test.c b/tools/tests/net_multi_interface_test.c new file mode 100644 index 00000000..2f003a05 --- /dev/null +++ b/tools/tests/net_multi_interface_test.c @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Exercise the real interface ioctl and route code with two independent NICs. */ +#include +#include +#include +#include +#include "../../net/internal.h" +#include +#include "../../net/socket/netlink.c" + +static struct task current; +static bool carrier[4] = {false, false, true, true}; +static uint8_t macs[4][6] = {{0}, {0}, {2,0,0,0,0,2}, {2,0,0,0,0,3}}; +struct task *sched_current_task(void) { return ¤t; } +void *kernel_malloc(size_t size) { return malloc(size); } +void kernel_free(void *memory) { free(memory); } +bool user_range_ok(uint64_t address, uint64_t size) { return address >= 4096 && size <= 65536; } +bool user_range_writable(uint64_t address, uint64_t size) { return user_range_ok(address, size); } +int netdev_is_ready(uint32_t index) { return index < 4 && carrier[index]; } +const uint8_t *netdev_mac(uint32_t index) { return macs[index < 4 ? index : 0]; } +void netdev_get_info(uint32_t index, struct e1000_info *out) +{ + *out = (struct e1000_info){.present = index == 2 || index == 3, + .active = index < 4 && carrier[index]}; + memcpy(out->mac, netdev_mac(index), 6); +} +int net_ipv6_link_changed_on(uint32_t index, bool up) { (void)index; (void)up; return 0; } +uint64_t time_uptime_ms(void) { return 1000; } +void kernel_poll_readiness_notify(void) { } +uint32_t net_ipv6_addresses(struct net_ipv6_address *out, uint32_t capacity) +{ (void)out; (void)capacity; return 0; } +uint32_t net_ipv6_routes(struct net_ipv6_route *out, uint32_t capacity) +{ (void)out; (void)capacity; return 0; } +int net_ipv6_address_update(const struct net_ipv6_address *address, bool remove) +{ (void)address; (void)remove; return 0; } +int net_ipv6_route_update(const struct net_ipv6_route *route, bool remove) +{ (void)route; (void)remove; return 0; } +void net_arp_cache_clear_on(uint32_t index) { (void)index; } + +static int call(unsigned command, struct ifreq *req) +{ return net_interface_ioctl(command, (uintptr_t)req); } +static int netlink_request_error(const struct nlmsghdr *request) +{ + struct netlink_message *message=kernel_malloc(sizeof(*message)+NL_MESSAGE_MAX); + assert(message); + struct nl_builder builder={.message=message}; + assert(!nl_request(&builder,request)); + assert(builder.used>=sizeof(struct nlmsghdr)+sizeof(struct nlmsgerr)); + struct nlmsghdr *reply=(void *)message->data; + assert(reply->nlmsg_type==NLMSG_ERROR); + int error=((struct nlmsgerr *)(reply+1))->error; + kernel_free(message); + return error; +} +static void set_address(const char *name, unsigned command, uint32_t ip) +{ + struct ifreq req = {0}; + strcpy(req.ifr_name, name); + req.data.addr.sa_family = AF_INET; + net_put_u32((uint8_t *)&req.data.addr + 4, ip); + assert(call(command, &req) == 0); +} + +int main(void) +{ + struct ifconf conf = {0}; + assert(net_interface_ioctl(SIOCGIFCONF, (uintptr_t)&conf) == 0); + assert(conf.ifc_len == sizeof(struct ifreq)); + assert(!net_interface_register(2) && !net_interface_register(3)); + struct { struct nlmsghdr header; struct ifinfomsg info; } link={ + .header={.nlmsg_len=sizeof(link),.nlmsg_type=RTM_NEWLINK, + .nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK}, + .info={.ifi_family=AF_UNSPEC,.ifi_index=3,.ifi_change=IFF_UP}}; + assert(netlink_request_error(&link.header)==-LINUX_EPERM); + assert(net_interface_is_up(2) && net_interface_is_up(3)); + current.cap_effective=1ULL << CAP_NET_ADMIN; + assert(!netlink_request_error(&link.header)); + assert(net_interface_is_up(2) && !net_interface_is_up(3)); + struct { struct nlmsghdr header; struct ifinfomsg info; + struct rtattr name; char value[8]; struct rtattr mtu; uint32_t size; } named_link={ + .header={.nlmsg_len=sizeof(named_link),.nlmsg_type=19, + .nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK}, + .info={.ifi_family=AF_UNSPEC,.ifi_flags=IFF_UP,.ifi_change=IFF_UP}, + .name={.rta_len=RTA_LENGTH(5),.rta_type=IFLA_IFNAME},.value="eth1", + .mtu={.rta_len=RTA_LENGTH(4),.rta_type=IFLA_MTU},.size=1500}; + assert(!netlink_request_error(&named_link.header)); + assert(net_interface_is_up(3)); + named_link.size=1400; + named_link.info.ifi_flags=0; + assert(netlink_request_error(&named_link.header)==-LINUX_EOPNOTSUPP); + assert(net_interface_is_up(3)); + named_link.size=1500; + named_link.mtu.rta_type=IFLA_ADDRESS; + assert(netlink_request_error(&named_link.header)==-LINUX_EOPNOTSUPP); + assert(net_interface_is_up(3)); + current.cap_effective=0; + struct ifreq req = {.ifr_name = "eth1"}; + assert(!call(SIOCGIFINDEX, &req) && req.data.value == 3); + assert(!call(SIOCGIFHWADDR, &req) && req.data.addr.sa_data[5] == 3); + struct { + struct nlmsghdr header; + struct ifaddrmsg address; + struct rtattr local; + uint8_t ip[16]; + struct rtattr label; + char name[8]; + } address6 = {.header={.nlmsg_len=sizeof(address6),.nlmsg_flags=NLM_F_CREATE}, + .address={.ifa_family=AF_INET6,.ifa_prefixlen=64,.ifa_index=3}, + .local={.rta_len=RTA_LENGTH(16),.rta_type=IFA_LOCAL},.ip={0x20,1,0xdb,8}, + .label={.rta_len=RTA_LENGTH(5),.rta_type=IFA_LABEL},.name="eth1"}; + assert(!nl_change_address(&address6.header,false)); + strcpy(req.ifr_name, "eth01"); + assert(call(SIOCGIFINDEX, &req) == -LINUX_ENODEV); + + strcpy(req.ifr_name, "eth1"); + assert(call(SIOCGIFTXQLEN, &req) == 0 && req.data.value == 0); + req.data.addr.sa_family = AF_INET; + net_put_u32((uint8_t *)&req.data.addr + 4, 0xc0a8070fu); + assert(call(SIOCSIFADDR, &req) == -LINUX_EPERM); + current.cap_effective = 1ULL << CAP_NET_ADMIN; + set_address("eth0", SIOCSIFADDR, 0x0a00020fu); + set_address("eth0", SIOCSIFNETMASK, 0xffffff00u); + set_address("eth1", SIOCSIFADDR, 0xc0a8070fu); + set_address("eth1", SIOCSIFNETMASK, 0xffffff00u); + struct ifreq list[3]; + conf = (struct ifconf){.ifc_len = sizeof(list), .ifc_buf = (uintptr_t)list}; + assert(!net_interface_ioctl(SIOCGIFCONF, (uintptr_t)&conf)); + assert(conf.ifc_len == sizeof(list)); + assert(!strcmp(list[0].ifr_name, "lo") && !strcmp(list[1].ifr_name, "eth0") && + !strcmp(list[2].ifr_name, "eth1")); + assert(net_get_u32((uint8_t *)&list[1].data.addr + 4) == 0x0a00020fu); + assert(net_get_u32((uint8_t *)&list[2].data.addr + 4) == 0xc0a8070fu); + conf.ifc_len = 2 * sizeof(struct ifreq) + 1; + assert(!net_interface_ioctl(SIOCGIFCONF, (uintptr_t)&conf)); + assert(conf.ifc_len == 2 * sizeof(struct ifreq)); + + struct net_ipv4_path path; + assert(!net_ipv4_route_lookup(0xc0a80732u, 0x0a00020fu, 0, &path)); + assert(path.ifindex == 3 && path.source == 0x0a00020fu && path.next_hop == 0xc0a80732u); + char eth1[] = "eth1"; + struct rtentry route = {.rt_dst.sa_family = AF_INET, .rt_gateway.sa_family = AF_INET, + .rt_flags = RTF_UP | RTF_GATEWAY, .rt_dev = (uintptr_t)eth1}; + net_put_u32((uint8_t *)&route.rt_gateway + 4, 0xc0a80701u); + assert(!net_interface_ioctl(SIOCADDRT, (uintptr_t)&route)); + assert(!net_ipv4_route_lookup(0x08080808u, 0, 0, &path)); + assert(path.ifindex == 3 && path.source == 0xc0a8070fu && path.next_hop == 0xc0a80701u); + assert(!net_interface_ioctl(SIOCDELRT, (uintptr_t)&route)); + assert(net_ipv4_route_lookup(0x08080808u, 0, 0, &path) == -LINUX_ENETUNREACH); + + struct { + struct nlmsghdr header; + struct ifaddrmsg address; + struct rtattr local; + uint32_t ip; + } change = {.header={.nlmsg_len=sizeof(change),.nlmsg_flags=NLM_F_REPLACE}, + .address={.ifa_family=AF_INET,.ifa_prefixlen=24,.ifa_index=3}, + .local={.rta_len=RTA_LENGTH(4),.rta_type=IFA_LOCAL}, + .ip=__builtin_bswap32(0xc0a8080fu)}; + assert(!nl_change_address(&change.header, false)); + assert(net_interface_get(3)->config.local_ip == 0xc0a8080fu); + assert(net_interface_get(3)->config.subnet_mask == 0xffffff00u); + struct { + struct nlmsghdr header; + struct rtmsg route; + struct rtattr destination; + uint32_t dst; + struct rtattr gateway; + uint32_t gw; + struct rtattr output; + uint32_t ifindex; + } route_change = {.header={.nlmsg_len=sizeof(route_change),.nlmsg_flags=NLM_F_CREATE|NLM_F_EXCL}, + .route={.rtm_family=AF_INET,.rtm_dst_len=16,.rtm_table=RT_TABLE_MAIN,.rtm_type=RTN_UNICAST}, + .destination={.rta_len=RTA_LENGTH(4),.rta_type=RTA_DST},.dst=__builtin_bswap32(0xc6330000u), + .gateway={.rta_len=RTA_LENGTH(4),.rta_type=RTA_GATEWAY},.gw=__builtin_bswap32(0xc0a80801u), + .output={.rta_len=RTA_LENGTH(4),.rta_type=RTA_OIF},.ifindex=3}; + assert(!nl_change_route(&route_change.header,false)); + assert(!net_ipv4_route_lookup(0xc6336409u,0,0,&path)); + assert(path.ifindex==3 && path.next_hop==0xc0a80801u); + + net_interface_get(2)->config.local_ip = 0; + assert(!net_ipv4_route_lookup(UINT32_MAX, 0, 2, &path)); + assert(path.ifindex == 2 && path.source == 0); + struct netlink_socket listener={.groups=RTMGRP_LINK}; + netlink_sockets=&listener; + assert(!nl_initial_snapshot()); + net_interface_remove(3); + netlink_ipv6_changed(); + assert(listener.head && ((struct nlmsghdr *)(void *)listener.head->data)->nlmsg_type==RTM_DELLINK); + struct ifinfomsg *event=(void *)(listener.head->data+sizeof(struct nlmsghdr)); + assert(event->ifi_index==3); + kernel_free(listener.head); listener.head=listener.tail=NULL; + nl_forget_snapshot(); netlink_sockets=NULL; + net_interface_remove(2); + assert(net_interface_next(1) == 0 && net_interface_index("eth0") == 0); + puts("PASS multi-interface IPv4 configuration, enumeration, routes, weak-host source and broadcast"); + return 0; +} diff --git a/tools/tests/net_multinic_data_test.c b/tools/tests/net_multinic_data_test.c new file mode 100644 index 00000000..f1e48033 --- /dev/null +++ b/tools/tests/net_multinic_data_test.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include "../../net/internal.h" +#include "../../net/utility.c" +#include "../../net/link/ethernet.c" +#include "../../net/link/arp.c" +#include "../../net/ipv4/udp.c" + +const uint8_t net_broadcast_mac[6] = {255,255,255,255,255,255}; +uint16_t net_ipv4_id; +static uint64_t clock_ms; +static bool ready[2] = {true,true}; +static const uint8_t device_macs[2][6] = {{2,0,0,0,0,1},{2,0,0,0,0,2}}; +static const uint8_t peer_macs[2][6] = {{2,1,0,0,0,1},{2,1,0,0,0,2}}; +static struct net_interface devices[2] = { + {.index=2,.up=true,.config={.local_ip=0x0a00020f,.subnet_mask=0xffffff00}}, + {.index=3,.up=true,.config={.local_ip=0x0a00030f,.subnet_mask=0xffffff00}} +}; +static uint8_t wire[1514]; +static uint32_t wire_length, wire_index, transmissions; +uint64_t time_uptime_ms(void) { return clock_ms; } +uint32_t netdev_next(uint32_t after) { return after < 2 ? 2 : after == 2 ? 3 : 0; } +const uint8_t *netdev_mac(uint32_t index) { assert(index==2 || index==3); return device_macs[index-2]; } +bool net_interface_is_ready(uint32_t index) { return index>=2 && index<=3 && ready[index-2]; } +struct net_interface *net_interface_get(uint32_t index) { return index>=2 && index<=3 ? &devices[index-2] : NULL; } +int net_interface_get_config(uint32_t index, struct reliefos_net_config *out) +{ + struct net_interface *device=net_interface_get(index); + if (!device) return -LINUX_ENODEV; + *out=device->config; + return 0; +} +bool net_interface_name(uint32_t index, char out[16]) +{ if (index<2 || index>3) return false; strcpy(out,index==2 ? "eth0" : "eth1"); return true; } +int netdev_send(uint32_t index, const void *frame, uint32_t length) +{ + assert(net_interface_is_ready(index) && length<=sizeof(wire)); + wire_index=index; wire_length=length; ++transmissions; + memcpy(wire,frame,length); + return 0; +} +int net_ipv4_route_lookup(uint32_t destination, uint32_t source, uint32_t scope, struct net_ipv4_path *out) +{ + uint32_t index=scope ? scope : (destination & 0xffffff00)==0x0a000300 ? 3 : 2; + if (!net_interface_is_ready(index)) return -LINUX_ENETDOWN; + *out=(struct net_ipv4_path){.ifindex=index,.source=source ? source : devices[index-2].config.local_ip, + .destination=destination,.next_hop=destination}; + return 0; +} +void net_poll_once(struct net_arp_wait *arp, struct net_ping_wait *ping, + struct net_udp_wait *udp, struct net_tcp_wait *tcp) +{ (void)arp; (void)ping; (void)udp; (void)tcp; ++clock_ms; } +void net_udp_input(uint32_t source, uint32_t destination, uint16_t source_port, + uint16_t destination_port, const void *data, uint32_t length, int index) +{ (void)source; (void)destination; (void)source_port; (void)destination_port; (void)data; (void)length; (void)index; } + +static void neighbor(uint32_t index, uint32_t ip, const uint8_t mac[6], uint16_t operation) +{ + uint8_t frame[42]={0}; + net_write_eth_on(index,frame,device_macs[index-2],ETH_TYPE_ARP); + net_put_u16(frame+14,ARP_HTYPE_ETHERNET); net_put_u16(frame+16,ETH_TYPE_IPV4); + frame[18]=6; frame[19]=4; net_put_u16(frame+20,operation); + memcpy(frame+22,mac,6); net_put_u32(frame+28,ip); + net_put_u32(frame+38,devices[index-2].config.local_ip); + net_handle_arp_on(index,frame,sizeof(frame),NULL); +} +int main(void) +{ + /* The same next-hop address is valid on two separate Ethernet links. */ + const uint32_t peer=0x0a000002; + neighbor(2,peer,peer_macs[0],ARP_OPER_REPLY); + neighbor(3,peer,peer_macs[1],ARP_OPER_REPLY); + uint8_t mac[6]; + assert(!net_resolve_mac_on(2,peer,20,mac) && !memcmp(mac,peer_macs[0],6)); + assert(!net_resolve_mac_on(3,peer,20,mac) && !memcmp(mac,peer_macs[1],6)); + assert(!transmissions); + net_arp_cache_clear_on(2); + assert(!net_resolve_mac_on(3,peer,20,mac) && !memcmp(mac,peer_macs[1],6)); + assert(!transmissions); + neighbor(3,peer,peer_macs[1],ARP_OPER_REQUEST); + assert(wire_index==3 && wire_length==42 && net_get_u16(wire+20)==ARP_OPER_REPLY); + assert(!memcmp(wire+6,device_macs[1],6)); + assert(net_get_u32(wire+28)==devices[1].config.local_ip); + neighbor(3,0x0a000302,peer_macs[1],ARP_OPER_REPLY); + assert(!net_ipv4_send_udp_on(3,0,0x0a000302,55000,55001,"second",6)); + assert(wire_index==3 && wire_length==48 && !memcmp(wire,peer_macs[1],6)); + assert(!memcmp(wire+6,device_macs[1],6) && net_get_u32(wire+26)==devices[1].config.local_ip); + uint32_t before=transmissions; + ready[0]=false; + assert(net_resolve_mac_on(2,peer,20,mac)==-LINUX_ENETDOWN); + assert(!net_ipv4_send_udp_on(3,0,0x0a000302,55000,55001,"ok",2)); + assert(transmissions==before+1 && wire_index==3); + puts("PASS per-interface ARP neighbors, reply identity, UDP output and independent link failure"); + return 0; +} diff --git a/tools/tests/net_packet_test.c b/tools/tests/net_packet_test.c index 0094cf77..680cbd63 100644 --- a/tools/tests/net_packet_test.c +++ b/tools/tests/net_packet_test.c @@ -3,6 +3,7 @@ #include #include #include "../../net/socket/packet.c" +#include "../../net/interface/socket.c" static struct task current; static struct task_file description; static uint8_t sent[1514]; @@ -19,12 +20,44 @@ bool user_range_writable(uint64_t p, uint64_t n) { return user_range_ok(p,n); } uint64_t time_ticks(void) { return 1; } void sched_sleep_current_until(uint64_t deadline) { (void)deadline; } void net_poll_packets(void) {} +void kernel_poll_readiness_notify(void) {} +uint32_t net_ipv6_addresses(struct net_ipv6_address *out, uint32_t capacity) +{ (void)out; (void)capacity; return 0; } +int net_ipv6_multicast_change(const struct in6_addr *address, uint32_t index, int delta) +{ (void)address; (void)index; (void)delta; return 0; } +int net_ipv6_route(const struct in6_addr *destination, uint32_t scope, + const struct in6_addr *source, struct net_ipv6_path *path) +{ (void)destination; (void)scope; (void)source; (void)path; return -LINUX_ENETUNREACH; } +int net_ipv6_output(const struct net_ipv6_path *path, uint8_t protocol, + const void *data, uint32_t length, int hops, uint32_t flow, + bool dont_fragment, bool multicast_loop) +{ (void)path; (void)protocol; (void)data; (void)length; (void)hops; (void)flow; + (void)dont_fragment; (void)multicast_loop; return -LINUX_ENETUNREACH; } +uint16_t net_ipv6_checksum(const struct in6_addr *source, const struct in6_addr *destination, + uint8_t protocol, const void *data, uint32_t length) +{ (void)source; (void)destination; (void)protocol; (void)data; (void)length; return 0; } bool net_interface_ready(void) { return true; } const uint8_t *e1000_mac(void) { static const uint8_t mac[6] = {2,3,4,5,6,7}; return mac; } int e1000_send(const void *p, uint32_t n) { assert(n <= sizeof(sent)); memcpy(sent,p,n); sent_length=n; return 0; } int net_get_config(struct reliefos_net_config *c) { *c=(struct reliefos_net_config){0}; return 0; } int net_ipv4_send_raw(uint32_t d, uint32_t s, uint8_t p, bool h, const void *b, uint32_t n) { (void)d; (void)s; (void)p; (void)h; (void)b; return n; } +bool net_interface_exists(uint32_t index) { return index >= 1 && index <= 3; } +bool net_interface_is_ready(uint32_t index) { return index >= 2 && index <= 3; } +uint32_t net_interface_index(const char *name) +{ return !strcmp(name,"lo") ? 1 : !strcmp(name,"eth0") ? 2 : !strcmp(name,"eth1") ? 3 : 0; } +bool net_interface_name(uint32_t index, char out[16]) +{ if (!net_interface_exists(index)) return false; strcpy(out,index == 1 ? "lo" : index == 2 ? "eth0" : "eth1"); return true; } +uint32_t net_interface_for_ipv4(uint32_t ip) { return (ip >> 24) == 127 ? 1 : 0; } +uint32_t netdev_next(uint32_t after) { return after < 2 ? 2 : after == 2 ? 3 : 0; } +const uint8_t *netdev_mac(uint32_t index) +{ static const uint8_t macs[2][6] = {{2,3,4,5,6,7},{2,3,4,5,6,8}}; return macs[index == 3]; } +static uint32_t sent_interface; +int netdev_send(uint32_t index, const void *data, uint32_t length) +{ sent_interface = index; return e1000_send(data, length); } +int net_ipv4_send_raw_on(uint32_t index, uint32_t d, uint32_t s, uint8_t p, + bool h, const void *data, uint32_t length) +{ (void)index; return net_ipv4_send_raw(d,s,p,h,data,length); } int main(void) { assert(syscall_packet(__NR_socket, AF_PACKET, SOCK_DGRAM, 8,0,0,0) == -LINUX_EPERM); @@ -35,6 +68,18 @@ int main(void) assert(description.fd_flags == RELIEFOS_FD_CLOEXEC); struct sockaddr_ll dest = {.sll_family=AF_PACKET,.sll_ifindex=2,.sll_protocol=8,.sll_halen=6, .sll_addr={255,255,255,255,255,255}}; + dest.sll_ifindex = 3; + assert(syscall_packet(__NR_bind,3,(uintptr_t)&dest,sizeof(dest),0,0,0) == 0); + const char second[] = "second link"; + assert(task_packet_send(&description,second,sizeof(second),0,(uintptr_t)&dest,sizeof(dest)) == sizeof(second)); + assert(sent_interface == 3 && !memcmp(sent+6,netdev_mac(3),6)); + net_packet_input_on(2,sent,sent_length); + assert(task_packet_available(&description) == 0); + net_packet_input_on(3,sent,sent_length); + struct sockaddr_ll ingress; uint32_t ingress_length=sizeof(ingress); char second_out[sizeof(second)]; + assert(task_packet_recv(&description,second_out,sizeof(second_out),0,(uintptr_t)&ingress,(uintptr_t)&ingress_length) == sizeof(second)); + assert(ingress.sll_ifindex == 3 && !memcmp(second_out,second,sizeof(second))); + dest.sll_ifindex = 2; assert(syscall_packet(__NR_bind,3,(uintptr_t)&dest,-1,0,0,0) == -LINUX_EINVAL); assert(syscall_packet(__NR_bind,3,(uintptr_t)&dest,sizeof(dest),0,0,0) == 0); const char data[]="packet regression"; diff --git a/tools/tests/r8169_test.c b/tools/tests/r8169_test.c index b0f9960f..34016e8d 100644 --- a/tools/tests/r8169_test.c +++ b/tools/tests/r8169_test.c @@ -6,6 +6,7 @@ #include #include #include "../../drivers/net/r8169/internal.h" +#include extern const struct reliefos_driver_module r8169_driver_module; @@ -13,6 +14,10 @@ static uint8_t registers[0x10000]; static uint16_t mac_ocp[32768], phy_ocp[32768], ephy[128], phy[32]; static uint32_t csi[1024]; static uint16_t device_id, pci_command, original_command; +static int multi_mode; +static const struct reliefnt_netdev_ops *networks[2]; +static void *contexts[2]; +static unsigned network_count; static uint16_t pmcsr; static bool power_capability; static unsigned live, maps, unmaps, allocation_attempts, allocation_failure; @@ -20,7 +25,10 @@ static unsigned kicks, resets, mdio_accesses; static int reset_stuck, indirect_stuck, mapping_failure, registration_error, teardown_error; static uint64_t next_physical = 0x140000000ULL; static const uint64_t bar = 0xc000000000ULL; -static const struct reliefos_driver_e1000_ops *network; +static const struct reliefnt_netdev_ops *network; +static void *network_context; +void *kernel_malloc(size_t size) { return calloc(1, size); } +void kernel_free(void *p) { free(p); } static struct allocation { uint64_t physical; unsigned pages; void *bytes; } memory[8]; static uint32_t raw32(unsigned offset) @@ -72,6 +80,8 @@ uint32_t rtl_read32(const struct rtl_device *d, unsigned offset) { assert((d->registers == registers || d->io_base == 0xe000) && !(offset & 3) && offset + 4 <= d->mmio_bytes); + if (offset == 0x40 && multi_mode && d->pci.slot==4 && !d->chip_name) + return 0x54100800u; /* This second BDF has its own initial XID. */ if (offset == 0x40) return raw32(offset) | 0x800; /* No outstanding TX DMA in the boundary model. */ return raw32(offset); @@ -225,9 +235,17 @@ static int find(uint16_t vendor, uint16_t id, struct reliefos_driver_pci_device return 0; } +static int enumerate(uint32_t index, struct reliefos_driver_pci_device *out) +{ + if (index > (unsigned)multi_mode) return -19; + int rc=find(0x10ec, device_id, out); + if (!rc) out->slot=(uint8_t)(3+index); + return rc; +} + static uint16_t pci_read16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset) { - assert(b == 2 && s == 3 && f == 0); + assert(b == 2 && (s == 3 || (multi_mode && s == 4)) && f == 0); if (power_capability) { if (offset == 6) return 0x10; @@ -241,7 +259,7 @@ static uint16_t pci_read16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset) static void pci_write16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset, uint16_t value) { - assert(b == 2 && s == 3 && f == 0); + assert(b == 2 && (s == 3 || (multi_mode && s == 4)) && f == 0); if (power_capability && offset == 0x44) { pmcsr = (value & ~0x8000u) | (pmcsr & ~value & 0x8000u); return; @@ -252,7 +270,7 @@ static void pci_write16(uint8_t b, uint8_t s, uint8_t f, uint8_t offset, uint16_ static uint32_t pci_read32(uint8_t b, uint8_t s, uint8_t f, uint8_t offset) { - assert(b == 2 && s == 3 && f == 0); + assert(b == 2 && (s == 3 || (multi_mode && s == 4)) && f == 0); if (power_capability && offset == 0x34) return 0x40; if (offset == 0x10) @@ -276,7 +294,8 @@ static void *map(uint64_t physical, uint64_t bytes) static void unmap(void *pointer, uint64_t bytes) { assert(pointer == registers && bytes <= sizeof(registers)); - assert(!(pci_command & 4) && !live && maps == unmaps + 1); + assert(!(pci_command & 4)); + assert(multi_mode || (!live && maps == unmaps + 1)); ++unmaps; } @@ -309,12 +328,13 @@ static void io_outl(uint16_t port, uint32_t value) store32(port - 0xe000, value); } -static int register_network(const struct reliefos_driver_e1000_ops *ops) +int driver_register_netdev(const struct reliefnt_netdev_ops *ops, void *context, uint32_t *id) { if (registration_error) return registration_error; - assert(!network); - network = ops; + assert(multi_mode || !network); + network = ops; network_context = context; *id = 2; + if (multi_mode) { assert(network_count<2); networks[network_count]=ops; contexts[network_count++]=context; } return 0; } @@ -334,16 +354,17 @@ static void failed_stop(int error) static const struct reliefos_driver_kernel_api api = { .abi_version = RELIEFOS_DRIVER_ABI_VERSION, .struct_size = sizeof(api), - .pci_find = find, .pci_read16 = pci_read16, .pci_write16 = pci_write16, + .pci_find = find, .pci_enumerate = enumerate, .pci_read16 = pci_read16, .pci_write16 = pci_write16, .pci_read32 = pci_read32, .map_mmio = map, .unmap_mmio = unmap, .alloc_dma = allocate, .free_dma = release, .phys_to_virt = alias, .sleep_ms = sleep_ms, .console_log = log_message, - .register_e1000 = register_network, .report_teardown_failure = failed_stop, + .report_teardown_failure = failed_stop, }; static void fixture(uint16_t id, uint32_t xid) { assert(!live && maps == unmaps); + multi_mode=0; network_count=0; memset(registers, 0, sizeof(registers)); memset(mac_ocp, 0, sizeof(mac_ocp)); memset(phy_ocp, 0, sizeof(phy_ocp)); @@ -458,9 +479,9 @@ static void lifecycle(uint16_t id, uint32_t xid) if (rc) fprintf(stderr, "init id=%04x xid=%08x failed %d\n", id, xid, rc); assert(rc == 0 && network && live == 4 && mdio_accesses); - assert(network->is_ready()); - struct reliefos_driver_e1000_info info; - network->get_info(&info); + assert(network->is_ready(network_context)); + struct e1000_info info; + network->get_info(network_context, &info); assert(info.present && info.active && info.vendor_id == 0x10ec && info.device_id == id); assert(!memcmp(info.mac, "\x02\x11\x22\x33\x44\x55", 6)); assert(raw32(0x20) && raw32(0x24) && raw32(0xe4) && raw32(0xe8)); @@ -472,9 +493,9 @@ static void lifecycle(uint16_t id, uint32_t xid) assert(phy_ocp[0xa408 / 2] == 0x0de1 && phy_ocp[0xa412 / 2] == 0x0300); } registers[0x6c] = 0; - assert(!network->is_ready()); + assert(!network->is_ready(network_context)); registers[0x6c] = 2; - assert(network->is_ready()); + assert(network->is_ready(network_context)); reset_stuck = 1; network = NULL; r8169_driver_module.fini(); @@ -541,6 +562,23 @@ int main(void) assert((pci_command & 7) == 5); assert(pmcsr == 0x8000); finish(); + /* Same-ID PCI functions still dispatch through separate ring contexts. */ + fixture(0x8168, 0x54100000); multi_mode=1; + int multi_rc=r8169_driver_module.init(&api); + if (multi_rc || network_count!=2 || live!=8) + fprintf(stderr,"multi init rc=%d registrations=%u live=%u xid=%08x\n",multi_rc,network_count,live,raw32(0x40)); + assert(!multi_rc && network_count==2 && live==8); + struct rtl_device *first=contexts[0], *second=contexts[1]; + assert(first!=second && first->pci.slot==3 && second->pci.slot==4); + assert(first->rx_physical!=second->rx_physical); + first->rx_data[0]=0xa1; first->rx[0].command=RTL_DESC_FIRST|RTL_DESC_LAST|64; + second->rx_data[0]=0xb2; second->rx[0].command=RTL_DESC_FIRST|RTL_DESC_LAST|64; + uint8_t multi_output[128]; uint32_t multi_length; + assert(networks[1]->poll(contexts[1],multi_output,sizeof(multi_output),&multi_length)==1 && + multi_length==60 && multi_output[0]==0xb2); + assert(networks[0]->poll(contexts[0],multi_output,sizeof(multi_output),&multi_length)==1 && + multi_length==60 && multi_output[0]==0xa1); + finish(); puts("r8169: DMA ring wrap, framing, high addresses, failure rollback and lifecycle passed"); return 0; } diff --git a/tools/tests/task8_host_stubs/net-ioctl-user-candidate-3-net-ioctl-user-candidate-3.stub.c b/tools/tests/task8_host_stubs/net-ioctl-user-candidate-3-net-ioctl-user-candidate-3.stub.c index 75a4e012..1ba0b794 100644 --- a/tools/tests/task8_host_stubs/net-ioctl-user-candidate-3-net-ioctl-user-candidate-3.stub.c +++ b/tools/tests/task8_host_stubs/net-ioctl-user-candidate-3-net-ioctl-user-candidate-3.stub.c @@ -22,6 +22,22 @@ __attribute__((noreturn)) void kernel_wait_queue_remove(void) { __builtin_trap() __attribute__((noreturn)) void kernel_wait_queue_wake_all(void) { __builtin_trap(); } /** @brief Trap if the fixture reaches unmodeled net_packet_input. */ __attribute__((noreturn)) void net_packet_input(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture attempts hardware transmission. */ +__attribute__((noreturn)) void netdev_send(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture attempts hardware receive polling. */ +__attribute__((noreturn)) void netdev_poll(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture delivers a received packet frame. */ +__attribute__((noreturn)) void net_packet_input_on(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture delivers an IPv6 frame. */ +__attribute__((noreturn)) void net_ipv6_input_on(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture runs IPv6 discovery maintenance. */ +__attribute__((noreturn)) void net_ipv6_poll(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture selects an IPv6 transport route. */ +__attribute__((noreturn)) void net_ipv6_route(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture computes an IPv6 transport checksum. */ +__attribute__((noreturn)) void net_ipv6_checksum(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture transmits an IPv6 packet. */ +__attribute__((noreturn)) void net_ipv6_output(void) { __builtin_trap(); } /** @brief Trap if the fixture reaches unmodeled net_udp_input. */ __attribute__((noreturn)) void net_udp_input(void) { __builtin_trap(); } /** @brief Trap if the fixture reaches unmodeled sched_sleep_current_until. */ @@ -34,6 +50,8 @@ __attribute__((noreturn)) void storage_inode_get(void) { __builtin_trap(); } __attribute__((noreturn)) void task_packet_available(void) { __builtin_trap(); } /** @brief Trap if the fixture reaches unmodeled task_udp_available. */ __attribute__((noreturn)) void task_udp_available(void) { __builtin_trap(); } +/** @brief Trap if the ioctl-only fixture queries netlink receive bytes. */ +__attribute__((noreturn)) void task_netlink_available(void) { __builtin_trap(); } /** @brief Trap if the fixture reaches unmodeled time_set_wall_clock. */ __attribute__((noreturn)) void time_set_wall_clock(void) { __builtin_trap(); } /** @brief Trap if the fixture reaches unmodeled time_ticks. */ diff --git a/tools/tests/task8_net_ioctl_user_test.c b/tools/tests/task8_net_ioctl_user_test.c index 7936ef92..598f5100 100644 --- a/tools/tests/task8_net_ioctl_user_test.c +++ b/tools/tests/task8_net_ioctl_user_test.c @@ -4,6 +4,7 @@ * Controlled substitutes model only the boundaries asserted by this fixture. */ #include +#define NET_INTERFACE_TEST_EXTERNAL_ALLOC #define main inherited_interface_main #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreturn-type" @@ -37,6 +38,7 @@ static int scoped(unsigned request,uint64_t address) } int main(void) { + assert(net_interface_register(2) == 0); current=(struct task){.kind=TASK_KIND_USER,.pid=45,.syscall_file=&net_file, .syscall_fd=3,.syscall_file_number=LINUX_SYS_IOCTL, .cap_effective=1ULL<= 42 && net_get_u16(sent_frame + 12) == ETH_TYPE_ARP && + net_get_u16(sent_frame + 20) == ARP_OPER_REQUEST) arp_reply_pending = true; return send_error; } int e1000_poll(void *frame, uint32_t capacity, uint32_t *length) -{ (void)frame; (void)capacity; ++network_poll_count; *length = 0; if (advance_poll_clock) ++now_ms; return 0; } +{ + ++network_poll_count; + *length = 0; + if (advance_poll_clock) ++now_ms; + if (!arp_reply_pending) return 0; + assert(capacity >= 42); + arp_reply_pending = false; + uint8_t *reply = frame; + const uint8_t peer[6] = {2,9,8,7,6,5}; + net_memcpy(reply, sent_frame, 42); + net_memcpy(reply, e1000_mac(), 6); + net_memcpy(reply + 6, peer, 6); + net_put_u16(reply + 20, ARP_OPER_REPLY); + net_memcpy(reply + 22, peer, 6); + net_put_u32(reply + 28, net_get_u32(sent_frame + 38)); + net_memcpy(reply + 32, e1000_mac(), 6); + net_put_u32(reply + 38, net_get_u32(sent_frame + 28)); + *length = 42; + return 1; +} void net_udp_input(uint32_t source, uint32_t destination, uint16_t source_port, uint16_t destination_port, const void *data, uint32_t length, int ifindex) { (void)ifindex; (void)source; (void)destination; (void)source_port; (void)destination_port; (void)data; (void)length; } @@ -153,7 +175,7 @@ static void test_proc_network(struct net_socket *socket) assert(strstr(text, "eth0\t0000250a\t00000000\t0001") && strstr(text, "00ffffff")); assert(strstr(text, "eth0\t00000000\t0200250a\t0003")); const uint8_t mac[] = {2, 3, 4, 5, 6, 7}; - net_arp_cache_store(0x0a250002, mac); + net_arp_store_on(2, 0x0a250002, mac); memset(text, 0, sizeof(text)); stream = (struct text_stream){.buffer = text, .capacity = sizeof(text) - 1}; assert(!net_proc_emit(&stream, "arp") && strstr(text, "10.37.0.2")); @@ -264,6 +286,9 @@ static void test_nonblocking_tcp_send_without_ack(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51001; socket->remote_port = 8000; socket->local_seq = 9000; @@ -360,6 +385,9 @@ static void test_tcp_peer_window_and_persist(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51002; socket->remote_port = 8001; socket->local_seq = socket->acked_seq = 12000; @@ -391,6 +419,9 @@ static void test_tcp_peer_window_and_persist(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51003; socket->remote_port = 8002; socket->local_seq = socket->acked_seq = 33000; @@ -455,6 +486,9 @@ static void test_tcp_timeouts_and_read_errors(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51004; socket->remote_port = 8003; socket->local_seq = socket->acked_seq = 55000; @@ -531,6 +565,9 @@ static void test_negative_socket_timeout_seconds_clamp_to_immediate(void) inet->state = RELIEFOS_NET_TCP_ESTABLISHED; inet->local_ip = 0x0a25000f; inet->remote_ip = 0x0a250002; + inet->ifindex = 2; + inet->next_hop = inet->remote_ip; + net_memcpy(inet->dst_mac, e1000_mac(), 6); inet->local_port = 51015; inet->remote_port = 8014; inet->local_seq = inet->acked_seq = 94000; @@ -639,6 +676,9 @@ static void test_tcp_release_then_owner_exit_drains_queued_bytes(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51010; socket->remote_port = 8009; socket->local_seq = socket->acked_seq = initial_seq; @@ -719,6 +759,9 @@ static void test_tcp_fin_retransmit_ack_and_half_close(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51011; socket->remote_port = 8010; socket->local_seq = socket->acked_seq = 91000; @@ -814,6 +857,9 @@ static void test_tcp_peer_half_close_allows_reply(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51014; socket->remote_port = 8013; socket->local_seq = socket->acked_seq = 93000; @@ -888,6 +934,9 @@ static void test_tcp_unacknowledged_fin_is_not_reclaimed(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51012; socket->remote_port = 8011; socket->local_seq = socket->acked_seq = 92000; @@ -923,6 +972,9 @@ static void test_tcp_out_of_window_segment_cannot_ack_data(void) socket->state = RELIEFOS_NET_TCP_ESTABLISHED; socket->local_ip = 0x0a25000f; socket->remote_ip = 0x0a250002; + socket->ifindex = 2; + socket->next_hop = socket->remote_ip; + net_memcpy(socket->dst_mac, e1000_mac(), 6); socket->local_port = 51013; socket->remote_port = 8012; socket->local_seq = 30008; @@ -948,8 +1000,89 @@ static void test_tcp_out_of_window_segment_cannot_ack_data(void) net_socket_clear(socket); } +static void test_tcp_route_gateway_change(void) +{ + struct reliefos_net_config saved = net_config; + const uint8_t first_mac[6] = {2,1,2,3,4,5}; + const uint8_t second_mac[6] = {2,6,7,8,9,10}; + net_config.gateway_ip = 0x0a250002; + net_arp_store_on(2, 0x0a250002, first_mac); + net_arp_store_on(2, 0x0a250003, second_mac); + struct net_socket *socket = net_socket_alloc(7, 1000); + assert(socket); + socket->local_ip = net_config.local_ip; + socket->remote_ip = 0x08080808; + socket->local_port = 55050; + socket->remote_port = 80; + socket->ifindex = 2; + memcpy(socket->dst_mac, first_mac, 6); + assert(net_send_tcp_socket(socket, 1, 1, TCP_FLAG_ACK, NULL, 0) >= 0); + assert(!memcmp(sent_frame, first_mac, 6)); + net_config.gateway_ip = 0x0a250003; + assert(net_send_tcp_socket(socket, 1, 1, TCP_FLAG_ACK, NULL, 0) >= 0); + assert(!memcmp(sent_frame, second_mac, 6)); + net_socket_clear(socket); + net_config = saved; +} + +static void test_tcp_bound_device_local_delivery(void) +{ + struct net_socket *listener = net_socket_alloc(7, 1000); + struct net_socket *client = net_socket_alloc(7, 1000); + assert(listener && client); + uint32_t index = 2; + assert(!net_socket_bound_device(listener->handle, true, &index)); + assert(!tcp_bind_port(listener, net_config.local_ip, 55051)); + assert(!net_socket_listen_fd(listener->handle, 2)); + assert(net_socket_connect_fd(client->handle, net_config.local_ip, 55051, true) == -LINUX_EINPROGRESS); + for (unsigned pass = 0; pass < 8 && net_tcp_loop_pending(); ++pass) + net_tcp_loop_service(); + assert(client->state == RELIEFOS_NET_TCP_ESTABLISHED); + int child_handle = net_socket_accept_peek(listener->handle); + assert(child_handle > 0); + struct net_socket *child = net_socket_find(child_handle, 0, 1); + assert(child && child->bound_ifindex == 2 && child->ifindex == 2); + net_socket_clear(child); + net_socket_clear(client); + net_socket_clear(listener); +} + +static void test_tcp_matching_and_scope_isolation(void) +{ + struct net_socket *first = net_socket_alloc(7, 1000); + struct net_socket *second = net_socket_alloc(7, 1000); + assert(first && second); + first->state = second->state = RELIEFOS_NET_TCP_ESTABLISHED; + first->local_ip = second->local_ip = 0x0a25000f; + first->remote_ip = second->remote_ip = 0x0a250002; + first->local_port = second->local_port = 55052; + first->remote_port = second->remote_port = 80; + first->bound_ifindex = 2; + second->bound_ifindex = 3; + assert(net_socket_match(2,0x0a250002,0x0a25000f,80,55052) == first); + assert(net_socket_match(3,0x0a250002,0x0a25000f,80,55052) == second); + first->resolving_neighbor = true; + unsigned before = sent_count; + assert(net_send_tcp_socket(first,1,1,TCP_FLAG_ACK,NULL,0) == -LINUX_EAGAIN); + net_socket_service(first); + assert(sent_count == before); + net_socket_clear(second); + net_socket_clear(first); + first = net_socket_alloc(7,1000); + assert(first && !net_socket_set_ipv6(first->handle,true)); + uint32_t index=2; + assert(!net_socket_bound_device(first->handle,true,&index)); + struct sockaddr_in6 peer={.sin6_family=AF_INET6,.sin6_port=__builtin_bswap16(80), + .sin6_scope_id=1,.sin6_addr={.s6_addr={0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}}; + assert(net_socket_connect6_fd(first->handle,&peer,true) == -LINUX_EINVAL); + net_socket_clear(first); +} + int main(void) { + static struct net_interface test_interface = {.index=2, .up=true, + .config={.local_ip=0x0a25000f,.subnet_mask=0xffffff00,.gateway_ip=0x0a250002}}; + interfaces = &test_interface; struct net_socket *s = net_socket_alloc(7, 1000); bool interface_was_up = net_interface_up; assert(s); @@ -965,6 +1098,9 @@ int main(void) assert(net_socket_find(handle, 0, 1) == s); s->state = RELIEFOS_NET_TCP_ESTABLISHED; s->local_ip = 0x0a25000f; s->remote_ip = 0x0a250002; + s->ifindex = 2; + s->next_hop = s->remote_ip; + net_memcpy(s->dst_mac, e1000_mac(), 6); s->local_port = 51000; s->remote_port = 8000; s->remote_seq = 500; s->local_seq = s->acked_seq = 100; s->rx_len = NET_SOCKET_RX_CAP - 2; @@ -1013,6 +1149,10 @@ int main(void) assert(s && buffers == 3); net_socket_clear(s); assert(buffers == 0); + net_config = (struct reliefos_net_config){.local_ip=0x0a25000f,.subnet_mask=0xffffff00,.gateway_ip=0x0a250002}; + test_tcp_route_gateway_change(); + test_tcp_bound_device_local_delivery(); + test_tcp_matching_and_scope_isolation(); test_nonblocking_tcp_send_without_ack(); test_tcp_peer_window_and_persist(); test_tcp_timeouts_and_read_errors(); @@ -1033,3 +1173,22 @@ int main(void) int syscall_unix_path(struct vfs_namespace *ns,struct task *task,const char *name, bool create,struct vfs_path *out) { (void)ns;(void)task;(void)name;(void)create;(void)out;abort(); } + +uint32_t netdev_next(uint32_t after) { return after < 2 ? 2 : 0; } +int netdev_is_ready(uint32_t index) { return index == 2; } +const uint8_t *netdev_mac(uint32_t index) { (void)index; return e1000_mac(); } +void netdev_get_info(uint32_t index, struct e1000_info *out) +{ if (index == 2) e1000_get_info(out); else *out = (struct e1000_info){0}; } +int netdev_send(uint32_t index, const void *data, uint32_t length) +{ (void)index; return e1000_send(data, length); } +int netdev_poll(uint32_t index, void *frame, uint32_t capacity, uint32_t *length) +{ (void)index; return e1000_poll(frame, capacity, length); } +void netdev_counters(uint32_t index, struct net_proc_counters *out) +{ (void)index; *out = (struct net_proc_counters){0}; } +void net_ipv6_input_on(uint32_t index, const uint8_t *frame, uint32_t length) +{ (void)index; net_ipv6_input(frame,length); } +void net_packet_input_on(uint32_t index, const void *frame, uint32_t length) +{ (void)index; net_packet_input(frame,length); } +int net_ipv6_link_changed_on(uint32_t index, bool up) { (void)index; (void)up; return 0; } +void net_ipv6_detached_on(uint32_t index) { (void)index; } +void netlink_ipv6_changed(void) {} diff --git a/tools/tests/udp_socket_test.c b/tools/tests/udp_socket_test.c index 29826e75..fb028dad 100644 --- a/tools/tests/udp_socket_test.c +++ b/tools/tests/udp_socket_test.c @@ -3,6 +3,7 @@ #include #include #include "../../net/socket/udp.c" +#include "../../net/interface/socket.c" static struct task current; static struct task_file files[80]; @@ -51,17 +52,67 @@ int net_ipv4_send_udp(uint32_t source, uint32_t destination, uint16_t source_por uint16_t destination_port, const void *data, uint32_t length) { (void)source; (void)destination; (void)source_port; (void)destination_port; (void)data; (void)length; return -LINUX_ENETDOWN; } +uint32_t net_interface_for_ipv4(uint32_t ip) +{ return (ip >> 24) == 127 ? 1 : ip == 0x0a00020f ? 2 : ip == 0x0a00030f ? 3 : 0; } +bool net_interface_exists(uint32_t index) { return index >= 1 && index <= 3; } +uint32_t net_interface_index(const char *name) +{ return !strcmp(name,"lo") ? 1 : !strcmp(name,"eth0") ? 2 : !strcmp(name,"eth1") ? 3 : 0; } +bool net_interface_name(uint32_t index, char out[16]) +{ if (!net_interface_exists(index)) return false; strcpy(out, index == 1 ? "lo" : index == 2 ? "eth0" : "eth1"); return true; } +uint32_t netdev_next(uint32_t after) { return after < 2 ? 2 : after == 2 ? 3 : 0; } +int net_ipv4_route_lookup(uint32_t destination, uint32_t source, uint32_t scope, struct net_ipv4_path *path) +{ uint32_t index=scope ? scope : 2; *path=(struct net_ipv4_path){.ifindex=index, + .source=source ? source : index==3 ? 0x0a00030f : 0x0a00020f,.destination=destination,.next_hop=destination}; return 0; } +int net_ipv4_send_udp_on(uint32_t index, uint32_t source, uint32_t destination, + uint16_t source_port, uint16_t destination_port, + const void *data, uint32_t length) +{ (void)index; return net_ipv4_send_udp(source,destination,source_port,destination_port,data,length); } + static long call(uint64_t nr, uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) { return syscall_udp(nr, a, b, c, d, e, 0); } static int open_socket(void) { return (int)call(__NR_socket, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, 0, 0); } +static void test_udp_device_local_delivery(void) +{ + int sender = open_socket(), receiver = open_socket(); + assert(sender >= 0 && receiver >= 0); + const char device[] = "eth1"; + assert(!call(__NR_setsockopt, receiver, SOL_SOCKET, SO_BINDTODEVICE, (uintptr_t)device, sizeof(device))); + struct sockaddr_in local = {.sin_family=AF_INET,.sin_port=swap16(55009), + .sin_addr.s_addr=swap32(0x0a00030f)}; + assert(!call(__NR_bind, receiver, (uintptr_t)&local, sizeof(local), 0, 0)); + assert(task_udp_send(&files[sender], "local", 5, 0, (uintptr_t)&local, sizeof(local)) == 5); + char result[8]; + assert(task_udp_recv(&files[receiver], result, sizeof(result), 0, 0, 0) == 5); + assert(!memcmp(result,"local",5)); + assert(!call(__NR_setsockopt, sender, SOL_SOCKET, SO_BINDTODEVICE, (uintptr_t)device, sizeof(device))); + assert(task_udp_send(&files[sender], "bound", 5, 0, (uintptr_t)&local, sizeof(local)) == 5); + assert(task_udp_recv(&files[receiver], result, sizeof(result), 0, 0, 0) == 5); + assert(!memcmp(result,"bound",5)); + task_udp_release(&files[sender]); files[sender].used=0; + task_udp_release(&files[receiver]); files[receiver].used=0; + loopback_bytes = loopback_packets = 0; +} + int main(void) { current.cap_effective = 1ULL << CAP_NET_BIND_SERVICE; current.euid = 1234; + test_udp_device_local_delivery(); int a = open_socket(), b = open_socket(); assert(a >= 0 && b >= 0 && a != b); + const char secondary[] = "eth1"; + assert(call(__NR_setsockopt, a, SOL_SOCKET, SO_BINDTODEVICE, + (uintptr_t)secondary, sizeof(secondary)) == 0); + char selected[16] = {0}; + uint32_t selected_size = sizeof(selected); + assert(call(__NR_getsockopt, a, SOL_SOCKET, SO_BINDTODEVICE, + (uintptr_t)selected, (uintptr_t)&selected_size) == 0); + assert(selected_size == sizeof(secondary) && !strcmp(selected, secondary)); + assert(call(__NR_setsockopt, a, SOL_SOCKET, SO_BINDTODEVICE, (uintptr_t)"", 1) == -LINUX_EPERM); + current.cap_effective |= 1ULL << CAP_NET_RAW; + assert(call(__NR_setsockopt, a, SOL_SOCKET, SO_BINDTODEVICE, (uintptr_t)"", 1) == 0); assert(files[a].fd_flags == 1 && (files[a].flags & RELIEFOS_O_NONBLOCK)); struct sockaddr_in dest = {.sin_family = AF_INET, .sin_port = swap16(55000), .sin_addr = {.s_addr = swap32(0x7f000001)}};