Skip to content

Commit b5b9f84

Browse files
donkahlerogconnell
authored andcommitted
Add Support for LCM Defragmentation
This patch includes support for defragmentation of LCM packages, as specified in the LCM protocol description (https://lcm-proj.github.io/udp_multicast_protocol.html). This patch code should also be used in combination with an upcoming PR towards the GitHub LCM repository that adds golang + gopacket support.
1 parent 60ab61c commit b5b9f84

File tree

5 files changed

+256
-2
lines changed

5 files changed

+256
-2
lines changed

.travis.golint.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
cd "$(dirname $0)"
44

55
go get github.com/golang/lint/golint
6-
DIRS=". tcpassembly tcpassembly/tcpreader ip4defrag reassembly macs pcapgo pcap afpacket pfring routing"
6+
DIRS=". tcpassembly tcpassembly/tcpreader ip4defrag reassembly macs pcapgo pcap afpacket pfring routing defrag"
77
# Add subdirectories here as we clean up golint on each.
88
for subdir in $DIRS; do
99
pushd $subdir

.travis.govet.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22

33
cd "$(dirname $0)"
4-
DIRS=". layers pcap pcapgo pfring tcpassembly tcpassembly/tcpreader routing ip4defrag bytediff macs"
4+
DIRS=". layers pcap pcapgo pfring tcpassembly tcpassembly/tcpreader routing ip4defrag bytediff macs defrag"
55
set -e
66
for subdir in $DIRS; do
77
pushd $subdir

defrag/lcmdefrag.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2018 Google, Inc. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style license
4+
// that can be found in the LICENSE file in the root of the source
5+
// tree.
6+
7+
package defrag
8+
9+
import (
10+
"fmt"
11+
"time"
12+
13+
"github.com/google/gopacket"
14+
"github.com/google/gopacket/layers"
15+
)
16+
17+
const (
18+
// Packages are cleaned up/removed after no input was received for this
19+
// amount of seconds.
20+
timeout time.Duration = 3 * time.Second
21+
)
22+
23+
type lcmPacket struct {
24+
lastPacket time.Time
25+
done bool
26+
recFrags uint16
27+
totalFrags uint16
28+
frags map[uint16]*layers.LCM
29+
}
30+
31+
// LCMDefragmenter supports defragmentation of LCM messages.
32+
//
33+
// References
34+
// https://lcm-proj.github.io/
35+
// https://github.com/lcm-proj/lcm
36+
type LCMDefragmenter struct {
37+
packets map[uint32]*lcmPacket
38+
}
39+
40+
func newLCMPacket(totalFrags uint16) *lcmPacket {
41+
return &lcmPacket{
42+
done: false,
43+
recFrags: 0,
44+
totalFrags: totalFrags,
45+
frags: make(map[uint16]*layers.LCM),
46+
}
47+
}
48+
49+
// NewLCMDefragmenter returns a new LCMDefragmenter.
50+
func NewLCMDefragmenter() *LCMDefragmenter {
51+
return &LCMDefragmenter{
52+
packets: make(map[uint32]*lcmPacket),
53+
}
54+
}
55+
56+
func (lp *lcmPacket) append(in *layers.LCM) {
57+
lp.frags[in.FragmentNumber] = in
58+
lp.recFrags++
59+
lp.lastPacket = time.Now()
60+
}
61+
62+
func (lp *lcmPacket) assemble() (out *layers.LCM, err error) {
63+
var blob []byte
64+
65+
//Extract packets
66+
for i := uint16(0); i < lp.totalFrags; i++ {
67+
fragment, ok := lp.frags[i]
68+
if !ok {
69+
err = fmt.Errorf("Tried to defragment incomplete packet. Waiting "+
70+
"for more potential (unordered) packets... %d", i)
71+
return
72+
}
73+
74+
// For the very first packet, we also want the header.
75+
if i == 0 {
76+
blob = append(blob, fragment.LayerContents()...)
77+
}
78+
79+
// Append the data for each packet.
80+
blob = append(blob, fragment.Payload()...)
81+
}
82+
83+
packet := gopacket.NewPacket(blob, layers.LayerTypeLCM, gopacket.NoCopy)
84+
lcmHdrLayer := packet.Layer(layers.LayerTypeLCM)
85+
out, ok := lcmHdrLayer.(*layers.LCM)
86+
if !ok {
87+
err = fmt.Errorf("Error while decoding the defragmented packet. " +
88+
"Erasing/dropping packet.")
89+
}
90+
91+
lp.done = true
92+
93+
return
94+
}
95+
96+
func (ld *LCMDefragmenter) cleanUp() {
97+
for key, packet := range ld.packets {
98+
if packet.done || time.Now().Sub(packet.lastPacket) > timeout {
99+
delete(ld.packets, key)
100+
}
101+
}
102+
}
103+
104+
// Defrag takes a reference to an LCM packet and processes it.
105+
// In case the packet does not need to be defragmented, it immediately returns
106+
// the as in passed reference. In case in was the last missing fragment, out
107+
// will be the defragmented packet. If in was a fragment, but we are awaiting
108+
// more, out will be set to nil.
109+
// In the case that in was nil, we will just run the internal cleanup of the
110+
// defragmenter that times out packages.
111+
// If an error was encountered during defragmentation, out will also be nil,
112+
// while err will contain further information on the failure.
113+
func (ld *LCMDefragmenter) Defrag(in *layers.LCM) (out *layers.LCM, err error) {
114+
// Timeout old packages and erase error prone ones.
115+
ld.cleanUp()
116+
117+
// For running cleanup only
118+
if in == nil {
119+
return
120+
}
121+
122+
// Quick check if this is acutally a single packet. In that case, just
123+
// return it quickly.
124+
if !in.Fragmented {
125+
out = in
126+
return
127+
}
128+
129+
// Do we need to start a new fragments obj?
130+
if _, ok := ld.packets[in.SequenceNumber]; !ok {
131+
ld.packets[in.SequenceNumber] = newLCMPacket(in.TotalFragments)
132+
}
133+
134+
// Append the packet
135+
ld.packets[in.SequenceNumber].append(in)
136+
137+
// Check if this is the last package of that series
138+
if ld.packets[in.SequenceNumber].recFrags == in.TotalFragments {
139+
out, err = ld.packets[in.SequenceNumber].assemble()
140+
}
141+
142+
return
143+
}

defrag/lcmdefrag_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2018 Google, Inc. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style license
4+
// that can be found in the LICENSE file in the root of the source
5+
// tree.
6+
7+
package defrag
8+
9+
import (
10+
"testing"
11+
12+
"github.com/google/gopacket"
13+
"github.com/google/gopacket/layers"
14+
)
15+
16+
var (
17+
fragmentOne = []byte{
18+
0x4c, 0x43, 0x30, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d,
19+
0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x02, 0x4c, 0x43, 0x4d, 0x5f,
20+
0x53, 0x45, 0x4c, 0x46, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x00, 0x6c, 0x63,
21+
0x6d, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x20, 0x74, 0x65, 0x73, 0x74,
22+
}
23+
24+
fragmentTwo = []byte{
25+
0x4c, 0x43, 0x30, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d,
26+
0x00, 0x00, 0x00, 0x2d, 0x00, 0x01, 0x00, 0x02, 0x6c, 0x63, 0x6d, 0x20,
27+
0x73, 0x65, 0x6c, 0x66, 0x20, 0x74, 0x65, 0x73, 0x74,
28+
}
29+
30+
completePacket = []byte{
31+
0x4c, 0x43, 0x30, 0x32, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x43, 0x4d, 0x5f,
32+
0x53, 0x45, 0x4c, 0x46, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x00, 0x6c, 0x63,
33+
0x6d, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x20, 0x74, 0x65, 0x73, 0x74,
34+
}
35+
)
36+
37+
func TestOrderedLCMDefrag(t *testing.T) {
38+
defragmenter := NewLCMDefragmenter()
39+
var err error
40+
41+
packet := gopacket.NewPacket(fragmentOne, layers.LayerTypeLCM, gopacket.NoCopy)
42+
lcm := packet.Layer(layers.LayerTypeLCM).(*layers.LCM)
43+
44+
lcm, err = defragmenter.Defrag(lcm)
45+
if lcm != nil {
46+
t.Fatal("Returned incomplete LCM message.")
47+
}
48+
if err != nil {
49+
t.Fatal(err)
50+
}
51+
52+
packet = gopacket.NewPacket(fragmentTwo, layers.LayerTypeLCM, gopacket.NoCopy)
53+
lcm = packet.Layer(layers.LayerTypeLCM).(*layers.LCM)
54+
55+
lcm, err = defragmenter.Defrag(lcm)
56+
if lcm == nil {
57+
t.Fatal("Did not receive defragmented LCM message.")
58+
}
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
}
63+
64+
func TestUnorderedLCMDefrag(t *testing.T) {
65+
defragmenter := NewLCMDefragmenter()
66+
var err error
67+
68+
packet := gopacket.NewPacket(fragmentTwo, layers.LayerTypeLCM, gopacket.NoCopy)
69+
lcm := packet.Layer(layers.LayerTypeLCM).(*layers.LCM)
70+
71+
lcm, err = defragmenter.Defrag(lcm)
72+
if lcm != nil {
73+
t.Fatal("Returned incomplete LCM message.")
74+
}
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
79+
packet = gopacket.NewPacket(fragmentOne, layers.LayerTypeLCM, gopacket.NoCopy)
80+
lcm = packet.Layer(layers.LayerTypeLCM).(*layers.LCM)
81+
82+
lcm, err = defragmenter.Defrag(lcm)
83+
if lcm == nil {
84+
t.Fatal("Did not receive defragmented LCM message.")
85+
}
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
}
90+
91+
func TestNonLCMDefrag(t *testing.T) {
92+
defragmenter := NewLCMDefragmenter()
93+
var err error
94+
95+
packet := gopacket.NewPacket(completePacket, layers.LayerTypeLCM, gopacket.NoCopy)
96+
lcm := packet.Layer(layers.LayerTypeLCM).(*layers.LCM)
97+
98+
lcm, err = defragmenter.Defrag(lcm)
99+
if lcm == nil {
100+
t.Fatal("Did not receive complete LCM message.")
101+
}
102+
if err != nil {
103+
t.Fatal(err)
104+
}
105+
}

gc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ if [ -f /usr/include/pfring.h ]; then
186186
go build
187187
popd
188188
fi
189+
pushd ip4defrag
190+
go test ./...
191+
popd
192+
pushd defrag
193+
go test ./...
194+
popd
189195

190196
for travis_script in `ls .travis.*.sh`; do
191197
./$travis_script

0 commit comments

Comments
 (0)