diff --git a/port/zephyr/README.md b/port/zephyr/README.md new file mode 100644 index 0000000..00b9a2b --- /dev/null +++ b/port/zephyr/README.md @@ -0,0 +1,728 @@ +# wolfIP port for Zephyr RTOS + +This directory contains a set of patches that replace Zephyr's native +IPv4/TCP/UDP/ARP/ICMP/DHCPv4 stack with [wolfIP](https://github.com/wolfSSL/wolfIP) +on a per-interface basis. The patches are maintained out-of-tree. + +After applying the patches, any Zephyr application that uses BSD sockets +through `CONFIG_NET_SOCKETS` will transparently route its traffic through +wolfIP. ARP, ICMP, IPv4 routing, and (optional) DHCPv4 are also owned by +wolfIP rather than Zephyr. + +The port has been developed and verified against **Zephyr v4.4.0**. + +--- + +## Table of contents + +1. [Design overview](#design-overview) +2. [Repository layout](#repository-layout) +3. [Prerequisites](#prerequisites) +4. [Quick start](#quick-start) +5. [The patches in detail](#the-patches-in-detail) +6. [Application configuration](#application-configuration) +7. [Sample: `wolfip_tap_echo`](#sample-wolfip_tap_echo) +8. [Building for a hardware target](#building-for-a-hardware-target) +9. [Net-shell commands](#net-shell-commands) +10. [API reference](#api-reference) +11. [Known limitations](#known-limitations) +12. [Maintenance notes](#maintenance-notes) +13. [Troubleshooting](#troubleshooting) + +--- + +## Design overview + +### The problem with Zephyr's native stack + +Zephyr's networking subsystem assumes its native L3+ (IPv4, IPv6, TCP, UDP, +ICMP) implementations own each interface. The `struct net_if_dev` carries +the IPv4 configuration, the ARP cache lives inside the Ethernet L2 module +in `subsys/net/l2/ethernet/`, and the per-protocol dispatch happens in +`subsys/net/ip/net_core.c::process_data()` after L2 demux. Bolting wolfIP +on top by diverting *after* L2 processing has structural problems: + +- Zephyr's ARP consumes ARP frames before any L2-divert hook fires, + so wolfIP never learns the peer MAC and can never reply. +- The Ethernet header is stripped by `net_buf_pull(pkt->frags, hdr_len)` + inside `ethernet_recv()` before any post-L2 hook runs, so wolfIP — which + expects raw Ethernet frames — would parse the IP header as if it were + the L2 header. +- Two stacks end up owning the same MAC: ambiguous, racy, and a + debugging nightmare. + +### The fix: wolfIP owns the interface end-to-end + +This port introduces **`NET_L2_WOLFIP`** — a thin L2 module under +`subsys/net/l2/wolfip/` that, on `recv`, linearises the frame and hands +the full raw bytes (Ethernet header included) to `wolfip_zephyr_l2_input`; +on `send`, it calls the underlying Ethernet driver's `api->send` directly +because wolfIP has already built the L2 header. + +Attaching wolfIP to a Zephyr interface (`wolfip_attach_iface()`) performs +a one-time runtime swap of the interface's L2 pointer: + +```c +const struct net_l2 **l2_ptr = (const struct net_l2 **)&iface->if_dev->l2; +*l2_ptr = &NET_L2_GET_NAME(WOLFIP_L2); +``` + +Yes, this casts away `const`. Zephyr offers no public runtime-L2-swap +API, and `iface->if_dev->l2` is declared `const struct net_l2 * const`. +Since the pointer is read at runtime via dereference (the compiler cannot +constant-fold it), the cast is practically safe. + +After the swap, every frame on this interface routes: + +``` +eth driver → NET_L2_WOLFIP.recv → wolfip_zephyr_l2_input → +wolfIP_recv_ex → wolfIP_poll → socket-offload callback / event +``` + +…and socket calls from the application (`socket()`, `bind()`, `connect()`, +`send()`, `recv()`, `close()`, etc.) route through Zephyr's existing +socket-offload mechanism into `wolfip_socket_fd_op_vtable`. + +### Why the L2 calls `wolfIP_poll` synchronously between frames + +wolfIP fires its registered socket callbacks from `wolfIP_poll`'s +"step 3" loop, not from `wolfIP_recv_ex` itself. wolfIP's listener +design is deliberately sequential: when a SYN arrives the listener +itself becomes the SYN_RCVD socket for the in-flight connection. If +the listener's callback fires only on a periodic timer tick, the peer's +final ACK (and possibly data + FIN in a fast LAN) can land before the +application's `accept()` runs, stranding the connection in ESTABLISHED +or CLOSE_WAIT before wolfIP can hand it off to a fresh socket. The +port works around this by calling `wolfIP_poll` synchronously after +every `wolfIP_recv_ex`, and by pre-accepting in the listener callback +(see `wolfip_socket_event_cb` in `wolfip_zephyr.c`). + +### What is *not* affected + +- Zephyr's net_if infrastructure, net_pkt allocator, the Ethernet + driver model, NET_NATIVE, NET_MGMT events, and the socket-offload + framework are all kept enabled. The port only **disables** the L3+ + symbols (`NET_IPV4`, `NET_TCP`, `NET_UDP`, `NET_ARP`, `NET_DHCPV4`, + …) — the rest of the network subsystem still drives the driver and + serves BSD sockets. +- IPv6 is out of scope and untouched. + +--- + +## Repository layout + +``` +wolfip/ +├── port/ +│ └── zephyr/ +│ ├── README.md <-- you are here +│ └── patches/ +│ ├── 0001-wolfip-glue-and-public-api.patch +│ ├── 0002-net-l2-wolfip-module.patch +│ ├── 0003-net-shell-wolfip.patch +│ └── 0004-sample-wolfip-tap-echo.patch +└── src/ + └── wolfip.c (the wolfIP stack itself, unchanged by these patches) +``` + +The patches are applied **into a Zephyr v4.4.0 checkout**, not into wolfIP. +The Zephyr-side build wires wolfIP into its library tree via +`add_subdirectory_ifdef(CONFIG_WOLFIP wolfip)` and expects the wolfIP +source tree to live next to the Zephyr tree on disk (see +[Quick start](#quick-start)). + +--- + +## Prerequisites + +| Component | Tested version | Notes | +|---|---|---| +| Zephyr RTOS | v4.4.0 | The patches assume this exact tree. | +| Zephyr SDK / toolchain | matched to your board | `native_sim` only needs host gcc. | +| west | ≥ 0.14 | for `west build`. | +| wolfIP | tip of `master` at port time | Lives next to the Zephyr tree. | +| net-tools | from Zephyr | Provides `net-setup.sh` / `zeth.conf` for TAP testing. | + +For the `native_sim` sample you also need: + +- Linux with `CAP_NET_ADMIN` (or `sudo`) to bring up the `zeth` TAP +- `iproute2`, `nc`/`ncat`, `ping` (for end-to-end testing) + +--- + +## Quick start + +This walks through bringing up the sample on `native_sim` from scratch. + +### 1. Layout the source trees + +The patches assume this directory layout: + +``` +/ +├── wolfip/ <-- this repo +└── zephyr-v4.4.0/ <-- a clean Zephyr v4.4.0 west workspace + └── zephyr/ <-- the Zephyr tree itself +``` + +The wolfIP integration library's CMakeLists hard-codes the wolfIP +source path as `${ZEPHYR_BASE}/../../wolfip`. If your layout differs +either symlink it or edit +`subsys/net/lib/wolfip/CMakeLists.txt:5` after applying the patches. + +```bash +mkdir -p ~/src && cd ~/src +git clone https://github.com/wolfSSL/wolfIP.git wolfip + +# Zephyr v4.4.0 — use west init OR git directly +mkdir zephyr-v4.4.0 && cd zephyr-v4.4.0 +west init -m https://github.com/zephyrproject-rtos/zephyr --mr v4.4.0 +west update +``` + +### 2. Apply the patches + +```bash +cd ~/src/zephyr-v4.4.0/zephyr +for p in ~/src/wolfip/port/zephyr/patches/*.patch; do + git apply --whitespace=nowarn "$p" || exit 1 +done +``` + +Apply order matters: 0001 → 0002 → 0003 → 0004. The numbering is +significant — 0002 references symbols declared by 0001, etc. + +If you prefer `git am` you can pipe each patch through it; the +`Subject:` line at the top of each patch is `git am`-compatible. + +### 3. Build the sample + +```bash +cd ~/src/zephyr-v4.4.0/zephyr +source ../.venv/bin/activate # or however you activate west's venv +west build -b native_sim -p auto samples/net/sockets/wolfip_tap_echo +``` + +### 4. Set up the TAP interface + +Zephyr's `net-tools` provides `net-setup.sh` which creates a `zeth` +TAP at `192.0.2.2/24`. The sample binds to `192.0.2.1/24` on the +Zephyr side. + +```bash +# In a separate terminal, from the zephyr/net-tools clone: +sudo ./net-setup.sh +# Confirm: +ip addr show zeth +# inet 192.0.2.2/24 brd 192.0.2.255 scope global zeth +``` + +### 5. Run the binary + +```bash +./build/zephyr/zephyr.exe --eth-if=zeth +``` + +You should see: + +``` +*** Booting Zephyr OS build v4.4.0 *** + wolfip_tap_echo: wolfIP enabled on iface 1 + wolfip_tap_echo: wolfIP: configured 192.0.2.1/255.255.255.0 gw 192.0.2.2 + wolfip_tap_echo: TCP echo listening on 192.0.2.1:4242 + wolfip_tap_echo: UDP echo listening on 192.0.2.1:4242 +uart connected to pseudotty: /dev/pts/N +``` + +### 6. Test end-to-end + +```bash +# ICMP +ping -c 3 192.0.2.1 + +# UDP echo +echo "hello-udp" | nc -u -w 1 192.0.2.1 4242 + +# TCP echo +echo "hello-tcp" | nc -q 1 192.0.2.1 4242 + +# Net shell — connect to the pseudotty path printed above +screen /dev/pts/N # or `cu`, `picocom`, `socat`, … +uart:~$ net iface +uart:~$ net ipv4 +uart:~$ net arp lookup 1 192.0.2.2 +uart:~$ net ping -c 3 192.0.2.2 +``` + +If all four work, the port is functioning correctly. + +--- + +## The patches in detail + +Each patch is a `git diff`-format unified diff with a `Subject:` header +and a short rationale at the top. They apply in numeric order and each +builds on the previous one. + +### `0001-wolfip-glue-and-public-api.patch` + +The wolfIP runtime glue, control wrapper API, and public header. +This patch alone gives you a buildable but functionally-inactive +wolfIP runtime — `CONFIG_WOLFIP=y` becomes selectable and the +library compiles, but no interface is yet bound. + +Adds: + +| Path | Purpose | +|---|---| +| `include/zephyr/net/wolfip.h` | Public API — iface attach/detach, `wolfip_ctrl_*` wrappers, accessors. Forward-declares `ip4`, `struct wolfIP`, `struct wolfIP_route_info` so callers needn't include the wolfIP source-tree header. | +| `subsys/net/lib/wolfip/wolfip_zephyr.c` | The integration runtime. Owns the wolfIP stack instance, the worker thread that drives `wolfIP_poll` on a timer, the socket-offload vtable (`socket`/`bind`/`connect`/…/`close` + ioctl/poll prepare/update), DNS offload, and the `wolfip_attach_iface` / `wolfip_detach_iface` / `wolfip_zephyr_l2_input` entrypoints used by patches 2/3/4. | +| `subsys/net/lib/wolfip/wolfip_control.c` | Thin wrappers around the wolfIP public API (`wolfIP_ipconfig_get/set_ex`, `wolfIP_arp_lookup_ex`, `wolfIP_route_*`, `dhcp_client_init`, etc.). Each wrapper takes the wolfIP lock and returns 0/negative-errno. Consumers in the shell call these instead of the wolfIP source-tree symbols directly. | +| `subsys/net/lib/wolfip/Kconfig` | The `menuconfig WOLFIP` symbol with its dependencies (`NETWORKING`, `NET_SOCKETS`) and selects (`NET_SOCKETS_OFFLOAD`, `NET_L2_WOLFIP`), plus tuning knobs for the worker stack/priority/interval and wolfIP instance storage. | +| `subsys/net/lib/wolfip/CMakeLists.txt` | Compiles the integration library, exposes `${WOLFIP_ROOT}` as an include directory, and pulls in `${WOLFIP_ROOT}/src/wolfip.c` itself. | + +Modifies: + +| Path | Change | +|---|---| +| `subsys/net/lib/CMakeLists.txt` | `add_subdirectory_ifdef(CONFIG_WOLFIP wolfip)` | +| `subsys/net/lib/Kconfig` | `source "subsys/net/lib/wolfip/Kconfig"` | + +### `0002-net-l2-wolfip-module.patch` + +The new `NET_L2_WOLFIP` L2 module. With this applied and +`CONFIG_NET_L2_WOLFIP=y`, the L2 layer is registered with the kernel, +and `wolfip_attach_iface` can swap it onto an interface. + +Adds: + +| Path | Purpose | +|---|---| +| `subsys/net/l2/wolfip/wolfip_l2.c` | The four L2 ops — `recv` (linearise, hand to `wolfip_zephyr_l2_input`, unref), `send` (delegate to `net_l2_send(api->send, …)`), `enable` (forward to the eth driver `start`/`stop`), and `flags` (returns `NET_L2_MULTICAST \| NET_L2_PROMISC_MODE`). Registers via `NET_L2_INIT(WOLFIP_L2, …)`. | +| `subsys/net/l2/wolfip/Kconfig` | `config NET_L2_WOLFIP` — depends on `WOLFIP`. | +| `subsys/net/l2/wolfip/CMakeLists.txt` | Module build. | + +Modifies: + +| Path | Change | +|---|---| +| `subsys/net/l2/CMakeLists.txt` | `if(CONFIG_NET_L2_WOLFIP) add_subdirectory(wolfip) endif()` | +| `subsys/net/l2/Kconfig` | `source "subsys/net/l2/wolfip/Kconfig"` | + +### `0003-net-shell-wolfip.patch` + +Makes the `net` shell tree wolfIP-aware. Two parts: + +1. **Kconfig umbrella amendments** in `subsys/net/lib/shell/Kconfig` — + every `NET_SHELL_*_SUPPORTED` symbol that previously keyed off + `NET_IPV4` / `NET_TCP` / `NET_UDP` / `NET_DHCPV4` / `NET_ROUTE` / + `NET_IP` now also accepts `WOLFIP`. Without this, the shell command + files would not even be compiled in a wolfIP-only build. + +2. **Per-file rewrites**, each adding an `#elif defined(CONFIG_WOLFIP)` + branch alongside the existing `#if defined(CONFIG_NET_…)` native + branch, so the native paths remain compilable for users who + keep Zephyr's stack: + + | File | Commands affected | Backend used | + |---|---|---| + | `iface.c` | IPv4 + DHCPv4 print blocks in `net iface` | `wolfip_ctrl_get_addr`, `wolfip_ctrl_dhcp_is_running/is_bound` | + | `ipv4.c` | `net ipv4`, `net ipv4 add/del/gateway` | `wolfip_ctrl_get_addr` / `set_addr` | + | `arp.c` | `net arp`, `net arp lookup`, `net arp flush`, `net arp add` | `wolfip_ctrl_arp_lookup`; flush/add stubbed as "not supported" | + | `ping.c` | `net ping ` | BSD `socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)` via the wolfIP offload | + | `route.c` | `net route add/del`, `net route` dump | `wolfip_ctrl_route_add/del/count/get` | + | `dhcpv4.c` | `net dhcpv4 client start/stop/status` | `wolfip_ctrl_dhcp_start/is_running/is_bound`; server stubbed | + | `tcp.c` | `net tcp connect/send/recv/close` | BSD sockets | + | `udp.c` | `net udp bind/send/recv/close` | BSD sockets | + | `conn.c` | Guards tightened so undefined refs (`net_conn_foreach`, etc.) skip cleanly | n/a | + | `stats.c` | Native-counter blocks gated more tightly | n/a | + +### `0004-sample-wolfip-tap-echo.patch` + +A minimal sample under `samples/net/sockets/wolfip_tap_echo/`: + +| File | Purpose | +|---|---| +| `src/main.c` | Brings the interface up via `wolfip_enable_iface`, sets `192.0.2.1/24 gw 192.0.2.2` via `wolfip_ctrl_set_addr`, then spawns two threads each running a textbook BSD-socket echo loop (TCP and UDP on port 4242). | +| `prj.conf` | `CONFIG_WOLFIP=y`, `CONFIG_NET_L2_WOLFIP=y`, `CONFIG_ETH_NATIVE_TAP=y`, `CONFIG_NET_SOCKETS=y`, `CONFIG_POSIX_API=y`, `CONFIG_SHELL=y`, `CONFIG_NET_SHELL=y`. **No** `CONFIG_NET_IPV4` / `NET_TCP` / `NET_UDP` / `NET_CONFIG_*`. | +| `boards/native_sim.conf` | Board-specific tuning (TAP driver, stack sizes). | +| `CMakeLists.txt`, `sample.yaml`, `README.rst` | Standard Zephyr sample plumbing. | + +--- + +## Application configuration + +For any application that wants to use wolfIP instead of Zephyr's native +stack, the `prj.conf` skeleton looks like this: + +```kconfig +# Networking core +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_POSIX_API=y + +# Zephyr native IPv4/TCP/UDP are off — wolfIP owns the L3+ stack. +CONFIG_NET_IPV6=n + +# L2 stack +CONFIG_NET_L2_ETHERNET=y # driver model requirement +CONFIG_NET_L2_WOLFIP=y # intercepts raw frames + +# wolfIP +CONFIG_WOLFIP=y +CONFIG_WOLFIP_DHCPV4=n +CONFIG_WOLFIP_MAX_INTERFACES=1 + +# Net packet buffers (still used for raw L2 frames) +CONFIG_NET_PKT_RX_COUNT=24 +CONFIG_NET_PKT_TX_COUNT=24 +CONFIG_NET_BUF_RX_COUNT=96 +CONFIG_NET_BUF_TX_COUNT=96 + +# Sockets +CONFIG_ZVFS_OPEN_MAX=32 +``` + +**Do not enable** `CONFIG_NET_IPV4`, `CONFIG_NET_TCP`, `CONFIG_NET_UDP`, +`CONFIG_NET_ARP`, `CONFIG_NET_DHCPV4`, or `CONFIG_NET_CONFIG_SETTINGS`. +The `WOLFIP` Kconfig does **not** depend on `NET_IPV4` — it is +specifically designed to be used **instead** of the native stack. + +### Bringing the interface up in your application + +```c +#include +#include + +int main(void) +{ + struct net_if *iface = net_if_get_default(); + unsigned int if_idx; + struct in_addr ip, mask, gw; + + /* This (a) marks iface as wolfIP-owned, (b) swaps L2 to NET_L2_WOLFIP, + * (c) registers the socket offload, (d) brings the iface admin-up. */ + wolfip_enable_iface(iface); + + /* Map Zephyr's net_if index to wolfIP's if_idx, then configure + * the IPv4 address. IP arithmetic uses wolfIP's host-order ip4 type. */ + wolfip_zephyr_get_if_idx(iface, &if_idx); + + net_addr_pton(AF_INET, "192.0.2.1", &ip); + net_addr_pton(AF_INET, "255.255.255.0", &mask); + net_addr_pton(AF_INET, "192.0.2.2", &gw); + + wolfip_ctrl_set_addr(if_idx, + sys_be32_to_cpu(ip.s_addr), + sys_be32_to_cpu(mask.s_addr), + sys_be32_to_cpu(gw.s_addr)); + + /* From this point on, plain POSIX BSD sockets work: */ + int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + /* … */ +} +``` + +If you prefer DHCPv4 instead of static configuration, set +`CONFIG_WOLFIP_DHCPV4=y` and call `wolfip_ctrl_dhcp_start(if_idx)` +after `wolfip_enable_iface`. + +--- + +## Sample: `wolfip_tap_echo` + +The sample lives at `samples/net/sockets/wolfip_tap_echo/`. It: + +- attaches wolfIP to the default interface (the `eth_native_tap` driver) +- configures `192.0.2.1/24 gw 192.0.2.2` +- runs a TCP echo on port 4242 +- runs a UDP echo on port 4242 +- enables the `net` shell on the pseudo-tty + +### Running + +```bash +# Terminal 1 — host network setup (once per session) +sudo ip link add zeth type tuntap mode tap +sudo ip addr add 192.0.2.2/24 dev zeth +sudo ip link set zeth up +# (or just use net-tools/net-setup.sh from Zephyr if you have it) + +# Terminal 2 — the sample +cd ~/src/zephyr-v4.4.0/zephyr +west build -b native_sim -p auto samples/net/sockets/wolfip_tap_echo +./build/zephyr/zephyr.exe --eth-if=zeth --mac-addr=02:00:5e:00:53:61 + +# Terminal 3 — testing +ping -c 3 192.0.2.1 +echo "x" | nc -u -w 1 192.0.2.1 4242 +echo "y" | nc -q 1 192.0.2.1 4242 +``` + +The `--mac-addr=…` argument is optional but recommended — without it +the simulator picks a random MAC each boot, which means the host kernel's +ARP cache for `192.0.2.1` may go stale between runs. + +### Sample-specific configuration + +If you change the addresses, edit the `SAMPLE_LOCAL_IPV4` / +`SAMPLE_LOCAL_NETMASK` / `SAMPLE_LOCAL_GW` macros at the top of +`samples/net/sockets/wolfip_tap_echo/src/main.c`. They are passed +directly to `wolfip_ctrl_set_addr` at boot. + +--- + +## Building for a hardware target + +The sample uses `native_sim` with the TAP driver, but the wolfIP port +itself is driver-agnostic — `NET_L2_WOLFIP` overlays any Ethernet +driver that registers via `ETHERNET_L2`. To run on real hardware: + +1. Pick a board with an Ethernet driver (e.g. `frdm_k64f`, + `nucleo_h743zi`, `mimxrt1064_evk`, …). +2. Use the same `prj.conf` skeleton from the sample. Adjust the + `_MAX_INTERFACES`, stack sizes, and DHCPv4 settings as needed. +3. `west build -b samples/net/sockets/wolfip_tap_echo`. +4. Flash and connect a serial console to access the shell. + +The port has not been formally regression-tested on hardware — please +report issues you hit. + +--- + +## Net-shell commands + +After `CONFIG_NET_SHELL=y`, the standard `net` command tree is wired +into wolfIP: + +| Command | Action | +|---|---| +| `net iface` | Per-interface report with IPv4 (wolfIP) and DHCPv4 (wolfIP) blocks. | +| `net ipv4` | Top-level IPv4 summary, plus per-iface table. | +| `net ipv4 add []` | Configure address on a wolfIP interface. | +| `net ipv4 del ` | Clear the address (wolfIP stores one per iface). | +| `net ipv4 gateway ` | Set default gateway. | +| `net arp lookup ` | One-shot ARP resolution; prints `ip -> MAC` or `no ARP entry`. | +| `net route` | Dump wolfIP routing table. | +| `net route add [/] ` | Add a static route. | +| `net route del [/]` | Remove a static route. | +| `net dhcpv4 client start ` | Start the wolfIP DHCPv4 client. | +| `net dhcpv4 client status ` | Reports `bound` / `running` / `idle`. | +| `net ping [-c N] [-i ms] ` | ICMP echo via a wolfIP ICMP socket. | +| `net tcp connect ` | Open a TCP client connection. | +| `net tcp send ` | Send a string on the open TCP connection. | +| `net tcp recv` | Non-blocking receive on the open TCP connection. | +| `net tcp close` | Close the TCP connection. | +| `net udp bind ` | Bind a UDP socket. | +| `net udp send ` | Send a datagram. | +| `net udp recv` | Non-blocking receive on the bound UDP socket. | +| `net udp close` | Close the UDP socket. | + +Commands not in the wolfIP world (DHCPv4 server, IGMP join, static +ARP add, ARP cache flush/iterate) print a clear `"not supported"` +message rather than silently no-op'ing. + +--- + +## API reference + +All declarations live in ``. The control wrapper +implementations live in `subsys/net/lib/wolfip/wolfip_control.c` and +the integration glue in `wolfip_zephyr.c`. + +### Interface management + +```c +int wolfip_enable_iface(struct net_if *iface); +int wolfip_disable_iface(struct net_if *iface); +bool wolfip_iface_is_enabled(struct net_if *iface); +int wolfip_attach_iface(struct net_if *iface); /* alias for enable */ +int wolfip_detach_iface(struct net_if *iface); /* alias for disable */ +int wolfip_zephyr_get_if_idx(struct net_if *iface, unsigned int *if_idx); +``` + +### Stack/lock accessors (advanced) + +```c +struct wolfIP *wolfip_zephyr_get_stack(void); +void wolfip_zephyr_lock(void); +void wolfip_zephyr_unlock(void); +``` + +Use the lock if you call wolfIP source-tree APIs directly — every +`wolfip_ctrl_*` wrapper already takes it internally. + +### IPv4 / ARP / route / DHCP wrappers + +```c +int wolfip_ctrl_get_addr(unsigned int if_idx, ip4 *ip, ip4 *mask, ip4 *gw); +int wolfip_ctrl_set_addr(unsigned int if_idx, ip4 ip, ip4 mask, ip4 gw); + +int wolfip_ctrl_dhcp_start(unsigned int if_idx); +int wolfip_ctrl_dhcp_is_running(unsigned int if_idx); +int wolfip_ctrl_dhcp_is_bound(unsigned int if_idx); + +int wolfip_ctrl_get_dns(ip4 *dns); +int wolfip_ctrl_arp_lookup(unsigned int if_idx, ip4 ip, uint8_t mac_out[6]); + +int wolfip_ctrl_route_add(unsigned int if_idx, ip4 prefix, uint8_t plen, ip4 gw); +int wolfip_ctrl_route_del(unsigned int if_idx, ip4 prefix, uint8_t plen); +unsigned int wolfip_ctrl_route_count(void); +int wolfip_ctrl_route_get(unsigned int idx, struct wolfIP_route_info *info_out); +``` + +All wrappers return 0 on success and a negative errno on failure. +`ip4` is `uint32_t` in host byte order, MSB first (i.e. +`192.0.2.1 == 0xC0000201`). + +### Internal L2 hook + +```c +int wolfip_zephyr_l2_input(struct net_if *iface, const uint8_t *frame, size_t len); +``` + +Called only by `subsys/net/l2/wolfip/wolfip_l2.c`. Not for application use. + +--- + +## Known limitations + +- **No IGMP / multicast group management** — wolfIP exposes no public + API for joining/leaving groups, so `net ipv4 add … join` returns + "not supported". +- **No DHCPv4 server** — wolfIP is client-only on DHCPv4. +- **No DHCPv4 client stop** — wolfIP has no `dhcp_client_stop` entry + point; `net dhcpv4 client stop` prints "not supported". +- **No ARP cache iteration / flush / static add** — `net arp` and + `net arp flush` / `net arp add` are stubbed accordingly. Only + `net arp lookup` works. +- **No per-iface DHCPv4** — `dhcp_client_init` is a stack-wide + function. `wolfip_ctrl_dhcp_start` accepts an `if_idx` for API + symmetry but currently ignores it beyond bounds-checking. +- **Single in-flight TCP accept** — by wolfIP design, a listening + socket processes one connection at a time. New SYNs arriving while + another handshake is in progress are dropped (DoS protection). The + integration layer pre-accepts inline from the RX callback to keep + this window as small as one `wolfIP_recv_ex` invocation, but bursty + load can still drop SYNs that would have been queued in a + conventional accept-queue stack. +- **Net statistics counters** are not exposed by wolfIP. `net stats` + prints a `wolfIP statistics: not exposed.` placeholder. +- **IPv6 is out of scope** — neither the L2 module nor any of the + shell rewrites touch IPv6. If your application needs both, you must + run with Zephyr's native IPv6 alongside wolfIP's IPv4 (untested + configuration). +- **`const` L2 swap is a controlled hack** — see the [Design + overview](#design-overview). Should Zephyr ever introduce a real + runtime-L2-swap API the cast can be removed; for now the cast is + the only practical mechanism. + +--- + +## Maintenance notes + +### Patch organisation rationale + +Patches are split by **subsystem boundary** rather than by code change +size, so a future maintainer can rebase one onto a new Zephyr release +without dragging unrelated areas along: + +- 0001 owns the wolfIP integration core. Touches only + `include/zephyr/net/wolfip.h` and `subsys/net/lib/wolfip/`, plus two + one-line additions in the parent net/lib build tree. +- 0002 owns the L2 module. Touches only `subsys/net/l2/wolfip/` plus + two one-line additions in the parent net/l2 build tree. +- 0003 owns the net-shell rewrites. Touches only + `subsys/net/lib/shell/`. This is the most upstream-volatile patch: + shell command files churn between Zephyr releases. +- 0004 owns the demo app under `samples/`. Touches nothing else. + +### Refreshing the patches against a newer Zephyr + +When porting to a newer Zephyr release: + +1. Try `git apply --3way ` first — three-way merge handles + most context drift. +2. If `--3way` fails, apply manually and regenerate the patch with + `git diff > `. +3. Run the sample's end-to-end test (see [Quick start](#quick-start) + step 6) before declaring the refresh done. +4. Patch 0003 is the most likely to break — Zephyr's shell command + files get reformatted and reorganised relatively often. The + amendments are mechanical (`#elif defined(CONFIG_WOLFIP)` + branches); diff carefully against the new upstream version. + +### Reading the diffs + +All four patches are produced by `git diff --cached`, so they apply +with either `git apply` or `patch -p1`. They are *not* `git +format-patch` output and do not carry author / date metadata — they +are plain unified diffs with a short rationale at the top. + +### Code locations to know + +| File | Role | +|---|---| +| `subsys/net/lib/wolfip/wolfip_zephyr.c::wolfip_socket_event_cb` | The pre-accept inline that closes the SYN_RCVD race. Touch this if multi-connection TCP regresses. | +| `subsys/net/lib/wolfip/wolfip_zephyr.c::wolfip_zephyr_l2_input` | Calls `wolfIP_poll` synchronously. Touch this if performance is a concern (poll is currently per-frame). | +| `subsys/net/lib/wolfip/wolfip_zephyr.c::wolfip_attach_iface` | The L2 swap (`*l2_ptr = &NET_L2_GET_NAME(WOLFIP_L2);`). The `const`-cast is here. | +| `subsys/net/lib/wolfip/wolfip_zephyr.c::wolfip_is_supported_family` | Whitelist of protocols accepted by the offload socket — TCP/UDP/ICMP/0. Extend if you add new protocol support. | +| `subsys/net/l2/wolfip/wolfip_l2.c::wolfip_l2_recv` | Linearises the frame into a stack buffer. Stack-buffer size (1600 B) caps the per-frame size. | + +--- + +## Troubleshooting + +### `west build` fails with `WOLFIP depends on NET_IPV4` or similar + +You are not at the right Zephyr version, or you are applying these +patches on top of an older snapshot where `WOLFIP` was experimentally +gated on `NET_IPV4`. Patch 0001 explicitly removes that dependency +and replaces it with `depends on NETWORKING`. Re-check that 0001 +applied cleanly. + +### `Cannot create zeth (-16/Device or resource busy)` at boot + +A previous instance of the binary is still holding the TAP. `pkill +-9 zephyr.exe` and retry. + +### `ping 192.0.2.1` works but TCP/UDP echo to port 4242 silently times out + +The host kernel cached a stale MAC for `192.0.2.1` from a previous +run. `ip neigh del 192.0.2.1 dev zeth` and retry. Or pass +`--mac-addr=02:00:5e:00:53:61` to the simulator so the MAC is +stable across boots. + +### `net ping` returns immediately with no replies + +Verify that `wolfip_is_supported_family` in `wolfip_zephyr.c` +accepts `IPPROTO_ICMP`. The patch enables it; if you've cherry-picked +parts of 0001 you may have dropped this. Grep: + +``` +grep IPPROTO_ICMP subsys/net/lib/wolfip/wolfip_zephyr.c +``` + +### Multi-connection TCP misbehaves under load + +Increase `MAX_TCPSOCKETS` in `wolfip/config.h` (default 4). Note +that wolfIP processes incoming connections strictly serially; bursty +clients sending many SYNs at once will see some get dropped. The +correct mitigation is on the client side (back-off + retry); changing +wolfIP's listener semantics is out of scope of this port. + +### `net iface` shows the interface but `Flags` does not list `IPv4` + +`wolfip_attach_iface` calls `net_if_flag_set(iface, NET_IF_IPV4)`. If +you see no `IPv4` flag, the iface attach didn't happen — confirm +`wolfip_enable_iface(iface)` was called from your app's `main`. + +--- + +## License + +The wolfIP runtime and these Zephyr-port patches are licensed under +GPLv3. Zephyr itself is Apache-2.0. Because the patches modify +Apache-2.0 files in-place, applying them produces a derivative work; +distribute the patched Zephyr tree under GPLv3 if you redistribute. +The patches as standalone files in this repository are GPLv3. diff --git a/port/zephyr/patches/0001-wolfip-glue-and-public-api.patch b/port/zephyr/patches/0001-wolfip-glue-and-public-api.patch new file mode 100644 index 0000000..c375ef8 --- /dev/null +++ b/port/zephyr/patches/0001-wolfip-glue-and-public-api.patch @@ -0,0 +1,1976 @@ +Subject: [PATCH 1/4] wolfIP integration glue and public API + +Adds the wolfIP runtime glue (wolfip_zephyr.c), the wolfIP control +wrapper API (wolfip_control.c), Kconfig + CMakeLists, and the public +header at include/zephyr/net/wolfip.h. Hooks subsys/net/lib into the +build via add_subdirectory_ifdef(CONFIG_WOLFIP wolfip) and a Kconfig +source line. No behavioural change to other subsystems yet — this +patch alone gives you a buildable but inactive wolfIP runtime. + +--- + +diff --git a/include/zephyr/net/wolfip.h b/include/zephyr/net/wolfip.h +new file mode 100644 +index 00000000..8d3866a8 +--- /dev/null ++++ b/include/zephyr/net/wolfip.h +@@ -0,0 +1,81 @@ ++#ifndef ZEPHYR_INCLUDE_NET_WOLFIP_H_ ++#define ZEPHYR_INCLUDE_NET_WOLFIP_H_ ++ ++#include ++#include ++#include ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++struct net_if; ++struct net_pkt; ++ ++int wolfip_enable_iface(struct net_if *iface); ++int wolfip_disable_iface(struct net_if *iface); ++bool wolfip_iface_is_enabled(struct net_if *iface); ++ ++enum net_verdict wolfip_zephyr_consume_rx(struct net_pkt *pkt); ++ ++/* ++ * Called by the NET_L2_WOLFIP layer on each received Ethernet frame. ++ * The buffer holds the full frame including the 14-byte Ethernet header. ++ * Implementation lives in wolfip_zephyr.c. ++ */ ++int wolfip_zephyr_l2_input(struct net_if *iface, const uint8_t *frame, size_t len); ++ ++/* ++ * Attach iface to wolfIP: marks the iface's L2 as NET_L2_WOLFIP_L2, ++ * registers the socket offload, and copies the iface MAC into the wolfIP ++ * binding. Replaces the older wolfip_enable_iface flow. ++ */ ++int wolfip_attach_iface(struct net_if *iface); ++int wolfip_detach_iface(struct net_if *iface); ++ ++/* ++ * wolfIP type and struct forward declarations. These are only emitted when ++ * the wolfIP native header () has not already been included; that ++ * header's own include guard is WOLFIP_H. ++ */ ++#ifndef WOLFIP_H ++typedef uint32_t ip4; ++struct wolfIP; ++/* Mirror of struct wolfIP_route_info from . Defined here so ++ * Zephyr-side consumers don't need to include the wolfIP source-tree ++ * header. The wolfIP-side definition is the source of truth; if either ++ * field set changes, this struct must change in lockstep. */ ++struct wolfIP_route_info { ++ ip4 prefix; ++ ip4 gateway; ++ uint8_t prefix_len; ++ uint8_t if_idx; ++}; ++#endif ++ ++/* wolfip_zephyr accessor functions (implemented in wolfip_zephyr.c) */ ++struct wolfIP *wolfip_zephyr_get_stack(void); ++int wolfip_zephyr_get_if_idx(struct net_if *iface, unsigned int *if_idx); ++void wolfip_zephyr_lock(void); ++void wolfip_zephyr_unlock(void); ++ ++/* wolfIP control/query wrappers (implemented in wolfip_control.c) */ ++int wolfip_ctrl_get_addr(unsigned int if_idx, ip4 *ip, ip4 *mask, ip4 *gw); ++int wolfip_ctrl_set_addr(unsigned int if_idx, ip4 ip, ip4 mask, ip4 gw); ++int wolfip_ctrl_dhcp_start(unsigned int if_idx); ++int wolfip_ctrl_dhcp_is_running(unsigned int if_idx); ++int wolfip_ctrl_dhcp_is_bound(unsigned int if_idx); ++int wolfip_ctrl_get_dns(ip4 *dns); ++int wolfip_ctrl_arp_lookup(unsigned int if_idx, ip4 ip, uint8_t mac_out[6]); ++int wolfip_ctrl_route_add(unsigned int if_idx, ip4 prefix, uint8_t plen, ip4 gw); ++int wolfip_ctrl_route_del(unsigned int if_idx, ip4 prefix, uint8_t plen); ++unsigned int wolfip_ctrl_route_count(void); ++int wolfip_ctrl_route_get(unsigned int idx, struct wolfIP_route_info *info_out); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* ZEPHYR_INCLUDE_NET_WOLFIP_H_ */ +diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt +index b1255ccc..ef295408 100644 +--- a/subsys/net/lib/CMakeLists.txt ++++ b/subsys/net/lib/CMakeLists.txt +@@ -24,6 +24,7 @@ add_subdirectory_ifdef(CONFIG_WIFI_CREDENTIALS wifi_credentials) + add_subdirectory_ifdef(CONFIG_OCPP ocpp) + add_subdirectory_ifdef(CONFIG_NETMIDI2_HOST midi2) + add_subdirectory_ifdef(CONFIG_WIREGUARD wireguard) ++add_subdirectory_ifdef(CONFIG_WOLFIP wolfip) + + if(CONFIG_NET_DHCPV4 OR CONFIG_NET_DHCPV4_SERVER) + add_subdirectory(dhcpv4) +diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig +index 55c7e85b..7bb01529 100644 +--- a/subsys/net/lib/Kconfig ++++ b/subsys/net/lib/Kconfig +@@ -32,6 +32,7 @@ source "subsys/net/lib/socks/Kconfig" + source "subsys/net/lib/sntp/Kconfig" + + source "subsys/net/lib/ocpp/Kconfig" ++source "subsys/net/lib/wolfip/Kconfig" + + endmenu + +diff --git a/subsys/net/lib/wolfip/CMakeLists.txt b/subsys/net/lib/wolfip/CMakeLists.txt +new file mode 100644 +index 00000000..854c94c7 +--- /dev/null ++++ b/subsys/net/lib/wolfip/CMakeLists.txt +@@ -0,0 +1,18 @@ ++# SPDX-License-Identifier: Apache-2.0 ++ ++zephyr_library() ++ ++set(WOLFIP_ROOT ${ZEPHYR_BASE}/../../wolfip) ++ ++zephyr_library_include_directories(${WOLFIP_ROOT}) ++zephyr_library_compile_definitions( ++ WOLFIP_MAX_INTERFACES=${CONFIG_WOLFIP_MAX_INTERFACES} ++ WOLFIP_ENABLE_FORWARDING=1 ++) ++ ++zephyr_library_sources( ++ ${WOLFIP_ROOT}/src/wolfip.c ++ wolfip_zephyr.c ++ wolfip_control.c ++) ++ +diff --git a/subsys/net/lib/wolfip/Kconfig b/subsys/net/lib/wolfip/Kconfig +new file mode 100644 +index 00000000..49156ba0 +--- /dev/null ++++ b/subsys/net/lib/wolfip/Kconfig +@@ -0,0 +1,53 @@ ++# SPDX-License-Identifier: Apache-2.0 ++ ++menuconfig WOLFIP ++ bool "wolfIP IPv4 adapter" ++ depends on NETWORKING ++ depends on NET_SOCKETS ++ select NET_SOCKETS_OFFLOAD ++ select NET_L2_WOLFIP ++ help ++ Route IPv4 socket traffic and IPv4 ingress/egress for selected ++ interfaces through wolfIP while preserving the existing Zephyr ++ driver model and interface-management APIs. ++ ++if WOLFIP ++ ++config WOLFIP_MAX_INTERFACES ++ int "Maximum number of wolfIP-backed interfaces" ++ range 1 8 ++ default 2 ++ ++config WOLFIP_RX_QUEUE_LEN ++ int "wolfIP RX queue depth" ++ range 2 32 ++ default 8 ++ ++config WOLFIP_POLL_INTERVAL_MS ++ int "wolfIP worker poll interval (ms)" ++ range 1 1000 ++ default 10 ++ ++config WOLFIP_STACK_SIZE ++ int "wolfIP worker stack size" ++ range 1024 16384 ++ default 4096 ++ ++config WOLFIP_INSTANCE_STORAGE_SIZE ++ int "wolfIP instance storage size" ++ range 65536 2097152 ++ default 524288 ++ help ++ Size of the dedicated backing storage used for one wolfIP stack ++ instance. This must be at least wolfIP_instance_size() for the ++ enabled wolfIP feature set. ++ ++config WOLFIP_THREAD_PRIORITY ++ int "wolfIP worker thread priority" ++ default 7 ++ ++config WOLFIP_DHCPV4 ++ bool "Start wolfIP DHCPv4 on the primary bound interface" ++ default y ++ ++endif +diff --git a/subsys/net/lib/wolfip/wolfip_control.c b/subsys/net/lib/wolfip/wolfip_control.c +new file mode 100644 +index 00000000..af913a72 +--- /dev/null ++++ b/subsys/net/lib/wolfip/wolfip_control.c +@@ -0,0 +1,245 @@ ++/* ++ * Copyright (c) 2026 ++ * ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++int wolfip_ctrl_get_addr(unsigned int if_idx, ip4 *ip, ip4 *mask, ip4 *gw) ++{ ++ struct wolfIP *s; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ if (ip == NULL || mask == NULL || gw == NULL) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ wolfIP_ipconfig_get_ex(s, if_idx, ip, mask, gw); ++ wolfip_zephyr_unlock(); ++ return 0; ++} ++ ++int wolfip_ctrl_set_addr(unsigned int if_idx, ip4 ip, ip4 mask, ip4 gw) ++{ ++ struct wolfIP *s; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ wolfIP_ipconfig_set_ex(s, if_idx, ip, mask, gw); ++ wolfip_zephyr_unlock(); ++ return 0; ++} ++ ++int wolfip_ctrl_dhcp_start(unsigned int if_idx) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = dhcp_client_init(s); ++ wolfip_zephyr_unlock(); ++ return ret; ++} ++ ++int wolfip_ctrl_dhcp_is_running(unsigned int if_idx) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = dhcp_client_is_running(s); ++ wolfip_zephyr_unlock(); ++ return ret; ++} ++ ++int wolfip_ctrl_dhcp_is_bound(unsigned int if_idx) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = dhcp_bound(s); ++ wolfip_zephyr_unlock(); ++ return ret; ++} ++ ++int wolfip_ctrl_get_dns(ip4 *dns) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (dns == NULL) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = wolfIP_dns_server_get(s, dns); ++ wolfip_zephyr_unlock(); ++ return ret; ++} ++ ++int wolfip_ctrl_arp_lookup(unsigned int if_idx, ip4 ip, uint8_t mac_out[6]) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ if (mac_out == NULL) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = wolfIP_arp_lookup_ex(s, if_idx, ip, mac_out); ++ wolfip_zephyr_unlock(); ++ return ret; ++} ++ ++int wolfip_ctrl_route_add(unsigned int if_idx, ip4 prefix, uint8_t plen, ip4 gw) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = wolfIP_route_add(s, if_idx, prefix, plen, gw); ++ wolfip_zephyr_unlock(); ++ return ret; ++} ++ ++int wolfip_ctrl_route_del(unsigned int if_idx, ip4 prefix, uint8_t plen) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (if_idx >= CONFIG_WOLFIP_MAX_INTERFACES) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = wolfIP_route_delete(s, if_idx, prefix, plen); ++ wolfip_zephyr_unlock(); ++ return ret; ++} ++ ++unsigned int wolfip_ctrl_route_count(void) ++{ ++ struct wolfIP *s; ++ unsigned int count; ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return 0; ++ } ++ ++ count = wolfIP_route_count(s); ++ wolfip_zephyr_unlock(); ++ return count; ++} ++ ++int wolfip_ctrl_route_get(unsigned int idx, struct wolfIP_route_info *info_out) ++{ ++ struct wolfIP *s; ++ int ret; ++ ++ if (info_out == NULL) { ++ return -EINVAL; ++ } ++ ++ wolfip_zephyr_lock(); ++ s = wolfip_zephyr_get_stack(); ++ if (s == NULL) { ++ wolfip_zephyr_unlock(); ++ return -ENODEV; ++ } ++ ++ ret = wolfIP_route_get(s, idx, info_out); ++ wolfip_zephyr_unlock(); ++ return ret; ++} +diff --git a/subsys/net/lib/wolfip/wolfip_zephyr.c b/subsys/net/lib/wolfip/wolfip_zephyr.c +new file mode 100644 +index 00000000..d0ace363 +--- /dev/null ++++ b/subsys/net/lib/wolfip/wolfip_zephyr.c +@@ -0,0 +1,1514 @@ ++/* ++ * Copyright (c) 2026 ++ * ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++LOG_MODULE_REGISTER(net_wolfip, CONFIG_NET_CORE_LOG_LEVEL); ++ ++#define WOLFIP_ETH_ADDR_LEN 6U ++#define WOLFIP_ETH_HEADER_LEN 14U ++#define WOLFIP_LINK_MTU 1600U ++ ++struct wolfip_binding { ++ struct net_if *iface; ++ bool enabled; ++ unsigned int if_idx; ++}; ++ ++struct wolfip_poll_state { ++ struct k_poll_signal signal; ++ short events; ++ short revents; ++ bool armed; ++}; ++ ++struct wolfip_socket { ++ int fd; ++ int wolf_fd; ++ k_timeout_t recv_timeout; ++ k_timeout_t send_timeout; ++ atomic_t pending_events; ++ bool nonblock; ++ bool is_listener; ++ int pending_child_wolf_fd; ++ struct wolfIP_sockaddr_in pending_child_peer; ++ struct k_sem event_sem; ++ struct wolfip_poll_state poll; ++}; ++ ++struct wolfip_dns_wait { ++ struct k_sem sem; ++ int status; ++ ip4 ip; ++}; ++ ++struct wolfip_context { ++ struct wolfIP *stack; ++ struct k_mutex lock; ++ struct k_sem wake_sem; ++ struct k_thread thread; ++ bool initialized; ++ bool dns_registered; ++ struct wolfip_binding bindings[CONFIG_WOLFIP_MAX_INTERFACES]; ++ struct wolfip_dns_wait dns_wait; ++}; ++ ++static struct wolfip_context wolfip_ctx; ++K_THREAD_STACK_DEFINE(wolfip_thread_stack, CONFIG_WOLFIP_STACK_SIZE); ++static uint8_t wolfip_stack_storage[CONFIG_WOLFIP_INSTANCE_STORAGE_SIZE] ++ __aligned(sizeof(void *)); ++ ++#define WOLFIP_STACK() (wolfip_ctx.stack) ++ ++static ssize_t wolfip_sendto(void *obj, const void *buf, size_t len, int flags, ++ const struct net_sockaddr *dest_addr, ++ net_socklen_t addrlen); ++static ssize_t wolfip_recvfrom(void *obj, void *buf, size_t max_len, int flags, ++ struct net_sockaddr *src_addr, ++ net_socklen_t *addrlen); ++static int wolfip_socket_create(int family, int type, int proto); ++static void wolfip_socket_event_cb(int sock_fd, uint16_t events, void *arg); ++static const struct socket_op_vtable wolfip_socket_fd_op_vtable; ++ ++static ip4 net_addr_to_wolfip(const struct net_in_addr *addr) ++{ ++ if (addr == NULL) { ++ return 0U; ++ } ++ ++ return ((ip4)addr->s4_addr[0] << 24) | ++ ((ip4)addr->s4_addr[1] << 16) | ++ ((ip4)addr->s4_addr[2] << 8) | ++ (ip4)addr->s4_addr[3]; ++} ++ ++static void wolfip_to_net_addr(ip4 addr, struct net_in_addr *out) ++{ ++ if (out == NULL) { ++ return; ++ } ++ ++ out->s4_addr[0] = (uint8_t)(addr >> 24); ++ out->s4_addr[1] = (uint8_t)(addr >> 16); ++ out->s4_addr[2] = (uint8_t)(addr >> 8); ++ out->s4_addr[3] = (uint8_t)addr; ++} ++ ++static int wolfip_errno_from_ret(int ret) ++{ ++ if (ret >= 0) { ++ return 0; ++ } ++ ++ if (ret == -1) { ++ return EIO; ++ } ++ ++ return -ret; ++} ++ ++static struct wolfip_binding *wolfip_binding_for_iface(struct net_if *iface) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(wolfip_ctx.bindings); i++) { ++ if (wolfip_ctx.bindings[i].iface == iface) { ++ return &wolfip_ctx.bindings[i]; ++ } ++ } ++ ++ return NULL; ++} ++ ++static bool wolfip_any_enabled(void) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(wolfip_ctx.bindings); i++) { ++ if (wolfip_ctx.bindings[i].enabled) { ++ return true; ++ } ++ } ++ ++ return false; ++} ++ ++static int wolfip_poll_ll_dev(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) ++{ ++ ARG_UNUSED(ll); ++ ARG_UNUSED(buf); ++ ARG_UNUSED(len); ++ ++ return 0; ++} ++ ++static int wolfip_send_ll_dev(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) ++{ ++ struct wolfip_binding *binding; ++ struct net_if *iface; ++ struct net_pkt *pkt; ++ int ret; ++ uint16_t ptype; ++ ++ if (ll == NULL || buf == NULL || len == 0U) { ++ return -WOLFIP_EINVAL; ++ } ++ ++ binding = ll->priv; ++ if (binding == NULL || !binding->enabled || binding->iface == NULL) { ++ return -WOLFIP_EINVAL; ++ } ++ ++ iface = binding->iface; ++ pkt = net_pkt_alloc_with_buffer(iface, len, NET_AF_PACKET, 0, K_NO_WAIT); ++ if (pkt == NULL) { ++ return -WOLFIP_ENOMEM; ++ } ++ ++ if (net_pkt_write(pkt, buf, len) < 0) { ++ net_pkt_unref(pkt); ++ return -WOLFIP_ENOMEM; ++ } ++ ++ net_pkt_set_family(pkt, NET_AF_PACKET); ++ net_pkt_set_iface(pkt, iface); ++ net_pkt_cursor_init(pkt); ++ ++ if (len >= WOLFIP_ETH_HEADER_LEN) { ++ const uint8_t *frame = buf; ++ ++ (void)net_linkaddr_set(net_pkt_lladdr_dst(pkt), frame, ++ WOLFIP_ETH_ADDR_LEN); ++ (void)net_linkaddr_set(net_pkt_lladdr_src(pkt), ++ frame + WOLFIP_ETH_ADDR_LEN, ++ WOLFIP_ETH_ADDR_LEN); ++ ptype = sys_get_be16(frame + 12); ++ net_pkt_set_ll_proto_type(pkt, ptype); ++ } ++ ++ ret = net_if_send_data(iface, pkt); ++ if (ret < 0) { ++ net_pkt_unref(pkt); ++ return ret; ++ } ++ ++ return ret; ++} ++ ++static void wolfip_copy_iface_name(struct wolfIP_ll_dev *ll, struct net_if *iface) ++{ ++ const struct device *dev; ++ ++ dev = net_if_get_device(iface); ++ if (dev == NULL) { ++ ll->ifname[0] = '\0'; ++ return; ++ } ++ ++ snprintk(ll->ifname, sizeof(ll->ifname), "%s", dev->name); ++} ++ ++static void wolfip_worker(void *arg1, void *arg2, void *arg3) ++{ ++ ARG_UNUSED(arg1); ++ ARG_UNUSED(arg2); ++ ARG_UNUSED(arg3); ++ ++ while (true) { ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ (void)wolfIP_poll(WOLFIP_STACK(), k_uptime_get()); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ (void)k_sem_take(&wolfip_ctx.wake_sem, ++ K_MSEC(CONFIG_WOLFIP_POLL_INTERVAL_MS)); ++ } ++} ++ ++static int wolfip_init_runtime(void) ++{ ++ if (wolfip_ctx.initialized) { ++ return 0; ++ } ++ ++ memset(&wolfip_ctx, 0, sizeof(wolfip_ctx)); ++ k_mutex_init(&wolfip_ctx.lock); ++ k_sem_init(&wolfip_ctx.wake_sem, 0, UINT_MAX); ++ k_sem_init(&wolfip_ctx.dns_wait.sem, 0, 1); ++ if (wolfIP_instance_size() > sizeof(wolfip_stack_storage)) { ++ return -ENOMEM; ++ } ++ ++ wolfip_ctx.stack = (struct wolfIP *)wolfip_stack_storage; ++ wolfIP_init(WOLFIP_STACK()); ++ ++ k_thread_create(&wolfip_ctx.thread, wolfip_thread_stack, ++ K_THREAD_STACK_SIZEOF(wolfip_thread_stack), ++ wolfip_worker, NULL, NULL, NULL, ++ CONFIG_WOLFIP_THREAD_PRIORITY, 0, K_NO_WAIT); ++ k_thread_name_set(&wolfip_ctx.thread, "wolfip"); ++ ++ wolfip_ctx.initialized = true; ++ return 0; ++} ++ ++static short wolfip_socket_current_revents(struct wolfip_socket *sock, ++ short requested) ++{ ++ short revents = 0; ++ int can_read; ++ int can_write; ++ atomic_val_t pending; ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ can_read = wolfIP_sock_can_read(WOLFIP_STACK(), sock->wolf_fd); ++ can_write = wolfIP_sock_can_write(WOLFIP_STACK(), sock->wolf_fd); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ if ((requested & ZSOCK_POLLIN) && can_read > 0) { ++ revents |= ZSOCK_POLLIN; ++ } ++ ++ if ((requested & ZSOCK_POLLOUT) && can_write > 0) { ++ revents |= ZSOCK_POLLOUT; ++ } ++ ++ pending = atomic_get(&sock->pending_events); ++ if (pending & CB_EVENT_CLOSED) { ++ revents |= ZSOCK_POLLHUP; ++ } ++ ++ return revents; ++} ++ ++static void wolfip_socket_signal_poll(struct wolfip_socket *sock) ++{ ++ if (!sock->poll.armed) { ++ return; ++ } ++ ++ sock->poll.revents |= wolfip_socket_current_revents(sock, sock->poll.events); ++ if (sock->poll.revents != 0) { ++ k_poll_signal_raise(&sock->poll.signal, sock->poll.revents); ++ } ++} ++ ++static void wolfip_socket_event_cb(int sock_fd, uint16_t events, void *arg) ++{ ++ struct wolfip_socket *sock = arg; ++ ++ ARG_UNUSED(sock_fd); ++ ++ if (sock == NULL) { ++ return; ++ } ++ ++ /* Pre-accept inline while wolfIP's lock is held and the listener is ++ * still in TCP_SYN_RCVD. If we wait for the app thread to wake and ++ * call accept(), the peer's final ACK will arrive first and advance ++ * the listener past SYN_RCVD — leaving wolfIP_sock_accept unable to ++ * recover the connection. wolfIP's design is sequential: one ++ * connection in flight at a time, so a single-deep stash is enough. ++ */ ++ if (sock->is_listener && (events & CB_EVENT_READABLE) && ++ sock->pending_child_wolf_fd < 0) { ++ struct wolfIP_sockaddr_in peer; ++ socklen_t peer_len = sizeof(peer); ++ int child_fd = wolfIP_sock_accept(WOLFIP_STACK(), sock->wolf_fd, ++ (struct wolfIP_sockaddr *)&peer, ++ &peer_len); ++ if (child_fd >= 0) { ++ sock->pending_child_wolf_fd = child_fd; ++ sock->pending_child_peer = peer; ++ } ++ } ++ ++ atomic_or(&sock->pending_events, events); ++ k_sem_give(&sock->event_sem); ++ wolfip_socket_signal_poll(sock); ++} ++ ++static int wolfip_socket_wait(struct wolfip_socket *sock, uint16_t want, ++ k_timeout_t timeout) ++{ ++ int ret; ++ uint64_t end = 0U; ++ ++ if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) { ++ return -EAGAIN; ++ } ++ ++ if (!K_TIMEOUT_EQ(timeout, K_FOREVER)) { ++ end = k_uptime_get() + k_ticks_to_ms_floor64(timeout.ticks); ++ } ++ ++ while (true) { ++ atomic_val_t pending; ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ if ((want & CB_EVENT_READABLE) && ++ wolfIP_sock_can_read(WOLFIP_STACK(), sock->wolf_fd) > 0) { ++ k_mutex_unlock(&wolfip_ctx.lock); ++ return 0; ++ } ++ ++ if ((want & CB_EVENT_WRITABLE) && ++ wolfIP_sock_can_write(WOLFIP_STACK(), sock->wolf_fd) > 0) { ++ k_mutex_unlock(&wolfip_ctx.lock); ++ return 0; ++ } ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ if (atomic_get(&sock->pending_events) & CB_EVENT_CLOSED) { ++ return -ECONNRESET; ++ } ++ ++ /* Drain any pending event bits the caller is waiting on, then ++ * return. Reading the bits here closes the wake-up race: if ++ * the RX-side callback ran between the can_read/can_write ++ * checks and the sem_take below, the bit it set is what ++ * tells us to retry rather than block on a stale sem. */ ++ pending = atomic_and(&sock->pending_events, ~(atomic_val_t)want); ++ if (pending & want) { ++ return 0; ++ } ++ ++ k_sem_reset(&sock->event_sem); ++ /* Re-check pending after reset: a give that landed between ++ * the atomic_and above and the reset is now dropped, so we ++ * must observe its bit explicitly. */ ++ pending = atomic_and(&sock->pending_events, ~(atomic_val_t)want); ++ if (pending & want) { ++ return 0; ++ } ++ ++ ret = k_sem_take(&sock->event_sem, ++ K_TIMEOUT_EQ(timeout, K_FOREVER) ? ++ K_FOREVER : ++ K_MSEC(MAX((int64_t)0, (int64_t)(end - k_uptime_get())))); ++ if (ret < 0) { ++ return -EAGAIN; ++ } ++ } ++} ++ ++static int wolfip_alloc_socket_obj(int wolf_fd, struct wolfip_socket **sock_out) ++{ ++ struct wolfip_socket *sock; ++ int fd; ++ ++ fd = zvfs_reserve_fd(); ++ if (fd < 0) { ++ return -1; ++ } ++ ++ sock = k_malloc(sizeof(*sock)); ++ if (sock == NULL) { ++ zvfs_free_fd(fd); ++ errno = ENOMEM; ++ return -1; ++ } ++ ++ memset(sock, 0, sizeof(*sock)); ++ sock->fd = fd; ++ sock->wolf_fd = wolf_fd; ++ sock->recv_timeout = K_FOREVER; ++ sock->send_timeout = K_FOREVER; ++ sock->pending_child_wolf_fd = -1; ++ k_sem_init(&sock->event_sem, 0, UINT_MAX); ++ k_poll_signal_init(&sock->poll.signal); ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ wolfIP_register_callback(WOLFIP_STACK(), wolf_fd, ++ wolfip_socket_event_cb, sock); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ if (sock_out != NULL) { ++ *sock_out = sock; ++ } ++ ++ return fd; ++} ++ ++static int wolfip_dns_numeric_result(const char *node, const char *service, ++ struct zsock_addrinfo **res) ++{ ++ struct zsock_addrinfo *ai; ++ struct net_sockaddr_in *sin; ++ struct net_in_addr addr; ++ unsigned long port = 0U; ++ char *endptr = NULL; ++ ++ if (node == NULL || res == NULL) { ++ return -EINVAL; ++ } ++ ++ if (net_addr_pton(NET_AF_INET, node, &addr) < 0) { ++ return -EINVAL; ++ } ++ ++ if (service != NULL) { ++ port = strtoul(service, &endptr, 10); ++ if (endptr == NULL || *endptr != '\0' || port > UINT16_MAX) { ++ return -EINVAL; ++ } ++ } ++ ++ ai = k_calloc(1, sizeof(*ai)); ++ sin = k_calloc(1, sizeof(*sin)); ++ if (ai == NULL || sin == NULL) { ++ k_free(ai); ++ k_free(sin); ++ return -ENOMEM; ++ } ++ ++ sin->sin_family = NET_AF_INET; ++ sin->sin_port = sys_cpu_to_be16((uint16_t)port); ++ sin->sin_addr = addr; ++ ++ ai->ai_family = NET_AF_INET; ++ ai->ai_socktype = 0; ++ ai->ai_protocol = 0; ++ ai->ai_addrlen = sizeof(*sin); ++ ai->ai_addr = (struct sockaddr *)sin; ++ *res = ai; ++ return 0; ++} ++ ++static void wolfip_dns_lookup_cb(uint32_t ip) ++{ ++ wolfip_ctx.dns_wait.ip = ip; ++ wolfip_ctx.dns_wait.status = (ip == 0U) ? -EHOSTUNREACH : 0; ++ k_sem_give(&wolfip_ctx.dns_wait.sem); ++} ++ ++static int wolfip_getaddrinfo(const char *node, const char *service, ++ const struct zsock_addrinfo *hints, ++ struct zsock_addrinfo **res) ++{ ++ struct zsock_addrinfo *ai; ++ struct net_sockaddr_in *sin; ++ struct net_in_addr addr; ++ unsigned long port = 0U; ++ uint16_t dns_id; ++ int ret; ++ ++ if (res == NULL) { ++ return EAI_FAIL; ++ } ++ ++ *res = NULL; ++ ++ if (hints != NULL && hints->ai_family != AF_UNSPEC && ++ hints->ai_family != AF_INET) { ++ return EAI_FAMILY; ++ } ++ ++ ret = wolfip_dns_numeric_result(node, service, res); ++ if (ret == 0) { ++ return 0; ++ } ++ ++ if (node == NULL) { ++ return EAI_NONAME; ++ } ++ ++ if (service != NULL) { ++ char *endptr = NULL; ++ ++ port = strtoul(service, &endptr, 10); ++ if (endptr == NULL || *endptr != '\0' || port > UINT16_MAX) { ++ return EAI_SERVICE; ++ } ++ } ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ k_sem_reset(&wolfip_ctx.dns_wait.sem); ++ wolfip_ctx.dns_wait.status = -EINPROGRESS; ++ wolfip_ctx.dns_wait.ip = 0U; ++ ret = nslookup(WOLFIP_STACK(), node, &dns_id, wolfip_dns_lookup_cb); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ARG_UNUSED(dns_id); ++ if (ret < 0) { ++ return EAI_FAIL; ++ } ++ ++ if (k_sem_take(&wolfip_ctx.dns_wait.sem, K_SECONDS(5)) < 0 || ++ wolfip_ctx.dns_wait.status < 0) { ++ return EAI_AGAIN; ++ } ++ ++ ai = k_calloc(1, sizeof(*ai)); ++ sin = k_calloc(1, sizeof(*sin)); ++ if (ai == NULL || sin == NULL) { ++ k_free(ai); ++ k_free(sin); ++ return EAI_MEMORY; ++ } ++ ++ wolfip_to_net_addr(wolfip_ctx.dns_wait.ip, &addr); ++ sin->sin_family = AF_INET; ++ sin->sin_port = sys_cpu_to_be16((uint16_t)port); ++ sin->sin_addr = addr; ++ ++ ai->ai_family = AF_INET; ++ ai->ai_socktype = hints ? hints->ai_socktype : 0; ++ ai->ai_protocol = hints ? hints->ai_protocol : 0; ++ ai->ai_addrlen = sizeof(*sin); ++ ai->ai_addr = (struct sockaddr *)sin; ++ *res = ai; ++ return 0; ++} ++ ++static void wolfip_freeaddrinfo(struct zsock_addrinfo *res) ++{ ++ struct zsock_addrinfo *next; ++ ++ while (res != NULL) { ++ next = res->ai_next; ++ k_free(res->ai_addr); ++ k_free(res->ai_canonname); ++ k_free(res); ++ res = next; ++ } ++} ++ ++static const struct socket_dns_offload wolfip_dns_ops = { ++ .getaddrinfo = wolfip_getaddrinfo, ++ .freeaddrinfo = wolfip_freeaddrinfo, ++}; ++ ++static bool wolfip_is_supported_family(int family, int type, int proto) ++{ ++ if (!wolfip_any_enabled()) { ++ return false; ++ } ++ ++ if (family != NET_AF_INET) { ++ return false; ++ } ++ ++ if (type != NET_SOCK_STREAM && type != NET_SOCK_DGRAM) { ++ return false; ++ } ++ ++ if (proto == 0 || proto == IPPROTO_TCP || proto == IPPROTO_UDP || ++ proto == IPPROTO_ICMP) { ++ return true; ++ } ++ ++ return false; ++} ++ ++static int wolfip_socket_create(int family, int type, int proto) ++{ ++ struct wolfip_socket *sock; ++ int wolf_fd; ++ int fd; ++ ++ if (!wolfip_is_supported_family(family, type, proto)) { ++ errno = EAFNOSUPPORT; ++ return -1; ++ } ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ wolf_fd = wolfIP_sock_socket(WOLFIP_STACK(), family, type, proto); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (wolf_fd < 0) { ++ errno = wolfip_errno_from_ret(wolf_fd); ++ return -1; ++ } ++ ++ fd = wolfip_alloc_socket_obj(wolf_fd, &sock); ++ if (fd < 0) { ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ (void)wolfIP_sock_close(WOLFIP_STACK(), wolf_fd); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ return -1; ++ } ++ ++ zvfs_finalize_typed_fd(fd, sock, &wolfip_socket_fd_op_vtable.fd_vtable, ++ ZVFS_MODE_IFSOCK); ++ return fd; ++} ++ ++static struct wolfIP_sockaddr_in wolfip_sockaddr_in_from_net(const struct net_sockaddr_in *in) ++{ ++ struct wolfIP_sockaddr_in out; ++ ++ memset(&out, 0, sizeof(out)); ++ out.sin_family = AF_INET; ++ out.sin_port = in->sin_port; ++ out.sin_addr.s_addr = sys_cpu_to_be32(net_addr_to_wolfip(&in->sin_addr)); ++ return out; ++} ++ ++static void wolfip_sockaddr_in_to_net(const struct wolfIP_sockaddr_in *in, ++ struct net_sockaddr_in *out) ++{ ++ ip4 addr; ++ ++ memset(out, 0, sizeof(*out)); ++ out->sin_family = AF_INET; ++ out->sin_port = in->sin_port; ++ addr = sys_be32_to_cpu(in->sin_addr.s_addr); ++ wolfip_to_net_addr(addr, &out->sin_addr); ++} ++ ++static ssize_t wolfip_read(void *obj, void *buf, size_t sz) ++{ ++ return wolfip_recvfrom(obj, buf, sz, 0, NULL, NULL); ++} ++ ++static ssize_t wolfip_write(void *obj, const void *buf, size_t sz) ++{ ++ return wolfip_sendto(obj, buf, sz, 0, NULL, 0); ++} ++ ++static int wolfip_close2(void *obj, int fd) ++{ ++ struct wolfip_socket *sock = obj; ++ int ret; ++ ++ ARG_UNUSED(fd); ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ wolfIP_register_callback(WOLFIP_STACK(), sock->wolf_fd, NULL, NULL); ++ if (sock->pending_child_wolf_fd >= 0) { ++ (void)wolfIP_sock_close(WOLFIP_STACK(), ++ sock->pending_child_wolf_fd); ++ sock->pending_child_wolf_fd = -1; ++ } ++ ret = wolfIP_sock_close(WOLFIP_STACK(), sock->wolf_fd); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ k_free(sock); ++ if (ret < 0) { ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++ } ++ return 0; ++} ++ ++static int wolfip_poll_prepare(void *obj, struct zsock_pollfd *pfd, ++ struct k_poll_event **pev, ++ struct k_poll_event *pev_end) ++{ ++ struct wolfip_socket *sock = obj; ++ ++ if (*pev == pev_end) { ++ return -ENOMEM; ++ } ++ ++ k_poll_signal_reset(&sock->poll.signal); ++ sock->poll.events = pfd->events; ++ sock->poll.revents = wolfip_socket_current_revents(sock, pfd->events); ++ sock->poll.armed = true; ++ ++ if (sock->poll.revents != 0) { ++ k_poll_signal_raise(&sock->poll.signal, sock->poll.revents); ++ } ++ ++ **pev = (struct k_poll_event)K_POLL_EVENT_INITIALIZER( ++ K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, ++ &sock->poll.signal); ++ (*pev)++; ++ return 0; ++} ++ ++static int wolfip_poll_update(void *obj, struct zsock_pollfd *pfd, ++ struct k_poll_event **pev) ++{ ++ struct wolfip_socket *sock = obj; ++ unsigned int signaled; ++ int result; ++ ++ ARG_UNUSED(pev); ++ ++ signaled = 0U; ++ result = 0; ++ k_poll_signal_check(&sock->poll.signal, &signaled, &result); ++ pfd->revents = sock->poll.revents; ++ sock->poll.revents = 0; ++ sock->poll.armed = false; ++ return 0; ++} ++ ++static int wolfip_ioctl(void *obj, unsigned int request, va_list args) ++{ ++ struct wolfip_socket *sock = obj; ++ ++ switch (request) { ++ case ZFD_IOCTL_POLL_PREPARE: { ++ struct zsock_pollfd *pfd; ++ struct k_poll_event **pev; ++ struct k_poll_event *pev_end; ++ ++ pfd = va_arg(args, struct zsock_pollfd *); ++ pev = va_arg(args, struct k_poll_event **); ++ pev_end = va_arg(args, struct k_poll_event *); ++ return wolfip_poll_prepare(obj, pfd, pev, pev_end); ++ } ++ ++ case ZFD_IOCTL_POLL_UPDATE: { ++ struct zsock_pollfd *pfd; ++ struct k_poll_event **pev; ++ ++ pfd = va_arg(args, struct zsock_pollfd *); ++ pev = va_arg(args, struct k_poll_event **); ++ return wolfip_poll_update(obj, pfd, pev); ++ } ++ ++ case ZFD_IOCTL_POLL_OFFLOAD: ++ return -EOPNOTSUPP; ++ ++ case ZVFS_F_GETFL: ++ return sock->nonblock ? ZVFS_O_NONBLOCK : 0; ++ ++ case ZVFS_F_SETFL: { ++ int flags = va_arg(args, int); ++ ++ sock->nonblock = (flags & ZVFS_O_NONBLOCK) != 0; ++ return 0; ++ } ++ ++ case ZFD_IOCTL_FIONBIO: { ++ int *value = va_arg(args, int *); ++ ++ sock->nonblock = (*value != 0); ++ return 0; ++ } ++ ++ case ZFD_IOCTL_FIONREAD: { ++ int *avail = va_arg(args, int *); ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ *avail = wolfIP_sock_can_read(WOLFIP_STACK(), sock->wolf_fd) > 0 ? 1 : 0; ++ k_mutex_unlock(&wolfip_ctx.lock); ++ return 0; ++ } ++ } ++ ++ return -EINVAL; ++} ++ ++static int wolfip_shutdown(void *obj, int how) ++{ ++ ARG_UNUSED(obj); ++ ARG_UNUSED(how); ++ ++ errno = ENOTSUP; ++ return -1; ++} ++ ++static int wolfip_bind(void *obj, const struct net_sockaddr *addr, ++ net_socklen_t addrlen) ++{ ++ struct wolfip_socket *sock = obj; ++ struct wolfIP_sockaddr_in sin; ++ int ret; ++ ++ if (addr == NULL || addr->sa_family != AF_INET || ++ addrlen < sizeof(struct net_sockaddr_in)) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ sin = wolfip_sockaddr_in_from_net((const struct net_sockaddr_in *)addr); ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_bind(WOLFIP_STACK(), sock->wolf_fd, ++ (const struct wolfIP_sockaddr *)&sin, ++ sizeof(sin)); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret < 0) { ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++ } ++ return 0; ++} ++ ++static int wolfip_connect(void *obj, const struct net_sockaddr *addr, ++ net_socklen_t addrlen) ++{ ++ struct wolfip_socket *sock = obj; ++ struct wolfIP_sockaddr_in sin; ++ int ret; ++ ++ if (addr == NULL || addr->sa_family != AF_INET || ++ addrlen < sizeof(struct net_sockaddr_in)) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ sin = wolfip_sockaddr_in_from_net((const struct net_sockaddr_in *)addr); ++ ++ do { ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_connect(WOLFIP_STACK(), sock->wolf_fd, ++ (const struct wolfIP_sockaddr *)&sin, ++ sizeof(sin)); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret >= 0) { ++ return 0; ++ } ++ } while ((ret == -WOLFIP_EAGAIN || ret == -EINPROGRESS) && ++ !sock->nonblock && ++ wolfip_socket_wait(sock, CB_EVENT_WRITABLE | CB_EVENT_CLOSED, ++ sock->send_timeout) == 0); ++ ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++} ++ ++static int wolfip_listen(void *obj, int backlog) ++{ ++ struct wolfip_socket *sock = obj; ++ int ret; ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_listen(WOLFIP_STACK(), sock->wolf_fd, backlog); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret < 0) { ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++ } ++ sock->is_listener = true; ++ return 0; ++} ++ ++static int wolfip_accept(void *obj, struct net_sockaddr *addr, ++ net_socklen_t *addrlen) ++{ ++ struct wolfip_socket *sock = obj; ++ struct wolfIP_sockaddr_in peer; ++ int child_wolf_fd = -1; ++ int fd; ++ struct wolfip_socket *child; ++ ++ /* The RX-side event callback pre-accepts inline when a SYN lands on ++ * this listener and stashes the resulting wolfIP child fd. Pop it ++ * here; otherwise block waiting for the next SYN. ++ */ ++ while (true) { ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ if (sock->pending_child_wolf_fd >= 0) { ++ child_wolf_fd = sock->pending_child_wolf_fd; ++ peer = sock->pending_child_peer; ++ sock->pending_child_wolf_fd = -1; ++ } ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ if (child_wolf_fd >= 0) { ++ break; ++ } ++ ++ if (atomic_get(&sock->pending_events) & CB_EVENT_CLOSED) { ++ errno = ECONNRESET; ++ return -1; ++ } ++ ++ if (sock->nonblock) { ++ errno = EAGAIN; ++ return -1; ++ } ++ ++ if (wolfip_socket_wait(sock, ++ CB_EVENT_READABLE | CB_EVENT_CLOSED, ++ sock->recv_timeout) != 0) { ++ errno = EAGAIN; ++ return -1; ++ } ++ } ++ ++ fd = wolfip_alloc_socket_obj(child_wolf_fd, &child); ++ if (fd < 0) { ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ (void)wolfIP_sock_close(WOLFIP_STACK(), child_wolf_fd); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ return -1; ++ } ++ ++ /* The child was pre-accepted in TCP_SYN_RCVD. Block until the peer's ++ * final ACK lands and wolfIP fires CB_EVENT_WRITABLE on the ++ * ESTABLISHED transition (or CB_EVENT_READABLE if data arrived in ++ * the same RX burst, or CB_EVENT_CLOSED on RST). Without this wait ++ * the sample's first recv() would hit wolfIP's "not established" ++ * branch and fail with -1. ++ * ++ * wolfip_socket_wait()'s can_write fast-path can't help here: ++ * wolfIP_sock_can_write returns 1 for SYN_RCVD too, so the wait ++ * would return immediately. Drive the wait purely off the cb-fired ++ * pending_events bits. ++ */ ++ if (!sock->nonblock) { ++ const atomic_val_t want = CB_EVENT_READABLE | CB_EVENT_WRITABLE | ++ CB_EVENT_CLOSED; ++ while (true) { ++ atomic_val_t pending; ++ ++ pending = atomic_and(&child->pending_events, ~want); ++ if (pending & want) { ++ break; ++ } ++ ++ k_sem_reset(&child->event_sem); ++ pending = atomic_and(&child->pending_events, ~want); ++ if (pending & want) { ++ break; ++ } ++ ++ if (k_sem_take(&child->event_sem, sock->recv_timeout) < 0) { ++ break; ++ } ++ } ++ } ++ ++ zvfs_finalize_typed_fd(fd, child, &wolfip_socket_fd_op_vtable.fd_vtable, ++ ZVFS_MODE_IFSOCK); ++ ++ if (addr != NULL && addrlen != NULL && *addrlen >= sizeof(struct net_sockaddr_in)) { ++ wolfip_sockaddr_in_to_net(&peer, (struct net_sockaddr_in *)addr); ++ *addrlen = sizeof(struct net_sockaddr_in); ++ } ++ ++ return fd; ++} ++ ++static ssize_t wolfip_sendto(void *obj, const void *buf, size_t len, int flags, ++ const struct net_sockaddr *dest_addr, ++ net_socklen_t addrlen) ++{ ++ struct wolfip_socket *sock = obj; ++ struct wolfIP_sockaddr_in sin; ++ const struct wolfIP_sockaddr *dest = NULL; ++ int ret; ++ ++ if (dest_addr != NULL) { ++ if (dest_addr->sa_family != AF_INET || ++ addrlen < sizeof(struct net_sockaddr_in)) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ sin = wolfip_sockaddr_in_from_net((const struct net_sockaddr_in *)dest_addr); ++ dest = (const struct wolfIP_sockaddr *)&sin; ++ } ++ ++ do { ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_sendto(WOLFIP_STACK(), sock->wolf_fd, buf, len, ++ flags, dest, dest ? sizeof(sin) : 0); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret >= 0) { ++ return ret; ++ } ++ } while (ret == -WOLFIP_EAGAIN && ++ !sock->nonblock && ++ wolfip_socket_wait(sock, CB_EVENT_WRITABLE | CB_EVENT_CLOSED, ++ sock->send_timeout) == 0); ++ ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++} ++ ++static ssize_t wolfip_recvfrom(void *obj, void *buf, size_t max_len, int flags, ++ struct net_sockaddr *src_addr, ++ net_socklen_t *addrlen) ++{ ++ struct wolfip_socket *sock = obj; ++ struct wolfIP_sockaddr_in sin; ++ socklen_t sin_len; ++ int ret; ++ ++ sin_len = sizeof(sin); ++ ++ do { ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_recvfrom(WOLFIP_STACK(), sock->wolf_fd, buf, ++ max_len, flags, ++ src_addr ? (struct wolfIP_sockaddr *)&sin : NULL, ++ src_addr ? &sin_len : NULL); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret >= 0) { ++ if (src_addr != NULL && addrlen != NULL && ++ *addrlen >= sizeof(struct net_sockaddr_in)) { ++ wolfip_sockaddr_in_to_net(&sin, ++ (struct net_sockaddr_in *)src_addr); ++ *addrlen = sizeof(struct net_sockaddr_in); ++ } ++ ++ return ret; ++ } ++ } while (ret == -WOLFIP_EAGAIN && ++ !sock->nonblock && ++ wolfip_socket_wait(sock, CB_EVENT_READABLE | CB_EVENT_CLOSED, ++ sock->recv_timeout) == 0); ++ ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++} ++ ++static int wolfip_getsockopt(void *obj, int level, int optname, ++ void *optval, net_socklen_t *optlen) ++{ ++ struct wolfip_socket *sock = obj; ++ socklen_t wolf_len; ++ int ret; ++ ++ if (optlen == NULL) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ wolf_len = *optlen; ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_getsockopt(WOLFIP_STACK(), sock->wolf_fd, level, ++ optname, optval, &wolf_len); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret < 0) { ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++ } ++ ++ *optlen = wolf_len; ++ return 0; ++} ++ ++static int wolfip_setsockopt(void *obj, int level, int optname, ++ const void *optval, net_socklen_t optlen) ++{ ++ struct wolfip_socket *sock = obj; ++ int ret; ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_setsockopt(WOLFIP_STACK(), sock->wolf_fd, level, ++ optname, optval, optlen); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret < 0) { ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++ } ++ return 0; ++} ++ ++static size_t wolfip_msghdr_len(const struct net_msghdr *msg) ++{ ++ size_t total = 0U; ++ size_t i; ++ ++ if (msg == NULL) { ++ return 0U; ++ } ++ ++ for (i = 0; i < msg->msg_iovlen; i++) { ++ total += msg->msg_iov[i].iov_len; ++ } ++ ++ return total; ++} ++ ++static void wolfip_scatter_iov(const struct net_msghdr *msg, ++ const uint8_t *src, size_t len) ++{ ++ size_t copied = 0U; ++ size_t i; ++ ++ for (i = 0; i < msg->msg_iovlen && copied < len; i++) { ++ size_t chunk = MIN(msg->msg_iov[i].iov_len, len - copied); ++ ++ memcpy(msg->msg_iov[i].iov_base, src + copied, chunk); ++ copied += chunk; ++ } ++} ++ ++static ssize_t wolfip_sendmsg(void *obj, const struct net_msghdr *msg, int flags) ++{ ++ uint8_t stack_buf[256]; ++ uint8_t *buf = stack_buf; ++ size_t len; ++ size_t copied = 0U; ++ size_t i; ++ ++ if (msg == NULL) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (msg->msg_iovlen == 0U) { ++ return wolfip_sendto(obj, NULL, 0U, flags, ++ (const struct net_sockaddr *)msg->msg_name, ++ msg->msg_namelen); ++ } ++ ++ if (msg->msg_iovlen == 1U) { ++ return wolfip_sendto(obj, msg->msg_iov[0].iov_base, ++ msg->msg_iov[0].iov_len, flags, ++ (const struct net_sockaddr *)msg->msg_name, ++ msg->msg_namelen); ++ } ++ ++ len = wolfip_msghdr_len(msg); ++ if (len > sizeof(stack_buf)) { ++ buf = k_malloc(len); ++ if (buf == NULL) { ++ errno = ENOMEM; ++ return -1; ++ } ++ } ++ ++ for (i = 0; i < msg->msg_iovlen; i++) { ++ memcpy(buf + copied, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len); ++ copied += msg->msg_iov[i].iov_len; ++ } ++ ++ i = wolfip_sendto(obj, buf, len, flags, ++ (const struct net_sockaddr *)msg->msg_name, ++ msg->msg_namelen); ++ ++ if (buf != stack_buf) { ++ k_free(buf); ++ } ++ ++ return (ssize_t)i; ++} ++ ++static ssize_t wolfip_recvmsg(void *obj, struct net_msghdr *msg, int flags) ++{ ++ uint8_t stack_buf[256]; ++ uint8_t *buf = stack_buf; ++ size_t len; ++ ssize_t ret; ++ ++ if (msg == NULL) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (msg->msg_iovlen == 0U) { ++ return 0; ++ } ++ ++ if (msg->msg_iovlen == 1U) { ++ ret = wolfip_recvfrom(obj, msg->msg_iov[0].iov_base, ++ msg->msg_iov[0].iov_len, flags, ++ (struct net_sockaddr *)msg->msg_name, ++ &msg->msg_namelen); ++ if (ret >= 0) { ++ msg->msg_flags = 0; ++ } ++ return ret; ++ } ++ ++ len = wolfip_msghdr_len(msg); ++ if (len > sizeof(stack_buf)) { ++ buf = k_malloc(len); ++ if (buf == NULL) { ++ errno = ENOMEM; ++ return -1; ++ } ++ } ++ ++ ret = wolfip_recvfrom(obj, buf, len, flags, ++ (struct net_sockaddr *)msg->msg_name, ++ &msg->msg_namelen); ++ if (ret >= 0) { ++ wolfip_scatter_iov(msg, buf, (size_t)ret); ++ msg->msg_flags = 0; ++ } ++ ++ if (buf != stack_buf) { ++ k_free(buf); ++ } ++ ++ return ret; ++} ++ ++static int wolfip_getpeername(void *obj, struct net_sockaddr *addr, ++ net_socklen_t *addrlen) ++{ ++ struct wolfip_socket *sock = obj; ++ struct wolfIP_sockaddr_in peer; ++ socklen_t peer_len; ++ int ret; ++ ++ if (addr == NULL || addrlen == NULL || *addrlen < sizeof(struct net_sockaddr_in)) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ peer_len = sizeof(peer); ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_getpeername(WOLFIP_STACK(), sock->wolf_fd, ++ (struct wolfIP_sockaddr *)&peer, &peer_len); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret < 0) { ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++ } ++ ++ wolfip_sockaddr_in_to_net(&peer, (struct net_sockaddr_in *)addr); ++ *addrlen = sizeof(struct net_sockaddr_in); ++ return 0; ++} ++ ++static int wolfip_getsockname(void *obj, struct net_sockaddr *addr, ++ net_socklen_t *addrlen) ++{ ++ struct wolfip_socket *sock = obj; ++ struct wolfIP_sockaddr_in self; ++ socklen_t self_len; ++ int ret; ++ ++ if (addr == NULL || addrlen == NULL || *addrlen < sizeof(struct net_sockaddr_in)) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ self_len = sizeof(self); ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ ret = wolfIP_sock_getsockname(WOLFIP_STACK(), sock->wolf_fd, ++ (struct wolfIP_sockaddr *)&self, &self_len); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ if (ret < 0) { ++ errno = wolfip_errno_from_ret(ret); ++ return -1; ++ } ++ ++ wolfip_sockaddr_in_to_net(&self, (struct net_sockaddr_in *)addr); ++ *addrlen = sizeof(struct net_sockaddr_in); ++ return 0; ++} ++ ++static const struct socket_op_vtable wolfip_socket_fd_op_vtable = { ++ .fd_vtable = { ++ .read = wolfip_read, ++ .write = wolfip_write, ++ .close2 = wolfip_close2, ++ .ioctl = wolfip_ioctl, ++ }, ++ .shutdown = wolfip_shutdown, ++ .bind = wolfip_bind, ++ .connect = wolfip_connect, ++ .listen = wolfip_listen, ++ .accept = wolfip_accept, ++ .sendto = wolfip_sendto, ++ .recvfrom = wolfip_recvfrom, ++ .getsockopt = wolfip_getsockopt, ++ .setsockopt = wolfip_setsockopt, ++ .sendmsg = wolfip_sendmsg, ++ .recvmsg = wolfip_recvmsg, ++ .getpeername = wolfip_getpeername, ++ .getsockname = wolfip_getsockname, ++}; ++ ++NET_SOCKET_OFFLOAD_REGISTER(wolfip, CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY, ++ NET_AF_INET, wolfip_is_supported_family, ++ wolfip_socket_create); ++ ++int wolfip_attach_iface(struct net_if *iface) ++{ ++ extern const struct net_l2 NET_L2_GET_NAME(WOLFIP_L2); ++ struct wolfip_binding *binding; ++ struct wolfIP_ll_dev *ll; ++ struct net_linkaddr *linkaddr; ++ const struct net_l2 **l2_ptr; ++ int ret; ++ ++ if (iface == NULL) { ++ return -EINVAL; ++ } ++ ++ ret = wolfip_init_runtime(); ++ if (ret < 0) { ++ return ret; ++ } ++ ++ binding = wolfip_binding_for_iface(iface); ++ if (binding == NULL) { ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(wolfip_ctx.bindings); i++) { ++ if (wolfip_ctx.bindings[i].iface == NULL) { ++ binding = &wolfip_ctx.bindings[i]; ++ binding->iface = iface; ++ binding->if_idx = i; ++ break; ++ } ++ } ++ } ++ ++ if (binding == NULL) { ++ return -ENOMEM; ++ } ++ ++ ll = wolfIP_getdev_ex(WOLFIP_STACK(), binding->if_idx); ++ if (ll == NULL) { ++ return -EINVAL; ++ } ++ ++ memset(ll, 0, sizeof(*ll)); ++ ll->poll = wolfip_poll_ll_dev; ++ ll->send = wolfip_send_ll_dev; ++ ll->priv = binding; ++ ll->mtu = net_if_get_mtu(iface); ++ wolfip_copy_iface_name(ll, iface); ++ ++ linkaddr = net_if_get_link_addr(iface); ++ if (linkaddr != NULL && linkaddr->len >= WOLFIP_ETH_ADDR_LEN) { ++ memcpy(ll->mac, linkaddr->addr, WOLFIP_ETH_ADDR_LEN); ++ } ++ ++ binding->enabled = true; ++ ++ l2_ptr = (const struct net_l2 **)&iface->if_dev->l2; ++ *l2_ptr = &NET_L2_GET_NAME(WOLFIP_L2); ++ ++ net_if_flag_set(iface, NET_IF_IPV4); ++ ++ net_if_socket_offload_set(iface, wolfip_socket_create); ++ ++ if (!wolfip_ctx.dns_registered) { ++ socket_offload_dns_register(&wolfip_dns_ops); ++ wolfip_ctx.dns_registered = true; ++ } ++ ++ socket_offload_dns_enable(true); ++ ++ if (!net_if_is_admin_up(iface)) { ++ (void)net_if_up(iface); ++ } ++ ++ k_sem_give(&wolfip_ctx.wake_sem); ++ return 0; ++} ++ ++int wolfip_detach_iface(struct net_if *iface) ++{ ++ struct wolfip_binding *binding; ++ ++ binding = wolfip_binding_for_iface(iface); ++ if (binding == NULL) { ++ return -ENOENT; ++ } ++ ++ net_if_flag_clear(iface, NET_IF_IPV4); ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ (void)wolfIP_route_delete(WOLFIP_STACK(), binding->if_idx, 0U, 0U); ++ wolfIP_ipconfig_set_ex(WOLFIP_STACK(), binding->if_idx, 0U, 0U, 0U); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ net_if_socket_offload_set(iface, NULL); ++ memset(binding, 0, sizeof(*binding)); ++ ++ if (!wolfip_any_enabled()) { ++ socket_offload_dns_enable(false); ++ } ++ ++ return 0; ++} ++ ++int wolfip_enable_iface(struct net_if *iface) ++{ ++ return wolfip_attach_iface(iface); ++} ++ ++int wolfip_disable_iface(struct net_if *iface) ++{ ++ return wolfip_detach_iface(iface); ++} ++ ++bool wolfip_iface_is_enabled(struct net_if *iface) ++{ ++ struct wolfip_binding *binding; ++ ++ binding = wolfip_binding_for_iface(iface); ++ return binding != NULL && binding->enabled; ++} ++ ++int wolfip_zephyr_l2_input(struct net_if *iface, const uint8_t *frame, size_t len) ++{ ++ struct wolfip_binding *binding; ++ ++ binding = wolfip_binding_for_iface(iface); ++ if (binding == NULL || !binding->enabled) { ++ return -ENOENT; ++ } ++ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++ wolfIP_recv_ex(WOLFIP_STACK(), binding->if_idx, (void *)frame, ++ (uint32_t)len); ++ /* Drive callbacks synchronously between frames. wolfIP holds events ++ * on each socket between wolfIP_recv_ex and the next wolfIP_poll. If ++ * we let the worker thread pick this up on its poll-interval tick, ++ * follow-up frames in the same TAP burst (e.g. the peer's ACK of a ++ * just-arrived SYN) will land before the LISTEN socket's accept-cb ++ * can run, leaving the listener stranded mid-handshake. ++ */ ++ (void)wolfIP_poll(WOLFIP_STACK(), k_uptime_get()); ++ k_mutex_unlock(&wolfip_ctx.lock); ++ ++ k_sem_give(&wolfip_ctx.wake_sem); ++ return 0; ++} ++ ++uint32_t wolfIP_getrandom(void) ++{ ++ return sys_rand32_get(); ++} ++ ++struct wolfIP *wolfip_zephyr_get_stack(void) ++{ ++ return wolfip_ctx.stack; ++} ++ ++int wolfip_zephyr_get_if_idx(struct net_if *iface, unsigned int *if_idx) ++{ ++ struct wolfip_binding *binding = wolfip_binding_for_iface(iface); ++ if (binding == NULL || !binding->enabled || if_idx == NULL) { ++ return -ENOENT; ++ } ++ *if_idx = binding->if_idx; ++ return 0; ++} ++ ++void wolfip_zephyr_lock(void) ++{ ++ k_mutex_lock(&wolfip_ctx.lock, K_FOREVER); ++} ++ ++void wolfip_zephyr_unlock(void) ++{ ++ k_mutex_unlock(&wolfip_ctx.lock); ++} ++ ++static int wolfip_sys_init(void) ++{ ++ return 0; ++} ++ ++SYS_INIT(wolfip_sys_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/port/zephyr/patches/0002-net-l2-wolfip-module.patch b/port/zephyr/patches/0002-net-l2-wolfip-module.patch new file mode 100644 index 0000000..3773f4b --- /dev/null +++ b/port/zephyr/patches/0002-net-l2-wolfip-module.patch @@ -0,0 +1,170 @@ +Subject: [PATCH 2/4] NET_L2_WOLFIP — raw-frame L2 owned by wolfIP + +Adds a thin L2 module that intercepts Ethernet frames before Zephyr's +native ethernet L2 / ARP / IPv4 dispatch, handing the raw frame to +wolfip_zephyr_l2_input and delegating TX to the underlying ethernet +driver. Hooks subsys/net/l2/{CMakeLists.txt,Kconfig} to source the new +subdir. Depends on patch 0001 for the wolfip header / glue. + +--- + +diff --git a/subsys/net/l2/CMakeLists.txt b/subsys/net/l2/CMakeLists.txt +index d0cfc48f..f94b1d43 100644 +--- a/subsys/net/l2/CMakeLists.txt ++++ b/subsys/net/l2/CMakeLists.txt +@@ -35,3 +35,7 @@ endif() + if(CONFIG_NET_L2_CANBUS_RAW) + add_subdirectory(canbus) + endif() ++ ++if(CONFIG_NET_L2_WOLFIP) ++ add_subdirectory(wolfip) ++endif() +diff --git a/subsys/net/l2/Kconfig b/subsys/net/l2/Kconfig +index 64695b54..cce78802 100644 +--- a/subsys/net/l2/Kconfig ++++ b/subsys/net/l2/Kconfig +@@ -18,6 +18,8 @@ source "subsys/net/l2/virtual/Kconfig" + + source "subsys/net/l2/ethernet/Kconfig" + ++source "subsys/net/l2/wolfip/Kconfig" ++ + source "subsys/net/l2/ppp/Kconfig" + + config NET_L2_PHY_IEEE802154 +diff --git a/subsys/net/l2/wolfip/CMakeLists.txt b/subsys/net/l2/wolfip/CMakeLists.txt +new file mode 100644 +index 00000000..c30e3c7b +--- /dev/null ++++ b/subsys/net/l2/wolfip/CMakeLists.txt +@@ -0,0 +1,10 @@ ++# SPDX-License-Identifier: Apache-2.0 ++ ++zephyr_library() ++zephyr_library_compile_definitions_ifdef( ++ CONFIG_NEWLIB_LIBC __LINUX_ERRNO_EXTENSIONS__ ++ ) ++ ++zephyr_library_include_directories(. ${ZEPHYR_BASE}/subsys/net/ip) ++ ++zephyr_library_sources_ifdef(CONFIG_NET_L2_WOLFIP wolfip_l2.c) +diff --git a/subsys/net/l2/wolfip/Kconfig b/subsys/net/l2/wolfip/Kconfig +new file mode 100644 +index 00000000..8d65bfd7 +--- /dev/null ++++ b/subsys/net/l2/wolfip/Kconfig +@@ -0,0 +1,10 @@ ++# SPDX-License-Identifier: Apache-2.0 ++ ++config NET_L2_WOLFIP ++ bool "wolfIP raw-frame L2" ++ depends on WOLFIP ++ help ++ Lean L2 layer that delegates the entire L3+ stack (ARP, IPv4, ICMP, ++ TCP, UDP, DHCPv4, DNS) to wolfIP. The underlying Zephyr device must ++ still be an Ethernet driver; this L2 passes raw frames in and out ++ without invoking Zephyr's Ethernet L2 / ARP / IPv4. +diff --git a/subsys/net/l2/wolfip/wolfip_l2.c b/subsys/net/l2/wolfip/wolfip_l2.c +new file mode 100644 +index 00000000..5f7eb3c6 +--- /dev/null ++++ b/subsys/net/l2/wolfip/wolfip_l2.c +@@ -0,0 +1,97 @@ ++/* ++ * Copyright (c) 2026 wolfSSL Inc. ++ * ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++#include ++LOG_MODULE_REGISTER(net_l2_wolfip, CONFIG_NET_CORE_LOG_LEVEL); ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "net_stats.h" ++ ++static enum net_verdict wolfip_l2_recv(struct net_if *iface, ++ struct net_pkt *pkt) ++{ ++ uint8_t frame[1600]; ++ size_t len; ++ ++ len = net_pkt_get_len(pkt); ++ if (len == 0 || len > sizeof(frame)) { ++ NET_DBG("wolfIP L2 recv: invalid frame length %zu", len); ++ return NET_DROP; ++ } ++ ++ net_pkt_cursor_init(pkt); ++ if (net_pkt_read(pkt, frame, len) < 0) { ++ return NET_DROP; ++ } ++ ++ wolfip_zephyr_l2_input(iface, frame, len); ++ ++ net_stats_update_bytes_recv(iface, len); ++ ++ /* NET_OK means "consumed"; processing_data() will NOT unref — we must. */ ++ net_pkt_unref(pkt); ++ return NET_OK; ++} ++ ++static int wolfip_l2_send(struct net_if *iface, struct net_pkt *pkt) ++{ ++ const struct ethernet_api *api = net_if_get_device(iface)->api; ++ int ret; ++ ++ if (!api) { ++ return -ENOENT; ++ } ++ ++ ret = net_l2_send(api->send, net_if_get_device(iface), iface, pkt); ++ if (!ret) { ++ size_t pkt_len = net_pkt_get_len(pkt); ++ ++ net_stats_update_bytes_sent(iface, pkt_len); ++ ret = (int)pkt_len; ++ net_pkt_unref(pkt); ++ } ++ ++ return ret; ++} ++ ++static int wolfip_l2_enable(struct net_if *iface, bool state) ++{ ++ const struct ethernet_api *api = net_if_get_device(iface)->api; ++ ++ if (!api) { ++ return -ENOENT; ++ } ++ ++ if (!state) { ++ if (api->stop) { ++ return api->stop(net_if_get_device(iface)); ++ } ++ } else { ++ if (api->start) { ++ return api->start(net_if_get_device(iface)); ++ } ++ } ++ ++ return 0; ++} ++ ++static enum net_l2_flags wolfip_l2_flags(struct net_if *iface) ++{ ++ ARG_UNUSED(iface); ++ return NET_L2_MULTICAST | NET_L2_PROMISC_MODE; ++} ++ ++NET_L2_INIT(WOLFIP_L2, wolfip_l2_recv, wolfip_l2_send, wolfip_l2_enable, wolfip_l2_flags); diff --git a/port/zephyr/patches/0003-net-shell-wolfip.patch b/port/zephyr/patches/0003-net-shell-wolfip.patch new file mode 100644 index 0000000..c825668 --- /dev/null +++ b/port/zephyr/patches/0003-net-shell-wolfip.patch @@ -0,0 +1,1936 @@ +Subject: [PATCH 3/4] net shell — wolfIP-aware command rewrites + +Amends the NET_SHELL_*_SUPPORTED umbrellas to also evaluate true when +WOLFIP is enabled, and adds wolfIP-aware variants alongside the +existing native-Zephyr code in each shell command file: + iface.c, ipv4.c, arp.c, ping.c, route.c, dhcpv4.c, + tcp.c, udp.c, conn.c, stats.c +Existing CONFIG_NET_NATIVE_* paths are preserved as backwards-compat +siblings. Depends on patch 0001 (public header + wolfip_ctrl_* API). + +--- + +diff --git a/subsys/net/lib/shell/Kconfig b/subsys/net/lib/shell/Kconfig +index 5d5e3bdb..e1150c79 100644 +--- a/subsys/net/lib/shell/Kconfig ++++ b/subsys/net/lib/shell/Kconfig +@@ -38,7 +38,7 @@ config NET_SHELL_CAPTURE_SUPPORTED + config NET_SHELL_DHCPV4_SUPPORTED + bool "DHCPv4 start / stop" + default y +- depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_DHCPV4_SERVER || NET_DHCPV4 ++ depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_DHCPV4_SERVER || NET_DHCPV4 || WOLFIP + + config NET_SHELL_DHCPV6_SUPPORTED + bool "DHCPv6 start / stop" +@@ -83,7 +83,7 @@ config NET_SHELL_CONNECTION_MANAGER_SUPPORTED + config NET_SHELL_IPV4_SUPPORTED + bool "IPv4 config" + default y +- depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_NATIVE_IPV4 && NET_IPV4) ++ depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_NATIVE_IPV4 && NET_IPV4) || WOLFIP + + config NET_SHELL_IPV6_SUPPORTED + bool "IPv6 config" +@@ -93,7 +93,7 @@ config NET_SHELL_IPV6_SUPPORTED + config NET_SHELL_IP_SUPPORTED + bool "Generic IP utilities" + default y +- depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_IP ++ depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_IP || WOLFIP + + config NET_SHELL_PKT_ALLOC_SUPPORTED + bool "Packet allocation monitoring" +@@ -123,7 +123,7 @@ config NET_SHELL_POWER_MANAGEMENT_SUPPORTED + config NET_SHELL_ROUTE_SUPPORTED + bool "IP routing config" + default y +- depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_ROUTE && NET_NATIVE) ++ depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_ROUTE && NET_NATIVE) || WOLFIP + + config NET_SHELL_SOCKETS_SERVICE_SUPPORTED + bool "Socket service status" +@@ -138,12 +138,12 @@ config NET_SHELL_STATISTICS_SUPPORTED + config NET_SHELL_TCP_SUPPORTED + bool "Send / receive TCP data" + default y +- depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_NATIVE_TCP && NET_TCP) ++ depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_NATIVE_TCP && NET_TCP) || WOLFIP + + config NET_SHELL_UDP_SUPPORTED + bool "Send / receive UDP data" + default y +- depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_NATIVE_UDP && NET_UDP) ++ depends on NET_SHELL_SHOW_DISABLED_COMMANDS || (NET_NATIVE_UDP && NET_UDP) || WOLFIP + + config NET_SHELL_VIRTUAL_SUPPORTED + bool "Virtual network interface management" +diff --git a/subsys/net/lib/shell/arp.c b/subsys/net/lib/shell/arp.c +index 1070977b..8d818173 100644 +--- a/subsys/net/lib/shell/arp.c ++++ b/subsys/net/lib/shell/arp.c +@@ -15,6 +15,13 @@ LOG_MODULE_DECLARE(net_shell); + #include + #endif + ++#if defined(CONFIG_WOLFIP) ++#include ++#include ++#include ++#include ++#endif ++ + #if defined(CONFIG_NET_ARP) && defined(CONFIG_NET_NATIVE) + static void arp_cb(struct arp_entry *entry, void *user_data) + { +@@ -33,9 +40,9 @@ static void arp_cb(struct arp_entry *entry, void *user_data) + + (*count)++; + } +-#endif /* CONFIG_NET_ARP */ ++#endif /* CONFIG_NET_ARP && CONFIG_NET_NATIVE */ + +-#if !defined(CONFIG_NET_ARP) ++#if !defined(CONFIG_NET_ARP) && !defined(CONFIG_WOLFIP) + static void print_arp_error(const struct shell *sh) + { + PR_INFO("Set %s to enable %s support.\n", +@@ -64,7 +71,12 @@ static int cmd_net_arp(const struct shell *sh, size_t argc, char *argv[]) + PR("ARP cache is empty.\n"); + } + } ++#elif defined(CONFIG_WOLFIP) ++ ARG_UNUSED(argv); ++ PR("ARP table: managed by wolfIP — iteration not exposed.\n" ++ "Use 'net arp lookup ' to resolve a single entry.\n"); + #else ++ ARG_UNUSED(argv); + print_arp_error(sh); + #endif + +@@ -79,6 +91,8 @@ static int cmd_net_arp_flush(const struct shell *sh, size_t argc, char *argv[]) + #if defined(CONFIG_NET_ARP) + PR("Flushing ARP cache.\n"); + net_arp_clear_cache(NULL); ++#elif defined(CONFIG_WOLFIP) ++ PR("Not supported (wolfIP)\n"); + #else + print_arp_error(sh); + #endif +@@ -143,6 +157,52 @@ static int cmd_net_arp_add(const struct shell *sh, size_t argc, char *argv[]) + } + #endif /* CONFIG_NET_ARP && CONFIG_NET_NATIVE */ + ++#if defined(CONFIG_WOLFIP) ++static int cmd_net_arp_add(const struct shell *sh, size_t argc, char *argv[]) ++{ ++ ARG_UNUSED(argc); ++ ARG_UNUSED(argv); ++ ++ PR("Not supported (wolfIP): static ARP entries are not exposed.\n"); ++ return 0; ++} ++ ++static int cmd_net_arp_lookup(const struct shell *sh, size_t argc, char *argv[]) ++{ ++ struct in_addr a; ++ unsigned int if_idx; ++ ip4 v; ++ uint8_t mac[6]; ++ int ret; ++ ++ if (argc < 3) { ++ PR_WARNING("Usage: net arp lookup \n"); ++ return -ENOEXEC; ++ } ++ ++ if_idx = (unsigned int)atoi(argv[1]); ++ ++ ret = net_addr_pton(AF_INET, argv[2], &a); ++ if (ret < 0) { ++ PR_WARNING("Invalid IPv4 address: %s\n", argv[2]); ++ return -ENOEXEC; ++ } ++ ++ v = sys_be32_to_cpu(a.s_addr); ++ ++ ret = wolfip_ctrl_arp_lookup(if_idx, v, mac); ++ if (ret < 0) { ++ PR("no ARP entry\n"); ++ } else { ++ PR("%s -> %02x:%02x:%02x:%02x:%02x:%02x\n", ++ argv[2], ++ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); ++ } ++ ++ return 0; ++} ++#endif /* CONFIG_WOLFIP */ ++ + SHELL_STATIC_SUBCMD_SET_CREATE( + net_cmd_arp, + SHELL_CMD(flush, NULL, SHELL_HELP("Remove all entries from ARP cache", ""), +@@ -150,6 +210,14 @@ SHELL_STATIC_SUBCMD_SET_CREATE( + #if defined(CONFIG_NET_ARP) && defined(CONFIG_NET_NATIVE) + SHELL_CMD(add, NULL, SHELL_HELP("Add a static ARP entry", "[] "), + cmd_net_arp_add), ++#endif ++#if defined(CONFIG_WOLFIP) ++ SHELL_CMD(add, NULL, ++ SHELL_HELP("Add a static ARP entry (not supported with wolfIP)", ""), ++ cmd_net_arp_add), ++ SHELL_CMD(lookup, NULL, ++ SHELL_HELP("Look up an ARP entry via wolfIP", " "), ++ cmd_net_arp_lookup), + #endif + SHELL_SUBCMD_SET_END); + +diff --git a/subsys/net/lib/shell/conn.c b/subsys/net/lib/shell/conn.c +index bd0a5b28..2f97ea6a 100644 +--- a/subsys/net/lib/shell/conn.c ++++ b/subsys/net/lib/shell/conn.c +@@ -15,7 +15,9 @@ LOG_MODULE_DECLARE(net_shell); + #include + #endif + +-#if defined(CONFIG_NET_OFFLOAD) || defined(CONFIG_NET_NATIVE) ++#if (defined(CONFIG_NET_OFFLOAD) || defined(CONFIG_NET_NATIVE)) && \ ++ (defined(CONFIG_NET_TCP) || defined(CONFIG_NET_UDP) || \ ++ defined(CONFIG_NET_SOCKETS_PACKET) || defined(CONFIG_NET_SOCKETS_CAN)) + static void context_cb(struct net_context *context, void *user_data) + { + #if defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4) +@@ -49,8 +51,10 @@ static void context_cb(struct net_context *context, void *user_data) + + (*count)++; + } +-#endif /* CONFIG_NET_OFFLOAD || CONFIG_NET_NATIVE */ ++#endif /* (CONFIG_NET_OFFLOAD || CONFIG_NET_NATIVE) && (TCP || UDP || PACKET || CAN) */ + ++#if defined(CONFIG_NET_TCP) || defined(CONFIG_NET_UDP) || \ ++ defined(CONFIG_NET_SOCKETS_PACKET) || defined(CONFIG_NET_SOCKETS_CAN) + static void conn_handler_cb(struct net_conn *conn, void *user_data) + { + #if defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4) +@@ -103,6 +107,7 @@ static void conn_handler_cb(struct net_conn *conn, void *user_data) + + (*count)++; + } ++#endif /* CONFIG_NET_TCP || CONFIG_NET_UDP || CONFIG_NET_SOCKETS_PACKET || CONFIG_NET_SOCKETS_CAN */ + + #if CONFIG_NET_TCP_LOG_LEVEL >= LOG_LEVEL_DBG + struct tcp_detail_info { +@@ -216,11 +221,13 @@ static int cmd_net_conn(const struct shell *sh, size_t argc, char *argv[]) + struct net_shell_user_data user_data; + int count = 0; + +- PR(" Context \tIface Flags Local Remote\n"); +- + user_data.sh = sh; + user_data.user_data = &count; + ++#if defined(CONFIG_NET_TCP) || defined(CONFIG_NET_UDP) || \ ++ defined(CONFIG_NET_SOCKETS_PACKET) || defined(CONFIG_NET_SOCKETS_CAN) ++ PR(" Context \tIface Flags Local Remote\n"); ++ + net_context_foreach(context_cb, &user_data); + + if (count == 0) { +@@ -236,6 +243,9 @@ static int cmd_net_conn(const struct shell *sh, size_t argc, char *argv[]) + if (count == 0) { + PR("No connection handlers found.\n"); + } ++#else ++ PR("Native network connections: not built.\n"); ++#endif /* CONFIG_NET_TCP || CONFIG_NET_UDP || CONFIG_NET_SOCKETS_PACKET || CONFIG_NET_SOCKETS_CAN */ + + #if defined(CONFIG_NET_TCP) + PR("\nTCP Context Src port Dst port " +diff --git a/subsys/net/lib/shell/dhcpv4.c b/subsys/net/lib/shell/dhcpv4.c +index 10c915c7..d4231994 100644 +--- a/subsys/net/lib/shell/dhcpv4.c ++++ b/subsys/net/lib/shell/dhcpv4.c +@@ -8,9 +8,16 @@ + LOG_MODULE_DECLARE(net_shell); + + #include ++#if !defined(CONFIG_WOLFIP) + #include ++#endif + #include + ++#if defined(CONFIG_WOLFIP) ++#include ++#include ++#endif ++ + #include "net_shell_private.h" + + static int cmd_net_dhcpv4_server_start(const struct shell *sh, size_t argc, char *argv[]) +@@ -45,6 +52,8 @@ static int cmd_net_dhcpv4_server_start(const struct shell *sh, size_t argc, char + } else { + PR("DHCPv4 server started on interface %d\n", idx); + } ++#elif defined(CONFIG_WOLFIP) ++ PR_INFO("wolfIP: DHCPv4 server not supported\n"); + #else /* CONFIG_NET_DHCPV4_SERVER */ + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_DHCPV4_SERVER", "DHCPv4 server"); +@@ -78,6 +87,8 @@ static int cmd_net_dhcpv4_server_stop(const struct shell *sh, size_t argc, char + } else { + PR("DHCPv4 server stopped on interface %d\n", idx); + } ++#elif defined(CONFIG_WOLFIP) ++ PR_INFO("wolfIP: DHCPv4 server not supported\n"); + #else /* CONFIG_NET_DHCPV4_SERVER */ + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_DHCPV4_SERVER", "DHCPv4 server"); +@@ -180,6 +191,8 @@ static int cmd_net_dhcpv4_server_status(const struct shell *sh, size_t argc, cha + } else if (count == 0) { + PR("DHCPv4 server - no addresses assigned\n"); + } ++#elif defined(CONFIG_WOLFIP) ++ PR_INFO("wolfIP: DHCPv4 server not supported\n"); + #else /* CONFIG_NET_DHCPV4_SERVER */ + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_DHCPV4_SERVER", "DHCPv4 server"); +@@ -211,6 +224,35 @@ static int cmd_net_dhcpv4_client_start(const struct shell *sh, size_t argc, char + + net_dhcpv4_restart(iface); + ++#elif defined(CONFIG_WOLFIP) ++ struct net_if *iface; ++ unsigned int if_idx; ++ int idx, ret; ++ ++ if (argc < 2) { ++ PR_ERROR("Correct usage: net dhcpv4 client %s \n", "start"); ++ return -EINVAL; ++ } ++ ++ idx = atoi(argv[1]); ++ iface = net_if_get_by_index(idx); ++ if (!iface) { ++ PR_WARNING("No such interface in index %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ ret = wolfip_zephyr_get_if_idx(iface, &if_idx); ++ if (ret < 0) { ++ PR_ERROR("Failed to get wolfIP interface index: %d\n", ret); ++ return -ENOEXEC; ++ } ++ ++ ret = wolfip_ctrl_dhcp_start(if_idx); ++ if (ret < 0) { ++ PR_ERROR("DHCPv4 start failed: %d\n", ret); ++ } else { ++ PR("DHCPv4 client started on iface %d\n", idx); ++ } + #else /* CONFIG_NET_DHCPV4 */ + PR_INFO("Set %s to enable %s support.\n", "CONFIG_NET_DHCPV4", "DHCPv4"); + #endif /* CONFIG_NET_DHCPV4 */ +@@ -241,12 +283,51 @@ static int cmd_net_dhcpv4_client_stop(const struct shell *sh, size_t argc, char + + net_dhcpv4_stop(iface); + ++#elif defined(CONFIG_WOLFIP) ++ PR_INFO("wolfIP: DHCPv4 client stop not supported (no API)\n"); + #else /* CONFIG_NET_DHCPV4 */ + PR_INFO("Set %s to enable %s support.\n", "CONFIG_NET_DHCPV4", "DHCPv4"); + #endif /* CONFIG_NET_DHCPV4 */ + return 0; + } + ++#if defined(CONFIG_WOLFIP) ++static int cmd_net_dhcpv4_client_status(const struct shell *sh, size_t argc, char *argv[]) ++{ ++ struct net_if *iface; ++ unsigned int if_idx; ++ int idx, ret; ++ ++ if (argc < 2) { ++ PR_ERROR("Correct usage: net dhcpv4 client status \n"); ++ return -EINVAL; ++ } ++ ++ idx = atoi(argv[1]); ++ iface = net_if_get_by_index(idx); ++ if (!iface) { ++ PR_WARNING("No such interface in index %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ ret = wolfip_zephyr_get_if_idx(iface, &if_idx); ++ if (ret < 0) { ++ PR_ERROR("Failed to get wolfIP interface index: %d\n", ret); ++ return -ENOEXEC; ++ } ++ ++ if (wolfip_ctrl_dhcp_is_bound(if_idx)) { ++ PR("DHCPv4 client on iface %d: bound\n", idx); ++ } else if (wolfip_ctrl_dhcp_is_running(if_idx)) { ++ PR("DHCPv4 client on iface %d: running\n", idx); ++ } else { ++ PR("DHCPv4 client on iface %d: idle\n", idx); ++ } ++ ++ return 0; ++} ++#endif /* CONFIG_WOLFIP */ ++ + SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_dhcpv4_server, + SHELL_CMD_ARG(start, NULL, + SHELL_HELP("Start the DHCPv4 server operation on the interface", +@@ -273,6 +354,12 @@ SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_dhcpv4_client, + SHELL_HELP("Stop the DHCPv4 client operation on the interface", + ""), + cmd_net_dhcpv4_client_stop, 2, 0), ++#if defined(CONFIG_WOLFIP) ++ SHELL_CMD_ARG(status, NULL, ++ SHELL_HELP("Print the DHCPv4 client status on the interface", ++ ""), ++ cmd_net_dhcpv4_client_status, 2, 0), ++#endif /* CONFIG_WOLFIP */ + SHELL_SUBCMD_SET_END + ); + +diff --git a/subsys/net/lib/shell/iface.c b/subsys/net/lib/shell/iface.c +index 649e8062..4d3a5ae7 100644 +--- a/subsys/net/lib/shell/iface.c ++++ b/subsys/net/lib/shell/iface.c +@@ -28,6 +28,11 @@ LOG_MODULE_DECLARE(net_shell); + + #include "net_shell_private.h" + ++#if defined(CONFIG_WOLFIP) ++#include ++#include ++#endif ++ + #define UNICAST_MASK GENMASK(7, 1) + #define LOCAL_BIT BIT(1) + +@@ -597,7 +602,41 @@ skip_ipv4: + PR("IPv4 gateway : %s\n", + net_sprint_ipv4_addr(&ipv4->gw)); + } +-#endif /* CONFIG_NET_IPV4 */ ++#elif defined(CONFIG_WOLFIP) ++ { ++ unsigned int if_idx; ++ ip4 wip = 0, wmask = 0, wgw = 0; ++ char buf[NET_IPV4_ADDR_LEN]; ++ struct in_addr addr; ++ ++ if (wolfip_zephyr_get_if_idx(iface, &if_idx) < 0) { ++ goto skip_ipv4; ++ } ++ ++ wolfip_ctrl_get_addr(if_idx, &wip, &wmask, &wgw); ++ ++ if (wip == 0) { ++ PR("IPv4 (wolfIP): unconfigured\n"); ++ goto skip_ipv4; ++ } ++ ++ PR("IPv4 (wolfIP):\n"); ++ ++ addr.s_addr = sys_cpu_to_be32(wip); ++ net_addr_ntop(AF_INET, &addr, buf, sizeof(buf)); ++ PR("\tAddress: %s\n", buf); ++ ++ addr.s_addr = sys_cpu_to_be32(wmask); ++ net_addr_ntop(AF_INET, &addr, buf, sizeof(buf)); ++ PR("\tNetmask: %s\n", buf); ++ ++ addr.s_addr = sys_cpu_to_be32(wgw); ++ net_addr_ntop(AF_INET, &addr, buf, sizeof(buf)); ++ PR("\tGateway: %s\n", buf); ++ ++skip_ipv4:; ++ } ++#endif /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + + #if defined(CONFIG_NET_DHCPV4) + if (net_if_flag_is_set(iface, NET_IF_IPV4)) { +@@ -619,7 +658,20 @@ skip_ipv4: + PR("DHCPv4 state : %s\n", + net_dhcpv4_state_name(iface->config.dhcpv4.state)); + } +-#endif /* CONFIG_NET_DHCPV4 */ ++#elif defined(CONFIG_WOLFIP) ++ { ++ unsigned int if_idx; ++ int running, bound; ++ ++ if (wolfip_zephyr_get_if_idx(iface, &if_idx) >= 0) { ++ running = wolfip_ctrl_dhcp_is_running(if_idx); ++ bound = wolfip_ctrl_dhcp_is_bound(if_idx); ++ PR("DHCPv4 (wolfIP): %s%s\n", ++ running ? "running " : "idle", ++ bound ? "/ bound" : ""); ++ } ++ } ++#endif /* CONFIG_NET_DHCPV4 / CONFIG_WOLFIP */ + } + + static int cmd_net_set_mac(const struct shell *sh, size_t argc, char *argv[]) +diff --git a/subsys/net/lib/shell/ipv4.c b/subsys/net/lib/shell/ipv4.c +index a18a963e..01ea8a8b 100644 +--- a/subsys/net/lib/shell/ipv4.c ++++ b/subsys/net/lib/shell/ipv4.c +@@ -9,6 +9,11 @@ + LOG_MODULE_DECLARE(net_shell); + + #include ++#include ++ ++#if defined(CONFIG_WOLFIP) ++#include ++#endif + + #include "net_shell_private.h" + #include "../ip/ipv4.h" +@@ -66,10 +71,48 @@ static void ip_address_info_cb(struct net_if *iface, void *user_data) + net_sprint_ipv4_addr(&ipv4->mcast[i].address.in6_addr)); + } + } +-#endif /* CONFIG_NET_IPV4 */ ++#elif defined(CONFIG_WOLFIP) ++static void ip_address_info_cb(struct net_if *iface, void *user_data) ++{ ++ struct net_shell_user_data *data = user_data; ++ const struct shell *sh = data->sh; ++ unsigned int if_idx; ++ ip4 ip, mask, gw; ++ struct net_in_addr a; ++ char ip_str[NET_IPV4_ADDR_LEN]; ++ char mask_str[NET_IPV4_ADDR_LEN]; ++ char gw_str[NET_IPV4_ADDR_LEN]; ++ ++ if (wolfip_zephyr_get_if_idx(iface, &if_idx) < 0) { ++ return; ++ } ++ ++ if (wolfip_ctrl_get_addr(if_idx, &ip, &mask, &gw) < 0) { ++ PR("iface %u: error reading address\n", if_idx); ++ return; ++ } ++ ++ if (ip == 0) { ++ PR("iface %u: unconfigured\n", if_idx); ++ return; ++ } ++ ++ a.s_addr = sys_cpu_to_be32(ip); ++ net_addr_ntop(NET_AF_INET, &a, ip_str, sizeof(ip_str)); ++ ++ a.s_addr = sys_cpu_to_be32(mask); ++ net_addr_ntop(NET_AF_INET, &a, mask_str, sizeof(mask_str)); ++ ++ a.s_addr = sys_cpu_to_be32(gw); ++ net_addr_ntop(NET_AF_INET, &a, gw_str, sizeof(gw_str)); ++ ++ PR("iface %u: %s/%s gw %s\n", if_idx, ip_str, mask_str, gw_str); ++} ++#endif /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + + static int cmd_net_ipv4(const struct shell *sh, size_t argc, char *argv[]) + { ++#if defined(CONFIG_NET_IPV4) + PR("IPv4 support : %s\n", + IS_ENABLED(CONFIG_NET_IPV4) ? + "enabled" : "disabled"); +@@ -88,27 +131,43 @@ static int cmd_net_ipv4(const struct shell *sh, size_t argc, char *argv[]) + IS_ENABLED(CONFIG_NET_IPV4_PMTU) ? "enabled" : "disabled"); + #endif /* CONFIG_NET_NATIVE_IPV4 */ + +-#if defined(CONFIG_NET_IPV4) ++ { ++ struct net_shell_user_data user_data; ++ ++ PR("Max number of IPv4 network interfaces " ++ "in the system : %d\n", ++ CONFIG_NET_IF_MAX_IPV4_COUNT); ++ PR("Max number of unicast IPv4 addresses " ++ "per network interface : %d\n", ++ CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT); ++ PR("Max number of multicast IPv4 addresses " ++ "per network interface : %d\n", ++ CONFIG_NET_IF_MCAST_IPV4_ADDR_COUNT); ++ ++ user_data.sh = sh; ++ user_data.user_data = NULL; ++ ++ /* Print information about address lifetime */ ++ net_if_foreach(ip_address_info_cb, &user_data); ++ } ++ ++ return 0; ++#elif defined(CONFIG_WOLFIP) + struct net_shell_user_data user_data; + +- PR("Max number of IPv4 network interfaces " +- "in the system : %d\n", +- CONFIG_NET_IF_MAX_IPV4_COUNT); +- PR("Max number of unicast IPv4 addresses " +- "per network interface : %d\n", +- CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT); +- PR("Max number of multicast IPv4 addresses " +- "per network interface : %d\n", +- CONFIG_NET_IF_MCAST_IPV4_ADDR_COUNT); ++ PR("IPv4 support (wolfIP): yes\n"); ++ PR("Max interfaces: %d\n", CONFIG_WOLFIP_MAX_INTERFACES); + + user_data.sh = sh; + user_data.user_data = NULL; + +- /* Print information about address lifetime */ + net_if_foreach(ip_address_info_cb, &user_data); +-#endif /* CONFIG_NET_IPV4 */ + + return 0; ++#else ++ PR("IPv4 support : disabled\n"); ++ return -ENOEXEC; ++#endif + } + + static int cmd_net_ip_add(const struct shell *sh, size_t argc, char *argv[]) +@@ -172,9 +231,74 @@ static int cmd_net_ip_add(const struct shell *sh, size_t argc, char *argv[]) + net_if_ipv4_set_netmask_by_addr(iface, &addr, &netmask); + } + +-#else /* CONFIG_NET_IPV4 */ ++#elif defined(CONFIG_WOLFIP) ++ struct net_if *iface; ++ int idx; ++ struct net_in_addr a; ++ unsigned int if_idx; ++ ip4 ip, mask, gw, cur_ip, cur_mask, cur_gw; ++ ++ if (argc < 3) { ++ PR_ERROR("Correct usage: net ipv4 add
[]\n"); ++ return -EINVAL; ++ } ++ ++ idx = get_iface_idx(sh, argv[1]); ++ if (idx < 0) { ++ return -ENOEXEC; ++ } ++ ++ iface = net_if_get_by_index(idx); ++ if (!iface) { ++ PR_WARNING("No such interface in index %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ /* wolfIP has no IGMP/multicast join API */ ++ if (argc >= 3 && strcmp(argv[2], "join") == 0) { ++ PR_ERROR("wolfIP: multicast join not supported\n"); ++ return -ENOTSUP; ++ } ++ ++ if (net_addr_pton(NET_AF_INET, argv[2], &a)) { ++ PR_ERROR("Invalid address: %s\n", argv[2]); ++ return -EINVAL; ++ } ++ ip = sys_be32_to_cpu(a.s_addr); ++ ++ /* Default mask to 255.255.255.0 if not supplied */ ++ if (argc >= 4) { ++ if (net_addr_pton(NET_AF_INET, argv[3], &a)) { ++ PR_ERROR("Invalid netmask: %s\n", argv[3]); ++ return -EINVAL; ++ } ++ mask = sys_be32_to_cpu(a.s_addr); ++ } else { ++ mask = 0xFFFFFF00U; ++ } ++ ++ if (wolfip_zephyr_get_if_idx(iface, &if_idx) < 0) { ++ PR_ERROR("Cannot get wolfIP interface index for iface %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ /* Preserve existing gateway */ ++ cur_gw = 0; ++ if (wolfip_ctrl_get_addr(if_idx, &cur_ip, &cur_mask, &cur_gw) < 0) { ++ cur_gw = 0; ++ } ++ gw = cur_gw; ++ ++ if (wolfip_ctrl_set_addr(if_idx, ip, mask, gw) < 0) { ++ PR_ERROR("Cannot set address on interface %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ PR("Address set on interface %d\n", idx); ++ ++#else /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + PR_INFO("Set %s to enable %s support.\n", "CONFIG_NET_IPV4", "IPv4"); +-#endif /* CONFIG_NET_IPV4 */ ++#endif /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + return 0; + } + +@@ -221,9 +345,43 @@ static int cmd_net_ip_del(const struct shell *sh, size_t argc, char *argv[]) + return -ENOEXEC; + } + } +-#else /* CONFIG_NET_IPV4 */ ++#elif defined(CONFIG_WOLFIP) ++ struct net_if *iface; ++ int idx; ++ unsigned int if_idx; ++ ++ if (argc != 3) { ++ PR_ERROR("Correct usage: net ipv4 del
\n"); ++ return -EINVAL; ++ } ++ ++ idx = get_iface_idx(sh, argv[1]); ++ if (idx < 0) { ++ return -ENOEXEC; ++ } ++ ++ iface = net_if_get_by_index(idx); ++ if (!iface) { ++ PR_WARNING("No such interface in index %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ if (wolfip_zephyr_get_if_idx(iface, &if_idx) < 0) { ++ PR_ERROR("Cannot get wolfIP interface index for iface %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ /* wolfIP stores one address per iface; clearing means zeroing all */ ++ if (wolfip_ctrl_set_addr(if_idx, 0, 0, 0) < 0) { ++ PR_ERROR("Failed to clear address on interface %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ PR("Address cleared on interface %d\n", idx); ++ ++#else /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + PR_INFO("Set %s to enable %s support.\n", "CONFIG_NET_IPV4", "IPv4"); +-#endif /* CONFIG_NET_IPV4 */ ++#endif /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + return 0; + } + +@@ -257,12 +415,60 @@ static int cmd_net_ip_gateway(const struct shell *sh, size_t argc, char *argv[]) + + net_if_ipv4_set_gw(iface, &addr); + +-#else /* CONFIG_NET_IPV4 */ ++#elif defined(CONFIG_WOLFIP) ++ struct net_if *iface; ++ int idx; ++ struct net_in_addr a; ++ unsigned int if_idx; ++ ip4 cur_ip, cur_mask, cur_gw, new_gw; ++ ++ if (argc != 3) { ++ PR_ERROR("Correct usage: net ipv4 gateway \n"); ++ return -ENOEXEC; ++ } ++ ++ idx = get_iface_idx(sh, argv[1]); ++ if (idx < 0) { ++ return -ENOEXEC; ++ } ++ ++ iface = net_if_get_by_index(idx); ++ if (!iface) { ++ PR_WARNING("No such interface in index %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ if (net_addr_pton(NET_AF_INET, argv[2], &a)) { ++ PR_ERROR("Invalid address: %s\n", argv[2]); ++ return -EINVAL; ++ } ++ new_gw = sys_be32_to_cpu(a.s_addr); ++ ++ if (wolfip_zephyr_get_if_idx(iface, &if_idx) < 0) { ++ PR_ERROR("Cannot get wolfIP interface index for iface %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ /* Read current ip/mask so we don't clobber them */ ++ if (wolfip_ctrl_get_addr(if_idx, &cur_ip, &cur_mask, &cur_gw) < 0) { ++ PR_ERROR("Cannot read current address for interface %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ if (wolfip_ctrl_set_addr(if_idx, cur_ip, cur_mask, new_gw) < 0) { ++ PR_ERROR("Cannot set gateway on interface %d\n", idx); ++ return -ENOEXEC; ++ } ++ ++ PR("Gateway set on interface %d\n", idx); ++ ++#else /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + PR_INFO("Set %s to enable %s support.\n", "CONFIG_NET_IPV4", "IPv4"); +-#endif /* CONFIG_NET_IPV4 */ ++#endif /* CONFIG_NET_IPV4 / CONFIG_WOLFIP */ + return 0; + } + ++#if defined(CONFIG_NET_IPV4) || defined(CONFIG_WOLFIP) + SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_ip, + SHELL_CMD(add, NULL, + SHELL_HELP("Adds the address to the interface", +@@ -283,3 +489,4 @@ SHELL_SUBCMD_ADD((net), ipv4, &net_cmd_ip, + "Print information about IPv4 specific information and " + "configuration.", + cmd_net_ipv4, 1, 0); ++#endif /* CONFIG_NET_IPV4 || CONFIG_WOLFIP */ +diff --git a/subsys/net/lib/shell/ping.c b/subsys/net/lib/shell/ping.c +index a3d98961..11855779 100644 +--- a/subsys/net/lib/shell/ping.c ++++ b/subsys/net/lib/shell/ping.c +@@ -11,15 +11,274 @@ LOG_MODULE_DECLARE(net_shell); + #include + #include + #include ++ ++#if defined(CONFIG_WOLFIP) ++#include ++#include ++#include ++#include ++#else + #include ++#endif + + #include "net_shell_private.h" + ++#if !defined(CONFIG_WOLFIP) + #include "../ip/icmpv6.h" + #include "../ip/icmpv4.h" + #include "../ip/route.h" ++#endif ++ ++#if defined(CONFIG_NET_IP) || defined(CONFIG_WOLFIP) ++ ++/* ------------------------------------------------------------------ */ ++/* wolfIP ICMP ping backend */ ++/* ------------------------------------------------------------------ */ ++#if defined(CONFIG_WOLFIP) ++ ++/* Minimal ICMP echo header built and parsed inline. */ ++struct icmp_echo_hdr { ++ uint8_t type; ++ uint8_t code; ++ uint16_t chksum; ++ uint16_t id; ++ uint16_t seq; ++}; ++ ++#define ICMP_ECHO_REQUEST 8 ++#define ICMP_ECHO_REPLY 0 ++#define WOLFIP_PING_PAYLOAD_SIZE 32 ++ ++static uint16_t icmp_checksum(const void *data, size_t len) ++{ ++ const uint16_t *p = data; ++ uint32_t sum = 0; ++ ++ while (len > 1) { ++ sum += *p++; ++ len -= 2; ++ } ++ ++ if (len == 1) { ++ uint16_t last = 0; ++ ++ memcpy(&last, p, 1); ++ sum += last; ++ } ++ ++ while (sum >> 16) { ++ sum = (sum & 0xFFFF) + (sum >> 16); ++ } ++ ++ return (uint16_t)(~sum); ++} ++ ++struct ping_context_wolfip { ++ int fd; ++ struct k_work_delayable work; ++ struct k_work_delayable recv_work; ++ uint32_t count; ++ uint32_t interval; ++ uint32_t sent; ++ uint32_t received; ++ uint16_t id; ++ uint16_t seq; ++ struct sockaddr_in dest; ++ const struct shell *sh; ++ /* recv retry bookkeeping */ ++ int recv_retries; ++}; ++ ++static struct ping_context_wolfip wolfip_ping_ctx; ++ ++static void wolfip_ping_recv_work(struct k_work *w); ++ ++static void wolfip_ping_work(struct k_work *w) ++{ ++ struct k_work_delayable *dwork = k_work_delayable_from_work(w); ++ struct ping_context_wolfip *ctx = ++ CONTAINER_OF(dwork, struct ping_context_wolfip, work); ++ const struct shell *sh = ctx->sh; ++ uint8_t pkt[sizeof(struct icmp_echo_hdr) + WOLFIP_PING_PAYLOAD_SIZE]; ++ struct icmp_echo_hdr *hdr = (struct icmp_echo_hdr *)pkt; ++ ssize_t ret; ++ ++ if (ctx->fd < 0) { ++ return; ++ } ++ ++ if (ctx->sent >= ctx->count) { ++ /* All pings sent; wait for last reply via recv_work. */ ++ return; ++ } ++ ++ ctx->seq++; ++ ctx->sent++; ++ ++ memset(pkt, 0, sizeof(pkt)); ++ hdr->type = ICMP_ECHO_REQUEST; ++ hdr->code = 0; ++ hdr->chksum = 0; ++ hdr->id = htons(ctx->id); ++ hdr->seq = htons(ctx->seq); ++ ++ /* Fill payload with a recognisable pattern. */ ++ for (int i = 0; i < WOLFIP_PING_PAYLOAD_SIZE; i++) { ++ pkt[sizeof(struct icmp_echo_hdr) + i] = (uint8_t)('a' + (i % 26)); ++ } ++ ++ hdr->chksum = icmp_checksum(pkt, sizeof(pkt)); ++ ++ ret = sendto(ctx->fd, pkt, sizeof(pkt), 0, ++ (const struct sockaddr *)&ctx->dest, ++ sizeof(ctx->dest)); ++ if (ret < 0) { ++ PR_WARNING("ping: sendto failed: %d\n", errno); ++ } ++ ++ /* Schedule receive polling; recv_work will reschedule itself up to ++ * a few times if the reply hasn't arrived yet. ++ */ ++ ctx->recv_retries = 20; /* 20 * 50ms = 1 s per ping */ ++ k_work_reschedule(&ctx->recv_work, K_MSEC(50)); ++ ++ /* Schedule next TX. */ ++ if (ctx->sent < ctx->count) { ++ k_work_reschedule(&ctx->work, K_MSEC(ctx->interval)); ++ } ++} ++ ++static void wolfip_ping_recv_work(struct k_work *w) ++{ ++ struct k_work_delayable *dwork = k_work_delayable_from_work(w); ++ struct ping_context_wolfip *ctx = ++ CONTAINER_OF(dwork, struct ping_context_wolfip, recv_work); ++ const struct shell *sh = ctx->sh; ++ uint8_t buf[128]; ++ struct sockaddr_in from; ++ socklen_t fromlen = sizeof(from); ++ ssize_t n; ++ char addr_str[NET_IPV4_ADDR_LEN]; ++ ++ if (ctx->fd < 0) { ++ return; ++ } ++ ++ n = recvfrom(ctx->fd, buf, sizeof(buf), MSG_DONTWAIT, ++ (struct sockaddr *)&from, &fromlen); ++ if (n < 0) { ++ if (errno == EAGAIN || errno == EWOULDBLOCK) { ++ /* Reply not yet available. */ ++ ctx->recv_retries--; ++ if (ctx->recv_retries > 0) { ++ k_work_reschedule(&ctx->recv_work, K_MSEC(50)); ++ } else { ++ PR_WARNING("ping: timeout waiting for reply seq=%u\n", ++ ctx->seq); ++ /* Check if we are done. */ ++ if (ctx->sent >= ctx->count) { ++ PR_INFO("Ping complete: sent=%u received=%u\n", ++ ctx->sent, ctx->received); ++ zsock_close(ctx->fd); ++ ctx->fd = -1; ++ shell_set_bypass(ctx->sh, NULL, NULL); ++ shell_fprintf(ctx->sh, SHELL_NORMAL, ""); ++ } ++ } ++ return; ++ } ++ PR_WARNING("ping: recvfrom error: %d\n", errno); ++ return; ++ } ++ ++ if (n < (ssize_t)sizeof(struct icmp_echo_hdr)) { ++ /* Truncated — try again. */ ++ ctx->recv_retries--; ++ if (ctx->recv_retries > 0) { ++ k_work_reschedule(&ctx->recv_work, K_MSEC(50)); ++ } ++ return; ++ } ++ ++ /* ++ * wolfIP's ICMP socket delivers only the ICMP payload (no IP header ++ * prepended), so buf[0] is the ICMP type byte directly. ++ */ ++ struct icmp_echo_hdr *hdr = (struct icmp_echo_hdr *)buf; ++ ++ if (hdr->type != ICMP_ECHO_REPLY) { ++ /* Not an echo reply; keep waiting. */ ++ ctx->recv_retries--; ++ if (ctx->recv_retries > 0) { ++ k_work_reschedule(&ctx->recv_work, K_MSEC(50)); ++ } ++ return; ++ } ++ ++ /* wolfIP rewrites the ICMP echo id field to its per-socket value ++ * (used for demuxing replies). The id we set on TX never makes it ++ * onto the wire, so we can't compare against ctx->id here. Trust ++ * wolfIP's per-socket demux (replies for this fd land here only) ++ * and accept any seq we've already sent. ++ */ ++ uint16_t reply_seq = ntohs(hdr->seq); ++ if (reply_seq == 0 || reply_seq > ctx->seq) { ++ /* Out-of-band or future seq; skip. */ ++ ctx->recv_retries--; ++ if (ctx->recv_retries > 0) { ++ k_work_reschedule(&ctx->recv_work, K_MSEC(50)); ++ } ++ return; ++ } ++ ++ ctx->received++; ++ ++ net_addr_ntop(AF_INET, &from.sin_addr, addr_str, sizeof(addr_str)); ++ ++ PR_SHELL(sh, "%zd bytes from %s: icmp_seq=%u\n", ++ n, addr_str, ntohs(hdr->seq)); ++ ++ if (ctx->sent >= ctx->count && ctx->received >= ctx->count) { ++ PR_INFO("Ping complete: sent=%u received=%u\n", ++ ctx->sent, ctx->received); ++ zsock_close(ctx->fd); ++ ctx->fd = -1; ++ shell_set_bypass(ctx->sh, NULL, NULL); ++ shell_fprintf(ctx->sh, SHELL_NORMAL, ""); ++ } ++} + +-#if defined(CONFIG_NET_IP) ++static void wolfip_ping_cleanup(struct ping_context_wolfip *ctx) ++{ ++ k_work_cancel_delayable(&ctx->work); ++ k_work_cancel_delayable(&ctx->recv_work); ++ if (ctx->fd >= 0) { ++ zsock_close(ctx->fd); ++ ctx->fd = -1; ++ } ++ shell_set_bypass(ctx->sh, NULL, NULL); ++} ++ ++#define ASCII_CTRL_C 0x03 ++ ++static void ping_bypass(const struct shell *sh, uint8_t *data, size_t len, void *user_data) ++{ ++ ARG_UNUSED(sh); ++ ARG_UNUSED(user_data); ++ ++ for (size_t i = 0; i < len; i++) { ++ if (data[i] == ASCII_CTRL_C) { ++ wolfip_ping_cleanup(&wolfip_ping_ctx); ++ break; ++ } ++ } ++} ++ ++#else /* !CONFIG_WOLFIP — native Zephyr path */ ++ ++/* ------------------------------------------------------------------ */ ++/* Native Zephyr ping backend (net_icmp_* APIs) */ ++/* ------------------------------------------------------------------ */ + + static struct ping_context { + struct k_work_delayable work; +@@ -212,6 +471,8 @@ static enum net_verdict handle_ipv4_echo_reply(struct net_icmp_ctx *ctx, + } + #endif /* CONFIG_NET_IPV4 */ + ++#endif /* !CONFIG_WOLFIP */ ++ + static int parse_arg(size_t *i, size_t argc, char *argv[]) + { + int res; +@@ -243,6 +504,8 @@ static int parse_arg(size_t *i, size_t argc, char *argv[]) + return res; + } + ++#if !defined(CONFIG_WOLFIP) ++ + static void ping_cleanup(struct ping_context *ctx) + { + (void)net_icmp_cleanup_ctx(&ctx->icmp); +@@ -366,17 +629,99 @@ out: + return iface; + } + +-#endif /* CONFIG_NET_IP */ ++#endif /* !CONFIG_WOLFIP */ ++ ++#endif /* CONFIG_NET_IP || CONFIG_WOLFIP */ + + static int cmd_net_ping(const struct shell *sh, size_t argc, char *argv[]) + { +-#if !defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) ++#if defined(CONFIG_WOLFIP) ++ /* ------------------------------------------------------------ */ ++ /* wolfIP path: BSD socket ICMP backend (IPv4 only) */ ++ /* ------------------------------------------------------------ */ ++ char *host = NULL; ++ int count = 3; ++ int interval = 1000; ++ struct in_addr addr4; ++ ++ for (size_t i = 1; i < argc; ++i) { ++ if (*argv[i] != '-') { ++ host = argv[i]; ++ continue; ++ } ++ ++ switch (argv[i][1]) { ++ case 'c': ++ count = parse_arg(&i, argc, argv); ++ if (count < 0) { ++ PR_WARNING("Parse error: %s\n", argv[i]); ++ return -ENOEXEC; ++ } ++ break; ++ case 'i': ++ interval = parse_arg(&i, argc, argv); ++ if (interval < 0) { ++ PR_WARNING("Parse error: %s\n", argv[i]); ++ return -ENOEXEC; ++ } ++ break; ++ default: ++ PR_WARNING("Unrecognized argument: %s\n", argv[i]); ++ return -ENOEXEC; ++ } ++ } ++ ++ if (!host) { ++ PR_WARNING("Target host missing\n"); ++ return -ENOEXEC; ++ } ++ ++ if (net_addr_pton(AF_INET, host, &addr4) != 0) { ++ PR_WARNING("Invalid IPv4 address: %s\n", host); ++ return -ENOEXEC; ++ } ++ ++ memset(&wolfip_ping_ctx, 0, sizeof(wolfip_ping_ctx)); ++ wolfip_ping_ctx.fd = -1; ++ ++ /* Open an ICMP socket via the wolfIP BSD socket offload. */ ++ int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); ++ ++ if (fd < 0) { ++ shell_error(sh, "wolfIP ICMP socket failed: errno=%d", errno); ++ return -ENOTSUP; ++ } ++ ++ wolfip_ping_ctx.fd = fd; ++ wolfip_ping_ctx.sh = sh; ++ wolfip_ping_ctx.count = (uint32_t)count; ++ wolfip_ping_ctx.interval = (uint32_t)interval; ++ wolfip_ping_ctx.id = (uint16_t)sys_rand32_get(); ++ ++ wolfip_ping_ctx.dest.sin_family = AF_INET; ++ wolfip_ping_ctx.dest.sin_addr = addr4; ++ wolfip_ping_ctx.dest.sin_port = 0; ++ ++ k_work_init_delayable(&wolfip_ping_ctx.work, wolfip_ping_work); ++ k_work_init_delayable(&wolfip_ping_ctx.recv_work, wolfip_ping_recv_work); ++ ++ PR("PING %s\n", host); ++ ++ shell_set_bypass(sh, ping_bypass, NULL); ++ k_work_reschedule(&wolfip_ping_ctx.work, K_NO_WAIT); ++ ++ return 0; ++ ++#elif !defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) + ARG_UNUSED(sh); + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + return -EOPNOTSUPP; + #else ++ /* ------------------------------------------------------------ */ ++ /* Native Zephyr path */ ++ /* ------------------------------------------------------------ */ + char *host = NULL; + + int count = 3; +diff --git a/subsys/net/lib/shell/route.c b/subsys/net/lib/shell/route.c +index 45ac0284..3de741d5 100644 +--- a/subsys/net/lib/shell/route.c ++++ b/subsys/net/lib/shell/route.c +@@ -12,6 +12,13 @@ LOG_MODULE_DECLARE(net_shell); + + #include "../ip/route.h" + ++#if defined(CONFIG_WOLFIP) ++#include ++#include ++#include ++/* struct wolfIP_route_info is declared in . */ ++#endif /* CONFIG_WOLFIP */ ++ + #if defined(CONFIG_NET_ROUTE) && defined(CONFIG_NET_NATIVE) + static void route_cb(struct net_route_entry *entry, void *user_data) + { +@@ -112,6 +119,90 @@ static void route_mcast_cb(struct net_route_entry_mcast *entry, + + static int cmd_net_ip6_route_add(const struct shell *sh, size_t argc, char *argv[]) + { ++#if defined(CONFIG_WOLFIP) ++ struct in_addr a4 = {0}; ++ ++ /* Detect IPv4 prefix — if so, dispatch to wolfIP */ ++ if (argc >= 3) { ++ char prefix_str[NET_IPV4_ADDR_LEN]; ++ char *slash; ++ ++ /* copy prefix arg so we can strip optional /plen */ ++ strncpy(prefix_str, argv[2], sizeof(prefix_str) - 1); ++ prefix_str[sizeof(prefix_str) - 1] = '\0'; ++ slash = strchr(prefix_str, '/'); ++ if (slash) { ++ *slash = '\0'; ++ } ++ ++ if (net_addr_pton(AF_INET, prefix_str, &a4) == 0) { ++ /* IPv4 path via wolfIP */ ++ struct net_if *iface = NULL; ++ int zidx; ++ unsigned int wif_idx; ++ uint8_t plen = 32; ++ struct in_addr gw4 = {0}; ++ uint32_t prefix_v; ++ uint32_t gw_v; ++ ++ if (argc != 4) { ++ PR_ERROR("Correct usage: net route add " ++ " [/] \n"); ++ return -EINVAL; ++ } ++ ++ zidx = get_iface_idx(sh, argv[1]); ++ if (zidx < 0) { ++ return -ENOEXEC; ++ } ++ ++ iface = net_if_get_by_index(zidx); ++ if (!iface) { ++ PR_WARNING("No such interface in index %d\n", ++ zidx); ++ return -ENOEXEC; ++ } ++ ++ if (wolfip_zephyr_get_if_idx(iface, &wif_idx) < 0) { ++ PR_ERROR("Interface %d is not a wolfIP interface\n", ++ zidx); ++ return -ENOEXEC; ++ } ++ ++ /* Re-parse prefix from original argv[2] with /plen */ ++ strncpy(prefix_str, argv[2], sizeof(prefix_str) - 1); ++ prefix_str[sizeof(prefix_str) - 1] = '\0'; ++ slash = strchr(prefix_str, '/'); ++ if (slash) { ++ *slash = '\0'; ++ plen = (uint8_t)atoi(slash + 1); ++ } ++ ++ if (net_addr_pton(AF_INET, prefix_str, &a4) != 0) { ++ PR_ERROR("Invalid prefix: %s\n", prefix_str); ++ return -EINVAL; ++ } ++ prefix_v = sys_be32_to_cpu(a4.s_addr); ++ ++ if (net_addr_pton(AF_INET, argv[3], &gw4) != 0) { ++ PR_ERROR("Invalid gateway: %s\n", argv[3]); ++ return -EINVAL; ++ } ++ gw_v = sys_be32_to_cpu(gw4.s_addr); ++ ++ if (wolfip_ctrl_route_add(wif_idx, prefix_v, plen, ++ gw_v) < 0) { ++ PR_ERROR("Failed to add IPv4 route\n"); ++ return -ENOEXEC; ++ } ++ ++ PR("Route %s/%u via %s added\n", prefix_str, plen, ++ argv[3]); ++ return 0; ++ } ++ } ++#endif /* CONFIG_WOLFIP */ ++ + #if defined(CONFIG_NET_NATIVE_IPV6) && (CONFIG_NET_ROUTE) + struct net_if *iface = NULL; + int idx; +@@ -164,6 +255,79 @@ static int cmd_net_ip6_route_add(const struct shell *sh, size_t argc, char *argv + + static int cmd_net_ip6_route_del(const struct shell *sh, size_t argc, char *argv[]) + { ++#if defined(CONFIG_WOLFIP) ++ struct in_addr a4 = {0}; ++ ++ /* Detect IPv4 prefix — if so, dispatch to wolfIP */ ++ if (argc >= 3) { ++ char prefix_str[NET_IPV4_ADDR_LEN]; ++ char *slash; ++ ++ strncpy(prefix_str, argv[2], sizeof(prefix_str) - 1); ++ prefix_str[sizeof(prefix_str) - 1] = '\0'; ++ slash = strchr(prefix_str, '/'); ++ if (slash) { ++ *slash = '\0'; ++ } ++ ++ if (net_addr_pton(AF_INET, prefix_str, &a4) == 0) { ++ /* IPv4 path via wolfIP */ ++ struct net_if *iface = NULL; ++ int zidx; ++ unsigned int wif_idx; ++ uint8_t plen = 32; ++ uint32_t prefix_v; ++ ++ if (argc != 3) { ++ PR_ERROR("Correct usage: net route del " ++ " [/]\n"); ++ return -EINVAL; ++ } ++ ++ zidx = get_iface_idx(sh, argv[1]); ++ if (zidx < 0) { ++ return -ENOEXEC; ++ } ++ ++ iface = net_if_get_by_index(zidx); ++ if (!iface) { ++ PR_WARNING("No such interface in index %d\n", ++ zidx); ++ return -ENOEXEC; ++ } ++ ++ if (wolfip_zephyr_get_if_idx(iface, &wif_idx) < 0) { ++ PR_ERROR("Interface %d is not a wolfIP interface\n", ++ zidx); ++ return -ENOEXEC; ++ } ++ ++ /* Re-parse with /plen from original argv[2] */ ++ strncpy(prefix_str, argv[2], sizeof(prefix_str) - 1); ++ prefix_str[sizeof(prefix_str) - 1] = '\0'; ++ slash = strchr(prefix_str, '/'); ++ if (slash) { ++ *slash = '\0'; ++ plen = (uint8_t)atoi(slash + 1); ++ } ++ ++ if (net_addr_pton(AF_INET, prefix_str, &a4) != 0) { ++ PR_ERROR("Invalid prefix: %s\n", prefix_str); ++ return -EINVAL; ++ } ++ prefix_v = sys_be32_to_cpu(a4.s_addr); ++ ++ if (wolfip_ctrl_route_del(wif_idx, prefix_v, plen) < 0) { ++ PR_ERROR("Failed to delete IPv4 route\n"); ++ return -ENOEXEC; ++ } ++ ++ PR("Route %s/%u deleted\n", prefix_str, plen); ++ return 0; ++ } ++ } ++#endif /* CONFIG_WOLFIP */ ++ + #if defined(CONFIG_NET_NATIVE_IPV6) && (CONFIG_NET_ROUTE) + struct net_if *iface = NULL; + int idx; +@@ -226,7 +390,37 @@ static int cmd_net_route(const struct shell *sh, size_t argc, char *argv[]) + #if defined(CONFIG_NET_ROUTE_MCAST) + net_route_mcast_foreach(route_mcast_cb, NULL, &user_data); + #endif +-#endif ++#endif /* CONFIG_NET_NATIVE */ ++ ++#if defined(CONFIG_WOLFIP) ++ PR("\nIPv4 routes (wolfIP):\n"); ++ unsigned int rc = wolfip_ctrl_route_count(); ++ ++ if (rc == 0) { ++ PR("\tno routes\n"); ++ } else { ++ for (unsigned int i = 0; i < rc; i++) { ++ struct wolfIP_route_info info; ++ char prefix_buf[NET_IPV4_ADDR_LEN]; ++ char gw_buf[NET_IPV4_ADDR_LEN]; ++ struct in_addr a; ++ ++ if (wolfip_ctrl_route_get(i, &info) < 0) { ++ continue; ++ } ++ ++ a.s_addr = sys_cpu_to_be32(info.prefix); ++ net_addr_ntop(AF_INET, &a, prefix_buf, ++ sizeof(prefix_buf)); ++ a.s_addr = sys_cpu_to_be32(info.gateway); ++ net_addr_ntop(AF_INET, &a, gw_buf, sizeof(gw_buf)); ++ PR("\t%s/%u via %s iface %u\n", ++ prefix_buf, info.prefix_len, gw_buf, ++ (unsigned int)info.if_idx); ++ } ++ } ++#endif /* CONFIG_WOLFIP */ ++ + return 0; + } + +diff --git a/subsys/net/lib/shell/stats.c b/subsys/net/lib/shell/stats.c +index 06aa1c4f..4884c56f 100644 +--- a/subsys/net/lib/shell/stats.c ++++ b/subsys/net/lib/shell/stats.c +@@ -554,12 +554,12 @@ static void net_shell_print_statistics(struct net_if *iface, void *user_data) + GET_STAT(iface, ip_errors.chkerr), + GET_STAT(iface, ip_errors.protoerr)); + +-#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) ++#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) && defined(CONFIG_NET_NATIVE_IPV4) + PR("IPv4 PMTU recv %u\tsent\t%u\tdrop\t%u\n", + GET_STAT(iface, ipv4_pmtu.recv), + GET_STAT(iface, ipv4_pmtu.sent), + GET_STAT(iface, ipv4_pmtu.drop)); +-#endif /* CONFIG_NET_STATISTICS_IPV4_PMTU */ ++#endif /* CONFIG_NET_STATISTICS_IPV4_PMTU && CONFIG_NET_NATIVE_IPV4 */ + + #if defined(CONFIG_NET_STATISTICS_ICMP) && defined(CONFIG_NET_NATIVE_IPV4) + PR("ICMP recv %u\tsent\t%u\tdrop\t%u\n", +@@ -570,12 +570,12 @@ static void net_shell_print_statistics(struct net_if *iface, void *user_data) + GET_STAT(iface, icmp.typeerr), + GET_STAT(iface, icmp.chkerr)); + #endif +-#if defined(CONFIG_NET_STATISTICS_IGMP) ++#if defined(CONFIG_NET_STATISTICS_IGMP) && defined(CONFIG_NET_NATIVE_IPV4) + PR("IGMP recv %u\tsent\t%u\tdrop\t%u\n", + GET_STAT(iface, ipv4_igmp.recv), + GET_STAT(iface, ipv4_igmp.sent), + GET_STAT(iface, ipv4_igmp.drop)); +-#endif /* CONFIG_NET_STATISTICS_IGMP */ ++#endif /* CONFIG_NET_STATISTICS_IGMP && CONFIG_NET_NATIVE_IPV4 */ + #if defined(CONFIG_NET_STATISTICS_UDP) && defined(CONFIG_NET_NATIVE_UDP) + PR("UDP recv %u\tsent\t%u\tdrop\t%u\n", + GET_STAT(iface, udp.recv), +@@ -680,6 +680,10 @@ static void net_shell_print_statistics(struct net_if *iface, void *user_data) + } + #endif /* CONFIG_NET_STATISTICS_PPP && CONFIG_NET_STATISTICS_USER_API */ + ++#if defined(CONFIG_WOLFIP) ++ PR("\nwolfIP statistics: not exposed.\n"); ++#endif /* CONFIG_WOLFIP */ ++ + print_net_pm_stats(sh, iface); + } + +diff --git a/subsys/net/lib/shell/tcp.c b/subsys/net/lib/shell/tcp.c +index 4738cdf7..b2f29782 100644 +--- a/subsys/net/lib/shell/tcp.c ++++ b/subsys/net/lib/shell/tcp.c +@@ -10,6 +10,14 @@ LOG_MODULE_DECLARE(net_shell); + + #include + ++#if defined(CONFIG_WOLFIP) ++#include ++#include ++#include ++#include ++#include ++#endif ++ + #include "net_shell_private.h" + + #if defined(CONFIG_NET_TCP) && defined(CONFIG_NET_NATIVE_TCP) +@@ -253,7 +261,14 @@ static void tcp_recv_cb(struct net_context *context, struct net_pkt *pkt, + + net_pkt_unref(pkt); + } +-#endif ++#endif /* CONFIG_NET_TCP && CONFIG_NET_NATIVE_TCP */ ++ ++#if defined(CONFIG_WOLFIP) ++static struct { ++ int fd; ++ char peer_buf[48]; ++} wolfip_tcp_state = { .fd = -1 }; ++#endif /* CONFIG_WOLFIP */ + + static int cmd_net_tcp_connect(const struct shell *sh, size_t argc, char *argv[]) + { +@@ -288,10 +303,61 @@ static int cmd_net_tcp_connect(const struct shell *sh, size_t argc, char *argv[] + } + + tcp_connect(sh, ip, port, &tcp_ctx); ++#elif defined(CONFIG_WOLFIP) ++ /* tcp connect — wolfIP POSIX socket path */ ++ struct sockaddr_in dest; ++ uint16_t port; ++ int fd; ++ int ret; ++ ++ if (wolfip_tcp_state.fd >= 0) { ++ PR_WARNING("Already connected — close first\n"); ++ return -ENOEXEC; ++ } ++ ++ if (argv[1] == NULL) { ++ PR_WARNING("Peer IP address missing.\n"); ++ return -ENOEXEC; ++ } ++ ++ if (argv[2] == NULL) { ++ PR_WARNING("Peer port missing.\n"); ++ return -ENOEXEC; ++ } ++ ++ port = (uint16_t)strtoul(argv[2], NULL, 10); ++ ++ memset(&dest, 0, sizeof(dest)); ++ dest.sin_family = AF_INET; ++ dest.sin_port = htons(port); ++ ++ ret = net_addr_pton(AF_INET, argv[1], &dest.sin_addr); ++ if (ret < 0) { ++ PR_WARNING("Invalid IPv4 address: %s\n", argv[1]); ++ return -ENOEXEC; ++ } ++ ++ fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ++ if (fd < 0) { ++ PR_WARNING("Failed to create socket (errno %d)\n", errno); ++ return -ENOEXEC; ++ } ++ ++ ret = connect(fd, (struct sockaddr *)&dest, sizeof(dest)); ++ if (ret < 0) { ++ PR_WARNING("Connect failed (errno %d)\n", errno); ++ close(fd); ++ return -ENOEXEC; ++ } ++ ++ wolfip_tcp_state.fd = fd; ++ snprintf(wolfip_tcp_state.peer_buf, sizeof(wolfip_tcp_state.peer_buf), ++ "%s:%u", argv[1], port); ++ PR("Connected to %s:%u (fd %d)\n", argv[1], (unsigned int)port, fd); + #else + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP"); +-#endif /* CONFIG_NET_NATIVE_TCP */ ++#endif /* CONFIG_NET_NATIVE_TCP / CONFIG_WOLFIP */ + + return 0; + } +@@ -323,10 +389,31 @@ static int cmd_net_tcp_send(const struct shell *sh, size_t argc, char *argv[]) + return -ENOEXEC; + } + ++#elif defined(CONFIG_WOLFIP) ++ /* tcp send — wolfIP POSIX socket path */ ++ ssize_t n; ++ ++ if (wolfip_tcp_state.fd < 0) { ++ PR_WARNING("Not connected\n"); ++ return -ENOEXEC; ++ } ++ ++ if (argv[1] == NULL) { ++ PR_WARNING("No data to send.\n"); ++ return -ENOEXEC; ++ } ++ ++ n = send(wolfip_tcp_state.fd, argv[1], strlen(argv[1]), 0); ++ if (n < 0) { ++ PR_WARNING("send failed (errno %d)\n", errno); ++ return -ENOEXEC; ++ } ++ ++ PR("Sent %zd bytes\n", n); + #else + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP"); +-#endif /* CONFIG_NET_NATIVE_TCP */ ++#endif /* CONFIG_NET_NATIVE_TCP / CONFIG_WOLFIP */ + + return 0; + } +@@ -351,10 +438,33 @@ static int cmd_net_tcp_recv(const struct shell *sh, size_t argc, char *argv[]) + return -ENOEXEC; + } + ++#elif defined(CONFIG_WOLFIP) ++ /* tcp recv — wolfIP POSIX socket path */ ++ char buf[256]; ++ ssize_t n; ++ ++ if (wolfip_tcp_state.fd < 0) { ++ PR_WARNING("Not connected\n"); ++ return -ENOEXEC; ++ } ++ ++ n = recv(wolfip_tcp_state.fd, buf, sizeof(buf) - 1, MSG_DONTWAIT); ++ if (n > 0) { ++ buf[n] = '\0'; ++ PR("Received %zd bytes: %s\n", n, buf); ++ } else if (n == 0) { ++ PR("Peer closed connection\n"); ++ close(wolfip_tcp_state.fd); ++ wolfip_tcp_state.fd = -1; ++ } else if (errno == EAGAIN || errno == EWOULDBLOCK) { ++ PR("No data available\n"); ++ } else { ++ PR_WARNING("recv failed: %d\n", errno); ++ } + #else + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP"); +-#endif /* CONFIG_NET_NATIVE_TCP */ ++#endif /* CONFIG_NET_NATIVE_TCP / CONFIG_WOLFIP */ + + return 0; + } +@@ -378,10 +488,20 @@ static int cmd_net_tcp_close(const struct shell *sh, size_t argc, char *argv[]) + + PR("Connection closed.\n"); + tcp_ctx = NULL; ++#elif defined(CONFIG_WOLFIP) ++ /* tcp close — wolfIP POSIX socket path */ ++ if (wolfip_tcp_state.fd < 0) { ++ PR_WARNING("Not connected\n"); ++ return -ENOEXEC; ++ } ++ ++ close(wolfip_tcp_state.fd); ++ wolfip_tcp_state.fd = -1; ++ PR("Closed\n"); + #else + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP"); +-#endif /* CONFIG_NET_TCP */ ++#endif /* CONFIG_NET_TCP / CONFIG_WOLFIP */ + + return 0; + } +diff --git a/subsys/net/lib/shell/udp.c b/subsys/net/lib/shell/udp.c +index c29fe1ce..da37a02c 100644 +--- a/subsys/net/lib/shell/udp.c ++++ b/subsys/net/lib/shell/udp.c +@@ -49,17 +49,24 @@ static void udp_sent(struct net_context *context, int status, void *user_data) + PR_SHELL(udp_shell, "Message sent\n"); + k_sem_give(&udp_send_wait); + } +-#endif ++#endif /* CONFIG_NET_UDP && CONFIG_NET_NATIVE_UDP */ ++ ++#if defined(CONFIG_WOLFIP) ++#include ++#include ++#include ++#include ++#include ++ ++static struct { ++ int fd; ++ char local_buf[48]; ++} wolfip_udp_state = { .fd = -1 }; ++#endif /* CONFIG_WOLFIP */ + + static int cmd_net_udp_bind(const struct shell *sh, size_t argc, char *argv[]) + { +-#if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP) +- ARG_UNUSED(sh); +- ARG_UNUSED(argc); +- ARG_UNUSED(argv); +- +- return -EOPNOTSUPP; +-#else ++#if defined(CONFIG_NET_UDP) && defined(CONFIG_NET_NATIVE_UDP) + char *addr_str = NULL; + char *endptr = NULL; + uint16_t port; +@@ -149,18 +156,74 @@ release_ctx: + } + + return 0; +-#endif +-} + +-static int cmd_net_udp_close(const struct shell *sh, size_t argc, char *argv[]) +-{ +-#if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP) ++#elif defined(CONFIG_WOLFIP) ++ char *endptr = NULL; ++ uint16_t port; ++ int fd; ++ struct sockaddr_in addr; ++ ++ ARG_UNUSED(argc); ++ ++ if (argc < 3) { ++ PR_WARNING("Not enough arguments given for udp bind command\n"); ++ return -EINVAL; ++ } ++ ++ if (wolfip_udp_state.fd >= 0) { ++ PR_WARNING("Socket already bound; close first\n"); ++ return -EALREADY; ++ } ++ ++ port = (uint16_t)strtol(argv[2], &endptr, 0); ++ if (endptr == argv[2]) { ++ PR_WARNING("Invalid port number\n"); ++ return -EINVAL; ++ } ++ ++ fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ++ if (fd < 0) { ++ PR_WARNING("Cannot create UDP socket (errno %d)\n", errno); ++ return -errno; ++ } ++ ++ memset(&addr, 0, sizeof(addr)); ++ addr.sin_family = AF_INET; ++ addr.sin_port = htons(port); ++ ++ if (strcmp(argv[1], "any") == 0 || strcmp(argv[1], "0.0.0.0") == 0) { ++ addr.sin_addr.s_addr = INADDR_ANY; ++ } else { ++ if (net_addr_pton(AF_INET, argv[1], &addr.sin_addr) < 0) { ++ PR_WARNING("Cannot parse address \"%s\"\n", argv[1]); ++ close(fd); ++ return -EINVAL; ++ } ++ } ++ ++ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { ++ PR_WARNING("bind() failed (errno %d)\n", errno); ++ close(fd); ++ return -errno; ++ } ++ ++ wolfip_udp_state.fd = fd; ++ PR("Bound to %s:%u (fd %d)\n", argv[1], (unsigned int)port, fd); ++ ++ return 0; ++ ++#else + ARG_UNUSED(sh); + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + return -EOPNOTSUPP; +-#else ++#endif ++} ++ ++static int cmd_net_udp_close(const struct shell *sh, size_t argc, char *argv[]) ++{ ++#if defined(CONFIG_NET_UDP) && defined(CONFIG_NET_NATIVE_UDP) + int ret; + + if (!udp_ctx || !net_context_is_used(udp_ctx)) { +@@ -174,18 +237,34 @@ static int cmd_net_udp_close(const struct shell *sh, size_t argc, char *argv[]) + } + + return 0; +-#endif +-} + +-static int cmd_net_udp_send(const struct shell *sh, size_t argc, char *argv[]) +-{ +-#if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP) ++#elif defined(CONFIG_WOLFIP) ++ ARG_UNUSED(argc); ++ ARG_UNUSED(argv); ++ ++ if (wolfip_udp_state.fd < 0) { ++ PR_WARNING("Not bound\n"); ++ return -EINVAL; ++ } ++ ++ close(wolfip_udp_state.fd); ++ wolfip_udp_state.fd = -1; ++ PR("Closed\n"); ++ ++ return 0; ++ ++#else + ARG_UNUSED(sh); + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + return -EOPNOTSUPP; +-#else ++#endif ++} ++ ++static int cmd_net_udp_send(const struct shell *sh, size_t argc, char *argv[]) ++{ ++#if defined(CONFIG_NET_UDP) && defined(CONFIG_NET_NATIVE_UDP) + char *host = NULL; + char *endptr = NULL; + uint16_t port; +@@ -281,6 +360,100 @@ release_ctx: + } + + return 0; ++ ++#elif defined(CONFIG_WOLFIP) ++ char *endptr = NULL; ++ uint16_t port; ++ struct sockaddr_in dest; ++ ssize_t n; ++ ++ ARG_UNUSED(sh); ++ ++ if (argc < 4) { ++ PR_WARNING("Not enough arguments given for udp send command\n"); ++ return -EINVAL; ++ } ++ ++ if (wolfip_udp_state.fd < 0) { ++ PR_WARNING("Not bound; run 'net udp bind' first\n"); ++ return -EINVAL; ++ } ++ ++ port = (uint16_t)strtol(argv[2], &endptr, 0); ++ if (endptr == argv[2]) { ++ PR_WARNING("Invalid port number\n"); ++ return -EINVAL; ++ } ++ ++ memset(&dest, 0, sizeof(dest)); ++ dest.sin_family = AF_INET; ++ dest.sin_port = htons(port); ++ ++ if (net_addr_pton(AF_INET, argv[1], &dest.sin_addr) < 0) { ++ PR_WARNING("Cannot parse address \"%s\"\n", argv[1]); ++ return -EINVAL; ++ } ++ ++ n = sendto(wolfip_udp_state.fd, argv[3], strlen(argv[3]), 0, ++ (struct sockaddr *)&dest, sizeof(dest)); ++ if (n < 0) { ++ PR_WARNING("sendto() failed (errno %d)\n", errno); ++ return -errno; ++ } ++ ++ PR("Sent %zd bytes to %s:%u\n", n, argv[1], (unsigned int)port); ++ ++ return 0; ++ ++#else ++ ARG_UNUSED(sh); ++ ARG_UNUSED(argc); ++ ARG_UNUSED(argv); ++ ++ return -EOPNOTSUPP; ++#endif ++} ++ ++static int cmd_net_udp_recv(const struct shell *sh, size_t argc, char *argv[]) ++{ ++#if defined(CONFIG_WOLFIP) ++ char buf[256]; ++ struct sockaddr_in from; ++ socklen_t fromlen = sizeof(from); ++ ssize_t n; ++ char from_str[NET_IPV4_ADDR_LEN]; ++ ++ ARG_UNUSED(sh); ++ ARG_UNUSED(argc); ++ ARG_UNUSED(argv); ++ ++ if (wolfip_udp_state.fd < 0) { ++ PR_WARNING("Not bound\n"); ++ return -EINVAL; ++ } ++ ++ n = recvfrom(wolfip_udp_state.fd, buf, sizeof(buf) - 1, MSG_DONTWAIT, ++ (struct sockaddr *)&from, &fromlen); ++ if (n > 0) { ++ buf[n] = '\0'; ++ net_addr_ntop(AF_INET, &from.sin_addr, from_str, sizeof(from_str)); ++ PR("%zd bytes from %s:%u: %s\n", n, from_str, ++ (unsigned int)ntohs(from.sin_port), buf); ++ } else if (n < 0 && errno == EAGAIN) { ++ PR("No datagram pending\n"); ++ } else { ++ PR_WARNING("recvfrom failed: %d\n", errno); ++ return -errno; ++ } ++ ++ return 0; ++ ++#else ++ ARG_UNUSED(sh); ++ ARG_UNUSED(argc); ++ ARG_UNUSED(argv); ++ ++ return -EOPNOTSUPP; + #endif + } + +@@ -293,6 +466,7 @@ static int cmd_net_udp(const struct shell *sh, size_t argc, char *argv[]) + return 0; + } + ++#if defined(CONFIG_NET_UDP) || defined(CONFIG_WOLFIP) + SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_udp, + SHELL_CMD(bind, NULL, + SHELL_HELP("Binds to UDP local port", " "), +@@ -304,9 +478,15 @@ SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_udp, + SHELL_HELP("Sends UDP packet to a network host", + " "), + cmd_net_udp_send), ++#if defined(CONFIG_WOLFIP) ++ SHELL_CMD(recv, NULL, ++ SHELL_HELP("Receives a pending UDP datagram (non-blocking)", ""), ++ cmd_net_udp_recv), ++#endif + SHELL_SUBCMD_SET_END + ); + + SHELL_SUBCMD_ADD((net), udp, &net_cmd_udp, + "Send/recv UDP packet", + cmd_net_udp, 1, 0); ++#endif /* CONFIG_NET_UDP || CONFIG_WOLFIP */ diff --git a/port/zephyr/patches/0004-sample-wolfip-tap-echo.patch b/port/zephyr/patches/0004-sample-wolfip-tap-echo.patch new file mode 100644 index 0000000..f780d75 --- /dev/null +++ b/port/zephyr/patches/0004-sample-wolfip-tap-echo.patch @@ -0,0 +1,393 @@ +Subject: [PATCH 4/4] sample: wolfip_tap_echo (native_sim + TAP) + +TCP + UDP echo server sample bound to 192.0.2.1:4242, runnable under +native_sim against a TAP interface. Demonstrates wolfip_attach_iface + +wolfip_ctrl_set_addr boot wiring. Depends on patches 0001 + 0002. + +--- + +diff --git a/samples/net/sockets/wolfip_tap_echo/CMakeLists.txt b/samples/net/sockets/wolfip_tap_echo/CMakeLists.txt +new file mode 100644 +index 00000000..11f7d664 +--- /dev/null ++++ b/samples/net/sockets/wolfip_tap_echo/CMakeLists.txt +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: Apache-2.0 ++ ++cmake_minimum_required(VERSION 3.20.0) ++find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) ++project(wolfip_tap_echo) ++ ++target_sources(app PRIVATE src/main.c) +diff --git a/samples/net/sockets/wolfip_tap_echo/README.rst b/samples/net/sockets/wolfip_tap_echo/README.rst +new file mode 100644 +index 00000000..2e755e50 +--- /dev/null ++++ b/samples/net/sockets/wolfip_tap_echo/README.rst +@@ -0,0 +1,60 @@ ++wolfIP TAP Echo Smoke Test ++########################## ++ ++Overview ++******** ++ ++This sample runs on ``native_sim`` with the native TAP Ethernet driver and ++opts the default Zephyr network interface into the ``wolfIP`` adapter. It then ++opens normal IPv4 TCP and UDP echo sockets through the standard Zephyr socket ++API. ++ ++The purpose is to verify the Zephyr-to-``wolfIP`` integration seam end to end: ++ ++* TAP driver RX enters Zephyr ++* the ``wolfIP`` ingress hook consumes IPv4 traffic on the selected interface ++* application sockets still use the regular Zephyr/POSIX IPv4 API ++* ``wolfIP`` TX exits through the unchanged Zephyr TAP driver path ++ ++Host setup ++********** ++ ++Bring the host-side TAP interface up and assign the peer IPv4 address: ++ ++.. code-block:: console ++ ++ sudo ip link set dev zeth up ++ sudo ip addr add 192.0.2.2/24 dev zeth ++ ++Build and run ++************* ++ ++.. zephyr-app-commands:: ++ :zephyr-app: samples/net/sockets/wolfip_tap_echo ++ :host-os: unix ++ :board: native_sim ++ :goals: build run ++ :compact: ++ ++Expected boot log includes: ++ ++* ``wolfIP enabled on iface`` ++* ``TCP echo listening on 192.0.2.1:4242`` ++* ``UDP echo listening on 192.0.2.1:4242`` ++ ++Manual smoke test ++***************** ++ ++TCP: ++ ++.. code-block:: console ++ ++ printf 'wolfip-tcp\n' | nc 192.0.2.1 4242 ++ ++UDP: ++ ++.. code-block:: console ++ ++ printf 'wolfip-udp\n' | nc -u -w 1 192.0.2.1 4242 ++ ++Each command should return the same payload. +diff --git a/samples/net/sockets/wolfip_tap_echo/boards/native_sim.conf b/samples/net/sockets/wolfip_tap_echo/boards/native_sim.conf +new file mode 100644 +index 00000000..fe5796a3 +--- /dev/null ++++ b/samples/net/sockets/wolfip_tap_echo/boards/native_sim.conf +@@ -0,0 +1,3 @@ ++CONFIG_ETH_NATIVE_TAP_RANDOM_MAC=n ++CONFIG_ETH_NATIVE_TAP_MAC_ADDR="02:00:5e:00:53:61" ++CONFIG_ETH_NATIVE_TAP_DRV_NAME="zeth" +diff --git a/samples/net/sockets/wolfip_tap_echo/prj.conf b/samples/net/sockets/wolfip_tap_echo/prj.conf +new file mode 100644 +index 00000000..bf8bf3e3 +--- /dev/null ++++ b/samples/net/sockets/wolfip_tap_echo/prj.conf +@@ -0,0 +1,38 @@ ++# Networking core ++CONFIG_NETWORKING=y ++CONFIG_NET_SOCKETS=y ++CONFIG_POSIX_API=y ++ ++# Zephyr native IPv4/TCP/UDP are off — wolfIP owns the L3+ stack. ++CONFIG_NET_IPV6=n ++ ++# L2 stack ++CONFIG_NET_L2_ETHERNET=y ++CONFIG_NET_L2_WOLFIP=y ++CONFIG_ETH_NATIVE_TAP=y ++ ++# wolfIP ++CONFIG_WOLFIP=y ++CONFIG_WOLFIP_DHCPV4=n ++CONFIG_WOLFIP_MAX_INTERFACES=1 ++ ++# Application bits ++CONFIG_LOG=y ++CONFIG_PRINTK=y ++CONFIG_MAIN_STACK_SIZE=4096 ++CONFIG_HEAP_MEM_POOL_SIZE=32768 ++CONFIG_ENTROPY_GENERATOR=y ++CONFIG_TEST_RANDOM_GENERATOR=y ++ ++# Net packet buffers (still used for raw L2 frames) ++CONFIG_NET_PKT_RX_COUNT=24 ++CONFIG_NET_PKT_TX_COUNT=24 ++CONFIG_NET_BUF_RX_COUNT=96 ++CONFIG_NET_BUF_TX_COUNT=96 ++ ++# Sockets ++CONFIG_ZVFS_OPEN_MAX=32 ++ ++# Shell (for manual inspection / debugging) ++CONFIG_SHELL=y ++CONFIG_NET_SHELL=y +diff --git a/samples/net/sockets/wolfip_tap_echo/sample.yaml b/samples/net/sockets/wolfip_tap_echo/sample.yaml +new file mode 100644 +index 00000000..1f3b5f50 +--- /dev/null ++++ b/samples/net/sockets/wolfip_tap_echo/sample.yaml +@@ -0,0 +1,11 @@ ++sample: ++ name: wolfIP TAP echo smoke test ++tests: ++ sample.net.sockets.wolfip_tap_echo: ++ platform_allow: ++ - native_sim ++ - native_sim/native/64 ++ tags: ++ - net ++ - socket ++ - wolfip +diff --git a/samples/net/sockets/wolfip_tap_echo/src/main.c b/samples/net/sockets/wolfip_tap_echo/src/main.c +new file mode 100644 +index 00000000..874c4e4e +--- /dev/null ++++ b/samples/net/sockets/wolfip_tap_echo/src/main.c +@@ -0,0 +1,230 @@ ++/* ++ * Copyright (c) 2026 ++ * ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++LOG_MODULE_REGISTER(wolfip_tap_echo, LOG_LEVEL_INF); ++ ++#define ECHO_PORT 4242 ++#define BUF_SIZE 512 ++#define STACK_SIZE 2048 ++#define THREAD_PRIO 7 ++ ++#define SAMPLE_LOCAL_IPV4 "192.0.2.1" ++#define SAMPLE_LOCAL_NETMASK "255.255.255.0" ++#define SAMPLE_LOCAL_GW "192.0.2.2" ++ ++K_THREAD_STACK_DEFINE(tcp_stack, STACK_SIZE); ++K_THREAD_STACK_DEFINE(udp_stack, STACK_SIZE); ++static struct k_thread tcp_thread; ++static struct k_thread udp_thread; ++ ++static void sockaddr_to_str(const struct sockaddr_in *addr, char *buf, size_t len) ++{ ++ (void)snprintk(buf, len, "%u.%u.%u.%u:%u", ++ addr->sin_addr.s4_addr[0], addr->sin_addr.s4_addr[1], ++ addr->sin_addr.s4_addr[2], addr->sin_addr.s4_addr[3], ++ ntohs(addr->sin_port)); ++} ++ ++static void setup_bind_addr(struct sockaddr_in *addr) ++{ ++ memset(addr, 0, sizeof(*addr)); ++ addr->sin_family = AF_INET; ++ addr->sin_port = htons(ECHO_PORT); ++ net_addr_pton(AF_INET, SAMPLE_LOCAL_IPV4, &addr->sin_addr); ++} ++ ++static void tcp_server(void *arg1, void *arg2, void *arg3) ++{ ++ struct sockaddr_in bind_addr; ++ int listen_fd; ++ ++ ARG_UNUSED(arg1); ++ ARG_UNUSED(arg2); ++ ARG_UNUSED(arg3); ++ ++ setup_bind_addr(&bind_addr); ++ ++ listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ++ if (listen_fd < 0) { ++ LOG_ERR("TCP socket failed: %d", errno); ++ return; ++ } ++ ++ if (bind(listen_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { ++ LOG_ERR("TCP bind failed: %d", errno); ++ close(listen_fd); ++ return; ++ } ++ ++ if (listen(listen_fd, 1) < 0) { ++ LOG_ERR("TCP listen failed: %d", errno); ++ close(listen_fd); ++ return; ++ } ++ ++ LOG_INF("TCP echo listening on %s:%d", SAMPLE_LOCAL_IPV4, ECHO_PORT); ++ ++ while (true) { ++ struct sockaddr_in peer; ++ socklen_t peer_len = sizeof(peer); ++ char peer_buf[32]; ++ int client_fd; ++ ++ client_fd = accept(listen_fd, (struct sockaddr *)&peer, &peer_len); ++ if (client_fd < 0) { ++ LOG_ERR("TCP accept failed: %d", errno); ++ k_sleep(K_MSEC(100)); ++ continue; ++ } ++ ++ sockaddr_to_str(&peer, peer_buf, sizeof(peer_buf)); ++ LOG_INF("TCP client %s connected", peer_buf); ++ ++ while (true) { ++ char buf[BUF_SIZE]; ++ ssize_t received; ++ ++ received = recv(client_fd, buf, sizeof(buf), 0); ++ if (received == 0) { ++ LOG_INF("TCP client %s closed", peer_buf); ++ break; ++ } ++ ++ if (received < 0) { ++ LOG_ERR("TCP recv failed: %d", errno); ++ break; ++ } ++ ++ if (send(client_fd, buf, received, 0) < 0) { ++ LOG_ERR("TCP send failed: %d", errno); ++ break; ++ } ++ } ++ ++ close(client_fd); ++ } ++} ++ ++static void udp_server(void *arg1, void *arg2, void *arg3) ++{ ++ struct sockaddr_in bind_addr; ++ int sock_fd; ++ ++ ARG_UNUSED(arg1); ++ ARG_UNUSED(arg2); ++ ARG_UNUSED(arg3); ++ ++ setup_bind_addr(&bind_addr); ++ ++ sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ++ if (sock_fd < 0) { ++ LOG_ERR("UDP socket failed: %d", errno); ++ return; ++ } ++ ++ if (bind(sock_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { ++ LOG_ERR("UDP bind failed: %d", errno); ++ close(sock_fd); ++ return; ++ } ++ ++ LOG_INF("UDP echo listening on %s:%d", SAMPLE_LOCAL_IPV4, ECHO_PORT); ++ ++ while (true) { ++ struct sockaddr_in peer; ++ socklen_t peer_len = sizeof(peer); ++ char buf[BUF_SIZE]; ++ ssize_t received; ++ ++ received = recvfrom(sock_fd, buf, sizeof(buf), 0, ++ (struct sockaddr *)&peer, &peer_len); ++ if (received < 0) { ++ LOG_ERR("UDP recvfrom failed: %d", errno); ++ k_sleep(K_MSEC(100)); ++ continue; ++ } ++ ++ if (sendto(sock_fd, buf, received, 0, ++ (struct sockaddr *)&peer, peer_len) < 0) { ++ LOG_ERR("UDP sendto failed: %d", errno); ++ } ++ } ++} ++ ++int main(void) ++{ ++ struct net_if *iface; ++ int ret; ++ ++ iface = net_if_get_default(); ++ if (iface == NULL) { ++ LOG_ERR("No default network interface"); ++ return 0; ++ } ++ ++ ret = wolfip_enable_iface(iface); ++ if (ret < 0) { ++ LOG_ERR("wolfIP enable failed: %d", ret); ++ return 0; ++ } ++ ++ LOG_INF("wolfIP enabled on iface %d", net_if_get_by_iface(iface)); ++ ++ unsigned int if_idx; ++ ++ ret = wolfip_zephyr_get_if_idx(iface, &if_idx); ++ if (ret < 0) { ++ LOG_ERR("wolfIP: cannot map iface to if_idx: %d", ret); ++ return 0; ++ } ++ ++ struct in_addr ip_in, mask_in, gw_in; ++ ip4 ip_w, mask_w, gw_w; ++ ++ net_addr_pton(AF_INET, SAMPLE_LOCAL_IPV4, &ip_in); ++ net_addr_pton(AF_INET, SAMPLE_LOCAL_NETMASK, &mask_in); ++ net_addr_pton(AF_INET, SAMPLE_LOCAL_GW, &gw_in); ++ ip_w = sys_be32_to_cpu(ip_in.s_addr); ++ mask_w = sys_be32_to_cpu(mask_in.s_addr); ++ gw_w = sys_be32_to_cpu(gw_in.s_addr); ++ ++ ret = wolfip_ctrl_set_addr(if_idx, ip_w, mask_w, gw_w); ++ if (ret < 0) { ++ LOG_ERR("wolfIP: set_addr failed: %d", ret); ++ return 0; ++ } ++ ++ LOG_INF("wolfIP: configured %s/%s gw %s", ++ SAMPLE_LOCAL_IPV4, SAMPLE_LOCAL_NETMASK, SAMPLE_LOCAL_GW); ++ ++ k_thread_create(&tcp_thread, tcp_stack, K_THREAD_STACK_SIZEOF(tcp_stack), ++ tcp_server, NULL, NULL, NULL, THREAD_PRIO, 0, K_NO_WAIT); ++ k_thread_name_set(&tcp_thread, "wolfip_tcp"); ++ ++ k_thread_create(&udp_thread, udp_stack, K_THREAD_STACK_SIZEOF(udp_stack), ++ udp_server, NULL, NULL, NULL, THREAD_PRIO, 0, K_NO_WAIT); ++ k_thread_name_set(&udp_thread, "wolfip_udp"); ++ ++ while (true) { ++ k_sleep(K_SECONDS(1)); ++ } ++ ++ return 0; ++} diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index cc95d33..bb05234 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -132,6 +132,13 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_segment_acceptable_zero_window_and_overlap_cases); tcase_add_test(tc_utils, test_tcp_segment_acceptable_counts_syn_in_segment_length); tcase_add_test(tc_utils, test_wolfip_ipconfig_ex_per_interface); +#if WOLFIP_ENABLE_FORWARDING + tcase_add_test(tc_utils, test_wolfip_route_table_longest_prefix_match); + tcase_add_test(tc_utils, test_wolfip_route_table_default_route_delete_and_fallback); + tcase_add_test(tc_utils, test_wolfip_route_table_update_replaces_gateway_without_duplication); + tcase_add_test(tc_utils, test_wolfip_route_table_connected_subnet_beats_broader_static_route); +#endif + tcase_add_test(tc_utils, test_wolfip_dns_server_get_returns_value_and_validates_args); tcase_add_test(tc_utils, test_wolfip_poll_executes_timers_and_callbacks); tcase_add_test(tc_utils, test_wolfip_poll_drains_all_expired_timers_in_one_pass); tcase_add_test(tc_utils, test_wolfip_poll_preserves_tcp_events_raised_during_callback); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 9cb38c5..dec82de 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -3534,6 +3534,121 @@ START_TEST(test_wolfip_ipconfig_ex_per_interface) } END_TEST +#if WOLFIP_ENABLE_FORWARDING +START_TEST(test_wolfip_route_table_longest_prefix_match) +{ + struct wolfIP s; + unsigned int if_idx = 0U; + ip4 nexthop = 0U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set_ex(&s, TEST_PRIMARY_IF, 0x0A000001U, 0xFF000000U, 0x0A0000FEU); + wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0xC0A80101U, 0xFFFFFF00U, 0xC0A801FEU); + + ck_assert_int_eq(wolfIP_route_add(&s, TEST_PRIMARY_IF, 0x0A000000U, 8U, 0x0A0000FEU), 0); + ck_assert_int_eq(wolfIP_route_add(&s, TEST_SECOND_IF, 0x0A010200U, 24U, 0xC0A801FEU), 0); + ck_assert_uint_eq(wolfIP_route_count(&s), 2U); + + ck_assert_int_eq(wolfIP_route_lookup(&s, 0x0A010203U, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_SECOND_IF); + ck_assert_uint_eq(nexthop, 0xC0A801FEU); +} +END_TEST + +START_TEST(test_wolfip_route_table_default_route_delete_and_fallback) +{ + struct wolfIP s; + unsigned int if_idx = 0U; + ip4 nexthop = 0U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set_ex(&s, TEST_PRIMARY_IF, 0x0A000001U, 0xFFFFFF00U, 0x0A0000FEU); + wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0xC0A80101U, 0xFFFFFF00U, 0xC0A801FEU); + + ck_assert_int_eq(wolfIP_route_add(&s, TEST_SECOND_IF, 0U, 0U, 0xC0A801FEU), 0); + ck_assert_int_eq(wolfIP_route_lookup(&s, 0x08080808U, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_SECOND_IF); + ck_assert_uint_eq(nexthop, 0xC0A801FEU); + + ck_assert_int_eq(wolfIP_route_delete(&s, TEST_SECOND_IF, 0U, 0U), 0); + ck_assert_uint_eq(wolfIP_route_count(&s), 0U); + ck_assert_int_eq(wolfIP_route_lookup(&s, 0x08080808U, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_PRIMARY_IF); + ck_assert_uint_eq(nexthop, 0x0A0000FEU); +} +END_TEST + +START_TEST(test_wolfip_route_table_update_replaces_gateway_without_duplication) +{ + struct wolfIP s; + struct wolfIP_route_info info; + unsigned int if_idx = 0U; + ip4 nexthop = 0U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set_ex(&s, TEST_PRIMARY_IF, 0x0A000001U, 0xFFFFFF00U, 0x0A0000FEU); + + ck_assert_int_eq(wolfIP_route_add(&s, TEST_PRIMARY_IF, 0xAC100000U, 12U, 0x0A0000FEU), 0); + ck_assert_int_eq(wolfIP_route_add(&s, TEST_PRIMARY_IF, 0xAC100000U, 12U, 0x0A0000FDU), 0); + ck_assert_uint_eq(wolfIP_route_count(&s), 1U); + ck_assert_int_eq(wolfIP_route_get(&s, 0U, &info), 0); + ck_assert_uint_eq(info.gateway, 0x0A0000FDU); + + ck_assert_int_eq(wolfIP_route_lookup(&s, 0xAC100123U, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_PRIMARY_IF); + ck_assert_uint_eq(nexthop, 0x0A0000FDU); +} +END_TEST + +START_TEST(test_wolfip_route_table_connected_subnet_beats_broader_static_route) +{ + struct wolfIP s; + unsigned int if_idx = 0U; + ip4 nexthop = 0U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set_ex(&s, TEST_PRIMARY_IF, 0xC0A80101U, 0xFFFFFF00U, 0xC0A801FEU); + wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0x0A000001U, 0xFF000000U, 0x0A0000FEU); + + ck_assert_int_eq(wolfIP_route_add(&s, TEST_SECOND_IF, 0xC0A80000U, 16U, 0x0A0000FEU), 0); + ck_assert_int_eq(wolfIP_route_lookup(&s, 0xC0A80163U, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_PRIMARY_IF); + ck_assert_uint_eq(nexthop, 0xC0A80163U); +} +END_TEST +#endif + +START_TEST(test_wolfip_dns_server_get_returns_value_and_validates_args) +{ + struct wolfIP s; + ip4 dns = 0xDEADBEEFU; + + wolfIP_init(&s); + + /* NULL stack -> EINVAL, output is untouched. */ + ck_assert_int_eq(wolfIP_dns_server_get(NULL, &dns), -WOLFIP_EINVAL); + ck_assert_uint_eq(dns, 0xDEADBEEFU); + + /* NULL out pointer -> EINVAL. */ + ck_assert_int_eq(wolfIP_dns_server_get(&s, NULL), -WOLFIP_EINVAL); + + /* Fresh stack: DNS server is zero by default. */ + dns = 0xDEADBEEFU; + ck_assert_int_eq(wolfIP_dns_server_get(&s, &dns), 0); + ck_assert_uint_eq(dns, 0U); + + /* After a value has been stashed (DHCP would do this), the getter + * faithfully returns it. */ + s.dns_server = 0x08080808U; + ck_assert_int_eq(wolfIP_dns_server_get(&s, &dns), 0); + ck_assert_uint_eq(dns, 0x08080808U); +} +END_TEST + START_TEST(test_wolfip_recv_ex_multi_interface_arp_reply) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index c0b458f..714283e 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -35,6 +35,11 @@ #ifndef LINK_MTU_MIN #define LINK_MTU_MIN 64U #endif + +#ifndef WOLFIP_MAX_ROUTES +#define WOLFIP_MAX_ROUTES 16U +#endif + #define WOLFIP_LOOPBACK_IP 0x7F000001U #define WOLFIP_LOOPBACK_MASK 0xFF000000U #if WOLFIP_ENABLE_LOOPBACK @@ -1236,6 +1241,12 @@ static void tcp_fin_wait_2_timeout_stop(struct tsocket *t); static int tcp_ctrl_state_needs_rto(const struct tsocket *t); static int tcp_has_pending_unsent_payload(struct tsocket *t); static inline struct wolfIP_ll_dev *wolfIP_ll_at(struct wolfIP *s, unsigned int if_idx); +#if WOLFIP_ENABLE_FORWARDING +static int wolfIP_route_match_prefix(ip4 addr, ip4 prefix, uint8_t prefix_len); +static uint32_t wolfIP_prefix_mask(uint8_t prefix_len); +#endif +static int wolfIP_route_lookup_internal(struct wolfIP *s, ip4 dest, + unsigned int *if_idx, ip4 *nexthop); static inline unsigned int wolfIP_socket_if_idx(const struct tsocket *t); #ifdef WOLFIP_ESP static int esp_send(struct wolfIP_ll_dev *ll_dev, @@ -1306,6 +1317,15 @@ struct wolfIP_timer { void (*cb)(void *arg); }; +struct wolfIP_route_entry { + ip4 prefix; + ip4 gateway; + uint32_t order; + uint8_t prefix_len; + uint8_t if_idx; + uint8_t used; +}; + /* Timer binary heap */ struct timers_binheap { struct wolfIP_timer timers[MAX_TIMERS]; @@ -1354,6 +1374,10 @@ struct wolfIP { #endif uint16_t ipcounter; uint64_t last_tick; +#if WOLFIP_ENABLE_FORWARDING + uint32_t route_generation; + struct wolfIP_route_entry routes[WOLFIP_MAX_ROUTES]; +#endif #ifdef ETHERNET struct wolfIP_arp { uint64_t last_arp[WOLFIP_MAX_INTERFACES]; @@ -1613,6 +1637,27 @@ static inline struct ipconf *wolfIP_primary_ipconf(struct wolfIP *s) return wolfIP_ipconf_at(s, WOLFIP_PRIMARY_IF_IDX); } +#if WOLFIP_ENABLE_FORWARDING +static uint32_t wolfIP_prefix_mask(uint8_t prefix_len) +{ + if (prefix_len == 0U) + return 0U; + if (prefix_len >= 32U) + return 0xFFFFFFFFU; + return 0xFFFFFFFFU << (32U - prefix_len); +} + +static int wolfIP_route_match_prefix(ip4 addr, ip4 prefix, uint8_t prefix_len) +{ + uint32_t mask = wolfIP_prefix_mask(prefix_len); + + if (prefix_len == 0U) + return 1; + + return ((addr & mask) == (prefix & mask)) ? 1 : 0; +} +#endif /* WOLFIP_ENABLE_FORWARDING */ + static inline uint16_t ipcounter_next(struct wolfIP *s) { uint16_t id = s->ipcounter; @@ -1771,6 +1816,138 @@ static int wolfIP_forward_interface(struct wolfIP *s, unsigned int in_if, ip4 de } #endif +static int wolfIP_route_lookup_internal(struct wolfIP *s, ip4 dest, + unsigned int *if_idx, ip4 *nexthop) +{ + unsigned int best_if = 0U; + ip4 best_nexthop = dest; + uint8_t best_prefix = 0U; +#if WOLFIP_ENABLE_FORWARDING + uint32_t best_order = UINT32_MAX; +#endif + int found = 0; + unsigned int i; + + if (!s || s->if_count == 0U) + return -WOLFIP_EINVAL; + + if (WOLFIP_PRIMARY_IF_IDX < s->if_count) + best_if = WOLFIP_PRIMARY_IF_IDX; + + if (dest == IPADDR_ANY || wolfIP_ip_is_broadcast(s, dest)) { + if (if_idx) + *if_idx = best_if; + if (nexthop) + *nexthop = dest; + return 0; + } + + /* Score connected (on-link) subnets and static routes together under + * one longest-prefix-match. The connected subnet must not short- + * circuit, otherwise a more-specific static route on a different + * iface (e.g. 10.1.2.0/24 -> if 2) can never override a less- + * specific connected subnet (e.g. 10.0.0.0/8 -> if 1). */ + for (i = 0; i < s->if_count; i++) { + const struct ipconf *conf = wolfIP_ipconf_at(s, i); + uint8_t prefix_len; + + if (!conf || conf->ip == IPADDR_ANY) + continue; + + /* Exact local-IP match always wins immediately (delivery to self). */ + if (conf->ip == dest) { + if (if_idx) + *if_idx = i; + if (nexthop) + *nexthop = dest; + return 0; + } + + if (!ip_is_local_conf(conf, dest)) + continue; + + prefix_len = (uint8_t)__builtin_popcount(conf->mask); + if (!found || prefix_len > best_prefix) { + best_if = i; + best_nexthop = dest; + best_prefix = prefix_len; +#if WOLFIP_ENABLE_FORWARDING + best_order = 0U; +#endif + found = 1; + } + } + + #if WOLFIP_ENABLE_FORWARDING + for (i = 0; i < WOLFIP_MAX_ROUTES; i++) { + const struct wolfIP_route_entry *route = &s->routes[i]; + + if (!route->used) + continue; + if (!wolfIP_route_match_prefix(dest, route->prefix, route->prefix_len)) + continue; + + if (!found || + route->prefix_len > best_prefix || + (route->prefix_len == best_prefix && route->order < best_order) || + (route->prefix_len == best_prefix && route->order == best_order && + route->if_idx < best_if)) { + best_if = route->if_idx; + best_nexthop = route->gateway != IPADDR_ANY ? route->gateway : dest; + best_prefix = route->prefix_len; + best_order = route->order; + found = 1; + } + } + #endif + + if (!found) { + int has_gw_fallback = 0; + int has_non_loop = 0; + unsigned int gw_fallback = best_if; + unsigned int first_non_loop = best_if; + + for (i = 0; i < s->if_count; i++) { + const struct ipconf *conf = wolfIP_ipconf_at(s, i); + + if (!conf || (conf->ip == IPADDR_ANY && conf->gw == IPADDR_ANY)) + continue; + if (!wolfIP_is_loopback_if(i) && !has_non_loop) { + first_non_loop = i; + has_non_loop = 1; + } + if (!wolfIP_is_loopback_if(i) && !has_gw_fallback && + conf->gw != IPADDR_ANY) { + gw_fallback = i; + has_gw_fallback = 1; + } + } + + if (has_gw_fallback) { + const struct ipconf *conf = wolfIP_ipconf_at(s, gw_fallback); + best_if = gw_fallback; + best_nexthop = (conf && conf->gw != IPADDR_ANY) ? conf->gw : dest; + } else if (has_non_loop) { + best_if = first_non_loop; + best_nexthop = dest; + } + } + + if (if_idx) + *if_idx = best_if; + if (nexthop) + *nexthop = best_nexthop; + return 0; +} + +static unsigned int wolfIP_route_for_ip(struct wolfIP *s, ip4 dest) +{ + unsigned int if_idx = 0U; + + (void)wolfIP_route_lookup_internal(s, dest, &if_idx, NULL); + return if_idx; +} + static inline ip4 wolfIP_select_nexthop(const struct ipconf *conf, ip4 dest) { if (dest == 0xFFFFFFFFU) @@ -1784,47 +1961,18 @@ static inline ip4 wolfIP_select_nexthop(const struct ipconf *conf, ip4 dest) return dest; } -static unsigned int wolfIP_route_for_ip(struct wolfIP *s, ip4 dest) +static ip4 wolfIP_select_nexthop_ex(struct wolfIP *s, unsigned int *if_idx, ip4 dest) { - unsigned int default_if = 0; - unsigned int gw_fallback = 0; - unsigned int first_non_loop = 0; - int has_gw_fallback = 0; - int has_non_loop = 0; - unsigned int i; - - if (!s || s->if_count == 0) - return 0; - - if (WOLFIP_PRIMARY_IF_IDX < s->if_count) - default_if = WOLFIP_PRIMARY_IF_IDX; + unsigned int resolved_if = if_idx ? *if_idx : 0U; + ip4 nexthop = dest; - if (dest == IPADDR_ANY || wolfIP_ip_is_broadcast(s, dest)) - return default_if; - - for (i = 0; i < s->if_count; i++) { - struct ipconf *conf = &s->ipconf[i]; - if (conf->ip == IPADDR_ANY && conf->gw == IPADDR_ANY) - continue; - if (ip_is_local_conf(conf, dest) || conf->ip == dest) { - return i; - } - if (!wolfIP_is_loopback_if(i) && !has_non_loop) { - first_non_loop = i; - has_non_loop = 1; - } - if (!wolfIP_is_loopback_if(i) && !has_gw_fallback && conf->gw != IPADDR_ANY) { - gw_fallback = i; - has_gw_fallback = 1; - } - } - if (has_gw_fallback) { - return gw_fallback; + if (wolfIP_route_lookup_internal(s, dest, &resolved_if, &nexthop) == 0) { + if (if_idx) + *if_idx = resolved_if; + return nexthop; } - if (has_non_loop) { - return first_non_loop; - } - return default_if; + + return dest; } static inline unsigned int wolfIP_socket_if_idx(const struct tsocket *t) @@ -1857,7 +2005,8 @@ static unsigned int raw_route_for_ip(struct wolfIP *s, struct rawsocket *rs, ip4 return i; } } - return wolfIP_route_for_ip(s, dest); + (void)wolfIP_route_lookup_internal(s, dest, &if_idx, NULL); + return if_idx; } #endif @@ -3024,12 +3173,9 @@ static int tcp_send_empty_immediate(struct tsocket *t, struct wolfIP_tcp_seg *tc return -1; memcpy(t->nexthop_mac, ll->mac, 6); } else if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { - struct ipconf *conf = wolfIP_ipconf_at(t->S, tx_if); ip4 nexthop; - if (!conf) - return -1; - nexthop = wolfIP_select_nexthop(conf, t->remote_ip); + nexthop = wolfIP_select_nexthop_ex(t->S, &tx_if, t->remote_ip); if (arp_lookup(t->S, tx_if, nexthop, t->nexthop_mac) < 0) { arp_request(t->S, tx_if, nexthop); @@ -3500,7 +3646,6 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) uint32_t frame_len; unsigned int tx_if; #ifdef ETHERNET - struct ipconf *conf; ip4 nexthop; #endif @@ -3549,8 +3694,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) tx_if = wolfIP_socket_if_idx(t); #ifdef ETHERNET - conf = wolfIP_ipconf_at(t->S, tx_if); - nexthop = wolfIP_select_nexthop(conf, t->remote_ip); + nexthop = wolfIP_select_nexthop_ex(t->S, &tx_if, t->remote_ip); if (wolfIP_is_loopback_if(tx_if)) { struct wolfIP_ll_dev *loop = wolfIP_ll_at(t->S, tx_if); if (loop) @@ -8206,6 +8350,15 @@ int wolfIP_arp_lookup_ex(struct wolfIP *s, unsigned int if_idx, ip4 ip, uint8_t #endif +int wolfIP_dns_server_get(struct wolfIP *s, ip4 *dns_server) +{ + if (!s || !dns_server) + return -WOLFIP_EINVAL; + + *dns_server = s->dns_server; + return 0; +} + /* Initialize the IP stack */ void wolfIP_init(struct wolfIP *s) { @@ -9235,8 +9388,7 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) continue; } else { #ifdef ETHERNET - struct ipconf *conf = wolfIP_ipconf_at(s, tx_if); - ip4 nexthop = wolfIP_select_nexthop(conf, ts->remote_ip); + ip4 nexthop = wolfIP_select_nexthop_ex(s, &tx_if, ts->remote_ip); if (wolfIP_is_loopback_if(tx_if)) { struct wolfIP_ll_dev *loop = wolfIP_ll_at(s, tx_if); if (loop) @@ -9365,8 +9517,7 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) unsigned int tx_if = wolfIP_socket_if_idx(t); int send_ret = 0; #ifdef ETHERNET - struct ipconf *conf = wolfIP_ipconf_at(s, tx_if); - ip4 nexthop = wolfIP_select_nexthop(conf, t->remote_ip); + ip4 nexthop = wolfIP_select_nexthop_ex(s, &tx_if, t->remote_ip); if (wolfIP_is_loopback_if(tx_if)) { struct wolfIP_ll_dev *loop = wolfIP_ll_at(s, tx_if); if (loop) @@ -9444,8 +9595,7 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) unsigned int tx_if = wolfIP_socket_if_idx(t); int send_ret = 0; #ifdef ETHERNET - struct ipconf *conf = wolfIP_ipconf_at(s, tx_if); - ip4 nexthop = wolfIP_select_nexthop(conf, t->remote_ip); + ip4 nexthop = wolfIP_select_nexthop_ex(s, &tx_if, t->remote_ip); if (wolfIP_is_loopback_if(tx_if)) { struct wolfIP_ll_dev *loop = wolfIP_ll_at(s, tx_if); if (loop) @@ -9509,9 +9659,6 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) ip4 dst_ip = ee32(ip->dst); unsigned int tx_if = r->if_idx; ip4 nexthop; -#ifdef ETHERNET - struct ipconf *conf; -#endif if (dst_ip == 0) { fifo_pop(&r->txbuf); desc = fifo_peek(&r->txbuf); @@ -9521,8 +9668,11 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) tx_if = raw_route_for_ip(s, r, dst_ip, r->dontroute); r->if_idx = (uint8_t)tx_if; #ifdef ETHERNET - conf = wolfIP_ipconf_at(s, tx_if); - nexthop = r->dontroute ? dst_ip : wolfIP_select_nexthop(conf, dst_ip); + nexthop = dst_ip; + if (!r->dontroute) { + nexthop = wolfIP_select_nexthop_ex(s, &tx_if, dst_ip); + r->if_idx = (uint8_t)tx_if; + } if (wolfIP_is_loopback_if(tx_if)) { struct wolfIP_ll_dev *loop = wolfIP_ll_at(s, tx_if); if (loop) @@ -9622,3 +9772,148 @@ void wolfIP_ipconfig_get_ex(struct wolfIP *s, unsigned int if_idx, ip4 *ip, if (gw) *gw = conf->gw; } + +#if WOLFIP_ENABLE_FORWARDING +unsigned int wolfIP_route_count(struct wolfIP *s) +{ + unsigned int i; + unsigned int count = 0U; + + if (!s) + return 0U; + +#if WOLFIP_ENABLE_FORWARDING + for (i = 0; i < WOLFIP_MAX_ROUTES; i++) { + if (s->routes[i].used) + count++; + } +#else + (void)i; +#endif + + return count; +} + +int wolfIP_route_get(struct wolfIP *s, unsigned int route_idx, + struct wolfIP_route_info *info) +{ +#if WOLFIP_ENABLE_FORWARDING + unsigned int i; + unsigned int seen = 0U; + + if (!s || !info) + return -WOLFIP_EINVAL; + + for (i = 0; i < WOLFIP_MAX_ROUTES; i++) { + if (!s->routes[i].used) + continue; + if (seen++ != route_idx) + continue; + info->prefix = s->routes[i].prefix; + info->gateway = s->routes[i].gateway; + info->prefix_len = s->routes[i].prefix_len; + info->if_idx = s->routes[i].if_idx; + return 0; + } + + return -WOLFIP_EINVAL; +#else + (void)s; + (void)route_idx; + (void)info; + return -WOLFIP_EINVAL; +#endif +} + +int wolfIP_route_add(struct wolfIP *s, unsigned int if_idx, ip4 prefix, + uint8_t prefix_len, ip4 gateway) +{ +#if WOLFIP_ENABLE_FORWARDING + unsigned int i; + struct wolfIP_route_entry *free_slot = NULL; + uint32_t mask; + + if (!s || if_idx >= s->if_count || prefix_len > 32U) + return -WOLFIP_EINVAL; + + mask = wolfIP_prefix_mask(prefix_len); + prefix &= mask; + + for (i = 0; i < WOLFIP_MAX_ROUTES; i++) { + struct wolfIP_route_entry *route = &s->routes[i]; + + if (!route->used) { + if (!free_slot) + free_slot = route; + continue; + } + + if (route->if_idx == if_idx && + route->prefix_len == prefix_len && + route->prefix == prefix) { + route->gateway = gateway; + return 0; + } + } + + if (!free_slot) + return -WOLFIP_ENOMEM; + + free_slot->used = 1U; + free_slot->if_idx = (uint8_t)if_idx; + free_slot->prefix_len = prefix_len; + free_slot->prefix = prefix; + free_slot->gateway = gateway; + free_slot->order = s->route_generation++; + return 0; +#else + (void)s; + (void)if_idx; + (void)prefix; + (void)prefix_len; + (void)gateway; + return -WOLFIP_EINVAL; +#endif +} + +int wolfIP_route_delete(struct wolfIP *s, unsigned int if_idx, ip4 prefix, + uint8_t prefix_len) +{ +#if WOLFIP_ENABLE_FORWARDING + unsigned int i; + uint32_t mask; + + if (!s || if_idx >= s->if_count || prefix_len > 32U) + return -WOLFIP_EINVAL; + + mask = wolfIP_prefix_mask(prefix_len); + prefix &= mask; + + for (i = 0; i < WOLFIP_MAX_ROUTES; i++) { + struct wolfIP_route_entry *route = &s->routes[i]; + + if (!route->used) + continue; + if (route->if_idx != if_idx || route->prefix_len != prefix_len || + route->prefix != prefix) + continue; + memset(route, 0, sizeof(*route)); + return 0; + } + + return -WOLFIP_EINVAL; +#else + (void)s; + (void)if_idx; + (void)prefix; + (void)prefix_len; + return -WOLFIP_EINVAL; +#endif +} + +int wolfIP_route_lookup(struct wolfIP *s, ip4 dest, unsigned int *if_idx, + ip4 *nexthop) +{ + return wolfIP_route_lookup_internal(s, dest, if_idx, nexthop); +} +#endif /* WOLFIP_ENABLE_FORWARDING */ diff --git a/wolfip.h b/wolfip.h index 03b24eb..46ecb8e 100644 --- a/wolfip.h +++ b/wolfip.h @@ -187,6 +187,13 @@ struct ipconf { ip4 gw; }; +struct wolfIP_route_info { + ip4 prefix; + ip4 gateway; + uint8_t prefix_len; + uint8_t if_idx; +}; + #ifdef IP_MULTICAST struct wolfIP_mreq_addr { uint32_t s_addr; @@ -258,9 +265,6 @@ struct wolfIP_ip_mreq { #define IPSTACK_SOCK_STREAM 1 #define IPSTACK_SOCK_DGRAM 2 #define IPSTACK_SOCK_RAW 3 -#ifndef AF_PACKET -#define AF_PACKET 17 -#endif struct wolfIP_sockaddr_in { @@ -271,13 +275,21 @@ struct wolfIP_sockaddr_in { struct wolfIP_sockaddr { uint16_t sa_family; }; typedef uint32_t socklen_t; +/* Pull in the system socket types when available, but only declare + * WOLFIP_HAVE_POSIX_TYPES once BOTH AND are + * confirmed present — wolfIP needs iovec/msghdr (from ) just + * as much as the socket types. If is missing we fall through + * to the local iovec/msghdr definitions below so the build still works. + */ #if defined(__has_include) #if __has_include() #include +#if __has_include() #include #define WOLFIP_HAVE_POSIX_TYPES 1 #endif #endif +#endif #ifndef WOLFIP_HAVE_POSIX_TYPES struct iovec { void *iov_base; size_t iov_len; }; @@ -369,6 +381,7 @@ int wolfIP_sock_can_write(struct wolfIP *s, int sockfd); int dhcp_client_init(struct wolfIP *s); int dhcp_bound(struct wolfIP *s); int dhcp_client_is_running(struct wolfIP *s); +int wolfIP_dns_server_get(struct wolfIP *s, ip4 *dns_server); /* DNS client */ @@ -396,6 +409,17 @@ int wolfIP_mtu_get(struct wolfIP *s, unsigned int if_idx, uint32_t *mtu); void wolfIP_ipconfig_set_ex(struct wolfIP *s, unsigned int if_idx, ip4 ip, ip4 mask, ip4 gw); void wolfIP_ipconfig_get_ex(struct wolfIP *s, unsigned int if_idx, ip4 *ip, ip4 *mask, ip4 *gw); int wolfIP_arp_lookup_ex(struct wolfIP *s, unsigned int if_idx, ip4 ip, uint8_t *mac); +#if WOLFIP_ENABLE_FORWARDING +int wolfIP_route_add(struct wolfIP *s, unsigned int if_idx, ip4 prefix, + uint8_t prefix_len, ip4 gateway); +int wolfIP_route_delete(struct wolfIP *s, unsigned int if_idx, ip4 prefix, + uint8_t prefix_len); +int wolfIP_route_lookup(struct wolfIP *s, ip4 dest, unsigned int *if_idx, + ip4 *nexthop); +int wolfIP_route_get(struct wolfIP *s, unsigned int route_idx, + struct wolfIP_route_info *info); +unsigned int wolfIP_route_count(struct wolfIP *s); +#endif /* WOLFIP_ENABLE_FORWARDING */ /* Callback flags */ #define CB_EVENT_READABLE 0x01 /* Accepted connection or data available */