Tuesday, May 3, 2022

[Golang] 用 Golang 使用 Raw Socket v.s. libpcap 兩者優劣比較

前陣子花了一些時間在找一種方式,可以透過寫程式的方式抓取進出於任何device interfaces on Linux主機上的封包,目前主要是有這兩大作法: Raw Socket v.s. libpcap

Raw Socket:

可用於多種用途,例如發送和接收原始 IPv4 數據包而不必擔心鏈路層(即,它們插入 IP 層而不是網絡設備驅動程序)。如果需要訪問原始鏈接層,大多數操作系統上的Raw Socket不支持(Linux 是明顯的例外)。

Golang 使用 Raw Socket開發,原則上不需安裝其他套件,是透過system call方式開啟 Raw Socket,它的domain 可以使用AF_INET (Layer 3) 與 AF_PACKET (Layer 2),但AF_INET對於 outgoing packets 會抓不到,所以需使用AF_PACKET回比較完整。

可以透過interface上的IP來綁定特定interface,部分範例如下:

// AF_INET can't capture outgoing packets, must change to use AF_PACKET
    // https://github.com/golang/go/issues/7653
    // http://www.binarytides.com/packet-sniffer-code-in-c-using-linux-sockets-bsd-part-2/
    proto := (syscall.ETH_P_ALL<<8)&0xff00 | syscall.ETH_P_ALL>>8 // change to Big-Endian order
    fmt.Println("[DEBUG] proto: ", proto)
    fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, proto)
    if err != nil {
        log.Fatal("socket: ", err)
    }
    defer syscall.Close(fd)
    if t.addr != "" && t.addr != "0.0.0.0" {
        ifi, err := net.InterfaceByName(t.addr)
        if err != nil {
            log.Fatal(t.addr, " interfacebyname: ", err)
        }
        lla := syscall.SockaddrLinklayer{Protocol: uint16(proto), Ifindex: ifi.Index}
        if err := syscall.Bind(fd, &lla); err != nil {
            log.Fatal("bind: ", err)
        }
    }


libpcap:

libpcap 在不同的操作系統上使用不同的機制。在 Linux 上,它使用 PF_PACKET Raw或Cooked Socket,這取決於它是否知道接口的 Linux 鏈路層類型(ARPHRD_ 值)以及該鏈路層類型的接口是否產生有用的鏈路層標頭。

Golang使用libpcap開發,在需要先安裝 libpcap-dev 套件,在Golang下有一強大的package : gopacket,其使用libpcap函示庫擷取封包,對上提供大量的封包處理的功能,並且還可以類似於tcpdump一樣設定過濾條件,部分範例如下:

    // Open device
    handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
    if err != nil {
        log.Printf("[%v] Error : %s", device, err)
    }

    // Set BPF Filter
    if err := handle.SetBPFFilter("not port 22"); err != nil {
        panic(err)
    }

    defer handle.Close()
    // Use the handle as a packet source to process all packets
    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
    packets := packetSource.Packets()
    timer := time.NewTimer(time.Duration(timerclick) * time.Second)

    for {
        select {
        case packet := <-packets:
...

P.S: Gopacket相關參考文件如下:

使用 Gopacket 進行數據包捕獲、注入和分析

網路流量抓包庫 gopacket



為什麼使用libpcap會比較好?

三個原因:

1)正確設置更容易。

2) 它是可移植的,甚至可以移植到 Windows,它使用非常相似但不同的套接字 API。

3)它快得多。


Reference:

Does libpcap use raw sockets underneath them?
Why libpcap is better than sniffing with raw?


No comments: