diff --git a/cmd/netstack/main.go b/cmd/netstack/main.go index d037da8..2a3e703 100644 --- a/cmd/netstack/main.go +++ b/cmd/netstack/main.go @@ -12,6 +12,7 @@ import ( "netstack/tcpip/network/ipv4" "netstack/tcpip/network/ipv6" "netstack/tcpip/stack" + "netstack/tcpip/transport/tcp" "netstack/tcpip/transport/udp" "netstack/waiter" "os" @@ -151,6 +152,28 @@ func main() { //TCPServer(conn, rcv) } +func tcpListen(s *stack.Stack, proto tcpip.NetworkProtocolNumber, localPort int) tcpip.Endpoint { + 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{0, "", uint16(localPort)}, nil); err != nil { + log.Fatal("Bind failed: ", err) + } + + // 开始监听 + if err := ep.Listen(10); err != nil { + log.Fatal("Listen failed: ", err) + } + + return ep +} + type UdpConn struct { raddr tcpip.FullAddress ep tcpip.Endpoint diff --git a/tcpip/header/tcp.go b/tcpip/header/tcp.go new file mode 100644 index 0000000..c8226cb --- /dev/null +++ b/tcpip/header/tcp.go @@ -0,0 +1,36 @@ +package header + +import "netstack/tcpip" + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Source Port | Destination Port | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Sequence Number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Acknowledgment Number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Data | |U|A|P|R|S|F| | +| Offset| Reserved |R|C|S|S|Y|I| Window | +| | |G|K|H|T|N|N| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Checksum | Urgent Pointer | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Options | Padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// TCP represents a TCP header stored in a byte array. +type TCP []byte + +const ( + // TCPMinimumSize is the minimum size of a valid TCP packet. + TCPMinimumSize = 20 + + // TCPProtocolNumber is TCP's transport protocol number. + TCPProtocolNumber tcpip.TransportProtocolNumber = 6 +) diff --git a/tcpip/stack/stack.go b/tcpip/stack/stack.go index c20f211..adf6a41 100644 --- a/tcpip/stack/stack.go +++ b/tcpip/stack/stack.go @@ -13,8 +13,7 @@ import ( const ( // ageLimit is set to the same cache stale time used in Linux. - //ageLimit = 1 * time.Minute - ageLimit = 5 * time.Second + ageLimit = 1 * time.Minute // resolutionTimeout is set to the same ARP timeout used in Linux. resolutionTimeout = 1 * time.Second // resolutionAttempts is set to the same ARP retries used in Linux. @@ -486,14 +485,14 @@ func (s *Stack) RegisterTransportEndpoint(nicID tcpip.NICID, netProtos []tcpip.N func (s *Stack) UnregisterTransportEndpoint(nicID tcpip.NICID, netProtos []tcpip.NetworkProtocolNumber, protocol tcpip.TransportProtocolNumber, id TransportEndpointID) { if nicID == 0 { - s.demux.unregisterEndpoint(netProtos, protocol, id) + s.demux.unregisterEndpoint(netProtos, protocol, id) // 释放协议栈中的传输端 return } s.mu.RLock() defer s.mu.RUnlock() nic := s.nics[nicID] if nic != nil { - nic.demux.unregisterEndpoint(netProtos, protocol, id) + nic.demux.unregisterEndpoint(netProtos, protocol, id) //释放该网卡中的传输端 } } diff --git a/tcpip/transport/tcp/README.md b/tcpip/transport/tcp/README.md new file mode 100644 index 0000000..0299c06 --- /dev/null +++ b/tcpip/transport/tcp/README.md @@ -0,0 +1,51 @@ +# TCP 协议 + +## tcp特点 +1. tcp 是面向连接的传输协议。 +2. tcp 的连接是端到端的。 +3. tcp 提供可靠的传输。 +4. tcp 的传输以字节流的方式。 +5. tcp 提供全双工的通信。 +6. tcp 有拥塞控制。 + +![img](https://doc.shiyanlou.com/document-uid949121labid10418timestamp1555573949562.png) + +``` sh + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Source Port | Destination Port | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Sequence Number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Acknowledgment Number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Data | |U|A|P|R|S|F| | +| Offset| Reserved |R|C|S|S|Y|I| Window | +| | |G|K|H|T|N|N| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Checksum | Urgent Pointer | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Options | Padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +``` + +1. 源端口和目的端口 各占 2 个字节,分别 tcp 连接的源端口和目的端口。关于端口的概念之前已经介绍过了。 +2. 序号 占 4 字节,序号范围是[0,2^32 - 1],共 2^32(即 4294967296)个序号。序号增加到 2^32-1 后,下一个序号就又回到 0。TCP 是面向字节流的,在一个 TCP 连接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号(ISN)必须在连接建立时设置。首部中的序号字段值则是指的是本报文段所发送的数据的第一个字节的序号。例如,一报文段的序号是 301,而接待的数据共有 100 字节。这就表明:本报文段的数据的第一个字节的序号是 301,最后一个字节的序号是 400。显然,下一个报文段(如果还有的话)的数据序号应当从 401 开始,即下一个报文段的序号字段值应为 401。 +3. 确认号 占 4 字节,是期望收到对方下一个报文段的第一个数据字节的序号。例如,B 正确收到了 A 发送过来的一个报文段,其序号字段值是 501,而数据长度是 200 字节(序号 501~700),这表明 B 正确收到了 A 发送的到序号 700 为止的数据。因此,B 期望收到 A 的下一个数据序号是 701,于是 B 在发送给 A 的确认报文段中把确认号置为 701。注意,现在确认号不是 501,也不是 700,而是 701。 总之:若确认号为 N,则表明:到序号 N-1 为止的所有数据都已正确收到。TCP 除了第一个 SYN 报文之外,所有 TCP 报文都需要携带 ACK 状态位。 +4. 数据偏移 占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。这个字段实际上是指出 TCP 报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的,但应注意,“数据偏移”的单位是 4 个字节,由于 4 位二进制数能表示的最大十进制数字是 15,因此数据偏移的最大值是 60 字节。 +5. 保留 占 6 位,保留为今后使用,但目前应置为 0。 +6. 控制报文标志 + - 紧急URG(URGent) 当 URG=1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快发送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行,因此用户从键盘发出中断命令。如果不使用紧急数据,那么这两个字符将存储在接收 TCP 的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了很多时间。 当 URG 置为 1 时,发送应用进程就告诉发送方的 TCP 有紧急数据要传送。于是发送方 TCP 就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍然是普通数据。这时要与首部中紧急指针(Urgent Pointer)字段配合使用。 + - 确认ACK(ACKnowledgment) 仅当 ACK=1 时确认号字段才有效,当 ACK=0 时确认号无效。TCP 规定,在连接建立后所有的传送的报文段都必须把 ACK 置为 1。 + - 推送 PSH(PuSH) 当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP 就可以使用推送(push)操作。这时,发送方 TCP 把 PSH 置为 1,并立即创建一个报文段发送出去。接收方 TCP 收到 PSH=1 的报文段,就尽快地交付接收应用进程。 + - 复位RST(ReSeT) 当 RST=1 时,表名 TCP 连接中出现了严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立传输连接。RST 置为 1 用来拒绝一个非法的报文段或拒绝打开一个连接。 + - 同步SYN(SYNchronization) 在连接建立时用来同步序号。当 SYN=1 而 ACK=0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN=1 和 ACK=1,因此 SYN 置为 1 就表示这是一个连接请求或连接接受报文。 + - 终止FIN(FINis,意思是“完”“终”) 用来释放一个连接。当 FIN=1 时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。 +7. 窗口 占 2 字节,窗口值是[0,2^16-1]之间的整数。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据,作为流量控制的依据,后面会详细介绍。 总之:窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化。 +8. 检验和 占 2 字节,检验和字段检验的范围包括首部和数据这两部分。和 UDP 用户数据报一样,在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。伪首部的格式和 UDP 用户数据报的伪首部一样。但应把伪首部第 4 个字段中的 17 改为 6(TCP 的协议号是 6);把第 5 字段中的 UDP 中的长度改为 TCP 长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。若使用 IPv6,则相应的伪首部也要改变。 +9. 紧急指针 占 2 字节,紧急指针仅在 URG=1 时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据) 。因此,在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP 就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为 0 时也可以发送紧急数据。 +10. 选项 选项长度可变,最长可达 40 字节。当没有使用“选项”时,TCP 的首部长度是 20 字节。TCP 首部总长度由 TCP 头中的“数据偏移”字段决定,前面说了,最长偏移为 60 字节。那么“tcp 选项”的长度最大为 60-20=40 字节。 + diff --git a/tcpip/transport/tcp/endpoint.go b/tcpip/transport/tcp/endpoint.go new file mode 100644 index 0000000..2dd40e3 --- /dev/null +++ b/tcpip/transport/tcp/endpoint.go @@ -0,0 +1,6 @@ +package tcp + +// endpoint 表示TCP端点。该结构用作端点用户和协议实现之间的接口;让并发goroutine调用端点是合法的, +// 它们是正确同步的。然而,协议实现在单个goroutine中运行。 +type endpoint struct { +} diff --git a/tcpip/transport/tcp/protocol.go b/tcpip/transport/tcp/protocol.go new file mode 100644 index 0000000..cc6efe7 --- /dev/null +++ b/tcpip/transport/tcp/protocol.go @@ -0,0 +1,19 @@ +package tcp + +import "netstack/tcpip/header" + +const ( + // ProtocolName is the string representation of the tcp protocol name. + ProtocolName = "tcp" + + // ProtocolNumber is the tcp protocol number. + ProtocolNumber = header.TCPProtocolNumber + // MinBufferSize is the smallest size of a receive or send buffer. + minBufferSize = 4 << 10 // 4096 bytes. + + // DefaultBufferSize is the default size of the receive and send buffers. + DefaultBufferSize = 1 << 20 // 1MB + + // MaxBufferSize is the largest size a receive and send buffer can grow to. + maxBufferSize = 4 << 20 // 4MB +) diff --git a/tcpip/transport/udp/endpoint.go b/tcpip/transport/udp/endpoint.go index 1f81250..3ff2481 100644 --- a/tcpip/transport/udp/endpoint.go +++ b/tcpip/transport/udp/endpoint.go @@ -121,7 +121,7 @@ func NewConnectedEndpoint(stack *stack.Stack, r *stack.Route, id stack.Transport func (e *endpoint) Close() { e.mu.Lock() - e.shutdownFlags = tcpip.ShutdownRead | tcpip.ShutdownWrite + e.shutdownFlags = tcpip.ShutdownRead | tcpip.ShutdownWrite // 关闭读写 switch e.state { case stateBound, stateConnected: @@ -255,6 +255,7 @@ func (e *endpoint) prepareForWrite(to *tcpip.FullAddress) (retry bool, err *tcpi } // The state is still 'initial', so try to bind the endpoint. + // 随机绑定一个端口 if err := e.bindLocked(tcpip.FullAddress{}, nil); err != nil { return false, err } @@ -284,6 +285,19 @@ func (e *endpoint) Write(p tcpip.Payload, opts tcpip.WriteOptions) (uintptr, <-c return 0, nil, tcpip.ErrClosedForSend } + // Prepare for write. + // 准备发送数据 + for { + retry, err := e.prepareForWrite(to) + if err != nil { + return 0, nil, err + } + + if !retry { + break + } + } + var route *stack.Route var dstPort uint16 if to == nil { @@ -501,15 +515,37 @@ func (e *endpoint) Connect(addr tcpip.FullAddress) *tcpip.Error { } func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error { + e.mu.Lock() + defer e.mu.Unlock() + + // A socket in the bound state can still receive multicast messages, + // so we need to notify waiters on shutdown. + if e.state != stateBound && e.state != stateConnected { + return tcpip.ErrNotConnected + } + + e.shutdownFlags |= flags + + if flags&tcpip.ShutdownRead != 0 { + e.rcvMu.Lock() + wasClosed := e.rcvClosed + e.rcvClosed = true + e.rcvMu.Unlock() + + if !wasClosed { + e.waiterQueue.Notify(waiter.EventIn) + } + } + return nil } func (e *endpoint) Listen(backlog int) *tcpip.Error { - return nil + return tcpip.ErrNotSupported } func (e *endpoint) Accept() (tcpip.Endpoint, *waiter.Queue, *tcpip.Error) { - return nil, nil, nil + return nil, nil, tcpip.ErrNotSupported } func (e *endpoint) registerWithStack(nicid tcpip.NICID, netProtos []tcpip.NetworkProtocolNumber,