Fix location estimation via ICMP traceroute
This commit is contained in:
@@ -196,6 +196,17 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Submit to ICMP listener.
|
||||||
|
submitted := netenv.SubmitPacketToICMPListener(pkt)
|
||||||
|
if submitted {
|
||||||
|
// If the packet was submitted to the listener, we must not do a
|
||||||
|
// permanent accept, because then we won't see any future packets of that
|
||||||
|
// connection and thus cannot continue to submit them.
|
||||||
|
log.Debugf("filter: fast-track tracing ICMP/v6: %s", pkt)
|
||||||
|
_ = pkt.Accept()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Handle echo request and replies regularly.
|
// Handle echo request and replies regularly.
|
||||||
// Other ICMP packets are considered system business.
|
// Other ICMP packets are considered system business.
|
||||||
icmpLayers := pkt.Layers().LayerClass(layers.LayerClassIPControl)
|
icmpLayers := pkt.Layers().LayerClass(layers.LayerClassIPControl)
|
||||||
@@ -214,20 +225,8 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Premit all ICMP/v6 packets that are not echo requests or replies.
|
// Permit all ICMP/v6 packets that are not echo requests or replies.
|
||||||
log.Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
|
log.Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
|
||||||
|
|
||||||
// Submit to ICMP listener.
|
|
||||||
submitted := netenv.SubmitPacketToICMPListener(pkt)
|
|
||||||
|
|
||||||
// If the packet was submitted to the listener, we must not do a
|
|
||||||
// permanent accept, because then we won't see any future packets of that
|
|
||||||
// connection and thus cannot continue to submit them.
|
|
||||||
if submitted {
|
|
||||||
_ = pkt.Accept()
|
|
||||||
} else {
|
|
||||||
_ = pkt.PermanentAccept()
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case packet.UDP, packet.TCP:
|
case packet.UDP, packet.TCP:
|
||||||
|
|||||||
@@ -50,5 +50,18 @@ func registerAPIEndpoints() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := api.RegisterEndpoint(api.Endpoint{
|
||||||
|
Path: "network/location/traceroute",
|
||||||
|
Read: api.PermitUser,
|
||||||
|
BelongsTo: module,
|
||||||
|
StructFunc: func(ar *api.Request) (i interface{}, err error) {
|
||||||
|
return getLocationFromTraceroute()
|
||||||
|
},
|
||||||
|
Name: "Get Approximate Internet Location via Traceroute",
|
||||||
|
Description: "Returns an approximation of where the device is on the Internet using a the traceroute technique.",
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package netenv
|
package netenv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/tevino/abool"
|
"github.com/tevino/abool"
|
||||||
@@ -31,21 +32,23 @@ var (
|
|||||||
listenICMPEnabled = abool.New()
|
listenICMPEnabled = abool.New()
|
||||||
|
|
||||||
// listenICMPInput is created for every use of the ICMP listenting system.
|
// listenICMPInput is created for every use of the ICMP listenting system.
|
||||||
listenICMPInput chan packet.Packet
|
listenICMPInput chan packet.Packet
|
||||||
listenICMPInputLock sync.Mutex
|
listenICMPInputTargetIP net.IP
|
||||||
|
listenICMPInputLock sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListenToICMP returns a new channel for listenting to icmp packets. Please
|
// ListenToICMP returns a new channel for listenting to icmp packets. Please
|
||||||
// note that any icmp packet will be passed and filtering must be done on
|
// note that any icmp packet will be passed and filtering must be done on
|
||||||
// the side of the caller. The caller must call the returned done function when
|
// the side of the caller. The caller must call the returned done function when
|
||||||
// done with the listener.
|
// done with the listener.
|
||||||
func ListenToICMP() (packets chan packet.Packet, done func()) {
|
func ListenToICMP(targetIP net.IP) (packets chan packet.Packet, done func()) {
|
||||||
// Lock for single use.
|
// Lock for single use.
|
||||||
listenICMPLock.Lock()
|
listenICMPLock.Lock()
|
||||||
|
|
||||||
// Create new input channel.
|
// Create new input channel.
|
||||||
listenICMPInputLock.Lock()
|
listenICMPInputLock.Lock()
|
||||||
listenICMPInput = make(chan packet.Packet, 100)
|
listenICMPInput = make(chan packet.Packet, 100)
|
||||||
|
listenICMPInputTargetIP = targetIP
|
||||||
listenICMPEnabled.Set()
|
listenICMPEnabled.Set()
|
||||||
listenICMPInputLock.Unlock()
|
listenICMPInputLock.Unlock()
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ func ListenToICMP() (packets chan packet.Packet, done func()) {
|
|||||||
// Close input channel.
|
// Close input channel.
|
||||||
listenICMPInputLock.Lock()
|
listenICMPInputLock.Lock()
|
||||||
listenICMPEnabled.UnSet()
|
listenICMPEnabled.UnSet()
|
||||||
|
listenICMPInputTargetIP = nil
|
||||||
close(listenICMPInput)
|
close(listenICMPInput)
|
||||||
listenICMPInputLock.Unlock()
|
listenICMPInputLock.Unlock()
|
||||||
}
|
}
|
||||||
@@ -71,15 +75,14 @@ func SubmitPacketToICMPListener(pkt packet.Packet) (submitted bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slow path.
|
// Slow path.
|
||||||
submitPacketToICMPListenerSlow(pkt)
|
return submitPacketToICMPListenerSlow(pkt)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func submitPacketToICMPListenerSlow(pkt packet.Packet) {
|
func submitPacketToICMPListenerSlow(pkt packet.Packet) (submitted bool) {
|
||||||
// Make sure the payload is available.
|
// Make sure the payload is available.
|
||||||
if err := pkt.LoadPacketData(); err != nil {
|
if err := pkt.LoadPacketData(); err != nil {
|
||||||
log.Warningf("netenv: failed to get payload for ICMP listener: %s", err)
|
log.Warningf("netenv: failed to get payload for ICMP listener: %s", err)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to input channel.
|
// Send to input channel.
|
||||||
@@ -88,7 +91,14 @@ func submitPacketToICMPListenerSlow(pkt packet.Packet) {
|
|||||||
|
|
||||||
// Check if still enabled.
|
// Check if still enabled.
|
||||||
if !listenICMPEnabled.IsSet() {
|
if !listenICMPEnabled.IsSet() {
|
||||||
return
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only listen for outbound packets to the target IP.
|
||||||
|
if pkt.IsOutbound() &&
|
||||||
|
listenICMPInputTargetIP != nil &&
|
||||||
|
!pkt.Info().Dst.Equal(listenICMPInputTargetIP) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to channel, if possible.
|
// Send to channel, if possible.
|
||||||
@@ -97,4 +107,5 @@ func submitPacketToICMPListenerSlow(pkt packet.Packet) {
|
|||||||
default:
|
default:
|
||||||
log.Warning("netenv: failed to send packet payload to ICMP listener: channel full")
|
log.Warning("netenv: failed to send packet payload to ICMP listener: channel full")
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// locationTestingIPv4 holds the IP address of the server that should be
|
||||||
|
// tracerouted to find the location of the device. The ping will never reach
|
||||||
|
// the destination in most cases.
|
||||||
|
// The selection of this IP requires sensitivity, as the IP address must be
|
||||||
|
// far enough away to produce good results.
|
||||||
|
// At the same time, the IP address should be common and not raise attention.
|
||||||
locationTestingIPv4 = "1.1.1.1"
|
locationTestingIPv4 = "1.1.1.1"
|
||||||
locationTestingIPv4Addr *net.IPAddr
|
locationTestingIPv4Addr *net.IPAddr
|
||||||
|
|
||||||
@@ -162,10 +168,10 @@ func (a sortLocationsByAccuracy) Len() int { return len(a) }
|
|||||||
func (a sortLocationsByAccuracy) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a sortLocationsByAccuracy) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a sortLocationsByAccuracy) Less(i, j int) bool { return a[j].IsMoreAccurateThan(a[i]) }
|
func (a sortLocationsByAccuracy) Less(i, j int) bool { return a[j].IsMoreAccurateThan(a[i]) }
|
||||||
|
|
||||||
func SetInternetLocation(ip net.IP, source DeviceLocationSource) (ok bool) {
|
func SetInternetLocation(ip net.IP, source DeviceLocationSource) (dl *DeviceLocation, ok bool) {
|
||||||
// Check if IP is global.
|
// Check if IP is global.
|
||||||
if netutils.GetIPScope(ip) != netutils.Global {
|
if netutils.GetIPScope(ip) != netutils.Global {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new location.
|
// Create new location.
|
||||||
@@ -188,29 +194,32 @@ func SetInternetLocation(ip net.IP, source DeviceLocationSource) (ok bool) {
|
|||||||
loc.Location = geoLoc
|
loc.Location = geoLoc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addLocation(loc)
|
||||||
|
return loc, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLocation(dl *DeviceLocation) {
|
||||||
locationsLock.Lock()
|
locationsLock.Lock()
|
||||||
defer locationsLock.Unlock()
|
defer locationsLock.Unlock()
|
||||||
|
|
||||||
// Add to locations, if better.
|
// Add to locations, if better.
|
||||||
var exists bool
|
var exists bool
|
||||||
for i, existing := range locations.All {
|
for i, existing := range locations.All {
|
||||||
if ip.Equal(existing.IP) {
|
if (dl.IP == nil && existing.IP == nil) || dl.IP.Equal(existing.IP) {
|
||||||
exists = true
|
exists = true
|
||||||
if loc.IsMoreAccurateThan(existing) {
|
if dl.IsMoreAccurateThan(existing) {
|
||||||
// Replace
|
// Replace
|
||||||
locations.All[i] = loc
|
locations.All[i] = dl
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
locations.All = append(locations.All, loc)
|
locations.All = append(locations.All, dl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort locations.
|
// Sort locations.
|
||||||
sort.Sort(sortLocationsByAccuracy(locations.All))
|
sort.Sort(sortLocationsByAccuracy(locations.All))
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEPRECATED: Please use GetInternetLocation instead.
|
// DEPRECATED: Please use GetInternetLocation instead.
|
||||||
@@ -292,20 +301,18 @@ func getLocationFromUPnP() (ok bool) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func getLocationFromTraceroute() (v4ok bool) {
|
func getLocationFromTraceroute() (dl *DeviceLocation, err error) {
|
||||||
// Create connection.
|
// Create connection.
|
||||||
conn, err := net.ListenPacket("ip4:icmp", "")
|
conn, err := net.ListenPacket("ip4:icmp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("netenv: location: failed to open icmp conn: %s", err)
|
return nil, fmt.Errorf("failed to open icmp conn: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
v4Conn := ipv4.NewPacketConn(conn)
|
v4Conn := ipv4.NewPacketConn(conn)
|
||||||
|
|
||||||
// Generate a random ID for the ICMP packets.
|
// Generate a random ID for the ICMP packets.
|
||||||
generatedID, err := rng.Number(0xFFFF) // uint16
|
generatedID, err := rng.Number(0xFFFF) // uint16
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("netenv: location: failed to generate icmp msg ID: %s", err)
|
return nil, fmt.Errorf("failed to generate icmp msg ID: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
msgID := int(generatedID)
|
msgID := int(generatedID)
|
||||||
var msgSeq int
|
var msgSeq int
|
||||||
@@ -323,7 +330,7 @@ func getLocationFromTraceroute() (v4ok bool) {
|
|||||||
maxHops := 4 // add one for every reply that is not global
|
maxHops := 4 // add one for every reply that is not global
|
||||||
|
|
||||||
// Get additional listener for ICMP messages via the firewall.
|
// Get additional listener for ICMP messages via the firewall.
|
||||||
icmpPacketsViaFirewall, doneWithListeningToICMP := ListenToICMP()
|
icmpPacketsViaFirewall, doneWithListeningToICMP := ListenToICMP(locationTestingIPv4Addr.IP)
|
||||||
defer doneWithListeningToICMP()
|
defer doneWithListeningToICMP()
|
||||||
|
|
||||||
nextHop:
|
nextHop:
|
||||||
@@ -339,15 +346,13 @@ nextHop:
|
|||||||
// Make packet data.
|
// Make packet data.
|
||||||
pingPacket, err := pingMessage.Marshal(nil)
|
pingPacket, err := pingMessage.Marshal(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("netenv: location: failed to build icmp packet: %s", err)
|
return nil, fmt.Errorf("failed to build icmp packet: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set TTL on IP packet.
|
// Set TTL on IP packet.
|
||||||
err = v4Conn.SetTTL(i)
|
err = v4Conn.SetTTL(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("netenv: location: failed to set icmp packet TTL: %s", err)
|
return nil, fmt.Errorf("failed to set icmp packet TTL: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send ICMP packet.
|
// Send ICMP packet.
|
||||||
@@ -357,8 +362,7 @@ nextHop:
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Warningf("netenv: location: failed to send icmp packet: %s", err)
|
return nil, fmt.Errorf("failed to send icmp packet: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for replies of the ICMP packet.
|
// Listen for replies of the ICMP packet.
|
||||||
@@ -381,7 +385,7 @@ nextHop:
|
|||||||
}
|
}
|
||||||
// We received a reply, so we did not trigger a time exceeded response on the way.
|
// We received a reply, so we did not trigger a time exceeded response on the way.
|
||||||
// This means we were not able to find the nearest router to us.
|
// This means we were not able to find the nearest router to us.
|
||||||
return false
|
return nil, errors.New("received final echo reply without time exceeded messages")
|
||||||
case layers.ICMPv4TypeDestinationUnreachable,
|
case layers.ICMPv4TypeDestinationUnreachable,
|
||||||
layers.ICMPv4TypeTimeExceeded:
|
layers.ICMPv4TypeTimeExceeded:
|
||||||
// Continue processing.
|
// Continue processing.
|
||||||
@@ -413,13 +417,17 @@ nextHop:
|
|||||||
switch icmpPacket.TypeCode.Type() {
|
switch icmpPacket.TypeCode.Type() {
|
||||||
case layers.ICMPv4TypeDestinationUnreachable:
|
case layers.ICMPv4TypeDestinationUnreachable:
|
||||||
// We have received a valid destination unreachable response, abort.
|
// We have received a valid destination unreachable response, abort.
|
||||||
return false
|
return nil, errors.New("destination unreachable")
|
||||||
|
|
||||||
case layers.ICMPv4TypeTimeExceeded:
|
case layers.ICMPv4TypeTimeExceeded:
|
||||||
// We have received a valid time exceeded error.
|
// We have received a valid time exceeded error.
|
||||||
// If message came from a global unicast, us it!
|
// If message came from a global unicast, us it!
|
||||||
if netutils.GetIPScope(remoteIP) == netutils.Global {
|
if netutils.GetIPScope(remoteIP) == netutils.Global {
|
||||||
return SetInternetLocation(remoteIP, SourceTraceroute)
|
dl, ok := SetInternetLocation(remoteIP, SourceTraceroute)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid IP address")
|
||||||
|
}
|
||||||
|
return dl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, continue.
|
// Otherwise, continue.
|
||||||
@@ -430,7 +438,7 @@ nextHop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We did not receive anything actionable.
|
// We did not receive anything actionable.
|
||||||
return false
|
return nil, errors.New("did not receive any actionable ICMP reply")
|
||||||
}
|
}
|
||||||
|
|
||||||
func recvICMP(currentHop int, icmpPacketsViaFirewall chan packet.Packet) (
|
func recvICMP(currentHop int, icmpPacketsViaFirewall chan packet.Packet) (
|
||||||
@@ -455,7 +463,7 @@ func recvICMP(currentHop int, icmpPacketsViaFirewall chan packet.Packet) (
|
|||||||
}
|
}
|
||||||
return pkt.Info().RemoteIP(), icmp4, true
|
return pkt.Info().RemoteIP(), icmp4, true
|
||||||
|
|
||||||
case <-time.After(time.Duration(currentHop*10+50) * time.Millisecond):
|
case <-time.After(time.Duration(currentHop*20+100) * time.Millisecond):
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user