Skip to content

Commit aed23db

Browse files
aaaaaaaalexaboch
authored andcommitted
Adds ConntrackCreate & ConntrackUpdate
- Also refactored setUpNetlinkTestWithKModule function to reduce redundant NS's created and checks made. - Add conntrack protoinfo TCP support + groundwork for other protocols. - Tests to cover the above.
1 parent a1c5e02 commit aed23db

File tree

6 files changed

+1008
-42
lines changed

6 files changed

+1008
-42
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
.idea/
2+
.vscode/

conntrack_linux.go

Lines changed: 263 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ func ConntrackTableFlush(table ConntrackTableType) error {
5555
return pkgHandle.ConntrackTableFlush(table)
5656
}
5757

58+
// ConntrackCreate creates a new conntrack flow in the desired table
59+
// conntrack -I [table] Create a conntrack or expectation
60+
func ConntrackCreate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error {
61+
return pkgHandle.ConntrackCreate(table, family, flow)
62+
}
63+
64+
// ConntrackUpdate updates an existing conntrack flow in the desired table using the handle
65+
// conntrack -U [table] Update a conntrack
66+
func ConntrackUpdate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error {
67+
return pkgHandle.ConntrackUpdate(table, family, flow)
68+
}
69+
5870
// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter
5971
// conntrack -D [table] parameters Delete conntrack or expectation
6072
func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
@@ -87,6 +99,40 @@ func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error {
8799
return err
88100
}
89101

102+
// ConntrackCreate creates a new conntrack flow in the desired table using the handle
103+
// conntrack -I [table] Create a conntrack or expectation
104+
func (h *Handle) ConntrackCreate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error {
105+
req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_NEW, unix.NLM_F_ACK|unix.NLM_F_CREATE)
106+
attr, err := flow.toNlData()
107+
if err != nil {
108+
return err
109+
}
110+
111+
for _, a := range attr {
112+
req.AddData(a)
113+
}
114+
115+
_, err = req.Execute(unix.NETLINK_NETFILTER, 0)
116+
return err
117+
}
118+
119+
// ConntrackUpdate updates an existing conntrack flow in the desired table using the handle
120+
// conntrack -U [table] Update a conntrack
121+
func (h *Handle) ConntrackUpdate(table ConntrackTableType, family InetFamily, flow *ConntrackFlow) error {
122+
req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_NEW, unix.NLM_F_ACK|unix.NLM_F_REPLACE)
123+
attr, err := flow.toNlData()
124+
if err != nil {
125+
return err
126+
}
127+
128+
for _, a := range attr {
129+
req.AddData(a)
130+
}
131+
132+
_, err = req.Execute(unix.NETLINK_NETFILTER, 0)
133+
return err
134+
}
135+
90136
// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed
91137
// conntrack -D [table] parameters Delete conntrack or expectation
92138
func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
@@ -128,10 +174,44 @@ func (h *Handle) dumpConntrackTable(table ConntrackTableType, family InetFamily)
128174
return req.Execute(unix.NETLINK_NETFILTER, 0)
129175
}
130176

177+
// ProtoInfo wraps an L4-protocol structure - roughly corresponds to the
178+
// __nfct_protoinfo union found in libnetfilter_conntrack/include/internal/object.h.
179+
// Currently, only protocol names, and TCP state is supported.
180+
type ProtoInfo interface {
181+
Protocol() string
182+
}
183+
184+
// ProtoInfoTCP corresponds to the `tcp` struct of the __nfct_protoinfo union.
185+
// Only TCP state is currently supported.
186+
type ProtoInfoTCP struct {
187+
State uint8
188+
}
189+
// Protocol returns "tcp".
190+
func (*ProtoInfoTCP) Protocol() string {return "tcp"}
191+
func (p *ProtoInfoTCP) toNlData() ([]*nl.RtAttr, error) {
192+
ctProtoInfo := nl.NewRtAttr(unix.NLA_F_NESTED | nl.CTA_PROTOINFO, []byte{})
193+
ctProtoInfoTCP := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_PROTOINFO_TCP, []byte{})
194+
ctProtoInfoTCPState := nl.NewRtAttr(nl.CTA_PROTOINFO_TCP_STATE, nl.Uint8Attr(p.State))
195+
ctProtoInfoTCP.AddChild(ctProtoInfoTCPState)
196+
ctProtoInfo.AddChild(ctProtoInfoTCP)
197+
198+
return []*nl.RtAttr{ctProtoInfo}, nil
199+
}
200+
201+
// ProtoInfoSCTP only supports the protocol name.
202+
type ProtoInfoSCTP struct {}
203+
// Protocol returns "sctp".
204+
func (*ProtoInfoSCTP) Protocol() string {return "sctp"}
205+
206+
// ProtoInfoDCCP only supports the protocol name.
207+
type ProtoInfoDCCP struct {}
208+
// Protocol returns "dccp".
209+
func (*ProtoInfoDCCP) Protocol() string {return "dccp"}
210+
131211
// The full conntrack flow structure is very complicated and can be found in the file:
132212
// http://git.netfilter.org/libnetfilter_conntrack/tree/include/internal/object.h
133213
// For the time being, the structure below allows to parse and extract the base information of a flow
134-
type ipTuple struct {
214+
type IPTuple struct {
135215
Bytes uint64
136216
DstIP net.IP
137217
DstPort uint16
@@ -141,16 +221,49 @@ type ipTuple struct {
141221
SrcPort uint16
142222
}
143223

224+
// toNlData generates the inner fields of a nested tuple netlink datastructure
225+
// does not generate the "nested"-flagged outer message.
226+
func (t *IPTuple) toNlData(family uint8) ([]*nl.RtAttr, error) {
227+
228+
var srcIPsFlag, dstIPsFlag int
229+
if family == nl.FAMILY_V4 {
230+
srcIPsFlag = nl.CTA_IP_V4_SRC
231+
dstIPsFlag = nl.CTA_IP_V4_DST
232+
} else if family == nl.FAMILY_V6 {
233+
srcIPsFlag = nl.CTA_IP_V6_SRC
234+
dstIPsFlag = nl.CTA_IP_V6_DST
235+
} else {
236+
return []*nl.RtAttr{}, fmt.Errorf("couldn't generate netlink message for tuple due to unrecognized FamilyType '%d'", family)
237+
}
238+
239+
ctTupleIP := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_IP, nil)
240+
ctTupleIPSrc := nl.NewRtAttr(srcIPsFlag, t.SrcIP)
241+
ctTupleIP.AddChild(ctTupleIPSrc)
242+
ctTupleIPDst := nl.NewRtAttr(dstIPsFlag, t.DstIP)
243+
ctTupleIP.AddChild(ctTupleIPDst)
244+
245+
ctTupleProto := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_PROTO, nil)
246+
ctTupleProtoNum := nl.NewRtAttr(nl.CTA_PROTO_NUM, []byte{t.Protocol})
247+
ctTupleProto.AddChild(ctTupleProtoNum)
248+
ctTupleProtoSrcPort := nl.NewRtAttr(nl.CTA_PROTO_SRC_PORT, nl.BEUint16Attr(t.SrcPort))
249+
ctTupleProto.AddChild(ctTupleProtoSrcPort)
250+
ctTupleProtoDstPort := nl.NewRtAttr(nl.CTA_PROTO_DST_PORT, nl.BEUint16Attr(t.DstPort))
251+
ctTupleProto.AddChild(ctTupleProtoDstPort, )
252+
253+
return []*nl.RtAttr{ctTupleIP, ctTupleProto}, nil
254+
}
255+
144256
type ConntrackFlow struct {
145257
FamilyType uint8
146-
Forward ipTuple
147-
Reverse ipTuple
258+
Forward IPTuple
259+
Reverse IPTuple
148260
Mark uint32
149261
Zone uint16
150262
TimeStart uint64
151263
TimeStop uint64
152264
TimeOut uint32
153265
Labels []byte
266+
ProtoInfo ProtoInfo
154267
}
155268

156269
func (s *ConntrackFlow) String() string {
@@ -175,14 +288,93 @@ func (s *ConntrackFlow) String() string {
175288
return res
176289
}
177290

291+
// toNlData generates netlink messages representing the flow.
292+
func (s *ConntrackFlow) toNlData() ([]*nl.RtAttr, error) {
293+
var payload []*nl.RtAttr
294+
// The message structure is built as follows:
295+
// <len, NLA_F_NESTED|CTA_TUPLE_ORIG>
296+
// <len, NLA_F_NESTED|CTA_TUPLE_IP>
297+
// <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC]>
298+
// <IP>
299+
// <len, [CTA_IP_V4_DST|CTA_IP_V6_DST]>
300+
// <IP>
301+
// <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO>
302+
// <len, CTA_PROTO_NUM>
303+
// <uint8>
304+
// <len, CTA_PROTO_SRC_PORT>
305+
// <BEuint16>
306+
// <len, CTA_PROTO_DST_PORT>
307+
// <BEuint16>
308+
// <len, NLA_F_NESTED|CTA_TUPLE_REPLY>
309+
// <len, NLA_F_NESTED|CTA_TUPLE_IP>
310+
// <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC]>
311+
// <IP>
312+
// <len, [CTA_IP_V4_DST|CTA_IP_V6_DST]>
313+
// <IP>
314+
// <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO>
315+
// <len, CTA_PROTO_NUM>
316+
// <uint8>
317+
// <len, CTA_PROTO_SRC_PORT>
318+
// <BEuint16>
319+
// <len, CTA_PROTO_DST_PORT>
320+
// <BEuint16>
321+
// <len, CTA_STATUS>
322+
// <uint64>
323+
// <len, CTA_MARK>
324+
// <BEuint64>
325+
// <len, CTA_TIMEOUT>
326+
// <BEuint64>
327+
// <len, NLA_F_NESTED|CTA_PROTOINFO>
328+
329+
// CTA_TUPLE_ORIG
330+
ctTupleOrig := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_ORIG, nil)
331+
forwardFlowAttrs, err := s.Forward.toNlData(s.FamilyType)
332+
if err != nil {
333+
return nil, fmt.Errorf("couldn't generate netlink data for conntrack forward flow: %w", err)
334+
}
335+
for _, a := range forwardFlowAttrs {
336+
ctTupleOrig.AddChild(a)
337+
}
338+
339+
// CTA_TUPLE_REPLY
340+
ctTupleReply := nl.NewRtAttr(unix.NLA_F_NESTED|nl.CTA_TUPLE_REPLY, nil)
341+
reverseFlowAttrs, err := s.Reverse.toNlData(s.FamilyType)
342+
if err != nil {
343+
return nil, fmt.Errorf("couldn't generate netlink data for conntrack reverse flow: %w", err)
344+
}
345+
for _, a := range reverseFlowAttrs {
346+
ctTupleReply.AddChild(a)
347+
}
348+
349+
ctMark := nl.NewRtAttr(nl.CTA_MARK, nl.BEUint32Attr(s.Mark))
350+
ctTimeout := nl.NewRtAttr(nl.CTA_TIMEOUT, nl.BEUint32Attr(s.TimeOut))
351+
352+
payload = append(payload, ctTupleOrig, ctTupleReply, ctMark, ctTimeout)
353+
354+
if s.ProtoInfo != nil {
355+
switch p := s.ProtoInfo.(type) {
356+
case *ProtoInfoTCP:
357+
attrs, err := p.toNlData()
358+
if err != nil {
359+
return nil, fmt.Errorf("couldn't generate netlink data for conntrack flow's TCP protoinfo: %w", err)
360+
}
361+
payload = append(payload, attrs...)
362+
default:
363+
return nil, errors.New("couldn't generate netlink data for conntrack: field 'ProtoInfo' only supports TCP or nil")
364+
}
365+
}
366+
367+
return payload, nil
368+
}
369+
178370
// This method parse the ip tuple structure
179371
// The message structure is the following:
180372
// <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC], 16 bytes for the IP>
181373
// <len, [CTA_IP_V4_DST|CTA_IP_V6_DST], 16 bytes for the IP>
182374
// <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO, 1 byte for the protocol, 3 bytes of padding>
183375
// <len, CTA_PROTO_SRC_PORT, 2 bytes for the source port, 2 bytes of padding>
184376
// <len, CTA_PROTO_DST_PORT, 2 bytes for the source port, 2 bytes of padding>
185-
func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) uint8 {
377+
func parseIpTuple(reader *bytes.Reader, tpl *IPTuple) uint8 {
186378
for i := 0; i < 2; i++ {
187379
_, t, _, v := parseNfAttrTLV(reader)
188380
switch t {
@@ -201,7 +393,7 @@ func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) uint8 {
201393
tpl.Protocol = uint8(v[0])
202394
}
203395
// We only parse TCP & UDP headers. Skip the others.
204-
if tpl.Protocol != 6 && tpl.Protocol != 17 {
396+
if tpl.Protocol != unix.IPPROTO_TCP && tpl.Protocol != unix.IPPROTO_UDP {
205397
// skip the rest
206398
bytesRemaining := protoInfoTotalLen - protoInfoBytesRead
207399
reader.Seek(int64(bytesRemaining), seekCurrent)
@@ -250,9 +442,13 @@ func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) {
250442
return isNested, attrType, len
251443
}
252444

253-
func skipNfAttrValue(r *bytes.Reader, len uint16) {
445+
// skipNfAttrValue seeks `r` past attr of length `len`.
446+
// Maintains buffer alignment.
447+
// Returns length of the seek performed.
448+
func skipNfAttrValue(r *bytes.Reader, len uint16) uint16 {
254449
len = (len + nl.NLA_ALIGNTO - 1) & ^(nl.NLA_ALIGNTO - 1)
255450
r.Seek(int64(len), seekCurrent)
451+
return len
256452
}
257453

258454
func parseBERaw16(r *bytes.Reader, v *uint16) {
@@ -267,6 +463,10 @@ func parseBERaw64(r *bytes.Reader, v *uint64) {
267463
binary.Read(r, binary.BigEndian, v)
268464
}
269465

466+
func parseRaw32(r *bytes.Reader, v *uint32) {
467+
binary.Read(r, nl.NativeEndian(), v)
468+
}
469+
270470
func parseByteAndPacketCounters(r *bytes.Reader) (bytes, packets uint64) {
271471
for i := 0; i < 2; i++ {
272472
switch _, t, _ := parseNfAttrTL(r); t {
@@ -306,6 +506,60 @@ func parseTimeStamp(r *bytes.Reader, readSize uint16) (tstart, tstop uint64) {
306506

307507
}
308508

509+
func parseProtoInfoTCPState(r *bytes.Reader) (s uint8) {
510+
binary.Read(r, binary.BigEndian, &s)
511+
r.Seek(nl.SizeofNfattr - 1, seekCurrent)
512+
return s
513+
}
514+
515+
// parseProtoInfoTCP reads the entire nested protoinfo structure, but only parses the state attr.
516+
func parseProtoInfoTCP(r *bytes.Reader, attrLen uint16) (*ProtoInfoTCP) {
517+
p := new(ProtoInfoTCP)
518+
bytesRead := 0
519+
for bytesRead < int(attrLen) {
520+
_, t, l := parseNfAttrTL(r)
521+
bytesRead += nl.SizeofNfattr
522+
523+
switch t {
524+
case nl.CTA_PROTOINFO_TCP_STATE:
525+
p.State = parseProtoInfoTCPState(r)
526+
bytesRead += nl.SizeofNfattr
527+
default:
528+
bytesRead += int(skipNfAttrValue(r, l))
529+
}
530+
}
531+
532+
return p
533+
}
534+
535+
func parseProtoInfo(r *bytes.Reader, attrLen uint16) (p ProtoInfo) {
536+
bytesRead := 0
537+
for bytesRead < int(attrLen) {
538+
_, t, l := parseNfAttrTL(r)
539+
bytesRead += nl.SizeofNfattr
540+
541+
switch t {
542+
case nl.CTA_PROTOINFO_TCP:
543+
p = parseProtoInfoTCP(r, l)
544+
bytesRead += int(l)
545+
// No inner fields of DCCP / SCTP currently supported.
546+
case nl.CTA_PROTOINFO_DCCP:
547+
p = new(ProtoInfoDCCP)
548+
skipped := skipNfAttrValue(r, l)
549+
bytesRead += int(skipped)
550+
case nl.CTA_PROTOINFO_SCTP:
551+
p = new(ProtoInfoSCTP)
552+
skipped := skipNfAttrValue(r, l)
553+
bytesRead += int(skipped)
554+
default:
555+
skipped := skipNfAttrValue(r, l)
556+
bytesRead += int(skipped)
557+
}
558+
}
559+
560+
return p
561+
}
562+
309563
func parseTimeOut(r *bytes.Reader) (ttimeout uint32) {
310564
parseBERaw32(r, &ttimeout)
311565
return
@@ -365,19 +619,19 @@ func parseRawData(data []byte) *ConntrackFlow {
365619
case nl.CTA_TIMESTAMP:
366620
s.TimeStart, s.TimeStop = parseTimeStamp(reader, l)
367621
case nl.CTA_PROTOINFO:
368-
skipNfAttrValue(reader, l)
622+
s.ProtoInfo = parseProtoInfo(reader, l)
369623
default:
370624
skipNfAttrValue(reader, l)
371625
}
372626
} else {
373627
switch t {
374628
case nl.CTA_MARK:
375629
s.Mark = parseConnectionMark(reader)
376-
case nl.CTA_LABELS:
630+
case nl.CTA_LABELS:
377631
s.Labels = parseConnectionLabels(reader)
378632
case nl.CTA_TIMEOUT:
379633
s.TimeOut = parseTimeOut(reader)
380-
case nl.CTA_STATUS, nl.CTA_USE, nl.CTA_ID:
634+
case nl.CTA_ID, nl.CTA_STATUS, nl.CTA_USE:
381635
skipNfAttrValue(reader, l)
382636
case nl.CTA_ZONE:
383637
s.Zone = parseConnectionZone(reader)

0 commit comments

Comments
 (0)