@@ -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
6072func 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
92138func (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+
144256type 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
156269func (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
258454func 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+
270470func 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+
309563func 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