Skip to content
This repository has been archived by the owner on Feb 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #178 from stealthrocket/network
Browse files Browse the repository at this point in the history
add internal/network package
  • Loading branch information
achille-roussel authored Jul 20, 2023
2 parents 2e63287 + 34c1698 commit 15dc18f
Show file tree
Hide file tree
Showing 24 changed files with 3,263 additions and 4 deletions.
3 changes: 2 additions & 1 deletion internal/htls/htls.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package htls

const Level = 0x74696d65
const Option = 1

const ServerName = 1
23 changes: 23 additions & 0 deletions internal/ipam/ipam.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
// Package ipam contains types to implement IP address management on IPv4 and
// IPv6 networks.
package ipam

import "net/netip"

// Pool is an interface implemented by the IPv4Pool and IPv6Pool types to
// abstract the type of IP addresses that are managed by the pool.
type Pool interface {
// Obtains the next IP address, or returns nil if the pool was exhausted.
GetAddr() (netip.Addr, bool)
// Returns an IP address to the pool. The ip address must have been obtained
// by a previous call to GetIP or the method panics.
PutAddr(netip.Addr)
}

// NewPool constructs a pool of IP addresses for the network passed as argument.
func NewPool(prefix netip.Prefix) Pool {
addr := prefix.Addr()
bits := prefix.Bits()
if addr.Is4() {
return NewIPv4Pool(addr.As4(), bits)
} else {
return NewIPv6Pool(addr.As16(), bits)
}
}
9 changes: 9 additions & 0 deletions internal/ipam/ipv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ func (p *IPv4Pool) Reset(ip IPv4, nbits int) {
p.bits.clear()
}

func (p *IPv4Pool) GetAddr() (netip.Addr, bool) {
ip, ok := p.Get()
return netip.AddrFrom4(ip), ok
}

func (p *IPv4Pool) Get() (IPv4, bool) {
i := p.bits.findFirstZeroBit()
a := p.base.add(i)
Expand All @@ -82,6 +87,10 @@ func (p *IPv4Pool) Get() (IPv4, bool) {
return p.base.add(i), true
}

func (p *IPv4Pool) PutAddr(ip netip.Addr) {
p.Put(ip.As4())
}

func (p *IPv4Pool) Put(ip IPv4) {
i := ip.sub(p.base)
if !p.bits.has(i) {
Expand Down
9 changes: 9 additions & 0 deletions internal/ipam/ipv6.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ func (p *IPv6Pool) Reset(ip IPv6, nbits int) {
p.bits.clear()
}

func (p *IPv6Pool) GetAddr() (netip.Addr, bool) {
ip, ok := p.Get()
return netip.AddrFrom16(ip), ok
}

func (p *IPv6Pool) Get() (IPv6, bool) {
i := p.bits.findFirstZeroBit()
a := p.base.add(i)
Expand All @@ -108,6 +113,10 @@ func (p *IPv6Pool) Get() (IPv6, bool) {
return p.base.add(i), true
}

func (p *IPv6Pool) PutAddr(ip netip.Addr) {
p.Put(ip.As16())
}

func (p *IPv6Pool) Put(ip IPv6) {
i := ip.sub(p.base)
if !p.bits.has(i) {
Expand Down
47 changes: 47 additions & 0 deletions internal/network/host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package network

import "net"

func Host() Namespace { return hostNamespace{} }

type hostNamespace struct{}

func (hostNamespace) InterfaceByIndex(index int) (Interface, error) {
i, err := net.InterfaceByIndex(index)
if err != nil {
return nil, err
}
return hostInterface{i}, nil
}

func (hostNamespace) InterfaceByName(name string) (Interface, error) {
i, err := net.InterfaceByName(name)
if err != nil {
return nil, err
}
return hostInterface{i}, nil
}

func (hostNamespace) Interfaces() ([]Interface, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
hostInterfaces := make([]Interface, len(interfaces))
for i := range interfaces {
hostInterfaces[i] = hostInterface{&interfaces[i]}
}
return hostInterfaces, nil
}

type hostInterface struct{ *net.Interface }

func (i hostInterface) Index() int { return i.Interface.Index }

func (i hostInterface) MTU() int { return i.Interface.MTU }

func (i hostInterface) Name() string { return i.Interface.Name }

func (i hostInterface) HardwareAddr() net.HardwareAddr { return i.Interface.HardwareAddr }

func (i hostInterface) Flags() net.Flags { return i.Interface.Flags }
44 changes: 44 additions & 0 deletions internal/network/host_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package network

import (
"syscall"

"golang.org/x/sys/unix"
)

func (hostNamespace) Socket(family Family, socktype Socktype, protocol Protocol) (Socket, error) {
syscall.ForkLock.RLock()
defer syscall.ForkLock.RUnlock()
fd, err := ignoreEINTR2(func() (int, error) {
return unix.Socket(int(family), int(socktype), int(protocol))
})
if err != nil {
return nil, err
}
if err := setCloseOnExecAndNonBlocking(fd); err != nil {
unix.Close(fd)
return nil, err
}
return newHostSocket(fd, family, socktype), nil
}

func (s *hostSocket) Accept() (Socket, Sockaddr, error) {
fd := s.fd.acquire()
if fd < 0 {
return nil, nil, EBADF
}
defer s.fd.release(fd)
syscall.ForkLock.RLock()
defer syscall.ForkLock.RUnlock()
conn, addr, err := ignoreEINTR3(func() (int, Sockaddr, error) {
return unix.Accept(fd)
})
if err != nil {
return nil, nil, err
}
if err := setCloseOnExecAndNonBlocking(conn); err != nil {
unix.Close(conn)
return nil, nil, err
}
return newHostSocket(conn, s.family, s.socktype), addr, nil
}
28 changes: 28 additions & 0 deletions internal/network/host_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package network

import "golang.org/x/sys/unix"

func (hostNamespace) Socket(family Family, socktype Socktype, protocol Protocol) (Socket, error) {
fd, err := ignoreEINTR2(func() (int, error) {
return unix.Socket(int(family), int(socktype)|unix.SOCK_CLOEXEC|unix.SOCK_NONBLOCK, int(protocol))
})
if err != nil {
return nil, err
}
return newHostSocket(fd, family, socktype), nil
}

func (s *hostSocket) Accept() (Socket, Sockaddr, error) {
fd := s.fd.acquire()
if fd < 0 {
return nil, nil, EBADF
}
defer s.fd.release(fd)
conn, addr, err := ignoreEINTR3(func() (int, unix.Sockaddr, error) {
return unix.Accept4(fd, unix.SOCK_CLOEXEC|unix.SOCK_NONBLOCK)
})
if err != nil {
return nil, nil, err
}
return newHostSocket(conn, s.family, s.socktype), addr, nil
}
92 changes: 92 additions & 0 deletions internal/network/host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package network_test

import (
"net"
"testing"

"github.com/stealthrocket/timecraft/internal/assert"
"github.com/stealthrocket/timecraft/internal/network"
)

func TestHostNetwork(t *testing.T) {
tests := []struct {
scenario string
function func(*testing.T, network.Namespace)
}{
{
scenario: "a host network namespace has at least one loopback interface",
function: testHostNetworkInterface,
},

{
scenario: "ipv4 stream sockets can connect to one another on the loopback interface",
function: testNamespaceConnectStreamLoopbackIPv4,
},

{
scenario: "ipv6 stream sockets can connect to one another on the loopback interface",
function: testNamespaceConnectStreamLoopbackIPv6,
},

{
scenario: "ipv4 datagram sockets can connect to one another on the loopback interface",
function: testNamespaceConnectDatagramLoopbackIPv4,
},

{
scenario: "ipv6 datagram sockets can connect to one another on the loopback interface",
function: testNamespaceConnectDatagramLoopbackIPv6,
},

{
scenario: "ipv4 sockets can exchange datagrams on the loopback interface",
function: testNamespaceExchangeDatagramLoopbackIPv4,
},

{
scenario: "ipv6 sockets can exchange datagrams on the loopback interface",
function: testNamespaceExchangeDatagramLoopbackIPv6,
},
}

for _, test := range tests {
t.Run(test.scenario, func(t *testing.T) {
test.function(t, network.Host())
})
}
}

func testHostNetworkInterface(t *testing.T, ns network.Namespace) {
ifaces, err := ns.Interfaces()
assert.OK(t, err)

for _, iface := range ifaces {
if (iface.Flags() & net.FlagLoopback) == 0 {
continue
}
if (iface.Flags() & net.FlagUp) == 0 {
continue
}

lo0 := iface
assert.NotEqual(t, lo0.Name(), "")

lo0Addrs, err := lo0.Addrs()
assert.OK(t, err)

ipv4 := false
ipv6 := false
for _, addr := range lo0Addrs {
switch addr.String() {
case "127.0.0.1/8":
ipv4 = true
case "::1/128":
ipv6 = true
}
}
assert.True(t, ipv4 && ipv6)
return
}

t.Fatal("host network has not loopback interface")
}
Loading

0 comments on commit 15dc18f

Please sign in to comment.