diff --git a/sharedsock/example/README.md b/sharedsock/example/README.md new file mode 100644 index 000000000..0632d6e1d --- /dev/null +++ b/sharedsock/example/README.md @@ -0,0 +1,35 @@ +### How to run + +This will only work on Linux + +1. Run netcat listening on the UDP port 51820. This is going to be our external process: +```bash +nc -kluvw 1 51820 +``` + +2. Build and run the example Go code: + +```bash + go build -o sharedsock && sudo ./sharedsock +``` + +3. Test the logic by sending a STUN binding request + +```bash +STUN_PACKET="000100002112A4425454" +echo -n $STUN_PACKET | xxd -r -p | nc -u -w 1 localhost 51820 +``` + +4. You should see a similar output of the Go program. Note that you'll see some binary output in the netcat server too. This is due to the fact that kernel copies packets to both processes. + +```bash + read a STUN packet of size 18 from ... +``` + +5. Send a non-STUN packet + +```bash +echo -n 'hello' | nc -u -w 1 localhost 51820 +``` + +6. The Go program won't print anything. diff --git a/sharedsock/example/main.go b/sharedsock/example/main.go new file mode 100644 index 000000000..7c879b4c9 --- /dev/null +++ b/sharedsock/example/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "github.com/netbirdio/netbird/sharedsock" + log "github.com/sirupsen/logrus" + "os" + "os/signal" +) + +func main() { + + port := 51820 + rawSock, err := sharedsock.Listen(port, sharedsock.NewIncomingSTUNFilter()) + if err != nil { + panic(err) + } + + log.Infof("attached to to the raw socket on port %d", port) + + ctx, cancel := context.WithCancel(context.Background()) + // read packets + go func() { + buf := make([]byte, 1500) + for { + select { + case <-ctx.Done(): + log.Debugf("stopped reading from the shared socket") + return + default: + size, addr, err := rawSock.ReadFrom(buf) + if err != nil { + log.Errorf("error while reading packet from the shared socket: %s", err) + continue + } + log.Infof("read a STUN packet of size %d from %s", size, addr.String()) + } + } + }() + + // terminate the program on ^C + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for range c { + log.Infof("received ^C signal, stopping the program") + cancel() + err = rawSock.Close() + if err != nil { + log.Errorf("failed closing raw socket") + } + } + }() + + <-ctx.Done() +} diff --git a/sharedsock/filter.go b/sharedsock/filter.go index da27639fb..53339e93f 100644 --- a/sharedsock/filter.go +++ b/sharedsock/filter.go @@ -2,8 +2,6 @@ package sharedsock import "golang.org/x/net/bpf" -const magicCookie uint32 = 0x2112A442 - // BPFFilter is a generic filter that provides ipv4 and ipv6 BPF instructions type BPFFilter interface { // GetInstructions returns raw BPF instructions for ipv4 and ipv6 diff --git a/sharedsock/sock_linux.go b/sharedsock/sock_linux.go index 5d2b5a528..c9e35dfa2 100644 --- a/sharedsock/sock_linux.go +++ b/sharedsock/sock_linux.go @@ -67,17 +67,17 @@ func Listen(port int, filter BPFFilter) (net.PacketConn, error) { rawSock.router, err = netroute.New() if err != nil { - return nil, fmt.Errorf("failed to create router: %rawSock", err) + return nil, fmt.Errorf("failed to create raw socket router: %v", err) } rawSock.conn4, err = socket.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_UDP, "raw_udp4", nil) if err != nil { - return nil, fmt.Errorf("socket.Socket for ipv4 failed with: %rawSock", err) + return nil, fmt.Errorf("failed to create ipv4 raw socket: %v", err) } rawSock.conn6, err = socket.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_UDP, "raw_udp6", nil) if err != nil { - log.Errorf("socket.Socket for ipv6 failed with: %rawSock", err) + log.Errorf("failed to create ipv6 raw socket: %v", err) } ipv4Instructions, ipv6Instructions, err := filter.GetInstructions(uint32(rawSock.port)) diff --git a/sharedsock/sock_nolinux.go b/sharedsock/sock_nolinux.go index 94a061b90..93ac6b96f 100644 --- a/sharedsock/sock_nolinux.go +++ b/sharedsock/sock_nolinux.go @@ -8,7 +8,7 @@ import ( "runtime" ) -// Listen is not supported on other platforms +// Listen is not supported on other platforms then Linux func Listen(port int, filter BPFFilter) (net.PacketConn, error) { return nil, fmt.Errorf(fmt.Sprintf("Not supported OS %s. SharedSocket is only supported on Linux", runtime.GOOS)) } diff --git a/sharedsock/filter_linux.go b/sharedsock/stun_filter_linux.go similarity index 98% rename from sharedsock/filter_linux.go rename to sharedsock/stun_filter_linux.go index 2dd3eaded..a9ece622d 100644 --- a/sharedsock/filter_linux.go +++ b/sharedsock/stun_filter_linux.go @@ -2,6 +2,8 @@ package sharedsock import "golang.org/x/net/bpf" +const magicCookie uint32 = 0x2112A442 + // IncomingSTUNFilter implements BPFFilter and filters out anything but incoming STUN packets to a specified destination port. // Other packets (non STUN) will be forwarded to the process that own the port (e.g., WireGuard). type IncomingSTUNFilter struct { diff --git a/sharedsock/filter_nolinux.go b/sharedsock/stun_filter_nolinux.go similarity index 100% rename from sharedsock/filter_nolinux.go rename to sharedsock/stun_filter_nolinux.go