Skip to content

Instantly share code, notes, and snippets.

@yescallop
Last active May 24, 2024 08:48
Show Gist options
  • Save yescallop/556ed8abb3042ae535f338581b471c4b to your computer and use it in GitHub Desktop.
Save yescallop/556ed8abb3042ae535f338581b471c4b to your computer and use it in GitHub Desktop.
Assigning dynamic IPv6 GUA to WireGuard interface
[CmdletBinding(DefaultParameterSetName = 'Reresolve')]
param (
[Parameter(Mandatory, ParameterSetName = "Stop")]
[switch]$Stop,
[Parameter(Mandatory, ParameterSetName = "Reresolve")]
[Parameter(Mandatory, ParameterSetName = "ReresolveAndAssignV6")]
[string]$Peer,
[Parameter(Mandatory, ParameterSetName = "Reresolve")]
[Parameter(Mandatory, ParameterSetName = "ReresolveAndAssignV6")]
[string]$DnsName,
[Parameter(ParameterSetName = "Reresolve")]
[Parameter(ParameterSetName = "ReresolveAndAssignV6")]
[string]$DnsType = "A_AAAA",
[Parameter(Mandatory, ParameterSetName = "Reresolve")]
[Parameter(Mandatory, ParameterSetName = "ReresolveAndAssignV6")]
[int]$Port,
[Parameter(Mandatory, ParameterSetName = "AssignV6")]
[Parameter(ParameterSetName = "ReresolveAndAssignV6")]
[string]$DnsNameV6 = $DnsName,
[Parameter(ParameterSetName = "AssignV6")]
[Parameter(ParameterSetName = "ReresolveAndAssignV6")]
[int]$PrefixLen = 64,
[Parameter(Mandatory, ParameterSetName = "AssignV6")]
[Parameter(Mandatory, ParameterSetName = "ReresolveAndAssignV6")]
[string]$Suffix,
[Parameter(ParameterSetName = "AssignV6")]
[Parameter(ParameterSetName = "ReresolveAndAssignV6")]
[switch]$AddDefaultRouteV6
)
$ErrorActionPreference = "Stop"
$tunName = $env:WIREGUARD_TUNNEL_NAME
$pidFile = Join-Path $env:TEMP "wg-dynconf-$tunName.pid"
if ($Stop) {
$daemonPid = Get-Content $pidFile
Stop-Process -Id $daemonPid
Write-Output "Stopped daemon with PID $daemonPid"
exit
}
if ($DnsType -notin ("A_AAAA", "A", "AAAA")) {
Write-Output "Invalid DnsType"
exit 1
}
function Ipv6AddrToBigInt {
param ([string]$addr)
$addrBytes = [ipaddress]::Parse($addr).GetAddressBytes();
[array]::Reverse($addrBytes)
return [bigint]::New($addrBytes)
}
function BigIntToIpv6Addr {
param ([bigint]$addrBits)
$addrBytes = $addrBits.ToByteArray();
[array]::Resize([ref]$addrBytes, 16);
[array]::Reverse($addrBytes)
return [ipaddress]::new($addrBytes)
}
if ($Suffix) {
if ($PrefixLen -lt 0 -or $PrefixLen -gt 128) {
Write-Output "PrefixLen must be between 0 to 128"
exit 1
}
$suffixMask = ([bigint]1 -shl (128 - $PrefixLen)) - 1
$prefixMask = ([bigint]1 -shl 128) - 1 - $suffixMask
$suffixBits = Ipv6AddrToBigInt $Suffix
if (($suffixBits -band $prefixMask) -ne 0) {
Write-Output "Invalid combination of PrefixLen and Suffix"
exit 1
}
}
Set-Content $pidFile -Value $PID
$ErrorActionPreference = "Continue"
if ($AddDefaultRouteV6) {
$null = New-NetRoute "::/0" -InterfaceAlias $tunName -RouteMetric 1024
}
while ($true) {
if ($DnsName) {
$record = Resolve-DnsName $DnsName -Type $DnsType
if ($record -and $record.Type -in ("A", "AAAA")) {
$addr = $record.IPAddress
if ($record.Type -eq "AAAA") {
$addr = "[$addr]"
}
if ($lastAddr -ne $addr) {
$argList = "set", $tunName, "peer", $Peer, "endpoint", "${addr}:$Port"
Start-Process "wg" -ArgumentList $argList
$lastAddr = $addr
}
}
}
if ($Suffix) {
if (!$DnsName -or $DnsNameV6 -ne $DnsName -or $record.Type -ne "AAAA") {
$record = Resolve-DnsName $DnsNameV6 -Type "AAAA"
}
$addrV6 = if ($record -and $record.Type -eq "AAAA") {
BigIntToIpv6Addr (((Ipv6AddrToBigInt $record.IPAddress) -band $prefixMask) -bor $suffixBits)
}
else { $null }
if ($lastAddrV6 -ne $addrV6) {
if ($null -ne $lastAddrV6) {
Remove-NetIPAddress $lastAddrV6 -InterfaceAlias $tunName -Confirm:$false
}
if ($null -ne $addrV6) {
$null = New-NetIPAddress $addrV6 -InterfaceAlias $tunName
}
}
$lastAddrV6 = $addrV6
}
Start-Sleep 60
}
#!/bin/bash
eth_if="eth0"
wg_if=$1
trap cleanup SIGINT
trap cleanup SIGTERM
cleanup() {
prefix=""
update
exit
}
count_colons() {
local colons=${1//[^:]}
echo ${#colons}
}
concat_addr() {
prefix_colons=$(count_colons $1)
suffix_colons=$(count_colons $2)
colon_count=$((prefix_colons+suffix_colons))
if [[ "$colon_count" == "8" ]]; then
echo ${1%:}$2
else
echo $1$2
fi
}
echo_eval() {
echo "[#] $1"
eval "$1"
}
update() {
if [[ "$prefix" == "$last_prefix" ]]; then
return
fi
sed_exp=""
suffixes=$(grep -Po '2001:db8::\K[0-9a-f:]*' "/etc/wireguard/$wg_if.conf")
while read -r suffix; do
if [[ -n "$last_prefix" ]]; then
last_addr=$(concat_addr $last_prefix $suffix)
echo_eval "ip -6 neigh del proxy $last_addr dev $eth_if"
echo_eval "ip -6 route del $last_addr dev $wg_if"
fi
if [[ -n "$prefix" ]]; then
addr=$(concat_addr $prefix $suffix)
echo_eval "ip -6 route add $addr dev $wg_if"
echo_eval "ip -6 neigh add proxy $addr dev $eth_if"
sed_exp="s/2001:db8::$suffix/$addr/g;$sed_exp"
fi
done <<< "$suffixes"
if [[ -z "$inited" ]]; then
echo_eval "sysctl net.ipv6.conf.all.forwarding=1"
inited=true
fi
echo_eval "wg syncconf $wg_if <(wg-quick strip $wg_if | sed -e '$sed_exp')"
last_prefix=$prefix
}
coproc (ip -6 route show dev $eth_if && ip -6 monitor route dev $eth_if)
while read -r -u ${COPROC[0]} route; do
if [[ "$route" =~ ^([0-9a-f:]*)/64\ proto\ ra ]]; then
prefix=${BASH_REMATCH[1]}
update
fi
done
[Unit]
Description=WireGuard NDP proxy for %I
[Service]
Type=simple
ExecStart=/usr/local/sbin/wg-ndproxy.sh %i
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment