diff --git a/firewall/api.go b/firewall/api.go index ba4c0585..b17efe6d 100644 --- a/firewall/api.go +++ b/firewall/api.go @@ -2,12 +2,10 @@ package firewall import ( "context" - "errors" "fmt" "net" "net/http" "path/filepath" - "strconv" "strings" "time" @@ -16,6 +14,7 @@ import ( "github.com/safing/portbase/log" "github.com/safing/portbase/utils" "github.com/safing/portmaster/netenv" + "github.com/safing/portmaster/network/netutils" "github.com/safing/portmaster/network/packet" "github.com/safing/portmaster/process" "github.com/safing/portmaster/updates" @@ -53,7 +52,7 @@ func prepAPIAuth() error { func startAPIAuth() { var err error - apiIP, apiPort, err = parseHostPort(apiListenAddress()) + apiIP, apiPort, err = netutils.ParseIPPort(apiListenAddress()) if err != nil { log.Warningf("filter: failed to parse API address for improved api auth mechanism: %s", err) return @@ -71,13 +70,13 @@ func apiAuthenticator(r *http.Request, s *http.Server) (token *api.AuthToken, er } // get local IP/Port - localIP, localPort, err := parseHostPort(s.Addr) + localIP, localPort, err := netutils.ParseIPPort(s.Addr) if err != nil { return nil, fmt.Errorf("failed to get local IP/Port: %w", err) } // get remote IP/Port - remoteIP, remotePort, err := parseHostPort(r.RemoteAddr) + remoteIP, remotePort, err := netutils.ParseIPPort(r.RemoteAddr) if err != nil { return nil, fmt.Errorf("failed to get remote IP/Port: %w", err) } @@ -214,22 +213,3 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo ) } } - -func parseHostPort(address string) (net.IP, uint16, error) { - ipString, portString, err := net.SplitHostPort(address) - if err != nil { - return nil, 0, err - } - - ip := net.ParseIP(ipString) - if ip == nil { - return nil, 0, errors.New("invalid IP address") - } - - port, err := strconv.ParseUint(portString, 10, 16) - if err != nil { - return nil, 0, err - } - - return ip, uint16(port), nil -} diff --git a/go.mod b/go.mod index 8ec3182d..5244d774 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/safing/jess v0.3.1 github.com/safing/portbase v0.17.3 github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626 - github.com/safing/spn v0.6.17 + github.com/safing/spn v0.6.18-prep github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/cobra v1.7.0 github.com/spkg/zipfs v0.7.1 diff --git a/go.sum b/go.sum index d17847ba..b220c683 100644 --- a/go.sum +++ b/go.sum @@ -216,6 +216,8 @@ github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626 h1:ol github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626/go.mod h1:abwyAQrZGemWbSh/aCD9nnkp0SvFFf/mGWkAbOwPnFE= github.com/safing/spn v0.6.17 h1:3Lu1cpTcy8zYhA/2UEfeG08Rx1nlwIj1aobSfNXXgUI= github.com/safing/spn v0.6.17/go.mod h1:2CuZfJJazIYyMDrhiwX2eFal0urQyLiX8rXLvJiCTcw= +github.com/safing/spn v0.6.18-prep h1:e6jjDFVsOh9B7YQLCjfCgqbCHiHOxRdpjXi5gR+85rA= +github.com/safing/spn v0.6.18-prep/go.mod h1:flegLqCJjFQ0uDB39GMWrIttJga5cSeNea2G6XlKRJ0= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0= diff --git a/intel/entity.go b/intel/entity.go index 2e5283f6..b890b1ae 100644 --- a/intel/entity.go +++ b/intel/entity.go @@ -35,6 +35,14 @@ type Entity struct { //nolint:maligned resolveSubDomainLists bool checkCNAMEs bool + // IP is the IP address of the connection. If domain is + // set, IP has been resolved by following all CNAMEs. + IP net.IP + + // IPScope holds the network scope of the IP. + // For DNS requests, this signifies in which scope the DNS request was resolved. + IPScope netutils.IPScope + // Protocol is the protcol number used by the connection. Protocol uint8 @@ -55,14 +63,6 @@ type Entity struct { //nolint:maligned // resolved for Domain. CNAME []string - // IP is the IP address of the connection. If domain is - // set, IP has been resolved by following all CNAMEs. - IP net.IP - - // IPScope holds the network scope of the IP. - // For DNS requests, this signifies in which scope the DNS request was resolved. - IPScope netutils.IPScope - // Country holds the country the IP address (ASN) is // located in. Country string @@ -106,23 +106,28 @@ type Entity struct { //nolint:maligned loadAsnListOnce sync.Once } -// Init initializes the internal state and returns the entity. -func (e *Entity) Init() *Entity { - // for backwards compatibility, remove that one +// Init initializes internal metadata about the entity. +// If the entity does not describe a destination, you can supply a different +// destination port for endpoint matching. +// It returns the entity itself for single line formatting. +func (e *Entity) Init(dstPort uint16) *Entity { + // Get IP scope. + if e.IP != nil { + e.IPScope = netutils.GetIPScope(e.IP) + } else { + e.IPScope = netutils.Undefined + } + + // Set dst port to given value or fall back to entity. + if dstPort > 0 { + e.dstPort = dstPort + } else { + e.dstPort = e.Port + } + 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 -} - // DstPort returns the destination port. func (e *Entity) DstPort() uint16 { return e.dstPort diff --git a/network/connection.go b/network/connection.go index b1d1958b..08390779 100644 --- a/network/connection.go +++ b/network/connection.go @@ -438,12 +438,12 @@ func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) { // Create remote entity. if conn.Entity == nil { // Remote - conn.Entity = &intel.Entity{ + conn.Entity = (&intel.Entity{ + IP: pkt.Info().RemoteIP(), Protocol: uint8(pkt.Info().Protocol), Port: pkt.Info().RemotePort(), - } - conn.Entity.SetIP(pkt.Info().RemoteIP()) - conn.Entity.SetDstPort(pkt.Info().DstPort) + }).Init(pkt.Info().DstPort) + // Local conn.SetLocalIP(pkt.Info().LocalIP()) conn.LocalPort = pkt.Info().LocalPort() diff --git a/network/connection_android.go b/network/connection_android.go index 6fd30157..71b16ed4 100644 --- a/network/connection_android.go +++ b/network/connection_android.go @@ -27,11 +27,11 @@ func NewDefaultConnection(localIP net.IP, localPort uint16, remoteIP net.IP, rem LocalIPScope: netutils.Global, LocalPort: localPort, PID: process.UnidentifiedProcessID, - Entity: &intel.Entity{ - Protocol: uint8(protocol), + Entity: (&intel.Entity{ IP: remoteIP, + Protocol: uint8(protocol), Port: remotePort, - }, + }).Init(0), Resolver: nil, Started: time.Now().Unix(), VerdictPermanent: false, diff --git a/network/netutils/address.go b/network/netutils/address.go index 59d2a262..3d89c39c 100644 --- a/network/netutils/address.go +++ b/network/netutils/address.go @@ -2,39 +2,45 @@ package netutils import ( "errors" - "fmt" "net" "strconv" + + "github.com/safing/portmaster/network/packet" ) var errInvalidIP = errors.New("invalid IP address") -// IPFromAddr extracts or parses the IP address contained in the given address. -func IPFromAddr(addr net.Addr) (net.IP, error) { +// IPPortFromAddr extracts or parses the IP address and port contained in the given address. +func IPPortFromAddr(addr net.Addr) (ip net.IP, port uint16, err error) { // Convert addr to IP if needed. switch v := addr.(type) { case *net.TCPAddr: - return v.IP, nil + return v.IP, uint16(v.Port), nil case *net.UDPAddr: - return v.IP, nil + return v.IP, uint16(v.Port), nil case *net.IPAddr: - return v.IP, nil + return v.IP, 0, nil + case *net.UnixAddr: + return nil, 0, errors.New("unix addresses don't have IPs") default: - // Parse via string. - host, _, err := net.SplitHostPort(addr.String()) - if err != nil { - return nil, fmt.Errorf("failed to split host and port of %q: %w", addr, err) - } - ip := net.ParseIP(host) - if ip == nil { - return nil, fmt.Errorf("address %q does not contain a valid IP address", addr) - } - return ip, nil + return ParseIPPort(addr.String()) } } -// ParseHostPort parses a :port formatted address. -func ParseHostPort(address string) (net.IP, uint16, error) { +// ProtocolFromNetwork returns the protocol from the given net, as used in the "net" golang stdlib. +func ProtocolFromNetwork(net string) (protocol packet.IPProtocol) { + switch net { + case "tcp", "tcp4", "tcp6": + return packet.TCP + case "udp", "udp4", "udp6": + return packet.UDP + default: + return 0 + } +} + +// ParseIPPort parses a :port formatted address. +func ParseIPPort(address string) (net.IP, uint16, error) { ipString, portString, err := net.SplitHostPort(address) if err != nil { return nil, 0, err diff --git a/process/find.go b/process/find.go index 9c96e1fe..c0a209e9 100644 --- a/process/find.go +++ b/process/find.go @@ -115,7 +115,7 @@ func GetNetworkHost(ctx context.Context, remoteIP net.IP) (process *Process, err // GetProcessByRequestOrigin returns the process that initiated the API request ar. func GetProcessByRequestOrigin(ar *api.Request) (*Process, error) { // get remote IP/Port - remoteIP, remotePort, err := netutils.ParseHostPort(ar.RemoteAddr) + remoteIP, remotePort, err := netutils.ParseIPPort(ar.RemoteAddr) if err != nil { return nil, fmt.Errorf("failed to get remote IP/Port: %w", err) } diff --git a/profile/endpoints/endpoints_test.go b/profile/endpoints/endpoints_test.go index 2d3fc76b..342d81d8 100644 --- a/profile/endpoints/endpoints_test.go +++ b/profile/endpoints/endpoints_test.go @@ -19,8 +19,6 @@ func TestMain(m *testing.M) { func testEndpointMatch(t *testing.T, ep Endpoint, entity *intel.Entity, expectedResult EPResult) { t.Helper() - entity.SetDstPort(entity.Port) - result, _ := ep.Matches(context.TODO(), entity) if result != expectedResult { t.Errorf( @@ -75,13 +73,13 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) // DOMAIN @@ -93,31 +91,31 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc-example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc-example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) ep, err = parseEndpoint("+ *.example.com") if err != nil { @@ -126,31 +124,31 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc-example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc-example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) ep, err = parseEndpoint("+ .example.com") if err != nil { @@ -159,31 +157,31 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc-example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc-example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) ep, err = parseEndpoint("+ example.*") if err != nil { @@ -192,22 +190,22 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) ep, err = parseEndpoint("+ *.exampl*") if err != nil { @@ -216,22 +214,22 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "abc.example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) ep, err = parseEndpoint("+ *.com.") if err != nil { @@ -240,10 +238,10 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.org.", - }).Init(), NoMatch) + }).Init(0), NoMatch) // protocol @@ -257,16 +255,16 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 17, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) // ports @@ -280,24 +278,28 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO IP: net.ParseIP("10.2.3.4"), Protocol: 17, Port: 441, - }).Init() + }).Init(0) testEndpointMatch(t, ep, entity, NoMatch) entity.Port = 442 + entity.Init(0) testEndpointMatch(t, ep, entity, Permitted) entity.Port = 443 + entity.Init(0) testEndpointMatch(t, ep, entity, Permitted) entity.Port = 444 + entity.Init(0) testEndpointMatch(t, ep, entity, Permitted) entity.Port = 445 + entity.Init(0) testEndpointMatch(t, ep, entity, NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) // IP @@ -311,30 +313,30 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO IP: net.ParseIP("10.2.3.4"), Protocol: 6, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.4"), Protocol: 17, Port: 443, - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "", IP: net.ParseIP("10.2.3.3"), Protocol: 6, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", IP: net.ParseIP("10.2.3.5"), Protocol: 17, Port: 443, - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ Domain: "example.com.", - }).Init(), NoMatch) + }).Init(0), NoMatch) // IP Range @@ -344,13 +346,13 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO } testEndpointMatch(t, ep, (&intel.Entity{ IP: net.ParseIP("10.2.2.4"), - }).Init(), NoMatch) + }).Init(0), NoMatch) testEndpointMatch(t, ep, (&intel.Entity{ IP: net.ParseIP("10.2.3.4"), - }).Init(), Permitted) + }).Init(0), Permitted) testEndpointMatch(t, ep, (&intel.Entity{ IP: net.ParseIP("10.2.4.4"), - }).Init(), NoMatch) + }).Init(0), NoMatch) // Skip test that need the geoip database in CI. if !testing.Short() { @@ -362,12 +364,10 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO t.Fatal(err) } - entity = &intel.Entity{} - entity.SetIP(net.ParseIP("8.8.8.8")) + entity = (&intel.Entity{IP: net.IPv4(8, 8, 8, 8)}).Init(0) testEndpointMatch(t, ep, entity, Permitted) - entity = &intel.Entity{} - entity.SetIP(net.ParseIP("1.1.1.1")) + entity = (&intel.Entity{IP: net.IPv4(1, 1, 1, 1)}).Init(0) testEndpointMatch(t, ep, entity, NoMatch) // Country @@ -377,12 +377,10 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO t.Fatal(err) } - entity = &intel.Entity{} - entity.SetIP(net.ParseIP("194.232.104.1")) // orf.at + entity = (&intel.Entity{IP: net.IPv4(194, 232, 104, 1)}).Init(0) // orf.at testEndpointMatch(t, ep, entity, Permitted) - entity = &intel.Entity{} - entity.SetIP(net.ParseIP("151.101.1.164")) // nytimes.com + entity = (&intel.Entity{IP: net.IPv4(151, 101, 1, 164)}).Init(0) // nytimes.com testEndpointMatch(t, ep, entity, NoMatch) } @@ -394,10 +392,10 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO t.Fatal(err) } - entity = &intel.Entity{} - entity.SetIP(net.ParseIP("192.168.0.1")) + entity = (&intel.Entity{IP: net.IPv4(192, 168, 0, 1)}).Init(0) testEndpointMatch(t, ep, entity, Permitted) - entity.SetIP(net.ParseIP("151.101.1.164")) // nytimes.com + + entity = (&intel.Entity{IP: net.IPv4(151, 101, 1, 164)}).Init(0) // nytimes.com testEndpointMatch(t, ep, entity, NoMatch) // Port with protocol wildcard @@ -412,6 +410,7 @@ func TestEndpointMatching(t *testing.T) { //nolint:maintidx // TODO Protocol: 6, Port: 443, } + entity.Init(0) testEndpointMatch(t, ep, entity, Permitted) // Lists