Files
GHOSTWIRE/ipam.go
T
Daniel Redetzke 3482a03707 Install takes over a pivpn WireGuard server
On a server running pivpn's WireGuard, a new install offers to take it
over: the server key, port, MTU, tunnel networks, endpoint, DNS,
AllowedIPs and keepalive, and every client with its public key,
preshared key and addresses. Devices keep their configs. Clients pivpn
switched off are imported switched off, with the note "Imported from
pivpn". Client private keys in /etc/wireguard/configs are not read.

- Install notes which peers are connected, stops wg-quick@wg0, starts
  GHOSTWIRE on the same wg0 and waits up to 30 s for those peers. The
  wait only reports; idle devices reconnect when they next send.
- If the service does not stay running, install removes what it set up,
  including config.json, and starts pivpn's WireGuard again.
- Without a terminal the takeover needs -import-pivpn; install refuses
  to run next to pivpn otherwise, and the flag is refused on an
  existing install.
- Names GHOSTWIRE does not accept are renamed and listed in the
  summary. An IPv6 address that differs from the mapped one is kept on
  the peer until its config is issued again.
- uninstall without a config of its own (e.g. after a takeover was
  undone) leaves the WireGuard interface alone and removes only the
  firewall table.
- README: "Coming from pivpn?" under the intro, a Features entry and a
  "Moving from pivpn" section.

Tested end to end on Ubuntu 24.04 with pivpn aa96de7.
2026-10-06 00:54:08 +03:00

141 lines
4.0 KiB
Go

package main
import (
"crypto/rand"
"encoding/binary"
"errors"
"math/big"
"net"
"net/netip"
)
// Subnets that commonly appear on home and office networks. A tunnel network
// overlapping one of them breaks routing for clients that sit on such a LAN.
// Source: pivpn, https://community.openvpn.net/openvpn/wiki/AvoidRoutingConflicts
var avoidedSubnets = mustPrefixes(
"10.0.0.0/24", "10.0.1.0/24", "10.1.1.0/24", "10.1.10.0/24", "10.2.0.0/24",
"10.8.0.0/24", "10.10.1.0/24", "10.90.90.0/24", "10.100.1.0/24",
"10.255.255.0/24", "192.168.0.0/24", "192.168.1.0/24",
)
func mustPrefixes(s ...string) []netip.Prefix {
out := make([]netip.Prefix, len(s))
for i, v := range s {
out[i] = netip.MustParsePrefix(v)
}
return out
}
// hostNetworks returns the networks of all addresses configured on this host.
func hostNetworks() []netip.Prefix {
var out []netip.Prefix
addrs, _ := net.InterfaceAddrs()
for _, a := range addrs {
if n, ok := a.(*net.IPNet); ok {
if p, err := netip.ParsePrefix(n.String()); err == nil {
out = append(out, p.Masked())
}
}
}
return out
}
func hasGlobalIPv6() bool {
addrs, _ := net.InterfaceAddrs()
for _, a := range addrs {
if n, ok := a.(*net.IPNet); ok && n.IP.To4() == nil && n.IP.IsGlobalUnicast() && !n.IP.IsPrivate() {
return true
}
}
return false
}
// randomSubnet picks a random, unused /bits network from 10/8, then
// 172.16/12, then 192.168/16, like pivpn does.
func randomSubnet(bits int) (netip.Prefix, error) {
taken := append(append([]netip.Prefix{}, avoidedSubnets...), hostNetworks()...)
for _, pool := range mustPrefixes("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16") {
if bits < pool.Bits() {
continue
}
count := int64(1) << (bits - pool.Bits())
base := binary.BigEndian.Uint32(pool.Addr().AsSlice())
size := uint32(1) << (32 - bits)
for range 2000 {
n, err := rand.Int(rand.Reader, big.NewInt(count))
if err != nil {
return netip.Prefix{}, err
}
var b [4]byte
binary.BigEndian.PutUint32(b[:], base+uint32(n.Int64())*size)
cand := netip.PrefixFrom(netip.AddrFrom4(b), bits)
if !overlapsAny(cand, taken) {
return cand, nil
}
}
}
return netip.Prefix{}, errors.New("no free private IPv4 subnet found")
}
func overlapsAny(p netip.Prefix, list []netip.Prefix) bool {
for _, q := range list {
if p.Overlaps(q) {
return true
}
}
return false
}
func addrToU32(a netip.Addr) uint32 { return binary.BigEndian.Uint32(a.AsSlice()) }
func u32ToAddr(v uint32) netip.Addr {
var b [4]byte
binary.BigEndian.PutUint32(b[:], v)
return netip.AddrFrom4(b)
}
// serverIPv4 is the first host address of the tunnel network.
func serverIPv4(n netip.Prefix) netip.Addr { return n.Addr().Next() }
func lastAddr(n netip.Prefix) netip.Addr {
return u32ToAddr(addrToU32(n.Addr()) | (1<<(32-n.Bits()) - 1))
}
// nextFreeIPv4 returns the lowest unused peer address (server is .1).
func nextFreeIPv4(c *Config) (netip.Addr, error) {
n := netip.MustParsePrefix(c.Server.IPv4)
used := map[netip.Addr]bool{}
for _, p := range c.Peers {
if a, err := netip.ParseAddr(p.IPv4); err == nil {
used[a] = true
}
}
last := lastAddr(n)
for a := serverIPv4(n).Next(); a.Less(last); a = a.Next() {
if !used[a] {
return a, nil
}
}
return netip.Addr{}, badRequest("no free address left in %s", n)
}
// capacity is the number of peer addresses in the tunnel network.
func capacity(n netip.Prefix) int { return 1<<(32-n.Bits()) - 3 }
// peerIPv6 is the peer's IPv6 tunnel address: the one kept from pivpn, or
// the one mapped from its IPv4 address.
func peerIPv6(c *Config, p *Peer) netip.Addr {
if a, err := netip.ParseAddr(p.IPv6); err == nil {
return a
}
return mapIPv6(netip.MustParsePrefix(c.Server.IPv6), netip.MustParseAddr(p.IPv4))
}
// mapIPv6 puts the 32 bits of an IPv4 address into the low bits of the IPv6
// network: 10.84.12.8 in fd11:5ee:bad:c0de::/64 becomes fd11:5ee:bad:c0de::a54:c08.
func mapIPv6(v6net netip.Prefix, v4 netip.Addr) netip.Addr {
b := v6net.Addr().As16()
copy(b[12:], v4.AsSlice())
return netip.AddrFrom16(b)
}