(环境:裸机 x86_64 全新未修改 Ubuntu 22.04LTS)
我想做 golang net.Dialer 相当于以下 Python 代码。此 Python 确保以太网设备“enp1s0f1”的 MAC 地址出现在 TCP 数据包的源部分中,以便每个传输的数据包都会增加 tx_unicast 计数,如
ethtool -S enp1s0f1
所示。 tcpdump 永远不会显示离开环回设备的数据包,即“lo In ifindex 1 00:00:00:00:00:00...”:
#!/usr/bin/python3
import time
import socket
def send_tcp_packet(dev, packet):
# Create a raw socket
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.SOCK_RAW)
# Bind the socket to the specific Ethernet device
s.bind((dev, 0)) <--- MAGIC CALL HERE
. . .
send_tcp_packet("enp1s0f1", ...)
这是我编写的 golang 等效代码:
func main() {
// Lookup ethernet device 'enp1s0f1'
ief, err := net.InterfaceByName("enp1s0f1")
if err!=nil {
panic(err)
}
fmt.Printf("ief : %+v\n", ief)
addr, err := ief.Addrs()
if err!=nil || len(addr)==0 {
panic(err)
}
fmt.Printf("addr: %v\n", addr)
// for now hardcode 192.168.0.2; later use addr[0] since
// I know addr[0]=='192.168.0.2/28'
bindAddr, err := net.ResolveIPAddr("ip", "192.168.0.2")
if err!=nil || len(addr)==0 {
panic(err)
}
// Make dialer s.t. it sends packets from 'bindAddr'
var d = &net.Dialer{LocalAddr: bindAddr,}
fmt.Printf("dialer bound to %v...\n", d.LocalAddr)
fmt.Printf("dialing 192.168.0.2:2000 ...\n")
conn, err1 := d.Dial("udp", "192.168.0.2:2000")
当我运行此代码时:
ief : &{Index:3 MTU:1500 Name:enp1s0f1 HardwareAddr:08:c0:eb:d4:d1:6f Flags:...}
addr: [192.168.0.2/28 fe80::ac0:ebff:fed4:d16f/64]
dialer bound to 192.168.0.2...
dialing ...
2024/01/03 18:09:03 Failed to dial: <nil>
如果我没有在拨号器中设置
LocalAddr
,连接可以工作,但 tcpdump 报告数据包通过环回设备。
我需要做什么才能让Dialer发送MAC地址为“enp1s0f1”的UDP数据包?我尝试过使用源和目标的 DialUDP,但没有成功。
看来有问题,
听起来您正在寻找的实际上并不是创建原始套接字,而是将套接字绑定到特定设备。这是特定于平台的操作,但在 Linux 上您可以使用
SO_BINDTODEVICE
sockopt。
特定于平台,仅在
syscall
包中公开,您可以在侦听或拨号连接时通过 Control
选项挂钩。由于普通的 UDP 套接字处于侦听状态,因此您需要使用侦听器来实现此目的:
listener := &net.ListenConfig{
Control: func(network, addres string, c syscall.RawConn) error {
var err error
ctrlErr := c.Control(func(fd uintptr) {
err = syscall.BindToDevice(int(fd), "enp1s0f1")
})
if ctrlErr != nil {
return ctrlErr
}
return err
},
}
pc, err := listener.ListenPacket(context.Background(), "udp", "192.168.0.2:0")
if err != nil {
log.Fatal(err)
}
udpConn := pc.(*net.UDPConn)
...