Created
April 4, 2026 12:59
-
-
Save Gowee/44d051df3dd777c7a4a15771af7ae33c to your computer and use it in GitHub Desktop.
CLAT netifd proto helper using Jool SIIT + netns + veth
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # | |
| # netifd proto for a CLAT-style Jool SIIT instance in its own network namespace. | |
| JOOL_CLAT_STATE_DIR="/var/run/jool-clat-proto" | |
| # Fixed defaults keep the proto stateless; users can still override both sides. | |
| JOOL_DEFAULT_HOST_V4="192.0.2.0/31" | |
| JOOL_DEFAULT_NS_V4="192.0.2.1/31" | |
| [ -n "$INCLUDE_ONLY" ] || { | |
| . /lib/functions.sh | |
| . /lib/functions/network.sh | |
| . ../netifd-proto.sh | |
| init_proto "$@" | |
| } | |
| jool_clat_proto_log() { | |
| local cfg="$1" | |
| shift | |
| logger -t "jool-clat-proto[$cfg]" -- "$*" | |
| } | |
| jool_clat_proto_fail_common() { | |
| local cfg="$1" | |
| local code="$2" | |
| # block_restart=1 turns permanent setup errors into non-retryable failures. | |
| local block_restart="$3" | |
| shift 3 | |
| [ "$#" -gt 0 ] && jool_clat_proto_log "$cfg" "$*" | |
| proto_notify_error "$cfg" "$code" | |
| [ "$block_restart" = 0 ] || proto_block_restart "$cfg" | |
| proto_setup_failed "$cfg" | |
| return 1 | |
| } | |
| jool_clat_proto_fail() { | |
| jool_clat_proto_fail_common "$1" "$2" 1 "${@:3}" | |
| } | |
| jool_clat_proto_fail_retryable() { | |
| jool_clat_proto_fail_common "$1" "$2" 0 "${@:3}" | |
| } | |
| jool_clat_proto_wait_linklocal() { | |
| local netns="$1" | |
| local ifname="$2" | |
| local addr tries=0 | |
| while [ "$tries" -lt 5 ]; do | |
| if [ -n "$netns" ]; then | |
| addr="$(ip netns exec "$netns" ip -6 -o addr show scope link dev "$ifname" | awk '$0 !~ / tentative( |$)/ { print $4; exit }' | cut -d/ -f1)" | |
| else | |
| addr="$(ip -6 -o addr show scope link dev "$ifname" | awk '$0 !~ / tentative( |$)/ { print $4; exit }' | cut -d/ -f1)" | |
| fi | |
| [ -n "$addr" ] && { | |
| echo "$addr" | |
| return 0 | |
| } | |
| tries=$((tries + 1)) | |
| sleep 1 | |
| done | |
| return 1 | |
| } | |
| jool_clat_proto_normalize_lan_v4_cidr() { | |
| local cidr="$1" | |
| ( | |
| eval "$(ipcalc.sh "$cidr")" || exit 1 | |
| [ -n "$NETWORK" ] && [ -n "$PREFIX" ] || exit 1 | |
| echo "$NETWORK/$PREFIX" | |
| ) || return 1 | |
| } | |
| jool_clat_proto_word_count() { | |
| local count=0 | |
| local word | |
| for word in $1; do | |
| count=$((count + 1)) | |
| done | |
| echo "$count" | |
| } | |
| jool_clat_proto_run() { | |
| local cfg="$1" | |
| local message="$2" | |
| shift 2 | |
| "$@" || { | |
| jool_clat_proto_log "$cfg" "$message" | |
| return 1 | |
| } | |
| } | |
| jool_clat_proto_cleanup() { | |
| local cfg="$1" | |
| local host_if="$2" | |
| local netns="$3" | |
| [ -n "$netns" ] && ip netns exec "$netns" jool_siit instance remove >/dev/null 2>&1 | |
| [ -n "$netns" ] && ip netns del "$netns" >/dev/null 2>&1 | |
| [ -n "$host_if" ] && ip link del dev "$host_if" >/dev/null 2>&1 | |
| rm -f "$JOOL_CLAT_STATE_DIR/$cfg.state" | |
| } | |
| jool_clat_proto_setup_instance() { | |
| local cfg="$1" | |
| local plat_prefix="$2" | |
| local host_v4="$3" | |
| local ns_v4="$4" | |
| local host_ifname="$5" | |
| local ns_ifname="$6" | |
| local netns="$7" | |
| local clat_prefix_v4="$8" | |
| local clat_prefix_v6="$9" | |
| local state_file="${10}" | |
| local prefix_count4="${11}" | |
| local host_ip host_v6 ns_v6 host_mac ns_mac | |
| local prefix4 prefix6 | |
| jool_clat_proto_run "$cfg" "failed to create namespace '$netns'" \ | |
| ip netns add "$netns" || return 1 | |
| jool_clat_proto_run "$cfg" "failed to create veth pair '$host_ifname'/'$ns_ifname'" \ | |
| ip link add name "$host_ifname" type veth peer name "$ns_ifname" || return 1 | |
| jool_clat_proto_run "$cfg" "failed to move '$ns_ifname' into '$netns'" \ | |
| ip link set dev "$ns_ifname" netns "$netns" || return 1 | |
| # Bring the host veth up now so we can learn its link-local address before | |
| # handing the interface to netifd for long-term lifecycle management. | |
| jool_clat_proto_run "$cfg" "failed to bring up '$host_ifname'" \ | |
| ip link set dev "$host_ifname" up || return 1 | |
| jool_clat_proto_run "$cfg" "failed to bring up loopback in '$netns'" \ | |
| ip netns exec "$netns" ip link set dev lo up || return 1 | |
| jool_clat_proto_run "$cfg" "failed to bring up '$ns_ifname' in '$netns'" \ | |
| ip netns exec "$netns" ip link set dev "$ns_ifname" up || return 1 | |
| host_v6="$(jool_clat_proto_wait_linklocal "" "$host_ifname")" || { | |
| jool_clat_proto_log "$cfg" "failed to discover link-local IPv6 on '$host_ifname'" | |
| return 1 | |
| } | |
| ns_v6="$(jool_clat_proto_wait_linklocal "$netns" "$ns_ifname")" || { | |
| jool_clat_proto_log "$cfg" "failed to discover link-local IPv6 on '$ns_ifname'" | |
| return 1 | |
| } | |
| host_mac="$(cat "/sys/class/net/$host_ifname/address")" || { | |
| jool_clat_proto_log "$cfg" "failed to read MAC address for '$host_ifname'" | |
| return 1 | |
| } | |
| ns_mac="$(ip netns exec "$netns" cat "/sys/class/net/$ns_ifname/address")" || { | |
| jool_clat_proto_log "$cfg" "failed to read MAC address for '$ns_ifname' in '$netns'" | |
| return 1 | |
| } | |
| # Seed the namespace-side neighbor immediately; the host-side neighbor is | |
| # delegated to netifd so it survives its final device reconfiguration. | |
| jool_clat_proto_run "$cfg" "failed to seed namespace neighbor entry for '$host_v6'" \ | |
| ip netns exec "$netns" ip -6 neigh replace "$host_v6" lladdr "$host_mac" dev "$ns_ifname" nud permanent || return 1 | |
| jool_clat_proto_run "$cfg" "failed to assign '$ns_v4' to '$ns_ifname'" \ | |
| ip netns exec "$netns" ip addr add "$ns_v4" dev "$ns_ifname" || return 1 | |
| jool_clat_proto_run "$cfg" "failed to install namespace default route" \ | |
| ip netns exec "$netns" ip -6 route replace default via "$host_v6" dev "$ns_ifname" || return 1 | |
| if [ "$prefix_count4" -gt 0 ]; then | |
| host_ip="${host_v4%/*}" | |
| for prefix4 in $clat_prefix_v4; do | |
| jool_clat_proto_run "$cfg" "failed to install namespace route for '$prefix4'" \ | |
| ip netns exec "$netns" ip route replace "$prefix4" via "$host_ip" dev "$ns_ifname" || return 1 | |
| done | |
| fi | |
| jool_clat_proto_run "$cfg" "failed to enable namespace IPv4 forwarding" \ | |
| ip netns exec "$netns" sysctl -q -w net.ipv4.conf.all.forwarding=1 || return 1 | |
| jool_clat_proto_run "$cfg" "failed to enable namespace IPv6 forwarding" \ | |
| ip netns exec "$netns" sysctl -q -w net.ipv6.conf.all.forwarding=1 || return 1 | |
| jool_clat_proto_run "$cfg" "failed to create jool_siit instance in '$netns'" \ | |
| ip netns exec "$netns" jool_siit instance add --netfilter --pool6 "$plat_prefix" || return 1 | |
| if [ "$prefix_count4" -gt 0 ]; then | |
| # Prefix counts are validated earlier, so each IPv4 prefix maps to the | |
| # next IPv6 prefix in order. | |
| set -- $clat_prefix_v6 | |
| for prefix4 in $clat_prefix_v4; do | |
| prefix6="$1" | |
| shift | |
| jool_clat_proto_run "$cfg" "failed to map '$prefix4' to '$prefix6'" \ | |
| ip netns exec "$netns" jool_siit eamt add --force "$prefix4" "$prefix6" || return 1 | |
| done | |
| fi | |
| cat >"$state_file" <<-EOF | |
| HOST_IFNAME='$host_ifname' | |
| NS_IFNAME='$ns_ifname' | |
| NETNS='$netns' | |
| EOF | |
| JOOL_CLAT_NS_V6="$ns_v6" | |
| JOOL_CLAT_NS_MAC="$ns_mac" | |
| return 0 | |
| } | |
| proto_jool_clat_init_config() { | |
| available=1 | |
| no_device=1 | |
| proto_config_add_string "plat_prefix" | |
| proto_config_add_array "clat_prefix_v4:list(string)" | |
| proto_config_add_array "clat_prefix_v6:list(string)" | |
| proto_config_add_string "host_v4" | |
| proto_config_add_string "ns_v4" | |
| proto_config_add_string "host_ifname" | |
| proto_config_add_string "ns_ifname" | |
| proto_config_add_string "netns" | |
| proto_config_add_boolean "defaultroute" | |
| proto_config_add_int "metric" | |
| } | |
| proto_jool_clat_setup() { | |
| local cfg="$1" | |
| local plat_prefix host_v4 ns_v4 host_ifname ns_ifname netns defaultroute metric | |
| local clat_prefix_v4 clat_prefix_v6 | |
| local prefix_count4 prefix_count6 prefix6 state_file | |
| local lan_cidr="" route_src="" | |
| local expanded_prefix_v4="" prefix4 | |
| json_get_vars plat_prefix host_v4 ns_v4 host_ifname ns_ifname netns defaultroute metric | |
| json_get_values clat_prefix_v4 clat_prefix_v4 | |
| json_get_values clat_prefix_v6 clat_prefix_v6 | |
| [ -n "$plat_prefix" ] || { | |
| jool_clat_proto_fail "$cfg" MISSING_PLAT_PREFIX "missing required option plat_prefix" | |
| return 1 | |
| } | |
| host_v4="${host_v4:-$JOOL_DEFAULT_HOST_V4}" | |
| ns_v4="${ns_v4:-$JOOL_DEFAULT_NS_V4}" | |
| host_ifname="${host_ifname:-veth-$cfg}" | |
| ns_ifname="${ns_ifname:-$host_ifname-peer}" | |
| netns="${netns:-ns-$cfg}" | |
| defaultroute="${defaultroute:-1}" | |
| route_src="${host_v4%/*}" | |
| clat_prefix_v4="${clat_prefix_v4:-LAN}" | |
| prefix_count6="$(jool_clat_proto_word_count "$clat_prefix_v6")" | |
| [ "$prefix_count6" -gt 0 ] || { | |
| jool_clat_proto_fail "$cfg" MISSING_CLAT_PREFIX_V6 "at least one clat_prefix_v6 is required" | |
| return 1 | |
| } | |
| prefix_count4="$(jool_clat_proto_word_count "$clat_prefix_v4")" | |
| [ "$prefix_count4" -eq "$prefix_count6" ] || { | |
| jool_clat_proto_fail "$cfg" PREFIX_COUNT_MISMATCH "clat_prefix_v4 and clat_prefix_v6 must have the same number of entries; 'LAN' counts as one IPv4 prefix" | |
| return 1 | |
| } | |
| # Expand IPv4 CLAT prefixes in place. The list already defaults to LAN, and | |
| # only LAN entries require runtime lan data. If LAN is not used, route_src | |
| # stays on host_v4 for simplicity. | |
| for prefix4 in $clat_prefix_v4; do | |
| if [ "$prefix4" = "LAN" ]; then | |
| { | |
| network_get_ipaddr route_src "lan" 2>/dev/null && | |
| network_get_subnet lan_cidr "lan" 2>/dev/null && | |
| lan_cidr="$(jool_clat_proto_normalize_lan_v4_cidr "$lan_cidr")" && | |
| [ -n "$route_src" ] && [ -n "$lan_cidr" ] | |
| } || { | |
| jool_clat_proto_fail_retryable "$cfg" PREFIX_V4_PREPARE_FAILED "failed to resolve LAN for clat_prefix_v4" | |
| return 1 | |
| } | |
| append expanded_prefix_v4 "$lan_cidr" | |
| else | |
| append expanded_prefix_v4 "$prefix4" | |
| fi | |
| done | |
| clat_prefix_v4="$expanded_prefix_v4" | |
| if [ -e "/sys/class/net/$host_ifname" ]; then | |
| jool_clat_proto_fail "$cfg" HOST_IF_EXISTS "host interface '$host_ifname' already exists" | |
| return 1 | |
| fi | |
| if ip netns list | awk '{ print $1 }' | grep -Fxq "$netns"; then | |
| jool_clat_proto_fail "$cfg" NETNS_EXISTS "network namespace '$netns' already exists" | |
| return 1 | |
| fi | |
| modprobe jool_siit >/dev/null 2>&1 | |
| [ -x /usr/bin/jool_siit ] || { | |
| jool_clat_proto_fail "$cfg" MISSING_BINARY "missing /usr/bin/jool_siit" | |
| return 1 | |
| } | |
| mkdir -p "$JOOL_CLAT_STATE_DIR" || { | |
| jool_clat_proto_fail "$cfg" STATE_DIR_FAILED "failed to create $JOOL_CLAT_STATE_DIR" | |
| return 1 | |
| } | |
| state_file="$JOOL_CLAT_STATE_DIR/$cfg.state" | |
| jool_clat_proto_setup_instance \ | |
| "$cfg" "$plat_prefix" "$host_v4" "$ns_v4" "$host_ifname" "$ns_ifname" \ | |
| "$netns" "$clat_prefix_v4" "$clat_prefix_v6" "$state_file" "$prefix_count4" || { | |
| jool_clat_proto_cleanup "$cfg" "$host_ifname" "$netns" | |
| jool_clat_proto_fail "$cfg" SETUP_FAILED "setup failed; see logread for details" | |
| return 1 | |
| } | |
| proto_init_update "$host_ifname" 1 | |
| proto_add_ipv4_address "${host_v4%/*}" 31 | |
| [ "$defaultroute" = 0 ] || proto_add_ipv4_route "0.0.0.0" 0 "${ns_v4%/*}" "$route_src" "$metric" | |
| proto_add_ipv6_neighbor "$JOOL_CLAT_NS_V6" "$JOOL_CLAT_NS_MAC" | |
| for prefix6 in $clat_prefix_v6; do | |
| proto_add_ipv6_route "${prefix6%/*}" "${prefix6#*/}" "$JOOL_CLAT_NS_V6" | |
| done | |
| proto_send_update "$cfg" | |
| } | |
| proto_jool_clat_teardown() { | |
| local cfg="$1" | |
| local host_ifname ns_ifname netns state_file | |
| state_file="$JOOL_CLAT_STATE_DIR/$cfg.state" | |
| if [ -f "$state_file" ]; then | |
| . "$state_file" | |
| host_ifname="$HOST_IFNAME" | |
| ns_ifname="$NS_IFNAME" | |
| netns="$NETNS" | |
| else | |
| json_get_vars host_ifname ns_ifname netns | |
| host_ifname="${host_ifname:-veth-$cfg}" | |
| ns_ifname="${ns_ifname:-$host_ifname-peer}" | |
| netns="${netns:-ns-$cfg}" | |
| fi | |
| jool_clat_proto_cleanup "$cfg" "$host_ifname" "$netns" | |
| } | |
| [ -n "$INCLUDE_ONLY" ] || { | |
| add_protocol jool_clat | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment