Add endpoint type network scope

Also, update default service endpoint list configuration to allow localhost
This commit is contained in:
Daniel 2020-05-15 22:40:05 +02:00
parent 635d5770d1
commit 87a55541b2
4 changed files with 154 additions and 37 deletions

View file

@ -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):
<protocol>/<port>
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/]+)?$`,
})

View file

@ -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)
}

View file

@ -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

View file

@ -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
}