mirror of
https://github.com/safing/portmaster
synced 2025-04-25 13:29:10 +00:00
Rework inspection framework
This commit is contained in:
parent
d044686935
commit
efec52ba18
21 changed files with 1553 additions and 79 deletions
cmds/portmaster-core
firewall
go.modgo.sumnetwork
profile
|
@ -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
168
firewall/dpi/assembler.go
Normal 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
186
firewall/dpi/manager.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
91
firewall/inspection/http/httpinspect.go
Normal file
91
firewall/inspection/http/httpinspect.go
Normal 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)
|
|
@ -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
|
||||
}
|
||||
|
|
108
firewall/inspection/inspectutils/http_request.go
Normal file
108
firewall/inspection/inspectutils/http_request.go
Normal 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
|
||||
}
|
9
firewall/inspection/inspectutils/reason.go
Normal file
9
firewall/inspection/inspectutils/reason.go
Normal 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 }
|
173
firewall/inspection/tls/handshake.go
Normal file
173
firewall/inspection/tls/handshake.go
Normal 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
|
||||
}
|
192
firewall/inspection/tls/inspector.go
Normal file
192
firewall/inspection/tls/inspector.go
Normal 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)
|
233
firewall/inspection/tls/tls_layer.go
Normal file
233
firewall/inspection/tls/tls_layer.go
Normal 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())
|
||||
}
|
110
firewall/inspection/upnpigd/inspector.go
Normal file
110
firewall/inspection/upnpigd/inspector.go
Normal 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)
|
|
@ -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
10
go.mod
|
@ -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
12
go.sum
|
@ -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=
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
133
network/handlers.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue