-
-
Notifications
You must be signed in to change notification settings - Fork 322
/
Copy pathlocation.go
162 lines (138 loc) Β· 4.35 KB
/
location.go
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package netenv
import (
"errors"
"fmt"
"net"
"syscall"
"time"
"golang.org/x/net/ipv4"
"golang.org/x/net/icmp"
"github.com/safing/portbase/log"
"github.com/safing/portbase/rng"
"github.com/safing/portmaster/network/netutils"
)
var (
locationTestingIPv4 = "1.1.1.1"
locationTestingIPv4Addr *net.IPAddr
)
func prepLocation() (err error) {
locationTestingIPv4Addr, err = net.ResolveIPAddr("ip", locationTestingIPv4)
return err
}
// GetApproximateInternetLocation returns the nearest detectable IP address. If one or more global IP addresses are configured, one of them is returned. Currently only support IPv4. Else, the IP address of the nearest ping-answering internet node is returned.
func GetApproximateInternetLocation() (net.IP, error) { //nolint:gocognit
// TODO: Create IPv6 version of GetApproximateInternetLocation
// First check if we have an assigned IPv6 address. Return that if available.
globalIPv4, _, err := GetAssignedGlobalAddresses()
if err != nil {
log.Warningf("netenv: location approximation: failed to get assigned global addresses: %s", err)
} else if len(globalIPv4) > 0 {
return globalIPv4[0], nil
}
// Create OS specific ICMP Listener.
conn, err := newICMPListener(locationTestingIPv4)
if err != nil {
return nil, fmt.Errorf("failed to listen: %s", err)
}
defer conn.Close()
v4Conn := ipv4.NewPacketConn(conn)
// Generate a random ID for the ICMP packets.
msgID, err := rng.Number(0xFFFF) // uint16
if err != nil {
return nil, fmt.Errorf("failed to generate ID: %s", err)
}
// Create ICMP message body
pingMessage := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: int(msgID),
Seq: 0, // increased before marshal
Data: []byte{},
},
}
recvBuffer := make([]byte, 1500)
maxHops := 4 // add one for every reply that is not global
next:
for i := 1; i <= maxHops; i++ {
repeat:
for j := 1; j <= 2; j++ { // Try every hop twice.
// Increase sequence number.
pingMessage.Body.(*icmp.Echo).Seq++
// Make packet data.
pingPacket, err := pingMessage.Marshal(nil)
if err != nil {
return nil, err
}
// Set TTL on IP packet.
err = v4Conn.SetTTL(i)
if err != nil {
return nil, err
}
// Send ICMP packet.
if _, err := conn.WriteTo(pingPacket, locationTestingIPv4Addr); err != nil {
if neterr, ok := err.(*net.OpError); ok {
if neterr.Err == syscall.ENOBUFS {
continue
}
}
return nil, err
}
// Listen for replies to the ICMP packet.
listen:
for {
// Set read timeout.
err = conn.SetReadDeadline(
time.Now().Add(
time.Duration(i*2+30) * time.Millisecond,
),
)
if err != nil {
return nil, err
}
// Read next packet.
n, src, err := conn.ReadFrom(recvBuffer)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
// Continue with next packet if we timeout
continue repeat
}
return nil, err
}
// Parse remote IP address.
addr, ok := src.(*net.IPAddr)
if !ok {
return nil, fmt.Errorf("failed to parse IP: %s", src.String())
}
// Continue if we receive a packet from ourself. This is specific to Windows.
if me, err := IsMyIP(addr.IP); err == nil && me {
log.Tracef("netenv: location approximation: ignoring own message from %s", src)
continue listen
}
// If we received something from a global IP address, we have succeeded and can return immediately.
if netutils.IPIsGlobal(addr.IP) {
return addr.IP, nil
}
// For everey non-global reply received, increase the maximum hops to try.
maxHops++
// Parse the ICMP message.
icmpReply, err := icmp.ParseMessage(1, recvBuffer[:n])
if err != nil {
log.Warningf("netenv: location approximation: failed to parse ICMP message: %s", err)
continue listen
}
// React based on message type.
switch icmpReply.Type {
case ipv4.ICMPTypeTimeExceeded, ipv4.ICMPTypeEchoReply:
log.Tracef("netenv: location approximation: receveived %q from %s", icmpReply.Type, addr.IP)
continue next
case ipv4.ICMPTypeDestinationUnreachable:
return nil, fmt.Errorf("destination unreachable")
default:
log.Tracef("netenv: location approximation: unexpected ICMP reply: received %q from %s", icmpReply.Type, addr.IP)
}
}
}
}
return nil, errors.New("no usable response to any icmp message")
}