aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/jackpal/go-nat-pmp/network.go
blob: c42b4fee9d423d233fbc03d3a62b57bb228ad7ca (plain) (blame)
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
package natpmp

import (
    "fmt"
    "net"
    "time"
)

const nAT_PMP_PORT = 5351
const nAT_TRIES = 9
const nAT_INITIAL_MS = 250

// A caller that implements the NAT-PMP RPC protocol.
type network struct {
    gateway net.IP
}

func (n *network) call(msg []byte, timeout time.Duration) (result []byte, err error) {
    var server net.UDPAddr
    server.IP = n.gateway
    server.Port = nAT_PMP_PORT
    conn, err := net.DialUDP("udp", nil, &server)
    if err != nil {
        return
    }
    defer conn.Close()

    // 16 bytes is the maximum result size.
    result = make([]byte, 16)

    var finalTimeout time.Time
    if timeout != 0 {
        finalTimeout = time.Now().Add(timeout)
    }

    needNewDeadline := true

    var tries uint
    for tries = 0; (tries < nAT_TRIES && finalTimeout.IsZero()) || time.Now().Before(finalTimeout); {
        if needNewDeadline {
            nextDeadline := time.Now().Add((nAT_INITIAL_MS << tries) * time.Millisecond)
            err = conn.SetDeadline(minTime(nextDeadline, finalTimeout))
            if err != nil {
                return
            }
            needNewDeadline = false
        }
        _, err = conn.Write(msg)
        if err != nil {
            return
        }
        var bytesRead int
        var remoteAddr *net.UDPAddr
        bytesRead, remoteAddr, err = conn.ReadFromUDP(result)
        if err != nil {
            if err.(net.Error).Timeout() {
                tries++
                needNewDeadline = true
                continue
            }
            return
        }
        if !remoteAddr.IP.Equal(n.gateway) {
            // Ignore this packet.
            // Continue without increasing retransmission timeout or deadline.
            continue
        }
        // Trim result to actual number of bytes received
        if bytesRead < len(result) {
            result = result[:bytesRead]
        }
        return
    }
    err = fmt.Errorf("Timed out trying to contact gateway")
    return
}

func minTime(a, b time.Time) time.Time {
    if a.IsZero() {
        return b
    }
    if b.IsZero() {
        return a
    }
    if a.Before(b) {
        return a
    }
    return b
}