[Golang] gopacket 初体验 您所在的位置:网站首页 go抓包 [Golang] gopacket 初体验

[Golang] gopacket 初体验

2023-05-10 02:38| 来源: 网络整理| 查看: 265

先回顾下三次握手及四次分手,若有兴趣更详细的了解该过程请看这篇文章

三次握手

image.png

四次分手 (常规场景)

image.png

先写一个tcp 服务端 tcp_server.go,由于一些无法描述的原因客观上的客户端主动调用Close()函数时无法呈现常规场景的4次分手,当客户端调用Close()的时候必定会抛出了复位包(RST)导致不能走正常的4次分手。所以下面代码采用服务端主动断开了链接来呈现(在tcp协议原语描述中其实是主动断开方即为客户端,所以不妨碍场景的呈现)。

package main import ( "fmt" "log" "net" ) func main() { l, err := net.Listen("tcp", "0.0.0.0:8081") if err != nil { log.Fatalln(err) } defer l.Close() for { conn, err := l.Accept() if err != nil {log.Fatalln(err)} fmt.Printf("Received message %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr()) go handler(conn) } } func handler(conn net.Conn) { defer conn.Close() for { buf := make([]byte, 1024) num, err := conn.Read(buf) if err != nil { fmt.Println(err) if err.Error() == "EOF" { return } break } fmt.Printf("Received data: %v\n", string(buf[:num])) // 由于在关闭时tcp队列内依然有数据会发送rst包, // 所以这里屏蔽掉数据发送直接关闭链接 //if num, err =conn.Write([]byte("88")) ; err != nil { // fmt.Println(err) //} // 设置了服务端主动断开链接,主要是为了完整演示4次分手 if err := conn.Close(); err != nil { fmt.Println(err) } //return } } 复制代码

写一个tcp客户端 tcp_client.go。下面曾经想用tcp 参数 linger 来阻止客户端close时发送RST包,让tcp正常进入四次分手,但测试后发现没效果;

package main import ( "log" "net" "time" ) func main() { con, err := net.Dial("tcp", "192.168.0.3:8081") if err!=nil{ log.Fatalln(err) } c := con.(*net.TCPConn) //if err := c.SetLinger(1); err != nil { // log.Fatalln(err) //} if _ , err := c.Write([]byte("hello")); err != nil { log.Fatalln(err) } time.Sleep(2 * time.Second) if err := c.Close(); err != nil { log.Fatalln(err) } } 复制代码

新的 capture,cap4.go

package main import ( "fmt" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" "log" "time" ) // 可观测三次握手及四次分手 func main() { const ( EthDev string = "eth0" SnapLen int32 = 1024 Promisc bool = false Timeout time.Duration = time.Second * 2 Port layers.TCPPort = 8081 ) fmt.Println("Capture dev:", EthDev, "port:", Port) handler, err := pcap.OpenLive(EthDev, SnapLen, Promisc, Timeout) if err != nil { log.Fatalln(err) } defer handler.Close() source := gopacket.NewPacketSource(handler, handler.LinkType()) for pk := range source.Packets() { if layer2 := pk.NetworkLayer(); layer2 != nil { ipLayer, ok := layer2.(*layers.IPv4) if ! ok { continue } if layer3 := pk.TransportLayer() ; layer3 != nil { if tcpLayer, ok := layer3.(*layers.TCP); ok { if tcpLayer.DstPort == Port || tcpLayer.SrcPort == Port { fmt.Printf("%s:%d --> %s:%d, SYN=%v, ACK=%v, RST=%v, PSH=%v, FIN=%v, URG=%v, ECE=%v," + " Payload length=%d, seq=%v, ackNum=%v, window=%v \n", ipLayer.SrcIP, tcpLayer.SrcPort, ipLayer.DstIP, tcpLayer.DstPort, tcpLayer.SYN, tcpLayer.ACK, tcpLayer.RST, tcpLayer.PSH, tcpLayer.FIN, tcpLayer.URG, tcpLayer.ECE, len(tcpLayer.Payload), tcpLayer.Seq, tcpLayer.Ack, tcpLayer.Window, ) } } } } } } 复制代码

运行代码

go run tcp_server.go go run cap4.go go run tcp_client.go 复制代码

观测cap4.go的输出,下面经过调试后的正确输出,如果你禁止掉tcp_server.go中的主动close的代码,则会发现最后的包时客户端主动断开的 RST 包,四次分手无法正常走下去;

# 三次握手 192.168.0.2:51260 --> 192.168.0.3:8081, SYN=true, ACK=false, RST=false, PSH=false, FIN=false, URG=false, ECE=false, Payload length=0, seq=3786003339, ackNum=0, window=29200 192.168.0.3:8081 --> 192.168.0.2:51260, SYN=true, ACK=true, RST=false, PSH=false, FIN=false, URG=false, ECE=false, Payload length=0, seq=3563580175, ackNum=3786003340, window=28960 192.168.0.2:51260 --> 192.168.0.3:8081, SYN=false, ACK=true, RST=false, PSH=false, FIN=false, URG=false, ECE=false, Payload length=0, seq=3786003340, ackNum=3563580176, window=229 # 传输了数据 192.168.0.2:51260 --> 192.168.0.3:8081, SYN=false, ACK=true, RST=false, PSH=true, FIN=false, URG=false, ECE=false, Payload length=5, seq=3786003340, ackNum=3563580176, window=229 192.168.0.3:8081 --> 192.168.0.2:51260, SYN=false, ACK=true, RST=false, PSH=false, FIN=false, URG=false, ECE=false, Payload length=0, seq=3563580176, ackNum=3786003345, window=227 # 四次分手 192.168.0.3:8081 --> 192.168.0.2:51260, SYN=false, ACK=true, RST=false, PSH=false, FIN=true, URG=false, ECE=false, Payload length=0, seq=3563580176, ackNum=3786003345, window=227 192.168.0.2:51260 --> 192.168.0.3:8081, SYN=false, ACK=true, RST=false, PSH=false, FIN=false, URG=false, ECE=false, Payload length=0, seq=3786003345, ackNum=3563580177, window=229 192.168.0.2:51260 --> 192.168.0.3:8081, SYN=false, ACK=true, RST=false, PSH=false, FIN=true, URG=false, ECE=false, Payload length=0, seq=3786003345, ackNum=3563580177, window=229 192.168.0.3:8081 --> 192.168.0.2:51260, SYN=false, ACK=true, RST=false, PSH=false, FIN=false, URG=false, ECE=false, Payload length=0, seq=3563580177, ackNum=3786003346, window=227 复制代码

补充知识点

tcp 参数 SO_LINGER, 设置函数close()关闭TCP连接时的行为,下面是其结构体。 type Linger struct { Onoff int32 Linger int32 } 复制代码 Onoff 非 0,Linger为0,close()用以下方式关闭连接。 立即关闭该连接,通过发送RST分组(而不是用正常的FIN|ACK|FIN|ACK四个分组)来关闭该连接。至于发送缓冲区中如果有未发送完的数据,则丢弃。主动关闭一方的TCP状态则跳过TIMEWAIT,直接进入CLOSED。网上很多人想利用这一点来解决服务器上出现大量的TIMEWAIT状态的socket的问题,但是,这并不是一个好主意,这种关闭方式的用途并不在这儿,实际用途在于服务器在应用层的需求 Onoff 非 0, Linger非0,close()用以下方式关闭连接。 将连接的关闭设置一个超时。如果socket发送缓冲区中仍残留数据,进程进入睡眠,内核进入定时状态去尽量去发送这些数据。在超时之前,如果所有数据都发送完且被对方确认,内核用正常的FIN|ACK|FIN|ACK四个分组来关闭该连接,close()成功返回。如果超时之时,数据仍然未能成功发送及被确认,用上述a方式来关闭此连接。close()返回EWOULDBLOCK。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有