Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions client/iface/wgproxy/bind/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,32 +114,19 @@ func (p *ProxyBind) Pause() {
}

func (p *ProxyBind) RedirectAs(endpoint *net.UDPAddr) {
p.pausedCond.L.Lock()
p.paused = false

ep, err := addrToEndpoint(endpoint)
if err != nil {
log.Errorf("failed to convert endpoint address: %v", err)
} else {
p.wgCurrentUsed = ep
log.Errorf("failed to start package redirection: %v", err)
return
}

p.pausedCond.Signal()
p.pausedCond.L.Unlock()
}

func addrToEndpoint(addr *net.UDPAddr) (*bind.Endpoint, error) {
if addr == nil {
return nil, errors.New("nil address")
}
p.pausedCond.L.Lock()
p.paused = false

ip, ok := netip.AddrFromSlice(addr.IP)
if !ok {
return nil, fmt.Errorf("convert %s to netip.Addr", addr)
}
p.wgCurrentUsed = ep

addrPort := netip.AddrPortFrom(ip.Unmap(), uint16(addr.Port))
return &bind.Endpoint{AddrPort: addrPort}, nil
p.pausedCond.Signal()
p.pausedCond.L.Unlock()
}

func (p *ProxyBind) CloseConn() error {
Expand Down Expand Up @@ -225,3 +212,16 @@ func fakeAddress(peerAddress *net.UDPAddr) (*netip.AddrPort, error) {
netipAddr := netip.AddrPortFrom(fakeIP, uint16(peerAddress.Port))
return &netipAddr, nil
}

func addrToEndpoint(addr *net.UDPAddr) (*bind.Endpoint, error) {
if addr == nil {
return nil, fmt.Errorf("invalid address")
}
ip, ok := netip.AddrFromSlice(addr.IP)
if !ok {
return nil, fmt.Errorf("convert %s to netip.Addr", addr)
}

addrPort := netip.AddrPortFrom(ip.Unmap(), uint16(addr.Port))
return &bind.Endpoint{AddrPort: addrPort}, nil
}
82 changes: 65 additions & 17 deletions client/iface/wgproxy/ebpf/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ const (
)

var (
localHostNetIP = net.ParseIP("127.0.0.1")
localHostNetIPv4 = net.ParseIP("127.0.0.1")
localHostNetIPv6 = net.ParseIP("::1")

serializeOpts = gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
)

// WGEBPFProxy definition for proxy with EBPF support
Expand All @@ -40,7 +46,8 @@ type WGEBPFProxy struct {
turnConnMutex sync.Mutex

lastUsedPort uint16
rawConn net.PacketConn
rawConnIPv4 net.PacketConn
rawConnIPv6 net.PacketConn
conn transport.UDPConn

ctx context.Context
Expand All @@ -67,13 +74,20 @@ func (p *WGEBPFProxy) Listen() error {
return err
}

p.rawConn, err = rawsocket.PrepareSenderRawSocket()
// Prepare both IPv4 and IPv6 raw sockets
p.rawConnIPv4, p.rawConnIPv6, err = rawsocket.PrepareSenderRawSocket()
if err != nil {
return err
}

err = p.ebpfManager.LoadWgProxy(wgPorxyPort, p.localWGListenPort)
if err != nil {
if closeErr := p.rawConnIPv4.Close(); closeErr != nil {
log.Warnf("failed to close IPv4 raw socket: %v", closeErr)
}
if closeErr := p.rawConnIPv6.Close(); closeErr != nil {
log.Warnf("failed to close IPv6 raw socket: %v", closeErr)
}
return err
}

Expand Down Expand Up @@ -135,8 +149,16 @@ func (p *WGEBPFProxy) Free() error {
result = multierror.Append(result, err)
}

if err := p.rawConn.Close(); err != nil {
result = multierror.Append(result, err)
if p.rawConnIPv4 != nil {
if err := p.rawConnIPv4.Close(); err != nil {
result = multierror.Append(result, err)
}
}

if p.rawConnIPv6 != nil {
if err := p.rawConnIPv6.Close(); err != nil {
result = multierror.Append(result, err)
}
}
return nberrors.FormatErrorOrNil(result)
}
Expand Down Expand Up @@ -218,31 +240,57 @@ generatePort:
}

func (p *WGEBPFProxy) sendPkg(data []byte, endpointAddr *net.UDPAddr) error {
payload := gopacket.Payload(data)
ipH := &layers.IPv4{
DstIP: localHostNetIP,
SrcIP: endpointAddr.IP,
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolUDP,

var ipH gopacket.SerializableLayer
var networkLayer gopacket.NetworkLayer
var dstIP net.IP
var rawConn net.PacketConn

if endpointAddr.IP.To4() != nil {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// IPv4 path
ipv4 := &layers.IPv4{
DstIP: localHostNetIPv4,
SrcIP: endpointAddr.IP,
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolUDP,
}
ipH = ipv4
networkLayer = ipv4
dstIP = localHostNetIPv4
rawConn = p.rawConnIPv4
} else {
// IPv6 path
ipv6 := &layers.IPv6{
DstIP: localHostNetIPv6,
SrcIP: endpointAddr.IP,
Version: 6,
HopLimit: 64,
NextHeader: layers.IPProtocolUDP,
}
ipH = ipv6
networkLayer = ipv6
dstIP = localHostNetIPv6
rawConn = p.rawConnIPv6
}

udpH := &layers.UDP{
SrcPort: layers.UDPPort(endpointAddr.Port),
DstPort: layers.UDPPort(p.localWGListenPort),
}

err := udpH.SetNetworkLayerForChecksum(ipH)
if err != nil {
if err := udpH.SetNetworkLayerForChecksum(networkLayer); err != nil {
return fmt.Errorf("set network layer for checksum: %w", err)
}

layerBuffer := gopacket.NewSerializeBuffer()
payload := gopacket.Payload(data)

err = gopacket.SerializeLayers(layerBuffer, gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}, ipH, udpH, payload)
if err != nil {
if err := gopacket.SerializeLayers(layerBuffer, serializeOpts, ipH, udpH, payload); err != nil {
return fmt.Errorf("serialize layers: %w", err)
}
if _, err = p.rawConn.WriteTo(layerBuffer.Bytes(), &net.IPAddr{IP: localHostNetIP}); err != nil {

if _, err := rawConn.WriteTo(layerBuffer.Bytes(), &net.IPAddr{IP: dstIP}); err != nil {
return fmt.Errorf("write to raw conn: %w", err)
}
return nil
Expand Down
8 changes: 5 additions & 3 deletions client/iface/wgproxy/ebpf/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,14 @@ func (p *ProxyWrapper) Pause() {
}

func (p *ProxyWrapper) RedirectAs(endpoint *net.UDPAddr) {
if endpoint == nil || endpoint.IP == nil {
log.Errorf("failed to start package redirection, endpoint is nil")
return
}
p.pausedCond.L.Lock()
p.paused = false

if endpoint != nil && endpoint.IP != nil {
p.wgEndpointCurrentUsedAddr = endpoint
}
p.wgEndpointCurrentUsedAddr = endpoint

p.pausedCond.Signal()
p.pausedCond.L.Unlock()
Expand Down
61 changes: 55 additions & 6 deletions client/iface/wgproxy/rawsocket/rawsocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,90 @@ import (
"os"
"syscall"

log "github.com/sirupsen/logrus"

nbnet "github.com/netbirdio/netbird/client/net"
)

func PrepareSenderRawSocket() (net.PacketConn, error) {
// PrepareSenderRawSocket creates and configures raw sockets for sending both IPv4 and IPv6 packets.
// Returns IPv4 socket, IPv6 socket, and error.
func PrepareSenderRawSocket() (net.PacketConn, net.PacketConn, error) {
ipv4Conn, err := PrepareSenderRawSocketIPv4()
if err != nil {
return nil, nil, fmt.Errorf("prepare IPv4 raw socket: %w", err)
}

ipv6Conn, err := PrepareSenderRawSocketIPv6()
if err != nil {
if closeErr := ipv4Conn.Close(); closeErr != nil {
log.Warnf("failed to close IPv4 raw socket: %v", closeErr)
}
return nil, nil, fmt.Errorf("prepare IPv6 raw socket: %w", err)
}

return ipv4Conn, ipv6Conn, nil
}

// PrepareSenderRawSocketIPv4 creates and configures a raw socket for sending IPv4 packets
func PrepareSenderRawSocketIPv4() (net.PacketConn, error) {
return prepareSenderRawSocket(syscall.AF_INET, true)
}

// PrepareSenderRawSocketIPv6 creates and configures a raw socket for sending IPv6 packets
func PrepareSenderRawSocketIPv6() (net.PacketConn, error) {
return prepareSenderRawSocket(syscall.AF_INET6, false)
}

func prepareSenderRawSocket(family int, isIPv4 bool) (net.PacketConn, error) {
// Create a raw socket.
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
fd, err := syscall.Socket(family, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
if err != nil {
return nil, fmt.Errorf("creating raw socket failed: %w", err)
}

// Set the IP_HDRINCL option on the socket to tell the kernel that headers are included in the packet.
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
if err != nil {
return nil, fmt.Errorf("setting IP_HDRINCL failed: %w", err)
// Set the header include option on the socket to tell the kernel that headers are included in the packet.
// For IPv4, we need to set IP_HDRINCL. For IPv6, IPPROTO_RAW implies header inclusion.
Comment thread
pappz marked this conversation as resolved.
Outdated
if isIPv4 {
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
Comment thread
pappz marked this conversation as resolved.
Outdated
if err != nil {
if closeErr := syscall.Close(fd); closeErr != nil {
log.Warnf("failed to close raw socket fd: %v", closeErr)
}
return nil, fmt.Errorf("setting IP_HDRINCL failed: %w", err)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Bind the socket to the "lo" interface.
err = syscall.SetsockoptString(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, "lo")
if err != nil {
if closeErr := syscall.Close(fd); closeErr != nil {
log.Warnf("failed to close raw socket fd: %v", closeErr)
}
return nil, fmt.Errorf("binding to lo interface failed: %w", err)
}

// Set the fwmark on the socket.
err = nbnet.SetSocketOpt(fd)
if err != nil {
if closeErr := syscall.Close(fd); closeErr != nil {
log.Warnf("failed to close raw socket fd: %v", closeErr)
}
return nil, fmt.Errorf("setting fwmark failed: %w", err)
}

// Convert the file descriptor to a PacketConn.
file := os.NewFile(uintptr(fd), fmt.Sprintf("fd %d", fd))
if file == nil {
if closeErr := syscall.Close(fd); closeErr != nil {
log.Warnf("failed to close raw socket fd: %v", closeErr)
}
return nil, fmt.Errorf("converting fd to file failed")
}
packetConn, err := net.FilePacketConn(file)
if err != nil {
if closeErr := file.Close(); closeErr != nil {
log.Warnf("failed to close file: %v", closeErr)
}
return nil, fmt.Errorf("converting file to packet conn failed: %w", err)
}
Comment thread
pappz marked this conversation as resolved.

Expand Down
Loading
Loading