Skip to content

Commit a04f96a

Browse files
committed
🐔 conn: support SO_BINDANY, IP_RECVDSTADDR, IP_RECVDSTPORT, IPV6_RECVPKTINFO, IPV6_RECVDSTPORT on OpenBSD
1 parent b3d5dd6 commit a04f96a

File tree

8 files changed

+218
-24
lines changed

8 files changed

+218
-24
lines changed

ā€Žconn/cmsg_openbsd.goā€Ž

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

ā€Ž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 && !freebsd && !linux && !windows
1+
//go:build !darwin && !freebsd && !linux && !openbsd && !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-
// Available on Linux and FreeBSD.
148+
// Available on Linux, FreeBSD, and OpenBSD.
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, FreeBSD, and Windows.
193+
// Available on Linux, macOS, FreeBSD, OpenBSD, and Windows.
194194
ReceivePacketInfo bool
195195

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

ā€Žconn/conn_bufsize_reuseport_tclass.goā€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build aix || dragonfly || netbsd || openbsd || zos
1+
//go:build aix || dragonfly || netbsd || zos
22

33
package conn
44

ā€Žconn/conn_freebsdlinux.goā€Ž

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,6 @@ 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-
3214
func (dso DialerSocketOptions) buildSetFns() setFuncSlice {
3315
return setFuncSlice{}.
3416
appendSetFwmarkFunc(dso.Fwmark).

ā€Žconn/conn_openbsd.goā€Ž

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package conn
2+
3+
import (
4+
"fmt"
5+
6+
"golang.org/x/sys/unix"
7+
)
8+
9+
func setTransparent(fd int, _ string) error {
10+
if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_BINDANY, 1); err != nil {
11+
return fmt.Errorf("failed to set socket option IP_BINDANY: %w", err)
12+
}
13+
return nil
14+
}
15+
16+
func setRecvPktinfo(fd int, network string) error {
17+
switch network {
18+
case "udp4":
19+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVDSTADDR, 1); err != nil {
20+
return fmt.Errorf("failed to set socket option IP_RECVDSTADDR: %w", err)
21+
}
22+
// OpenBSD also has IP_RECVIF, but it cannot be used when sending.
23+
case "udp6":
24+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1); err != nil {
25+
return fmt.Errorf("failed to set socket option IPV6_RECVPKTINFO: %w", err)
26+
}
27+
default:
28+
return fmt.Errorf("unsupported network: %s", network)
29+
}
30+
return nil
31+
}
32+
33+
func setRecvOrigDstAddr(fd int, network string) error {
34+
switch network {
35+
case "udp4":
36+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVDSTADDR, 1); err != nil {
37+
return fmt.Errorf("failed to set socket option IP_RECVDSTADDR: %w", err)
38+
}
39+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVDSTPORT, 1); err != nil {
40+
return fmt.Errorf("failed to set socket option IP_RECVDSTPORT: %w", err)
41+
}
42+
case "udp6":
43+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1); err != nil {
44+
return fmt.Errorf("failed to set socket option IPV6_RECVPKTINFO: %w", err)
45+
}
46+
if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVDSTPORT, 1); err != nil {
47+
return fmt.Errorf("failed to set socket option IPV6_RECVDSTPORT: %w", err)
48+
}
49+
default:
50+
return fmt.Errorf("unsupported network: %s", network)
51+
}
52+
53+
return nil
54+
}
55+
56+
func (lso ListenerSocketOptions) buildSetFns() setFuncSlice {
57+
return setFuncSlice{}.
58+
appendSetSendBufferSize(lso.SendBufferSize).
59+
appendSetRecvBufferSize(lso.ReceiveBufferSize).
60+
appendSetTrafficClassFunc(lso.TrafficClass).
61+
appendSetReusePortFunc(lso.ReusePort).
62+
appendSetTransparentFunc(lso.Transparent).
63+
appendSetRecvPktinfoFunc(lso.ReceivePacketInfo).
64+
appendSetRecvOrigDstAddrFunc(lso.ReceiveOriginalDestAddr)
65+
}

ā€Žconn/conn_pktinfo.goā€Ž

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

33
package conn
44

ā€Žconn/conn_tproxy.goā€Ž

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//go:build freebsd || linux || openbsd
2+
3+
package conn
4+
5+
func (fns setFuncSlice) appendSetTransparentFunc(transparent bool) setFuncSlice {
6+
if transparent {
7+
return append(fns, func(fd int, network string, _ *SocketInfo) error {
8+
return setTransparent(fd, network)
9+
})
10+
}
11+
return fns
12+
}
13+
14+
func (fns setFuncSlice) appendSetRecvOrigDstAddrFunc(recvOrigDstAddr bool) setFuncSlice {
15+
if recvOrigDstAddr {
16+
return append(fns, func(fd int, network string, _ *SocketInfo) error {
17+
return setRecvOrigDstAddr(fd, network)
18+
})
19+
}
20+
return fns
21+
}

0 commit comments

Comments
Ā (0)