Rework inspection framework

This commit is contained in:
Patrick Pacher 2021-09-02 17:04:16 +02:00
parent d044686935
commit efec52ba18
21 changed files with 1553 additions and 79 deletions

View file

@ -13,6 +13,9 @@ import (
_ "github.com/safing/portmaster/core"
_ "github.com/safing/portmaster/firewall"
_ "github.com/safing/portmaster/firewall/inspection/encryption"
_ "github.com/safing/portmaster/firewall/inspection/http"
_ "github.com/safing/portmaster/firewall/inspection/tls"
_ "github.com/safing/portmaster/firewall/inspection/upnpigd"
_ "github.com/safing/portmaster/nameserver"
_ "github.com/safing/portmaster/ui"
_ "github.com/safing/spn/captain"

168
firewall/dpi/assembler.go Normal file
View file

@ -0,0 +1,168 @@
package dpi
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/reassembly"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/network"
)
// Context implements reassembly.AssemblerContext.
type Context struct {
CaptureInfo gopacket.CaptureInfo
Connection *network.Connection
Verdict *network.Verdict
Reason network.VerdictReason
Tracer *log.ContextTracer
HandlerExecuted bool
}
func (c *Context) GetCaptureInfo() gopacket.CaptureInfo {
return c.CaptureInfo
}
type tcpStreamFactory struct {
manager *Manager
}
func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream {
log.Infof("tcp-stream-factory: new stream for %s %s", net, transport)
fsmOptions := reassembly.TCPSimpleFSMOptions{
SupportMissingEstablishment: true,
}
stream := &tcpStream{
net: net,
transport: transport,
tcpstate: reassembly.NewTCPSimpleFSM(fsmOptions),
ident: fmt.Sprintf("%s:%s", net, transport),
optchecker: reassembly.NewTCPOptionCheck(),
manager: factory.manager,
}
return stream
}
type tcpStream struct {
conn *network.Connection
manager *Manager
tcpstate *reassembly.TCPSimpleFSM
optchecker reassembly.TCPOptionCheck
net, transport gopacket.Flow
ident string
}
func (t *tcpStream) Accept(
tcp *layers.TCP,
ci gopacket.CaptureInfo,
dir reassembly.TCPFlowDirection,
nextSeq reassembly.Sequence,
start *bool,
ac reassembly.AssemblerContext) bool {
conn := ac.(*Context).Connection
if t.conn != nil && t.conn.ID != conn.ID {
// TODO(ppacher): for localhost to localhost connections this stream-reassembler will be called for both
// connections because gopacket's flow IDs collide with it's reverse tuple. That's on purpose so
// client->server and server->client packets are attributed correctly but it may cause errors if the
// portmaster sees both, the client and server side of a connection
if t.conn.LocalPort != conn.Entity.Port {
panic(fmt.Sprintf("TCPStream already has a connection object assigned: %s != %s", t.conn.ID, conn.ID))
}
}
t.conn = conn
if !t.tcpstate.CheckState(tcp, dir) {
log.Errorf("tcp-stream %s: fsm: packet rejected by FSM (state: %s)", t.ident, t.tcpstate.String())
return false
}
err := t.optchecker.Accept(tcp, ci, dir, nextSeq, start)
if err != nil {
log.Errorf("tcp-stream %s: option-checker: packet rejected: %s", t.ident, err)
return false
}
return true
}
func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
c := ac.(*Context)
conn := c.Connection
dir, start, end, skip := sg.Info()
length, saved := sg.Lengths()
sgStats := sg.Stats()
data := sg.Fetch(length)
var ident string
if dir == reassembly.TCPDirClientToServer {
ident = fmt.Sprintf("%v %v(%s): ", t.net, t.transport, dir)
conn.OutgoingStream.Append(data)
} else {
ident = fmt.Sprintf("%v %v(%s): ", t.net.Reverse(), t.transport.Reverse(), dir)
conn.IncomingStream.Append(data)
}
c.Tracer.Debugf("tcp-stream %s: reassembled packet with %d bytes (start:%v,end:%v,skip:%d,saved:%d,nb:%d,%d,overlap:%d,%d)", ident, length, start, end, skip, saved, sgStats.Packets, sgStats.Chunks, sgStats.OverlapBytes, sgStats.OverlapPackets)
var (
verdict network.Verdict
reason network.VerdictReason
hasHandler bool
)
c.HandlerExecuted = true
all := conn.StreamHandlers()
for idx, sh := range all {
if sh == nil {
continue
}
name := fmt.Sprintf("%T", sh)
if n, ok := sh.(named); ok {
name = n.Name()
}
hasHandler = true
c.Tracer.Infof("inspector(%s, %d/%d): running stream inspector", name, idx+1, len(all))
v, r, err := sh.HandleStream(conn, network.FlowDirection(dir), data)
if err != nil {
c.Tracer.Errorf("inspector(%s): failed to run stream handler: %s", name, err)
}
if v == network.VerdictUndeterminable {
// not applicable anymore
c.Tracer.Debugf("inspector(%s): stream inspector is not applicable anymore", name)
conn.RemoveHandler(idx, sh)
continue
}
if err != nil {
continue
}
if v > network.VerdictUndecided {
c.Tracer.Infof("inspector(%s): stream inspector found a conclusion: %s", name, v.String())
}
if v > verdict {
verdict = v
reason = r
}
}
if hasHandler {
c.Verdict = &verdict
c.Reason = reason
}
}
func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
log.Infof("tcp-stream %s: connection closed", t.ident)
if t.conn == nil {
return true
}
log.Infof("tcp-stream: connection %s sent %d bytes and received %d bytes", t.conn.OutgoingStream.Length(), t.conn.IncomingStream.Length())
return true
}

186
firewall/dpi/manager.go Normal file
View file

@ -0,0 +1,186 @@
package dpi
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/ip4defrag"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/reassembly"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/packet"
)
type Manager struct {
defragv4 *ip4defrag.IPv4Defragmenter
pool *reassembly.StreamPool
assembler *reassembly.Assembler
}
func NewManager() *Manager {
streamFactory := new(tcpStreamFactory)
streamPool := reassembly.NewStreamPool(streamFactory)
assembler := reassembly.NewAssembler(streamPool)
mng := &Manager{
defragv4: ip4defrag.NewIPv4Defragmenter(),
pool: streamPool,
assembler: assembler,
}
// make sure the streamFactory has a reference to the
// manager for dispatching reassembled streams.
streamFactory.manager = mng
return mng
}
type named interface{ Name() string }
func (mng *Manager) HandlePacket(conn *network.Connection, p packet.Packet) (network.Verdict, network.VerdictReason, error) {
trace := log.Tracer(p.Ctx())
gp := p.Layers()
// if this is a IPv4 packet make sure we defrag it.
ipv4Layer := gp.Layer(layers.LayerTypeIPv4)
if ipv4Layer != nil {
ipv4 := ipv4Layer.(*layers.IPv4)
l := ipv4.Length
newip4, err := mng.defragv4.DefragIPv4(ipv4)
if err != nil {
return 0, nil, fmt.Errorf("failed to de-fragment: %w", err)
}
if newip4 == nil {
// this is a fragmented packet
// wait for the next one
trace.Debugf("tcp-stream-manager: fragmented IPv4 packet ...")
return 0, nil, nil
}
if newip4.Length != l {
pb, ok := gp.(gopacket.PacketBuilder)
if !ok {
return 0, nil, fmt.Errorf("expected a PacketBuilder, got %T", p)
}
trace.Debugf("decoding re-assembled packet ...")
nextDecoder := newip4.NextLayerType()
nextDecoder.Decode(newip4.Payload, pb)
}
}
var (
verdict network.Verdict
reason network.VerdictReason
hasActiveHandler bool
)
for idx, pk := range conn.PacketHandlers() {
if pk == nil {
continue
}
name := fmt.Sprintf("%T", pk)
if n, ok := pk.(named); ok {
name = n.Name()
}
hasActiveHandler = true
trace.Infof("%s: running packet inspector", name)
v, r, err := pk.HandlePacket(conn, gp)
if err != nil {
trace.Errorf("inspector(%s): failed to call packet handler: %s", name, err)
}
if v == network.VerdictUndeterminable {
// this handler is not applicable for conn anymore
trace.Debugf("inspector(%s): packet inspector is not applicable for this connection anymore ...", name)
conn.RemoveHandler(idx, pk)
continue
}
if err != nil {
continue
}
if v > network.VerdictUndecided {
trace.Infof("inspector(%s): packet inspector found a conclusion: %s", name, v.String())
}
if v > verdict {
verdict = v
reason = r
}
}
// handle TCP stream reassembling
tcp := gp.Layer(layers.LayerTypeTCP)
if tcp != nil {
tcp := tcp.(*layers.TCP)
c := &Context{
CaptureInfo: gp.Metadata().CaptureInfo,
Connection: conn,
Tracer: trace,
}
// reassemble the stream and call any stream handlers of the connection
mng.assembler.AssembleWithContext(gp.NetworkLayer().NetworkFlow(), tcp, c)
if !c.HandlerExecuted {
// if we did not even try to execute the handler
// we need to assume there are still active ones.
// This may happen we we are still waiting for
// an IP frame ...
hasActiveHandler = true
} else {
if c.Verdict != nil {
hasActiveHandler = true
if *c.Verdict > verdict {
verdict = *c.Verdict
}
}
}
}
udp := gp.Layer(layers.LayerTypeUDP)
if udp != nil {
payload := gp.ApplicationLayer().Payload()
for idx, uh := range conn.DgramHandlers() {
if uh == nil {
continue
}
name := fmt.Sprintf("%T", uh)
if n, ok := uh.(named); ok {
name = n.Name()
}
hasActiveHandler = true
trace.Infof("inspector(%s): running dgram inspector", name)
v, r, err := uh.HandleDGRAM(conn, network.FlowDirection(!conn.Inbound), payload)
if err != nil {
trace.Errorf("inspector(%s): failed to run dgram handler: %s", name, err)
}
if v == network.VerdictUndeterminable {
trace.Debugf("inspector(%s): dgram inspector is not applicable for this connection anymore ...", name)
conn.RemoveHandler(idx, uh)
continue
}
if err != nil {
continue
}
if v > network.VerdictUndecided {
trace.Infof("inspector(%s): dgram inspector found a conclusion: %s", name, v.String())
}
if v > verdict {
verdict = v
reason = r
}
}
}
if !hasActiveHandler || verdict > network.VerdictUndeterminable {
trace.Infof("stopping inspection %s: hasActiveHandler=%v verdict=%s", conn.ID, hasActiveHandler, verdict.String())
// we don't have any active handlers anymore so
// there's no need to continue inspection
conn.Inspecting = false
}
return verdict, reason, nil
}

View file

@ -1,6 +1,7 @@
package encryption
import (
"github.com/google/gopacket"
"github.com/safing/portmaster/firewall/inspection"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/packet"
@ -15,7 +16,7 @@ func (d *Detector) Name() string {
}
// Inspect implements the inspection interface.
func (d *Detector) Inspect(conn *network.Connection, pkt packet.Packet) (pktVerdict network.Verdict, proceed bool, err error) {
func (d *Detector) HandlePacket(conn *network.Connection, pkt gopacket.Packet) (network.Verdict, network.VerdictReason, error) {
if !conn.Inbound {
switch conn.Entity.Port {
case 443, 465, 993, 995:
@ -24,7 +25,7 @@ func (d *Detector) Inspect(conn *network.Connection, pkt packet.Packet) (pktVerd
}
}
return network.VerdictUndecided, false, nil
return network.VerdictUndeterminable, nil, nil
}
// Destroy implements the destroy interface.
@ -39,12 +40,11 @@ func DetectorFactory(conn *network.Connection, pkt packet.Packet) (network.Inspe
// Register registers the encryption detection inspector with the inspection framework.
func init() {
err := inspection.RegisterInspector(&inspection.Registration{
inspection.MustRegister(&inspection.Registration{
Name: "Encryption Detection",
Order: 0,
Factory: DetectorFactory,
})
if err != nil {
panic(err)
}
}
var _ network.PacketHandler = new(Detector)

View file

@ -0,0 +1,91 @@
package http
import (
"context"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/firewall/inspection"
"github.com/safing/portmaster/firewall/inspection/inspectutils"
"github.com/safing/portmaster/intel"
"github.com/safing/portmaster/intel/filterlists"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/profile/endpoints"
)
// Inspector implements a simple plain HTTP inspector.
type Inspector struct {
decoder *inspectutils.HTTPRequestDecoder
}
func (inspector *Inspector) HandleStream(conn *network.Connection, dir network.FlowDirection, data []byte) (network.Verdict, network.VerdictReason, error) {
req, err := inspector.decoder.HandleStream(conn, dir, data)
if err != nil {
// we're done here ...
return network.VerdictUndeterminable, nil, err
}
if req == nil {
// we don't have a full request yet
return network.VerdictUndecided, nil, nil
}
if conn.Entity.Domain != req.Host {
ctx := context.TODO()
lp := conn.Process().Profile()
log.Infof("%s found domain, re-evaluating connection from %s to %s", lp.Key(), conn.Entity.Domain, req.Host)
e := &intel.Entity{
Domain: req.Host + ".",
IP: conn.Entity.IP,
Port: conn.Entity.Port,
}
e.ResolveSubDomainLists(ctx, lp.FilterSubDomains())
e.EnableCNAMECheck(ctx, lp.FilterCNAMEs())
e.LoadLists(context.TODO())
d, err := filterlists.LookupDomain(e.Domain)
log.Infof("found entity in lists: %v - %v (%v - %v)", e.BlockedByLists, e.ListsError, err, d)
result, reason := lp.MatchFilterLists(ctx, e)
switch result {
case endpoints.Denied:
log.Infof("connection blocked ...")
return network.VerdictBlock, reason, nil
}
}
return network.VerdictUndeterminable, nil, nil
}
// TODO(ppacher): get rid of this function, we already specify the name
// in inspection:RegisterInspector ...
func (*Inspector) Name() string {
return "HTTP"
}
// Destory does nothing ...
func (*Inspector) Destroy() error {
return nil
}
func init() {
inspection.MustRegister(&inspection.Registration{
Name: "HTTP",
Order: 1, // we don't actually care
Factory: func(conn *network.Connection, pkt packet.Packet) (network.Inspector, error) {
// we only support outgoing http clients for now ...
if conn.Entity.DstPort() != 80 {
return nil, nil
}
insp := &Inspector{
decoder: inspectutils.NewHTTPRequestDecoder(inspectutils.HTTPMethods),
}
return insp, nil
},
})
}
// compile time check if Inspector corretly satisfies all
// required interfaces ...
var _ network.StreamHandler = new(Inspector)

View file

@ -60,56 +60,27 @@ func RegisterInspector(new *Registration) error {
return nil
}
// MustRegister is like RegisterInspector but panics in case of an error.
func MustRegister(new *Registration) {
if err := RegisterInspector(new); err != nil {
panic(err.Error())
}
}
// InitializeInspectors initializes all applicable inspectors for the connection.
func InitializeInspectors(conn *network.Connection, pkt packet.Packet) {
inspectorRegistryLock.Lock()
defer inspectorRegistryLock.Unlock()
connInspectors := make([]network.Inspector, 0, len(inspectorRegistry))
for _, r := range inspectorRegistry {
inspector, err := r.Factory(conn, pkt)
switch {
case err != nil:
log.Tracer(pkt.Ctx()).Warningf("failed to initialize inspector %s: %v", r.Name, err)
case inspector != nil:
connInspectors = append(connInspectors, inspector)
}
}
conn.SetInspectors(connInspectors)
}
// RunInspectors runs all the applicable inspectors on the given packet of the connection. It returns the first error received by an inspector.
func RunInspectors(conn *network.Connection, pkt packet.Packet) (pktVerdict network.Verdict, continueInspection bool) {
connInspectors := conn.GetInspectors()
for i, inspector := range connInspectors {
// check if slot is active
if inspector == nil {
continue
}
// run inspector
inspectorPktVerdict, proceed, err := inspector.Inspect(conn, pkt)
if err != nil {
log.Tracer(pkt.Ctx()).Warningf("inspector %s failed: %s", inspector.Name(), err)
}
// merge
if inspectorPktVerdict > pktVerdict {
pktVerdict = inspectorPktVerdict
}
if proceed {
continueInspection = true
}
// destroy if finished or failed
if !proceed || err != nil {
err = inspector.Destroy()
if err != nil {
log.Tracer(pkt.Ctx()).Debugf("inspector %s failed to destroy: %s", inspector.Name(), err)
if err := conn.AddHandler(inspector); err != nil {
log.Tracer(pkt.Ctx()).Warningf("failed to initalize inspector %s: %s", r.Name, err)
}
connInspectors[i] = nil
}
}
return pktVerdict, continueInspection
}

View file

@ -0,0 +1,108 @@
package inspectutils
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"net/http"
"strings"
"sync"
"github.com/safing/portmaster/network"
)
var HTTPMethods = []string{
// Normal HTTP methods
"GET",
"POST",
"PUT",
"PATCH",
"OPTIONS",
"HEAD",
"DELETE",
// SSDP
"MS-SEARCH",
"NOTIFY",
// WebDAV
"PROPFIND",
"MKCOL",
"RMCOL",
// TODO(ppacher): possible more for webdav ...
}
var ErrUnexpectedHTTPVerb = errors.New("unexpected HTTP verb")
type HTTPRequestDecoder struct {
methods []string
minBytes int
l sync.Mutex
data map[string][]byte
}
// NewHTTPRequestDecoder returns a new HTTP request decoder. methods
// may be set to a list of allowed HTTP methods. These list is used
// to early-abort inspecting a TCP stream. If methods is nil the stream
// will be inspected until http.ParseRequests returns an non EOF error.
// To use the default set of methods, pass DefaultMethods here.
func NewHTTPRequestDecoder(methods []string) *HTTPRequestDecoder {
var minBytes int
for _, m := range methods {
if len(m) > minBytes {
minBytes = len(m)
}
}
return &HTTPRequestDecoder{
methods: methods,
minBytes: minBytes,
data: make(map[string][]byte),
}
}
func (decoder *HTTPRequestDecoder) HandleStream(conn *network.Connection, dir network.FlowDirection, data []byte) (*http.Request, error) {
decoder.l.Lock()
defer decoder.l.Unlock()
decoder.data[conn.ID] = append(decoder.data[conn.ID], data...)
allData := decoder.data[conn.ID]
if decoder.methods != nil {
// we don't even have enough data for a HTTP Verb
// so nothing todo...
if len(allData) < decoder.minBytes {
return nil, nil
}
// check if we find one of the requested HTTP methods
foundMethod := false
lowercased := strings.ToLower(string(allData))
for _, m := range decoder.methods {
if strings.HasPrefix(lowercased, strings.ToLower(m)) {
foundMethod = true
break
}
}
if !foundMethod {
return nil, fmt.Errorf("%w: %s", ErrUnexpectedHTTPVerb, string(allData[:decoder.minBytes]))
}
}
// try to parse a http request
reader := bufio.NewReader(bytes.NewReader(allData))
req, err := http.ReadRequest(reader)
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
// we don't have the full request yet ...
return nil, nil
}
// we failed to parse the request so we can safely reset the data here
delete(decoder.data, conn.ID)
if err != nil {
return nil, err
}
return req, nil
}

View file

@ -0,0 +1,9 @@
package inspectutils
type Reason struct {
Message string
Details interface{}
}
func (r *Reason) String() string { return r.Message }
func (r *Reason) Context() interface{} { return r.Details }

View file

@ -0,0 +1,173 @@
package tls
import (
"crypto/x509"
"errors"
"fmt"
"sync"
"github.com/google/gopacket/layers"
utls "github.com/refraction-networking/utls"
"github.com/safing/portbase/log"
)
// TLSHandshakeType defines the type of TLS handshake
type TLSHandshakeType uint8
// TLSHandshakeType known values.
const (
TLSHandshakeHelloRequest TLSHandshakeType = 0
TLSHandshakeClientHello TLSHandshakeType = 1
TLSHandshakeServerHello TLSHandshakeType = 2
TLSHandshakeCertificate TLSHandshakeType = 11
TLSHandshakeServerKeyExchange TLSHandshakeType = 12
TLSHandshakeCertificateRequest TLSHandshakeType = 13
TLSHandshakeServerDone TLSHandshakeType = 14
TLSHandshakeCertificateVerify TLSHandshakeType = 15
TLSHandshakeClientKeyExchange TLSHandshakeType = 16
TLSHandshakeFinished TLSHandshakeType = 20
)
func (tht TLSHandshakeType) String() string {
switch tht {
case TLSHandshakeHelloRequest:
return "HelloRequest"
case TLSHandshakeClientHello:
return "ClientHello"
case TLSHandshakeServerHello:
return "ServerHello"
case TLSHandshakeCertificate:
return "Certificate"
case TLSHandshakeServerKeyExchange:
return "ServerKeyExchange"
case TLSHandshakeCertificateRequest:
return "CertificateRequest"
case TLSHandshakeServerDone:
return "ServerDone"
case TLSHandshakeCertificateVerify:
return "CertificateVerify"
case TLSHandshakeClientKeyExchange:
return "ClientKeyExchange"
case TLSHandshakeFinished:
return "Finished"
default:
return "Unknown"
}
}
var validHandshakeValues = map[TLSHandshakeType]bool{
TLSHandshakeHelloRequest: true,
TLSHandshakeClientHello: true,
TLSHandshakeServerHello: true,
TLSHandshakeCertificate: true,
TLSHandshakeServerKeyExchange: true,
TLSHandshakeCertificateRequest: true,
TLSHandshakeServerDone: true,
TLSHandshakeCertificateVerify: true,
TLSHandshakeClientKeyExchange: true,
TLSHandshakeFinished: true,
}
// isEncrypted checks if packet seems encrypted (heuristics)
func isEncrypted(data []byte) bool {
// heuristics used by wireshark
// https://github.com/wireshark/wireshark/blob/d5fe2d494c6475263b954a36812b888b11e1a50b/epan/dissectors/packet-tls.c#L2158a
if len(data) < 16 {
return false
}
if len(data) > 0x010000 {
return true
}
_, ok := validHandshakeValues[TLSHandshakeType(data[0])]
return !ok
}
type TLSHandshakeRecord struct {
layers.TLSRecordHeader
Type *TLSHandshakeType
Record []byte
ClientHello *utls.ClientHelloMsg
ServerHello *utls.ServerHelloMsg
CertificateMsg *utls.CertificateMsg
certChainOnce sync.Once
certChain []*x509.Certificate
}
func (r *TLSHandshakeRecord) Certificates() []*x509.Certificate {
if r.CertificateMsg == nil {
return nil
}
r.certChainOnce.Do(func() {
certs := make([]*x509.Certificate, len(r.CertificateMsg.Certificates))
for idx, c := range r.CertificateMsg.Certificates {
cert, err := x509.ParseCertificate(c)
if err != nil {
log.Errorf("failed to parse certificate: %s", err)
certs[idx] = nil
} else {
certs[idx] = cert
}
}
r.certChain = certs
})
return r.certChain
}
func (r *TLSHandshakeRecord) String() string {
m := formatRecordHeader(r.TLSRecordHeader) + fmt.Sprintf(" Handshake-Type: %s", r.Type.String())
if r.ClientHello != nil {
v := layers.TLSVersion(r.ClientHello.Vers).String()
m += fmt.Sprintf("{Version: %s, SNI: %s}", v, r.ClientHello.ServerName)
}
if r.ServerHello != nil {
v := layers.TLSVersion(r.ServerHello.Vers).String()
m += fmt.Sprintf("{Version: %s}", v)
}
if r.CertificateMsg != nil {
m += fmt.Sprintf("{}")
}
return m
}
func (r *TLSHandshakeRecord) Decode(h layers.TLSRecordHeader, data []byte, encrypted bool) error {
r.TLSRecordHeader = h
r.Record = data
encrypted = encrypted || isEncrypted(data)
/*
if encrypted {
return nil
}
*/
tlsType := TLSHandshakeType(data[0])
r.Type = &tlsType
switch tlsType {
case TLSHandshakeClientHello:
msg := utls.UnmarshalClientHello(r.Record)
if msg == nil {
return errors.New("failed to parse ClientHello")
}
r.ClientHello = msg
case TLSHandshakeServerHello:
msg := utls.UnmarshalServerHello(r.Record)
if msg == nil {
return errors.New("failed to parse ServerHello")
}
r.ServerHello = msg
case TLSHandshakeCertificate:
msg := utls.UnmarshalCertificateMsg(r.Record)
if msg == nil {
return errors.New("failed to parse Certificate")
}
r.CertificateMsg = msg
}
return nil
}

View file

@ -0,0 +1,192 @@
package tls
import (
"crypto/x509"
utls "github.com/refraction-networking/utls"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/firewall/inspection"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/packet"
)
// Inspector implements a simple plain HTTP inspector.
type Inspector struct {
data []byte
tries int
sni string
hasClientHello bool
hasServerHello bool
}
// Reason implements endpoints.Reason and is used when certificate
// verification fails.
type Reason struct {
Error string
}
func (r *Reason) String() string { return r.Error }
func (r *Reason) Context() interface{} { return r }
func (inspector *Inspector) HandleStream(conn *network.Connection, dir network.FlowDirection, data []byte) (network.Verdict, network.VerdictReason, error) {
inspector.data = append(inspector.data, data...)
if len(inspector.data) < 5 {
if len(inspector.data) == 0 {
// This is the very very first packet of the TCP handshake
// so we need to wait for more ...
return network.VerdictUndecided, nil, nil
}
// TLS data size to short. This is likely not a TLS connection at all ...
return network.VerdictUndeterminable, nil, nil
}
tls := new(TLS)
var truncated bool
if err := tls.DecodeNextRecord(inspector.data, false, &truncated, true); err != nil {
// don't stop inspecting if we just have to less data
if truncated && tls.NumberOfRecords > 0 {
inspector.tries++
log.Debugf("truncated TLS packet: %s", err)
return network.VerdictUndecided, nil, nil
}
// we're done here ...
log.Tracef("failed to decode TLS records: %s", err)
return network.VerdictUndeterminable, nil, nil
}
// if there's no client-hello yet there must be one within the first few
// tries.
if !inspector.hasClientHello {
if tls.HasClientHello() {
inspector.hasClientHello = true
inspector.tries = 0
// since we have a client hello we can verify the SNI
if sni := tls.SNI(); sni != "" && sni != conn.Entity.Domain {
inspector.sni = sni
log.Errorf("Found connection with entity domain (%s) not matching TLS SNI (%s.)", conn.Entity.Domain, sni)
// TODO(ppacher): update connection entity and re-evaluate the whole connection
}
// continue inspection until we receive a server-hello
return network.VerdictUndecided, nil, nil
} else if inspector.tries > 5 {
// we already tried 5 times to get the client hello message
// it's time to give up now, this seems not to be a TLS
// encrypted connection.
return network.VerdictUndeterminable, nil, nil
}
// without a client hello we cannot really expect much
// more useful data right now so retry the next time
inspector.tries++
return network.VerdictUndecided, nil, nil
}
if !inspector.hasServerHello {
if tls.HasServerHello() {
inspector.hasServerHello = true
inspector.tries = 0
// update the connection with the TLS data we already
// have.
v := tls.Version()
conn.TLS = &network.TLSContext{
Version: v.String(),
VersionRaw: uint16(v),
SNI: inspector.sni,
}
// TODO(ppacher): we could actually wait for ChangeCipherSpec for that ....
conn.Encrypted = true
// ok we got the server hello now. If the TLS connection
// agreed upon using TLS1.3 there's nothing more we can
// do here.
if tls.Version() == utls.VersionTLS13 {
return network.VerdictUndeterminable, nil, nil
}
// this is a TLS version lower than TLS1.3 so we
// can inspect the certificate of the remote peer.
// do not return here as the same TLS message might
// already contain the certificate message ...
} else if inspector.tries > 10 {
// we waited for 10 packets to get the full certificate
// message and still don't succeeded. We can give up on
// that ...
return network.VerdictUndeterminable, nil, nil
}
// without a server-hello there won't be a CertificateMsg
// so we're done for now ...
inspector.tries++
return network.VerdictUndecided, nil, nil
}
// if we reach this point we have a client and server hello, know the TLS
// version is lower than TLS1.3 and thus wait for the certificate message
// so we can parse and verify the cert-chain.
if tls.HasCertificate() {
certs := tls.ServerCertChain()
var intermediateCerts *x509.CertPool
if len(certs) > 1 {
intermediateCerts = x509.NewCertPool()
for _, c := range certs[1:] {
intermediateCerts.AddCert(c)
}
}
// TODO(ppacher): CRL and OCSP is missing here
chain, err := certs[0].Verify(x509.VerifyOptions{
DNSName: inspector.sni,
Intermediates: intermediateCerts,
})
if err != nil {
// verification failed, block the connection.
return network.VerdictBlock, &Reason{Error: err.Error()}, nil
}
conn.TLS.SetChains(chain)
// we're done inspecting the TLS session
return network.VerdictUndeterminable, nil, nil
}
if inspector.tries > 10 {
return network.VerdictUndeterminable, nil, nil
}
// we're still waiting for the certificate ...
inspector.tries++
return network.VerdictUndecided, nil, nil
}
// TODO(ppacher): get rid of this function, we already specify the name
// in inspection:RegisterInspector ...
func (*Inspector) Name() string {
return "TLS"
}
// Destory does nothing ...
func (*Inspector) Destroy() error {
return nil
}
func init() {
inspection.MustRegister(&inspection.Registration{
Name: "TLS",
Order: 1, // we don't actually care
Factory: func(conn *network.Connection, pkt packet.Packet) (network.Inspector, error) {
// We only inspect TCP sessions
if conn.IPProtocol != packet.TCP {
return nil, nil
}
return &Inspector{}, nil
},
})
}
// compile time check if Inspector actually satisfies
// the expected interfaces.
var _ network.StreamHandler = new(Inspector)

View file

@ -0,0 +1,233 @@
package tls
import (
"crypto/x509"
"encoding/binary"
"fmt"
"strings"
"github.com/google/gopacket/layers"
"github.com/safing/portbase/log"
)
type TLS struct {
NumberOfRecords int
Handshake []TLSHandshakeRecord
ChangeCipherSpec []TLSChangeCipherSpecRecord
AppData []TLSAppDataRecord
Alert []TLSAlertRecord
}
func (tls *TLS) String() string {
// TODO(ppacher): add other record types
var records []string
for _, r := range tls.Handshake {
records = append(records, r.String())
}
for _, r := range tls.ChangeCipherSpec {
records = append(records, r.String())
}
for _, r := range tls.AppData {
records = append(records, r.String())
}
for _, r := range tls.Alert {
records = append(records, r.String())
}
return fmt.Sprintf("TLS-Records (#%d): %s", tls.NumberOfRecords, strings.Join(records, ";"))
}
func (tls *TLS) HasClientHello() bool {
for _, h := range tls.Handshake {
if h.ClientHello != nil {
return true
}
}
return false
}
func (tls *TLS) HasServerHello() bool {
for _, h := range tls.Handshake {
if h.ServerHello != nil {
return true
}
}
return false
}
func (tls *TLS) HasCertificate() bool {
for _, h := range tls.Handshake {
if h.CertificateMsg != nil {
return true
}
}
return false
}
// Version is a short-cut for searching the TLS records for either a
// server or client hello message and returning the TLS version
// specified there. If both hello messages are available, the server-
// hello takes precedence.
func (tls *TLS) Version() layers.TLSVersion {
var (
serverHelloVersion layers.TLSVersion
clientHelloVersion layers.TLSVersion
)
for _, h := range tls.Handshake {
if h.ServerHello != nil {
serverHelloVersion = layers.TLSVersion(h.ServerHello.Vers)
}
if h.ClientHello != nil {
clientHelloVersion = layers.TLSVersion(h.ClientHello.Vers)
}
}
if serverHelloVersion > 0 {
return serverHelloVersion
}
return clientHelloVersion
}
// SNI is a short utility method for accessing the ServerName sent
// in ClientHello messages. If there is not ClientHello handshake
// record in tls an empty string is returned.
func (tls *TLS) SNI() string {
if len(tls.Handshake) == 0 {
return ""
}
for _, r := range tls.Handshake {
if r.ClientHello != nil {
return r.ClientHello.ServerName
}
}
return ""
}
func (tls *TLS) ServerCertChain() []*x509.Certificate {
if len(tls.Handshake) == 0 {
return nil
}
for _, r := range tls.Handshake {
if r.CertificateMsg != nil {
return r.Certificates()
}
}
return nil
}
func (tls *TLS) DecodeNextRecord(data []byte, encrypted bool, truncated *bool, strict bool) error {
var h layers.TLSRecordHeader
h.ContentType = layers.TLSType(data[0])
h.Version = layers.TLSVersion(binary.BigEndian.Uint16(data[1:3]))
h.Length = binary.BigEndian.Uint16(data[3:5])
hl := 5
tl := hl + int(h.Length)
if len(data) < tl {
*truncated = true
return fmt.Errorf("TLS packet length missmatch: %d is less than %d", len(data), tl)
}
tls.NumberOfRecords++
var s fmt.Stringer
switch h.ContentType {
default:
if !strict {
log.Errorf("unknown TLS record type: %02x (length=%d version=%04x)", uint8(h.ContentType), h.Length, h.Version)
} else {
return fmt.Errorf("unknown TLS record type: %02d", uint8(h.ContentType))
}
case layers.TLSChangeCipherSpec:
encrypted = true
var r TLSChangeCipherSpecRecord
if err := r.Decode(h, data[hl:tl]); err != nil {
return err
}
tls.ChangeCipherSpec = append(tls.ChangeCipherSpec, r)
s = &r
case layers.TLSAlert:
var r TLSAlertRecord
if err := r.Decode(h, data[hl:tl]); err != nil {
return err
}
tls.Alert = append(tls.Alert, r)
s = &r
case layers.TLSApplicationData:
var r TLSAppDataRecord
if err := r.Decode(h, data[hl:tl]); err != nil {
return err
}
tls.AppData = append(tls.AppData, r)
s = &r
case layers.TLSHandshake:
var r TLSHandshakeRecord
if err := r.Decode(h, data[hl:tl], encrypted); err != nil {
return err
}
tls.Handshake = append(tls.Handshake, r)
s = &r
}
if s == nil {
log.Debugf("tls-records: found record %s", formatRecordHeader(h))
} else {
log.Debugf("tls-records: found record %s", s.String())
}
if len(data) == tl {
// this was the last TLS record in this
// packet.
return nil
}
//log.Debugf("tls-records: decoding next record: bytes-left: %d (encrypted=%v)", len(data)-tl, encrypted)
// continue with the next record
return tls.DecodeNextRecord(data[tl:], encrypted, truncated, strict)
}
type TLSAlertRecord struct {
layers.TLSRecordHeader
}
func (r *TLSAlertRecord) Decode(h layers.TLSRecordHeader, data []byte) error {
r.TLSRecordHeader = h
return nil
}
func (r *TLSAlertRecord) String() string {
return formatRecordHeader(r.TLSRecordHeader)
}
type TLSAppDataRecord struct {
layers.TLSRecordHeader
}
func (r *TLSAppDataRecord) Decode(h layers.TLSRecordHeader, data []byte) error {
r.TLSRecordHeader = h
return nil
}
func (r *TLSAppDataRecord) String() string {
return formatRecordHeader(r.TLSRecordHeader)
}
type TLSChangeCipherSpecRecord struct {
layers.TLSRecordHeader
}
func (r *TLSChangeCipherSpecRecord) Decode(h layers.TLSRecordHeader, data []byte) error {
r.TLSRecordHeader = h
return nil
}
func (r *TLSChangeCipherSpecRecord) String() string {
return formatRecordHeader(r.TLSRecordHeader)
}
func formatRecordHeader(h layers.TLSRecordHeader) string {
return fmt.Sprintf("%s (%02d), length=%d vers=%s", h.ContentType.String(), uint8(h.ContentType), h.Length, h.Version.String())
}

View file

@ -0,0 +1,110 @@
package upnpigd
import (
"strings"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/firewall/inspection"
"github.com/safing/portmaster/firewall/inspection/inspectutils"
"github.com/safing/portmaster/netenv"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/packet"
)
var DeniedSOAPURN = []string{
"urn:schemas-upnp-org:service:WANIPConnection",
}
type Inspector struct {
decoder *inspectutils.HTTPRequestDecoder
}
func (*Inspector) Name() string { return "UPNP/IGD" }
func (*Inspector) Destroy() error { return nil }
func (insp *Inspector) HandleStream(conn *network.Connection, dir network.FlowDirection, data []byte) (network.Verdict, network.VerdictReason, error) {
req, err := insp.decoder.HandleStream(conn, dir, data)
log.Errorf("handle stream: req=%v err=%v", req, err)
if err != nil {
// This seems not to be a HTTP connection so we can abort
log.Debugf("%s: aborting inspection as %s does not seem to be HTTP based (%s)", insp.Name(), conn.ID, err)
return network.VerdictUndeterminable, nil, nil
}
if req == nil {
// we don't have a full HTTP request yet
return network.VerdictUndecided, nil, nil
}
keepAlive := false
if connHeader := req.Header.Get("Connection"); strings.Contains(connHeader, "keep-alive") {
keepAlive = true
}
action := strings.ToLower(req.Header.Get("SOAPAction"))
if action != "" {
for _, a := range DeniedSOAPURN {
if strings.HasPrefix(action, strings.ToLower(a)) {
return network.VerdictBlock,
&inspectutils.Reason{
Message: "SOAP action blocked",
Details: map[string]interface{}{
"SOAPAction": action,
"URL": req.URL.String(),
"Method": req.Method,
},
},
nil
}
}
}
if keepAlive {
// there might be additional HTTP requests so make sure to
// inspect all of them.
return network.VerdictUndecided, nil, nil
}
// this connection should end immediately so no need to further
// inspect it
return network.VerdictUndeterminable, nil, nil
}
func init() {
inspection.MustRegister(&inspection.Registration{
Name: "UPNP/IGD",
Order: 0,
Factory: func(conn *network.Connection, pkt packet.Packet) (network.Inspector, error) {
// if there's no IP then conn is a DNS request and we don't
// want to check DNS requests ...
if conn.Entity.IP == nil {
return nil, nil
}
// we only want to check TCP connections
if conn.IPProtocol != packet.TCP {
return nil, nil
}
targetsIGD := false
for _, gw := range netenv.Gateways() {
if conn.Entity.IP.Equal(gw) {
targetsIGD = true
break
}
}
// we only care about connections to routers identified in the
// current network.
if !targetsIGD {
return nil, nil
}
log.Errorf("enabling upnp/igd inspector")
return &Inspector{
decoder: inspectutils.NewHTTPRequestDecoder([]string{"POST", "GET"}),
}, nil
},
})
}
// compile time checks ...
var _ network.StreamHandler = new(Inspector)

View file

@ -17,7 +17,9 @@ import (
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/firewall/dpi"
"github.com/safing/portmaster/firewall/inspection"
"github.com/safing/portmaster/firewall/inspection/inspectutils"
"github.com/safing/portmaster/firewall/interception"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/netutils"
@ -45,6 +47,8 @@ var (
blockedIPv6 = net.ParseIP("::17")
ownPID = os.Getpid()
streamManager = dpi.NewManager()
)
func init() {
@ -395,17 +399,41 @@ func defaultHandler(conn *network.Connection, pkt packet.Packet) {
}
func inspectThenVerdict(conn *network.Connection, pkt packet.Packet) {
pktVerdict, continueInspection := inspection.RunInspectors(conn, pkt)
if continueInspection {
issueVerdict(conn, pkt, pktVerdict, false)
return
log.Infof("%s: inspecting ...", conn.ID)
verdict, reason, err := streamManager.HandlePacket(conn, pkt)
if err != nil {
log.Tracer(pkt.Ctx()).Tracef("tcp-stream: failed to handle packet: %s", err)
// temporarly accept the packet and retry the next time
verdict = network.VerdictAccept
}
// we are done with inspecting
conn.Inspecting = false
conn.SaveWhenFinished()
conn.StopFirewallHandler()
issueVerdict(conn, pkt, 0, true)
if verdict > network.VerdictUndecided {
if reason == nil {
reason = &inspectutils.Reason{}
}
conn.SetVerdict(verdict, reason.String(), "", reason.Context())
}
if !conn.Inspecting {
log.Infof("stopping inspection of %s", conn.ID)
conn.StopFirewallHandler()
conn.SaveWhenFinished()
}
issueVerdict(conn, pkt, 0, !conn.Inspecting)
/*
pktVerdict, continueInspection := inspection.RunInspectors(conn, pkt)
if continueInspection {
issueVerdict(conn, pkt, pktVerdict, false)
return
}
// we are done with inspecting
conn.Inspecting = false
conn.SaveWhenFinished()
conn.StopFirewallHandler()
issueVerdict(conn, pkt, 0, true)
*/
}
func issueVerdict(conn *network.Connection, pkt packet.Packet, verdict network.Verdict, allowPermanent bool) {

10
go.mod
View file

@ -11,8 +11,10 @@ require (
github.com/google/gopacket v1.1.19
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.3.0
github.com/leodido/go-urn v1.2.1 // indirect
github.com/miekg/dns v1.1.40
github.com/oschwald/maxminddb-golang v1.8.0
github.com/refraction-networking/utls v0.0.0-20210713165636-0b2885c8c0d4
github.com/safing/portbase v0.11.2
github.com/safing/spn v0.2.5
github.com/shirou/gopsutil v3.21.4+incompatible
@ -22,7 +24,11 @@ require (
github.com/tevino/abool v1.2.0
github.com/tidwall/gjson v1.7.5
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/net v0.0.0-20210825183410-e898025ed96a
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
)
replace github.com/safing/portbase => ../portbase
replace github.com/refraction-networking/utls => ../utls

12
go.sum
View file

@ -207,6 +207,8 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@ -263,6 +265,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/refraction-networking/utls v0.0.0-20210713165636-0b2885c8c0d4 h1:n9NMHJusHylTmtaJ0Qe0VV9dkTZLiwAxHmrI/l98GeE=
github.com/refraction-networking/utls v0.0.0-20210713165636-0b2885c8c0d4/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@ -397,6 +401,8 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -447,6 +453,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -498,14 +506,18 @@ golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View file

@ -266,7 +266,7 @@ func (conn *Connection) fmtFlagsComponent() string {
if conn.Tunneled {
f += "T"
}
if len(conn.activeInspectors) > 0 {
if conn.Inspecting {
f += "A"
}
if conn.addedToMetrics {

View file

@ -2,6 +2,7 @@ package network
import (
"context"
"crypto/x509"
"fmt"
"net"
"sync"
@ -9,6 +10,7 @@ import (
"github.com/safing/portmaster/netenv"
"github.com/safing/portbase/container"
"github.com/safing/portbase/database/record"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/intel"
@ -53,6 +55,57 @@ const (
// ProxyRequest
)
type CertInfo struct {
Subject string
Issuer string
AlternateNames []string
NotBefore time.Time
NotAfter time.Time
}
func NewCertInfo(cert *x509.Certificate) *CertInfo {
return &CertInfo{
Subject: cert.Subject.String(),
Issuer: cert.Issuer.String(),
NotBefore: cert.NotBefore,
NotAfter: cert.NotAfter,
AlternateNames: cert.DNSNames,
}
}
type TLSContext struct {
// Version holds the TLS version used.
Version string
// VersionRaw holds the raw TLS version.
VersionRaw uint16
// SNI may hold the server name used during
// the TLS handshake.
SNI string
// Chain holds the certificate chain used to
// verify the connection. This can only be set
// if Version is lower than TLS1.3
chain [][]*x509.Certificate
Chain [][]CertInfo
}
// SetChains configures the certificate chains used to verify
// the TLS connectionv.
func (tls *TLSContext) SetChains(chains [][]*x509.Certificate) {
tls.chain = chains
tls.Chain = nil
for _, chain := range chains {
var infoChain []CertInfo
for _, cert := range chain {
infoChain = append(infoChain, *NewCertInfo(cert))
}
tls.Chain = append(tls.Chain, infoChain)
}
}
// Connection describes a distinct physical network connection
// identified by the IP/Port pair.
type Connection struct { //nolint:maligned // TODO: fix alignment
@ -162,11 +215,26 @@ type Connection struct { //nolint:maligned // TODO: fix alignment
// profile and required for correct re-evaluation of a connections
// verdict.
ProfileRevisionCounter uint64
// TLS may holds metadata about the TLS version of a connection.
TLS *TLSContext
// addedToMetrics signifies if the connection has already been counted in
// the metrics.
addedToMetrics bool
inspectors []Inspector
dpiStateLock sync.Mutex
// dpiState allows each DPI to store internal per-connection
// state.
dpiState map[string]interface{}
handlerLock sync.RWMutex
packetHandlers []PacketHandler
streamHandlers []StreamHandler
dgramHandlers []DgramHandler
IncomingStream container.Container
OutgoingStream container.Container
}
// Reason holds information justifying a verdict, as well as additional
@ -604,13 +672,13 @@ func (conn *Connection) packetHandler() {
if conn.firewallHandler != nil {
conn.firewallHandler(conn, pkt)
} else {
log.Errorf("%s: using default firewall handler in packetHandler()", conn.ID)
defaultFirewallHandler(conn, pkt)
}
// log verdict
log.Tracer(pkt.Ctx()).Infof("filter: connection %s %s: %s", conn, conn.Verdict.Verb(), conn.Reason.Msg)
// save does not touch any changing data
// must not be locked, will deadlock with cleaner functions
if conn.saveWhenFinished {
conn.saveWhenFinished = false
conn.Save()
@ -623,16 +691,6 @@ func (conn *Connection) packetHandler() {
}
}
// GetInspectors returns the list of inspectors.
func (conn *Connection) GetInspectors() []Inspector {
return conn.inspectors
}
// SetInspectors sets the list of inspectors.
func (conn *Connection) SetInspectors(new []Inspector) {
conn.inspectors = new
}
// String returns a string representation of conn.
func (conn *Connection) String() string {
switch {

133
network/handlers.go Normal file
View file

@ -0,0 +1,133 @@
package network
import (
"fmt"
"github.com/google/gopacket"
)
type FlowDirection bool
// Keep in sync with reassembly.TCPFlowDirection
const (
ClientToServer = false
ServerToClient = true
)
func (dir FlowDirection) String() string {
if !dir {
return "client->server"
}
return "server->client"
}
func (dir FlowDirection) Reverse() FlowDirection { return !dir }
// VerdictReason is basically the same as endpoints.Reason but is
// duplicated here so packages don't need to depend on endpoints just
// for the Reason interface ...
type VerdictReason interface {
// String should return a human readable string
// describing the decision reason.
String() string
// Context returns the context that was used
// for the decision.
Context() interface{}
}
type PacketHandler interface {
HandlePacket(conn *Connection, p gopacket.Packet) (Verdict, VerdictReason, error)
}
type StreamHandler interface {
HandleStream(conn *Connection, dir FlowDirection, data []byte) (Verdict, VerdictReason, error)
}
type DgramHandler interface {
HandleDGRAM(conn *Connection, dir FlowDirection, data []byte) (Verdict, VerdictReason, error)
}
func (conn *Connection) GetDPIState(key string) interface{} {
conn.dpiStateLock.Lock()
defer conn.dpiStateLock.Unlock()
if conn.dpiState == nil {
return nil
}
return conn.dpiState[key]
}
func (conn *Connection) SetDPIState(key string, val interface{}) {
conn.dpiStateLock.Lock()
defer conn.dpiStateLock.Unlock()
if conn.dpiState == nil {
conn.dpiState = make(map[string]interface{})
}
conn.dpiState[key] = val
}
// AddHandler
func (conn *Connection) AddHandler(handler interface{}) error {
conn.handlerLock.Lock()
defer conn.handlerLock.Unlock()
added := false
if ph, ok := handler.(PacketHandler); ok {
added = true
conn.packetHandlers = append(conn.packetHandlers, ph)
}
if sh, ok := handler.(StreamHandler); ok {
added = true
conn.streamHandlers = append(conn.streamHandlers, sh)
}
if dh, ok := handler.(DgramHandler); ok {
added = true
conn.dgramHandlers = append(conn.dgramHandlers, dh)
}
if !added {
return fmt.Errorf("AddHandler called with invalid argument %T", handler)
}
return nil
}
func (conn *Connection) PacketHandlers() []PacketHandler {
conn.handlerLock.RLock()
defer conn.handlerLock.RUnlock()
return conn.packetHandlers
}
func (conn *Connection) RemoveHandler(idx int, handler interface{}) error {
conn.handlerLock.Lock()
defer conn.handlerLock.Unlock()
deleted := false
if _, ok := handler.(PacketHandler); ok {
deleted = true
conn.packetHandlers[idx] = nil
}
if _, ok := handler.(StreamHandler); ok {
deleted = true
conn.streamHandlers[idx] = nil
}
if _, ok := handler.(DgramHandler); ok {
deleted = true
conn.dgramHandlers[idx] = nil
}
if !deleted {
return fmt.Errorf("RemoveHandler called with invalid argument %T", handler)
}
return nil
}
func (conn *Connection) StreamHandlers() []StreamHandler {
conn.handlerLock.RLock()
defer conn.handlerLock.RUnlock()
return conn.streamHandlers
}
func (conn *Connection) DgramHandlers() []DgramHandler {
conn.handlerLock.RLock()
defer conn.handlerLock.RUnlock()
return conn.dgramHandlers
}

View file

@ -1,18 +1,12 @@
package network
import (
"github.com/safing/portmaster/network/packet"
)
// Inspector is a connection inspection interface for detailed analysis of network connections.
type Inspector interface {
// Name returns the name of the inspector.
Name() string
// Inspect is called for every packet. It returns whether it wants to proceed with processing and possibly an error.
Inspect(conn *Connection, pkt packet.Packet) (pktVerdict Verdict, proceed bool, err error)
// Destroy cancels the inspector and frees all resources.
// It is called as soon as Inspect returns proceed=false, an error occures, or if the inspection has ended early.
// It is called as soon as Inspect returns proceed=false,
// an error occures, or if the inspection has ended early.
Destroy() error
}

View file

@ -1,5 +1,6 @@
package netutils
/*
import (
"sync"
@ -49,3 +50,4 @@ func (a *SimpleStreamAssembler) Reassembled(reassembly []tcpassembly.Reassembly)
func (a *SimpleStreamAssembler) ReassemblyComplete() {
a.Complete = true
}
*/

View file

@ -320,9 +320,6 @@ func (lp *LayeredProfile) MatchServiceEndpoint(ctx context.Context, entity *inte
// MatchFilterLists matches the entity against the set of filter
// lists. This functions requires the layered profile to be read locked.
func (lp *LayeredProfile) MatchFilterLists(ctx context.Context, entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) {
entity.ResolveSubDomainLists(ctx, lp.FilterSubDomains())
entity.EnableCNAMECheck(ctx, lp.FilterCNAMEs())
for _, layer := range lp.layers {
// Search for the first layer that has filter lists set.
if layer.filterListsSet {