Files
GHOSTWIRE/ipam.go
T
Daniel Redetzke 4793e8dfba GHOSTWIRE 0.1.2: WireGuard server manager with web UI and API
Single Go binary that manages a WireGuard server based on pivpn's defaults:
- config.json as the single source of truth, reconciled to the kernel via
  netlink, wgctrl and its own nftables table (NAT, forward, input)
- web interface (dashboard, peers, peer detail, add peer, server, settings)
  and a JSON API for the future iOS app, with session and API-token auth
- client private keys are never stored; configs and QR codes shown once
- per-peer traffic statistics in stats.json, logs in GHOSTWIRE.jsonl
- HTTPS via Let's Encrypt, self-signed, certificate files or off
- self-managing: install, update (restores the old binary on failure),
  uninstall and passwd subcommands; systemd unit generated by the binary

Tested end to end on Ubuntu 26.04 (kernel 7.0) at dev.redetzke.aero.

Claude-Session: https://claude.ai/code/session_01RAnLbyQZ5ZTA7KqwXP98nw
2026-10-03 16:54:37 +03:00

132 lines
3.7 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 }
// 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)
}