|
| 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 | +} |
0 commit comments