diff --git a/cmd/arp/arp b/cmd/arp/arp new file mode 100644 index 0000000..fb45509 Binary files /dev/null and b/cmd/arp/arp differ diff --git a/cmd/arp/main.go b/cmd/arp/main.go new file mode 100644 index 0000000..ad4302c --- /dev/null +++ b/cmd/arp/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "flag" + "log" + "net" + "os" + + "netstack/tcpip" + "netstack/tcpip/link/fdbased" + "netstack/tcpip/link/tuntap" + "netstack/tcpip/network/arp" + "netstack/tcpip/network/ipv4" + "netstack/tcpip/stack" +) + +// 链路层主要负责管理网卡和处理网卡数据, +// 包括新建网卡对象绑定真实网卡,更改网卡参数,接收网卡数据,去除以太网头部后分发给上层,接收上层数据,封装以太网头部写入网卡。 +// 需要注意的是主机与主机之间的二层通信,也需要主机有 ip 地址, +// 因为主机需要通过 arp 表来进行二层寻址,而 arp 表记录的是 ip 与 mac 地址的映射关系,所以主机的 ip 地址是必须的。 +// 经过上面的实验我们已经知道,只要配好路由,我们在系统发送的数据就都可以进入到 tap 网卡, +// 然后程序就可以读取到网卡数据,进行处理,实现对 arp 报文的处理,那如果我们继续处理 ip 报文、tcp 报文就可以实现整个协议栈了。 +func main() { + flag.Parse() + if len(flag.Args()) < 2 { + log.Fatal("Usage: ", os.Args[0], " ") + } + + log.SetFlags(log.Lshortfile | log.LstdFlags) + tapName := flag.Arg(0) + cidrName := flag.Arg(1) + + log.Printf("tap: %v, cidrName: %v", tapName, cidrName) + + parsedAddr, cidr, err := net.ParseCIDR(cidrName) + if err != nil { + log.Fatalf("Bad cidr: %v", cidrName) + } + + // 解析地址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) + } + + // 虚拟网卡配置 + 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) + // 设置路由 + tuntap.SetRoute(tapName, cidr.String()) + + // 获取mac地址 + mac, err := tuntap.GetHardwareAddr(tapName) + if err != nil { + panic(err) + } + + // 抽象网卡的文件接口 + linkID := fdbased.New(&fdbased.Options{ + FD: fd, + MTU: 1500, + Address: tcpip.LinkAddress(mac), + }) + + // 新建相关协议的协议栈 + s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, + []string{}, 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) + } + + select {} +} diff --git a/tcpip/network/READMD.md b/tcpip/network/READMD.md new file mode 100644 index 0000000..6c5a90a --- /dev/null +++ b/tcpip/network/READMD.md @@ -0,0 +1,19 @@ +# CIDR地址的计算方法 + +CIDR无类域间路由,打破了原本的ABC类地址的规划限定,使用地址段分配更加灵活,日常工作中也经常使用,也正是因为其灵活的特点使我们无法一眼辨认出网络号、广播地址、网络中的第一台主机等信息,本文主要针对这些信息的获得介绍一些计算方法。 + +当给定一个IP地址,比如18.232.133.86/22,需要求一下这个IP所在网络的 网络地址、子网掩码、广播i地址、这个网络的第一台主机的IP地址: + +斜线后是22并不是8的整数倍,直接很难看出结果,所以需要通过一系列的计算。 + +1. 先用8的整数倍对22进行切割:22 = 16+6 ,所以这个IP地址的前16位保持不动即18.232. + +2. 发现问题出在了第三个8位上,这8位中前面6位被拿来做了网络号,后面2位被拿去做了主机号,所以将这8位转化为二进制得到10000101,拿出前6位为<100001>。这是得到了全部的网络号为 18.232.<100001> + +3. 将主机号全部置0便是网络地址,18.232.<100001><00>.<00000000>即网络地址为18.232.132.0 + +4. 同时也得到了这个网络的第一台主机的ip地址,18.232.<100001><00>.<00000001>即18.232.132.1 + +5. 将主机位全部置1便是广播地址,18.232.<100001><11>.<11111111>即18.232.135.255 + +6. 子网掩码可以直接使用22计算即可,即前22位都为1,其余为0,即255.255.252.0 diff --git a/tcpip/stack/stack.go b/tcpip/stack/stack.go index 01441f6..47dbb4c 100644 --- a/tcpip/stack/stack.go +++ b/tcpip/stack/stack.go @@ -144,6 +144,12 @@ func (s *Stack) CreateNIC(id tcpip.NICID, linkEP tcpip.LinkEndpointID) *tcpip.Er return s.createNIC(id, "", linkEP, true) } +// CreateNamedNIC creates a NIC with the provided id and link-layer endpoint, +// and a human-readable name. +func (s *Stack) CreateNamedNIC(id tcpip.NICID, name string, linkEP tcpip.LinkEndpointID) *tcpip.Error { + return s.createNIC(id, name, linkEP, true) +} + // 新建一个网卡对象,并且激活它 激活就是准备好熊网卡中读取和写入数据 func (s *Stack) createNIC(id tcpip.NICID, name string, linkEP tcpip.LinkEndpointID, enable bool) *tcpip.Error { ep := FindLinkEndpoint(linkEP)