Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit b2edaca

Browse files
chripelldanderson
authored andcommitted
Add ICE establishment parameters tuning, better debugging and a standalone
testing tool.
1 parent 2825328 commit b2edaca

File tree

3 files changed

+164
-36
lines changed

3 files changed

+164
-36
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
*~
2+
nattester/nattester
3+
stun/stunclient/stunclient

nat.go

+57-36
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"log"
89
"net"
910
"time"
1011

@@ -13,7 +14,24 @@ import (
1314

1415
type ExchangeCandidatesFun func([]byte) []byte
1516

16-
func Connect(xchg ExchangeCandidatesFun, initiator bool) (net.Conn, error) {
17+
type Config struct {
18+
ProbeTimeout time.Duration
19+
ProbeInterval time.Duration
20+
DecisionTime time.Duration
21+
PeerDeadline time.Duration
22+
Verbose bool
23+
}
24+
25+
func DefaultConfig() *Config {
26+
return &Config{
27+
ProbeTimeout: 500 * time.Millisecond,
28+
ProbeInterval: 100 * time.Millisecond,
29+
DecisionTime: 2 * time.Second,
30+
PeerDeadline: 5 * time.Second,
31+
}
32+
}
33+
34+
func ConnectOpt(xchg ExchangeCandidatesFun, initiator bool, cfg *Config) (net.Conn, error) {
1735
sock, err := net.ListenUDP("udp", &net.UDPAddr{})
1836
if err != nil {
1937
return nil, err
@@ -22,7 +40,9 @@ func Connect(xchg ExchangeCandidatesFun, initiator bool) (net.Conn, error) {
2240
engine := &attemptEngine{
2341
xchg: xchg,
2442
sock: sock,
25-
initiator: initiator}
43+
initiator: initiator,
44+
cfg: cfg,
45+
}
2646

2747
conn, err := engine.run()
2848
if err != nil {
@@ -32,6 +52,10 @@ func Connect(xchg ExchangeCandidatesFun, initiator bool) (net.Conn, error) {
3252
return conn, nil
3353
}
3454

55+
func Connect(xchg ExchangeCandidatesFun, initiator bool) (net.Conn, error) {
56+
return ConnectOpt(xchg, initiator, DefaultConfig())
57+
}
58+
3559
type attempt struct {
3660
candidate
3761
tid []byte
@@ -48,15 +72,9 @@ type attemptEngine struct {
4872
attempts []attempt
4973
decision time.Time
5074
p2pconn net.Conn
75+
cfg *Config
5176
}
5277

53-
const (
54-
probeTimeout = 500 * time.Millisecond
55-
probeInterval = 100 * time.Millisecond
56-
decisionTime = 2 * time.Second
57-
peerDeadline = 5 * time.Second
58-
)
59-
6078
func (e *attemptEngine) init() error {
6179
candidates, err := GatherCandidates(e.sock)
6280
if err != nil {
@@ -80,7 +98,7 @@ func (e *attemptEngine) init() error {
8098
}
8199

82100
e.sock.SetWriteDeadline(time.Time{})
83-
e.decision = time.Now().Add(decisionTime)
101+
e.decision = time.Now().Add(e.cfg.DecisionTime)
84102

85103
return nil
86104
}
@@ -92,7 +110,7 @@ func (e *attemptEngine) xmit() (time.Time, error) {
92110

93111
for i := range e.attempts {
94112
if e.attempts[i].timeout.Before(now) {
95-
e.attempts[i].timeout = time.Now().Add(probeTimeout)
113+
e.attempts[i].timeout = time.Now().Add(e.cfg.ProbeTimeout)
96114
e.attempts[i].tid, err = stun.RandomTid()
97115
if err != nil {
98116
return time.Time{}, err
@@ -101,6 +119,9 @@ func (e *attemptEngine) xmit() (time.Time, error) {
101119
if err != nil {
102120
return time.Time{}, err
103121
}
122+
if e.cfg.Verbose {
123+
log.Printf("TX probe %v to %v", e.attempts[i].tid, e.attempts[i].Addr)
124+
}
104125
e.sock.WriteToUDP(packet, e.attempts[i].Addr)
105126
}
106127
if ret.IsZero() || e.attempts[i].timeout.Before(ret) {
@@ -122,20 +143,32 @@ func (e *attemptEngine) read() error {
122143

123144
packet, err := stun.ParsePacket(buf[:n], nil)
124145
if err != nil {
146+
if e.cfg.Verbose {
147+
log.Printf("Cannot parse packet from %v: %v", from, err)
148+
}
125149
return nil
126150
}
127151

128152
if packet.Method != stun.MethodBinding {
153+
if e.cfg.Verbose {
154+
log.Printf("Packet from %v is not a binding request", from)
155+
}
129156
return nil
130157
}
131158

132159
switch packet.Class {
133160
case stun.ClassRequest:
134161
response, err := stun.BindResponse(packet.Tid[:], from, nil, false)
135162
if err != nil {
163+
if e.cfg.Verbose {
164+
log.Printf("Cannot bind response: %v", err)
165+
}
136166
return nil
137167
}
138168
e.sock.WriteToUDP(response, from)
169+
if e.cfg.Verbose {
170+
log.Printf("RX %v from %v use candidate %v, answering", packet.Tid[:], from, packet.UseCandidate)
171+
}
139172
if packet.UseCandidate {
140173
for i := range e.attempts {
141174
if from.String() != e.attempts[i].Addr.String() {
@@ -144,12 +177,18 @@ func (e *attemptEngine) read() error {
144177
if !e.attempts[i].success {
145178
return errors.New("Initiator told us to use bad link")
146179
}
180+
if e.cfg.Verbose {
181+
log.Printf("Choose local %v remote %v", e.attempts[i].localaddr, e.attempts[i].Addr)
182+
}
147183
e.p2pconn = newConn(e.sock, e.attempts[i].localaddr, e.attempts[i].Addr)
148184
return nil
149185
}
150186
}
151187

152188
case stun.ClassSuccess:
189+
if e.cfg.Verbose {
190+
log.Printf("RX %v from %v", packet.Tid[:], from)
191+
}
153192
for i := range e.attempts {
154193
if !bytes.Equal(packet.Tid[:], e.attempts[i].tid) {
155194
continue
@@ -158,44 +197,28 @@ func (e *attemptEngine) read() error {
158197
return nil
159198
}
160199
if e.attempts[i].chosen {
200+
if e.cfg.Verbose {
201+
log.Printf("Choose local %v remote %v", e.attempts[i].localaddr, e.attempts[i].Addr)
202+
}
161203
e.p2pconn = newConn(e.sock, e.attempts[i].localaddr, e.attempts[i].Addr)
162204
return nil
163205
}
164206
e.attempts[i].success = true
165207
e.attempts[i].localaddr = packet.Addr
166-
e.attempts[i].timeout = time.Now().Add(probeInterval)
208+
e.attempts[i].timeout = time.Now().Add(e.cfg.ProbeInterval)
167209
return nil
168210
}
169211
}
170212

171213
return nil
172214
}
173215

174-
func (e *attemptEngine) debug() {
175-
if e.initiator {
176-
return
177-
}
178-
buf := new(bytes.Buffer)
179-
fmt.Fprintf(buf, "%t\t", e.initiator)
180-
for _, att := range e.attempts {
181-
timeout := att.timeout.Sub(time.Now())
182-
if timeout < 0 {
183-
timeout = 0
184-
}
185-
fmt.Fprintf(buf, "%s/%s/%s/%t\t", att.Addr, att.localaddr, timeout, att.success)
186-
}
187-
if e.initiator {
188-
buf.WriteString("\n")
189-
}
190-
fmt.Println(buf.String())
191-
}
192-
193216
func (e *attemptEngine) run() (net.Conn, error) {
194217
if err := e.init(); err != nil {
195218
return nil, err
196219
}
197220

198-
endTime := time.Now().Add(peerDeadline)
221+
endTime := time.Now().Add(e.cfg.PeerDeadline)
199222
for {
200223
if e.initiator && !e.decision.IsZero() && time.Now().After(e.decision) {
201224
e.decision = time.Time{}
@@ -204,15 +227,13 @@ func (e *attemptEngine) run() (net.Conn, error) {
204227
}
205228
}
206229

207-
e.debug()
208-
209230
timeout, err := e.xmit()
210231
if err != nil {
211232
return nil, err
212233
}
213234

214235
if time.Now().After(timeout) {
215-
timeout = time.Now().Add(peerDeadline)
236+
timeout = time.Now().Add(e.cfg.PeerDeadline)
216237
}
217238

218239
e.sock.SetReadDeadline(timeout)
@@ -225,7 +246,7 @@ func (e *attemptEngine) run() (net.Conn, error) {
225246
}
226247

227248
if time.Now().After(endTime) {
228-
return nil, fmt.Errorf("haven't heard from my peer after %v", peerDeadline)
249+
return nil, fmt.Errorf("haven't heard from my peer after %v", e.cfg.PeerDeadline)
229250
}
230251
}
231252

nattester/nattester.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// nattester is a tool to quickly test ICE traversal when you have ssh working:
2+
//
3+
// scp nattester do.not.leak.hostnames.google.com:
4+
// nattester --initiator=hostname.example.com
5+
6+
package main
7+
8+
import (
9+
"bufio"
10+
"bytes"
11+
"flag"
12+
"fmt"
13+
"io"
14+
"log"
15+
"os"
16+
"os/exec"
17+
"strings"
18+
"time"
19+
20+
"github.com/danderson/nat"
21+
)
22+
23+
var (
24+
initiator = flag.String("initiator", "", "Name of host to ssh to")
25+
testString = flag.String("test_string",
26+
"The quick UDP packet jumped over the lazy TCP stream",
27+
"String to test echo on")
28+
cmd *exec.Cmd
29+
)
30+
31+
func xchangeCandidates(mine []byte) []byte {
32+
var (
33+
out io.Reader
34+
err error
35+
)
36+
if *initiator == "" {
37+
fmt.Printf("%s\n", string(mine))
38+
out = os.Stdin
39+
} else {
40+
cmd = exec.Command("ssh", *initiator, "./nattester")
41+
cmd.Stdin = strings.NewReader(fmt.Sprintf("%s\n", mine))
42+
out, err = cmd.StdoutPipe()
43+
if err != nil {
44+
log.Fatal(err)
45+
}
46+
if cmd.Start() != nil {
47+
log.Fatal(err)
48+
}
49+
}
50+
scanner := bufio.NewScanner(out)
51+
for scanner.Scan() {
52+
ret := scanner.Bytes()
53+
if *initiator != "" {
54+
go func() {
55+
for scanner.Scan() {
56+
fmt.Printf("REMOTE: %s\n", string(scanner.Bytes()))
57+
}
58+
}()
59+
}
60+
return ret
61+
}
62+
if err := scanner.Err(); err != nil {
63+
log.Fatal(err)
64+
}
65+
log.Fatal("Got no candidates")
66+
return nil
67+
}
68+
69+
func main() {
70+
log.SetOutput(os.Stdout)
71+
flag.Parse()
72+
cfg := nat.DefaultConfig()
73+
cfg.Verbose = true
74+
conn, err := nat.ConnectOpt(xchangeCandidates, *initiator != "", cfg)
75+
if err != nil {
76+
log.Fatalf("NO CARRIER: %v\n", err)
77+
}
78+
log.Println("CONNECT 9600")
79+
conn.SetDeadline(time.Now().Add(10 * time.Second))
80+
if *initiator == "" {
81+
// Poor man's echo sever
82+
io.Copy(conn, conn)
83+
return
84+
}
85+
ret := 1
86+
for {
87+
if _, err := conn.Write([]byte(*testString)); err != nil {
88+
log.Printf("NO CARRIER: %v\n", err)
89+
break
90+
}
91+
recv := make([]byte, len(*testString))
92+
if _, err := conn.Read(recv); err != nil {
93+
log.Printf("NO CARRIER: %v\n", err)
94+
break
95+
}
96+
log.Printf("RX: %v\n", recv)
97+
if bytes.Compare(recv, []byte(*testString)) == 0 {
98+
log.Print("Success!\n")
99+
ret = 0
100+
break
101+
}
102+
}
103+
cmd.Process.Kill()
104+
os.Exit(ret)
105+
}

0 commit comments

Comments
 (0)