conntrack中的数据结构详解

栏目: IT技术 · 发布时间: 4年前

内容简介:在还是以由于是对conntrack的信息进行分析, 因此分析对象就是netfilter的返回包信息.所以我们从

用户态conntrack原理分析 中也说到过,对数据结构没有进行详细的分析,本篇文章就是深入到conntrack的消息,一步步解析出IP地址和端口号等等信息.

还是以 conntrack-gofirst commit 为例来分析.

分析

由于是对conntrack的信息进行分析, 因此分析对象就是netfilter的返回包信息.所以我们从 parseRawData 来分析

func parseRawData(data []byte) *ConntrackFlow {
	s := &ConntrackFlow{}
	var proto uint8
	// First there is the Nfgenmsg header
	// consume only the family field
	reader := bytes.NewReader(data)
	......

data是一个byte数组,通过bytes.NewReader()方法,得到一个Reader对象.

type Reader struct {
	s        []byte
	i        int64 // current reading index
	prevRune int   // index of previous rune; or < 0
}

当执行完毕 reader := bytes.NewReader(data) 之后,此时的data和reader的内容分别如下:

接下来的分析都是基于这个数据,牢记这个数据.

conntrack中的数据结构详解

reader中的s属性和data内容完全一样,是uint8(byte类型)的数组,len为252,cap为65520. 此时的i(即index)为0,表示没有任何的读取(因为Index为0,说明读取指针没有移动).

READ

接下来程序执行 binary.Read(reader, NativeEndian(), &s.FamilyType) , FamilyType 是一个uint8类型的数据.即取第一个元素.前面已经列出了data的数据. [2 0 0 0 52 0 1 128 20 0 1 12.....]

第一个元素是2,对应的是 s.FamilyType 是2. 由于已经读取了一位,所以reader中的index应该为1.

conntrack中的数据结构详解

数据的结果也和我们分析情况一致.

SEEK

接下来,程序执行如下代码:

const (
	// backward compatibility with golang 1.6 which does not have io.SeekCurrent
	seekCurrent = 1
)
// skip rest of the Netfilter header
reader.Seek(3, seekCurrent)

即,移动4个单位.移动的原因在 用户态conntrack原理分析 中也说到过 是因为在Netlink Data中的前面4个字节一般都是代表 nfgenmsg 信息. 观察此时的reader的信息. 由于reader调用了Seek()移动了3,所以此时的reader的index为4.

conntrack中的数据结构详解

parseNfAttrTL

此时reader的index喂,接下来就是执行 parseNfAttrTL(reader) . 此时的reader情况如下所示:

conntrack中的数据结构详解

同样一步步来分析整个过程.

func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) {
 	binary.Read(r, NativeEndian(), &len)
	len -= SizeofNfattr

	binary.Read(r, NativeEndian(), &attrType)
	isNested = (attrType & NLA_F_NESTED) == NLA_F_NESTED
	attrType = attrType & (NLA_F_NESTED - 1)

	return isNested, attrType, len
}

binary.Read(r, NativeEndian(), &len) 由于len是uint16类型,而Reader中的每个元素是uint8,所以len会读取2个元组,即52,0,所以len的长度是52.reader的i为6.如下所示:

conntrack中的数据结构详解

此时reader的状态变为:

conntrack中的数据结构详解

程序继续执行, binary.Read(r, NativeEndian(), &attrType) .同样由于attrType是uint16,所以读取的数据是1和128.将两者组合成为uint16的数据. 同时 NativeEndian() 返回的小端.所以1和128的组合方式是:

128转换为二进制是:10000000 , 由于是unit8,转换为uint16,需要填充,最终是 10000000 00000000,1的二进制是 00000000 00000001 ,所以两者的组合就是 10000000 00000000 + 00000000 00000001 = 1000000000000001 ,最终转换为十进制就是32769.同时reader的index也会移动2位,变为8. 实际分析结果与数据一致.

conntrack中的数据结构详解

parseNfAttrTL

程序第一次执行完 parseNfAttrTL ,根据attrType确定是 CTA_TUPLE_ORIG ,会继续执行 parseNfAttrTL

if nested, t, l := parseNfAttrTL(reader); nested {
    switch t {
    case CTA_TUPLE_ORIG:
        if nested, t, _ = parseNfAttrTL(reader); nested && t == CTA_TUPLE_IP {
            proto = parseIpTuple(reader, &s.Forward)
        }
        
...................................
func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) {
 	binary.Read(r, NativeEndian(), &len)
	len -= SizeofNfattr

	binary.Read(r, NativeEndian(), &attrType)
	isNested = (attrType & NLA_F_NESTED) == NLA_F_NESTED
	attrType = attrType & (NLA_F_NESTED - 1)

	return isNested, attrType, len
}

此时的reader的index还是为8.data的读取状态是:

conntrack中的数据结构详解

同样按照上面的分析方法,len长度变为了16.attrType的值是32769.reader移动4为,所以reader的index值是12.执行完毕之后,最终的data的数据情况如下所示:

conntrack中的数据结构详解

parseIpTuple

程序接下来就是执行 parseIpTuple(reader, &s.Forward)

func parseNfAttrTLV(r *bytes.Reader) (isNested bool, attrType, len uint16, value []byte) {
	isNested, attrType, len = parseNfAttrTL(r)

	value = make([]byte, len)
	binary.Read(r, binary.BigEndian, &value)
	return isNested, attrType, len, value
}
func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) {
 	binary.Read(r, NativeEndian(), &len)
	len -= SizeofNfattr

	binary.Read(r, NativeEndian(), &attrType)
	isNested = (attrType & NLA_F_NESTED) == NLA_F_NESTED
	attrType = attrType & (NLA_F_NESTED - 1)

	return isNested, attrType, len
}

// This method parse the ip tuple structure
// The message structure is the following:
// <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC], 16 bytes for the IP>
// <len, [CTA_IP_V4_DST|CTA_IP_V6_DST], 16 bytes for the IP>
// <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO, 1 byte for the protocol, 3 bytes of padding>
// <len, CTA_PROTO_SRC_PORT, 2 bytes for the source port, 2 bytes of padding>
// <len, CTA_PROTO_DST_PORT, 2 bytes for the source port, 2 bytes of padding>
func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) uint8 {
	for i := 0; i < 2; i++ {
		_, t, _, v := parseNfAttrTLV(reader)
		switch t {
		case CTA_IP_V4_SRC, CTA_IP_V6_SRC:
			tpl.SrcIP = v
		case CTA_IP_V4_DST, CTA_IP_V6_DST:
			tpl.DstIP = v
		}
	}
	// Skip the next 4 bytes  nl.NLA_F_NESTED|nl.CTA_TUPLE_PROTO
	reader.Seek(4, seekCurrent)
	_, t, _, v := parseNfAttrTLV(reader)
	if t == CTA_PROTO_NUM {
		tpl.Protocol = uint8(v[0])
	}
	// Skip some padding 3 bytes
	reader.Seek(3, seekCurrent)
	for i := 0; i < 2; i++ {
		_, t, _ := parseNfAttrTL(reader)
		switch t {
		case CTA_PROTO_SRC_PORT:
			parseBERaw16(reader, &tpl.SrcPort)
		case CTA_PROTO_DST_PORT:
			parseBERaw16(reader, &tpl.DstPort)
		}
		// Skip some padding 2 byte
		reader.Seek(2, seekCurrent)
	}
	return tpl.Protocol
}

程序首先会调用 parseNfAttrTLV(reader) , parseNfAttrTLV 相比 parseNfAttrTL 就是多返回了一个对应读取内容的值.由于本质上还是调用的 parseNfAttrTL ,所以分析方法与上面的分析一致,这里就不做说明了. 此时i已经变为了16.当执行完毕, isNested, attrType, len = parseNfAttrTL(r) 之后,得到 isNested 为false, attrType 为1, len 为4.接下来就是执行:

value = make([]byte, len)
binary.Read(r, binary.BigEndian, &value)
return isNested, attrType, len, value

此时reader继续读取数据,由于len为4,所以就会读取4个元素.读取到value中.

conntrack中的数据结构详解

读取方式是大端方式读取.value的值是127.0.0.1. 所以当执行完毕数据,以下的值分别变为:

  • isNested false
  • attrType 1
  • len 4
  • value 127.0.0.1
  • reader的index为20

最后程序回到如下的代码:

const (
	CTA_IP_V4_SRC = 1
	CTA_IP_V4_DST = 2
	CTA_IP_V6_SRC = 3
	CTA_IP_V6_DST = 4
)

for i := 0; i < 2; i++ {
    _, t, _, v := parseNfAttrTLV(reader)
    switch t {
    case CTA_IP_V4_SRC, CTA_IP_V6_SRC:
        tpl.SrcIP = v
    case CTA_IP_V4_DST, CTA_IP_V6_DST:
        tpl.DstIP = v
    }
}

由于t为1,所以命中 CTA_IP_V4_SRC ,最后得到 tpl.SrcIP 为127.0.0.1;同理可以得到 tpl.DstIP 为127.0.0.1. 之后程序又继续执行如下的代码:

// Skip the next 4 bytes  nl.NLA_F_NESTED|nl.CTA_TUPLE_PROTO
reader.Seek(4, seekCurrent)
_, t, _, v := parseNfAttrTLV(reader)
if t == CTA_PROTO_NUM {
    tpl.Protocol = uint8(v[0])
}

const (
	CTA_PROTO_NUM      = 1
	CTA_PROTO_SRC_PORT = 2
	CTA_PROTO_DST_PORT = 3
)

首先会跳过4个字符,之后同样是调用 parseNfAttrTLV 函数.此时data的读取情况如下所示:

conntrack中的数据结构详解

此时的数据情况是:

  • t:1
  • v:6

最终执行 tpl.Protocol = uint8(v[0]) , 得到 tpl.Protocol 为6,即当前协议是一个tcp的协议.

接下来程序执行如下代码:

// Skip some padding 3 bytes
reader.Seek(3, seekCurrent)
for i := 0; i < 2; i++ {
	_, t, _ := parseNfAttrTL(reader)
	switch t {
	case CTA_PROTO_SRC_PORT:
		parseBERaw16(reader, &tpl.SrcPort)
	case CTA_PROTO_DST_PORT:
		parseBERaw16(reader, &tpl.DstPort)
	}
	// Skip some padding 2 byte
	reader.Seek(2, seekCurrent)
}

程序首先会跳过3个元素,之后同样是调用 parseNfAttrTL(reader) 方法,获取t. 分析方法同上,最终得到的t为2.进入到 parseBERaw16(reader, &tpl.SrcPort) .

func parseBERaw16(r *bytes.Reader, v *uint16) {
	binary.Read(r, binary.BigEndian, v)
}

所以SrcPort的值是 10011101 (157的二进制) + 10000000 (128的二进制) = 1001110110000000(40320). 所以SrcPort是40320.

conntrack中的数据结构详解

接下来同样是获取 DstPort 的代码.分析方法一样,最终的结果是: 1000000(4的二进制,补充0000)+111000(56的二进制)= 10000111000(1080),所以DstPort是1080.此时data的状态如下所示:

conntrack中的数据结构详解

最后由于conntrack会同时记录网络包的发送信息和预期的返回包信息,当前我们仅仅只是分析了 CAT_TUPLE_ORIG ,即网络包的发送信息,接下来就是解析预期的返回包信息,解析方法完全与上述分析方法一样,就不做说明了.下图十分清晰明了地说明了各个数据结构.

conntrack中的数据结构详解

通过上面的分析,我们发现其实conntrack的连接跟踪表的数据结构还是相当有规律的.整个的数据结构在之前的代码注释也说明了.

// <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC], 16 bytes for the IP>
// <len, [CTA_IP_V4_DST|CTA_IP_V6_DST], 16 bytes for the IP>
// <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO, 1 byte for the protocol, 3 bytes of padding>
// <len, CTA_PROTO_SRC_PORT, 2 bytes for the source port, 2 bytes of padding>
// <len, CTA_PROTO_DST_PORT, 2 bytes for the source port, 2 bytes of padding>

整个的数据解析过程也是按照这个来进行的.

总结

通过对conntrack的整个数据解析过程的分析,对conntrack的数据结构加深了理解,同时也方便我们利用conntrack来记录主机中的网络状态信息.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

图解服务器端网络架构

图解服务器端网络架构

[日] 宫田宽士 / 曾薇薇 / 人民邮电出版社 / 2015-4 / 79.00元

本书以图配文,详细说明了服务器端网络架构的基础技术和设计要点。基础设计是服务器端网络架构最重要的一个阶段。本书就立足于基础设计的设计细分项目,详细介绍各细分项目的相关技术和设计要点。全书共分为5章,分别讲述进行物理设计、逻辑设计、安全设计和负载均衡设计、高可用性设计以及管理设计时所必需的技术和设计要点。一起来看看 《图解服务器端网络架构》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具