1package main
2
3import (
4	"flag"
5	"log"
6	"net"
7	"os"
8	"os/signal"
9	"regexp"
10	"strconv"
11	"syscall"
12
13	"github.com/pion/stun"
14	"github.com/pion/turn/v2"
15)
16
17// attributeAdder wraps a PacketConn and appends the SOFTWARE attribute to STUN packets
18// This pattern could be used to capture/inspect/modify data as well
19type attributeAdder struct {
20	net.PacketConn
21}
22
23func (s *attributeAdder) WriteTo(p []byte, addr net.Addr) (n int, err error) {
24	if stun.IsMessage(p) {
25		m := &stun.Message{Raw: p}
26		if err = m.Decode(); err != nil {
27			return
28		}
29
30		if err = stun.NewSoftware("CustomTURNServer").AddTo(m); err != nil {
31			return
32		}
33
34		m.Encode()
35		p = m.Raw
36	}
37
38	return s.PacketConn.WriteTo(p, addr)
39}
40
41func main() {
42	publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.")
43	port := flag.Int("port", 3478, "Listening port.")
44	users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")")
45	realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")")
46	flag.Parse()
47
48	if len(*publicIP) == 0 {
49		log.Fatalf("'public-ip' is required")
50	} else if len(*users) == 0 {
51		log.Fatalf("'users' is required")
52	}
53
54	// Create a UDP listener to pass into pion/turn
55	// pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in
56	// this allows us to add logging, storage or modify inbound/outbound traffic
57	udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port))
58	if err != nil {
59		log.Panicf("Failed to create TURN server listener: %s", err)
60	}
61
62	// Cache -users flag for easy lookup later
63	// If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey
64	usersMap := map[string][]byte{}
65	for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) {
66		usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2])
67	}
68
69	s, err := turn.NewServer(turn.ServerConfig{
70		Realm: *realm,
71		// Set AuthHandler callback
72		// This is called everytime a user tries to authenticate with the TURN server
73		// Return the key for that user, or false when no user is found
74		AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) {
75			if key, ok := usersMap[username]; ok {
76				return key, true
77			}
78			return nil, false
79		},
80		// PacketConnConfigs is a list of UDP Listeners and the configuration around them
81		PacketConnConfigs: []turn.PacketConnConfig{
82			{
83				PacketConn: &attributeAdder{udpListener},
84				RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{
85					RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP)
86					Address:      "0.0.0.0",              // But actually be listening on every interface
87				},
88			},
89		},
90	})
91	if err != nil {
92		log.Panic(err)
93	}
94
95	// Block until user sends SIGINT or SIGTERM
96	sigs := make(chan os.Signal, 1)
97	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
98	<-sigs
99
100	if err = s.Close(); err != nil {
101		log.Panic(err)
102	}
103}
104