From 87a55541b2b464c8459fceffc8dfb57d47fc706d Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 15 May 2020 22:40:05 +0200 Subject: [PATCH] Add endpoint type network scope Also, update default service endpoint list configuration to allow localhost --- profile/config.go | 56 +++++--------- profile/endpoints/endpoint-scopes.go | 112 +++++++++++++++++++++++++++ profile/endpoints/endpoint.go | 4 + profile/endpoints/endpoints_test.go | 19 +++++ 4 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 profile/endpoints/endpoint-scopes.go diff --git a/profile/config.go b/profile/config.go index ad1434fd..60a63070 100644 --- a/profile/config.go +++ b/profile/config.go @@ -121,17 +121,12 @@ func registerConfiguration() error { cfgOptionDisableAutoPermit = config.Concurrent.GetAsInt(CfgOptionDisableAutoPermitKey, int64(status.SecurityLevelsAll)) cfgIntOptions[CfgOptionDisableAutoPermitKey] = cfgOptionDisableAutoPermit - // Endpoint Filter List - err = config.Register(&config.Option{ - Name: "Endpoint Filter List", - Key: CfgOptionEndpointsKey, - Description: "Filter outgoing connections by matching the destination endpoint. Network Scope restrictions still apply.", - Help: `Format: + filterListHelp := `Format: Permission: "+": permit "-": block Host Matching: - IP, CIDR, Country Code, ASN, Filterlist, "*" for any + IP, CIDR, Country Code, ASN, Filterlist, Network Scope, "*" for any Domains: "example.com": exact match ".example.com": exact match + subdomains @@ -144,11 +139,20 @@ func registerConfiguration() error { Examples: + .example.com */HTTP - .example.com - + 192.168.0.1/24 + + 192.168.0.1 + + 192.168.1.1/24 + + Localhost,LAN + - AS123456789 - L:MAL - - AS0 + AT - - *`, + - *` + + // Endpoint Filter List + err = config.Register(&config.Option{ + Name: "Endpoint Filter List", + Key: CfgOptionEndpointsKey, + Description: "Filter outgoing connections by matching the destination endpoint. Network Scope restrictions still apply.", + Help: filterListHelp, Order: cfgOptionEndpointsOrder, OptType: config.OptTypeStringArray, DefaultValue: []string{}, @@ -163,35 +167,13 @@ Examples: // Service Endpoint Filter List err = config.Register(&config.Option{ - Name: "Service Endpoint Filter List", - Key: CfgOptionServiceEndpointsKey, - Description: "Filter incoming connections by matching the source endpoint. Network Scope restrictions and the inbound permission still apply. Also not that the implicit default action of this list is to always block.", - Help: `Format: - Permission: - "+": permit - "-": block - Host Matching: - IP, CIDR, Country Code, ASN, Filterlist, "*" for any - Domains: - "example.com": exact match - ".example.com": exact match + subdomains - "*xample.com": prefix wildcard - "example.*": suffix wildcard - "*example*": prefix and suffix wildcard - Protocol and Port Matching (optional): - / - -Examples: - + .example.com */HTTP - - .example.com - + 192.168.0.1/24 - - L:MAL - - AS0 - + AT - - *`, + Name: "Service Endpoint Filter List", + Key: CfgOptionServiceEndpointsKey, + Description: "Filter incoming connections by matching the source endpoint. Network Scope restrictions and the inbound permission still apply. Also not that the implicit default action of this list is to always block.", + Help: filterListHelp, Order: cfgOptionServiceEndpointsOrder, OptType: config.OptTypeStringArray, - DefaultValue: []string{}, + DefaultValue: []string{"+ Localhost"}, ExternalOptType: "endpoint list", ValidationRegex: `^(\+|\-) [A-z0-9\.:\-*/]+( [A-z0-9/]+)?$`, }) diff --git a/profile/endpoints/endpoint-scopes.go b/profile/endpoints/endpoint-scopes.go new file mode 100644 index 00000000..1c73aebe --- /dev/null +++ b/profile/endpoints/endpoint-scopes.go @@ -0,0 +1,112 @@ +package endpoints + +import ( + "strings" + + "github.com/safing/portmaster/network/netutils" + + "github.com/safing/portmaster/intel" +) + +const ( + scopeLocalhost = 1 + scopeLocalhostName = "Localhost" + scopeLocalhostMatcher = "localhost" + + scopeLAN = 2 + scopeLANName = "LAN" + scopeLANMatcher = "lan" + + scopeInternet = 4 + scopeInternetName = "Internet" + scopeInternetMatcher = "internet" +) + +// EndpointScope matches network scopes. +type EndpointScope struct { + EndpointBase + + scopes uint8 +} + +// Localhost +// LAN +// Internet + +// Matches checks whether the given entity matches this endpoint definition. +func (ep *EndpointScope) Matches(entity *intel.Entity) (EPResult, Reason) { + if entity.IP == nil { + return Undeterminable, nil + } + + classification := netutils.ClassifyIP(entity.IP) + var scope uint8 + switch classification { + case netutils.HostLocal: + scope = scopeLocalhost + case netutils.LinkLocal: + scope = scopeLAN + case netutils.SiteLocal: + scope = scopeLAN + case netutils.Global: + scope = scopeInternet + case netutils.LocalMulticast: + scope = scopeLAN + case netutils.GlobalMulticast: + scope = scopeInternet + } + + if ep.scopes&scope > 0 { + return ep.match(ep, entity, ep.Scopes(), "scope matches") + } + return NoMatch, nil +} + +// Scopes returns the string representation of all scopes. +func (ep *EndpointScope) Scopes() string { + if ep.scopes == 3 || ep.scopes > 4 { + // single scope + switch ep.scopes { + case scopeLocalhost: + return scopeLocalhostName + case scopeLAN: + return scopeLANName + case scopeInternet: + return scopeInternetName + } + } + + // multiple scopes + var s []string + if ep.scopes&scopeLocalhost > 0 { + s = append(s, scopeLocalhostName) + } + if ep.scopes&scopeLAN > 0 { + s = append(s, scopeLANName) + } + if ep.scopes&scopeInternet > 0 { + s = append(s, scopeInternetName) + } + return strings.Join(s, ",") +} + +func (ep *EndpointScope) String() string { + return ep.renderPPP(ep.Scopes()) +} + +func parseTypeScope(fields []string) (Endpoint, error) { + ep := &EndpointScope{} + for _, val := range strings.Split(strings.ToLower(fields[1]), ",") { + switch val { + case scopeLocalhostMatcher: + ep.scopes &= scopeLocalhost + case scopeLANMatcher: + ep.scopes &= scopeLAN + case scopeInternetMatcher: + ep.scopes &= scopeInternet + default: + return nil, nil + } + } + return ep.parsePPP(ep, fields) +} diff --git a/profile/endpoints/endpoint.go b/profile/endpoints/endpoint.go index 76847ac7..4e73d1d4 100644 --- a/profile/endpoints/endpoint.go +++ b/profile/endpoints/endpoint.go @@ -231,6 +231,10 @@ func parseEndpoint(value string) (endpoint Endpoint, err error) { if endpoint, err = parseTypeASN(fields); endpoint != nil || err != nil { return } + // scopes + if endpoint, err = parseTypeScope(fields); endpoint != nil || err != nil { + return + } // lists if endpoint, err = parseTypeList(fields); endpoint != nil || err != nil { return diff --git a/profile/endpoints/endpoints_test.go b/profile/endpoints/endpoints_test.go index 0eb4e2e1..ad23d352 100644 --- a/profile/endpoints/endpoints_test.go +++ b/profile/endpoints/endpoints_test.go @@ -342,7 +342,26 @@ func TestEndpointMatching(t *testing.T) { IP: net.ParseIP("151.101.1.164"), // nytimes.com }).Init(), NoMatch) + // Scope + + ep, err = parseEndpoint("+ Localhost,LAN") + if err != nil { + t.Fatal(err) + } + + testEndpointMatch(t, ep, (&intel.Entity{ + IP: net.ParseIP("192.168.0.1"), + }).Init(), Permitted) + testEndpointMatch(t, ep, (&intel.Entity{ + IP: net.ParseIP("151.101.1.164"), // nytimes.com + }).Init(), NoMatch) + // Lists + + ep, err = parseEndpoint("+ L:A,B,C") + if err != nil { + t.Fatal(err) + } // TODO: write test for lists matcher }