diff --git a/cmd/port/main.go b/cmd/port/main.go new file mode 100644 index 0000000..948ca37 --- /dev/null +++ b/cmd/port/main.go @@ -0,0 +1,162 @@ +package main + +import ( + "flag" + "log" + "net" + "netstack/tcpip" + "netstack/tcpip/link/fdbased" + "netstack/tcpip/link/tuntap" + "netstack/tcpip/network/arp" + "netstack/tcpip/network/ipv4" + "netstack/tcpip/network/ipv6" + "netstack/tcpip/stack" + "netstack/tcpip/transport/udp" + "netstack/waiter" + "os" + "strconv" + "strings" +) + +var mac = flag.String("mac", "01:01:01:01:01:01", "mac address to use in tap device") + +func main() { + flag.Parse() + if len(flag.Args()) != 3 { + log.Fatal("Usage: ", os.Args[0], " port") + } + + log.SetFlags(log.Lshortfile | log.LstdFlags) + tapName := flag.Arg(0) + listeAddr := flag.Arg(1) + portName := flag.Arg(2) + + log.Printf("tap: %v, listeAddr: %v, portName: %v", tapName, listeAddr, portName) + + // Parse the mac address. + maddr, err := net.ParseMAC(*mac) + if err != nil { + log.Fatalf("Bad MAC address: %v", *mac) + } + + parsedAddr := net.ParseIP(listeAddr) + + // 解析地址ip地址,ipv4或者ipv6地址都支持 + var addr tcpip.Address + var proto tcpip.NetworkProtocolNumber + if parsedAddr.To4() != nil { + addr = tcpip.Address(parsedAddr.To4()) + proto = ipv4.ProtocolNumber + } else if parsedAddr.To16() != nil { + addr = tcpip.Address(parsedAddr.To16()) + proto = ipv6.ProtocolNumber + } else { + log.Fatalf("Unknown IP type: %v", parsedAddr) + } + + localPort, err := strconv.Atoi(portName) + if err != nil { + log.Fatalf("Unable to convert port %v: %v", portName, err) + } + + // 虚拟网卡配置 + conf := &tuntap.Config{ + Name: tapName, + Mode: tuntap.TAP, + } + + var fd int + // 新建虚拟网卡 + fd, err = tuntap.NewNetDev(conf) + if err != nil { + log.Fatal(err) + } + + // 启动tap网卡 + _ = tuntap.SetLinkUp(tapName) + // 设置tap网卡IP地址 + _ = tuntap.AddIP(tapName, listeAddr) + + // 抽象网卡的文件接口 + linkID := fdbased.New(&fdbased.Options{ + FD: fd, + MTU: 1500, + Address: tcpip.LinkAddress(maddr), + }) + + // 新建相关协议的协议栈 + s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, + []string{ /*tcp.ProtocolName, */ udp.ProtocolName}, stack.Options{}) + + // 新建抽象的网卡 + if err := s.CreateNamedNIC(1, "vnic1", linkID); 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, + }, + }) + + // 同时监听tcp和udp localPort端口 + //tcpEp := tcpListen(s, proto, localPort) + udpEp := udpListen(s, proto, localPort) + // 关闭监听服务,此时会释放端口 + //tcpEp.Close() + udpEp.Close() +} + +//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 +//} + +func udpListen(s *stack.Stack, proto tcpip.NetworkProtocolNumber, localPort int) tcpip.Endpoint { + var wq waiter.Queue + // 新建一个udp端 + ep, err := s.NewEndpoint(udp.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) + } + + // 注意UDP是无连接的,它不需要Listen + return ep +} diff --git a/cmd/tcpclient/main.go b/cmd/tcpclient/main.go new file mode 100644 index 0000000..fd1577a --- /dev/null +++ b/cmd/tcpclient/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "net" +) + +func main() { + _, err := net.Dial("tcp", "192.168.1.1:9999") + if err != nil { + fmt.Println("err : ", err) + return + } +} diff --git a/cmd/tcpserver/main.go b/cmd/tcpserver/main.go new file mode 100644 index 0000000..80749f1 --- /dev/null +++ b/cmd/tcpserver/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "net" + "os" +) + +func main() { + _, err := net.Listen("tcp", "192.168.1.1:9999") + if err != nil { + fmt.Println("Error listening:", err) + os.Exit(1) + } +} diff --git a/tcpip/header/udp.go b/tcpip/header/udp.go new file mode 100644 index 0000000..5a936e6 --- /dev/null +++ b/tcpip/header/udp.go @@ -0,0 +1,38 @@ +package header + +import "netstack/tcpip" + +const ( + udpSrcPort = 0 + udpDstPort = 2 + udpLength = 4 + udpChecksum = 6 +) + +// UDPFields contains the fields of a UDP packet. It is used to describe the +// fields of a packet that needs to be encoded. +// udp 首部字段 +type UDPFields struct { + // SrcPort is the "source port" field of a UDP packet. + SrcPort uint16 + + // DstPort is the "destination port" field of a UDP packet. + DstPort uint16 + + // Length is the "length" field of a UDP packet. + Length uint16 + + // Checksum is the "checksum" field of a UDP packet. + Checksum uint16 +} + +// UDP represents a UDP header stored in a byte array. +type UDP []byte + +const ( + // UDPMinimumSize is the minimum size of a valid UDP packet. + UDPMinimumSize = 8 + + // UDPProtocolNumber is UDP's transport protocol number. + UDPProtocolNumber tcpip.TransportProtocolNumber = 17 +) diff --git a/tcpip/ports/README.md b/tcpip/ports/README.md index de91e01..1013c32 100644 --- a/tcpip/ports/README.md +++ b/tcpip/ports/README.md @@ -5,6 +5,41 @@ 运行在计算机中的进程是用进程标识符来标志的。一开始我们可能会想到根据进程标识符来区分数据包给哪个进程,但是因为在因特网上使用的计算机的操作系统种类很多,而不同的操作系统又使用不同格式的进程标识符,因此发送方非常可能无法识别其他机器上的进程。为了使运行不同操作系统的计算机的应用进程能够互相通信,就必须用统一的方法对 TCP/IP 体系的应用进程进行标志,因此 TCP/IP 体系的传输层端口被提了出来。 +![img](https://doc.shiyanlou.com/document-uid949121labid10418timestamp1555484076771.png) + TCP/IP 协议在运输层使用协议端口号(protocol port number),或通常简称为端口(port),端口统一用一个 16 位端口号进行标志。端口号只具有本地意义,即端口号只是为了标志本计算机应用层中的各进程。在因特网中不同计算机的相同端口号是没有联系的。虽然通信的终点是应用进程,但我们可以把端口想象是通信的终点,因为我们只要把要传送的报文交到目的主机的某一个合适的目的端口,剩下的工作(即最后交付目的进程)就由 TCP 来完成。 -如果把 IP 地址比作一栋楼房,端口号就是这栋楼房里各个房子的房间号。数据包来到主机这栋大楼,会查看是个房间号,再把数据发给相应的房间。端口号只有整数,范围是从 0 到 65535(2^16-1),其中 0 一般作为保留端口,表示让系统自动分配端口。 \ No newline at end of file +如果把 IP 地址比作一栋楼房,端口号就是这栋楼房里各个房子的房间号。数据包来到主机这栋大楼,会查看是个房间号,再把数据发给相应的房间。端口号只有整数,范围是从 0 到 65535(2^16-1),其中 0 一般作为保留端口,表示让系统自动分配端口。 + +最常见的是 TCP 端口和 UDP 端口。由于 TCP 和 UDP 两个协议是独立的,因此各自的端口号也相互独立,比如 TCP 有 235 端口,UDP 也可以有 235 端口,两者并不冲突。 + +TCP 和 UDP 协议首部的前四个字节都是用来表示端口的,分别表示源端口和目的端口,各占 2 个字节,详细的 TCP、UDP 协议头部会在下面的文章中讲到。 + +![img](https://doc.shiyanlou.com/document-uid949121labid10418timestamp1555484120164.png) + +1. 周知端口(Well Known Ports) 周知端口是众所周知的端口号,范围从 0 到 1023,其中 80 端口分配给 WWW 服务,21 端口分配给 FTP 服务等。我们在 IE 的地址栏里输入一个网址的时候是不必指定端口号的,因为在默认情况下 WWW 服务的端口是"80"。网络服务是可以使用其他端口号的,如果不是默认的端口号则应该在 地址栏上指定端口号,方法是在地址后面加上冒号":",再加上端口号。比如使用"8080"作为 WWW 服务的端口,则需要在地址栏里输入"网址:8080"。但是有些系统协议使用固定的端口号,它是不能被改变的,比如 139 端口专门用于 NetBIOS 与 TCP/IP 之间的通信,不能手动改变。 + +2. 注册端口(Registered Ports) 端口 1024 到 49151,分配给用户进程或应用程序。这些进程主要是用户选择安装的一些应用程序,而不是已经分配好了公认端口的常用程序。这些端口在没有被服务器资源占用的时候,可以用用户端动态选用为源端口。 + +3. 动态端口(Dynamic Ports) 动态端口的范围是从 49152 到 65535。之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。比如本地想和远端建立 TCP 连接,如果没有指定本地源端口,系统就会给你自动分配一个未占用的源端口,这个端口值就是动态的,当你断开再次建立连接的时候,很有可能你的源端口和上次得到的端口不一样。 + +### 一些常见的端口号及其用途如下: + +1. TCP21 端口:FTP 文件传输服务 +2. TCP22 端口:SSH 安全外壳协议 +3. TCP23 端口:TELNET 终端仿真服务 +4. TCP25 端口:SMTP 简单邮件传输服务 +5. UDP53 端口:DNS 域名解析服务 +6. UDP67 端口:DHCP 的服务端端口 +7. UDP68 端口:DHCP 的客户端端口 +8. TCP80 端口:HTTP 超文本传输服务 +9. TCP110 端口:POP3“邮局协议版本 3”使用的端口 +10. TCP443 端口:HTTPS 加密的超文本传输服务 + +端口在 tcpip 协议栈中算是比较简单的概念,提出端口的本质需求是希望能将数据包准确的发给某台主机上的进程,实现进程与进程之间的通信。 + +协议栈全局管理端口,一个端口被分配以后,不允许给其他进程使用,但是要注意的是端口是网络层协议地址+传输层协议号+端口号来区分的,比如: + +1. ipv4 的 tcp 80 端口和 ipv4 的 udp 80 端口不会冲突。 +2. 如果你主机有两个 ip 地址 ip1 和 ip2,那么你同时监听 ip1:80 和 ip2:80 不会冲突。 +3. ipv4 的 tcp 80 端口和 ipv6 的 tcp 80 端口不会冲突。 \ No newline at end of file diff --git a/tcpip/ports/ports.go b/tcpip/ports/ports.go index 032a191..3134b31 100644 --- a/tcpip/ports/ports.go +++ b/tcpip/ports/ports.go @@ -1,10 +1,19 @@ package ports import ( + "math" + "math/rand" "netstack/tcpip" "sync" ) +const ( + // 临时端口的最小值 + FirstEphemeral = 16000 + + anyIPAddress tcpip.Address = "" +) + // 端口的唯一标识 : 网络层协议-传输层协议-端口号 type portDescriptor struct { network tcpip.NetworkProtocolNumber @@ -12,12 +21,85 @@ type portDescriptor struct { port uint16 } -// 一个IP地址的集合 -type bindAddresses map[tcpip.Address]struct{} - -// 管理端口的对象 由他来保留和释放端口 +// PortManager 管理端口的对象 由他来保留和释放端口 type PortManager struct { mu sync.RWMutex // 用一个map接口来保存被占用的端口 + // port:ips ipv4-tcp-80:[192.168.1.1, 192.168.1.2] + // ipv4-udp-9999:[192.168.10.1, 192.168.10.2] allocatedPorts map[portDescriptor]bindAddresses } + +// 一个IP地址的集合 +type bindAddresses map[tcpip.Address]struct{} + +func (b bindAddresses) isAvailable(addr tcpip.Address) bool { + if addr == anyIPAddress { + return len(b) == 0 + } + + if _, ok := b[anyIPAddress]; ok { + return false + } + + if _, ok := b[addr]; ok { + return false + } + return true +} + +// NewPortManager 新建一个端口管理器 +func NewPortManager() *PortManager { + return &PortManager{ + allocatedPorts: make(map[portDescriptor]bindAddresses), + } +} + +// PickEphemeralPort 从端口管理器中随机分配一个端口,并调用testPort来检测是否可用 +func (s *PortManager) PickEphemeralPort(testPort func(p uint16) (bool, *tcpip.Error)) (port uint16, err *tcpip.Error) { + count := uint16(math.MaxUint16 - FirstEphemeral + 1) + offset := uint16(rand.Int31n(int32(count))) + + for i := uint16(0); i < count; i++ { + port = FirstEphemeral + (offset+i)%count + ok, err := testPort(port) + if err != nil { + return 0, nil + } + if ok { + return port, nil + } + } + return 0, tcpip.ErrNoPortAvailable +} + +// IsPortAvailable 根据参数判断该端口号是否已经被占用了 +func (s *PortManager) IsPortAvailable(networks []tcpip.NetworkProtocolNumber, + transport tcpip.TransportProtocolNumber, addr tcpip.Address, port uint16) bool { + s.mu.Lock() + defer s.mu.Unlock() + return s.isPortAvailableLocked(networks, transport, addr, port) +} + +// 根据参数判断该端口号是否被占用 +func (s *PortManager) isPortAvailableLocked(networks []tcpip.NetworkProtocolNumber, + transport tcpip.TransportProtocolNumber, addr tcpip.Address, port uint16) bool { + for _, network := range networks { // 遍历网络协议 + desc := portDescriptor{network: network, transport: transport, port: port} // 构造端口描述符 + if addrs, ok := s.allocatedPorts[desc]; ok { // 检查端口描述符绑定的ip集合 + if !addrs.isAvailable(addr) { // 该集合中已经有这个ip + return false + } + } + } + return true +} + +// ReservePort 将端口和IP地址绑定在一起,这样别的程序就无法使用已经被绑定的端口。 +// 如果传入的端口不为0,那么会尝试绑定该端口,若该端口没有被占用,那么绑定成功。 +// 如果传人的端口等于0,那么就是告诉协议栈自己分配端口,端口管理器就会随机返回一个端口。 +func (s *PortManager) ReservePort(networks []tcpip.NetworkProtocolNumber, + transport tcpip.TransportProtocolNumber, + addr tcpip.Address, port uint16) (reservedPort uint16, err *tcpip.Error) { + return 0, nil +} diff --git a/tcpip/stack/nic.go b/tcpip/stack/nic.go index 58ab3be..3fe526d 100644 --- a/tcpip/stack/nic.go +++ b/tcpip/stack/nic.go @@ -344,7 +344,7 @@ func (n *NIC) getRef(protocol tcpip.NetworkProtocolNumber, dst tcpip.Address) *r return nil } -// 当 NIC 从物理接口接收数据包时,将调用函数 DeliverNetworkPacket,用来分发网络层数据包。 +// DeliverNetworkPacket 当 NIC 从物理接口接收数据包时,将调用函数 DeliverNetworkPacket,用来分发网络层数据包。 // 比如 protocol 是 arp 协议号,那么会找到arp.HandlePacket来处理数据报。 // 简单来说就是根据网络层协议和目的地址来找到相应的网络层端,将网络层数据发给它, // 当前实现的网络层协议有 arp、ipv4 和 ipv6。 @@ -387,9 +387,13 @@ func (n *NIC) DeliverNetworkPacket(linkEP LinkEndpoint, remoteLinkAddr, localLin // DeliverTransportPacket delivers packets to the appropriate // transport protocol endpoint. func (n *NIC) DeliverTransportPacket(r *Route, protocol tcpip.TransportProtocolNumber, vv buffer.VectorisedView) { - log.Println("准备分发传输层数据报") - hdr := buffer.NewPrependable(header.EthernetMinimumSize + header.IPv4MinimumSize) - r.ref.ep.WritePacket(r, hdr, vv, protocol, 255) + // 先查找协议栈是否注册了该传输层协议 + _, ok := n.stack.transportProtocols[protocol] + if !ok { + n.stack.stats.UnknownProtocolRcvdPackets.Increment() + return + } + log.Println("准备分发传输层数据报", n.stack.transportProtocols) } diff --git a/tcpip/stack/registration.go b/tcpip/stack/registration.go index e9d6176..55abc6e 100644 --- a/tcpip/stack/registration.go +++ b/tcpip/stack/registration.go @@ -4,6 +4,7 @@ import ( "netstack/sleep" "netstack/tcpip" "netstack/tcpip/buffer" + "netstack/waiter" "sync" ) @@ -88,14 +89,16 @@ type NetworkDispatcher interface { type LinkEndpointCapabilities uint -// type TransportProtocolFactory func() TransportProtocol TODO +// TransportProtocolFactory 传输层实现工厂 +type TransportProtocolFactory func() TransportProtocol +// NetworkProtocolFactory 网络层实现工厂 type NetworkProtocolFactory func() NetworkProtocol var ( // 以下两个map需要在init函数中注册 - // 传输层协议的注册存储结构 TODO - //transportProtocols = make(map[string]TransportProtocolFactory) + // 传输层协议的注册存储结构 + transportProtocols = make(map[string]TransportProtocolFactory) // 网络层协议的注册存储结构 networkProtocols = make(map[string]NetworkProtocolFactory) @@ -191,17 +194,49 @@ const ( ControlUnknown ) -// TODO 需要解读 +// TransportEndpoint 传输层实现接口 type TransportEndpoint interface { HandlePacket(r *Route, id TransportEndpointID, vv buffer.VectorisedView) HandleControlPacker(id TransportEndpointID, typ ControlType, extra uint32, vv buffer.VectorisedView) } -// 传输层协议 TCP OR UDP +// TransportProtocol 传输层协议 TCP OR UDP type TransportProtocol interface { + // Number returns the transport protocol number. + Number() tcpip.TransportProtocolNumber + + // NewEndpoint creates a new endpoint of the transport protocol. + NewEndpoint(stack *Stack, netProto tcpip.NetworkProtocolNumber, waitQueue *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) + + // MinimumPacketSize returns the minimum valid packet size of this + // transport protocol. The stack automatically drops any packets smaller + // than this targeted at this protocol. + MinimumPacketSize() int + + // ParsePorts returns the source and destination ports stored in a + // packet of this protocol. + ParsePorts(v buffer.View) (src, dst uint16, err *tcpip.Error) + + // HandleUnknownDestinationPacket handles packets targeted at this + // protocol but that don't match any existing endpoint. For example, + // it is targeted at a port that have no listeners. + // + // The return value indicates whether the packet was well-formed (for + // stats purposes only). + HandleUnknownDestinationPacket(r *Route, id TransportEndpointID, vv buffer.VectorisedView) bool + + // SetOption allows enabling/disabling protocol specific features. + // SetOption returns an error if the option is not supported or the + // provided option value is invalid. + SetOption(option interface{}) *tcpip.Error + + // Option allows retrieving protocol specific option values. + // Option returns an error if the option is not supported or the + // provided option value is invalid. + Option(option interface{}) *tcpip.Error } -// 传输层调度器 +// TransportDispatcher 传输层调度器 type TransportDispatcher interface { // DeliverTransportPacket delivers packets to the appropriate // transport protocol endpoint. @@ -213,12 +248,17 @@ type TransportDispatcher interface { trans tcpip.TransportProtocolNumber, typ ControlType, extra uint32, vv buffer.VectorisedView) } -// 注册一个新的网络协议工厂 +// RegisterTransportProtocolFactory 注册一个新的传输层协议工厂 +func RegisterTransportProtocolFactory(name string, p TransportProtocolFactory) { + transportProtocols[name] = p +} + +// RegisterNetworkProtocolFactory 注册一个新的网络协议工厂 func RegisterNetworkProtocolFactory(name string, p NetworkProtocolFactory) { networkProtocols[name] = p } -// 注册一个链路层设备 +// RegisterLinkEndpoint 注册一个链路层设备 func RegisterLinkEndpoint(linkEP LinkEndpoint) tcpip.LinkEndpointID { linkEPMu.Lock() defer linkEPMu.Unlock() diff --git a/tcpip/stack/stack.go b/tcpip/stack/stack.go index a6505c6..fb2fcd7 100644 --- a/tcpip/stack/stack.go +++ b/tcpip/stack/stack.go @@ -4,7 +4,9 @@ import ( "log" "netstack/sleep" "netstack/tcpip" + "netstack/tcpip/buffer" "netstack/tcpip/ports" + "netstack/waiter" "sync" "time" ) @@ -26,14 +28,17 @@ type TcpEndpointState struct { // TODO 需要添加 } +// 传输层协议状态机 包含传输层协议以及默认处理方法 type transportProtocolState struct { + proto TransportProtocol + defaultHandler func(*Route, TransportEndpointID, buffer.VectorisedView) bool } // Stack 是一个网络堆栈,具有所有支持的协议、NIC 和路由表。 type Stack struct { transportProtocols map[tcpip.TransportProtocolNumber]*transportProtocolState // 各种传输层协议 networkProtocols map[tcpip.NetworkProtocolNumber]NetworkProtocol // 各种网络层协议 - linkAddrResolvers map[tcpip.NetworkProtocolNumber]LinkAddressResolver // 各种传输层协议 + linkAddrResolvers map[tcpip.NetworkProtocolNumber]LinkAddressResolver // 各种链接解析器 demux *transportDemuxer // 传输层的复用器 @@ -83,9 +88,9 @@ func New(network []string, transport []string, opts Options) *Stack { linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]LinkAddressResolver), nics: make(map[tcpip.NICID]*NIC), linkAddrCache: newLinkAddrCache(ageLimit, resolutionTimeout, resolutionAttempts), - //PortManager: ports.NewPortManager(), - clock: clock, - stats: opts.Stats.FillIn(), + PortManager: ports.NewPortManager(), + clock: clock, + stats: opts.Stats.FillIn(), } // 添加指定的网络端协议 必须已经在init中注册过 @@ -100,7 +105,17 @@ func New(network []string, transport []string, opts Options) *Stack { } // 添加指定的传输层协议 必已经在init中注册过 - // TODO + for _, name := range transport { + transProtoFactory, ok := transportProtocols[name] + if !ok { + continue + } + transProto := transProtoFactory() // 新建一个传输层协议 + s.transportProtocols[transProto.Number()] = &transportProtocolState{ + proto: transProto, + } + } + // TODO 添加传输层分流器 return s } @@ -140,6 +155,17 @@ func (s *Stack) GetRouteTable() []tcpip.Route { return append([]tcpip.Route(nil), s.routeTable...) } +// NewEndpoint 根据给定的网络层协议号和传输层协议号新建一个传输层实现 +func (s *Stack) NewEndpoint(transport tcpip.TransportProtocolNumber, + network tcpip.NetworkProtocolNumber, waiterQueue *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) { + t, ok := s.transportProtocols[transport] + if !ok { + return nil, tcpip.ErrUnknownProtocol + } + return t.proto.NewEndpoint(s, network, waiterQueue) +} + +// CreateNIC 根据给定的网卡号 和 链路层设备号 创建一个网卡对象 func (s *Stack) CreateNIC(id tcpip.NICID, linkEP tcpip.LinkEndpointID) *tcpip.Error { return s.createNIC(id, "", linkEP, true) } diff --git a/tcpip/tcpip.go b/tcpip/tcpip.go index c0bb0de..5ed90fc 100644 --- a/tcpip/tcpip.go +++ b/tcpip/tcpip.go @@ -3,6 +3,8 @@ package tcpip import ( "errors" "fmt" + "netstack/tcpip/buffer" + "netstack/waiter" "reflect" "strings" "sync/atomic" @@ -76,13 +78,6 @@ type Address string type AddressMask string -// 传输层的完整地址 -type FullAddress struct { - NIC NICID // NICID - Addr Address // IP Address - Port uint16 // transport Port -} - func (a AddressMask) String() string { return Address(a).String() } @@ -176,6 +171,178 @@ type NetworkProtocolNumber uint32 type NICID int32 +// ShutdownFlags represents flags that can be passed to the Shutdown() method +// of the Endpoint interface. +type ShutdownFlags int + +// Values of the flags that can be passed to the Shutdown() method. They can +// be OR'ed together. +const ( + ShutdownRead ShutdownFlags = 1 << iota + ShutdownWrite +) + +// FullAddress 传输层的完整地址 +type FullAddress struct { + NIC NICID // NICID + Addr Address // IP Address + Port uint16 // transport Port +} + +func (fa FullAddress) String() string { + return fmt.Sprintf("%d:%s:%d", fa.NIC, fa.Addr, fa.Port) +} + +// Payload provides an interface around data that is being sent to an endpoint. +// This allows the endpoint to request the amount of data it needs based on +// internal buffers without exposing them. 'p.Get(p.Size())' reads all the data. +type Payload interface { + // Get returns a slice containing exactly 'min(size, p.Size())' bytes. + Get(size int) ([]byte, *Error) + + // Size returns the payload size. + Size() int +} + +// SlicePayload 实现了 Payload +type SlicePayload []byte + +// Get implements Payload. +func (s SlicePayload) Get(size int) ([]byte, *Error) { + if size > s.Size() { + size = s.Size() + } + return s[:size], nil +} + +// Size implements Payload. +func (s SlicePayload) Size() int { + return len(s) +} + +// A ControlMessages contains socket control messages for IP sockets. +// +// +stateify savable +type ControlMessages struct { + // HasTimestamp indicates whether Timestamp is valid/set. + HasTimestamp bool + + // Timestamp is the time (in ns) that the last packed used to create + // the read data was received. + Timestamp int64 +} + +// Endpoint is the interface implemented by transport protocols (e.g., tcp, udp) +// that exposes functionality like read, write, connect, etc. to users of the +// networking stack. +// 传输层接口 +type Endpoint interface { + // Close puts the endpoint in a closed state and frees all resources + // associated with it. + Close() + + // Read reads data from the endpoint and optionally returns the sender. + // + // This method does not block if there is no data pending. It will also + // either return an error or data, never both. + // + // A timestamp (in ns) is optionally returned. A zero value indicates + // that no timestamp was available. + Read(*FullAddress) (buffer.View, ControlMessages, *Error) + + // Write writes data to the endpoint's peer. This method does not block if + // the data cannot be written. + // + // Unlike io.Writer.Write, Endpoint.Write transfers ownership of any bytes + // successfully written to the Endpoint. That is, if a call to + // Write(SlicePayload{data}) returns (n, err), it may retain data[:n], and + // the caller should not use data[:n] after Write returns. + // + // Note that unlike io.Writer.Write, it is not an error for Write to + // perform a partial write. + // + // For UDP and Ping sockets if address resolution is required, + // ErrNoLinkAddress and a notification channel is returned for the caller to + // block. Channel is closed once address resolution is complete (success or + // not). The channel is only non-nil in this case. + Write(Payload, WriteOptions) (uintptr, <-chan struct{}, *Error) + + // Peek reads data without consuming it from the endpoint. + // + // This method does not block if there is no data pending. + // + // A timestamp (in ns) is optionally returned. A zero value indicates + // that no timestamp was available. + Peek([][]byte) (uintptr, ControlMessages, *Error) + + // Connect connects the endpoint to its peer. Specifying a NIC is + // optional. + // + // There are three classes of return values: + // nil -- the attempt to connect succeeded. + // ErrConnectStarted/ErrAlreadyConnecting -- the connect attempt started + // but hasn't completed yet. In this case, the caller must call Connect + // or GetSockOpt(ErrorOption) when the endpoint becomes writable to + // get the actual result. The first call to Connect after the socket has + // connected returns nil. Calling connect again results in ErrAlreadyConnected. + // Anything else -- the attempt to connect failed. + Connect(address FullAddress) *Error + + // Shutdown closes the read and/or write end of the endpoint connection + // to its peer. + Shutdown(flags ShutdownFlags) *Error + + // Listen puts the endpoint in "listen" mode, which allows it to accept + // new connections. + Listen(backlog int) *Error + + // Accept returns a new endpoint if a peer has established a connection + // to an endpoint previously set to listen mode. This method does not + // block if no new connections are available. + // + // The returned Queue is the wait queue for the newly created endpoint. + Accept() (Endpoint, *waiter.Queue, *Error) + + // Bind binds the endpoint to a specific local address and port. + // Specifying a NIC is optional. + // + // An optional commit function will be executed atomically with respect + // to binding the endpoint. If this returns an error, the bind will not + // occur and the error will be propagated back to the caller. + Bind(address FullAddress, commit func() *Error) *Error + + // GetLocalAddress returns the address to which the endpoint is bound. + GetLocalAddress() (FullAddress, *Error) + + // GetRemoteAddress returns the address to which the endpoint is + // connected. + GetRemoteAddress() (FullAddress, *Error) + + // Readiness returns the current readiness of the endpoint. For example, + // if waiter.EventIn is set, the endpoint is immediately readable. + Readiness(mask waiter.EventMask) waiter.EventMask + + // SetSockOpt sets a socket option. opt should be one of the *Option types. + SetSockOpt(opt interface{}) *Error + + // GetSockOpt gets a socket option. opt should be a pointer to one of the + // *Option types. + GetSockOpt(opt interface{}) *Error +} + +// WriteOptions contains options for Endpoint.Write. +type WriteOptions struct { + // If To is not nil, write to the given address instead of the endpoint's + // peer. + To *FullAddress + + // More has the same semantics as Linux's MSG_MORE. + More bool + + // EndOfRecord has the same semantics as Linux's MSG_EOR. + EndOfRecord bool +} + type Route struct { Destination Address // 目标地址 Mask AddressMask // 掩码 @@ -263,9 +430,57 @@ type IPStats struct { OutgoingPacketErrors *StatCounter } -type TCPStats struct{} +type TCPStats struct { + // ActiveConnectionOpenings is the number of connections opened successfully + // via Connect. + ActiveConnectionOpenings *StatCounter -type UDPStats struct{} + // PassiveConnectionOpenings is the number of connections opened + // successfully via Listen. + PassiveConnectionOpenings *StatCounter + + // FailedConnectionAttempts is the number of calls to Connect or Listen + // (active and passive openings, respectively) that end in an error. + FailedConnectionAttempts *StatCounter + + // ValidSegmentsReceived is the number of TCP segments received that the + // transport layer successfully parsed. + ValidSegmentsReceived *StatCounter + + // InvalidSegmentsReceived is the number of TCP segments received that + // the transport layer could not parse. + InvalidSegmentsReceived *StatCounter + + // SegmentsSent is the number of TCP segments sent. + SegmentsSent *StatCounter + + // ResetsSent is the number of TCP resets sent. + ResetsSent *StatCounter + + // ResetsReceived is the number of TCP resets received. + ResetsReceived *StatCounter +} + +type UDPStats struct { + // PacketsReceived is the number of UDP datagrams received via + // HandlePacket. + PacketsReceived *StatCounter + + // UnknownPortErrors is the number of incoming UDP datagrams dropped + // because they did not have a known destination port. + UnknownPortErrors *StatCounter + + // ReceiveBufferErrors is the number of incoming UDP datagrams dropped + // due to the receiving buffer being in an invalid state. + ReceiveBufferErrors *StatCounter + + // MalformedPacketsReceived is the number of incoming UDP datagrams + // dropped due to the UDP header being in a malformed state. + MalformedPacketsReceived *StatCounter + + // PacketsSent is the number of UDP datagrams sent via sendUDP. + PacketsSent *StatCounter +} func fillIn(v reflect.Value) { for i := 0; i < v.NumField(); i++ { diff --git a/tcpip/transport/udp/endpoint.go b/tcpip/transport/udp/endpoint.go new file mode 100644 index 0000000..4be3ce8 --- /dev/null +++ b/tcpip/transport/udp/endpoint.go @@ -0,0 +1,103 @@ +package udp + +import ( + "log" + "netstack/tcpip" + "netstack/tcpip/buffer" + "netstack/tcpip/stack" + "netstack/waiter" + "sync" +) + +// udp报文结构 当收到udp报文时 会用这个结构来保存udp报文数据 +type udpPacker struct { + // TODO 需要添加 +} + +type endpoint struct { + stack *stack.Stack // udp所依赖的用户协议栈 + netProto tcpip.NetworkProtocolNumber // udp网络协议号 ipv4/ipv6 + waiterQueue *waiter.Queue // TODO 需要解析 + + // TODO 需要解析 + // The following fields are used to manage the receive queue, and are + // protected by rcvMu. + rcvMu sync.Mutex + rcvReady bool + rcvList udpPacketList + rcvBufSizeMax int + rcvBufSize int + rcvClosed bool + rcvTimestamp bool + + // The following fields are protected by the mu mutex. + mu sync.RWMutex + // TODO 需要添加 +} + +func newEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, + waiterQueue *waiter.Queue) *endpoint { + log.Println("新建传输层实现") + return &endpoint{} +} + +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 address and port. +// Specifying a NIC is optional. +// Bind 将该UDP端绑定本地的一个IP+端口 +// 例如:绑定本地0.0.0.0的9000端口,那么其他机器给这台机器9000端口发消息,该UDP端就能收到消息了 +func (e *endpoint) Bind(address tcpip.FullAddress, commit func() *tcpip.Error) *tcpip.Error { + log.Println("绑定端口", address) + return nil +} + +func (e *endpoint) GetLocalAddress() (tcpip.FullAddress, *tcpip.Error) { + return tcpip.FullAddress{}, nil +} + +func (e *endpoint) GetRemoteAddress() (tcpip.FullAddress, *tcpip.Error) { + return tcpip.FullAddress{}, 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 +} diff --git a/tcpip/transport/udp/protocol.go b/tcpip/transport/udp/protocol.go new file mode 100644 index 0000000..613f236 --- /dev/null +++ b/tcpip/transport/udp/protocol.go @@ -0,0 +1,68 @@ +package udp + +import ( + "log" + "netstack/tcpip" + "netstack/tcpip/buffer" + "netstack/tcpip/header" + "netstack/tcpip/stack" + "netstack/waiter" +) + +const ( + // ProtocolName is the string representation of the udp protocol name. + ProtocolName = "udp" + + // ProtocolNumber is the udp protocol number. + ProtocolNumber = header.UDPProtocolNumber +) + +// tcpip.Endpoint 接口的UDP协议实现 +type protocol struct{} + +// Number returns the udp protocol number. +func (*protocol) Number() tcpip.TransportProtocolNumber { + return ProtocolNumber +} + +// NewEndpoint creates a new udp endpoint. +func (*protocol) NewEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, + waiterQueue *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) { + log.Println("新建udp传输层协议") + return newEndpoint(stack, netProto, waiterQueue), nil +} + +// MinimumPacketSize returns the minimum valid udp packet size. +func (*protocol) MinimumPacketSize() int { + return header.UDPMinimumSize +} + +// ParsePorts returns the source and destination ports stored in the given udp +// packet. +func (*protocol) ParsePorts(v buffer.View) (src, dst uint16, err *tcpip.Error) { + //h := header.UDP(v) + //return h.SourcePort(), h.DestinationPort(), nil + return 0, 0, nil +} + +// HandleUnknownDestinationPacket handles packets targeted at this protocol but +// that don't match any existing endpoint. +func (p *protocol) HandleUnknownDestinationPacket(*stack.Route, stack.TransportEndpointID, buffer.VectorisedView) bool { + return true +} + +// SetOption implements TransportProtocol.SetOption. +func (p *protocol) SetOption(option interface{}) *tcpip.Error { + return tcpip.ErrUnknownProtocolOption +} + +// Option implements TransportProtocol.Option. +func (p *protocol) Option(option interface{}) *tcpip.Error { + return tcpip.ErrUnknownProtocolOption +} + +func init() { + stack.RegisterTransportProtocolFactory(ProtocolName, func() stack.TransportProtocol { + return &protocol{} + }) +} diff --git a/tcpip/transport/udp/udp_packet_list.go b/tcpip/transport/udp/udp_packet_list.go new file mode 100644 index 0000000..c965ed8 --- /dev/null +++ b/tcpip/transport/udp/udp_packet_list.go @@ -0,0 +1,174 @@ +package udp + +// ElementMapper provides an identity mapping by default. +// +// This can be replaced to provide a struct that maps elements to linker +// objects, if they are not the same. An ElementMapper is not typically +// required if: Linker is left as is, Element is left as is, or Linker and +// Element are the same type. +type udpPacketElementMapper struct{} + +// linkerFor maps an Element to a Linker. +// +// This default implementation should be inlined. +// +//go:nosplit +func (udpPacketElementMapper) linkerFor(elem *udpPacket) *udpPacket { return elem } + +// List is an intrusive list. Entries can be added to or removed from the list +// in O(1) time and with no additional memory allocations. +// +// The zero value for List is an empty list ready to use. +// +// To iterate over a list (where l is a List): +// for e := l.Front(); e != nil; e = e.Next() { +// // do something with e. +// } +// +// +stateify savable +// udp数据报的双向链表结构 +type udpPacketList struct { + head *udpPacket + tail *udpPacket +} + +// Reset resets list l to the empty state. +func (l *udpPacketList) Reset() { + l.head = nil + l.tail = nil +} + +// Empty returns true iff the list is empty. +func (l *udpPacketList) Empty() bool { + return l.head == nil +} + +// Front returns the first element of list l or nil. +func (l *udpPacketList) Front() *udpPacket { + return l.head +} + +// Back returns the last element of list l or nil. +func (l *udpPacketList) Back() *udpPacket { + return l.tail +} + +// PushFront inserts the element e at the front of list l. +func (l *udpPacketList) PushFront(e *udpPacket) { + udpPacketElementMapper{}.linkerFor(e).SetNext(l.head) + udpPacketElementMapper{}.linkerFor(e).SetPrev(nil) + + if l.head != nil { + udpPacketElementMapper{}.linkerFor(l.head).SetPrev(e) + } else { + l.tail = e + } + + l.head = e +} + +// PushBack inserts the element e at the back of list l. +func (l *udpPacketList) PushBack(e *udpPacket) { + udpPacketElementMapper{}.linkerFor(e).SetNext(nil) + udpPacketElementMapper{}.linkerFor(e).SetPrev(l.tail) + + if l.tail != nil { + udpPacketElementMapper{}.linkerFor(l.tail).SetNext(e) + } else { + l.head = e + } + + l.tail = e +} + +// PushBackList inserts list m at the end of list l, emptying m. +func (l *udpPacketList) PushBackList(m *udpPacketList) { + if l.head == nil { + l.head = m.head + l.tail = m.tail + } else if m.head != nil { + udpPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head) + udpPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail) + + l.tail = m.tail + } + + m.head = nil + m.tail = nil +} + +// InsertAfter inserts e after b. +func (l *udpPacketList) InsertAfter(b, e *udpPacket) { + a := udpPacketElementMapper{}.linkerFor(b).Next() + udpPacketElementMapper{}.linkerFor(e).SetNext(a) + udpPacketElementMapper{}.linkerFor(e).SetPrev(b) + udpPacketElementMapper{}.linkerFor(b).SetNext(e) + + if a != nil { + udpPacketElementMapper{}.linkerFor(a).SetPrev(e) + } else { + l.tail = e + } +} + +// InsertBefore inserts e before a. +func (l *udpPacketList) InsertBefore(a, e *udpPacket) { + b := udpPacketElementMapper{}.linkerFor(a).Prev() + udpPacketElementMapper{}.linkerFor(e).SetNext(a) + udpPacketElementMapper{}.linkerFor(e).SetPrev(b) + udpPacketElementMapper{}.linkerFor(a).SetPrev(e) + + if b != nil { + udpPacketElementMapper{}.linkerFor(b).SetNext(e) + } else { + l.head = e + } +} + +// Remove removes e from l. +func (l *udpPacketList) Remove(e *udpPacket) { + prev := udpPacketElementMapper{}.linkerFor(e).Prev() + next := udpPacketElementMapper{}.linkerFor(e).Next() + + if prev != nil { + udpPacketElementMapper{}.linkerFor(prev).SetNext(next) + } else { + l.head = next + } + + if next != nil { + udpPacketElementMapper{}.linkerFor(next).SetPrev(prev) + } else { + l.tail = prev + } +} + +// Entry is a default implementation of Linker. Users can add anonymous fields +// of this type to their structs to make them automatically implement the +// methods needed by List. +// +// +stateify savable +type udpPacketEntry struct { + next *udpPacket + prev *udpPacket +} + +// Next returns the entry that follows e in the list. +func (e *udpPacketEntry) Next() *udpPacket { + return e.next +} + +// Prev returns the entry that precedes e in the list. +func (e *udpPacketEntry) Prev() *udpPacket { + return e.prev +} + +// SetNext assigns 'entry' as the entry that follows e in the list. +func (e *udpPacketEntry) SetNext(elem *udpPacket) { + e.next = elem +} + +// SetPrev assigns 'entry' as the entry that precedes e in the list. +func (e *udpPacketEntry) SetPrev(elem *udpPacket) { + e.prev = elem +}