Add selftests for ipxlat data plane behavior and control-plane setup. The tests build an isolated netns topology, configure ipxlat through YNL, and exercise core traffic classes (TCP, UDP, ICMP info/error, and fragment-related paths). This provides reproducible end-to-end coverage for the translation pipeline and basic regression protection for future changes.
Signed-off-by: Ralf Lici <[email protected]> --- tools/testing/selftests/net/ipxlat/.gitignore | 1 + tools/testing/selftests/net/ipxlat/Makefile | 25 ++ .../selftests/net/ipxlat/ipxlat_data.sh | 70 +++++ .../selftests/net/ipxlat/ipxlat_frag.sh | 70 +++++ .../selftests/net/ipxlat/ipxlat_icmp_err.sh | 54 ++++ .../selftests/net/ipxlat/ipxlat_lib.sh | 273 ++++++++++++++++++ .../net/ipxlat/ipxlat_udp4_zero_csum_send.c | 119 ++++++++ 7 files changed, 612 insertions(+) create mode 100644 tools/testing/selftests/net/ipxlat/.gitignore create mode 100644 tools/testing/selftests/net/ipxlat/Makefile create mode 100755 tools/testing/selftests/net/ipxlat/ipxlat_data.sh create mode 100755 tools/testing/selftests/net/ipxlat/ipxlat_frag.sh create mode 100755 tools/testing/selftests/net/ipxlat/ipxlat_icmp_err.sh create mode 100644 tools/testing/selftests/net/ipxlat/ipxlat_lib.sh create mode 100644 tools/testing/selftests/net/ipxlat/ipxlat_udp4_zero_csum_send.c diff --git a/tools/testing/selftests/net/ipxlat/.gitignore b/tools/testing/selftests/net/ipxlat/.gitignore new file mode 100644 index 000000000000..43bd01d8a84b --- /dev/null +++ b/tools/testing/selftests/net/ipxlat/.gitignore @@ -0,0 +1 @@ +ipxlat_udp4_zero_csum_send diff --git a/tools/testing/selftests/net/ipxlat/Makefile b/tools/testing/selftests/net/ipxlat/Makefile new file mode 100644 index 000000000000..cca588945e48 --- /dev/null +++ b/tools/testing/selftests/net/ipxlat/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +# IPXLAT - Stateless IP/ICMP Translation (SIIT) virtual device driver +# +# Copyright (C) 2026- Mandelbit SRL +# Copyright (C) 2026- Daniel Gröber <[email protected]> +# +# Author: Antonio Quartulli <[email protected]> +# Daniel Gröber <[email protected]> +# Ralf Lici <[email protected]> + +TEST_PROGS := \ + ipxlat_data.sh \ + ipxlat_frag.sh \ + ipxlat_icmp_err.sh \ +# end of TEST_PROGS + +TEST_FILES := \ + ipxlat_lib.sh \ +# end of TEST_FILES + +TEST_GEN_FILES := \ + ipxlat_udp4_zero_csum_send \ +# end of TEST_GEN_FILES + +include ../../lib.mk diff --git a/tools/testing/selftests/net/ipxlat/ipxlat_data.sh b/tools/testing/selftests/net/ipxlat/ipxlat_data.sh new file mode 100755 index 000000000000..101e0a65f0a9 --- /dev/null +++ b/tools/testing/selftests/net/ipxlat/ipxlat_data.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# IPXLAT - Stateless IP/ICMP Translation (SIIT) virtual device driver +# +# Copyright (C) 2026- Mandelbit SRL +# Copyright (C) 2026- Daniel Gröber <[email protected]> +# +# Author: Antonio Quartulli <[email protected]> +# Daniel Gröber <[email protected]> +# Ralf Lici <[email protected]> + +set -o pipefail + +SCRIPT_DIR=$(dirname "$(readlink -f "$0")") +source "$SCRIPT_DIR/ipxlat_lib.sh" + +trap ipxlat_cleanup EXIT + +ipxlat_setup_env + +# Send ICMP Echo and verify we receive a reply back + +RET=0 +ip netns exec "$NS4" ping -c 2 -W 2 "$IPXLAT_V4_REMOTE" >/dev/null 2>&1 +check_err $? "ping 4->6 failed" +log_test "icmp-info 4->6" + +RET=0 +ip netns exec "$NS6" ping -6 -c 2 -W 2 -I "$IPXLAT_V6_NS6_SRC" \ + "$IPXLAT_V6_NS4" >/dev/null 2>&1 +check_err $? "ping 6->4 failed" +log_test "icmp-info 6->4" + +# Run a TCP data transfer over the translator path + +RET=0 +ipxlat_run_iperf "$NS6" "$NS4" "$IPXLAT_V4_REMOTE" 5201 -n 256K +check_err $? "tcp 4->6 failed" +log_test "tcp 4->6" + +RET=0 +ipxlat_run_iperf "$NS4" "$NS6" "$IPXLAT_V6_NS4" 5201 \ + -B "$IPXLAT_V6_NS6_SRC" -n 256K +check_err $? "tcp 6->4 failed" +log_test "tcp 6->4" + +# Run UDP traffic to verify UDP translation and delivery + +RET=0 +ipxlat_run_iperf "$NS6" "$NS4" "$IPXLAT_V4_REMOTE" 5202 -u -b 5M -t 1 +check_err $? "udp 4->6 failed" +log_test "udp 4->6" + +RET=0 +ipxlat_run_iperf "$NS4" "$NS6" "$IPXLAT_V6_NS4" 5202 \ + -B "$IPXLAT_V6_NS6_SRC" -u -b 5M -t 1 +check_err $? "udp 6->4 failed" +log_test "udp 6->4" + +# Send one IPv4 UDP packet with checksum=0 and verify 4->6 translation. + +RET=0 +ipxlat_capture_pkts "$NS6" \ + "ip6 and udp and dst host $IPXLAT_V6_REMOTE and dst port 5555" 1 3 \ + ip netns exec "$NS4" "$SCRIPT_DIR/ipxlat_udp4_zero_csum_send" \ + "$IPXLAT_NS4_ADDR" "$IPXLAT_V4_REMOTE" 5555 +check_err $? "udp checksum-zero 4->6 failed" +log_test "udp checksum-zero 4->6" + +exit "$EXIT_STATUS" diff --git a/tools/testing/selftests/net/ipxlat/ipxlat_frag.sh b/tools/testing/selftests/net/ipxlat/ipxlat_frag.sh new file mode 100755 index 000000000000..26ed351cd263 --- /dev/null +++ b/tools/testing/selftests/net/ipxlat/ipxlat_frag.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# IPXLAT - Stateless IP/ICMP Translation (SIIT) virtual device driver +# +# Copyright (C) 2026- Mandelbit SRL +# Copyright (C) 2026- Daniel Gröber <[email protected]> +# +# Author: Antonio Quartulli <[email protected]> +# Daniel Gröber <[email protected]> +# Ralf Lici <[email protected]> + +set -o pipefail + +SCRIPT_DIR=$(dirname "$(readlink -f "$0")") +source "$SCRIPT_DIR/ipxlat_lib.sh" + +trap ipxlat_cleanup EXIT + +ipxlat_setup_env + +# Exercise large TCP flow on 4->6 path to cover pre-fragmentation behavior +RET=0 +ipxlat_run_iperf "$NS6" "$NS4" "$IPXLAT_V4_REMOTE" 5301 -n 8M +check_err $? "large tcp 4->6 failed" +log_test "large tcp 4->6" + +# Exercise large UDP flow on 4->6 path to cover pre-fragmentation behavior +RET=0 +ipxlat_run_iperf "$NS6" "$NS4" "$IPXLAT_V4_REMOTE" 5302 -u -b 20M -t 2 -l 1400 +check_err $? "large udp 4->6 failed" +log_test "large udp 4->6" + +# Exercise large TCP flow on 6->4 path to cover +# fragmentation-sensitive translation +RET=0 +ipxlat_run_iperf "$NS4" "$NS6" "$IPXLAT_V6_NS4" 5303 \ + -B "$IPXLAT_V6_NS6_SRC" -n 8M +check_err $? "large tcp 6->4 failed" +log_test "large tcp 6->4" + +# Exercise large UDP flow on 6->4 path to cover +# fragmentation-sensitive translation +RET=0 +ipxlat_run_iperf "$NS4" "$NS6" "$IPXLAT_V6_NS4" 5304 \ + -B "$IPXLAT_V6_NS6_SRC" -u -b 20M -t 2 -l 1400 +check_err $? "large udp 6->4 failed" +log_test "large udp 6->4" + +# Send oversized IPv4 ICMP Echo with DF disabled (source fragmentation allowed) +# and verify translator drops fragmented ICMPv4 input (no translated ICMPv6 +# Echo seen in NS6) +RET=0 +ipxlat_capture_pkts "$NS6" "icmp6 and ip6[40] == 128" 0 5 \ + ip netns exec "$NS4" bash -c \ + "ping -M \"dont\" -s 2000 -c 1 -W 1 \"$IPXLAT_V4_REMOTE\" \ + >/dev/null 2>&1 || test \$? -eq 1" +check_err $? "fragmented icmp 4->6 should be dropped" +log_test "drop fragmented icmp 4->6" + +# Send oversized IPv6 ICMP echo request and verify translator drops fragmented +# ICMPv6 input (no translated ICMPv4 Echo seen in NS4) +RET=0 +ipxlat_capture_pkts "$NS4" "icmp and icmp[0] == 8" 0 5 \ + ip netns exec "$NS6" bash -c \ + "ping -6 -s 2000 -c 1 -W 1 -I \"$IPXLAT_V6_NS6_SRC\" \ + \"$IPXLAT_V6_NS4\" >/dev/null 2>&1 || test \$? -eq 1" +check_err $? "fragmented icmp 6->4 should be dropped" +log_test "drop fragmented icmp 6->4" + +exit "$EXIT_STATUS" diff --git a/tools/testing/selftests/net/ipxlat/ipxlat_icmp_err.sh b/tools/testing/selftests/net/ipxlat/ipxlat_icmp_err.sh new file mode 100755 index 000000000000..946584b55895 --- /dev/null +++ b/tools/testing/selftests/net/ipxlat/ipxlat_icmp_err.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# IPXLAT - Stateless IP/ICMP Translation (SIIT) virtual device driver +# +# Copyright (C) 2026- Mandelbit SRL +# Copyright (C) 2026- Daniel Gröber <[email protected]> +# +# Author: Antonio Quartulli <[email protected]> +# Daniel Gröber <[email protected]> +# Ralf Lici <[email protected]> + +set -o pipefail + +SCRIPT_DIR=$(dirname "$(readlink -f "$0")") +source "$SCRIPT_DIR/ipxlat_lib.sh" + +trap ipxlat_cleanup EXIT + +ipxlat_setup_env + +# Trigger UDP to a closed port from NS4 and capture translated +# ICMPv4 Port Unreachable +RET=0 +ipxlat_capture_pkts "$NS4" "icmp and icmp[0] == 3 and icmp[1] == 3" 1 3 \ + ip netns exec "$NS4" bash -c \ + "echo x > /dev/udp/$IPXLAT_V4_REMOTE/9 || true" +check_err $? "icmp-error 4->6 not observed" +log_test "icmp-error xlate 4->6" + +# Trigger UDP to a closed port from NS6 and capture translated +# ICMPv6 Port Unreachable +RET=0 +ipxlat_capture_pkts "$NS6" "icmp6 and ip6[40] == 1 and ip6[41] == 4" 1 3 \ + ip netns exec "$NS6" bash -c \ + "echo x > /dev/udp/$IPXLAT_V6_NS4/9 || true" +check_err $? "icmp-error 6->4 not observed" +log_test "icmp-error xlate 6->4" + +# Send oversized DF IPv4 packet and verify local ICMPv4 +# Fragmentation Needed emission +sysctl -qw net.ipv4.conf.ipxl0.accept_local=1 +sysctl -qw net.ipv4.conf.all.rp_filter=0 +sysctl -qw net.ipv4.conf.default.rp_filter=0 +sysctl -qw net.ipv4.conf.ipxl0.rp_filter=0 +sleep 2 +RET=0 +ipxlat_capture_pkts "$NS4" "icmp and icmp[0] == 3 and icmp[1] == 4" 1 3 \ + ip netns exec "$NS4" bash -c \ + "ping -M \"do\" -s 1300 -c 1 -W 1 \"$IPXLAT_V4_REMOTE\" \ + >/dev/null 2>&1 || test \$? -eq 1" +check_err $? "icmpv4 frag-needed emission not observed" +log_test "icmpv4 frag-needed emission" + +exit "$EXIT_STATUS" diff --git a/tools/testing/selftests/net/ipxlat/ipxlat_lib.sh b/tools/testing/selftests/net/ipxlat/ipxlat_lib.sh new file mode 100644 index 000000000000..e27683f280d4 --- /dev/null +++ b/tools/testing/selftests/net/ipxlat/ipxlat_lib.sh @@ -0,0 +1,273 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# IPXLAT - Stateless IP/ICMP Translation (SIIT) virtual device driver +# +# Copyright (C) 2026- Mandelbit SRL +# Copyright (C) 2026- Daniel Gröber <[email protected]> +# +# Author: Antonio Quartulli <[email protected]> +# Daniel Gröber <[email protected]> +# Ralf Lici <[email protected]> + +set -o pipefail + +IPXLAT_TEST_DIR=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") +source "$IPXLAT_TEST_DIR/../lib.sh" + +KDIR=${KDIR:-$(readlink -f "$IPXLAT_TEST_DIR/../../../../../")} +YNL_CLI="$KDIR/tools/net/ynl/pyynl/cli.py" +YNL_SPEC="$KDIR/Documentation/netlink/specs/ipxlat.yaml" +IPXLAT_IPERF_TIMEOUT=${IPXLAT_IPERF_TIMEOUT:-10} + +IPXLAT_TRANSLATOR_DEV=ipxl0 +IPXLAT_VETH4_HOST=veth4r +IPXLAT_VETH4_NS=veth4n +IPXLAT_VETH6_HOST=veth6r +IPXLAT_VETH6_NS=veth6n + +IPXLAT_XLAT_PREFIX6=2001:db8:100:: +IPXLAT_XLAT_PREFIX6_LEN=40 +IPXLAT_XLAT_PREFIX6_HEX=20010db8010000000000000000000000 +IPXLAT_LOWEST_IPV6_MTU=1280 + +IPXLAT_HOST4_ADDR=198.51.100.1 +IPXLAT_HOST6_ADDR=2001:db8:1::1 + +IPXLAT_NS4_ADDR=198.51.100.2 +IPXLAT_NS6_ADDR=2001:db8:1::2 +export IPXLAT_V4_REMOTE=192.0.2.33 + +IPXLAT_V6_REMOTE=2001:db8:1c0:2:21:: +IPXLAT_V6_NS4=2001:db8:1c6:3364:2:: +IPXLAT_V6_NS6_SRC=2001:db8:1c0:2:2:: + +NS4="" +NS6="" + +ipxlat_ynl() +{ + python3 "$YNL_CLI" --spec "$YNL_SPEC" "$@" +} + +ipxlat_build_dev_set_json() +{ + local ifindex="$1" + + jq -cn \ + --argjson ifindex "$ifindex" \ + --arg prefix "$IPXLAT_XLAT_PREFIX6_HEX" \ + --argjson prefix_len "$IPXLAT_XLAT_PREFIX6_LEN" \ + --argjson lowest_ipv6_mtu "$IPXLAT_LOWEST_IPV6_MTU" \ + '{ + ifindex: $ifindex, + config: { + "xlat-prefix6": { + prefix: $prefix, + "prefix-len": $prefix_len + }, + "lowest-ipv6-mtu": $lowest_ipv6_mtu + } + }' +} + +ipxlat_require_root() +{ + if [[ $(id -u) -ne 0 ]]; then + echo "ipxlat selftests need root; skipping" + exit "$ksft_skip" + fi +} + +ipxlat_require_tools() +{ + if [[ ! -f "$YNL_CLI" || ! -f "$YNL_SPEC" ]]; then + log_test_skip "ipxlat netlink spec/ynl not found" + exit "$ksft_skip" + fi + + for tool in ip python3 ping iperf3 tcpdump timeout jq; do + require_command "$tool" + done +} + +ipxlat_cleanup() +{ + cleanup_ns "${NS4:-}" "${NS6:-}" || true + ip link del "$IPXLAT_TRANSLATOR_DEV" 2>/dev/null || true + ip link del "$IPXLAT_VETH4_HOST" 2>/dev/null || true + ip link del "$IPXLAT_VETH6_HOST" 2>/dev/null || true +} + +# Test topology: +# +# host namespace: +# - owns ipxlat dev `ipxl0` +# - has veth peers `veth4r` and `veth6r` +# - routes IPv4 test prefix (192.0.2.0/24) to ipxl0 (v4 network steering rule) +# - routes xlat-prefix6 prefix (2001:db8:100::/40) out to NS6 side +# - routes mapped NS4 IPv6 identity (2001:db8:1c6:3364:2::/128) to ipxl0 +# so NS6->NS4 traffic enters 6->4 translation +# +# NS4: +# - IPv4-only endpoint: 198.51.100.2/24 on veth4n +# - default route via host 198.51.100.1 (veth4r) +# - sends traffic to 192.0.2.33 (translated by ipxl0 to IPv6) +# +# NS6: +# - IPv6 endpoint: 2001:db8:1::2/64 on veth6n +# - also owns mapped addresses used by tests: +# 2001:db8:1c0:2:21:: (maps to 192.0.2.33) +# 2001:db8:1c0:2:2:: (maps to 192.0.2.2, used as explicit src +# since we have multiple v6 addresses) +# - route to mapped NS4 IPv6 address is pinned via host: +# 2001:db8:1c6:3364:2::/128 +# This keeps the 6->4 test path deterministic. +# +# ipxlat config under test: +# - xlat-prefix6 = 2001:db8:100::/40 +# - lowest-ipv6-mtu = 1280 +ipxlat_configure_topology() +{ + local ifindex + local dev_set_json + + if ! ip link add "$IPXLAT_TRANSLATOR_DEV" type ipxlat; then + echo "ipxlat link kind unavailable; skipping" + exit "$ksft_skip" + fi + ip link set "$IPXLAT_TRANSLATOR_DEV" up + ifindex=$(cat /sys/class/net/"$IPXLAT_TRANSLATOR_DEV"/ifindex) + dev_set_json=$(ipxlat_build_dev_set_json "$ifindex") + + if ! ipxlat_ynl --do dev-set --json "$dev_set_json" >/dev/null; then + echo "ipxlat dev-set failed" + exit "$ksft_fail" + fi + + setup_ns NS4 NS6 || exit "$ksft_skip" + + ip link add "$IPXLAT_VETH4_HOST" type veth peer name "$IPXLAT_VETH4_NS" + ip link add "$IPXLAT_VETH6_HOST" type veth peer name "$IPXLAT_VETH6_NS" + ip link set "$IPXLAT_VETH4_NS" netns "$NS4" + ip link set "$IPXLAT_VETH6_NS" netns "$NS6" + + ip addr add "$IPXLAT_HOST4_ADDR/24" dev "$IPXLAT_VETH4_HOST" + ip -6 addr add "$IPXLAT_HOST6_ADDR/64" dev "$IPXLAT_VETH6_HOST" + ip link set "$IPXLAT_VETH4_HOST" up + ip link set "$IPXLAT_VETH6_HOST" up + + ip netns exec "$NS4" ip addr add "$IPXLAT_NS4_ADDR/24" \ + dev "$IPXLAT_VETH4_NS" + ip netns exec "$NS4" ip link set "$IPXLAT_VETH4_NS" up + ip netns exec "$NS4" ip route add default via "$IPXLAT_HOST4_ADDR" + + ip netns exec "$NS6" ip -6 addr add "$IPXLAT_NS6_ADDR/64" \ + dev "$IPXLAT_VETH6_NS" + ip netns exec "$NS6" ip -6 addr add "$IPXLAT_V6_REMOTE/128" \ + dev "$IPXLAT_VETH6_NS" + ip netns exec "$NS6" ip -6 addr add "$IPXLAT_V6_NS6_SRC/128" \ + dev "$IPXLAT_VETH6_NS" + ip netns exec "$NS6" ip link set "$IPXLAT_VETH6_NS" up + ip netns exec "$NS6" ip -6 route add default via "$IPXLAT_HOST6_ADDR" + ip netns exec "$NS6" ip -6 route replace "$IPXLAT_V6_NS4/128" \ + via "$IPXLAT_HOST6_ADDR" + sleep 2 + + sysctl -qw net.ipv4.ip_forward=1 + sysctl -qw net.ipv6.conf.all.forwarding=1 + + # 4->6 steering rule + ip route replace 192.0.2.0/24 dev "$IPXLAT_TRANSLATOR_DEV" + # Post-translation egress: + # IPv6 destinations in xlat-prefix6 leave toward NS6. + ip -6 route replace "$IPXLAT_XLAT_PREFIX6/$IPXLAT_XLAT_PREFIX6_LEN" \ + dev "$IPXLAT_VETH6_HOST" + # 6->4 steering rule + ip -6 route replace "$IPXLAT_V6_NS4/128" dev "$IPXLAT_TRANSLATOR_DEV" + + ip link set "$IPXLAT_VETH6_HOST" mtu 1280 + ip netns exec "$NS6" ip link set "$IPXLAT_VETH6_NS" mtu 1280 +} + +ipxlat_setup_env() +{ + ipxlat_require_root + ipxlat_require_tools + ipxlat_cleanup + + ipxlat_configure_topology +} + +ipxlat_run_iperf() +{ + local srv_ns="$1" + local cli_ns="$2" + local dst="$3" + local port="$4" + local -a args=() + local client_rc + local server_rc + local spid + local idx + + for ((idx = 5; idx <= $#; idx++)); do + args+=("${!idx}") + done + + ip netns exec "$srv_ns" timeout "$IPXLAT_IPERF_TIMEOUT" \ + iperf3 -s -1 -p "$port" >/dev/null 2>&1 & + spid=$! + sleep 0.2 + + ip netns exec "$cli_ns" timeout "$IPXLAT_IPERF_TIMEOUT" \ + iperf3 -c "$dst" -p "$port" "${args[@]}" >/dev/null 2>&1 + + client_rc=$? + if [[ $client_rc -ne 0 ]]; then + kill "$spid" >/dev/null 2>&1 || true + fi + + wait "$spid" >/dev/null 2>&1 + server_rc=$? + + ((client_rc != 0)) && return "$client_rc" + return "$server_rc" +} + +ipxlat_capture_pkts() +{ + local ns="$1" + local filter="$2" + local expect_pkts="$3" + local timeout_s="$4" + local cap_goal + local cap_pid + local rc + local trigger_rc + + shift 4 + + cap_goal=1 + [[ $expect_pkts -gt 0 ]] && cap_goal=$expect_pkts + + ip netns exec "$ns" timeout "$timeout_s" \ + tcpdump -nni any -c "$cap_goal" \ + "$filter" >/dev/null 2>&1 & + cap_pid=$! + sleep 0.2 + + "$@" + trigger_rc=$? + wait "$cap_pid" >/dev/null 2>&1 + rc=$? + + if [[ $trigger_rc -ne 0 ]]; then + return "$trigger_rc" + fi + + if [[ $expect_pkts -eq 0 ]]; then + [[ $rc -eq 124 ]] + else + [[ $rc -eq 0 ]] + fi +} diff --git a/tools/testing/selftests/net/ipxlat/ipxlat_udp4_zero_csum_send.c b/tools/testing/selftests/net/ipxlat/ipxlat_udp4_zero_csum_send.c new file mode 100644 index 000000000000..ef9f07f8d699 --- /dev/null +++ b/tools/testing/selftests/net/ipxlat/ipxlat_udp4_zero_csum_send.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* IPXLAT - Stateless IP/ICMP Translation (SIIT) virtual device driver + * + * Copyright (C) 2026- Mandelbit SRL + * Copyright (C) 2026- Daniel Gröber <[email protected]> + * + * Author: Antonio Quartulli <[email protected]> + * Daniel Gröber <[email protected]> + * Ralf Lici <[email protected]> + */ + +#include <arpa/inet.h> +#include <errno.h> +#include <linux/ip.h> +#include <linux/udp.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <unistd.h> + +static uint16_t iphdr_csum(const void *buf, size_t len) +{ + const uint16_t *p = buf; + uint32_t sum = 0; + + while (len > 1) { + sum += *p++; + len -= 2; + } + if (len) + sum += *(const uint8_t *)p; + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return (uint16_t)~sum; +} + +int main(int argc, char **argv) +{ + static const char payload[] = "ipxlat-zero-udp-csum"; + struct sockaddr_in dst = {}; + struct { + struct iphdr ip; + struct udphdr udp; + char payload[sizeof(payload)]; + } pkt = {}; + in_addr_t saddr, daddr; + unsigned long dport_ul; + socklen_t dst_len; + ssize_t n; + int one = 1; + int fd; + + if (argc != 4) { + fprintf(stderr, "usage: %s <src4> <dst4> <dport>\n", argv[0]); + return 2; + } + + if (!inet_pton(AF_INET, argv[1], &saddr) || + !inet_pton(AF_INET, argv[2], &daddr)) { + fprintf(stderr, "invalid IPv4 address\n"); + return 2; + } + + errno = 0; + dport_ul = strtoul(argv[3], NULL, 10); + if (errno || dport_ul > 65535) { + fprintf(stderr, "invalid UDP port\n"); + return 2; + } + + fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (fd < 0) { + perror("socket"); + return 1; + } + + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { + perror("setsockopt(IP_HDRINCL)"); + close(fd); + return 1; + } + + pkt.ip.version = 4; + pkt.ip.ihl = 5; + pkt.ip.ttl = 64; + pkt.ip.protocol = IPPROTO_UDP; + pkt.ip.tot_len = htons(sizeof(pkt)); + pkt.ip.id = htons(1); + pkt.ip.frag_off = 0; + pkt.ip.saddr = saddr; + pkt.ip.daddr = daddr; + pkt.ip.check = iphdr_csum(&pkt.ip, sizeof(pkt.ip)); + + pkt.udp.source = htons(4242); + pkt.udp.dest = htons((uint16_t)dport_ul); + pkt.udp.len = htons(sizeof(pkt.udp) + sizeof(payload)); + pkt.udp.check = 0; + + memcpy(pkt.payload, payload, sizeof(payload)); + + dst.sin_family = AF_INET; + dst.sin_port = pkt.udp.dest; + dst.sin_addr.s_addr = daddr; + dst_len = sizeof(dst); + + n = sendto(fd, &pkt, sizeof(pkt), 0, (struct sockaddr *)&dst, dst_len); + if (n != (ssize_t)sizeof(pkt)) { + perror("sendto"); + close(fd); + return 1; + } + + close(fd); + return 0; +} -- 2.53.0

