tcp基本框架

This commit is contained in:
impact-eintr
2022-12-05 11:28:59 +08:00
parent b576c7e261
commit f3ceda183b
7 changed files with 271 additions and 27 deletions

View File

@@ -95,7 +95,7 @@ func main() {
// 新建相关协议的协议栈
s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName},
[]string{ /*tcp.ProtocolName, */ udp.ProtocolName}, stack.Options{})
[]string{tcp.ProtocolName, udp.ProtocolName}, stack.Options{})
// 新建抽象的网卡
if err := s.CreateNamedNIC(1, "vnic1", linkID); err != nil {
@@ -122,22 +122,27 @@ func main() {
},
})
go func() { // echo server
// 监听udp localPort端口
conn := udpListen(s, proto, localPort)
//go func() { // echo server
// // 监听udp localPort端口
// conn := udpListen(s, proto, localPort)
for {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Println(err)
break
}
log.Println("接收到数据", string(buf[:n]))
conn.Write([]byte("server echo"))
}
// 关闭监听服务,此时会释放端口
conn.Close()
// for {
// buf := make([]byte, 1024)
// n, err := conn.Read(buf)
// if err != nil {
// log.Println(err)
// break
// }
// log.Println("接收到数据", string(buf[:n]))
// conn.Write([]byte("server echo"))
// }
// // 关闭监听服务,此时会释放端口
// conn.Close()
//}()
go func() { // echo server
conn := tcpListen(s, proto, localPort)
conn.Read(nil)
}()
c := make(chan os.Signal)
@@ -162,7 +167,7 @@ func tcpListen(s *stack.Stack, proto tcpip.NetworkProtocolNumber, localPort int)
// 绑定IP和端口这里的IP地址为空表示绑定任何IP
// 此时就会调用端口管理器
if err := ep.Bind(tcpip.FullAddress{0, "", uint16(localPort)}, nil); err != nil {
if err := ep.Bind(tcpip.FullAddress{NIC: 0, Addr: "", Port: uint16(localPort)}, nil); err != nil {
log.Fatal("Bind failed: ", err)
}

View File

@@ -25,7 +25,8 @@ func main() {
panic(err)
}
send := []byte("hello world")
//send := []byte("hello world")
send := make([]byte, 1600)
if _, err := conn.Write(send); err != nil {
panic(err)
}

View File

@@ -1,6 +1,9 @@
package header
import "netstack/tcpip"
import (
"encoding/binary"
"netstack/tcpip"
)
/*
0 1 2 3
@@ -24,6 +27,50 @@ import "netstack/tcpip"
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
// TCPFields contains the fields of a TCP packet. It is used to describe the
// fields of a packet that needs to be encoded.
// tcp首部字段
type TCPFields struct {
// SrcPort is the "source port" field of a TCP packet.
SrcPort uint16
// DstPort is the "destination port" field of a TCP packet.
DstPort uint16
// SeqNum is the "sequence number" field of a TCP packet.
SeqNum uint32
// AckNum is the "acknowledgement number" field of a TCP packet.
AckNum uint32
// DataOffset is the "data offset" field of a TCP packet.
DataOffset uint8
// Flags is the "flags" field of a TCP packet.
Flags uint8
// WindowSize is the "window size" field of a TCP packet.
WindowSize uint16
// Checksum is the "checksum" field of a TCP packet.
Checksum uint16
// UrgentPointer is the "urgent pointer" field of a TCP packet.
UrgentPointer uint16
}
const (
srcPort = 0
dstPort = 2
seqNum = 4
ackNum = 8
dataOffset = 12
tcpFlags = 13
winSize = 14
tcpChecksum = 16
urgentPtr = 18
)
// TCP represents a TCP header stored in a byte array.
type TCP []byte
@@ -34,3 +81,13 @@ const (
// TCPProtocolNumber is TCP's transport protocol number.
TCPProtocolNumber tcpip.TransportProtocolNumber = 6
)
// SourcePort returns the "source port" field of the tcp header.
func (b TCP) SourcePort() uint16 {
return binary.BigEndian.Uint16(b[srcPort:])
}
// DestinationPort returns the "destination port" field of the tcp header.
func (b TCP) DestinationPort() uint16 {
return binary.BigEndian.Uint16(b[dstPort:])
}

View File

@@ -17,6 +17,7 @@ package fragmentation
import (
"container/heap"
"fmt"
"log"
"netstack/tcpip/buffer"
)
@@ -58,6 +59,7 @@ func (h *fragHeap) reassemble() (buffer.VectorisedView, error) {
curr := heap.Pop(h).(fragment)
views := curr.vv.Views()
size := curr.vv.Size()
log.Println(size)
if curr.offset != 0 {
return buffer.VectorisedView{}, fmt.Errorf("offset of the first packet is != 0 (%d)", curr.offset)
@@ -66,10 +68,11 @@ func (h *fragHeap) reassemble() (buffer.VectorisedView, error) {
for h.Len() > 0 {
curr := heap.Pop(h).(fragment)
if int(curr.offset) < size {
curr.vv.TrimFront(size - int(curr.offset))
curr.vv.TrimFront(size - int(curr.offset)) // 截取重复的部分
} else if int(curr.offset) > size {
return buffer.VectorisedView{}, fmt.Errorf("packet has a hole, expected offset %d, got %d", size, curr.offset)
}
// curr.offset == size 没有空洞 紧密排布
size += curr.vv.Size()
views = append(views, curr.vv.Views()...)
}

View File

@@ -38,12 +38,12 @@
4. 数据偏移 占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。这个字段实际上是指出 TCP 报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的,但应注意,“数据偏移”的单位是 4 个字节,由于 4 位二进制数能表示的最大十进制数字是 15因此数据偏移的最大值是 60 字节。
5. 保留 占 6 位,保留为今后使用,但目前应置为 0。
6. 控制报文标志
- 紧急URGURGent 当 URG=1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快发送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行,因此用户从键盘发出中断命令。如果不使用紧急数据,那么这两个字符将存储在接收 TCP 的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了很多时间。 当 URG 置为 1 时,发送应用进程就告诉发送方的 TCP 有紧急数据要传送。于是发送方 TCP 就把紧急数据插入到本报文段数据的最前面而在紧急数据后面的数据仍然是普通数据。这时要与首部中紧急指针Urgent Pointer字段配合使用。
- 确认ACKACKnowledgment 仅当 ACK=1 时确认号字段才有效,当 ACK=0 时确认号无效。TCP 规定,在连接建立后所有的传送的报文段都必须把 ACK 置为 1。
- 推送 PSHPuSH 当两个应用进程进行交互式的通信时有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下TCP 就可以使用推送push操作。这时发送方 TCP 把 PSH 置为 1并立即创建一个报文段发送出去。接收方 TCP 收到 PSH=1 的报文段,就尽快地交付接收应用进程。
- 复位RSTReSeT 当 RST=1 时,表名 TCP 连接中出现了严重错误如由于主机崩溃或其他原因必须释放连接然后再重新建立传输连接。RST 置为 1 用来拒绝一个非法的报文段或拒绝打开一个连接。
- 同步SYNSYNchronization 在连接建立时用来同步序号。当 SYN=1 而 ACK=0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN=1 和 ACK=1因此 SYN 置为 1 就表示这是一个连接请求或连接接受报文。
- 终止FINFINis意思是“完”“终” 用来释放一个连接。当 FIN=1 时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。
- **紧急URGURGent** 当 URG=1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快发送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行,因此用户从键盘发出中断命令。如果不使用紧急数据,那么这两个字符将存储在接收 TCP 的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了很多时间。 当 URG 置为 1 时,发送应用进程就告诉发送方的 TCP 有紧急数据要传送。于是发送方 TCP 就把紧急数据插入到本报文段数据的最前面而在紧急数据后面的数据仍然是普通数据。这时要与首部中紧急指针Urgent Pointer字段配合使用。
- **确认ACKACKnowledgment** 仅当 ACK=1 时确认号字段才有效,当 ACK=0 时确认号无效。TCP 规定,在连接建立后所有的传送的报文段都必须把 ACK 置为 1。
- **推送 PSHPuSH** 当两个应用进程进行交互式的通信时有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下TCP 就可以使用推送push操作。这时发送方 TCP 把 PSH 置为 1并立即创建一个报文段发送出去。接收方 TCP 收到 PSH=1 的报文段,就尽快地交付接收应用进程。
- **复位RSTReSeT** 当 RST=1 时,表名 TCP 连接中出现了严重错误如由于主机崩溃或其他原因必须释放连接然后再重新建立传输连接。RST 置为 1 用来拒绝一个非法的报文段或拒绝打开一个连接。
- **同步SYNSYNchronization** 在连接建立时用来同步序号。当 SYN=1 而 ACK=0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN=1 和 ACK=1因此 SYN 置为 1 就表示这是一个连接请求或连接接受报文。
- **终止FINFINis意思是“完”“终”** 用来释放一个连接。当 FIN=1 时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。
7. 窗口 占 2 字节,窗口值是[02^16-1]之间的整数。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据,作为流量控制的依据,后面会详细介绍。 总之:窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化。
8. 检验和 占 2 字节,检验和字段检验的范围包括首部和数据这两部分。和 UDP 用户数据报一样,在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。伪首部的格式和 UDP 用户数据报的伪首部一样。但应把伪首部第 4 个字段中的 17 改为 6TCP 的协议号是 6把第 5 字段中的 UDP 中的长度改为 TCP 长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。若使用 IPv6则相应的伪首部也要改变。
9. 紧急指针 占 2 字节,紧急指针仅在 URG=1 时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据) 。因此在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时TCP 就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为 0 时也可以发送紧急数据。

View File

@@ -1,6 +1,134 @@
package tcp
import (
"log"
"netstack/tcpip"
"netstack/tcpip/buffer"
"netstack/tcpip/stack"
"netstack/waiter"
"sync"
)
// tcp状态机的状态
type endpointState int
// tcp 状态机的各种状态
const (
stateInitial endpointState = iota
stateBound
stateListen
stateConnecting
stateConnected
stateClosed
stateError
)
// endpoint 表示TCP端点。该结构用作端点用户和协议实现之间的接口;让并发goroutine调用端点是合法的
// 它们是正确同步的。然而协议实现在单个goroutine中运行。
type endpoint struct {
stack *stack.Stack // 网络协议栈
netProto tcpip.NetworkProtocolNumber // 网络协议号 ipv4 ipv6
waiterQueue *waiter.Queue // 事件驱动机制
// TODO 需要添加
// The following fields are protected by the mutex.
mu sync.RWMutex
id stack.TransportEndpointID
state endpointState
isPortReserved bool
isRegistered bool
boundNICID tcpip.NICID
route stack.Route
v6only bool
isConnectNotified bool
}
func newEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQueue *waiter.Queue) *endpoint {
e := &endpoint{
stack: stack,
netProto: netProto,
waiterQueue: waiterQueue,
}
// TODO 需要添加
log.Println("新建tcp端")
return e
}
func (e *endpoint) Close() {
}
func (e *endpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
return nil, tcpip.ControlMessages{}, nil
}
func (e *endpoint) Write(tcpip.Payload, tcpip.WriteOptions) (uintptr, <-chan struct{}, *tcpip.Error) {
return 0, nil, nil
}
func (e *endpoint) Peek([][]byte) (uintptr, tcpip.ControlMessages, *tcpip.Error) {
return 0, tcpip.ControlMessages{}, nil
}
func (e *endpoint) Connect(address tcpip.FullAddress) *tcpip.Error {
return nil
}
func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
return nil
}
func (e *endpoint) Listen(backlog int) *tcpip.Error {
return nil
}
func (e *endpoint) Accept() (tcpip.Endpoint, *waiter.Queue, *tcpip.Error) {
return nil, nil, nil
}
// Bind binds the endpoint to a specific local port and optionally address.
// 将端点绑定到特定的本地端口和可选的地址。
func (e *endpoint) Bind(address tcpip.FullAddress, commit func() *tcpip.Error) *tcpip.Error {
log.Println("绑定一个tcp端口")
return nil
}
func (e *endpoint) GetLocalAddress() (tcpip.FullAddress, *tcpip.Error) {
e.mu.RLock()
defer e.mu.RUnlock()
return tcpip.FullAddress{
Addr: e.id.LocalAddress,
Port: e.id.LocalPort,
NIC: e.boundNICID,
}, nil
}
func (e *endpoint) GetRemoteAddress() (tcpip.FullAddress, *tcpip.Error) {
e.mu.RLock()
defer e.mu.RUnlock()
if e.state != stateConnected {
return tcpip.FullAddress{}, tcpip.ErrNotConnected
}
return tcpip.FullAddress{
Addr: e.id.RemoteAddress,
Port: e.id.RemotePort,
NIC: e.boundNICID,
}, nil
}
func (e *endpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
return waiter.EventErr
}
func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error {
return nil
}
func (e *endpoint) GetSockOpt(opt interface{}) *tcpip.Error {
return nil
}

View File

@@ -1,6 +1,12 @@
package tcp
import "netstack/tcpip/header"
import (
"netstack/tcpip"
"netstack/tcpip/buffer"
"netstack/tcpip/header"
"netstack/tcpip/stack"
"netstack/waiter"
)
const (
// ProtocolName is the string representation of the tcp protocol name.
@@ -17,3 +23,47 @@ const (
// MaxBufferSize is the largest size a receive and send buffer can grow to.
maxBufferSize = 4 << 20 // 4MB
)
type protocol struct{}
// Number returns the tcp protocol number.
func (*protocol) Number() tcpip.TransportProtocolNumber {
return ProtocolNumber
}
// NewEndpoint creates a new tcp endpoint.
func (*protocol) NewEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQueue *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) {
return newEndpoint(stack, netProto, waiterQueue), nil
}
// ParsePorts returns the source and destination ports stored in the given tcp
// packet.
func (*protocol) ParsePorts(v buffer.View) (src, dst uint16, err *tcpip.Error) {
h := header.TCP(v)
return h.SourcePort(), h.DestinationPort(), nil
}
// MinimumPacketSize returns the minimum valid tcp packet size.
func (*protocol) MinimumPacketSize() int {
return header.TCPMinimumSize
}
func (*protocol) HandleUnknownDestinationPacket(r *stack.Route, id stack.TransportEndpointID, vv buffer.VectorisedView) bool {
return false
}
// SetOption implements TransportProtocol.SetOption.
func (p *protocol) SetOption(option interface{}) *tcpip.Error {
return nil
}
// Option implements TransportProtocol.Option.
func (p *protocol) Option(option interface{}) *tcpip.Error {
return nil
}
func init() {
stack.RegisterTransportProtocolFactory(ProtocolName, func() stack.TransportProtocol {
return &protocol{}
})
}