Skip to content

Commit dab65d8

Browse files
authored
Merge pull request #3 from negbie/master
SIP <-> RTCP correlation
2 parents d2dcb3f + 24cece9 commit dab65d8

File tree

8 files changed

+478
-62
lines changed

8 files changed

+478
-62
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ heplify is captagents little brother. While it offers a compareable performance
33
It's a single binary which you can place on your linux or windows machine. Just run it to capture packets and
44
send them to Homer. Right now heplify is able to send SIP, DNS, LOG or TLS handshakes into homer. It's able to
55
handle fragmented and duplicate packets out of the box.
6-
6+
<img align="right" width="300" src="https://user-images.githubusercontent.com/20154956/30700149-0278a246-9ee7-11e7-8aef-8d68baef554a.png">
77
### Requirements
88
* libpcap
9-
<img align="right" width="300" src="https://user-images.githubusercontent.com/20154956/30700149-0278a246-9ee7-11e7-8aef-8d68baef554a.png">
9+
1010
On Debian/Ubuntu: sudo apt-get install libpcap-dev
1111
On CentOS/RHEL: yum install libpcap-devel
1212
On Windows: install WinPcap

build_static.sh

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/sh
2+
3+
set -ex
4+
5+
apk update
6+
apk add linux-headers musl-dev gcc go libpcap-dev ca-certificates git
7+
8+
mkdir /go
9+
export GOPATH=/go
10+
mkdir -p /go/src/github.com/negbie
11+
mkdir -p /mnt/out
12+
cp -a /mnt /go/src/github.com/negbie/heplify
13+
cd /go/src/github.com/negbie/heplify
14+
rm -f heplify*
15+
go get -v ./ ./
16+
go build --ldflags '-linkmode external -extldflags "-static -s -w"' -v ./
17+
cp ./heplify /mnt/out/

config/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type InterfacesConfig struct {
2323
WriteFile string `config:"write_file"`
2424
Snaplen int `config:"snaplen"`
2525
BufferSizeMb int `config:"buffer_size_mb"`
26-
TopSpeed bool `config:"top_speed"`
26+
ReadSpeed bool `config:"top_speed"`
2727
OneAtATime bool `config:"one_at_a_time"`
2828
Loop int `config:"loop"`
2929
}

decoder/decoder.go

+122-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package decoder
22

33
import (
4+
"bytes"
45
"hash"
56
"os"
7+
"strconv"
8+
"time"
69

10+
"github.com/allegro/bigcache"
711
"github.com/cespare/xxhash"
812
"github.com/google/gopacket"
913
"github.com/google/gopacket/layers"
@@ -24,7 +28,10 @@ type Decoder struct {
2428
tcpCount int
2529
dnsCount int
2630
unknownCount int
27-
lru *lru.ARCCache
31+
IPFlow gopacket.Flow
32+
UDPFlow gopacket.Flow
33+
lru *lru.Cache
34+
bigcache *bigcache.BigCache
2835
hash hash.Hash64
2936
}
3037

@@ -40,19 +47,48 @@ type Packet struct {
4047
Dport uint16
4148
CorrelationID []byte
4249
Payload []byte
50+
Type byte
4351
}
4452

4553
func NewDecoder() *Decoder {
46-
4754
host, err := os.Hostname()
4855
if err != nil {
4956
host = "sniffer"
5057
}
51-
l, err := lru.NewARC(8192)
58+
59+
la, err := lru.New(8000)
5260
if err != nil {
5361
logp.Err("lru %v", err)
5462
}
55-
h := xxhash.New()
63+
64+
xh := xxhash.New()
65+
66+
bConf := bigcache.Config{
67+
// number of shards (must be a power of 2)
68+
Shards: 1024,
69+
// time after which entry can be evicted
70+
LifeWindow: 180 * time.Minute,
71+
// rps * lifeWindow, used only in initial memory allocation
72+
MaxEntriesInWindow: 1000 * 180 * 60,
73+
// max entry size in bytes, used only in initial memory allocation
74+
MaxEntrySize: 300,
75+
// prints information about additional memory allocation
76+
Verbose: true,
77+
// cache will not allocate more memory than this limit, value in MB
78+
// if value is reached then the oldest entries can be overridden for the new ones
79+
// 0 value means no size limit
80+
HardMaxCacheSize: 512,
81+
// callback fired when the oldest entry is removed because of its
82+
// expiration time or no space left for the new entry. Default value is nil which
83+
// means no callback and it prevents from unwrapping the oldest entry.
84+
OnRemove: nil,
85+
}
86+
87+
bc, err := bigcache.NewBigCache(bConf)
88+
if err != nil {
89+
logp.Err("bigcache %v", err)
90+
}
91+
5692
d := &Decoder{
5793
Host: host,
5894
defragger: ip4defrag.NewIPv4Defragmenter(),
@@ -63,8 +99,9 @@ func NewDecoder() *Decoder {
6399
tcpCount: 0,
64100
dnsCount: 0,
65101
unknownCount: 0,
66-
lru: l,
67-
hash: h,
102+
lru: la,
103+
hash: xh,
104+
bigcache: bc,
68105
}
69106
go d.flushFrag()
70107
go d.printStats()
@@ -101,16 +138,18 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
101138
}
102139
}
103140

141+
d.IPFlow = ip4.NetworkFlow()
104142
d.ip4Count++
143+
105144
pkt.Version = ip4.Version
106145
pkt.Protocol = uint8(ip4.Protocol)
107146
pkt.Srcip = ip2int(ip4.SrcIP)
108147
pkt.Dstip = ip2int(ip4.DstIP)
109148

110149
ip4New, err := d.defragger.DefragIPv4(ip4)
111150
if err != nil {
112-
logp.Err("Error while de-fragmenting", err)
113-
return nil, err
151+
logp.Warn("Error while de-fragmenting", err)
152+
return nil, nil
114153
} else if ip4New == nil {
115154
d.fragCount++
116155
return nil, nil
@@ -133,21 +172,28 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
133172
nextDecoder := ip4New.NextLayerType()
134173
nextDecoder.Decode(ip4New.Payload, pb)
135174
}
136-
// TODO: generate a more meaningful CorrelationID
137-
if config.Cfg.Mode == "DNS" || config.Cfg.Mode == "LOG" || config.Cfg.Mode == "TLS" {
138-
pkt.CorrelationID = []byte(config.Cfg.Mode)
139-
}
140175
}
141176

142177
if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil {
143178
udp, ok := udpLayer.(*layers.UDP)
144179
if !ok {
145180
return nil, nil
146181
}
182+
183+
d.UDPFlow = udp.TransportFlow()
147184
d.udpCount++
185+
148186
pkt.Sport = uint16(udp.SrcPort)
149187
pkt.Dport = uint16(udp.DstPort)
150188
pkt.Payload = udp.Payload
189+
pkt.Type = 1
190+
191+
if config.Cfg.Mode == "SIPRTCP" {
192+
d.cacheSDPIPPort(udp.Payload)
193+
if (udp.Payload[0]&0xc0)>>6 == 2 && (udp.Payload[1] == 200 || udp.Payload[1] == 201) {
194+
pkt.Payload, pkt.CorrelationID, pkt.Type = d.correlateRTCP(udp.Payload)
195+
}
196+
}
151197

152198
} else if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
153199
tcp, ok := tcpLayer.(*layers.TCP)
@@ -158,6 +204,11 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
158204
pkt.Sport = uint16(tcp.SrcPort)
159205
pkt.Dport = uint16(tcp.DstPort)
160206
pkt.Payload = tcp.Payload
207+
pkt.Type = 1
208+
209+
if config.Cfg.Mode == "SIPRTCP" {
210+
d.cacheSDPIPPort(tcp.Payload)
211+
}
161212
}
162213

163214
if dnsLayer := packet.Layer(layers.LayerTypeDNS); dnsLayer != nil {
@@ -186,3 +237,62 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
186237
d.unknownCount++
187238
return nil, nil
188239
}
240+
241+
func (d *Decoder) cacheSDPIPPort(payload []byte) {
242+
var SDPIP, RTCPPort string
243+
var callID []byte
244+
245+
if posSDPIP, posSDPPort := bytes.Index(payload, []byte("c=IN IP4 ")), bytes.Index(payload, []byte("m=audio ")); posSDPIP >= 0 && posSDPPort >= 0 {
246+
restIP := payload[posSDPIP:]
247+
if posRestIP := bytes.Index(restIP, []byte("\r\n")); posRestIP >= 0 {
248+
SDPIP = string(restIP[len("c=IN IP4 "):bytes.Index(restIP, []byte("\r\n"))])
249+
} else {
250+
logp.Warn("Couldn't find end of SDP IP in '%s'", string(restIP))
251+
}
252+
253+
restPort := payload[posSDPPort:]
254+
if posRestPort := bytes.Index(restIP, []byte(" RTP")); posRestPort >= 0 {
255+
SDPPort, err := strconv.Atoi(string(restPort[len("m=audio "):bytes.Index(restPort, []byte(" RTP"))]))
256+
if err != nil {
257+
logp.Warn("%v", err)
258+
}
259+
RTCPPort = strconv.Itoa(SDPPort + 1)
260+
} else {
261+
logp.Warn("Couldn't find end of SDP Port in '%s'", string(restPort))
262+
}
263+
264+
if posCallID := bytes.Index(payload, []byte("Call-ID: ")); posCallID >= 0 {
265+
restCallID := payload[posCallID:]
266+
if posRestCallID := bytes.Index(restIP, []byte("\r\n")); posRestCallID >= 0 {
267+
callID = restCallID[len("Call-ID: "):bytes.Index(restCallID, []byte("\r\n"))]
268+
} else {
269+
logp.Warn("Couldn't find end of Call-ID in '%s'", string(restCallID))
270+
}
271+
} else if posID := bytes.Index(payload, []byte("i: ")); posID >= 0 {
272+
restID := payload[posID:]
273+
if posRestID := bytes.Index(restIP, []byte("\r\n")); posRestID >= 0 {
274+
callID = restID[len("i: "):bytes.Index(restID, []byte("\r\n"))]
275+
} else {
276+
logp.Warn("Couldn't find end of Call-ID in '%s'", string(restID))
277+
}
278+
}
279+
d.bigcache.Set(SDPIP+RTCPPort, callID)
280+
}
281+
}
282+
283+
func (d *Decoder) correlateRTCP(payload []byte) ([]byte, []byte, byte) {
284+
jsonRTCP, err := protos.ParseRTCP(payload)
285+
if err != nil {
286+
logp.Warn("%v", err)
287+
return nil, nil, 0
288+
}
289+
290+
corrID, err := d.bigcache.Get(d.IPFlow.Src().String() + d.UDPFlow.Src().String())
291+
if err != nil {
292+
logp.Warn("%v", err)
293+
return nil, nil, 0
294+
}
295+
296+
logp.Debug("decoder", "RTCP JSON payload: %s", string(jsonRTCP))
297+
return jsonRTCP, corrID, 5
298+
}

main.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ func parseFlags() {
2828
flag.StringVar(&ifaceConfig.Type, "t", "pcap", "Capture types are [af_packet, pcap, file]")
2929
flag.StringVar(&ifaceConfig.ReadFile, "rf", "", "Read packets from file. Please use -t file")
3030
flag.StringVar(&ifaceConfig.WriteFile, "wf", "", "Write packets to file")
31-
flag.IntVar(&ifaceConfig.Loop, "lp", 0, "Loop count over ReadFile")
32-
flag.BoolVar(&ifaceConfig.TopSpeed, "ts", false, "Topspeed uses timestamps from packets")
31+
flag.IntVar(&ifaceConfig.Loop, "lp", 1, "Loop count over ReadFile")
32+
flag.BoolVar(&ifaceConfig.ReadSpeed, "rs", false, "Maximum read speed. Doesn't use packet timestamps")
3333
flag.IntVar(&ifaceConfig.Snaplen, "s", 32768, "Snap length")
3434
flag.IntVar(&ifaceConfig.BufferSizeMb, "b", 64, "Interface buffersize (MB)")
3535
flag.IntVar(&keepLogFiles, "kl", 4, "Rotate the number of log files")
@@ -38,7 +38,7 @@ func parseFlags() {
3838
flag.StringVar(&fileRotator.Path, "p", "./", "Log filepath")
3939
flag.StringVar(&fileRotator.Name, "n", "heplify.log", "Log filename")
4040
flag.Uint64Var(&rotateEveryKB, "r", 16384, "Log filesize (KB)")
41-
flag.StringVar(&config.Cfg.Mode, "m", "SIP", "Capture modes [DNS, LOG, SIP, TLS]")
41+
flag.StringVar(&config.Cfg.Mode, "m", "SIP", "Capture modes [DNS, LOG, SIP, RTCP, TLS]")
4242
flag.BoolVar(&config.Cfg.Dedup, "dd", true, "Deduplicate packets")
4343
flag.StringVar(&config.Cfg.Filter, "fi", "", "Filter out interesting packets like SIP INVITES, Handshakes ...")
4444
flag.StringVar(&config.Cfg.Discard, "di", "", "Discard uninteresting packets like SIP OPTIONS, HTTP Requests ...")

0 commit comments

Comments
 (0)