netstack ==================================>
網絡協定棧main函數路徑:--src\connectivity\network\netstack\main.go
main() //--分析關鍵步驟
stk := tcpipstack.New([]string{ipv4.ProtocolName,ipv6.ProtocolName,arp.ProtocolName,},
[]string{icmp.ProtocolName4,tcp.ProtocolName,udp.ProtocolName,},
tcpipstack.Options{HandleLocal: true,}) //調用協定棧New,根據傳入的協定初始化協定棧(ipv4、ipv6、arp、tcp、udp、icmp)
New //--third_party\golibs\github.com\google\netstack\tcpip\stack\stack.go
clock := opts.Clock //根據opt傳入的時鐘(逾時定時用?)
s := &Stack{
transportProtocols: make(map[tcpip.TransportProtocolNumber]*transportProtocolState),
networkProtocols: make(map[tcpip.NetworkProtocolNumber]NetworkProtocol),
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(),
handleLocal: opts.HandleLocal,
raw: opts.Raw,
} //初始化構造stack
{netProto := netProtoFactory()
s.networkProtocols[netProto.Number()] = netProto
s.linkAddrResolvers[r.LinkAddressProtocol()] = r
} //循環,根據傳入的proto初始化
{transProto := transProtoFactory()
} //循環,根據傳入的transport初始化
s.demux = newTransportDemuxer(s) //建立全局的transport demuxer
stk.SetTransportProtocolOption //設定option
arena, err := eth.NewArena() //配置設定協定棧收發資料buffer vmo
NewArena //--src\connectivity\network\netstack\link\eth\arena.go
iovmo, err := zx.NewVMO() //配置設定2048*2048大小的vmo
iovmo.Handle().SetProperty //設定vmo名字
zx.VMARRoot.Map //映射vmo,可讀可寫
a := &Arena{
iovmo: iovmo,
} //構造Arena對象
devicesettings.NewDeviceSettingsManagerInterfaceRequest() //TODO
ns := &Netstack{
arena: arena, //buffer
dnsClient: dns.NewClient(stk), //dns
deviceSettings: ds, //
sniff: *sniff, //嗅探器
}
ns.addLoopback() //添加回環
ns.OnInterfacesChanged = func(){} //接口改變回調函數 TODO
netstackImpl := &netstackImpl{} //協定棧impl?
ctx.OutgoingService.AddService(){} //協定棧對外提供netstack服務
…… //dns相關,忽略!
ctx.OutgoingService.AddService(){} //協定棧對外提供StackServic服務
ctx.OutgoingService.AddService(){} //協定棧對外提供SocketProviderService服務
connectivity.AddOutgoingService(ctx) //TODO
filter.New(stk.PortManager) //過濾器(此處port是tcp層還是mac的?)
filter.AddOutgoingService(ctx, f) //TODO
go pprofListen() //TODO
fidl.Serve() //開始提供服務,run loop
NIC添加
src\virtualization\bin\vmm\device\virtio_net.cc中用到的兩個協定棧接口流程
netstack_->AddEthernetDevice
AddEthernetDevice //--src\connectivity\network\netstack\netstack_service.go
ns.ns.addEth
addEth //--src\connectivity\network\netstack\netstack.go
client, err := eth.NewClient("netstack", topological_path, device, ns.arena)
NewClient //--src\connectivity\network\netstack\link\eth\client.go
device.SetClientName
device.ConfigMulticastSetPromiscuousMode(true)
device.GetInfo //調用網卡驅動通用接口層kOps.GetInfo,擷取mac等資訊
device.GetFifos //調用網卡驅動通用接口層kOps.GetFifos
c := &Client{}
{c.arena.iovmo.Handle().Duplicate(zx.RightSameRights)
device.SetIoBuffer(zx.VMO(h)) //調用網卡驅動通用接口層kOps.SetIoBuffer
c.rxCompleteLocked()
{c.arena.alloc(c)
buf = append(buf, c.arena.entry(b))
} //循環RxDepth,為rxfifo在arena上配置設定空間,并追加到buf後面
fifoWrite(c.fifos.Rx, buf) //向fifo中寫入資料,通知網卡驅動通用接口層
zx.Sys_fifo_write
c.arena.free
}
ns.addEndpoint(func(nicid tcpip.NICID) string {}, eth.NewLinkEndpoint(client), client, true, routes.Metric(config.Metric))
NewLinkEndpoint //--src\connectivity\network\netstack\link\eth\endpoint.go
&endpoint{client: client} //
addEndpoint
linkID := stack.RegisterLinkEndpoint(ep)
RegisterLinkEndpoint //--third_party\golibs\github.com\google\netstack\tcpip\stack\registration.go
v := nextLinkEndpointID //全局變量
linkEndpoints[v] = linkEP //全局map,儲存所有link層的ep
linkID = sniffer.New(linkID) //嗅探器包裝一層,同樣會調用RegisterLinkEndpoint注冊,分析TODO
linkID, ifs.filterEndpoint = filter.NewFilterEndpoint(ns.filter, linkID) //filter又包裝一層,同樣會調用RegisterLinkEndpoint注冊,分析TODO
linkID, ifs.bridgeable = bridge.NewEndpoint(linkID) //bridge包裝下,下面建立NIC的linkID是bridge的linkID,是以,所有到NIC的包都要經過bridge處理一下,再分發
stack.RegisterLinkEndpoint(e) //bridge的ep也關聯到此NIC,同時儲存到全局map
ns.mu.stack.CreateNIC(ifs.nicid, linkID)
CreateNIC //--third_party\golibs\github.com\google\netstack\tcpip\stack\stack.go
s.createNIC
ep := FindLinkEndpoint(linkEP)
newNIC(s, id, name, ep, loopback) //--third_party\golibs\github.com\google\netstack\tcpip\stack\nic.go
return &NIC{……} //構造NIC(network interface card,是協定棧attach的對象)
s.nics[id] = n //将n添加到stack的nics數組中
n.attachLinkEndpoint() //addtach NIC to endpoint,會使能收發包
n.linkEP.Attach(n) 【//這裡Attach就有多種實作(參見netstack\tcpip\link目錄),這裡是以fdbased實作為例;啟動一個goroutine從fd中讀取包,并通過dispatcher分發出去。
e.dispatcher = dispatcher
go e.dispatchLoop() //分發loop
e.inboundDispatcher()
e.inboundDispatcher = e.packetMMapDispatch
e.inboundDispatcher = e.recvMMsgDispatch
e.inboundDispatcher = e.dispatch //非socketfd走此路徑,此處以此為例
n, err := rawfile.BlockingReadv(e.fd, e.iovecs[0])
e.dispatcher.DeliverNetworkPacket(e, remote, local, p, vv)
DeliverNetworkPacket() //詳見nic.go檔案分析】 //這裡分析可能有誤
【Attach //--src\connectivity\network\netstack\link\eth\endpoint.go
go func() {……} //新啟一個goroutine來分發
b, err := e.client.Recv() //調用接收
v := append(buffer.View(nil), b...)
eth := header.Ethernet(v) //擷取eth頭
dispatcher.DeliverNetworkPacket(……) //調用ep對應的dispatcher分發處理資料包
e.dispatcher = dispatcher】 //這裡分析也是錯誤的,應該是bridge的attach
Attach //--src\connectivity\network\netstack\link\bridge\bridgeable.go
e.dispatcher = d //将bridge ep的dispatcher指派為NIC
e.LinkEndpoint.Attach(e) //将NIC filter層的ep attach到bridge,這裡應該也會遞歸觸發sniffer的attach
ns.mu.stack.AddAddress(ifs.nicid, arp.ProtocolNumber, arp.ProtocolAddress) //設定arp
lladdr := header.LinkLocalAddr(linkAddr)
mu.stack.AddAddress(ifs.nicid, ipv6.ProtocolNumber, lladdr)
snaddr := header.SolicitedNodeAddr(lladdr)
ns.mu.stack.AddAddress(ifs.nicid, ipv6.ProtocolNumber, snaddr)
dhcp.NewClient //TODO
netstack_->SetInterfaceAddress
SetInterfaceAddress //--src\connectivity\network\netstack\netstack_service.go
nic := tcpip.NICID(nicid) //根據nicid擷取nic
ni.ns.validateInterfaceAddress //檢查給定ipaddr有效性,并傳回protocol、addr
ni.ns.addInterfaceAddress
addInterfaceAddress //src\connectivity\network\netstack\netstack.go
toSubnet //擷取子網
route := subnetRoute(addr, subnet.Mask(), nic) //
ns.AddRouteLocked
AddRoutesLocked
ns.getNetInterfaces2Locked
ns.OnInterfacesChanged(interfaces)
---------》這個下一步是分析實體網卡netcfg注冊過程
TCP發送流程
third_party\golibs\github.com\google\netstack\tcpip\transport\tcp\snd.go
sendData
maybeSendSegment
sendSegment
sendSegmentFromView
sendRaw --connect.go
sendTCP
r.WritePacket(gso, hdr, data, ProtocolNumber, ttl)
WritePacket --third_party\golibs\github.com\google\netstack\tcpip\stack\route.go
r.ref.ep.WritePacket //TODO,後面的分析不對,到此截止。
e.linkEP.WritePacket --third_party\golibs\github.com\google\netstack\tcpip\link\fdbased\endpoint.go
rawfile.NonBlockingWrite3 --third_party\golibs\github.com\google\netstack\tcpip\link\rawfile\rawfile_unsafe.go
NonBlockingWrite
syscall.RawSyscall(syscall.SYS_WRITE, uintptr(fd), uintptr(ptr), uintptr(len(buf)))
--------------------------------------------------------------
初步分析:third_party\golibs\github.com\google\netstack\tcpip\link目錄為netstack網絡協定棧mac層(資料鍊路層)協定實作!下面對此檔案夾内檔案功能分析。
1.fdbased/endpoint.go<WritePacket>
WritePacket
{eth := header.Ethernet() //構造一個Ethernet頭
ethHdr := &header.EthernetFields{} //構造EthernetFields結構體(14個位元組頭:src<未指派>、dest<指派為r.RemoteLinkAddress>、type<指派為入參protocol>)指派給ethHdr
ethHdr.SrcAddr //對src指派,如果r.LocalLinkAddress有值則取它,否則取調用者e.addr
eth.Encode(ethHdr) //對頭進行編碼 }//e.hdrSize > 0
{……//gso填充 TODO
rawfile.NonBlockingWrite3 //調用rawfile包的NonBlockingWrite3函數
NonBlockingWrite3 //--link/rawfile/rawfile_unsafe.go
[NonBlockingWrite]
iovec := [3]syscall.Iovec{……} //構造iovec結構體數組,每個結構體包含base和len兩個元素
syscall.RawSyscall //系統調用怎麼實作TODO
}//e.Capabilities()&stack.CapabilityGSO != 0
2.loopback/loopback.go //對上層傳下來的包不加mac頭,直接又傳回上層dispatcher處理
3.sharedmem/sharedmem.go //共享記憶體發mac包
WritePacket
eth := header.Ethernet() //構造一個Ethernet頭
ethHdr := &header.EthernetFields{} //構造EthernetFields結構體(14個位元組頭:src<未指派>、dest<指派為r.RemoteLinkAddress>、type<指派為入參protocol>)指派給ethHdr
ethHdr.SrcAddr //對src指派,如果r.LocalLinkAddress有值則取它,否則取調用者e.addr
eth.Encode(ethHdr) //對頭進行編碼
v := payload.ToView() //payload為buffer.VectorisedView類型
ToView //--tcpip\buffer\view.go 傳回其View(View is a slice of a buffer, with convenience methods)
e.tx.transmit(hdr.View(), v) //e.mu.Lock鎖保護;hdr類型為buffer.Prependable,是一個向前增長的buffer,友善在buffer前端加上各層協定頭
id, ok := t.q.CompletedPacket //傳回最後完成的transmission的id
buf := t.ids.remove(id) //移除id及關聯的buffer,以便重用
t.bufs.free //釋放buffer
t.bufs.alloc //從manager處,循環配置設定足夠的buffer來裝資料
copy(dBuf, data) //拷貝資料(入參a、b)到前面配置設定的buffer
t.ids.add //從endpoint擷取一個id
t.q.Enqueue //發送packet?
t.tx.Push(totalLen) //壓入總長度
binary.LittleEndian.PutUint64(b[packetID:], id) //初始化packetID
binary.LittleEndian.PutUint32(b[packetSize:], totalDataLen)//初始化packetSize
binary.LittleEndian.PutUint32(b[packetReserved:], 0)//初始化packetReserved
{ binary.LittleEndian.PutUint64(b[offset+bufferOffset:], buffer.Offset)
binary.LittleEndian.PutUint32(b[offset+bufferSize:], buffer.Size)}//循環
t.tx.Flush() //flush cache到記憶體,接收端可以讀取資料了
4.channel/channel.go
WritePacket
p := PacketInfo{} //構造PacketInfo,包含header、payload、protocol和gso;貌似沒有填入mac到header?!
e.C <- p //這是什麼操作符?将p寫入endpoint的channel?
5.sniffer/sniffer.go //嗅探器,抓包工具
WritePacket //它實作自stack.LinkEndpoint interface,僅僅是記錄下包資訊,并把包傳遞給lower endpoint
6.muxed/injectable.go //把包發給遠端位址的可注入端點,隻用于遠端位址端點有路由器注冊情況
WritePacket
endpoint, ok := m.routes[r.RemoteAddress]
endpoint.WritePacket
7.waitable/waitable.go //Wait or WaitWrite沒被調用情況下,直接傳遞給lower.WritePacket;否則,傳回nil
------------------------------
端點(endpoint)管理
-----------------------------------------------
ifconfig-bridge 網橋
func main()--src\connectivity\network\netstack\ifconfig\ifconfig.go
switch os.Args[1] //根據傳入的參數分别處理
case "bridge"
ifaces := os.Args[2:] //将第二個以及後面的參數構造字元串ifaces
nicid, err := a.bridge(ifaces)
ifs := make([]*netstack.NetInterface2, len(ifNames)) //根據接口數配置設定ifs記憶體
nicIDs := make([]uint32, len(ifNames)) //根據接口數配置設定nicID記憶體
ifaces, err := a.netstack.GetInterfaces2() //擷取所有注冊的iface
for i, ifName := range ifNames {
iface := getIfaceByNameFromIfaces(ifName, ifaces)
if iface == nil {
return 0, fmt.Errorf("no such interface '%s'\n", ifName)
}
ifs[i] = iface
nicIDs[i] = iface.Id
} //根據提供的接口名(ifNames),找到所有的iface實體,并進行儲存指派
result, nicid, _ := a.netstack.BridgeInterfaces(nicIDs) //bridge重點函數:根據接口構造網橋 --src\connectivity\network\netstack\netstack_service.go
nics := make([]tcpip.NICID, len(nicids)) //配置設定記憶體
for i, n := range nicids {
nics[i] = tcpip.NICID(n)
} //數組指派
ifs, err := ni.ns.Bridge(nics) //--src\connectivity\network\netstack\netstack.go
links := make([]*bridge.BridgeableEndpoint, 0, len(nics)) //配置設定記憶體
for _, nicid := range nics {
ifs, ok := ns.mu.ifStates[nicid] //構造ifstate
if !ok {
panic("NIC known by netstack not in interface table")
}
if err := ifs.eth.SetPromiscuousMode(true); err != nil {
return nil, err
} //設定混雜模式
links = append(links, ifs.bridgeable) //構造links
}
b := bridge.New(links) //--src\connectivity\network\netstack\link\bridge\bridge.go
ep := &Endpoint{links: make(map[tcpip.LinkAddress]*BridgeableEndpoint)} //構造ep
for _, l := range links {
ep.links[linkAddress] = l //linkAddress為mac位址,将mac與BridgeableEndpoint(關聯一個NIC)做好映射
…… //MTUs、capabilities取最小值,maxHeaderLength取最大值
}
ep.linkAddress = tcpip.LinkAddress(b) //算法生成bridge的mac位址
ns.addEndpoint //将網橋加入endpoint
linkID := stack.RegisterLinkEndpoint(ep)
RegisterLinkEndpoint //--third_party\golibs\github.com\google\netstack\tcpip\stack\registration.go
v := nextLinkEndpointID //全局變量
linkEndpoints[v] = linkEP //全局map
linkID = sniffer.New(linkID) //嗅探器包裝一層
linkID, ifs.bridgeable = bridge.NewEndpoint(linkID) //bridge包裝下,
stack.RegisterLinkEndpoint(e)
ns.mu.stack.CreateNIC(ifs.nicid, linkID)
CreateNIC //--third_party\golibs\github.com\google\netstack\tcpip\stack\stack.go
s.createNIC
ep := FindLinkEndpoint(linkEP)
newNIC(s, id, name, ep, loopback) //--third_party\golibs\github.com\google\netstack\tcpip\stack\nic.go
return &NIC{……} //構造NIC(network interface card,是協定棧attach的對象)
s.nics[id] = n //将n添加到stack的nics數組中
n.attachLinkEndpoint() //addtach NIC to endpoint,會使能收發包
n.linkEP.Attach(n)
ep.dispatcher = d //将此ep的包分發對象設定為bridge--src\connectivity\network\netstack\link\bridge\bridge.go
interfaces, _ := a.netstack.GetInterfaces2()
bridge := getIfaceByIdFromIfaces(uint32(nicid), interfaces)
//啟動bridge
func (ep *Endpoint) Up() //--src\connectivity\network\netstack\link\bridge\bridge.go
for _, l := range ep.links {
l.SetBridge(ep) //将此橋的所有links的橋服務設定為此橋,此動作之後,到達任意links的包都要轉發到bridge來處理
}
onStateChange(link.StateStarted)
DeliverNetworkPacket
if l, ok := ep.links[dstLinkAddr]; ok {
l.Dispatcher().DeliverNetworkPacket(l, srcLinkAddr, dstLinkAddr, p, vv)
return
} //根據包的目的mac位址,調用對應NIC的dispatcher的DeliverNetworkPacket函數進行分發包處理
r := stack.Route{LocalLinkAddress: srcLinkAddr, RemoteLinkAddress: dstLinkAddr, NetProto: p} //構造route對象
rxaddr := rxEP.LinkAddress() //記錄包的入口mac位址
for linkaddr, l := range ep.links {
if linkaddr != rxaddr { //周遊網橋的所有link點(入口除外),調用其WritePacket函數,最終會調用client的send函數fifoWrite
l.WritePacket(&r, nil, hdr, payload, p)
}
}
//src\connectivity\network\netstack\link\bridge\bridgeable.go
DeliverNetworkPacket
b := e.mu.bridge
b.DeliverNetworkPacket //設定bridge的情況,調用bridge的分發函數
[e.dispatcher.DeliverNetworkPacket] //為設定bridge的情況,直接調用NIC的分發函數
----------------------------------
arena管理
//TODO:
=====================================================================部分網絡協定棧檔案分析
nic.go
//NIC接收到實體接口來的包處理
func (n *NIC) DeliverNetworkPacket(……)
src, dst := netProto.ParseAddresses(vv.First())
if dst == header.IPv4Broadcast {
for _, ref := range n.endpoints { //廣播場景,讓NIC所有關聯的網絡層ep都接收并處理包
r := makeRoute(protocol, dst, src, linkEP.LinkAddress(), ref, false , false )
r.RemoteLinkAddress = remote
ref.ep.HandlePacket(&r, vv) //交給上層業務處理,HandlePacket在上層協定中定義,如tcp、udp、icmp都有自己的定義
}}
if ref := n.getRef(protocol, dst); ref != nil {……} //非廣播場景,根據dst ip 擷取關聯referencedNetworkEndpoint,并調用其HandlePacket處理包。
if n.stack.Forwarding() {……} //本NIC不關心此包情況,找到另一個關心此包的NIC,傳遞給它處理。(目前貌似沒使能!分析TODO)