Skip to content

Instantly share code, notes, and snippets.

@Gowee
Created April 4, 2026 12:59
Show Gist options
  • Select an option

  • Save Gowee/44d051df3dd777c7a4a15771af7ae33c to your computer and use it in GitHub Desktop.

Select an option

Save Gowee/44d051df3dd777c7a4a15771af7ae33c to your computer and use it in GitHub Desktop.
CLAT netifd proto helper using Jool SIIT + netns + veth
#!/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