天天看點

google netstack 資料鍊路層分析

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)