2024年Go最新gopacket tcpassembly源码分析(3),字节跳动高工面试

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

	return
}

a.ret = a.ret[:0]
// 4元组组成的key
key := key{netFlow, t.TransportFlow()}

var conn *connection
// This for loop handles a race condition where a connection will close, lock
// the connection pool, and remove itself, but before it locked the connection
// pool it's returned to another Assemble statement.  This should loop 0-1
// times for the VAST majority of cases.
// 创建conn
for {
	// tcp keepalive syn=0 payload=0
	// 即 true && true end 为true?
	conn = a.connPool.getConnection(key, !t.SYN && len(t.LayerPayload()) == 0, timestamp)
	if conn == nil {
		if *debugLog {
			log.Printf("%v got empty packet on otherwise empty connection", key)
		}
		return
	}
	conn.mu.Lock()
	if !conn.closed {
		break
	}
	conn.mu.Unlock()
}
if conn.lastSeen.Before(timestamp) {
	conn.lastSeen = timestamp
}
//type Sequence int64 提供Difference和Add函数
seq, bytes := Sequence(t.Seq), t.Payload // seq:当前序号  bytes:tcp负载的数据
// 校验序号
if conn.nextSeq == invalidSequence {
	if t.SYN {
		if *debugLog {
			log.Printf("%v saw first SYN packet, returning immediately, seq=%v", key, seq)
		}
		// 添加 Reassembly重组后的对象
		a.ret = append(a.ret, Reassembly{
			Bytes: bytes,
			Skip:  0,
			Start: true,
			Seen:  timestamp,
		})
		// 下一个包的序号 = 当前的序号 + 字节数 + 1
		conn.nextSeq = seq.Add(len(bytes) + 1)
	} else {
		if *debugLog {
			log.Printf("%v waiting for start, storing into connection", key)
		}
		// 插入到数据到connection中
		a.insertIntoConn(t, conn, timestamp)
	}
} else if diff := conn.nextSeq.Difference(seq); diff > 0 {
	if *debugLog {
		log.Printf("%v gap in sequence numbers (%v, %v) diff %v, storing into connection", key, conn.nextSeq, seq, diff)
	}
	// 插入到数据到connection中
	a.insertIntoConn(t, conn, timestamp)
} else {=<0
	// 字节校准
	bytes, conn.nextSeq = byteSpan(conn.nextSeq, seq, bytes)
	if *debugLog {
		log.Printf("%v found contiguous data (%v, %v), returning immediately", key, seq, conn.nextSeq)
	}
	a.ret = append(a.ret, Reassembly{
		Bytes: bytes,
		Skip:  0,
		End:   t.RST || t.FIN,
		Seen:  timestamp,
	})
}
if len(a.ret) > 0 {
	a.sendToConnection(conn)
}
conn.mu.Unlock()

}


### insertIntoConn



func (a *Assembler) insertIntoConn(t *layers.TCP, conn *connection, ts time.Time) {
if conn.first != nil && conn.first.seq == conn.nextSeq {
panic(“wtf”)
}
// p:第一页 p2:最后一页 numPages:页数
p, p2, numPages := a.pagesFromTCP(t, ts)

//遍历双向链接page列表获取正确的放置给定序号的位置
// 直接插入不好吗?
prev, current := conn.traverseConn(Sequence(t.Seq))
conn.pushBetween(prev, current, p, p2)
conn.pages += numPages

// 校验最大缓冲page数
if (a.MaxBufferedPagesPerConnection > 0 && conn.pages >= a.MaxBufferedPagesPerConnection) ||
	(a.MaxBufferedPagesTotal > 0 && a.pc.used >= a.MaxBufferedPagesTotal) {
	if *debugLog {
		log.Printf("%v hit max buffer size: %+v, %v, %v", conn.key, a.AssemblerOptions, conn.pages, a.pc.used)
	}
	// 弹出
	a.addNextFromConn(conn)
}

}


### pagesFromTCP


从TCP数据包创建一个page(或设置一个pages)。


注意此函数不应该接受SYN包,因为它不能正确处理seq。


返回双连接的page列表中的第一个和最后一个页面。



func (a *Assembler) pagesFromTCP(t *layers.TCP, ts time.Time) (p, p2 *page, numPages int) {
first := a.pc.next(ts)
current := first
numPages++
seq, bytes := Sequence(t.Seq), t.Payload
for {
length := min(len(bytes), pageBytes)
// 拷贝负载数据
current.Bytes = current.buf[:length]
copy(current.Bytes, bytes)
// 设置seq
current.seq = seq
// 处理剩余数据>1900,一般不会进入到这里,实际场景下MTU会将TCP切段
bytes = bytes[length:]
if len(bytes) == 0 {
break
}
seq = seq.Add(length)
// 创建下一页
current.next = a.pc.next(ts)
// 设置下一个的prev为current
current.next.prev = current
// 设置下一页
current = current.next
numPages++
}
current.End = t.RST || t.FIN // 设置end
return first, current, numPages
}


### addNextFromConn


弹出第一页



func (a *Assembler) addNextFromConn(conn *connection) {
if conn.nextSeq == invalidSequence {
conn.first.Skip = -1
} else if diff := conn.nextSeq.Difference(conn.first.seq); diff > 0 {
conn.first.Skip = int(diff)
}
conn.first.Bytes, conn.nextSeq = byteSpan(conn.nextSeq, conn.first.seq, conn.first.Bytes)
if *debugLog {
log.Printf(“%v adding from conn (%v, %v)”, conn.key, conn.first.seq, conn.nextSeq)
}
a.ret = append(a.ret, conn.first.Reassembly)
a.pc.replace(conn.first)
if conn.first == conn.last {
conn.first = nil
conn.last = nil
} else {
conn.first = conn.first.next
conn.first.prev = nil
}
conn.pages–
}


### sendToConnection



func (a *Assembler) sendToConnection(conn *connection) {
// 组数据
a.addContiguous(conn)
if conn.stream == nil {
panic(“why?”)
}
conn.stream.Reassembled(a.ret)
if a.ret[len(a.ret)-1].End {
a.closeConnection(conn)
}
}


### addContiguous



func (a *Assembler) addContiguous(conn *connection) {
for conn.first != nil && conn.nextSeq.Difference(conn.first.seq) <= 0 {
a.addNextFromConn(conn)
}
}


### addNextFromConn


弹出第一页添加到数组中



func (a *Assembler) addNextFromConn(conn *connection) {
if conn.nextSeq == invalidSequence {
conn.first.Skip = -1
} else if diff := conn.nextSeq.Difference(conn.first.seq); diff > 0 {
conn.first.Skip = int(diff)
}
conn.first.Bytes, conn.nextSeq = byteSpan(conn.nextSeq, conn.first.seq, conn.first.Bytes)
if *debugLog {
log.Printf(“%v adding from conn (%v, %v)”, conn.key, conn.first.seq, conn.nextSeq)
}
a.ret = append(a.ret, conn.first.Reassembly)
a.pc.replace(conn.first)
if conn.first == conn.last {
conn.first = nil
conn.last = nil
} else {
conn.first = conn.first.next
conn.first.prev = nil
}
conn.pages–
}


### closeConnection



func (a *Assembler) closeConnection(conn *connection) {
if *debugLog {
log.Printf(“%v closing”, conn.key)
}
conn.stream.ReassemblyComplete()
conn.closed = true
a.connPool.remove(conn)
for p := conn.first; p != nil; p = p.next {
a.pc.replace§
}
}


## StreamPool


管理流的连接池,初始连接池分配1024个



type StreamPool struct {
conns map[key]*connection
users int
mu sync.RWMutex
factory StreamFactory
free []*connection
all [][]connection
nextAlloc int
newConnectionCount int64
}

func NewStreamPool(factory StreamFactory) *StreamPool {
return &StreamPool{
conns: make(map[key]*connection, initialAllocSize),
free: make([]*connection, 0, initialAllocSize),
factory: factory,
nextAlloc: initialAllocSize,
}
}


### grow


分配连接



func (p *StreamPool) grow() {
conns := make([]connection, p.nextAlloc)
p.all = append(p.all, conns)
for i := range conns {
p.free = append(p.free, &conns[i])
}
if *memLog {
log.Println(“StreamPool: created”, p.nextAlloc, “new connections”)
}
p.nextAlloc *= 2
}


### newConnection


创建连接



func (p *StreamPool) newConnection(k key, s Stream, ts time.Time) (c *connection) {
if *memLog {
p.newConnectionCount++
if p.newConnectionCount&0x7FFF == 0 {
log.Println(“StreamPool:”, p.newConnectionCount, “requests,”, len(p.conns), “used,”, len(p.free), “free”)
}
}
if len(p.free) == 0 {
p.grow()
}
index := len(p.free) - 1
c, p.free = p.free[index], p.free[:index]
c.reset(k, s, ts)
return c
}


### getConnection



// 返回一个连接,如果连接已经被关闭或者连接不存在,返回nil
func (p *StreamPool) getConnection(k key, end bool, ts time.Time) *connection {
p.mu.RLock()
conn := p.conns[k]
p.mu.RUnlock()
if end || conn != nil {
return conn
}
s := p.factory.New(k[0], k[1])
p.mu.Lock()
conn = p.newConnection(k, s, ts)
if conn2 := p.conns[k]; conn2 != nil {
p.mu.Unlock()
return conn2
}
p.conns[k] = conn
p.mu.Unlock()
return conn
}


### remove


删除某个个连接



func (p *StreamPool) remove(conn *connection) {
p.mu.Lock()
delete(p.conns, conn.key)
p.free = append(p.free, conn)
p.mu.Unlock()
}


### connection


返回所有的连接



func (p *StreamPool) connections() []*connection {
p.mu.RLock()
conns := make([]*connection, 0, len(p.conns))
for _, conn := range p.conns {
conns = append(conns, conn)
}
p.mu.RUnlock()
return conns
}


## connection



type connection struct {
key key
pages int
first, last *page
nextSeq Sequence
created, lastSeen time.Time
stream Stream
closed bool
mu sync.Mutex
}


### reset


因为连接是预先分配的,所以需要重置,相当于初始化



func (c *connection) reset(k key, s Stream, ts time.Time) {
c.key = k
c.pages = 0
c.first, c.last = nil, nil
c.nextSeq = invalidSequence
c.created = ts
c.stream = s
c.closed = false
}


### traverseConn


遍历双向链接page列表获取正确的放置给定序号的位置。


注意它是向后遍历的,从最大的序号开始,一直往下,因为我们假设常见的情况是TCP数据包的流是顺序,与最小损失或数据包重新排序。


遍历双向链接的page列表,以找到放置给定序号的正确位置。


注意,它是向后遍历的,从最高的序号开始,一直向下,因为我们假设通常的情况是,流的TCP包将按顺序出现,损失或包重排序最小。



func (c *connection) traverseConn(seq Sequence) (prev, current *page) {
prev = c.last
for prev != nil && prev.seq.Difference(seq) < 0 {
current = prev
prev = current.prev
}
return
}


### pushbettwen


首先插入双向链接列表first-…-last在另一个双链接列表中的节点prevnext之间。如果prev为nil,则首先成为连接列表中的新第一页。如果next为nil,则使last成为列表中新的最后一页。第一个/最后一个可能指向相同的页面。



func (c *connection) pushBetween(prev, next, first, last *page) {
// Maintain our doubly linked list
if next == nil || c.last == nil {
c.last = last
} else {
last.next = next
next.prev = last
}
if prev == nil || c.first == nil {
c.first = first
} else {
first.prev = prev
prev.next = first
}
}


## Reassembly


Reassembly由assembler传入stream中。



type Reassembly struct {
// TCP流的负载,可能为空

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

nil {
c.first = first
} else {
first.prev = prev
prev.next = first
}
}


## Reassembly


Reassembly由assembler传入stream中。



type Reassembly struct {
// TCP流的负载,可能为空

[外链图片转存中…(img-Uc2n28U0-1715633866735)]
[外链图片转存中…(img-wctPkAAM-1715633866735)]
[外链图片转存中…(img-zSEH6jWU-1715633866735)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值