Skip to content

Commit efec52b

Browse files
committed
Rework inspection framework
1 parent d044686 commit efec52b

File tree

21 files changed

+1553
-79
lines changed

21 files changed

+1553
-79
lines changed

cmds/portmaster-core/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import (
1313
_ "github.com/safing/portmaster/core"
1414
_ "github.com/safing/portmaster/firewall"
1515
_ "github.com/safing/portmaster/firewall/inspection/encryption"
16+
_ "github.com/safing/portmaster/firewall/inspection/http"
17+
_ "github.com/safing/portmaster/firewall/inspection/tls"
18+
_ "github.com/safing/portmaster/firewall/inspection/upnpigd"
1619
_ "github.com/safing/portmaster/nameserver"
1720
_ "github.com/safing/portmaster/ui"
1821
_ "github.com/safing/spn/captain"

firewall/dpi/assembler.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package dpi
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/google/gopacket"
7+
"github.com/google/gopacket/layers"
8+
"github.com/google/gopacket/reassembly"
9+
"github.com/safing/portbase/log"
10+
"github.com/safing/portmaster/network"
11+
)
12+
13+
// Context implements reassembly.AssemblerContext.
14+
type Context struct {
15+
CaptureInfo gopacket.CaptureInfo
16+
Connection *network.Connection
17+
Verdict *network.Verdict
18+
Reason network.VerdictReason
19+
Tracer *log.ContextTracer
20+
HandlerExecuted bool
21+
}
22+
23+
func (c *Context) GetCaptureInfo() gopacket.CaptureInfo {
24+
return c.CaptureInfo
25+
}
26+
27+
type tcpStreamFactory struct {
28+
manager *Manager
29+
}
30+
31+
func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream {
32+
log.Infof("tcp-stream-factory: new stream for %s %s", net, transport)
33+
fsmOptions := reassembly.TCPSimpleFSMOptions{
34+
SupportMissingEstablishment: true,
35+
}
36+
stream := &tcpStream{
37+
net: net,
38+
transport: transport,
39+
tcpstate: reassembly.NewTCPSimpleFSM(fsmOptions),
40+
ident: fmt.Sprintf("%s:%s", net, transport),
41+
optchecker: reassembly.NewTCPOptionCheck(),
42+
manager: factory.manager,
43+
}
44+
return stream
45+
}
46+
47+
type tcpStream struct {
48+
conn *network.Connection
49+
manager *Manager
50+
51+
tcpstate *reassembly.TCPSimpleFSM
52+
optchecker reassembly.TCPOptionCheck
53+
net, transport gopacket.Flow
54+
ident string
55+
}
56+
57+
func (t *tcpStream) Accept(
58+
tcp *layers.TCP,
59+
ci gopacket.CaptureInfo,
60+
dir reassembly.TCPFlowDirection,
61+
nextSeq reassembly.Sequence,
62+
start *bool,
63+
ac reassembly.AssemblerContext) bool {
64+
65+
conn := ac.(*Context).Connection
66+
if t.conn != nil && t.conn.ID != conn.ID {
67+
// TODO(ppacher): for localhost to localhost connections this stream-reassembler will be called for both
68+
// connections because gopacket's flow IDs collide with it's reverse tuple. That's on purpose so
69+
// client->server and server->client packets are attributed correctly but it may cause errors if the
70+
// portmaster sees both, the client and server side of a connection
71+
if t.conn.LocalPort != conn.Entity.Port {
72+
panic(fmt.Sprintf("TCPStream already has a connection object assigned: %s != %s", t.conn.ID, conn.ID))
73+
}
74+
}
75+
t.conn = conn
76+
77+
if !t.tcpstate.CheckState(tcp, dir) {
78+
log.Errorf("tcp-stream %s: fsm: packet rejected by FSM (state: %s)", t.ident, t.tcpstate.String())
79+
return false
80+
}
81+
82+
err := t.optchecker.Accept(tcp, ci, dir, nextSeq, start)
83+
if err != nil {
84+
log.Errorf("tcp-stream %s: option-checker: packet rejected: %s", t.ident, err)
85+
return false
86+
}
87+
88+
return true
89+
}
90+
91+
func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
92+
c := ac.(*Context)
93+
conn := c.Connection
94+
95+
dir, start, end, skip := sg.Info()
96+
length, saved := sg.Lengths()
97+
sgStats := sg.Stats()
98+
99+
data := sg.Fetch(length)
100+
var ident string
101+
if dir == reassembly.TCPDirClientToServer {
102+
ident = fmt.Sprintf("%v %v(%s): ", t.net, t.transport, dir)
103+
conn.OutgoingStream.Append(data)
104+
} else {
105+
ident = fmt.Sprintf("%v %v(%s): ", t.net.Reverse(), t.transport.Reverse(), dir)
106+
conn.IncomingStream.Append(data)
107+
}
108+
109+
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)
110+
111+
var (
112+
verdict network.Verdict
113+
reason network.VerdictReason
114+
hasHandler bool
115+
)
116+
c.HandlerExecuted = true
117+
118+
all := conn.StreamHandlers()
119+
for idx, sh := range all {
120+
if sh == nil {
121+
continue
122+
}
123+
name := fmt.Sprintf("%T", sh)
124+
if n, ok := sh.(named); ok {
125+
name = n.Name()
126+
}
127+
128+
hasHandler = true
129+
c.Tracer.Infof("inspector(%s, %d/%d): running stream inspector", name, idx+1, len(all))
130+
v, r, err := sh.HandleStream(conn, network.FlowDirection(dir), data)
131+
if err != nil {
132+
c.Tracer.Errorf("inspector(%s): failed to run stream handler: %s", name, err)
133+
}
134+
if v == network.VerdictUndeterminable {
135+
// not applicable anymore
136+
c.Tracer.Debugf("inspector(%s): stream inspector is not applicable anymore", name)
137+
conn.RemoveHandler(idx, sh)
138+
continue
139+
}
140+
if err != nil {
141+
continue
142+
}
143+
144+
if v > network.VerdictUndecided {
145+
c.Tracer.Infof("inspector(%s): stream inspector found a conclusion: %s", name, v.String())
146+
}
147+
if v > verdict {
148+
verdict = v
149+
reason = r
150+
}
151+
}
152+
if hasHandler {
153+
c.Verdict = &verdict
154+
c.Reason = reason
155+
}
156+
}
157+
158+
func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
159+
log.Infof("tcp-stream %s: connection closed", t.ident)
160+
161+
if t.conn == nil {
162+
return true
163+
}
164+
165+
log.Infof("tcp-stream: connection %s sent %d bytes and received %d bytes", t.conn.OutgoingStream.Length(), t.conn.IncomingStream.Length())
166+
167+
return true
168+
}

firewall/dpi/manager.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package dpi
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/google/gopacket"
7+
"github.com/google/gopacket/ip4defrag"
8+
"github.com/google/gopacket/layers"
9+
"github.com/google/gopacket/reassembly"
10+
"github.com/safing/portbase/log"
11+
"github.com/safing/portmaster/network"
12+
"github.com/safing/portmaster/network/packet"
13+
)
14+
15+
type Manager struct {
16+
defragv4 *ip4defrag.IPv4Defragmenter
17+
pool *reassembly.StreamPool
18+
assembler *reassembly.Assembler
19+
}
20+
21+
func NewManager() *Manager {
22+
streamFactory := new(tcpStreamFactory)
23+
streamPool := reassembly.NewStreamPool(streamFactory)
24+
assembler := reassembly.NewAssembler(streamPool)
25+
26+
mng := &Manager{
27+
defragv4: ip4defrag.NewIPv4Defragmenter(),
28+
pool: streamPool,
29+
assembler: assembler,
30+
}
31+
32+
// make sure the streamFactory has a reference to the
33+
// manager for dispatching reassembled streams.
34+
streamFactory.manager = mng
35+
36+
return mng
37+
}
38+
39+
type named interface{ Name() string }
40+
41+
func (mng *Manager) HandlePacket(conn *network.Connection, p packet.Packet) (network.Verdict, network.VerdictReason, error) {
42+
trace := log.Tracer(p.Ctx())
43+
44+
gp := p.Layers()
45+
46+
// if this is a IPv4 packet make sure we defrag it.
47+
ipv4Layer := gp.Layer(layers.LayerTypeIPv4)
48+
if ipv4Layer != nil {
49+
ipv4 := ipv4Layer.(*layers.IPv4)
50+
l := ipv4.Length
51+
52+
newip4, err := mng.defragv4.DefragIPv4(ipv4)
53+
if err != nil {
54+
return 0, nil, fmt.Errorf("failed to de-fragment: %w", err)
55+
}
56+
if newip4 == nil {
57+
// this is a fragmented packet
58+
// wait for the next one
59+
trace.Debugf("tcp-stream-manager: fragmented IPv4 packet ...")
60+
return 0, nil, nil
61+
}
62+
if newip4.Length != l {
63+
pb, ok := gp.(gopacket.PacketBuilder)
64+
if !ok {
65+
return 0, nil, fmt.Errorf("expected a PacketBuilder, got %T", p)
66+
}
67+
trace.Debugf("decoding re-assembled packet ...")
68+
nextDecoder := newip4.NextLayerType()
69+
nextDecoder.Decode(newip4.Payload, pb)
70+
}
71+
}
72+
73+
var (
74+
verdict network.Verdict
75+
reason network.VerdictReason
76+
hasActiveHandler bool
77+
)
78+
for idx, pk := range conn.PacketHandlers() {
79+
if pk == nil {
80+
continue
81+
}
82+
name := fmt.Sprintf("%T", pk)
83+
if n, ok := pk.(named); ok {
84+
name = n.Name()
85+
}
86+
87+
hasActiveHandler = true
88+
89+
trace.Infof("%s: running packet inspector", name)
90+
v, r, err := pk.HandlePacket(conn, gp)
91+
if err != nil {
92+
trace.Errorf("inspector(%s): failed to call packet handler: %s", name, err)
93+
}
94+
if v == network.VerdictUndeterminable {
95+
// this handler is not applicable for conn anymore
96+
trace.Debugf("inspector(%s): packet inspector is not applicable for this connection anymore ...", name)
97+
conn.RemoveHandler(idx, pk)
98+
continue
99+
}
100+
if err != nil {
101+
continue
102+
}
103+
104+
if v > network.VerdictUndecided {
105+
trace.Infof("inspector(%s): packet inspector found a conclusion: %s", name, v.String())
106+
}
107+
if v > verdict {
108+
verdict = v
109+
reason = r
110+
}
111+
}
112+
113+
// handle TCP stream reassembling
114+
tcp := gp.Layer(layers.LayerTypeTCP)
115+
if tcp != nil {
116+
tcp := tcp.(*layers.TCP)
117+
c := &Context{
118+
CaptureInfo: gp.Metadata().CaptureInfo,
119+
Connection: conn,
120+
Tracer: trace,
121+
}
122+
123+
// reassemble the stream and call any stream handlers of the connection
124+
mng.assembler.AssembleWithContext(gp.NetworkLayer().NetworkFlow(), tcp, c)
125+
if !c.HandlerExecuted {
126+
// if we did not even try to execute the handler
127+
// we need to assume there are still active ones.
128+
// This may happen we we are still waiting for
129+
// an IP frame ...
130+
hasActiveHandler = true
131+
} else {
132+
if c.Verdict != nil {
133+
hasActiveHandler = true
134+
if *c.Verdict > verdict {
135+
verdict = *c.Verdict
136+
}
137+
}
138+
}
139+
}
140+
141+
udp := gp.Layer(layers.LayerTypeUDP)
142+
if udp != nil {
143+
payload := gp.ApplicationLayer().Payload()
144+
for idx, uh := range conn.DgramHandlers() {
145+
if uh == nil {
146+
continue
147+
}
148+
name := fmt.Sprintf("%T", uh)
149+
if n, ok := uh.(named); ok {
150+
name = n.Name()
151+
}
152+
153+
hasActiveHandler = true
154+
trace.Infof("inspector(%s): running dgram inspector", name)
155+
v, r, err := uh.HandleDGRAM(conn, network.FlowDirection(!conn.Inbound), payload)
156+
if err != nil {
157+
trace.Errorf("inspector(%s): failed to run dgram handler: %s", name, err)
158+
}
159+
if v == network.VerdictUndeterminable {
160+
trace.Debugf("inspector(%s): dgram inspector is not applicable for this connection anymore ...", name)
161+
conn.RemoveHandler(idx, uh)
162+
continue
163+
}
164+
if err != nil {
165+
continue
166+
}
167+
168+
if v > network.VerdictUndecided {
169+
trace.Infof("inspector(%s): dgram inspector found a conclusion: %s", name, v.String())
170+
}
171+
if v > verdict {
172+
verdict = v
173+
reason = r
174+
}
175+
}
176+
}
177+
178+
if !hasActiveHandler || verdict > network.VerdictUndeterminable {
179+
trace.Infof("stopping inspection %s: hasActiveHandler=%v verdict=%s", conn.ID, hasActiveHandler, verdict.String())
180+
// we don't have any active handlers anymore so
181+
// there's no need to continue inspection
182+
conn.Inspecting = false
183+
}
184+
185+
return verdict, reason, nil
186+
}

0 commit comments

Comments
 (0)