Skip to content

Commit 6e3e8cc

Browse files
authored
Merge pull request #5 from negbie/master
Simple LRU cache, more checks
2 parents ab352e2 + d4cff29 commit 6e3e8cc

File tree

5 files changed

+175
-131
lines changed

5 files changed

+175
-131
lines changed

README.md

+21-24
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
# heplify
22
heplify is captagents little brother. While it offers a compareable performance the design goal was simplicity.
3-
It's a single binary which you can place on your linux or windows machine. Just run it to capture packets and
4-
send them to Homer. Right now heplify is able to send SIP, DNS, LOG or TLS handshakes into homer. It's able to
3+
It's a single binary which you can run to capture packets and send them to Homer.
4+
Right now heplify is able to send SIP, correlated RTCP and very basic DNS, LOG or TLS handshakes into homer. It's able to
55
handle fragmented and duplicate packets out of the box.
66
<img align="right" width="300" src="https://user-images.githubusercontent.com/20154956/30700149-0278a246-9ee7-11e7-8aef-8d68baef554a.png">
77
### Requirements
8-
* libpcap
9-
10-
On Debian/Ubuntu: sudo apt-get install libpcap-dev
11-
On CentOS/RHEL: yum install libpcap-devel
12-
On Windows: install WinPcap
8+
* None if you use the binary from the [releases](https://github.com/sipcapture/heplify/releases)
139

1410
### Installation
1511
Simply grab it from the [releases](https://github.com/sipcapture/heplify/releases)
@@ -18,35 +14,36 @@ chmod +x heplify
1814
### Usage
1915
```bash
2016
-i Listen on interface
21-
-t Capture types are [af_packet, pcap, file] (default "pcap")
22-
-m Capture modes [DNS, LOG, SIP, TLS] (default "SIP")
17+
-t Capture types are [pcap, af_packet] (default "pcap")
18+
-m Capture modes [DNS, LOG, SIP, SIPRTCP, TLS] (default "SIP")
19+
-pr Portrange to capture SIP (default "5060-5090")
2320
-hs HEP Server address (default "127.0.0.1:9060")
24-
-di Discard uninteresting packets like SIP OPTIONS, HTTP Requests ...
25-
-fi Filter out interesting packets like SIP INVITES, Handshakes ...
26-
-rf Read packets from file. Please use -t file
27-
-wf Write packets to file
21+
-di Discard uninteresting packets
22+
-fi Filter interesting packets
23+
-rf Read packets from pcap file
24+
-wf Write packets to pcap file
2825
-e Log to stderr and disable syslog/file output
2926
-l Log level [debug, info, warning, error] (default "info")
3027
```
3128

3229
### Examples
3330
```bash
34-
# Capture SIP packets on eth2 and send them to Homer under 192.168.1.1:9060
35-
./heplify -i eth2 -hs "192.168.1.1:9060"
31+
# Capture SIP packets on eth2 and send them to 192.168.1.1:9060
32+
./heplify -i eth2 -hs 192.168.1.1:9060 &
3633

37-
# Print default log level to stdout
38-
./heplify -i eth2 -hs "192.168.1.1:9060" -e
34+
# Capture SIP packets on eth2 and send them to 192.168.1.1:9060. Print debug log level to stdout
35+
./heplify -i eth2 -hs 192.168.1.1:9060 -e -l debug
3936

40-
# Print debug log level to stdout
41-
./heplify -i eth2 -hs "192.168.1.1:9060" -e -l debug
37+
# Capture SIP packets with custom port range on eth2 and send them to 192.168.1.1:9060
38+
./heplify -i eth2 -pr 6000-6010 -hs 192.168.1.1:9060 &
4239

43-
# Capture LOG packets on eth2 and send them to Homer under 192.168.1.1:9060
44-
./heplify -i eth2 -hs "192.168.1.1:9060" -m LOG
40+
# Use af_packet to capture SIP and correlated RTCP packets on eth2 and send them to 192.168.1.1:9060
41+
./heplify -i eth2 -hs 192.168.1.1:9060 -t af_packet -m SIPRTCP &
4542

4643
# Capture SIP packets on eth2 and save them to pcap into current folder
47-
./heplify -i eth2 -wf capture.pcap
44+
./heplify -i eth2 -wf capture.pcap -t af_packet &
4845

49-
# Read pcap file from current folder and send it's content to Homer under 192.168.1.1:9060
50-
./heplify -i eth2 -t file -rf capture.pcap -hs "192.168.1.1:9060"
46+
# Read example/rtp_rtcp_sip.pcap and send SIP and correlated RTCP packets to 192.168.1.1:9060
47+
./heplify -rf example/rtp_rtcp_sip.pcap -m SIPRTCP -hs 192.168.1.1:9060 &
5148

5249
```

decoder/decoder.go

+33-58
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@ import (
55
"hash"
66
"os"
77
"strconv"
8-
"time"
98

10-
"github.com/allegro/bigcache"
119
"github.com/cespare/xxhash"
1210
"github.com/google/gopacket"
1311
"github.com/google/gopacket/layers"
14-
"github.com/hashicorp/golang-lru"
1512
"github.com/negbie/heplify/config"
1613
"github.com/negbie/heplify/ip4defrag"
1714
"github.com/negbie/heplify/logp"
@@ -28,11 +25,12 @@ type Decoder struct {
2825
tcpCount int
2926
dnsCount int
3027
unknownCount int
31-
IPFlow gopacket.Flow
32-
UDPFlow gopacket.Flow
28+
FlowSrcIP string
29+
FlowSrcPort string
3330
SIPHash hash.Hash64
34-
SIPCache *lru.Cache
35-
RTCPCache *bigcache.BigCache
31+
SIPCache *Cache
32+
SDPCache *Cache
33+
RTCPCache *Cache
3634
}
3735

3836
type Packet struct {
@@ -56,38 +54,10 @@ func NewDecoder() *Decoder {
5654
host = "sniffer"
5755
}
5856

59-
sh := xxhash.New()
60-
61-
sc, err := lru.New(8000)
62-
if err != nil {
63-
logp.Err("lru %v", err)
64-
}
65-
66-
rcConf := 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: false,
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-
rc, err := bigcache.NewBigCache(rcConf)
88-
if err != nil {
89-
logp.Err("bigcache %v", err)
90-
}
57+
hSIP := xxhash.New()
58+
cSIP := NewLRUCache(4000)
59+
cSDP := NewLRUCache(10000)
60+
cRTCP := NewLRUCache(100000)
9161

9262
d := &Decoder{
9363
Host: host,
@@ -99,9 +69,10 @@ func NewDecoder() *Decoder {
9969
tcpCount: 0,
10070
dnsCount: 0,
10171
unknownCount: 0,
102-
SIPHash: sh,
103-
SIPCache: sc,
104-
RTCPCache: rc,
72+
SIPHash: hSIP,
73+
SIPCache: cSIP,
74+
SDPCache: cSDP,
75+
RTCPCache: cRTCP,
10576
}
10677
go d.flushFrag()
10778
go d.printStats()
@@ -127,8 +98,7 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
12798

12899
if config.Cfg.Dedup {
129100
d.SIPHash.Write(ip4.Payload)
130-
//key := fastHash(ip4.Payload)
131-
key := d.SIPHash.Sum64()
101+
key := strconv.FormatUint(d.SIPHash.Sum64(), 10)
132102
d.SIPHash.Reset()
133103
_, dup := d.SIPCache.Get(key)
134104
d.SIPCache.Add(key, nil)
@@ -138,7 +108,7 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
138108
}
139109
}
140110

141-
d.IPFlow = ip4.NetworkFlow()
111+
d.FlowSrcIP = ip4.NetworkFlow().Src().String()
142112
d.ip4Count++
143113

144114
pkt.Version = ip4.Version
@@ -180,7 +150,7 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
180150
return nil, nil
181151
}
182152

183-
d.UDPFlow = udp.TransportFlow()
153+
d.FlowSrcPort = udp.TransportFlow().Src().String()
184154
d.udpCount++
185155

186156
pkt.Sport = uint16(udp.SrcPort)
@@ -190,7 +160,7 @@ func (d *Decoder) Process(data []byte, ci *gopacket.CaptureInfo) (*Packet, error
190160

191161
if config.Cfg.Mode == "SIPRTCP" {
192162
d.cacheSDPIPPort(udp.Payload)
193-
if (udp.Payload[0]&0xc0)>>6 == 2 && (udp.Payload[1] == 200 || udp.Payload[1] == 201) {
163+
if (udp.Payload[0]&0xc0)>>6 == 2 && udp.SrcPort%2 != 0 && udp.DstPort%2 != 0 && (udp.Payload[1] == 200 || udp.Payload[1] == 201) {
194164
pkt.Payload, pkt.CorrelationID, pkt.Type = d.correlateRTCP(udp.Payload)
195165
}
196166
}
@@ -276,23 +246,28 @@ func (d *Decoder) cacheSDPIPPort(payload []byte) {
276246
logp.Warn("Couldn't find end of Call-ID in '%s'", string(restID))
277247
}
278248
}
279-
d.RTCPCache.Set(SDPIP+RTCPPort, callID)
249+
d.SDPCache.Add(SDPIP+RTCPPort, callID)
280250
}
281251
}
282252

283253
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
254+
jsonRTCP, info := protos.ParseRTCP(payload)
255+
if info != "" {
256+
logp.Info("%v", info)
257+
if jsonRTCP == nil {
258+
return nil, nil, 0
259+
}
288260
}
289261

290-
corrID, err := d.RTCPCache.Get(d.IPFlow.Src().String() + d.UDPFlow.Src().String())
291-
if err != nil {
292-
logp.Warn("%v", err)
293-
return nil, nil, 0
262+
if corrID, ok := d.SDPCache.Get(d.FlowSrcIP + d.FlowSrcPort); ok {
263+
logp.Debug("decoder", "SDPCache RTCP JSON payload: %s", string(jsonRTCP))
264+
d.RTCPCache.Add(d.FlowSrcIP+d.FlowSrcPort, corrID)
265+
return jsonRTCP, corrID, 5
266+
} else if corrID, ok := d.RTCPCache.Get(d.FlowSrcIP + d.FlowSrcPort); ok {
267+
logp.Debug("decoder", "RTCPCache RTCP JSON payload: %s", string(jsonRTCP))
268+
return jsonRTCP, corrID, 5
294269
}
295270

296-
logp.Debug("decoder", "RTCP JSON payload: %s", string(jsonRTCP))
297-
return jsonRTCP, corrID, 5
271+
logp.Info("Couldn't find RTCP correlation value for key=%v", d.FlowSrcIP+d.FlowSrcPort)
272+
return nil, nil, 0
298273
}

decoder/util.go

+67
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package decoder
22

33
import (
4+
"container/list"
45
"encoding/binary"
56
"net"
7+
"sync"
68
"time"
79

810
"github.com/negbie/heplify/logp"
@@ -46,3 +48,68 @@ func (d *Decoder) printStats() {
4648
}()
4749
}
4850
}
51+
52+
type cacheValue struct {
53+
key string
54+
bytes []byte
55+
}
56+
57+
// Just an estimate
58+
func (v *cacheValue) size() uint64 {
59+
return uint64(len([]byte(v.key)) + len(v.bytes))
60+
}
61+
62+
type Cache struct {
63+
sync.Mutex
64+
Size uint64
65+
capacity uint64
66+
list *list.List
67+
table map[string]*list.Element
68+
}
69+
70+
// NewLRUCache with a maximum size of capacity bytes.
71+
func NewLRUCache(capacity uint64) *Cache {
72+
return &Cache{
73+
capacity: capacity,
74+
list: list.New(),
75+
table: make(map[string]*list.Element),
76+
}
77+
}
78+
79+
// Set some {key, document} into the cache. Doesn't do anything if the key is already present.
80+
func (c *Cache) Add(key string, document []byte) {
81+
c.Lock()
82+
defer c.Unlock()
83+
84+
_, ok := c.table[key]
85+
if ok {
86+
return
87+
}
88+
v := &cacheValue{key, document}
89+
elt := c.list.PushFront(v)
90+
c.table[key] = elt
91+
c.Size += v.size()
92+
for c.Size > c.capacity {
93+
elt := c.list.Back()
94+
if elt == nil {
95+
return
96+
}
97+
v := c.list.Remove(elt).(*cacheValue)
98+
delete(c.table, v.key)
99+
c.Size -= v.size()
100+
}
101+
}
102+
103+
// Get retrieves a value from the cache and returns the value and an indicator boolean to show whether it was
104+
// present.
105+
func (c *Cache) Get(key string) (document []byte, ok bool) {
106+
c.Lock()
107+
defer c.Unlock()
108+
109+
elt, ok := c.table[key]
110+
if !ok {
111+
return nil, false
112+
}
113+
c.list.MoveToFront(elt)
114+
return elt.Value.(*cacheValue).bytes, true
115+
}

0 commit comments

Comments
 (0)