From 43cfba8445a11aa83a9004e7ebe9b12f100c7b4c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 20 Mar 2021 22:07:17 +0100 Subject: [PATCH] Add IP scoping data to entity.Entity and network.Connection --- firewall/interception.go | 2 +- intel/entity.go | 15 +++++++- netenv/addresses.go | 6 +-- netenv/location.go | 2 +- netenv/online-status.go | 4 +- network/connection.go | 30 ++++++++++----- network/netutils/ip.go | 55 +++++++++++++++------------- network/netutils/ip_test.go | 36 +++++++++--------- network/state/lookup.go | 2 +- profile/endpoints/endpoint-scopes.go | 3 +- 10 files changed, 92 insertions(+), 63 deletions(-) diff --git a/firewall/interception.go b/firewall/interception.go index a342b13a..30e16b7e 100644 --- a/firewall/interception.go +++ b/firewall/interception.go @@ -286,7 +286,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { // TODO: add implementation for forced tunneling if pkt.IsOutbound() && captain.ClientReady() && - netutils.IPIsGlobal(conn.Entity.IP) && + conn.Entity.IPScope.IsGlobal() && conn.Verdict == network.VerdictAccept { // try to tunnel err := sluice.AwaitRequest(pkt.Info(), conn.Entity.Domain) diff --git a/intel/entity.go b/intel/entity.go index c98126f0..d03cc3b6 100644 --- a/intel/entity.go +++ b/intel/entity.go @@ -58,6 +58,9 @@ type Entity struct { // set, IP has been resolved by following all CNAMEs. IP net.IP + // IPScope holds the network scope of the IP. + IPScope netutils.IPScope + // Country holds the country the IP address (ASN) is // located in. Country string @@ -65,6 +68,9 @@ type Entity struct { // ASN holds the autonomous system number of the IP. ASN uint + // ASOrg holds the owner's name of the autonomous system. + ASOrg string + location *geoip.Location // BlockedByLists holds list source IDs that @@ -95,6 +101,12 @@ func (e *Entity) Init() *Entity { return e } +// SetIP sets the IP address together with its network scope. +func (e *Entity) SetIP(ip net.IP) { + e.IP = ip + e.IPScope = netutils.GetIPScope(ip) +} + // SetDstPort sets the destination port. func (e *Entity) SetDstPort(dstPort uint16) { e.dstPort = dstPort @@ -229,6 +241,7 @@ func (e *Entity) getLocation(ctx context.Context) { e.location = loc e.Country = loc.Country.ISOCode e.ASN = loc.AutonomousSystemNumber + e.ASOrg = loc.AutonomousSystemOrganization }) } @@ -422,7 +435,7 @@ func (e *Entity) getIPLists(ctx context.Context) { } // only load lists for IP addresses that are classified as global. - if netutils.ClassifyIP(ip) != netutils.Global { + if !e.IPScope.IsGlobal() { return } diff --git a/netenv/addresses.go b/netenv/addresses.go index 48e9d380..f0edb84b 100644 --- a/netenv/addresses.go +++ b/netenv/addresses.go @@ -38,12 +38,12 @@ func GetAssignedGlobalAddresses() (ipv4 []net.IP, ipv6 []net.IP, err error) { return nil, nil, err } for _, ip4 := range allv4 { - if netutils.IPIsGlobal(ip4) { + if netutils.GetIPScope(ip4).IsGlobal() { ipv4 = append(ipv4, ip4) } } for _, ip6 := range allv6 { - if netutils.IPIsGlobal(ip6) { + if netutils.GetIPScope(ip6).IsGlobal() { ipv6 = append(ipv6, ip6) } } @@ -59,7 +59,7 @@ var ( // Broadcast or multicast addresses will never match, even if valid in in use. func IsMyIP(ip net.IP) (yes bool, err error) { // Check for IPs that don't need extra checks. - switch netutils.ClassifyIP(ip) { + switch netutils.GetIPScope(ip) { case netutils.HostLocal: return true, nil case netutils.LocalMulticast, netutils.GlobalMulticast: diff --git a/netenv/location.go b/netenv/location.go index 8fc67339..5d38bbee 100644 --- a/netenv/location.go +++ b/netenv/location.go @@ -130,7 +130,7 @@ next: } // If we received something from a global IP address, we have succeeded and can return immediately. - if netutils.IPIsGlobal(addr.IP) { + if netutils.GetIPScope(addr.IP).IsGlobal() { return addr.IP, nil } diff --git a/netenv/online-status.go b/netenv/online-status.go index 44d5a0ff..cbc5f26f 100644 --- a/netenv/online-status.go +++ b/netenv/online-status.go @@ -356,7 +356,7 @@ func checkOnlineStatus(ctx context.Context) { } else { var lan bool for _, ip := range ipv4 { - switch netutils.ClassifyIP(ip) { + switch netutils.GetIPScope(ip) { case netutils.SiteLocal: lan = true case netutils.Global: @@ -366,7 +366,7 @@ func checkOnlineStatus(ctx context.Context) { } } for _, ip := range ipv6 { - switch netutils.ClassifyIP(ip) { + switch netutils.GetIPScope(ip) { case netutils.SiteLocal, netutils.Global: // IPv6 global addresses are also used in local networks lan = true diff --git a/network/connection.go b/network/connection.go index 750de2a6..5ae6bd72 100644 --- a/network/connection.go +++ b/network/connection.go @@ -74,6 +74,8 @@ type Connection struct { //nolint:maligned // TODO: fix alignment // set for connections created from DNS requests. LocalIP is // considered immutable once a connection object has been created. LocalIP net.IP + // LocalIPScope holds the network scope of the local IP. + LocalIPScope netutils.IPScope // LocalPort holds the local port of the connection. It is not // set for connections created from DNS requests. LocalPort is // considered immutable once a connection object has been created. @@ -279,7 +281,14 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection { if inbound { // inbound connection - switch netutils.ClassifyIP(pkt.Info().Src) { + entity = &intel.Entity{ + Protocol: uint8(pkt.Info().Protocol), + Port: pkt.Info().SrcPort, + } + entity.SetIP(pkt.Info().Src) + entity.SetDstPort(pkt.Info().DstPort) + + switch entity.IPScope { case netutils.HostLocal: scope = IncomingHost case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: @@ -292,21 +301,15 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection { default: scope = IncomingInvalid } - entity = &intel.Entity{ - IP: pkt.Info().Src, - Protocol: uint8(pkt.Info().Protocol), - Port: pkt.Info().SrcPort, - } - entity.SetDstPort(pkt.Info().DstPort) } else { // outbound connection entity = &intel.Entity{ - IP: pkt.Info().Dst, Protocol: uint8(pkt.Info().Protocol), Port: pkt.Info().DstPort, } + entity.SetIP(pkt.Info().Dst) entity.SetDstPort(entity.Port) // check if we can find a domain for that IP @@ -331,7 +334,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection { if scope == "" { // outbound direct (possibly P2P) connection - switch netutils.ClassifyIP(pkt.Info().Dst) { + switch entity.IPScope { case netutils.HostLocal: scope = PeerHost case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: @@ -356,7 +359,6 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection { Inbound: inbound, // local endpoint IPProtocol: pkt.Info().Protocol, - LocalIP: pkt.Info().LocalIP(), LocalPort: pkt.Info().LocalPort(), ProcessContext: getProcessContext(pkt.Ctx(), proc), process: proc, @@ -366,6 +368,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection { Started: time.Now().Unix(), ProfileRevisionCounter: proc.Profile().RevisionCnt(), } + newConn.SetLocalIP(pkt.Info().LocalIP()) // Inherit internal status of profile. if localProfile := proc.Profile().LocalProfile(); localProfile != nil { @@ -380,6 +383,13 @@ func GetConnection(id string) (*Connection, bool) { return conns.get(id) } +// SetLocalIP sets the local IP address together with its network scope. The +// connection is not locked for this. +func (conn *Connection) SetLocalIP(ip net.IP) { + conn.LocalIP = ip + conn.LocalIPScope = netutils.GetIPScope(ip) +} + // AcceptWithContext accepts the connection. func (conn *Connection) AcceptWithContext(reason, reasonOptionKey string, ctx interface{}) { if !conn.SetVerdict(VerdictAccept, reason, reasonOptionKey, ctx) { diff --git a/network/netutils/ip.go b/network/netutils/ip.go index 8e40447c..0b058087 100644 --- a/network/netutils/ip.go +++ b/network/netutils/ip.go @@ -2,19 +2,29 @@ package netutils import "net" -// IP classifications +// IPScope is the scope of the IP address. +type IPScope int8 + +// Defined IP Scopes. const ( - HostLocal int8 = iota + Invalid IPScope = iota - 1 + Undefined + HostLocal LinkLocal SiteLocal Global LocalMulticast GlobalMulticast - Invalid int8 = -1 ) -// ClassifyIP returns the classification for the given IP address. -func ClassifyIP(ip net.IP) int8 { //nolint:gocognit +// ClassifyIP returns the network scope of the given IP address. +// Deprecated: Please use the new GetIPScope instead. +func ClassifyIP(ip net.IP) IPScope { + return GetIPScope(ip) +} + +// GetIPScope returns the network scope of the given IP address. +func GetIPScope(ip net.IP) IPScope { //nolint:gocognit if ip4 := ip.To4(); ip4 != nil { // IPv4 switch { @@ -76,32 +86,27 @@ func ClassifyIP(ip net.IP) int8 { //nolint:gocognit return Invalid } -// IPIsLocalhost returns whether the IP refers to the host itself. -func IPIsLocalhost(ip net.IP) bool { - return ClassifyIP(ip) == HostLocal +// IsLocalhost returns whether the IP refers to the host itself. +func (scope IPScope) IsLocalhost() bool { + return scope == HostLocal } -// IPIsLAN returns true if the given IP is a site-local or link-local address. -func IPIsLAN(ip net.IP) bool { - switch ClassifyIP(ip) { - case SiteLocal, LinkLocal: +// IsLAN returns true if the scope is site-local or link-local. +func (scope IPScope) IsLAN() bool { + switch scope { + case SiteLocal, LinkLocal, LocalMulticast: return true default: return false } } -// IPIsGlobal returns true if the given IP is a global address. -func IPIsGlobal(ip net.IP) bool { - return ClassifyIP(ip) == Global -} - -// IPIsLinkLocal returns true if the given IP is a link-local address. -func IPIsLinkLocal(ip net.IP) bool { - return ClassifyIP(ip) == LinkLocal -} - -// IPIsSiteLocal returns true if the given IP is a site-local address. -func IPIsSiteLocal(ip net.IP) bool { - return ClassifyIP(ip) == SiteLocal +// IsGlobal returns true if the scope is global. +func (scope IPScope) IsGlobal() bool { + switch scope { + case Global, GlobalMulticast: + return true + default: + return false + } } diff --git a/network/netutils/ip_test.go b/network/netutils/ip_test.go index 1d40f301..02ef2051 100644 --- a/network/netutils/ip_test.go +++ b/network/netutils/ip_test.go @@ -5,26 +5,30 @@ import ( "testing" ) -func TestIPClassification(t *testing.T) { - testClassification(t, net.IPv4(71, 87, 113, 211), Global) - testClassification(t, net.IPv4(127, 0, 0, 1), HostLocal) - testClassification(t, net.IPv4(127, 255, 255, 1), HostLocal) - testClassification(t, net.IPv4(192, 168, 172, 24), SiteLocal) - testClassification(t, net.IPv4(172, 15, 1, 1), Global) - testClassification(t, net.IPv4(172, 16, 1, 1), SiteLocal) - testClassification(t, net.IPv4(172, 31, 1, 1), SiteLocal) - testClassification(t, net.IPv4(172, 32, 1, 1), Global) +func TestIPScope(t *testing.T) { + testScope(t, net.IPv4(71, 87, 113, 211), Global) + testScope(t, net.IPv4(127, 0, 0, 1), HostLocal) + testScope(t, net.IPv4(127, 255, 255, 1), HostLocal) + testScope(t, net.IPv4(192, 168, 172, 24), SiteLocal) + testScope(t, net.IPv4(172, 15, 1, 1), Global) + testScope(t, net.IPv4(172, 16, 1, 1), SiteLocal) + testScope(t, net.IPv4(172, 31, 1, 1), SiteLocal) + testScope(t, net.IPv4(172, 32, 1, 1), Global) } -func testClassification(t *testing.T, ip net.IP, expectedClassification int8) { - c := ClassifyIP(ip) - if c != expectedClassification { - t.Errorf("%s is %s, expected %s", ip, classificationString(c), classificationString(expectedClassification)) +func testScope(t *testing.T, ip net.IP, expectedScope IPScope) { + c := GetIPScope(ip) + if c != expectedScope { + t.Errorf("%s is %s, expected %s", ip, scopeName(c), scopeName(expectedScope)) } } -func classificationString(c int8) string { +func scopeName(c IPScope) string { switch c { + case Invalid: + return "invalid" + case Undefined: + return "undefined" case HostLocal: return "hostLocal" case LinkLocal: @@ -37,9 +41,7 @@ func classificationString(c int8) string { return "localMulticast" case GlobalMulticast: return "globalMulticast" - case Invalid: - return "invalid" default: - return "unknown" + return "undefined" } } diff --git a/network/state/lookup.go b/network/state/lookup.go index 1d2f11ad..fdd8c3d1 100644 --- a/network/state/lookup.go +++ b/network/state/lookup.go @@ -151,7 +151,7 @@ func (table *udpTable) lookup(pktInfo *packet.Info, fast bool) ( // attribute an incoming broadcast/multicast packet to the wrong process if // there are multiple processes listening on the same local port, but // binding to different addresses. This highly unusual for clients. - isInboundMulticast := pktInfo.Inbound && netutils.ClassifyIP(pktInfo.LocalIP()) == netutils.LocalMulticast + isInboundMulticast := pktInfo.Inbound && netutils.GetIPScope(pktInfo.LocalIP()) == netutils.LocalMulticast // Search for the socket until found. for i := 1; i <= lookupRetries; i++ { diff --git a/profile/endpoints/endpoint-scopes.go b/profile/endpoints/endpoint-scopes.go index 6f1c2f27..c6f05529 100644 --- a/profile/endpoints/endpoint-scopes.go +++ b/profile/endpoints/endpoint-scopes.go @@ -36,9 +36,8 @@ func (ep *EndpointScope) Matches(_ context.Context, entity *intel.Entity) (EPRes return Undeterminable, nil } - classification := netutils.ClassifyIP(entity.IP) var scope uint8 - switch classification { + switch entity.IPScope { case netutils.HostLocal: scope = scopeLocalhost case netutils.LinkLocal: