GO編譯時避免引入外部動態庫的解決方法

2022-10-20 12:00:51

簡介

最近碰到一個問題,有一個流量採集的元件中使用到了github.com/google/gopacket 這個庫,這個庫使用一切正常,但是唯獨有一個缺點,編譯後的二進位制檔案依賴於libpcap.so的動態庫。這為安裝包相容多個平臺造成了一定的困擾,於是便想著如何把libpcap這個外部依賴已靜態庫的方式在go程式編譯的同時link進可執行程式。

gopacket是如何構建的?

此處先擷取一小片原始碼(github.com/google/gopacket/pcap/pcap_unix.go),此處可以看到在cgo中指定了部分的編譯引數,其中的 "-lpcap" 便是指定link到的庫的名稱。可以說是相當的粗暴了。

#cgo solaris LDFLAGS: -L /opt/local/lib -lpcap
#cgo linux LDFLAGS: -lpcap
#cgo dragonfly LDFLAGS: -lpcap
#cgo freebsd LDFLAGS: -lpcap
#cgo openbsd LDFLAGS: -lpcap
#cgo netbsd LDFLAGS: -lpcap
#cgo darwin LDFLAGS: -lpcap

演示demo

// 使用gopacket 抓包的簡單範例
package main

import (
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	logger "github.com/sirupsen/logrus"
	"log"
)

const (
	device  = "ens32"
	SnapLen = int32(65535) // libpcap 接收資料的長度
	Promisc = false        // 是否開啟混雜模式
	BPF     = "icmp"
)

func main() {
	handle, err := pcap.OpenLive(device, SnapLen, Promisc, pcap.BlockForever)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()

	// 編譯並設定bpf過濾規則
	if err = handle.SetBPFFilter(BPF); err != nil {
		log.Fatal(err)
	}

	// 開始獲取流量
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	packetSource.NoCopy = true
	packetChan := packetSource.Packets()

	for packet := range packetChan {
		if packet.TransportLayer() == nil {
			// icmp流量
			icmpStreamHandle(packet)
		} else if packet.TransportLayer().LayerType() == layers.LayerTypeTCP {
			// tcp流量
			tcpStreamHandle(packet)
		} else if packet.TransportLayer().LayerType() == layers.LayerTypeUDP {
			// udp流量
			udpStreamHandle(packet)
		}
	}
}

func icmpStreamHandle(packet gopacket.Packet) {
	logger.Info("get icmp packet")
}
func tcpStreamHandle(packet gopacket.Packet) {
}
func udpStreamHandle(packet gopacket.Packet) {
}

編譯並ldd檢視依賴庫的使用情況

[root@localhost ddk]# go build main.go && ldd main
	linux-vdso.so.1 =>  (0x00007ffe965f3000)
	libpcap.so.1 => /lib64/libpcap.so.1 (0x00007f6be101f000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f6be0e03000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f6be0a35000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6be1260000)
[root@localhost ddk]# 

很容易的檢視到對libpcap.so.1 這個動態庫的依賴

準備靜態庫

找到你的libpcap.so 對應的libpcap.a 檔案,無論是通過安裝libpcap-devel(libpcap-dev)的庫還是直接從頭構建。此處已重頭構建為例:

yum install -y gcc flex byacc
cd /usr/local/source
wget http://www.tcpdump.org/release/libpcap-1.9.1.tar.gz
tar zxvf libpcap-1.9.1.tar.gz
cd libpcap-1.9.1 && ./configure && make

指定編譯引數

「-lpcap」 這個引數既可以用於連結動態庫也可以用於連結靜態庫,動態庫優先, 那麼我我們讓go 編譯器在編譯時執行搜尋庫的路徑並把靜態庫放置於路徑下即可。

[root@localhost ddk]# CGO_LDFLAGS="-g -O2 -L/usr/local/source/libpcap-1.9.1 -I/usr/local/source/libpcap-1.9.1" go build -ldflags '-w -s' -o main main.go
[root@localhost ddk]# ldd main
	linux-vdso.so.1 =>  (0x00007fff6cde4000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1e767fa000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f1e7642c000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f1e76a16000)
[root@localhost ddk]# 

稍微解釋下這條編譯的命令CGO_LDFLAGS="-g -O2 -L/usr/local/source/libpcap-1.9.1 -I/usr/local/source/libpcap-1.9.1" go build -ldflags '-w -s' -o main main.go。CGO_LDFLAGS 環境變數用於指定構建時cgo的引數,-L 指定了查詢動靜態庫的位置,-I 用於指定原始碼標頭檔案的指定路徑,-ldflags '-w -s' 用於去除debug 和符號表的資訊,不加也沒事。
現在我們可以看到對libpcap.so的動態庫依賴消失了,因為libpcap已靜態庫的方式連結進了go編譯好的程式。