mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-08 18:46:34 -04:00
Compare commits
8 Commits
debug-dns
...
wg_bind_pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
940367e1c6 | ||
|
|
c80cb22cc0 | ||
|
|
d0e51dfc11 | ||
|
|
7e11842a5e | ||
|
|
1bf3e6feec | ||
|
|
c392fe44e4 | ||
|
|
fef24b4a4d | ||
|
|
bb1efe68aa |
2
go.mod
2
go.mod
@@ -19,7 +19,7 @@ require (
|
|||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.7.0
|
||||||
golang.org/x/sys v0.6.0
|
golang.org/x/sys v0.6.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
|
golang.zx2c4.com/wireguard v0.0.0-20230310135217-9e2f38602202
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
golang.zx2c4.com/wireguard/windows v0.5.1
|
||||||
google.golang.org/grpc v1.52.3
|
google.golang.org/grpc v1.52.3
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -887,6 +887,8 @@ golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+D
|
|||||||
golang.zx2c4.com/wireguard v0.0.0-20211129173154-2dd424e2d808/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
|
golang.zx2c4.com/wireguard v0.0.0-20211129173154-2dd424e2d808/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU=
|
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA=
|
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20230310135217-9e2f38602202 h1:KcD4X7IcoRdQpr9NSQpQpn5S4rUMIQaCdF90FOEj6KY=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20230310135217-9e2f38602202/go.mod h1:qc3aHNhM1Rc4hW2az896MjLVcxHvLbJ6LZc9MI7RTMY=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de h1:qDZ+lyO5jC9RNJ7ANJA0GWXk3pSn0Fu5SlcAIlgw+6w=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de h1:qDZ+lyO5jC9RNJ7ANJA0GWXk3pSn0Fu5SlcAIlgw+6w=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de/go.mod h1:Q2XNgour4QSkFj0BWCkVlW0HWJwQgNMsMahpSlI0Eno=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de/go.mod h1:Q2XNgour4QSkFj0BWCkVlW0HWJwQgNMsMahpSlI0Eno=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.1 h1:OnYw96PF+CsIMrqWo5QP3Q59q5hY1rFErk/yN3cS+JQ=
|
golang.zx2c4.com/wireguard/windows v0.5.1 h1:OnYw96PF+CsIMrqWo5QP3Q59q5hY1rFErk/yN3cS+JQ=
|
||||||
|
|||||||
@@ -1,95 +1,416 @@
|
|||||||
package bind
|
package bind
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/pion/stun"
|
"github.com/pion/stun"
|
||||||
"github.com/pion/transport/v2"
|
"github.com/pion/transport/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
wgConn "golang.zx2c4.com/wireguard/conn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ICEBind is the userspace implementation of WireGuard's conn.Bind interface using ice.UDPMux of the pion/ice library
|
var (
|
||||||
|
_ wgConn.Bind = (*ICEBind)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ICEBind implements Bind for all platforms except Windows.
|
||||||
type ICEBind struct {
|
type ICEBind struct {
|
||||||
// below fields, initialized on open
|
mu sync.Mutex // protects following fields
|
||||||
ipv4 net.PacketConn
|
ipv4 *net.UDPConn
|
||||||
udpMux *UniversalUDPMuxDefault
|
ipv6 *net.UDPConn
|
||||||
|
blackhole4 bool
|
||||||
|
blackhole6 bool
|
||||||
|
ipv4PC *ipv4.PacketConn
|
||||||
|
ipv6PC *ipv6.PacketConn
|
||||||
|
batchSize int
|
||||||
|
udpAddrPool sync.Pool
|
||||||
|
ipv4MsgsPool sync.Pool
|
||||||
|
ipv6MsgsPool sync.Pool
|
||||||
|
|
||||||
// below are fields initialized on creation
|
// NetBird related variables
|
||||||
transportNet transport.Net
|
transportNet transport.Net
|
||||||
mu sync.Mutex
|
udpMux *UniversalUDPMuxDefault
|
||||||
|
worker *worker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewICEBind create a new instance of ICEBind with a given transportNet function.
|
|
||||||
// The transportNet can be nil.
|
|
||||||
func NewICEBind(transportNet transport.Net) *ICEBind {
|
func NewICEBind(transportNet transport.Net) *ICEBind {
|
||||||
return &ICEBind{
|
b := &ICEBind{
|
||||||
transportNet: transportNet,
|
batchSize: wgConn.DefaultBatchSize,
|
||||||
mu: sync.Mutex{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
|
udpAddrPool: sync.Pool{
|
||||||
func (b *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) {
|
New: func() any {
|
||||||
b.mu.Lock()
|
return &net.UDPAddr{
|
||||||
defer b.mu.Unlock()
|
IP: make([]byte, 16),
|
||||||
if b.udpMux == nil {
|
}
|
||||||
return nil, fmt.Errorf("ICEBind has not been initialized yet")
|
},
|
||||||
}
|
|
||||||
|
|
||||||
return b.udpMux, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open creates a WireGuard socket and an instance of UDPMux that is used to glue up ICE and WireGuard for hole punching
|
|
||||||
func (b *ICEBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if b.ipv4 != nil {
|
|
||||||
return nil, 0, conn.ErrBindAlreadyOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
b.ipv4, _, err = listenNet("udp4", int(uport))
|
|
||||||
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.udpMux = NewUniversalUDPMuxDefault(UniversalUDPMuxParams{UDPConn: b.ipv4, Net: b.transportNet})
|
|
||||||
|
|
||||||
portAddr, err := netip.ParseAddrPort(b.ipv4.LocalAddr().String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("opened ICEBind on %s", b.ipv4.LocalAddr().String())
|
|
||||||
|
|
||||||
return []conn.ReceiveFunc{
|
|
||||||
b.makeReceiveIPv4(b.ipv4),
|
|
||||||
},
|
},
|
||||||
portAddr.Port(), nil
|
|
||||||
|
ipv4MsgsPool: sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
msgs := make([]ipv4.Message, wgConn.DefaultBatchSize)
|
||||||
|
for i := range msgs {
|
||||||
|
msgs[i].Buffers = make(net.Buffers, 1)
|
||||||
|
msgs[i].OOB = make([]byte, srcControlSize)
|
||||||
|
}
|
||||||
|
return &msgs
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ipv6MsgsPool: sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
msgs := make([]ipv6.Message, wgConn.DefaultBatchSize)
|
||||||
|
for i := range msgs {
|
||||||
|
msgs[i].Buffers = make(net.Buffers, 1)
|
||||||
|
msgs[i].OOB = make([]byte, srcControlSize)
|
||||||
|
}
|
||||||
|
return &msgs
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transportNet: transportNet,
|
||||||
|
}
|
||||||
|
b.worker = newWorker(b.handlePkgs)
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenNet(network string, port int) (net.PacketConn, int, error) {
|
type StdNetEndpoint struct {
|
||||||
c, err := net.ListenUDP(network, &net.UDPAddr{Port: port})
|
// AddrPort is the endpoint destination.
|
||||||
|
netip.AddrPort
|
||||||
|
// src is the current sticky source address and interface index, if supported.
|
||||||
|
src struct {
|
||||||
|
netip.Addr
|
||||||
|
ifidx int32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ wgConn.Bind = (*ICEBind)(nil)
|
||||||
|
_ wgConn.Endpoint = &StdNetEndpoint{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*ICEBind) ParseEndpoint(s string) (wgConn.Endpoint, error) {
|
||||||
|
e, err := netip.ParseAddrPort(s)
|
||||||
|
return asEndpoint(e), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) ClearSrc() {
|
||||||
|
e.src.ifidx = 0
|
||||||
|
e.src.Addr = netip.Addr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) DstIP() netip.Addr {
|
||||||
|
return e.AddrPort.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) SrcIP() netip.Addr {
|
||||||
|
return e.src.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) SrcIfidx() int32 {
|
||||||
|
return e.src.ifidx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) DstToBytes() []byte {
|
||||||
|
b, _ := e.AddrPort.MarshalBinary()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) DstToString() string {
|
||||||
|
return e.AddrPort.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) SrcToString() string {
|
||||||
|
return e.src.Addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenNet(network string, port int) (*net.UDPConn, int, error) {
|
||||||
|
conn, err := listenConfig().ListenPacket(context.Background(), network, ":"+strconv.Itoa(port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lAddr := c.LocalAddr()
|
// Retrieve port.
|
||||||
uAddr, err := net.ResolveUDPAddr(
|
laddr := conn.LocalAddr()
|
||||||
lAddr.Network(),
|
uaddr, err := net.ResolveUDPAddr(
|
||||||
lAddr.String(),
|
laddr.Network(),
|
||||||
|
laddr.String(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
return c, uAddr.Port, nil
|
return conn.(*net.UDPConn), uaddr.Port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) Open(uport uint16) ([]wgConn.ReceiveFunc, uint16, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var tries int
|
||||||
|
|
||||||
|
if s.ipv4 != nil || s.ipv6 != nil {
|
||||||
|
return nil, 0, wgConn.ErrBindAlreadyOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to open ipv4 and ipv6 listeners on the same port.
|
||||||
|
// If uport is 0, we can retry on failure.
|
||||||
|
again:
|
||||||
|
port := int(uport)
|
||||||
|
var v4conn, v6conn *net.UDPConn
|
||||||
|
|
||||||
|
v4conn, port, err = listenNet("udp4", port)
|
||||||
|
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen on the same port as we're using for ipv4.
|
||||||
|
v6conn, port, err = listenNet("udp6", port)
|
||||||
|
if uport == 0 && errors.Is(err, syscall.EADDRINUSE) && tries < 100 {
|
||||||
|
v4conn.Close()
|
||||||
|
tries++
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
v4conn.Close()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
var fns []wgConn.ReceiveFunc
|
||||||
|
if v4conn != nil {
|
||||||
|
fns = append(fns, s.receiveIPv4)
|
||||||
|
s.ipv4 = v4conn
|
||||||
|
}
|
||||||
|
if v6conn != nil {
|
||||||
|
fns = append(fns, s.receiveIPv6)
|
||||||
|
s.ipv6 = v6conn
|
||||||
|
}
|
||||||
|
if len(fns) == 0 {
|
||||||
|
return nil, 0, syscall.EAFNOSUPPORT
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ipv4PC = ipv4.NewPacketConn(s.ipv4)
|
||||||
|
s.ipv6PC = ipv6.NewPacketConn(s.ipv6)
|
||||||
|
|
||||||
|
s.udpMux = NewUniversalUDPMuxDefault(UniversalUDPMuxParams{UDPConn: s.ipv4, Net: s.transportNet})
|
||||||
|
return fns, uint16(port), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) receiveIPv4(buffs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) {
|
||||||
|
msgs := s.ipv4MsgsPool.Get().(*[]ipv4.Message)
|
||||||
|
defer s.ipv4MsgsPool.Put(msgs)
|
||||||
|
for i := range buffs {
|
||||||
|
(*msgs)[i].Buffers[0] = buffs[i]
|
||||||
|
}
|
||||||
|
numMsgs, err := s.ipv4PC.ReadBatch(*msgs, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.worker.doWork((*msgs)[:numMsgs], sizes, eps)
|
||||||
|
return numMsgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) receiveIPv6(buffs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) {
|
||||||
|
msgs := s.ipv6MsgsPool.Get().(*[]ipv6.Message)
|
||||||
|
defer s.ipv6MsgsPool.Put(msgs)
|
||||||
|
for i := range buffs {
|
||||||
|
(*msgs)[i].Buffers[0] = buffs[i]
|
||||||
|
}
|
||||||
|
numMsgs, err := s.ipv6PC.ReadBatch(*msgs, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for i := 0; i < numMsgs; i++ {
|
||||||
|
msg := &(*msgs)[i]
|
||||||
|
sizes[i] = msg.N
|
||||||
|
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
|
||||||
|
ep := asEndpoint(addrPort)
|
||||||
|
getSrcFromControl(msg.OOB, ep)
|
||||||
|
eps[i] = ep
|
||||||
|
}
|
||||||
|
return numMsgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) BatchSize() int {
|
||||||
|
return s.batchSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) Close() error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
var err1, err2 error
|
||||||
|
if s.ipv4 != nil {
|
||||||
|
err1 = s.ipv4.Close()
|
||||||
|
s.ipv4 = nil
|
||||||
|
}
|
||||||
|
if s.ipv6 != nil {
|
||||||
|
err2 = s.ipv6.Close()
|
||||||
|
s.ipv6 = nil
|
||||||
|
}
|
||||||
|
s.blackhole4 = false
|
||||||
|
s.blackhole6 = false
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) Send(buffs [][]byte, endpoint wgConn.Endpoint) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
blackhole := s.blackhole4
|
||||||
|
conn := s.ipv4
|
||||||
|
is6 := false
|
||||||
|
if endpoint.DstIP().Is6() {
|
||||||
|
blackhole = s.blackhole6
|
||||||
|
conn = s.ipv6
|
||||||
|
is6 = true
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
if blackhole {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
return syscall.EAFNOSUPPORT
|
||||||
|
}
|
||||||
|
if is6 {
|
||||||
|
return s.send6(s.ipv6PC, endpoint, buffs)
|
||||||
|
} else {
|
||||||
|
return s.send4(s.ipv4PC, endpoint, buffs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
|
||||||
|
func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.udpMux == nil {
|
||||||
|
return nil, fmt.Errorf("ICEBind has not been initialized yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.udpMux, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) send4(conn *ipv4.PacketConn, ep wgConn.Endpoint, buffs [][]byte) error {
|
||||||
|
ua := s.udpAddrPool.Get().(*net.UDPAddr)
|
||||||
|
as4 := ep.DstIP().As4()
|
||||||
|
copy(ua.IP, as4[:])
|
||||||
|
ua.IP = ua.IP[:4]
|
||||||
|
ua.Port = int(ep.(*StdNetEndpoint).Port())
|
||||||
|
msgs := s.ipv4MsgsPool.Get().(*[]ipv4.Message)
|
||||||
|
for i, buff := range buffs {
|
||||||
|
(*msgs)[i].Buffers[0] = buff
|
||||||
|
(*msgs)[i].Addr = ua
|
||||||
|
setSrcControl(&(*msgs)[i].OOB, ep.(*StdNetEndpoint))
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
start int
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
n, err = conn.WriteBatch((*msgs)[start:len(buffs)], 0)
|
||||||
|
if err != nil || n == len((*msgs)[start:len(buffs)]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start += n
|
||||||
|
}
|
||||||
|
s.udpAddrPool.Put(ua)
|
||||||
|
s.ipv4MsgsPool.Put(msgs)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) send6(conn *ipv6.PacketConn, ep wgConn.Endpoint, buffs [][]byte) error {
|
||||||
|
ua := s.udpAddrPool.Get().(*net.UDPAddr)
|
||||||
|
as16 := ep.DstIP().As16()
|
||||||
|
copy(ua.IP, as16[:])
|
||||||
|
ua.IP = ua.IP[:16]
|
||||||
|
ua.Port = int(ep.(*StdNetEndpoint).Port())
|
||||||
|
msgs := s.ipv6MsgsPool.Get().(*[]ipv6.Message)
|
||||||
|
for i, buff := range buffs {
|
||||||
|
(*msgs)[i].Buffers[0] = buff
|
||||||
|
(*msgs)[i].Addr = ua
|
||||||
|
setSrcControl(&(*msgs)[i].OOB, ep.(*StdNetEndpoint))
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
start int
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
n, err = conn.WriteBatch((*msgs)[start:len(buffs)], 0)
|
||||||
|
if err != nil || n == len((*msgs)[start:len(buffs)]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start += n
|
||||||
|
}
|
||||||
|
s.udpAddrPool.Put(ua)
|
||||||
|
s.ipv6MsgsPool.Put(msgs)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) filterOutStunMessages(buffers [][]byte, n int, addr net.Addr) (bool, error) {
|
||||||
|
for _, buffer := range buffers {
|
||||||
|
if !stun.IsMessage(buffer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := parseSTUNMessage(buffer[:n])
|
||||||
|
if err != nil {
|
||||||
|
buffer = []byte{}
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
muxErr := s.udpMux.HandleSTUNMessage(msg, addr)
|
||||||
|
if muxErr != nil {
|
||||||
|
log.Warnf("failed to handle packet")
|
||||||
|
}
|
||||||
|
buffer = []byte{}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) handlePkgs(msg *ipv4.Message) (int, *StdNetEndpoint) {
|
||||||
|
// todo: handle err
|
||||||
|
size := 0
|
||||||
|
ok, _ := s.filterOutStunMessages(msg.Buffers, msg.N, msg.Addr)
|
||||||
|
if !ok {
|
||||||
|
size = msg.N
|
||||||
|
}
|
||||||
|
|
||||||
|
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
|
||||||
|
ep := asEndpoint(addrPort)
|
||||||
|
getSrcFromControl(msg.OOB, ep)
|
||||||
|
return size, ep
|
||||||
|
}
|
||||||
|
|
||||||
|
// endpointPool contains a re-usable set of mapping from netip.AddrPort to Endpoint.
|
||||||
|
// This exists to reduce allocations: Putting a netip.AddrPort in an Endpoint allocates,
|
||||||
|
// but Endpoints are immutable, so we can re-use them.
|
||||||
|
var endpointPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return make(map[netip.AddrPort]*StdNetEndpoint)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// asEndpoint returns an Endpoint containing ap.
|
||||||
|
func asEndpoint(ap netip.AddrPort) *StdNetEndpoint {
|
||||||
|
m := endpointPool.Get().(map[netip.AddrPort]*StdNetEndpoint)
|
||||||
|
defer endpointPool.Put(m)
|
||||||
|
e, ok := m[ap]
|
||||||
|
if !ok {
|
||||||
|
e = &StdNetEndpoint{AddrPort: ap}
|
||||||
|
m[ap] = e
|
||||||
|
}
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSTUNMessage(raw []byte) (*stun.Message, error) {
|
func parseSTUNMessage(raw []byte) (*stun.Message, error) {
|
||||||
@@ -102,107 +423,3 @@ func parseSTUNMessage(raw []byte) (*stun.Message, error) {
|
|||||||
|
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ICEBind) makeReceiveIPv4(c net.PacketConn) conn.ReceiveFunc {
|
|
||||||
return func(buff []byte) (int, conn.Endpoint, error) {
|
|
||||||
n, endpoint, err := c.ReadFrom(buff)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
e, err := netip.ParseAddrPort(endpoint.String())
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
if !stun.IsMessage(buff) {
|
|
||||||
// WireGuard traffic
|
|
||||||
return n, (conn.StdNetEndpoint)(netip.AddrPortFrom(e.Addr(), e.Port())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := parseSTUNMessage(buff[:n])
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = b.udpMux.HandleSTUNMessage(msg, endpoint)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("failed to handle packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
// discard packets because they are STUN related
|
|
||||||
return 0, nil, nil //todo proper return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the WireGuard socket and UDPMux
|
|
||||||
func (b *ICEBind) Close() error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
var err1, err2 error
|
|
||||||
if b.ipv4 != nil {
|
|
||||||
c := b.ipv4
|
|
||||||
b.ipv4 = nil
|
|
||||||
err1 = c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.udpMux != nil {
|
|
||||||
m := b.udpMux
|
|
||||||
b.udpMux = nil
|
|
||||||
err2 = m.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err1 != nil {
|
|
||||||
return err1
|
|
||||||
}
|
|
||||||
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMark sets the mark for each packet sent through this Bind.
|
|
||||||
// This mark is passed to the kernel as the socket option SO_MARK.
|
|
||||||
func (b *ICEBind) SetMark(mark uint32) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send bytes to the remote endpoint (peer)
|
|
||||||
func (b *ICEBind) Send(buff []byte, endpoint conn.Endpoint) error {
|
|
||||||
|
|
||||||
nend, ok := endpoint.(conn.StdNetEndpoint)
|
|
||||||
if !ok {
|
|
||||||
return conn.ErrWrongEndpointType
|
|
||||||
}
|
|
||||||
addrPort := netip.AddrPort(nend)
|
|
||||||
_, err := b.ipv4.WriteTo(buff, &net.UDPAddr{
|
|
||||||
IP: addrPort.Addr().AsSlice(),
|
|
||||||
Port: int(addrPort.Port()),
|
|
||||||
Zone: addrPort.Addr().Zone(),
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseEndpoint creates a new endpoint from a string.
|
|
||||||
func (b *ICEBind) ParseEndpoint(s string) (ep conn.Endpoint, err error) {
|
|
||||||
e, err := netip.ParseAddrPort(s)
|
|
||||||
return asEndpoint(e), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// endpointPool contains a re-usable set of mapping from netip.AddrPort to Endpoint.
|
|
||||||
// This exists to reduce allocations: Putting a netip.AddrPort in an Endpoint allocates,
|
|
||||||
// but Endpoints are immutable, so we can re-use them.
|
|
||||||
var endpointPool = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return make(map[netip.AddrPort]conn.Endpoint)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// asEndpoint returns an Endpoint containing ap.
|
|
||||||
func asEndpoint(ap netip.AddrPort) conn.Endpoint {
|
|
||||||
m := endpointPool.Get().(map[netip.AddrPort]conn.Endpoint)
|
|
||||||
defer endpointPool.Put(m)
|
|
||||||
e, ok := m[ap]
|
|
||||||
if !ok {
|
|
||||||
e = conn.Endpoint(conn.StdNetEndpoint(ap))
|
|
||||||
m[ap] = e
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|||||||
36
iface/bind/controlfns.go
Normal file
36
iface/bind/controlfns.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// controlFn is the callback function signature from net.ListenConfig.Control.
|
||||||
|
// It is used to apply platform specific configuration to the socket prior to
|
||||||
|
// bind.
|
||||||
|
type controlFn func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
|
// controlFns is a list of functions that are called from the listen config
|
||||||
|
// that can apply socket options.
|
||||||
|
var controlFns = []controlFn{}
|
||||||
|
|
||||||
|
// listenConfig returns a net.ListenConfig that applies the controlFns to the
|
||||||
|
// socket prior to bind. This is used to apply socket buffer sizing and packet
|
||||||
|
// information OOB configuration for sticky sockets.
|
||||||
|
func listenConfig() *net.ListenConfig {
|
||||||
|
return &net.ListenConfig{
|
||||||
|
Control: func(network, address string, c syscall.RawConn) error {
|
||||||
|
for _, fn := range controlFns {
|
||||||
|
if err := fn(network, address, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
41
iface/bind/controlfns_linux.go
Normal file
41
iface/bind/controlfns_linux.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
controlFns = append(controlFns,
|
||||||
|
|
||||||
|
// Enable receiving of the packet information (IP_PKTINFO for IPv4,
|
||||||
|
// IPV6_PKTINFO for IPv6) that is used to implement sticky socket support.
|
||||||
|
func(network, address string, c syscall.RawConn) error {
|
||||||
|
var err error
|
||||||
|
switch network {
|
||||||
|
case "udp4":
|
||||||
|
c.Control(func(fd uintptr) {
|
||||||
|
err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_PKTINFO, 1)
|
||||||
|
})
|
||||||
|
case "udp6":
|
||||||
|
c.Control(func(fd uintptr) {
|
||||||
|
err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1)
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unhandled network: %s: %w", network, unix.EINVAL)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
28
iface/bind/controlfns_unix.go
Normal file
28
iface/bind/controlfns_unix.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//go:build !windows && !linux && !js
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
controlFns = append(controlFns,
|
||||||
|
func(network, address string, c syscall.RawConn) error {
|
||||||
|
var err error
|
||||||
|
if network == "udp6" {
|
||||||
|
c.Control(func(fd uintptr) {
|
||||||
|
err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
12
iface/bind/mark_default.go
Normal file
12
iface/bind/mark_default.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !linux && !openbsd && !freebsd
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
func (s *ICEBind) SetMark(mark uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
65
iface/bind/mark_unix.go
Normal file
65
iface/bind/mark_unix.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//go:build linux || openbsd || freebsd
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fwmarkIoctl int
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux", "android":
|
||||||
|
fwmarkIoctl = 36 /* unix.SO_MARK */
|
||||||
|
case "freebsd":
|
||||||
|
fwmarkIoctl = 0x1015 /* unix.SO_USER_COOKIE */
|
||||||
|
case "openbsd":
|
||||||
|
fwmarkIoctl = 0x1021 /* unix.SO_RTABLE */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICEBind) SetMark(mark uint32) error {
|
||||||
|
var operr error
|
||||||
|
if fwmarkIoctl == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s.ipv4 != nil {
|
||||||
|
fd, err := s.ipv4.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = fd.Control(func(fd uintptr) {
|
||||||
|
operr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, fwmarkIoctl, int(mark))
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
err = operr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.ipv6 != nil {
|
||||||
|
fd, err := s.ipv6.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = fd.Control(func(fd uintptr) {
|
||||||
|
operr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, fwmarkIoctl, int(mark))
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
err = operr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
28
iface/bind/sticky_default.go
Normal file
28
iface/bind/sticky_default.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import wgConn "golang.zx2c4.com/wireguard/conn"
|
||||||
|
|
||||||
|
// TODO: macOS, FreeBSD and other BSDs likely do support this feature set, but
|
||||||
|
// use alternatively named flags and need ports and require testing.
|
||||||
|
|
||||||
|
// getSrcFromControl parses the control for PKTINFO and if found updates ep with
|
||||||
|
// the source information found.
|
||||||
|
func getSrcFromControl(control []byte, ep *wgConn.StdNetEndpoint) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSrcControl parses the control for PKTINFO and if found updates ep with
|
||||||
|
// the source information found.
|
||||||
|
func setSrcControl(control *[]byte, ep *wgConn.StdNetEndpoint) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// srcControlSize returns the recommended buffer size for pooling sticky control
|
||||||
|
// data.
|
||||||
|
const srcControlSize = 0
|
||||||
111
iface/bind/sticky_linux.go
Normal file
111
iface/bind/sticky_linux.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getSrcFromControl parses the control for PKTINFO and if found updates ep with
|
||||||
|
// the source information found.
|
||||||
|
func getSrcFromControl(control []byte, ep *StdNetEndpoint) {
|
||||||
|
ep.ClearSrc()
|
||||||
|
|
||||||
|
var (
|
||||||
|
hdr unix.Cmsghdr
|
||||||
|
data []byte
|
||||||
|
rem []byte = control
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
for len(rem) > unix.SizeofCmsghdr {
|
||||||
|
hdr, data, rem, err = unix.ParseOneSocketControlMessage(control)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr.Level == unix.IPPROTO_IP &&
|
||||||
|
hdr.Type == unix.IP_PKTINFO {
|
||||||
|
|
||||||
|
info := pktInfoFromBuf[unix.Inet4Pktinfo](data)
|
||||||
|
ep.src.Addr = netip.AddrFrom4(info.Spec_dst)
|
||||||
|
ep.src.ifidx = info.Ifindex
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr.Level == unix.IPPROTO_IPV6 &&
|
||||||
|
hdr.Type == unix.IPV6_PKTINFO {
|
||||||
|
|
||||||
|
info := pktInfoFromBuf[unix.Inet6Pktinfo](data)
|
||||||
|
ep.src.Addr = netip.AddrFrom16(info.Addr)
|
||||||
|
ep.src.ifidx = int32(info.Ifindex)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pktInfoFromBuf returns type T populated from the provided buf via copy(). It
|
||||||
|
// panics if buf is of insufficient size.
|
||||||
|
func pktInfoFromBuf[T unix.Inet4Pktinfo | unix.Inet6Pktinfo](buf []byte) (t T) {
|
||||||
|
size := int(unsafe.Sizeof(t))
|
||||||
|
if len(buf) < size {
|
||||||
|
panic("pktInfoFromBuf: buffer too small")
|
||||||
|
}
|
||||||
|
copy(unsafe.Slice((*byte)(unsafe.Pointer(&t)), size), buf)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSrcControl parses the control for PKTINFO and if found updates ep with
|
||||||
|
// the source information found.
|
||||||
|
func setSrcControl(control *[]byte, ep *StdNetEndpoint) {
|
||||||
|
*control = (*control)[:cap(*control)]
|
||||||
|
if len(*control) < int(unsafe.Sizeof(unix.Cmsghdr{})) {
|
||||||
|
*control = (*control)[:0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ep.src.ifidx == 0 && !ep.SrcIP().IsValid() {
|
||||||
|
*control = (*control)[:0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*control) < srcControlSize {
|
||||||
|
*control = (*control)[:0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&(*control)[0]))
|
||||||
|
if ep.SrcIP().Is4() {
|
||||||
|
hdr.Level = unix.IPPROTO_IP
|
||||||
|
hdr.Type = unix.IP_PKTINFO
|
||||||
|
hdr.SetLen(unix.CmsgLen(unix.SizeofInet4Pktinfo))
|
||||||
|
|
||||||
|
info := (*unix.Inet4Pktinfo)(unsafe.Pointer(&(*control)[unix.SizeofCmsghdr]))
|
||||||
|
info.Ifindex = ep.src.ifidx
|
||||||
|
if ep.SrcIP().IsValid() {
|
||||||
|
info.Spec_dst = ep.SrcIP().As4()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hdr.Level = unix.IPPROTO_IPV6
|
||||||
|
hdr.Type = unix.IPV6_PKTINFO
|
||||||
|
hdr.Len = unix.SizeofCmsghdr + unix.SizeofInet6Pktinfo
|
||||||
|
|
||||||
|
info := (*unix.Inet6Pktinfo)(unsafe.Pointer(&(*control)[unix.SizeofCmsghdr]))
|
||||||
|
info.Ifindex = uint32(ep.src.ifidx)
|
||||||
|
if ep.SrcIP().IsValid() {
|
||||||
|
info.Addr = ep.SrcIP().As16()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*control = (*control)[:hdr.Len]
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcControlSize = unix.CmsgLen(unix.SizeofInet6Pktinfo)
|
||||||
207
iface/bind/sticky_linux_test.go
Normal file
207
iface/bind/sticky_linux_test.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_setSrcControl(t *testing.T) {
|
||||||
|
t.Run("IPv4", func(t *testing.T) {
|
||||||
|
ep := &StdNetEndpoint{
|
||||||
|
AddrPort: netip.MustParseAddrPort("127.0.0.1:1234"),
|
||||||
|
}
|
||||||
|
ep.src.Addr = netip.MustParseAddr("127.0.0.1")
|
||||||
|
ep.src.ifidx = 5
|
||||||
|
|
||||||
|
control := make([]byte, srcControlSize)
|
||||||
|
|
||||||
|
setSrcControl(&control, ep)
|
||||||
|
|
||||||
|
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||||
|
if hdr.Level != unix.IPPROTO_IP {
|
||||||
|
t.Errorf("unexpected level: %d", hdr.Level)
|
||||||
|
}
|
||||||
|
if hdr.Type != unix.IP_PKTINFO {
|
||||||
|
t.Errorf("unexpected type: %d", hdr.Type)
|
||||||
|
}
|
||||||
|
if hdr.Len != uint64(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet4Pktinfo{})))) {
|
||||||
|
t.Errorf("unexpected length: %d", hdr.Len)
|
||||||
|
}
|
||||||
|
info := (*unix.Inet4Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||||
|
if info.Spec_dst[0] != 127 || info.Spec_dst[1] != 0 || info.Spec_dst[2] != 0 || info.Spec_dst[3] != 1 {
|
||||||
|
t.Errorf("unexpected address: %v", info.Spec_dst)
|
||||||
|
}
|
||||||
|
if info.Ifindex != 5 {
|
||||||
|
t.Errorf("unexpected ifindex: %d", info.Ifindex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("IPv6", func(t *testing.T) {
|
||||||
|
ep := &StdNetEndpoint{
|
||||||
|
AddrPort: netip.MustParseAddrPort("[::1]:1234"),
|
||||||
|
}
|
||||||
|
ep.src.Addr = netip.MustParseAddr("::1")
|
||||||
|
ep.src.ifidx = 5
|
||||||
|
|
||||||
|
control := make([]byte, srcControlSize)
|
||||||
|
|
||||||
|
setSrcControl(&control, ep)
|
||||||
|
|
||||||
|
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||||
|
if hdr.Level != unix.IPPROTO_IPV6 {
|
||||||
|
t.Errorf("unexpected level: %d", hdr.Level)
|
||||||
|
}
|
||||||
|
if hdr.Type != unix.IPV6_PKTINFO {
|
||||||
|
t.Errorf("unexpected type: %d", hdr.Type)
|
||||||
|
}
|
||||||
|
if hdr.Len != uint64(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet6Pktinfo{})))) {
|
||||||
|
t.Errorf("unexpected length: %d", hdr.Len)
|
||||||
|
}
|
||||||
|
info := (*unix.Inet6Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||||
|
if info.Addr != ep.SrcIP().As16() {
|
||||||
|
t.Errorf("unexpected address: %v", info.Addr)
|
||||||
|
}
|
||||||
|
if info.Ifindex != 5 {
|
||||||
|
t.Errorf("unexpected ifindex: %d", info.Ifindex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ClearOnNoSrc", func(t *testing.T) {
|
||||||
|
control := make([]byte, srcControlSize)
|
||||||
|
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||||
|
hdr.Level = 1
|
||||||
|
hdr.Type = 2
|
||||||
|
hdr.Len = 3
|
||||||
|
|
||||||
|
setSrcControl(&control, &StdNetEndpoint{})
|
||||||
|
|
||||||
|
if len(control) != 0 {
|
||||||
|
t.Errorf("unexpected control: %v", control)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getSrcFromControl(t *testing.T) {
|
||||||
|
t.Run("IPv4", func(t *testing.T) {
|
||||||
|
control := make([]byte, srcControlSize)
|
||||||
|
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||||
|
hdr.Level = unix.IPPROTO_IP
|
||||||
|
hdr.Type = unix.IP_PKTINFO
|
||||||
|
hdr.Len = uint64(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet4Pktinfo{}))))
|
||||||
|
info := (*unix.Inet4Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||||
|
info.Spec_dst = [4]byte{127, 0, 0, 1}
|
||||||
|
info.Ifindex = 5
|
||||||
|
|
||||||
|
ep := &StdNetEndpoint{}
|
||||||
|
getSrcFromControl(control, ep)
|
||||||
|
|
||||||
|
if ep.src.Addr != netip.MustParseAddr("127.0.0.1") {
|
||||||
|
t.Errorf("unexpected address: %v", ep.src.Addr)
|
||||||
|
}
|
||||||
|
if ep.src.ifidx != 5 {
|
||||||
|
t.Errorf("unexpected ifindex: %d", ep.src.ifidx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("IPv6", func(t *testing.T) {
|
||||||
|
control := make([]byte, srcControlSize)
|
||||||
|
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||||
|
hdr.Level = unix.IPPROTO_IPV6
|
||||||
|
hdr.Type = unix.IPV6_PKTINFO
|
||||||
|
hdr.Len = uint64(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet6Pktinfo{}))))
|
||||||
|
info := (*unix.Inet6Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||||
|
info.Addr = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||||
|
info.Ifindex = 5
|
||||||
|
|
||||||
|
ep := &StdNetEndpoint{}
|
||||||
|
getSrcFromControl(control, ep)
|
||||||
|
|
||||||
|
if ep.SrcIP() != netip.MustParseAddr("::1") {
|
||||||
|
t.Errorf("unexpected address: %v", ep.SrcIP())
|
||||||
|
}
|
||||||
|
if ep.src.ifidx != 5 {
|
||||||
|
t.Errorf("unexpected ifindex: %d", ep.src.ifidx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("ClearOnEmpty", func(t *testing.T) {
|
||||||
|
control := make([]byte, srcControlSize)
|
||||||
|
ep := &StdNetEndpoint{}
|
||||||
|
ep.src.Addr = netip.MustParseAddr("::1")
|
||||||
|
ep.src.ifidx = 5
|
||||||
|
|
||||||
|
getSrcFromControl(control, ep)
|
||||||
|
if ep.SrcIP().IsValid() {
|
||||||
|
t.Errorf("unexpected address: %v", ep.src.Addr)
|
||||||
|
}
|
||||||
|
if ep.src.ifidx != 0 {
|
||||||
|
t.Errorf("unexpected ifindex: %d", ep.src.ifidx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_listenConfig(t *testing.T) {
|
||||||
|
t.Run("IPv4", func(t *testing.T) {
|
||||||
|
conn, err := listenConfig().ListenPacket(context.Background(), "udp4", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
sc, err := conn.(*net.UDPConn).SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
var i int
|
||||||
|
sc.Control(func(fd uintptr) {
|
||||||
|
i, err = unix.GetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_PKTINFO)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i != 1 {
|
||||||
|
t.Error("IP_PKTINFO not set!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Logf("listenConfig() does not set IPV6_RECVPKTINFO on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("IPv6", func(t *testing.T) {
|
||||||
|
conn, err := listenConfig().ListenPacket(context.Background(), "udp6", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sc, err := conn.(*net.UDPConn).SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
var i int
|
||||||
|
sc.Control(func(fd uintptr) {
|
||||||
|
i, err = unix.GetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i != 1 {
|
||||||
|
t.Error("IPV6_PKTINFO not set!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Logf("listenConfig() does not set IPV6_RECVPKTINFO on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
56
iface/bind/worker.go
Normal file
56
iface/bind/worker.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
wgConn "golang.zx2c4.com/wireguard/conn"
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo: add close function
|
||||||
|
type worker struct {
|
||||||
|
jobOffer chan int
|
||||||
|
numOfWorker int
|
||||||
|
|
||||||
|
jobFn func(msg *ipv4.Message) (int, *StdNetEndpoint)
|
||||||
|
|
||||||
|
messages []ipv4.Message
|
||||||
|
sizes []int
|
||||||
|
eps []wgConn.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWorker(jobFn func(msg *ipv4.Message) (int, *StdNetEndpoint)) *worker {
|
||||||
|
w := &worker{
|
||||||
|
jobOffer: make(chan int),
|
||||||
|
numOfWorker: runtime.NumCPU(),
|
||||||
|
jobFn: jobFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.populateWorkers()
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) doWork(messages []ipv4.Message, sizes []int, eps []wgConn.Endpoint) {
|
||||||
|
w.messages = messages
|
||||||
|
w.sizes = sizes
|
||||||
|
w.eps = eps
|
||||||
|
|
||||||
|
for i := 0; i < len(messages); i++ {
|
||||||
|
w.jobOffer <- i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) populateWorkers() {
|
||||||
|
for i := 0; i < w.numOfWorker; i++ {
|
||||||
|
go w.loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) loop() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msgPos := <-w.jobOffer:
|
||||||
|
w.sizes[msgPos], w.eps[msgPos] = w.jobFn(&w.messages[msgPos])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user