Skip to content

Commit b3d5dd6

Browse files
committed
😈 conn: support IP{V6}_BINDANY, IP_RECVDSTADDR, IPV6_RECVPKTINFO, IP{V6}_RECVORIGDSTADDR on FreeBSD
1 parent 5c749e3 commit b3d5dd6

File tree

7 files changed

+192
-24
lines changed

7 files changed

+192
-24
lines changed

‎conn/cmsg_freebsd.go‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package conn
2+
3+
import (
4+
"fmt"
5+
"net/netip"
6+
"slices"
7+
"unsafe"
8+
9+
"golang.org/x/sys/unix"
10+
)
11+
12+
const socketControlMessageBufferSize = unix.SizeofCmsghdr + max(alignedSizeofInet4Addr, alignedSizeofInet6Pktinfo) +
13+
unix.SizeofCmsghdr + max(alignedSizeofSockaddrInet4, alignedSizeofSockaddrInet6)
14+
15+
const sizeofInet4Addr = 4 // sizeof(struct in_addr)
16+
17+
func cmsgAlign(n int) int {
18+
return (n + unix.SizeofPtr - 1) & ^(unix.SizeofPtr - 1)
19+
}
20+
21+
func parseSocketControlMessage(cmsg []byte) (m SocketControlMessage, err error) {
22+
for len(cmsg) >= unix.SizeofCmsghdr {
23+
cmsghdr := (*unix.Cmsghdr)(unsafe.Pointer(unsafe.SliceData(cmsg)))
24+
msgSize := cmsgAlign(int(cmsghdr.Len))
25+
if cmsghdr.Len < unix.SizeofCmsghdr || msgSize > len(cmsg) {
26+
return m, fmt.Errorf("invalid control message length %d", cmsghdr.Len)
27+
}
28+
29+
switch cmsghdr.Level {
30+
case unix.IPPROTO_IP:
31+
switch cmsghdr.Type {
32+
case unix.IP_RECVDSTADDR:
33+
if len(cmsg) < unix.SizeofCmsghdr+sizeofInet4Addr {
34+
return m, fmt.Errorf("invalid IP_RECVDSTADDR control message length %d", cmsghdr.Len)
35+
}
36+
addr := [sizeofInet4Addr]byte(cmsg[unix.SizeofCmsghdr:])
37+
m.PktinfoAddr = netip.AddrFrom4(addr)
38+
39+
case unix.IP_ORIGDSTADDR:
40+
if len(cmsg) < unix.SizeofCmsghdr+unix.SizeofSockaddrInet4 {
41+
return m, fmt.Errorf("invalid IP_ORIGDSTADDR control message length %d", cmsghdr.Len)
42+
}
43+
var rsa4 unix.RawSockaddrInet4
44+
_ = copy(unsafe.Slice((*byte)(unsafe.Pointer(&rsa4)), unix.SizeofSockaddrInet4), cmsg[unix.SizeofCmsghdr:])
45+
m.OriginalDestinationAddrPort = netip.AddrPortFrom(netip.AddrFrom4(rsa4.Addr), rsa4.Port)
46+
}
47+
48+
case unix.IPPROTO_IPV6:
49+
switch cmsghdr.Type {
50+
case unix.IPV6_PKTINFO:
51+
if len(cmsg) < unix.SizeofCmsghdr+unix.SizeofInet6Pktinfo {
52+
return m, fmt.Errorf("invalid IPV6_PKTINFO control message length %d", cmsghdr.Len)
53+
}
54+
var pktinfo unix.Inet6Pktinfo
55+
_ = copy(unsafe.Slice((*byte)(unsafe.Pointer(&pktinfo)), unix.SizeofInet6Pktinfo), cmsg[unix.SizeofCmsghdr:])
56+
m.PktinfoAddr = netip.AddrFrom16(pktinfo.Addr)
57+
m.PktinfoIfindex = pktinfo.Ifindex
58+
59+
case unix.IPV6_ORIGDSTADDR:
60+
if len(cmsg) < unix.SizeofCmsghdr+unix.SizeofSockaddrInet6 {
61+
return m, fmt.Errorf("invalid IPV6_ORIGDSTADDR control message length %d", cmsghdr.Len)
62+
}
63+
var rsa6 unix.RawSockaddrInet6
64+
_ = copy(unsafe.Slice((*byte)(unsafe.Pointer(&rsa6)), unix.SizeofSockaddrInet6), cmsg[unix.SizeofCmsghdr:])
65+
m.OriginalDestinationAddrPort = netip.AddrPortFrom(netip.AddrFrom16(rsa6.Addr), rsa6.Port)
66+
}
67+
}
68+
69+
cmsg = cmsg[msgSize:]
70+
}
71+
72+
return m, nil
73+
}
74+
75+
const (
76+
alignedSizeofInet4Addr = (sizeofInet4Addr + unix.SizeofPtr - 1) & ^(unix.SizeofPtr - 1)
77+
alignedSizeofInet6Pktinfo = (unix.SizeofInet6Pktinfo + unix.SizeofPtr - 1) & ^(unix.SizeofPtr - 1)
78+
alignedSizeofSockaddrInet4 = (unix.SizeofSockaddrInet4 + unix.SizeofPtr - 1) & ^(unix.SizeofPtr - 1)
79+
alignedSizeofSockaddrInet6 = (unix.SizeofSockaddrInet6 + unix.SizeofPtr - 1) & ^(unix.SizeofPtr - 1)
80+
)
81+
82+
func (m SocketControlMessage) appendTo(b []byte) []byte {
83+
switch {
84+
case m.PktinfoAddr.Is4():
85+
bLen := len(b)
86+
b = slices.Grow(b, unix.SizeofCmsghdr+alignedSizeofInet4Addr)[:bLen+unix.SizeofCmsghdr+alignedSizeofInet4Addr]
87+
msgBuf := b[bLen:]
88+
cmsghdr := (*unix.Cmsghdr)(unsafe.Pointer(unsafe.SliceData(msgBuf)))
89+
*cmsghdr = unix.Cmsghdr{
90+
Len: unix.SizeofCmsghdr + sizeofInet4Addr,
91+
Level: unix.IPPROTO_IP,
92+
Type: unix.IP_SENDSRCADDR,
93+
}
94+
addr := m.PktinfoAddr.As4()
95+
_ = copy(msgBuf[unix.SizeofCmsghdr:], addr[:])
96+
97+
case m.PktinfoAddr.Is6():
98+
bLen := len(b)
99+
b = slices.Grow(b, unix.SizeofCmsghdr+alignedSizeofInet6Pktinfo)[:bLen+unix.SizeofCmsghdr+alignedSizeofInet6Pktinfo]
100+
msgBuf := b[bLen:]
101+
cmsghdr := (*unix.Cmsghdr)(unsafe.Pointer(unsafe.SliceData(msgBuf)))
102+
*cmsghdr = unix.Cmsghdr{
103+
Len: unix.SizeofCmsghdr + unix.SizeofInet6Pktinfo,
104+
Level: unix.IPPROTO_IPV6,
105+
Type: unix.IPV6_PKTINFO,
106+
}
107+
pktinfo := unix.Inet6Pktinfo{
108+
Addr: m.PktinfoAddr.As16(),
109+
Ifindex: m.PktinfoIfindex,
110+
}
111+
_ = copy(msgBuf[unix.SizeofCmsghdr:], unsafe.Slice((*byte)(unsafe.Pointer(&pktinfo)), unix.SizeofInet6Pktinfo))
112+
}
113+
114+
return b
115+
}

‎conn/cmsg_stub.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !darwin && !linux && !windows
1+
//go:build !darwin && !freebsd && !linux && !windows
22

33
package conn
44

‎conn/conn.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ type ListenerSocketOptions struct {
145145

146146
// Transparent enables transparent proxy on the listener.
147147
//
148-
// Only available on Linux.
148+
// Available on Linux and FreeBSD.
149149
Transparent bool
150150

151151
// PathMTUDiscovery enables Path MTU Discovery on the listener.
@@ -190,12 +190,12 @@ type ListenerSocketOptions struct {
190190

191191
// ReceivePacketInfo enables the reception of packet information control messages on the listener.
192192
//
193-
// Available on Linux, macOS, and Windows.
193+
// Available on Linux, macOS, FreeBSD, and Windows.
194194
ReceivePacketInfo bool
195195

196196
// ReceiveOriginalDestAddr enables the reception of original destination address control messages on the listener.
197197
//
198-
// Only available on Linux.
198+
// Available on Linux and FreeBSD.
199199
ReceiveOriginalDestAddr bool
200200
}
201201

‎conn/conn_freebsd.go‎

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,65 @@ func setFwmark(fd, fwmark int) error {
1313
return nil
1414
}
1515

16+
func setTransparent(fd int, network string) error {
17+
switch network {
18+
case "tcp4", "udp4":
19+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_BINDANY, 1); err != nil {
20+
return fmt.Errorf("failed to set socket option IP_BINDANY: %w", err)
21+
}
22+
case "tcp6", "udp6":
23+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_BINDANY, 1); err != nil {
24+
return fmt.Errorf("failed to set socket option IPV6_BINDANY: %w", err)
25+
}
26+
default:
27+
return fmt.Errorf("unsupported network: %s", network)
28+
}
29+
return nil
30+
}
31+
32+
func setRecvPktinfo(fd int, network string) error {
33+
switch network {
34+
case "udp4":
35+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVDSTADDR, 1); err != nil {
36+
return fmt.Errorf("failed to set socket option IP_RECVDSTADDR: %w", err)
37+
}
38+
// FreeBSD also has IP_RECVIF, but it cannot be used when sending.
39+
case "udp6":
40+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1); err != nil {
41+
return fmt.Errorf("failed to set socket option IPV6_RECVPKTINFO: %w", err)
42+
}
43+
default:
44+
return fmt.Errorf("unsupported network: %s", network)
45+
}
46+
return nil
47+
}
48+
49+
func setRecvOrigDstAddr(fd int, network string) error {
50+
switch network {
51+
case "udp4":
52+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVORIGDSTADDR, 1); err != nil {
53+
return fmt.Errorf("failed to set socket option IP_RECVORIGDSTADDR: %w", err)
54+
}
55+
case "udp6":
56+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVORIGDSTADDR, 1); err != nil {
57+
return fmt.Errorf("failed to set socket option IPV6_RECVORIGDSTADDR: %w", err)
58+
}
59+
default:
60+
return fmt.Errorf("unsupported network: %s", network)
61+
}
62+
63+
return nil
64+
}
65+
1666
func (lso ListenerSocketOptions) buildSetFns() setFuncSlice {
1767
return setFuncSlice{}.
1868
appendSetSendBufferSize(lso.SendBufferSize).
1969
appendSetRecvBufferSize(lso.ReceiveBufferSize).
2070
appendSetFwmarkFunc(lso.Fwmark).
2171
appendSetTrafficClassFunc(lso.TrafficClass).
2272
appendSetReusePortFunc(lso.ReusePort).
23-
appendSetPMTUDFunc(lso.PathMTUDiscovery)
73+
appendSetTransparentFunc(lso.Transparent).
74+
appendSetPMTUDFunc(lso.PathMTUDiscovery).
75+
appendSetRecvPktinfoFunc(lso.ReceivePacketInfo).
76+
appendSetRecvOrigDstAddrFunc(lso.ReceiveOriginalDestAddr)
2477
}

‎conn/conn_freebsdlinux.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ func (fns setFuncSlice) appendSetFwmarkFunc(fwmark int) setFuncSlice {
1111
return fns
1212
}
1313

14+
func (fns setFuncSlice) appendSetTransparentFunc(transparent bool) setFuncSlice {
15+
if transparent {
16+
return append(fns, func(fd int, network string, _ *SocketInfo) error {
17+
return setTransparent(fd, network)
18+
})
19+
}
20+
return fns
21+
}
22+
23+
func (fns setFuncSlice) appendSetRecvOrigDstAddrFunc(recvOrigDstAddr bool) setFuncSlice {
24+
if recvOrigDstAddr {
25+
return append(fns, func(fd int, network string, _ *SocketInfo) error {
26+
return setRecvOrigDstAddr(fd, network)
27+
})
28+
}
29+
return fns
30+
}
31+
1432
func (dso DialerSocketOptions) buildSetFns() setFuncSlice {
1533
return setFuncSlice{}.
1634
appendSetFwmarkFunc(dso.Fwmark).

‎conn/conn_linux.go‎

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -165,24 +165,6 @@ func (fns setFuncSlice) appendSetTCPUserTimeoutFunc(userTimeoutMsecs int) setFun
165165
return fns
166166
}
167167

168-
func (fns setFuncSlice) appendSetTransparentFunc(transparent bool) setFuncSlice {
169-
if transparent {
170-
return append(fns, func(fd int, network string, _ *SocketInfo) error {
171-
return setTransparent(fd, network)
172-
})
173-
}
174-
return fns
175-
}
176-
177-
func (fns setFuncSlice) appendSetRecvOrigDstAddrFunc(recvOrigDstAddr bool) setFuncSlice {
178-
if recvOrigDstAddr {
179-
return append(fns, func(fd int, network string, _ *SocketInfo) error {
180-
return setRecvOrigDstAddr(fd, network)
181-
})
182-
}
183-
return fns
184-
}
185-
186168
func (lso ListenerSocketOptions) buildSetFns() setFuncSlice {
187169
return setFuncSlice{}.
188170
appendSetSendBufferSize(lso.SendBufferSize).
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build darwin || linux || windows
1+
//go:build darwin || freebsd || linux || windows
22

33
package conn
44

0 commit comments

Comments
 (0)