Improved receive.go

- Fixed configuration listen-port semantics
- Improved receive.go code for updating listen port
- Updated under load detection, how follows the kernel space implementation
- Fixed trie bug accidentally introduced in last commit
- Added interface name to log (format still subject to change)
- Can now configure the logging level using the LOG_LEVEL variable
- Begin porting netsh.sh tests
- A number of smaller changes
This commit is contained in:
Mathias Hall-Andersen 2017-08-11 16:18:20 +02:00
parent cba1d6585a
commit a4eff12d7f
16 changed files with 616 additions and 218 deletions

View file

@ -28,6 +28,7 @@ func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
// create lines // create lines
device.mutex.RLock() device.mutex.RLock()
device.net.mutex.RLock()
lines := make([]string, 0, 100) lines := make([]string, 0, 100)
send := func(line string) { send := func(line string) {
@ -38,7 +39,9 @@ func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
send("private_key=" + device.privateKey.ToHex()) send("private_key=" + device.privateKey.ToHex())
} }
if device.net.addr != nil {
send(fmt.Sprintf("listen_port=%d", device.net.addr.Port)) send(fmt.Sprintf("listen_port=%d", device.net.addr.Port))
}
for _, peer := range device.peers { for _, peer := range device.peers {
func() { func() {
@ -68,6 +71,7 @@ func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
}() }()
} }
device.net.mutex.RUnlock()
device.mutex.RUnlock() device.mutex.RUnlock()
// send lines // send lines
@ -84,38 +88,6 @@ func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
return nil return nil
} }
func updateUDPConn(device *Device) error {
var err error
netc := &device.net
netc.mutex.Lock()
// close existing connection
if netc.conn != nil {
netc.conn.Close()
netc.conn = nil
}
// open new existing connection
conn, err := net.ListenUDP("udp", netc.addr)
if err == nil {
netc.conn = conn
signalSend(device.signal.newUDPConn)
}
netc.mutex.Unlock()
return err
}
func closeUDPConn(device *Device) {
device.net.mutex.Lock()
device.net.conn = nil
device.net.mutex.Unlock()
println("send signal")
signalSend(device.signal.newUDPConn)
}
func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError { func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
scanner := bufio.NewScanner(socket) scanner := bufio.NewScanner(socket)
logInfo := device.log.Info logInfo := device.log.Info
@ -166,13 +138,22 @@ func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
logError.Println("Failed to set listen_port:", err) logError.Println("Failed to set listen_port:", err)
return &IPCError{Code: ipcErrorInvalid} return &IPCError{Code: ipcErrorInvalid}
} }
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
if err != nil {
logError.Println("Failed to set listen_port:", err)
return &IPCError{Code: ipcErrorInvalid}
}
netc := &device.net netc := &device.net
netc.mutex.Lock() netc.mutex.Lock()
if netc.addr.Port != int(port) { netc.addr = addr
netc.addr.Port = int(port)
}
netc.mutex.Unlock() netc.mutex.Unlock()
updateUDPConn(device) err = updateUDPConn(device)
if err != nil {
logError.Println("Failed to set listen_port:", err)
return &IPCError{Code: ipcErrorIO}
}
// TODO: Clear source address of all peers // TODO: Clear source address of all peers
@ -298,7 +279,7 @@ func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
logError.Println("Failed to get tun device status:", err) logError.Println("Failed to get tun device status:", err)
return &IPCError{Code: ipcErrorIO} return &IPCError{Code: ipcErrorIO}
} }
if atomic.LoadInt32(&device.isUp) == AtomicTrue && !dummy { if device.tun.isUp.Get() && !dummy {
peer.SendKeepAlive() peer.SendKeepAlive()
} }
} }

40
src/conn.go Normal file
View file

@ -0,0 +1,40 @@
package main
import (
"net"
)
func updateUDPConn(device *Device) error {
var err error
netc := &device.net
netc.mutex.Lock()
// close existing connection
if netc.conn != nil {
netc.conn.Close()
}
// open new connection
if device.tun.isUp.Get() {
conn, err := net.ListenUDP("udp", netc.addr)
if err == nil {
netc.conn = conn
signalSend(device.signal.newUDPConn)
}
}
netc.mutex.Unlock()
return err
}
func closeUDPConn(device *Device) {
netc := &device.net
netc.mutex.Lock()
if netc.conn != nil {
netc.conn.Close()
}
netc.mutex.Unlock()
signalSend(device.signal.newUDPConn)
}

View file

@ -29,8 +29,12 @@ const (
QueueOutboundSize = 1024 QueueOutboundSize = 1024
QueueInboundSize = 1024 QueueInboundSize = 1024
QueueHandshakeSize = 1024 QueueHandshakeSize = 1024
QueueHandshakeBusySize = QueueHandshakeSize / 8
MinMessageSize = MessageTransportSize // size of keep-alive MinMessageSize = MessageTransportSize // size of keep-alive
MaxMessageSize = ((1 << 16) - 1) + MessageTransportHeaderSize MaxMessageSize = ((1 << 16) - 1) + MessageTransportHeaderSize
MaxPeers = 1 << 16 MaxPeers = 1 << 16
) )
const (
UnderLoadQueueSize = QueueHandshakeSize / 8
UnderLoadAfterTime = time.Second // how long does the device remain under load after detected
)

View file

@ -5,20 +5,22 @@ import (
"runtime" "runtime"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
) )
type Device struct { type Device struct {
mtu int32
tun TUNDevice
log *Logger // collection of loggers for levels log *Logger // collection of loggers for levels
idCounter uint // for assigning debug ids to peers idCounter uint // for assigning debug ids to peers
fwMark uint32 fwMark uint32
tun struct {
device TUNDevice
isUp AtomicBool
mtu int32
}
pool struct { pool struct {
// pools objects for reuse
messageBuffers sync.Pool messageBuffers sync.Pool
} }
net struct { net struct {
// seperate for performance reasons
mutex sync.RWMutex mutex sync.RWMutex
addr *net.UDPAddr // UDP source address addr *net.UDPAddr // UDP source address
conn *net.UDPConn // UDP "connection" conn *net.UDPConn // UDP "connection"
@ -35,10 +37,9 @@ type Device struct {
} }
signal struct { signal struct {
stop chan struct{} // halts all go routines stop chan struct{} // halts all go routines
newUDPConn chan struct{} // a net.conn was set newUDPConn chan struct{} // a net.conn was set (consumed by the receiver routine)
} }
isUp int32 // atomic bool: interface is up underLoadUntil atomic.Value
underLoad int32 // atomic bool: device is under load
ratelimiter Ratelimiter ratelimiter Ratelimiter
peers map[NoisePublicKey]*Peer peers map[NoisePublicKey]*Peer
mac MACStateDevice mac MACStateDevice
@ -58,6 +59,23 @@ func removePeerUnsafe(device *Device, key NoisePublicKey) {
peer.Close() peer.Close()
} }
func (device *Device) IsUnderLoad() bool {
// check if currently under load
now := time.Now()
underLoad := len(device.queue.handshake) >= UnderLoadQueueSize
if underLoad {
device.underLoadUntil.Store(now.Add(time.Second))
return true
}
// check if recently under load
until := device.underLoadUntil.Load().(time.Time)
return until.After(now)
}
func (device *Device) SetPrivateKey(sk NoisePrivateKey) error { func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
device.mutex.Lock() device.mutex.Lock()
defer device.mutex.Unlock() defer device.mutex.Unlock()
@ -115,20 +133,13 @@ func NewDevice(tun TUNDevice, logLevel int) *Device {
device.mutex.Lock() device.mutex.Lock()
defer device.mutex.Unlock() defer device.mutex.Unlock()
device.tun = tun device.log = NewLogger(logLevel, "("+tun.Name()+") ")
device.log = NewLogger(logLevel)
device.peers = make(map[NoisePublicKey]*Peer) device.peers = make(map[NoisePublicKey]*Peer)
device.tun.device = tun
device.indices.Init() device.indices.Init()
device.ratelimiter.Init() device.ratelimiter.Init()
device.routingTable.Reset() device.routingTable.Reset()
device.underLoadUntil.Store(time.Time{})
// listen
device.net.mutex.Lock()
device.net.conn, _ = net.ListenUDP("udp", device.net.addr)
addr := device.net.conn.LocalAddr()
device.net.addr, _ = net.ResolveUDPAddr(addr.Network(), addr.String())
device.net.mutex.Unlock()
// setup pools // setup pools
@ -157,42 +168,43 @@ func NewDevice(tun TUNDevice, logLevel int) *Device {
go device.RoutineHandshake() go device.RoutineHandshake()
} }
go device.RoutineBusyMonitor()
go device.RoutineReadFromTUN()
go device.RoutineTUNEventReader() go device.RoutineTUNEventReader()
go device.RoutineReceiveIncomming()
go device.ratelimiter.RoutineGarbageCollector(device.signal.stop) go device.ratelimiter.RoutineGarbageCollector(device.signal.stop)
go device.RoutineReadFromTUN()
go device.RoutineReceiveIncomming()
return device return device
} }
func (device *Device) RoutineTUNEventReader() { func (device *Device) RoutineTUNEventReader() {
events := device.tun.Events() logInfo := device.log.Info
logError := device.log.Error logError := device.log.Error
events := device.tun.device.Events()
for event := range events { for event := range events {
if event&TUNEventMTUUpdate != 0 { if event&TUNEventMTUUpdate != 0 {
mtu, err := device.tun.MTU() mtu, err := device.tun.device.MTU()
if err != nil { if err != nil {
logError.Println("Failed to load updated MTU of device:", err) logError.Println("Failed to load updated MTU of device:", err)
} else { } else {
if mtu+MessageTransportSize > MaxMessageSize { if mtu+MessageTransportSize > MaxMessageSize {
mtu = MaxMessageSize - MessageTransportSize mtu = MaxMessageSize - MessageTransportSize
} }
atomic.StoreInt32(&device.mtu, int32(mtu)) atomic.StoreInt32(&device.tun.mtu, int32(mtu))
} }
} }
if event&TUNEventUp != 0 { if event&TUNEventUp != 0 {
println("handle 1") device.tun.isUp.Set(true)
atomic.StoreInt32(&device.isUp, AtomicTrue)
updateUDPConn(device) updateUDPConn(device)
println("handle 2", device.net.conn) logInfo.Println("Interface set up")
} }
if event&TUNEventDown != 0 { if event&TUNEventDown != 0 {
atomic.StoreInt32(&device.isUp, AtomicFalse) device.tun.isUp.Set(false)
closeUDPConn(device) closeUDPConn(device)
logInfo.Println("Interface set down")
} }
} }
} }
@ -224,6 +236,7 @@ func (device *Device) RemoveAllPeers() {
func (device *Device) Close() { func (device *Device) Close() {
device.RemoveAllPeers() device.RemoveAllPeers()
close(device.signal.stop) close(device.signal.stop)
closeUDPConn(device)
} }
func (device *Device) WaitChannel() chan struct{} { func (device *Device) WaitChannel() chan struct{} {

View file

@ -12,6 +12,7 @@ type DummyTUN struct {
name string name string
mtu int mtu int
packets chan []byte packets chan []byte
events chan TUNEvent
} }
func (tun *DummyTUN) Name() string { func (tun *DummyTUN) Name() string {
@ -27,6 +28,14 @@ func (tun *DummyTUN) Write(d []byte) (int, error) {
return len(d), nil return len(d), nil
} }
func (tun *DummyTUN) Close() error {
return nil
}
func (tun *DummyTUN) Events() chan TUNEvent {
return tun.events
}
func (tun *DummyTUN) Read(d []byte) (int, error) { func (tun *DummyTUN) Read(d []byte) (int, error) {
t := <-tun.packets t := <-tun.packets
copy(d, t) copy(d, t)

View file

@ -2,8 +2,8 @@ package main
import ( import (
"crypto/rand" "crypto/rand"
"encoding/binary"
"sync" "sync"
"unsafe"
) )
/* Index=0 is reserved for unset indecies /* Index=0 is reserved for unset indecies
@ -24,7 +24,8 @@ type IndexTable struct {
func randUint32() (uint32, error) { func randUint32() (uint32, error) {
var buff [4]byte var buff [4]byte
_, err := rand.Read(buff[:]) _, err := rand.Read(buff[:])
return *((*uint32)(unsafe.Pointer(&buff))), err value := binary.LittleEndian.Uint32(buff[:])
return value, err
} }
func (table *IndexTable) Init() { func (table *IndexTable) Init() {

View file

@ -19,7 +19,7 @@ type Logger struct {
Error *log.Logger Error *log.Logger
} }
func NewLogger(level int) *Logger { func NewLogger(level int, prepend string) *Logger {
output := os.Stdout output := os.Stdout
logger := new(Logger) logger := new(Logger)
@ -34,16 +34,16 @@ func NewLogger(level int) *Logger {
}() }()
logger.Debug = log.New(logDebug, logger.Debug = log.New(logDebug,
"DEBUG: ", "DEBUG: "+prepend,
log.Ldate|log.Ltime|log.Lshortfile, log.Ldate|log.Ltime|log.Lshortfile,
) )
logger.Info = log.New(logInfo, logger.Info = log.New(logInfo,
"INFO: ", "INFO: "+prepend,
log.Ldate|log.Ltime, log.Ldate|log.Ltime,
) )
logger.Error = log.New(logErr, logger.Error = log.New(logErr,
"ERROR: ", "ERROR: "+prepend,
log.Ldate|log.Ltime, log.Ldate|log.Ltime,
) )
return logger return logger

View file

@ -13,8 +13,8 @@ func TestMAC1(t *testing.T) {
defer dev1.Close() defer dev1.Close()
defer dev2.Close() defer dev2.Close()
peer1 := dev2.NewPeer(dev1.privateKey.publicKey()) peer1, _ := dev2.NewPeer(dev1.privateKey.publicKey())
peer2 := dev1.NewPeer(dev2.privateKey.publicKey()) peer2, _ := dev1.NewPeer(dev2.privateKey.publicKey())
assertEqual(t, peer1.mac.keyMAC1[:], dev1.mac.keyMAC1[:]) assertEqual(t, peer1.mac.keyMAC1[:], dev1.mac.keyMAC1[:])
assertEqual(t, peer2.mac.keyMAC1[:], dev2.mac.keyMAC1[:]) assertEqual(t, peer2.mac.keyMAC1[:], dev2.mac.keyMAC1[:])
@ -45,8 +45,8 @@ func TestMACs(t *testing.T) {
defer device1.Close() defer device1.Close()
defer device2.Close() defer device2.Close()
peer1 := device2.NewPeer(device1.privateKey.publicKey()) peer1, _ := device2.NewPeer(device1.privateKey.publicKey())
peer2 := device1.NewPeer(device2.privateKey.publicKey()) peer2, _ := device1.NewPeer(device2.privateKey.publicKey())
if addr.Port < 0 { if addr.Port < 0 {
return true return true

View file

@ -65,9 +65,23 @@ func main() {
return return
} }
// get log level (default: info)
logLevel := func() int {
switch os.Getenv("LOG_LEVEL") {
case "debug":
return LogLevelDebug
case "info":
return LogLevelInfo
case "error":
return LogLevelError
}
return LogLevelInfo
}()
// create wireguard device // create wireguard device
device := NewDevice(tun, LogLevelDebug) device := NewDevice(tun, logLevel)
logInfo := device.log.Info logInfo := device.log.Info
logError := device.log.Error logError := device.log.Error

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"sync/atomic"
"time" "time"
) )
@ -8,10 +9,26 @@ import (
* (since booleans are not natively supported by sync/atomic) * (since booleans are not natively supported by sync/atomic)
*/ */
const ( const (
AtomicFalse = iota AtomicFalse = int32(iota)
AtomicTrue AtomicTrue
) )
type AtomicBool struct {
flag int32
}
func (a *AtomicBool) Get() bool {
return atomic.LoadInt32(&a.flag) == AtomicTrue
}
func (a *AtomicBool) Set(val bool) {
flag := AtomicFalse
if val {
flag = AtomicTrue
}
atomic.StoreInt32(&a.flag, flag)
}
func min(a uint, b uint) uint { func min(a uint, b uint) uint {
if a > b { if a > b {
return b return b

View file

@ -31,8 +31,8 @@ func TestNoiseHandshake(t *testing.T) {
defer dev1.Close() defer dev1.Close()
defer dev2.Close() defer dev2.Close()
peer1 := dev2.NewPeer(dev1.privateKey.publicKey()) peer1, _ := dev2.NewPeer(dev1.privateKey.publicKey())
peer2 := dev1.NewPeer(dev2.privateKey.publicKey()) peer2, _ := dev1.NewPeer(dev2.privateKey.publicKey())
assertEqual( assertEqual(
t, t,

View file

@ -72,43 +72,6 @@ func (device *Device) addToHandshakeQueue(
} }
} }
/* Routine determining the busy state of the interface
*
* TODO: Under load for some time
*/
func (device *Device) RoutineBusyMonitor() {
samples := 0
interval := time.Second
for timer := time.NewTimer(interval); ; {
select {
case <-device.signal.stop:
return
case <-timer.C:
}
// compute busy heuristic
if len(device.queue.handshake) > QueueHandshakeBusySize {
samples += 1
} else if samples > 0 {
samples -= 1
}
samples %= 30
busy := samples > 5
// update busy state
if busy {
atomic.StoreInt32(&device.underLoad, AtomicTrue)
} else {
atomic.StoreInt32(&device.underLoad, AtomicFalse)
}
timer.Reset(interval)
}
}
func (device *Device) RoutineReceiveIncomming() { func (device *Device) RoutineReceiveIncomming() {
logDebug := device.log.Debug logDebug := device.log.Debug
@ -118,23 +81,26 @@ func (device *Device) RoutineReceiveIncomming() {
// wait for new conn // wait for new conn
var conn *net.UDPConn logDebug.Println("Waiting for udp socket")
select { select {
case <-device.signal.newUDPConn:
device.net.mutex.RLock()
conn = device.net.conn
device.net.mutex.RUnlock()
case <-device.signal.stop: case <-device.signal.stop:
return return
}
case <-device.signal.newUDPConn:
// fetch connection
device.net.mutex.RLock()
conn := device.net.conn
device.net.mutex.RUnlock()
if conn == nil { if conn == nil {
continue continue
} }
// receive datagrams until closed logDebug.Println("Listening for inbound packets")
// receive datagrams until conn is closed
buffer := device.GetMessageBuffer() buffer := device.GetMessageBuffer()
@ -142,7 +108,7 @@ func (device *Device) RoutineReceiveIncomming() {
// read next datagram // read next datagram
size, raddr, err := conn.ReadFromUDP(buffer[:]) // TODO: This is broken size, raddr, err := conn.ReadFromUDP(buffer[:]) // Blocks sometimes
if err != nil { if err != nil {
break break
@ -203,7 +169,7 @@ func (device *Device) RoutineReceiveIncomming() {
device.addToInboundQueue(device.queue.decryption, elem) device.addToInboundQueue(device.queue.decryption, elem)
device.addToInboundQueue(peer.queue.inbound, elem) device.addToInboundQueue(peer.queue.inbound, elem)
buffer = nil buffer = device.GetMessageBuffer()
continue continue
// otherwise it is a handshake related packet // otherwise it is a handshake related packet
@ -233,6 +199,7 @@ func (device *Device) RoutineReceiveIncomming() {
} }
} }
} }
}
func (device *Device) RoutineDecryption() { func (device *Device) RoutineDecryption() {
var elem *QueueInboundElement var elem *QueueInboundElement
@ -326,10 +293,11 @@ func (device *Device) RoutineHandshake() {
return return
} }
busy := atomic.LoadInt32(&device.underLoad) == AtomicTrue if device.IsUnderLoad() {
if busy {
if !device.mac.CheckMAC2(elem.packet, elem.source) { if !device.mac.CheckMAC2(elem.packet, elem.source) {
// construct cookie reply
sender := binary.LittleEndian.Uint32(elem.packet[4:8]) // "sender" always follows "type" sender := binary.LittleEndian.Uint32(elem.packet[4:8]) // "sender" always follows "type"
reply, err := device.CreateMessageCookieReply(elem.packet, sender, elem.source) reply, err := device.CreateMessageCookieReply(elem.packet, sender, elem.source)
if err != nil { if err != nil {
@ -347,6 +315,7 @@ func (device *Device) RoutineHandshake() {
} }
continue continue
} }
if !device.ratelimiter.Allow(elem.source.IP) { if !device.ratelimiter.Allow(elem.source.IP) {
continue continue
} }
@ -577,7 +546,7 @@ func (peer *Peer) RoutineSequentialReceiver() {
// write to tun // write to tun
atomic.AddUint64(&peer.stats.rxBytes, uint64(len(elem.packet))) atomic.AddUint64(&peer.stats.rxBytes, uint64(len(elem.packet)))
_, err := device.tun.Write(elem.packet) _, err := device.tun.device.Write(elem.packet)
device.PutMessageBuffer(elem.buffer) device.PutMessageBuffer(elem.buffer)
if err != nil { if err != nil {
logError.Println("Failed to write packet to TUN device:", err) logError.Println("Failed to write packet to TUN device:", err)

View file

@ -137,10 +137,6 @@ func (peer *Peer) SendBuffer(buffer []byte) (int, error) {
*/ */
func (device *Device) RoutineReadFromTUN() { func (device *Device) RoutineReadFromTUN() {
if device.tun == nil {
return
}
var elem *QueueOutboundElement var elem *QueueOutboundElement
logDebug := device.log.Debug logDebug := device.log.Debug
@ -155,9 +151,8 @@ func (device *Device) RoutineReadFromTUN() {
elem = device.NewOutboundElement() elem = device.NewOutboundElement()
} }
// TODO: THIS!
elem.packet = elem.buffer[MessageTransportHeaderSize:] elem.packet = elem.buffer[MessageTransportHeaderSize:]
size, err := device.tun.Read(elem.packet) size, err := device.tun.device.Read(elem.packet)
if err != nil { if err != nil {
logError.Println("Failed to read packet from TUN device:", err) logError.Println("Failed to read packet from TUN device:", err)
device.Close() device.Close()
@ -345,7 +340,7 @@ func (device *Device) RoutineEncryption() {
// pad content to MTU size // pad content to MTU size
mtu := int(atomic.LoadInt32(&device.mtu)) mtu := int(atomic.LoadInt32(&device.tun.mtu))
pad := len(elem.packet) % PaddingMultiple pad := len(elem.packet) % PaddingMultiple
if pad > 0 { if pad > 0 {
for i := 0; i < PaddingMultiple-pad && len(elem.packet) < mtu; i++ { for i := 0; i < PaddingMultiple-pad && len(elem.packet) < mtu; i++ {

350
src/tests/netns.sh Executable file
View file

@ -0,0 +1,350 @@
#!/bin/bash
# Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
# This script tests the below topology:
#
# ┌─────────────────────┐ ┌──────────────────────────────────┐ ┌─────────────────────┐
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
# │ │ │ │ │ │
# │┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐│
# ││ wg1 │───────────┼───┼────────────│ lo │────────────┼───┼───────────│ wg2 ││
# │├────────┴──────────┐│ │ ┌───────┴────────┴────────┐ │ │┌──────────┴────────┤│
# ││192.168.241.1/24 ││ │ │(ns1) (ns2) │ │ ││192.168.241.2/24 ││
# ││fd00::1/24 ││ │ │127.0.0.1:1 127.0.0.1:2│ │ ││fd00::2/24 ││
# │└───────────────────┘│ │ │[::]:1 [::]:2 │ │ │└───────────────────┘│
# └─────────────────────┘ │ └─────────────────────────┘ │ └─────────────────────┘
# └──────────────────────────────────┘
#
# After the topology is prepared we run a series of TCP/UDP iperf3 tests between the
# wireguard peers in $ns1 and $ns2. Note that $ns0 is the endpoint for the wg1
# interfaces in $ns1 and $ns2. See https://www.wireguard.com/netns/ for further
# details on how this is accomplished.
set -e
exec 3>&1
export WG_HIDE_KEYS=never
netns0="wg-test-$$-0"
netns1="wg-test-$$-1"
netns2="wg-test-$$-2"
program="../wireguard-go"
export LOG_LEVEL="debug"
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
pp() { pretty "" "$*"; "$@"; }
maybe_exec() { if [[ $BASHPID -eq $$ ]]; then "$@"; else exec "$@"; fi; }
n0() { pretty 0 "$*"; maybe_exec ip netns exec $netns0 "$@"; }
n1() { pretty 1 "$*"; maybe_exec ip netns exec $netns1 "$@"; }
n2() { pretty 2 "$*"; maybe_exec ip netns exec $netns2 "$@"; }
ip0() { pretty 0 "ip $*"; ip -n $netns0 "$@"; }
ip1() { pretty 1 "ip $*"; ip -n $netns1 "$@"; }
ip2() { pretty 2 "ip $*"; ip -n $netns2 "$@"; }
sleep() { read -t "$1" -N 0 || true; }
waitiperf() { pretty "${1//*-}" "wait for iperf:5201"; while [[ $(ss -N "$1" -tlp 'sport = 5201') != *iperf3* ]]; do sleep 0.1; done; }
waitncatudp() { pretty "${1//*-}" "wait for udp:1111"; while [[ $(ss -N "$1" -ulp 'sport = 1111') != *ncat* ]]; do sleep 0.1; done; }
waitiface() { pretty "${1//*-}" "wait for $2 to come up"; ip netns exec "$1" bash -c "while [[ \$(< \"/sys/class/net/$2/operstate\") != up ]]; do read -t .1 -N 0 || true; done;"; }
cleanup() {
n0 wg show
set +e
exec 2>/dev/null
printf "$orig_message_cost" > /proc/sys/net/core/message_cost
ip0 link del dev wg1
ip1 link del dev wg1
ip2 link del dev wg1
local to_kill="$(ip netns pids $netns0) $(ip netns pids $netns1) $(ip netns pids $netns2)"
[[ -n $to_kill ]] && kill $to_kill
pp ip netns del $netns1
pp ip netns del $netns2
pp ip netns del $netns0
exit
}
orig_message_cost="$(< /proc/sys/net/core/message_cost)"
trap cleanup EXIT
printf 0 > /proc/sys/net/core/message_cost
ip netns del $netns0 2>/dev/null || true
ip netns del $netns1 2>/dev/null || true
ip netns del $netns2 2>/dev/null || true
pp ip netns add $netns0
pp ip netns add $netns1
pp ip netns add $netns2
ip0 link set up dev lo
# ip0 link add dev wg1 type wireguard
n0 $program -f wg1 &
sleep 1
ip0 link set wg1 netns $netns1
# ip0 link add dev wg1 type wireguard
n0 $program -f wg2 &
sleep 1
ip0 link set wg2 netns $netns2
key1="$(pp wg genkey)"
key2="$(pp wg genkey)"
pub1="$(pp wg pubkey <<<"$key1")"
pub2="$(pp wg pubkey <<<"$key2")"
psk="$(pp wg genpsk)"
[[ -n $key1 && -n $key2 && -n $psk ]]
configure_peers() {
ip1 addr add 192.168.241.1/24 dev wg1
ip1 addr add fd00::1/24 dev wg1
ip2 addr add 192.168.241.2/24 dev wg2
ip2 addr add fd00::2/24 dev wg2
n0 wg set wg1 \
private-key <(echo "$key1") \
listen-port 10000 \
peer "$pub2" \
preshared-key <(echo "$psk") \
allowed-ips 192.168.241.2/32,fd00::2/128
n0 wg set wg2 \
private-key <(echo "$key2") \
listen-port 20000 \
peer "$pub1" \
preshared-key <(echo "$psk") \
allowed-ips 192.168.241.1/32,fd00::1/128
n0 wg showconf wg1
n0 wg showconf wg2
ip1 link set up dev wg1
ip2 link set up dev wg2
}
configure_peers
tests() {
# Ping over IPv4
n2 ping -c 10 -f -W 1 192.168.241.1
n1 ping -c 10 -f -W 1 192.168.241.2
# Ping over IPv6
n2 ping6 -c 10 -f -W 1 fd00::1
n1 ping6 -c 10 -f -W 1 fd00::2
# TCP over IPv4
n2 iperf3 -s -1 -B 192.168.241.2 &
waitiperf $netns2
n1 iperf3 -Z -n 1G -c 192.168.241.2
# TCP over IPv6
n1 iperf3 -s -1 -B fd00::1 &
waitiperf $netns1
n2 iperf3 -Z -n 1G -c fd00::1
# UDP over IPv4
n1 iperf3 -s -1 -B 192.168.241.1 &
waitiperf $netns1
n2 iperf3 -Z -n 1G -b 0 -u -c 192.168.241.1
# UDP over IPv6
n2 iperf3 -s -1 -B fd00::2 &
waitiperf $netns2
n1 iperf3 -Z -n 1G -b 0 -u -c fd00::2
}
[[ $(ip1 link show dev wg1) =~ mtu\ ([0-9]+) ]] && orig_mtu="${BASH_REMATCH[1]}"
big_mtu=$(( 34816 - 1500 + $orig_mtu ))
# Test using IPv4 as outer transport
n0 wg set wg1 peer "$pub2" endpoint 127.0.0.1:20000
n0 wg set wg2 peer "$pub1" endpoint 127.0.0.1:10000
n0 wg show
# Before calling tests, we first make sure that the stats counters are working
n2 ping -c 10 -f -W 1 192.168.241.1
{ read _; read _; read _; read rx_bytes _; read _; read tx_bytes _; } < <(ip2 -stats link show dev wg2)
[[ $rx_bytes -ge 932 && $tx_bytes -ge 1516 && $rx_bytes -lt 2500 && $rx_bytes -lt 2500 ]]
tests
ip1 link set wg1 mtu $big_mtu
ip2 link set wg2 mtu $big_mtu
tests
ip1 link set wg1 mtu $orig_mtu
ip2 link set wg2 mtu $orig_mtu
# Test using IPv6 as outer transport
n0 wg set wg1 peer "$pub2" endpoint [::1]:20000
n0 wg set wg2 peer "$pub1" endpoint [::1]:10000
tests
ip1 link set wg1 mtu $big_mtu
ip2 link set wg2 mtu $big_mtu
tests
ip1 link set wg1 mtu $orig_mtu
ip2 link set wg2 mtu $orig_mtu
# Test using IPv4 that roaming works
ip0 -4 addr del 127.0.0.1/8 dev lo
ip0 -4 addr add 127.212.121.99/8 dev lo
n0 wg set wg1 listen-port 9999
n0 wg set wg1 peer "$pub2" endpoint 127.0.0.1:20000
n1 ping6 -W 1 -c 1 fd00::20000
[[ $(n2 wg show wg2 endpoints) == "$pub1 127.212.121.99:9999" ]]
# Test using IPv6 that roaming works
n1 wg set wg1 listen-port 9998
n1 wg set wg1 peer "$pub2" endpoint [::1]:20000
n1 ping -W 1 -c 1 192.168.241.2
[[ $(n2 wg show wg2 endpoints) == "$pub1 [::1]:9998" ]]
# Test that crypto-RP filter works
n1 wg set wg1 peer "$pub2" allowed-ips 192.168.241.0/24
exec 4< <(n1 ncat -l -u -p 1111)
nmap_pid=$!
waitncatudp $netns1
n2 ncat -u 192.168.241.1 1111 <<<"X"
read -r -N 1 -t 1 out <&4 && [[ $out == "X" ]]
kill $nmap_pid
more_specific_key="$(pp wg genkey | pp wg pubkey)"
n0 wg set wg1 peer "$more_specific_key" allowed-ips 192.168.241.2/32
n0 wg set wg2 listen-port 9997
exec 4< <(n1 ncat -l -u -p 1111)
nmap_pid=$!
waitncatudp $netns1
n2 ncat -u 192.168.241.1 1111 <<<"X"
! read -r -N 1 -t 1 out <&4
kill $nmap_pid
n0 wg set wg1 peer "$more_specific_key" remove
[[ $(n1 wg show wg1 endpoints) == "$pub2 [::1]:9997" ]]
ip1 link del wg1
ip2 link del wg2
# Test using NAT. We now change the topology to this:
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────────────┐ ┌────────────────────────────────────────┐
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
# │ │ │ │ │ │
# │ ┌─────┐ ┌─────┐ │ │ ┌──────┐ ┌──────┐ │ │ ┌─────┐ ┌─────┐ │
# │ │ wg1 │─────────────│vethc│───────────┼────┼────│vethrc│ │vethrs│──────────────┼─────┼──│veths│────────────│ wg2 │ │
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├──────┴─────────┐ ├──────┴────────────┐ │ │ ├─────┴──────────┐ ├─────┴──────────┐ │
# │ │192.168.241.1/24│ │192.168.1.100/24││ │ │192.168.1.100/24│ │10.0.0.1/24 │ │ │ │10.0.0.100/24 │ │192.168.241.2/24│ │
# │ │fd00::1/24 │ │ ││ │ │ │ │SNAT:192.168.1.0/24│ │ │ │ │ │fd00::2/24 │ │
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └───────────────────┘ │ │ └────────────────┘ └────────────────┘ │
# └────────────────────────────────────────┘ └────────────────────────────────────────────────┘ └────────────────────────────────────────┘
# ip1 link add dev wg1 type wireguard
# ip2 link add dev wg1 type wireguard
n1 $program wg1
n2 $program wg2
configure_peers
ip0 link add vethrc type veth peer name vethc
ip0 link add vethrs type veth peer name veths
ip0 link set vethc netns $netns1
ip0 link set veths netns $netns2
ip0 link set vethrc up
ip0 link set vethrs up
ip0 addr add 192.168.1.1/24 dev vethrc
ip0 addr add 10.0.0.1/24 dev vethrs
ip1 addr add 192.168.1.100/24 dev vethc
ip1 link set vethc up
ip1 route add default via 192.168.1.1
ip2 addr add 10.0.0.100/24 dev veths
ip2 link set veths up
waitiface $netns0 vethrc
waitiface $netns0 vethrs
waitiface $netns1 vethc
waitiface $netns2 veths
n0 bash -c 'printf 1 > /proc/sys/net/ipv4/ip_forward'
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout'
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream'
n0 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 10.0.0.0/24 -j SNAT --to 10.0.0.1
n0 wg set wg1 peer "$pub2" endpoint 10.0.0.100:20000 persistent-keepalive 1
n1 ping -W 1 -c 1 192.168.241.2
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n2 wg show wg2 endpoints) == "$pub1 10.0.0.1:10000" ]]
# Demonstrate n2 can still send packets to n1, since persistent-keepalive will prevent connection tracking entry from expiring (to see entries: `n0 conntrack -L`).
pp sleep 3
n2 ping -W 1 -c 1 192.168.241.1
n0 iptables -t nat -F
ip0 link del vethrc
ip0 link del vethrs
ip1 link del wg1
ip2 link del wg2
# Test that saddr routing is sticky but not too sticky, changing to this topology:
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
# │ $ns1 namespace │ │ $ns2 namespace │
# │ │ │ │
# │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │
# │ │ wg1 │─────────────│veth1│───────────┼────┼──│veth2│────────────│ wg2 │ │
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├─────┴──────────┐ ├─────┴──────────┐ │
# │ │192.168.241.1/24│ │10.0.0.1/24 ││ │ │10.0.0.2/24 │ │192.168.241.2/24│ │
# │ │fd00::1/24 │ │fd00:aa::1/96 ││ │ │fd00:aa::2/96 │ │fd00::2/24 │ │
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └────────────────┘ │
# └────────────────────────────────────────┘ └────────────────────────────────────────┘
# ip1 link add dev wg1 type wireguard
# ip2 link add dev wg1 type wireguard
n1 $program wg1
n2 $program wg1
configure_peers
ip1 link add veth1 type veth peer name veth2
ip1 link set veth2 netns $netns2
n1 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth1/accept_dad'
n2 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth2/accept_dad'
n1 bash -c 'printf 1 > /proc/sys/net/ipv4/conf/veth1/promote_secondaries'
# First we check that we aren't overly sticky and can fall over to new IPs when old ones are removed
ip1 addr add 10.0.0.1/24 dev veth1
ip1 addr add fd00:aa::1/96 dev veth1
ip2 addr add 10.0.0.2/24 dev veth2
ip2 addr add fd00:aa::2/96 dev veth2
ip1 link set veth1 up
ip2 link set veth2 up
waitiface $netns1 veth1
waitiface $netns2 veth2
n0 wg set wg1 peer "$pub2" endpoint 10.0.0.2:20000
n1 ping -W 1 -c 1 192.168.241.2
ip1 addr add 10.0.0.10/24 dev veth1
ip1 addr del 10.0.0.1/24 dev veth1
n1 ping -W 1 -c 1 192.168.241.2
n0 wg set wg1 peer "$pub2" endpoint [fd00:aa::2]:20000
n1 ping -W 1 -c 1 192.168.241.2
ip1 addr add fd00:aa::10/96 dev veth1
ip1 addr del fd00:aa::1/96 dev veth1
n1 ping -W 1 -c 1 192.168.241.2
# Now we show that we can successfully do reply to sender routing
ip1 link set veth1 down
ip2 link set veth2 down
ip1 addr flush dev veth1
ip2 addr flush dev veth2
ip1 addr add 10.0.0.1/24 dev veth1
ip1 addr add 10.0.0.2/24 dev veth1
ip1 addr add fd00:aa::1/96 dev veth1
ip1 addr add fd00:aa::2/96 dev veth1
ip2 addr add 10.0.0.3/24 dev veth2
ip2 addr add fd00:aa::3/96 dev veth2
ip1 link set veth1 up
ip2 link set veth2 up
waitiface $netns1 veth1
waitiface $netns2 veth2
n0 wg set wg2 peer "$pub1" endpoint 10.0.0.1:10000
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n0 wg show wg2 endpoints) == "$pub1 10.0.0.1:10000" ]]
n0 wg set wg2 peer "$pub1" endpoint [fd00:aa::1]:10000
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n0 wg show wg2 endpoints) == "$pub1 [fd00:aa::1]:10000" ]]
n0 wg set wg2 peer "$pub1" endpoint 10.0.0.2:10000
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n0 wg show wg2 endpoints) == "$pub1 10.0.0.2:10000" ]]
n0 wg set wg2 peer "$pub1" endpoint [fd00:aa::2]:10000
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n0 wg show wg2 endpoints) == "$pub1 [fd00:aa::2]:10000" ]]
ip1 link del veth1
ip1 link del wg1
ip2 link del wg2

View file

@ -38,7 +38,7 @@ type Trie struct {
*/ */
func commonBits(ip1 []byte, ip2 []byte) uint { func commonBits(ip1 []byte, ip2 []byte) uint {
var i uint var i uint
size := uint(len(ip1)) / 4 size := uint(len(ip1))
for i = 0; i < size; i++ { for i = 0; i < size; i++ {
v := ip1[i] ^ ip2[i] v := ip1[i] ^ ip2[i]

View file

@ -44,7 +44,12 @@ func (l *UAPIListener) Accept() (net.Conn, error) {
} }
func (l *UAPIListener) Close() error { func (l *UAPIListener) Close() error {
return l.listener.Close() err1 := unix.Close(l.inotifyFd)
err2 := l.listener.Close()
if err1 != nil {
return err1
}
return err2
} }
func (l *UAPIListener) Addr() net.Addr { func (l *UAPIListener) Addr() net.Addr {