Files
flvx/go-gost/x/internal/net/udp/packet_linux.go
T
tsosunchia 3549fe5ad8 fix(udp): preserve wildcard reply sources and isolate destination sessions (#565)
* fix(udp): preserve local destinations and unicast reply sources on Linux

* fix(udp): select unicast sources for IPv6 multicast replies

* fix(udp): retain interface zones for link-local multicast sessions

* fix(udp): isolate interface-local multicast sessions

* fix(udp): include ingress interfaces in session identity

* fix(udp): restrict destination zones to IPv6

* ci(udp): follow rc5 patched Go toolchain requirement

---------

Co-authored-by: sagit <36596628+Sagit-chu@users.noreply.github.com>
2026-10-24 18:35:22 +08:00

117 lines
3.2 KiB
Go

package udp
import (
"encoding/binary"
"errors"
"net"
"golang.org/x/net/ipv6"
"golang.org/x/sys/unix"
)
// PreserveDestination pins wildcard UDP replies to the address they arrived on.
func PreserveDestination(c *net.UDPConn) (net.PacketConn, error) {
local := c.LocalAddr().(*net.UDPAddr)
if !local.IP.IsUnspecified() {
return c, nil
}
raw, err := c.SyscallConn()
if err != nil {
return nil, err
}
var sockErr error
if err = raw.Control(func(fd uintptr) {
if local.IP.To4() != nil {
sockErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_PKTINFO, 1)
return
}
if sockErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1); sockErr != nil {
return
}
var onlyV6 int
onlyV6, sockErr = unix.GetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_V6ONLY)
if sockErr == nil && onlyV6 == 0 {
sockErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_PKTINFO, 1)
}
}); err != nil {
return nil, err
}
if sockErr != nil {
return nil, sockErr
}
return &destinationConn{UDPConn: c}, nil
}
type destinationConn struct {
*net.UDPConn
}
func (c *destinationConn) ReadFrom(b []byte) (int, net.Addr, error) {
var oob [128]byte
n, oobn, flags, remote, err := c.ReadMsgUDP(b, oob[:])
if err != nil {
return 0, remote, err
}
if flags&unix.MSG_CTRUNC != 0 {
return 0, nil, errors.New("UDP destination control message truncated")
}
var dst net.IP
var control []byte
var ifIndex int
if remote.IP.To4() != nil {
messages, err := unix.ParseSocketControlMessage(oob[:oobn])
if err != nil {
return 0, nil, err
}
for _, message := range messages {
if message.Header.Level != unix.IPPROTO_IP || message.Header.Type != unix.IP_PKTINFO {
continue
}
var info unix.Inet4Pktinfo
if _, err := binary.Decode(message.Data, binary.NativeEndian, &info); err != nil {
return 0, nil, err
}
dst, ifIndex = net.IP(info.Addr[:]), int(info.Ifindex)
// Spec_dst remains a local unicast address for broadcast destinations.
control = unix.PktInfo4(&unix.Inet4Pktinfo{Ifindex: info.Ifindex, Spec_dst: info.Spec_dst})
break
}
} else {
var cm ipv6.ControlMessage
if err = cm.Parse(oob[:oobn]); err != nil {
return 0, nil, err
}
dst, ifIndex = cm.Dst, cm.IfIndex
source := dst
if dst.IsMulticast() {
source = nil
}
control = (&ipv6.ControlMessage{Src: source, IfIndex: ifIndex}).Marshal()
}
if len(dst) == 0 || dst.IsUnspecified() {
return 0, nil, errors.New("UDP destination address missing")
}
local := &net.UDPAddr{IP: append(net.IP(nil), dst...), Port: c.LocalAddr().(*net.UDPAddr).Port}
if dst.To4() == nil && (dst.IsLinkLocalUnicast() || dst.IsLinkLocalMulticast() || dst.IsInterfaceLocalMulticast()) {
if iface, lookupErr := net.InterfaceByIndex(ifIndex); lookupErr == nil {
local.Zone = iface.Name
}
}
return n, &packetAddr{Addr: remote, local: local, control: control, ifIndex: ifIndex}, nil
}
func (c *destinationConn) WriteTo(b []byte, addr net.Addr) (int, error) {
if packet, ok := addr.(*packetAddr); ok {
remote, ok := packet.Addr.(*net.UDPAddr)
if !ok {
return 0, errors.New("invalid UDP peer address")
}
n, _, err := c.WriteMsgUDP(b, packet.control, remote)
if n < 0 {
n = 0
}
return n, err
}
return c.UDPConn.WriteTo(b, addr)
}