package main import ( "bufio" "fmt" "log" "net" "os" "sync" "time" "github.com/pkg/errors" "gortc.io/stun" ) func main() { client := NewClient() defer client.Close() wg := &sync.WaitGroup{} wg.Add(1) client.OnStunAddr(func(addr string) { fmt.Printf("Our address: %s\n", addr) wg.Done() }) client.OnMessage(func(message []byte) { fmt.Printf("Received: %s\n", string(message)) if string(message) == "ping" { if err := client.Send([]byte("pong")); err != nil { log.Fatal(err) } } }) // Listen go func() { if err := client.Listen(); err != nil { log.Fatal(err) } }() wg.Wait() fmt.Printf("Peer address: ") addr, err := readAddress() if err != nil { log.Fatal(err) } if err := client.SetPeer(addr); err != nil { log.Fatal(err) } if err := client.Send([]byte("ping")); err != nil { log.Fatal(err) } select {} } func readAddress() (string, error) { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { text := scanner.Text() return text, nil } if err := scanner.Err(); err != nil { return "", err } return "", errors.Errorf("no input") } var stunServer = "stun.l.google.com:19302" // Client ... type Client struct { publicAddr stun.XORMappedAddress conn *udpConn onStunAddr func(addr string) onMessage func(message []byte) } // NewClient ... func NewClient() *Client { return &Client{ onStunAddr: func(addr string) {}, onMessage: func([]byte) {}, } } // Close ... func (c *Client) Close() { if c.conn != nil { _ = c.conn.Close() } } // OnStunAddr ... func (c *Client) OnStunAddr(f func(addr string)) { c.onStunAddr = f } // OnMessage ... func (c *Client) OnMessage(f func(b []byte)) { c.onMessage = f } // SetPeer sets peer address. func (c *Client) SetPeer(addr string) error { log.Printf("Set peer %s\n", addr) return c.conn.SetPeer(addr) } // Send to peer. func (c *Client) Send(message []byte) error { return c.conn.Send(message) } // Listen ... func (c *Client) Listen() error { conn, err := listenUDP() if err != nil { return err } c.conn = conn defer c.conn.Close() log.Printf("STUN listening on %s\n", c.conn.LocalAddr()) messageChan := c.conn.Listen() if err := c.conn.SendBindingRequest(); err != nil { return err } log.Printf("Waiting for messages...\n") for { select { case <-time.After(time.Second * 10): return errors.Errorf("stun timed out") case message, ok := <-messageChan: if !ok { log.Printf("Listen done\n") return nil } if stun.IsMessage(message) { m := new(stun.Message) m.Raw = message decErr := m.Decode() if decErr != nil { return errors.Wrapf(decErr, "failed to decode stun message") } var xorAddr stun.XORMappedAddress if getErr := xorAddr.GetFrom(m); getErr != nil { return errors.Wrapf(getErr, "failed to get address from stun") } log.Printf("Got STUN message: %s\n", xorAddr.String()) if c.publicAddr.String() != xorAddr.String() { log.Printf("Public address: %s\n", xorAddr) c.publicAddr = xorAddr c.onStunAddr(c.publicAddr.String()) } } else { log.Printf("Got message (%d)\n", len(message)) c.onMessage(message) } } } } var udp = "udp" type udpConn struct { peerAddr *net.UDPAddr conn *net.UDPConn } func listenUDP() (*udpConn, error) { conn, err := net.ListenUDP(udp, nil) if err != nil { return nil, errors.Wrapf(err, "failed to net.ListenUDP") } c := &udpConn{conn: conn} return c, nil } func (c *udpConn) SendBindingRequest() error { srvAddr, err := net.ResolveUDPAddr(udp, stunServer) if err != nil { return errors.Wrapf(err, "failed to resolve addr") } if err := sendBindingRequest(c.conn, srvAddr); err != nil { return err } return nil } func (c *udpConn) Send(msg []byte) error { if c.peerAddr == nil { return errors.Errorf("no peer address set") } n, err := c.conn.WriteToUDP(msg, c.peerAddr) if err != nil { return err } if n != len(msg) { return errors.Errorf("failed to (udp) write all bytes") } return nil } func (c *udpConn) Close() error { return c.conn.Close() } func (c *udpConn) Listen() <-chan []byte { messages := make(chan []byte) go func() { for { buf := make([]byte, 1024) n, _, err := c.conn.ReadFromUDP(buf) if err != nil { close(messages) return } buf = buf[:n] messages <- buf } }() return messages } func (c *udpConn) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *udpConn) SetPeer(addr string) error { a, err := net.ResolveUDPAddr(udp, addr) if err != nil { return err } c.peerAddr = a return nil } func sendBindingRequest(conn *net.UDPConn, addr *net.UDPAddr) error { m := stun.MustBuild(stun.TransactionID, stun.BindingRequest) if err := sendUDP(m.Raw, conn, addr); err != nil { return errors.Wrapf(err, "failed to bind") } return nil } func sendUDP(msg []byte, conn *net.UDPConn, addr *net.UDPAddr) error { _, err := conn.WriteToUDP(msg, addr) if err != nil { return errors.Wrapf(err, "failed to send") } return nil }