Files
stun/cmd/stun-multiplex/main.go
2023-04-12 10:22:35 +02:00

191 lines
4.7 KiB
Go

// Command stun-multiplex is example of doing UDP connection multiplexing
// that splits incoming UDP packets to two streams, "STUN Data" and
// "Application Data".
package main
import (
"flag"
"fmt"
"io"
"net"
"os"
"os/signal"
"syscall"
"time"
"github.com/pion/stun"
)
func copyAddr(dst *stun.XORMappedAddress, src stun.XORMappedAddress) {
dst.IP = append(dst.IP, src.IP...)
dst.Port = src.Port
}
func keepAlive(c *stun.Client) {
// Keep-alive for NAT binding.
t := time.NewTicker(time.Second * 5)
for range t.C {
if err := c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), func(res stun.Event) {
if res.Error != nil {
panic(res.Error)
}
}); err != nil {
panic(err)
}
}
}
type message struct {
text string
addr net.Addr
}
func demultiplex(conn *net.UDPConn, stunConn io.Writer, messages chan message) {
buf := make([]byte, 1024)
for {
n, raddr, err := conn.ReadFrom(buf)
if err != nil {
panic(err)
}
// De-multiplexing incoming packets.
if stun.IsMessage(buf[:n]) {
// If buf looks like STUN message, send it to STUN client connection.
if _, err = stunConn.Write(buf[:n]); err != nil {
panic(err)
}
} else {
// If not, it is application data.
fmt.Printf("demultiplex: [%s]: %s\n", raddr, buf[:n])
messages <- message{
text: string(buf[:n]),
addr: raddr,
}
}
}
}
func multiplex(conn *net.UDPConn, stunAddr net.Addr, stunConn io.Reader) {
// Sending all data from stun client to stun server.
buf := make([]byte, 1024)
for {
n, err := stunConn.Read(buf)
if err != nil {
panic(err)
}
if _, err = conn.WriteTo(buf[:n], stunAddr); err != nil {
panic(err)
}
}
}
var stunServer = flag.String("stun", "stun.l.google.com:19302", "STUN Server to use") //nolint:gochecknoglobals
func main() {
flag.Parse()
// Allocating local UDP socket that will be used both for STUN and
// our application data.
addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0")
if err != nil {
panic(err)
}
conn, err := net.ListenUDP("udp4", addr)
if err != nil {
panic(err)
}
// Resolving STUN Server address.
stunAddr, err := net.ResolveUDPAddr("udp4", *stunServer)
if err != nil {
panic(err)
}
fmt.Println("local addr:", conn.LocalAddr(), "stun server addr:", stunAddr)
stunL, stunR := net.Pipe()
c, err := stun.NewClient(stunR)
if err != nil {
panic(err)
}
// Starting multiplexing (writing back STUN messages) with de-multiplexing
// (passing STUN messages to STUN client and processing application
// data separately).
//
// stunL and stunR are virtual connections, see net.Pipe for reference.
messages := make(chan message)
go demultiplex(conn, stunL, messages)
go multiplex(conn, stunAddr, stunL)
// Getting our "real" IP address from STUN Server.
// This will create a NAT binding on your provider/router NAT Server,
// and the STUN server will return allocated public IP for that binding.
//
// This can fail if your NAT Server is strict and will use separate ports
// for application data and STUN
var gotAddr stun.XORMappedAddress
if err = c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), func(res stun.Event) {
if res.Error != nil {
panic(res.Error)
}
var xorAddr stun.XORMappedAddress
if getErr := xorAddr.GetFrom(res.Message); getErr != nil {
panic(getErr)
}
copyAddr(&gotAddr, xorAddr)
}); err != nil {
panic(err)
}
fmt.Println("public addr:", gotAddr)
// Keep-alive is needed to keep our NAT port allocated.
// Any ping-pong will work, but we are just making binding requests.
// Note that STUN Server is not mandatory for keep alive, application
// data will keep alive that binding too.
go keepAlive(c)
notify := make(chan os.Signal, 1)
signal.Notify(notify, os.Interrupt, syscall.SIGTERM)
if flag.Arg(0) == "" {
fmt.Println("Acting as server. Use following command to connect:")
fmt.Println(os.Args[0], gotAddr)
for {
select {
case m := <-messages:
if _, err = conn.WriteTo([]byte(m.text), m.addr); err != nil {
panic(err)
}
case <-notify:
fmt.Println("\rStopping")
return
}
}
} else {
peerAddr, err := net.ResolveUDPAddr("udp4", flag.Arg(0))
if err != nil {
panic(err)
}
fmt.Println("Acting as client. Connecting to", peerAddr)
msg := "Hello peer"
sendMsg := func() {
fmt.Println("Writing", peerAddr)
if _, err = conn.WriteTo([]byte(msg), peerAddr); err != nil {
panic(err)
}
}
sendMsg()
deadline := time.After(time.Second * 10)
for {
select {
case <-deadline:
fmt.Println("Failed to connect: deadline reached.")
os.Exit(2)
case <-time.After(time.Second):
// Retry.
sendMsg()
case m := <-messages:
fmt.Printf("Got response from %s: %s\n", m.addr, m.text)
return
case <-notify:
fmt.Println("\rStopping")
return
}
}
}
}