diff --git a/cmd/netstack/TcpConn.go b/cmd/netstack/TcpConn.go index 2e74f56..bebd46d 100644 --- a/cmd/netstack/TcpConn.go +++ b/cmd/netstack/TcpConn.go @@ -147,7 +147,7 @@ func tcpListen(s *stack.Stack, proto tcpip.NetworkProtocolNumber, addr tcpip.Add // 绑定IP和端口,这里的IP地址为空,表示绑定任何IP // 此时就会调用端口管理器 - if err := ep.Bind(tcpip.FullAddress{NIC: 1, Addr: "", Port: uint16(localPort)}, nil); err != nil { + if err := ep.Bind(tcpip.FullAddress{NIC: 1, Addr: addr, Port: uint16(localPort)}, nil); err != nil { log.Fatal("Bind failed: ", err) } diff --git a/cmd/netstack/main.go b/cmd/netstack/main.go index 93fd5fc..b44c48a 100644 --- a/cmd/netstack/main.go +++ b/cmd/netstack/main.go @@ -33,7 +33,7 @@ func main() { log.Fatal("Usage: ", os.Args[0], " ") } - logger.SetFlags(logger.HANDSHAKE) + logger.SetFlags(logger.ETH) log.SetFlags(log.Lshortfile | log.LstdFlags) tapName := flag.Arg(0) @@ -97,8 +97,6 @@ func main() { ResolutionRequired: true, }) - _ = linkID - loopbackLinkID := loopback.New() // 新建相关协议的协议栈 @@ -106,7 +104,13 @@ func main() { []string{tcp.ProtocolName, udp.ProtocolName}, stack.Options{}) // 新建抽象的网卡 - if err := s.CreateNamedNIC(1, "vnic1", loopbackLinkID); err != nil { + _ = loopbackLinkID + if err := s.CreateNamedNIC(1, "lo", loopbackLinkID); err != nil { + log.Fatal(err) + } + + _ = linkID + if err := s.CreateNamedNIC(2, "fdbase", linkID); err != nil { log.Fatal(err) } @@ -209,7 +213,6 @@ func TestServerEcho(conn *TcpConn) { conn.ep.Close() } - func TestServerCase1(conn *TcpConn) { cnt := 0 time.Sleep(10 * time.Millisecond) diff --git a/cmd/tcpclient/tcpclient b/cmd/tcpclient/tcpclient deleted file mode 100755 index bfba280..0000000 Binary files a/cmd/tcpclient/tcpclient and /dev/null differ diff --git a/note/README.md b/note/README.md index 408d0a6..e38ce2d 100644 --- a/note/README.md +++ b/note/README.md @@ -8,380 +8,10 @@ 那么,有了这个认知,你可能会有种不太好的预感,是的,这是个巨大的工程。所幸谷歌为我们提供了一个极简版本的网络栈,在我跌跌撞撞地复现结束后,一个可以运行echo server的工程总共一万行出头,算是一个中等大小的项目了。 -不多说,先看看效果 +## 真实的内核如何接收网络包 -## echo server - -``` go -func main() { - flag.Parse() - if len(flag.Args()) != 4 { - log.Fatal("Usage: ", os.Args[0], " ") - } - - tapName := flag.Arg(0) - cidrName := flag.Arg(1) - addrName := flag.Arg(2) - portName := flag.Arg(3) - - // ... 解析各种配置 - - loopbackLinkID := loopback.New() - - // 新建相关协议的协议栈 - s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, - []string{tcp.ProtocolName, udp.ProtocolName}, stack.Options{}) - - // 新建抽象的网卡 - if err := s.CreateNamedNIC(1, "vnic1", loopbackLinkID); err != nil { - log.Fatal(err) - } - - // 在该协议栈上添加和注册相应的网络层 - if err := s.AddAddress(1, proto, addr); err != nil { - log.Fatal(err) - } - - // 在该协议栈上添加和注册ARP协议 - if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { - log.Fatal(err) - } - - // 添加默认路由 - s.SetRouteTable([]tcpip.Route{ - { - Destination: tcpip.Address(strings.Repeat("\x00", len(addr))), - Mask: tcpip.AddressMask(strings.Repeat("\x00", len(addr))), - Gateway: "", - NIC: 1, - }, - }) - - done := make(chan struct{}, 2) - - go func() { // echo server - listener := tcpListen(s, proto, addr, localPort) - done <- struct{}{} - for { - conn, err := listener.Accept() - if err != nil { - log.Println(err) - } - log.Println("服务端 建立连接") - - go TestServerEcho(conn) - } - - }() - - go func() { - <-done - port := localPort - conn, err := Dial(s, header.IPv4ProtocolNumber, addr, port) - if err != nil { - log.Fatal(err) - } - - log.Printf("客户端 建立连接\n\n客户端 写入数据\n") - - for i := 0; i < 3; i++ { - conn.Write([]byte("Hello Netstack")) - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - log.Println(err) - return - } - logger.NOTICE("客户端读取", string(buf[:n])) - } - - conn.Close() - }() - - defer close(done) - c := make(chan os.Signal) - signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGUSR1, syscall.SIGUSR2) - <-c -} - -func TestServerEcho(conn *TcpConn) { - for { - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - log.Println(err) - break - } - logger.NOTICE("服务端读取数据", string(buf[:n])) - conn.Write(buf) - } - - conn.ep.Close() -} -``` - - -上面的代码看上去内容很多,其实并不复杂,在`done := make(chan struct{}, 2)`之前的代码是在初始化协议栈,类比的话相当于linux内核的启动,其中的内部细节并不需要用户程序了解,我们只需要关注内核对外暴露的接口即可。 - -随后的两个goroutine,一个是服务端,一个是客户端。 - -先看看服务端做了什么 - -``` go -go func() { // echo server - listener := tcpListen(s, proto, addr, localPort) // 监听端口 - done <- struct{}{} // 通知客户端可以呼叫 - for { - conn, err := listener.Accept() // 循环接受连接 - if err != nil { - log.Println(err) - } - log.Println("服务端 建立连接") - - go TestServerEcho(conn) // 后台处理连接 - } - -}() - -// 连接的处理逻辑 -func TestServerEcho(conn *TcpConn) { - for { - buf := make([]byte, 1024) - n, err := conn.Read(buf) // 从连接中读数据 - if err != nil { // 当客户端关闭连接的时候 退出循环 - log.Println(err) - break - } - logger.NOTICE("服务端读取数据", string(buf[:n])) - conn.Write(buf) // 把读到的数据写回去 - } - - conn.ep.Close() // 客户端关闭后 我们退出 -} -``` - -非常简单的一个echo server 客户端写了什么 我们就回复什么 - -再来看看客户端 - -``` go - -go func() { - <-done // 等待服务端准备完毕 - port := localPort - conn, err := Dial(s, header.IPv4ProtocolNumber, addr, port) // 呼叫服务端 建立连接 - if err != nil { - log.Fatal(err) - } - - // 循环三次写入数据 - for i := 0; i < 3; i++ { - conn.Write([]byte("Hello Netstack")) - buf := make([]byte, 1024) - n, err := conn.Read(buf) // 写完再读 - if err != nil { - log.Println(err) - return - } - logger.NOTICE("客户端读取", string(buf[:n])) - } - - conn.Close() // 主动关闭连接 -}() -``` - -我们来看看这个程序编译运行的结果 - -``` go -2022/12/29 14:02:15 main.go:44: tap: tap0, addr: 192.168.1.1, port: 9999 -2022/12/29 14:02:15 ports.go:109: TCP 成功分配端口 9999 -2022/12/29 14:02:15 connect.go:877: TCP STATE SENT -2022/12/29 14:02:15 logger.go:75: NOTICE: 告诉对端 我的接收窗口为 65535 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [syn] 报文片段到 192.168.1.1:9999, seq: 1569505920, ack: 0, 可接收rcvWnd: 65535 -2022/12/29 14:02:15 accept.go:370: TCP STATE LISTEN -2022/12/29 14:02:15 accept.go:228: 收到一个远端握手申请 SYN seq = 1569505920 客户端请携带 标记 iss 710316102 +1 -2022/12/29 14:02:15 accept.go:217: 服务端握手成功 服务端的recver 9999 - +------> 1048576 <-----+ - | | ------------------+-------------+-------------+------------------------+ -| ANR 0 | not revived | rcvd unack | able rcv | ------------------+-------------+-------------+------------------------+ -^ ^ ^ -| | | -1569505921 1569505921 1570554497 -2022/12/29 14:02:15 accept.go:246: TCP STATE SYN_RCVD -2022/12/29 14:02:15 logger.go:75: NOTICE: 告诉对端 我的接收窗口为 65535 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|syn] 报文片段到 192.168.1.1:26913, seq: 710316102, ack: 1569505921, 可接收rcvWnd: 65535 -2022/12/29 14:02:15 connect.go:214: 客户端收到了 syn|ack segment -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505921, ack: 710316103, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 connect.go:901: 客户端握手成功 客户端的sender 26913 - +-----> 65535 <------+ - | Scale 5 | ------------------+-------------+-------------+------------------ -| 已确认 |UAC 0|NXT 1048576| 不可发送 ------------------+-------------+-------------+------------------ - ^ ^ - | | - 1569505921 1569505921 -2022/12/29 14:02:15 main.go:163: 客户端 建立连接 -2022/12/29 14:02:15 connect.go:332: TCP STATE ESTABLISHED -2022/12/29 14:02:15 main.go:148: 服务端 建立连接 -2022/12/29 14:02:15 logger.go:75: NOTICE: 扩张发送窗口到 1048576 - -客户端 写入数据 -2022/12/29 14:02:15 snd.go:586: 发送窗口是 65535 最多发送数据 65483 缓存数据头 1569505921 缓存数据尾 1569505935 发送端缓存包数量 1 拥塞窗口为 10 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|psh] 报文片段到 192.168.1.1:9999, seq: 1569505921, ack: 710316103, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505921 为 1569505935 下一次发送的数据头为 1569505935 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710316103, ack: 1569505935, 可接收rcvWnd: 32767 -2022/12/29 14:02:15 logger.go:75: NOTICE: 服务端读取数据 Hello Netstack - - -2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 710316103 缓存数据尾 710317127 发送端缓存包数量 1 拥塞窗口为 10 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|psh] 报文片段到 192.168.1.1:26913, seq: 710316103, ack: 1569505935, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710316103 为 710317127 下一次发送的数据头为 710317127 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505935, ack: 710317127, 可接收rcvWnd: 32736 -2022/12/29 14:02:15 logger.go:75: NOTICE: 客户端读取 Hello Netstack - - -2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 1569505935 缓存数据尾 1569505949 发送端缓存包数量 1 拥塞窗口为 11 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|psh] 报文片段到 192.168.1.1:9999, seq: 1569505935, ack: 710317127, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505935 为 1569505949 下一次发送的数据头为 1569505949 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710317127, ack: 1569505949, 可接收rcvWnd: 32767 -2022/12/29 14:02:15 logger.go:75: NOTICE: 服务端读取数据 Hello Netstack - - -2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 710317127 缓存数据尾 710318151 发送端缓存包数量 1 拥塞窗口为 11 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|psh] 报文片段到 192.168.1.1:26913, seq: 710317127, ack: 1569505949, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710317127 为 710318151 下一次发送的数据头为 710318151 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505949, ack: 710318151, 可接收rcvWnd: 32736 -2022/12/29 14:02:15 logger.go:75: NOTICE: 客户端读取 Hello Netstack - - -2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 1569505949 缓存数据尾 1569505963 发送端缓存包数量 1 拥塞窗口为 12 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|psh] 报文片段到 192.168.1.1:9999, seq: 1569505949, ack: 710318151, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505949 为 1569505963 下一次发送的数据头为 1569505963 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710318151, ack: 1569505963, 可接收rcvWnd: 32767 -2022/12/29 14:02:15 logger.go:75: NOTICE: 服务端读取数据 Hello Netstack - - -2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 710318151 缓存数据尾 710319175 发送端缓存包数量 1 拥塞窗口为 12 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|psh] 报文片段到 192.168.1.1:26913, seq: 710318151, ack: 1569505963, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710318151 为 710319175 下一次发送的数据头为 710319175 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505963, ack: 710319175, 可接收rcvWnd: 32736 -2022/12/29 14:02:15 logger.go:75: NOTICE: 客户端读取 Hello Netstack - - -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|fin] 报文片段到 192.168.1.1:9999, seq: 1569505963, ack: 710319175, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505963 为 1569505964 下一次发送的数据头为 1569505964 -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710319175, ack: 1569505964, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 main.go:202: endpoint is closed for receive -2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|fin] 报文片段到 192.168.1.1:26913, seq: 710319175, ack: 1569505964, 可接收rcvWnd: 32768 -2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710319175 为 710319176 下一次发送的数据头为 710319176 -2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505964, ack: 710319176, 可接收rcvWnd: 32768 - -``` - -为了看得清楚一点,我将结果分了几个段,我们可以看到,在每个段的最后一行有 服务端/客户端 读取 Hello Netstack 数量也符合我们的程序逻辑 - -## 接口 - -在上面的程序中,我们可以发现和正常的网络编程不同,我使用了一些不同于go/net库的函数。 - -``` go -TcpListen() - -Dial() - -... -``` - - -这些函数是我自己封装的,用于隐藏底层对协议栈的复杂操作。但这个封装是非常简陋的,仅仅是能测试而已,但我们先就用这个来简单说说。 - -首先是一个结构体,表示一条tcp的连接。 - -``` go -// TcpConn 一条tcp连接 -type TcpConn struct { - raddr tcpip.FullAddress // - ep tcpip.Endpoint - wq *waiter.Queue - we *waiter.Entry - notifyCh chan struct{} -} -``` - - -对于服务端而言,它是这样建立的 - -``` go -func tcpListen(s *stack.Stack, proto tcpip.NetworkProtocolNumber, addr tcpip.Address, localPort int) *TcpConn { - var wq waiter.Queue - // 新建一个tcp端 - ep, err := s.NewEndpoint(tcp.ProtocolNumber, proto, &wq) - if err != nil { - log.Fatal(err) - } - - // 绑定IP和端口,这里的IP地址为空,表示绑定任何IP - // 此时就会调用端口管理器 - if err := ep.Bind(tcpip.FullAddress{NIC: 1, Addr: "", Port: uint16(localPort)}, nil); err != nil { - log.Fatal("Bind failed: ", err) - } - - // 开始监听 - if err := ep.Listen(10); err != nil { - log.Fatal("Listen failed: ", err) - } - - waitEntry, notifyCh := waiter.NewChannelEntry(nil) - return &TcpConn{ - ep: ep, - wq: &wq, - we: &waitEntry, - notifyCh: notifyCh} -} -``` - - -对于客户端而言,它是这样建立的 - -``` go -// Dial 呼叫tcp服务端 -func Dial(s *stack.Stack, proto tcpip.NetworkProtocolNumber, addr tcpip.Address, port int) (*TcpConn, error) { - remote := tcpip.FullAddress{ - Addr: addr, - Port: uint16(port), - } - var wq waiter.Queue - waitEntry, notifyCh := waiter.NewChannelEntry(nil) - wq.EventRegister(&waitEntry, waiter.EventOut) - defer wq.EventUnregister(&waitEntry) - // 新建一个tcp端 - ep, err := s.NewEndpoint(tcp.ProtocolNumber, proto, &wq) - if err != nil { - return nil, fmt.Errorf("%s", err.String()) - } - err = ep.Connect(remote) - if err != nil { - if err == tcpip.ErrConnectStarted { - <-notifyCh - } else { - return nil, fmt.Errorf("%s", err.String()) - } - } - - ep.SetSockOpt(tcpip.KeepaliveEnabledOption(1)) - ep.SetSockOpt(tcpip.KeepaliveIntervalOption(75 * time.Second)) - ep.SetSockOpt(tcpip.KeepaliveIdleOption(30 * time.Second)) // 30s的探活心跳 - ep.SetSockOpt(tcpip.KeepaliveCountOption(9)) - - return &TcpConn{ - ep: ep, - wq: &wq, - we: &waitEntry, - notifyCh: notifyCh}, nil -} -``` +1. 当网卡收到数据以后,以DMA的方式把网卡收到的帧写到内存里,再向CPU发起一个中断,以通知CPU有数据到达。 +2. 当CPU收到中断请求后,会去调用网络设备驱动注册的中断处理函数。网卡的中断处理函数并不做过多工作,发出软中断请求,然后尽快释放CPU资源。 +3. ksoftirqd内核线程检测到有软中断请求到达,调用poll开始轮询收包,受到后交给各级协议栈处理。对于tcp包来说,会被放到用户socker的接受队列中。 diff --git a/note/echo/README.md b/note/echo/README.md new file mode 100644 index 0000000..460d358 --- /dev/null +++ b/note/echo/README.md @@ -0,0 +1,425 @@ +## echo server + +``` go +func main() { + flag.Parse() + if len(flag.Args()) != 4 { + log.Fatal("Usage: ", os.Args[0], " ") + } + + tapName := flag.Arg(0) + cidrName := flag.Arg(1) + addrName := flag.Arg(2) + portName := flag.Arg(3) + + // ... 解析各种配置 + + loopbackLinkID := loopback.New() + + // 新建相关协议的协议栈 + s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, + []string{tcp.ProtocolName, udp.ProtocolName}, stack.Options{}) + + // 新建抽象的网卡 + if err := s.CreateNamedNIC(1, "vnic1", loopbackLinkID); err != nil { + log.Fatal(err) + } + + // 在该协议栈上添加和注册相应的网络层 + if err := s.AddAddress(1, proto, addr); err != nil { + log.Fatal(err) + } + + // 在该协议栈上添加和注册ARP协议 + if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { + log.Fatal(err) + } + + // 添加默认路由 + s.SetRouteTable([]tcpip.Route{ + { + Destination: tcpip.Address(strings.Repeat("\x00", len(addr))), + Mask: tcpip.AddressMask(strings.Repeat("\x00", len(addr))), + Gateway: "", + NIC: 1, + }, + }) + + done := make(chan struct{}, 2) + + go func() { // echo server + listener := tcpListen(s, proto, addr, localPort) + done <- struct{}{} + for { + conn, err := listener.Accept() + if err != nil { + log.Println(err) + } + log.Println("服务端 建立连接") + + go TestServerEcho(conn) + } + + }() + + go func() { + <-done + port := localPort + conn, err := Dial(s, header.IPv4ProtocolNumber, addr, port) + if err != nil { + log.Fatal(err) + } + + log.Printf("客户端 建立连接\n\n客户端 写入数据\n") + + for i := 0; i < 3; i++ { + conn.Write([]byte("Hello Netstack")) + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + log.Println(err) + return + } + logger.NOTICE("客户端读取", string(buf[:n])) + } + + conn.Close() + }() + + defer close(done) + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGUSR1, syscall.SIGUSR2) + <-c +} + +func TestServerEcho(conn *TcpConn) { + for { + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + log.Println(err) + break + } + logger.NOTICE("服务端读取数据", string(buf[:n])) + conn.Write(buf) + } + + conn.ep.Close() +} +``` + + +上面的代码看上去内容很多,其实并不复杂,在`done := make(chan struct{}, 2)`之前的代码是在初始化协议栈,类比的话相当于linux内核的启动,其中的内部细节并不需要用户程序了解,我们只需要关注内核对外暴露的接口即可。 + +随后的两个goroutine,一个是服务端,一个是客户端。 + +先看看服务端做了什么 + +``` go +go func() { // echo server + listener := tcpListen(s, proto, addr, localPort) // 监听端口 + done <- struct{}{} // 通知客户端可以呼叫 + for { + conn, err := listener.Accept() // 循环接受连接 + if err != nil { + log.Println(err) + } + log.Println("服务端 建立连接") + + go TestServerEcho(conn) // 后台处理连接 + } + +}() + +// 连接的处理逻辑 +func TestServerEcho(conn *TcpConn) { + for { + buf := make([]byte, 1024) + n, err := conn.Read(buf) // 从连接中读数据 + if err != nil { // 当客户端关闭连接的时候 退出循环 + log.Println(err) + break + } + logger.NOTICE("服务端读取数据", string(buf[:n])) + conn.Write(buf) // 把读到的数据写回去 + } + + conn.ep.Close() // 客户端关闭后 我们退出 +} +``` + +非常简单的一个echo server 客户端写了什么 我们就回复什么 + +再来看看客户端 + +``` go + +go func() { + <-done // 等待服务端准备完毕 + port := localPort + conn, err := Dial(s, header.IPv4ProtocolNumber, addr, port) // 呼叫服务端 建立连接 + if err != nil { + log.Fatal(err) + } + + // 循环三次写入数据 + for i := 0; i < 3; i++ { + conn.Write([]byte("Hello Netstack")) + buf := make([]byte, 1024) + n, err := conn.Read(buf) // 写完再读 + if err != nil { + log.Println(err) + return + } + logger.NOTICE("客户端读取", string(buf[:n])) + } + + conn.Close() // 主动关闭连接 +}() +``` + +我们来看看这个程序编译运行的结果 + +``` go +2022/12/29 14:02:15 main.go:44: tap: tap0, addr: 192.168.1.1, port: 9999 +2022/12/29 14:02:15 ports.go:109: TCP 成功分配端口 9999 +2022/12/29 14:02:15 connect.go:877: TCP STATE SENT +2022/12/29 14:02:15 logger.go:75: NOTICE: 告诉对端 我的接收窗口为 65535 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [syn] 报文片段到 192.168.1.1:9999, seq: 1569505920, ack: 0, 可接收rcvWnd: 65535 +2022/12/29 14:02:15 accept.go:370: TCP STATE LISTEN +2022/12/29 14:02:15 accept.go:228: 收到一个远端握手申请 SYN seq = 1569505920 客户端请携带 标记 iss 710316102 +1 +2022/12/29 14:02:15 accept.go:217: 服务端握手成功 服务端的recver 9999 + +------> 1048576 <-----+ + | | +-----------------+-------------+-------------+------------------------+ +| ANR 0 | not revived | rcvd unack | able rcv | +-----------------+-------------+-------------+------------------------+ +^ ^ ^ +| | | +1569505921 1569505921 1570554497 +2022/12/29 14:02:15 accept.go:246: TCP STATE SYN_RCVD +2022/12/29 14:02:15 logger.go:75: NOTICE: 告诉对端 我的接收窗口为 65535 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|syn] 报文片段到 192.168.1.1:26913, seq: 710316102, ack: 1569505921, 可接收rcvWnd: 65535 +2022/12/29 14:02:15 connect.go:214: 客户端收到了 syn|ack segment +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505921, ack: 710316103, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 connect.go:901: 客户端握手成功 客户端的sender 26913 + +-----> 65535 <------+ + | Scale 5 | +-----------------+-------------+-------------+------------------ +| 已确认 |UAC 0|NXT 1048576| 不可发送 +-----------------+-------------+-------------+------------------ + ^ ^ + | | + 1569505921 1569505921 +2022/12/29 14:02:15 main.go:163: 客户端 建立连接 +2022/12/29 14:02:15 connect.go:332: TCP STATE ESTABLISHED +2022/12/29 14:02:15 main.go:148: 服务端 建立连接 +2022/12/29 14:02:15 logger.go:75: NOTICE: 扩张发送窗口到 1048576 + +客户端 写入数据 +2022/12/29 14:02:15 snd.go:586: 发送窗口是 65535 最多发送数据 65483 缓存数据头 1569505921 缓存数据尾 1569505935 发送端缓存包数量 1 拥塞窗口为 10 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|psh] 报文片段到 192.168.1.1:9999, seq: 1569505921, ack: 710316103, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505921 为 1569505935 下一次发送的数据头为 1569505935 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710316103, ack: 1569505935, 可接收rcvWnd: 32767 +2022/12/29 14:02:15 logger.go:75: NOTICE: 服务端读取数据 Hello Netstack + + +2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 710316103 缓存数据尾 710317127 发送端缓存包数量 1 拥塞窗口为 10 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|psh] 报文片段到 192.168.1.1:26913, seq: 710316103, ack: 1569505935, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710316103 为 710317127 下一次发送的数据头为 710317127 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505935, ack: 710317127, 可接收rcvWnd: 32736 +2022/12/29 14:02:15 logger.go:75: NOTICE: 客户端读取 Hello Netstack + + +2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 1569505935 缓存数据尾 1569505949 发送端缓存包数量 1 拥塞窗口为 11 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|psh] 报文片段到 192.168.1.1:9999, seq: 1569505935, ack: 710317127, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505935 为 1569505949 下一次发送的数据头为 1569505949 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710317127, ack: 1569505949, 可接收rcvWnd: 32767 +2022/12/29 14:02:15 logger.go:75: NOTICE: 服务端读取数据 Hello Netstack + + +2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 710317127 缓存数据尾 710318151 发送端缓存包数量 1 拥塞窗口为 11 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|psh] 报文片段到 192.168.1.1:26913, seq: 710317127, ack: 1569505949, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710317127 为 710318151 下一次发送的数据头为 710318151 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505949, ack: 710318151, 可接收rcvWnd: 32736 +2022/12/29 14:02:15 logger.go:75: NOTICE: 客户端读取 Hello Netstack + + +2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 1569505949 缓存数据尾 1569505963 发送端缓存包数量 1 拥塞窗口为 12 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|psh] 报文片段到 192.168.1.1:9999, seq: 1569505949, ack: 710318151, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505949 为 1569505963 下一次发送的数据头为 1569505963 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710318151, ack: 1569505963, 可接收rcvWnd: 32767 +2022/12/29 14:02:15 logger.go:75: NOTICE: 服务端读取数据 Hello Netstack + + +2022/12/29 14:02:15 snd.go:586: 发送窗口是 1048576 最多发送数据 65483 缓存数据头 710318151 缓存数据尾 710319175 发送端缓存包数量 1 拥塞窗口为 12 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|psh] 报文片段到 192.168.1.1:26913, seq: 710318151, ack: 1569505963, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710318151 为 710319175 下一次发送的数据头为 710319175 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505963, ack: 710319175, 可接收rcvWnd: 32736 +2022/12/29 14:02:15 logger.go:75: NOTICE: 客户端读取 Hello Netstack + + +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack|fin] 报文片段到 192.168.1.1:9999, seq: 1569505963, ack: 710319175, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 26913 更新sndNxt 1569505963 为 1569505964 下一次发送的数据头为 1569505964 +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack] 报文片段到 192.168.1.1:26913, seq: 710319175, ack: 1569505964, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 main.go:202: endpoint is closed for receive +2022/12/29 14:02:15 connect.go:608: TCP :9999 发送 [ack|fin] 报文片段到 192.168.1.1:26913, seq: 710319175, ack: 1569505964, 可接收rcvWnd: 32768 +2022/12/29 14:02:15 snd.go:603: 9999 更新sndNxt 710319175 为 710319176 下一次发送的数据头为 710319176 +2022/12/29 14:02:15 connect.go:608: TCP :26913 发送 [ack] 报文片段到 192.168.1.1:9999, seq: 1569505964, ack: 710319176, 可接收rcvWnd: 32768 + +``` + +为了看得清楚一点,我将结果分了几个段,我们可以看到,在每个段的最后一行有 服务端/客户端 读取 Hello Netstack 数量也符合我们的程序逻辑 + +## 接口 + +在上面的程序中,我们可以发现和正常的网络编程不同,我使用了一些不同于go/net库的函数。 + +``` go +TcpListen() + +Dial() + +... +``` + + +这些函数是我自己封装的,用于隐藏底层对协议栈的复杂操作。但这个封装是非常简陋的,仅仅是能测试而已,但我们先就用这个来简单说说。 + +首先是一个结构体,表示一条tcp的连接。 + +``` go +// TcpConn 一条tcp连接 +type TcpConn struct { + raddr tcpip.FullAddress + ep tcpip.Endpoint + wq *waiter.Queue + we *waiter.Entry + notifyCh chan struct{} +} +``` + + +对于服务端而言,它是这样建立的 + +``` go +func tcpListen(s *stack.Stack, proto tcpip.NetworkProtocolNumber, addr tcpip.Address, localPort int) *TcpConn { + var wq waiter.Queue + // 新建一个tcp端 + ep, err := s.NewEndpoint(tcp.ProtocolNumber, proto, &wq) + if err != nil { + log.Fatal(err) + } + + // 绑定IP和端口,这里的IP地址为空,表示绑定任何IP + // 此时就会调用端口管理器 + if err := ep.Bind(tcpip.FullAddress{NIC: 1, Addr: "", Port: uint16(localPort)}, nil); err != nil { + log.Fatal("Bind failed: ", err) + } + + // 开始监听 + if err := ep.Listen(10); err != nil { + log.Fatal("Listen failed: ", err) + } + + waitEntry, notifyCh := waiter.NewChannelEntry(nil) + return &TcpConn{ + ep: ep, + wq: &wq, + we: &waitEntry, + notifyCh: notifyCh} +} +``` + + +对于客户端而言,它是这样建立的 + +``` go +// Dial 呼叫tcp服务端 +func Dial(s *stack.Stack, proto tcpip.NetworkProtocolNumber, addr tcpip.Address, port int) (*TcpConn, error) { + remote := tcpip.FullAddress{ + Addr: addr, + Port: uint16(port), + } + var wq waiter.Queue + waitEntry, notifyCh := waiter.NewChannelEntry(nil) + wq.EventRegister(&waitEntry, waiter.EventOut) + defer wq.EventUnregister(&waitEntry) + // 新建一个tcp端 + ep, err := s.NewEndpoint(tcp.ProtocolNumber, proto, &wq) + if err != nil { + return nil, fmt.Errorf("%s", err.String()) + } + err = ep.Connect(remote) + if err != nil { + if err == tcpip.ErrConnectStarted { + <-notifyCh + } else { + return nil, fmt.Errorf("%s", err.String()) + } + } + + ep.SetSockOpt(tcpip.KeepaliveEnabledOption(1)) + ep.SetSockOpt(tcpip.KeepaliveIntervalOption(75 * time.Second)) + ep.SetSockOpt(tcpip.KeepaliveIdleOption(30 * time.Second)) // 30s的探活心跳 + ep.SetSockOpt(tcpip.KeepaliveCountOption(9)) + + return &TcpConn{ + ep: ep, + wq: &wq, + we: &waitEntry, + notifyCh: notifyCh}, nil +} +``` + + +数据的读写 + +``` go +// Read 读数据 +func (conn *TcpConn) Read(rcv []byte) (int, error) { + conn.wq.EventRegister(conn.we, waiter.EventIn) + defer conn.wq.EventUnregister(conn.we) + for { + buf, _, err := conn.ep.Read(&conn.raddr) + if err != nil { + if err == tcpip.ErrWouldBlock { + <-conn.notifyCh + continue + } + return 0, fmt.Errorf("%s", err.String()) + } + n := len(buf) + if n > cap(rcv) { + n = cap(rcv) + } + rcv = append(rcv[:0], buf[:n]...) + return len(buf), nil + } +} + +// Write 写数据 +func (conn *TcpConn) Write(snd []byte) error { + conn.wq.EventRegister(conn.we, waiter.EventOut) + defer conn.wq.EventUnregister(conn.we) + for { + n, _, err := conn.ep.Write(tcpip.SlicePayload(snd), tcpip.WriteOptions{To: &conn.raddr}) + if err != nil { + if err == tcpip.ErrWouldBlock { + <-conn.notifyCh + if int(n) < len(snd) && n > 0 { + snd = snd[n:] + } + continue + } + return fmt.Errorf("%s", err.String()) + } + return nil + } +} + +// Close 关闭连接 +func (conn *TcpConn) Close() { + conn.ep.Close() +} +``` diff --git a/note/link/READMD.md b/note/link/READMD.md index abe6802..150b1dd 100644 --- a/note/link/READMD.md +++ b/note/link/READMD.md @@ -1,2 +1,3 @@ # 链路层 + diff --git a/tcpip/link/fdbased/endpoint.go b/tcpip/link/fdbased/endpoint.go index 5759f50..f15fff6 100644 --- a/tcpip/link/fdbased/endpoint.go +++ b/tcpip/link/fdbased/endpoint.go @@ -1,6 +1,7 @@ package fdbased import ( + "fmt" "log" "netstack/logger" "netstack/tcpip" @@ -128,7 +129,7 @@ func (e *endpoint) WritePacket(r *stack.Route, hdr buffer.Prependable, } eth.Encode(ethHdr) // 将以太帧信息作为报文头编入 logger.GetInstance().Info(logger.ETH, func() { - log.Println("链路层写回以太报文") + log.Println("链路层写回以太报文 ", r.LocalAddress, " to ", r.RemoteAddress) }) // 写入网卡中 if payload.Size() == 0 { diff --git a/tcpip/link/loopback/loopback.go b/tcpip/link/loopback/loopback.go index 9934430..db80223 100644 --- a/tcpip/link/loopback/loopback.go +++ b/tcpip/link/loopback/loopback.go @@ -44,10 +44,6 @@ func (e *endpoint) WritePacket(r *stack.Route, hdr buffer.Prependable, payload b views = append(views, payload.Views()...) vv := buffer.NewVectorisedView(len(views[0])+payload.Size(), views) - // TODO 这里整点活 在特定的情况下丢掉数据报 模拟网络阻塞 - //rand.Seed(time.Now().Unix()) - //time.Sleep(time.Duration(rand.Intn(50)+50) * time.Millisecond) - e.count++ //if e.count == 6 { // 丢掉客户端写入的第二个包 // logger.NOTICE(fmt.Sprintf("统计 %d 丢掉这个报文", e.count)) diff --git a/tcpip/stack/route.go b/tcpip/stack/route.go index b84322b..3d8838f 100644 --- a/tcpip/stack/route.go +++ b/tcpip/stack/route.go @@ -111,6 +111,7 @@ func (r *Route) RemoveWaker(waker *sleep.Waker) { // IsResolutionRequired returns true if Resolve() must be called to resolve // the link address before the this route can be written to. +// 本地缓存有东西 而且不知道远端的MAC func (r *Route) IsResolutionRequired() bool { return r.ref.linkCache != nil && r.RemoteLinkAddress == "" } diff --git a/tcpip/transport/tcp/connect.go b/tcpip/transport/tcp/connect.go index 2ebf31a..be21b71 100644 --- a/tcpip/transport/tcp/connect.go +++ b/tcpip/transport/tcp/connect.go @@ -150,7 +150,7 @@ func (h *handshake) resetToSynRcvd(iss seqnum.Value, irs seqnum.Value, opts *hea h.sndWndScale = opts.WS } -// TODO 处理tcp路由 +// 处理tcp路由 func (h *handshake) resolveRoute() *tcpip.Error { // Set up the wakers. s := sleep.Sleeper{} @@ -166,7 +166,6 @@ func (h *handshake) resolveRoute() *tcpip.Error { case wakerForResolution: if _, err := h.ep.route.Resolve(resolutionWaker); err != tcpip.ErrWouldBlock { // Either success (err == nil) or failure. - log.Println("没有地址", err) return err } // Resolution not completed. Keep trying... @@ -406,6 +405,7 @@ func (h *handshake) execute() *tcpip.Error { return err } } + // Initialize the resend timer. // 初始化重传定时器 resendWaker := sleep.Waker{}