package main import ( "fmt" "github.com/pion/stun" "github.com/pion/turn/v2" "log" "net" "os" "os/signal" "regexp" "syscall" ) // stunLogger wraps a PacketConn and prints incoming/outgoing STUN packets // This pattern could be used to capture/inspect/modify data as well type stunLogger struct { net.PacketConn } func (s *stunLogger) WriteTo(p []byte, addr net.Addr) (n int, err error) { if n, err = s.PacketConn.WriteTo(p, addr); err == nil && stun.IsMessage(p) { msg := &stun.Message{Raw: p} if err = msg.Decode(); err != nil { return } fmt.Printf("Outbound STUN: %s \n", msg.String()) } return } func (s *stunLogger) ReadFrom(p []byte) (n int, addr net.Addr, err error) { if n, addr, err = s.PacketConn.ReadFrom(p); err == nil && stun.IsMessage(p) { msg := &stun.Message{Raw: p} if err = msg.Decode(); err != nil { return } fmt.Printf("Inbound STUN: %s \n", msg.String()) } return } func main() { publicIP := os.Getenv("KERBEROS_TURN_PUBLIC_IP") //"192.168.86.175" users := os.Getenv("KERBEROS_TURN_USERS") //"username1=password1" port := os.Getenv("KERBEROS_TURN_PORT") //"443" realm := os.Getenv("KERBEROS_TURN_REALM") //"kerberos.io" if publicIP == "" { log.Fatalf("'public-ip' is required") } else if users == "" { log.Fatalf("'users' is required") } // Create a TCP listener to pass into pion/turn // pion/turn itself doesn't allocate any TCP listeners, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic tcpListener, err := net.Listen("tcp4", "0.0.0.0:"+port) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // Or if you want toCreate a UDP listener to pass into pion/turn // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic // --- (UNCOMMENT BELOW) --- // udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port)) // if err != nil { // log.Panicf("Failed to create TURN server listener: %s", err) // } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: realm, // Set AuthHandler callback // This is called everytime a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // ListenerConfig is a list of Listeners and the configuration around them ListenerConfigs: []turn.ListenerConfig{ { Listener: tcpListener, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(publicIP), Address: "0.0.0.0", //MinPort: 50000, // If using UDP listener, you can specify the lower range of ports. //MaxPort: 55000, // If using UDP listener, you can specify the upper range of ports. }, }, }, }) if err != nil { log.Panic(err) } fmt.Println("Starting TURN") // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } }