Repository navigation
Expand file tree
/
Copy pathstun_server.go
More file actions
118 lines (101 loc) · 2.29 KB
/
Copy pathstun_server.go
File metadata and controls
118 lines (101 loc) · 2.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package main
import (
"errors"
"fmt"
"log"
"net"
"sync"
"sync/atomic"
"github.com/pion/stun/v3"
)
// STUNServer runs a lightweight UDP STUN binding server.
type STUNServer struct {
addr string
conn *net.UDPConn
closed atomic.Bool
closeChan chan struct{}
mu sync.Mutex
Silent bool
}
// NewSTUNServer initializes a new STUNServer instance.
func NewSTUNServer() *STUNServer {
return &STUNServer{
closeChan: make(chan struct{}),
}
}
func (s *STUNServer) logf(format string, v ...any) {
if !s.Silent {
log.Printf(format, v...)
}
}
// Start binds to the UDP address and starts listening for STUN binding requests.
func (s *STUNServer) Start(addr string) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return "", fmt.Errorf("STUNServer: failed to resolve address %s: %w", addr, err)
}
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return "", fmt.Errorf("STUNServer: failed to listen on %s: %w", addr, err)
}
s.conn = conn
s.addr = conn.LocalAddr().String()
s.logf("STUN server listening on %s (UDP)\n", s.addr)
go s.serve()
return s.addr, nil
}
func (s *STUNServer) serve() {
buf := make([]byte, 1500)
for {
n, clientAddr, err := s.conn.ReadFromUDP(buf)
if err != nil {
if s.closed.Load() {
return
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
continue
}
s.logf("STUN server read error: %v\n", err)
return
}
if n < 20 || !stun.IsMessage(buf[:n]) {
continue
}
var req stun.Message
req.Raw = make([]byte, n)
copy(req.Raw, buf[:n])
if err := req.Decode(); err != nil {
continue
}
if req.Type.Class == stun.ClassRequest && req.Type.Method == stun.MethodBinding {
res := stun.New()
err := res.Build(
stun.NewTransactionIDSetter(req.TransactionID),
stun.BindingSuccess,
&stun.XORMappedAddress{
IP: clientAddr.IP,
Port: clientAddr.Port,
},
stun.Fingerprint,
)
if err != nil {
s.logf("STUN server failed to build response: %v\n", err)
continue
}
_, _ = s.conn.WriteToUDP(res.Raw, clientAddr)
}
}
}
func (s *STUNServer) Close() error {
if s.closed.Swap(true) {
return nil
}
close(s.closeChan)
if s.conn != nil {
return s.conn.Close()
}
return nil
}