Skip to content

Instantly share code, notes, and snippets.

@DasSkelett
Last active September 21, 2024 22:48
Show Gist options
  • Save DasSkelett/597f83479b6099069bc6cb553acdf32f to your computer and use it in GitHub Desktop.
Save DasSkelett/597f83479b6099069bc6cb553acdf32f to your computer and use it in GitHub Desktop.
Unicast DHCPv6 Prefix Delegation over WireGuard
#!/bin/sh 23:47:56 [33/1865]
# https://glindhart.dk/2022/10/15/ipv6-prefixdelegation-routing.html
# https://kea.readthedocs.io/en/latest/arm/hooks.html#libdhcp-run-script-so-run-script-support-for-external-hook-scripts
# This script adds/removes IPv6 routes and updates WireGuard AllowedIPs on prefix-delegation from KEA/DHCP server
wg_add() {
local prefix="$1"
local prefix_len="$2"
local remote_addr="$3"
local iface="$4"
peer_data=$(wg show "${iface}" allowed-ips | grep "${remote_addr}")
peer=$(echo "${peer_data}" | awk '{printf $1}')
aips=$(echo "${peer_data}" | cut -f 2- | cut --delimiter=" " -f 1- --output-delimiter=",")
echo $peer $aips $iface $prefix $prefix_len
wg set "${iface}" peer $peer allowed-ips "${aips},${prefix}/${prefix_len}"
}
lease6_renew () {
if [ "$LEASE6_TYPE" = "IA_PD" ]; then
# Add route for delegated prefix (next hop is the client)
ip -6 route replace "${LEASE6_ADDRESS}/${LEASE6_PREFIX_LEN}" via "${QUERY6_REMOTE_ADDR}" dev "${QUERY6_IFACE_NAME}" proto static
wg_add "${LEASE6_ADDRESS}" "${LEASE6_PREFIX_LEN}" "${QUERY6_REMOTE_ADDR}" "${QUERY6_IFACE_NAME}"
fi
exit 0
}
lease6_expire () {
if [ "$LEASE6_TYPE" = "IA_PD" ]; then
# Remove route for delegated prefix
ip -6 route del "${LEASE6_ADDRESS}/${LEASE6_PREFIX_LEN}" proto static
fi
exit 0
}
leases6_committed () {
# TODO: If i.e. addresses are also available via DHCP, there can be more than a single AT[index], so Loop 0..($LEASES6_SIZE-1)
# if [ "$LEASES6_AT0_TYPE" = "IA_NA" ]; then it's an address
if [ "$LEASES6_AT0_TYPE" = "IA_PD" ]; then
# Add route for delegated prefix (next hop is the client). Remote-addr (via) will typically be LinkLocal, unless KEA listens on Unicast
ip -6 route replace "${LEASES6_AT0_ADDRESS}/${LEASES6_AT0_PREFIX_LEN}" via "${QUERY6_REMOTE_ADDR}" dev "${QUERY6_IFACE_NAME}" proto static
wg_add "${LEASES6_AT0_ADDRESS}" "${LEASES6_AT0_PREFIX_LEN}" "${QUERY6_REMOTE_ADDR}" "${QUERY6_IFACE_NAME}"
fi
exit 0
}
lease6_release () {
if [ "$LEASE6_TYPE" = "IA_PD" ]; then
# Remove route for delegated prefix
ip -6 route del "${LEASE6_ADDRESS}/${LEASE6_PREFIX_LEN}" proto static
fi
exit 0
}
case "$1" in
"lease6_renew")
lease6_renew
;;
"lease6_expire")
lease6_expire
;;
"leases6_committed")
leases6_committed
;;
"lease6_release")
lease6_release
;;
esac
{
"Dhcp6": {
"server-id": {
"type": "LL",
"identifier": "000006540001"
},
"interfaces-config": {
"interfaces": [ "wg-welt/fe80::654:1", "wg-welt" ]
},
"subnet6": [
{
"id": 2,
"subnet": "fe80::/64",
"pd-pools": [
{
"prefix": "3000:1::",
"prefix-len": 52,
"delegated-len": 63
}
]
}
],
"hooks-libraries": [
{
"library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_run_script.so",
"parameters": {
"name": "/etc/kea/ipv6-routes.sh",
"sync": false
}
}
],
}
}
package main
import (
"fmt"
"net"
"net/netip"
"github.com/insomniacslk/dhcp/dhcpv6"
"github.com/insomniacslk/dhcp/dhcpv6/client6"
"github.com/insomniacslk/dhcp/iana"
)
func main() {
var pdClient = client6.NewClient()
var addr, _ = netip.ParseAddr("fe80::654:1")
var addrPort = netip.AddrPortFrom(addr, dhcpv6.DefaultServerPort)
pdClient.RemoteAddr = net.UDPAddrFromAddrPort(addrPort)
// pdClient.SimulateRelay = true
// TODO: macaddr="$(uci get wireguard.mesh_vpn.privatekey | wg pubkey | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')"
// oldIFS="$IFS"
// IFS=':'
// # shellcheck disable=SC2086 # we need to split macaddr here using IFS
// set -- $macaddr
// IFS="$oldIFS"
// echo "fe80::${1}${2}:${3}ff:fe${4}:${5}${6}"
var hwAddr, _ = net.ParseMAC("42:5b:17:35:62:47")
var srvHwAddr, _ = net.ParseMAC("00:00:06:54:00:01")
var duid = &dhcpv6.DUIDLL{
HWType: iana.HWTypeEthernet,
LinkLayerAddr: srvHwAddr,
}
var iaid = [4]byte{0x1, 0x2, 0x3, 0x4}
var sol, _ = dhcpv6.NewSolicit(hwAddr)
var _, wantedPrefixLength, _ = net.ParseCIDR("::/63")
var iapd_opt = dhcpv6.WithIAPD(iaid, &dhcpv6.OptIAPrefix{Prefix: wantedPrefixLength})
var adv, _ = dhcpv6.NewAdvertiseFromSolicit(sol, dhcpv6.WithServerID(duid), dhcpv6.WithIANA(), iapd_opt)
var req, res, err = pdClient.Request("vpn", adv)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(req.LongString(2))
fmt.Println(res.LongString(2))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment