Last active
September 21, 2024 22:48
-
-
Save DasSkelett/597f83479b6099069bc6cb553acdf32f to your computer and use it in GitHub Desktop.
Unicast DHCPv6 Prefix Delegation over WireGuard
This file contains 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 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 |
This file contains 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
{ | |
"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 | |
} | |
} | |
], | |
} | |
} |
This file contains 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
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