mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-01 05:25:43 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
521a9f2bca | ||
|
|
5e245b082e | ||
|
|
d9c54026c5 | ||
|
|
c1958dba04 | ||
|
|
540b9070f5 |
@@ -1,18 +1,10 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/cipher"
|
||||
_ "crypto/tls"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
type CipherSuiteTLS13 struct {
|
||||
ID uint16
|
||||
KeyLen int
|
||||
AEAD func(key, fixedNonce []byte) cipher.AEAD
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
||||
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
||||
|
||||
@@ -3,7 +3,6 @@ package quic
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
@@ -28,22 +27,43 @@ func (s SniffHeader) Domain() string {
|
||||
return s.domain
|
||||
}
|
||||
|
||||
const (
|
||||
versionDraft29 uint32 = 0xff00001d
|
||||
version1 uint32 = 0x1
|
||||
var (
|
||||
errNotQUIC = errors.New("not quic")
|
||||
errNotQUICInitial = errors.New("not initial packet")
|
||||
)
|
||||
|
||||
type quicVersionSpec struct {
|
||||
ver uint32
|
||||
typeInitial byte
|
||||
initialSalt []byte
|
||||
labelPrefix string
|
||||
}
|
||||
|
||||
var (
|
||||
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||
initialSuite = &CipherSuiteTLS13{
|
||||
ID: tls.TLS_AES_128_GCM_SHA256,
|
||||
KeyLen: 16,
|
||||
AEAD: AEADAESGCMTLS13,
|
||||
Hash: crypto.SHA256,
|
||||
quicDraft29 = quicVersionSpec{
|
||||
ver: 0xff00001d,
|
||||
typeInitial: 0b00,
|
||||
initialSalt: []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99},
|
||||
labelPrefix: "quic",
|
||||
}
|
||||
quicV1 = quicVersionSpec{
|
||||
ver: 0x1,
|
||||
typeInitial: 0b00,
|
||||
initialSalt: []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a},
|
||||
labelPrefix: "quic",
|
||||
}
|
||||
quicV2 = quicVersionSpec{
|
||||
ver: 0x6b3343cf,
|
||||
typeInitial: 0b01,
|
||||
initialSalt: []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9},
|
||||
labelPrefix: "quicv2",
|
||||
}
|
||||
|
||||
quicVersionSpecMap = map[uint32]*quicVersionSpec{
|
||||
quicDraft29.ver: &quicDraft29,
|
||||
quicV1.ver: &quicV1,
|
||||
quicV2.ver: &quicV2,
|
||||
}
|
||||
errNotQuic = errors.New("not quic")
|
||||
errNotQuicInitial = errors.New("not initial packet")
|
||||
)
|
||||
|
||||
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
@@ -63,60 +83,61 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
buffer := buf.FromBytes(b)
|
||||
typeByte, err := buffer.ReadByte()
|
||||
if err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
isLongHeader := typeByte&0x80 > 0
|
||||
if !isLongHeader || typeByte&0x40 == 0 {
|
||||
return nil, errNotQuicInitial
|
||||
return nil, errNotQUICInitial
|
||||
}
|
||||
|
||||
vb, err := buffer.ReadBytes(4)
|
||||
if err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
versionNumber := binary.BigEndian.Uint32(vb)
|
||||
if versionNumber != 0 && typeByte&0x40 == 0 {
|
||||
return nil, errNotQuic
|
||||
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
||||
return nil, errNotQuic
|
||||
var s *quicVersionSpec
|
||||
if v, ok := quicVersionSpecMap[versionNumber]; ok {
|
||||
s = v
|
||||
} else {
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
packetType := (typeByte & 0x30) >> 4
|
||||
isQuicInitial := packetType == 0x0
|
||||
|
||||
var destConnID []byte
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||
tokenLen, err := readShortQuicVarint(buffer)
|
||||
packetType := (typeByte & 0x30) >> 4
|
||||
isQUICInitial := packetType == s.typeInitial
|
||||
|
||||
if isQUICInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||
tokenLen, err := readShortQUICVarint(buffer)
|
||||
if err != nil || tokenLen > int32(len(b)) {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
}
|
||||
|
||||
packetLen, err := readShortQuicVarint(buffer)
|
||||
packetLen, err := readShortQUICVarint(buffer)
|
||||
if err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
// packetLen is impossible to be shorter than this
|
||||
if packetLen < 4 {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
hdrLen := len(b) - int(buffer.Len())
|
||||
@@ -125,26 +146,22 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
}
|
||||
|
||||
restPayload := b[hdrLen+int(packetLen):]
|
||||
if !isQuicInitial { // Skip this packet if it's not initial packet
|
||||
if !isQUICInitial { // Skip this packet if it's not initial packet
|
||||
b = restPayload
|
||||
continue
|
||||
}
|
||||
|
||||
var salt []byte
|
||||
if versionNumber == version1 {
|
||||
salt = quicSalt
|
||||
} else {
|
||||
salt = quicSaltOld
|
||||
}
|
||||
salt := s.initialSalt
|
||||
label := s.labelPrefix
|
||||
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
||||
hpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, "quic hp", initialSuite.KeyLen)
|
||||
secret := hkdfExpandLabel(initialSecret, "client in", crypto.SHA256.Size())
|
||||
hpKey := hkdfExpandLabel(secret, label+" hp", 16)
|
||||
block, err := aes.NewCipher(hpKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) < hdrLen+4+block.BlockSize() {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
cache.Clear()
|
||||
mask := cache.Extend(int32(block.BlockSize()))
|
||||
@@ -155,8 +172,8 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
b[hdrLen+i] ^= mask[i+1]
|
||||
}
|
||||
|
||||
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
||||
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
||||
key := hkdfExpandLabel(secret, label+" key", 16)
|
||||
iv := hkdfExpandLabel(secret, label+" iv", 12)
|
||||
cipher := AEADAESGCMTLS13(key, iv)
|
||||
|
||||
nonce := cache.Extend(int32(cipher.NonceSize()))
|
||||
@@ -181,44 +198,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
case 0x00: // PADDING frame
|
||||
case 0x01: // PING frame
|
||||
case 0x02, 0x03: // ACK frame
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Delay
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Delay
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
ackRangeCount, err := readShortQuicVarint(buffer) // Field: ACK Range Count
|
||||
ackRangeCount, err := readShortQUICVarint(buffer) // Field: ACK Range Count
|
||||
if err != nil {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: First ACK Range
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: First ACK Range
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
if frameType == 0x03 {
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
case 0x06: // CRYPTO frame, we will use this frame
|
||||
offset, err := readShortQuicVarint(buffer) // Field: Offset
|
||||
offset, err := readShortQUICVarint(buffer) // Field: Offset
|
||||
if err != nil {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := readShortQuicVarint(buffer) // Field: Length
|
||||
length, err := readShortQUICVarint(buffer) // Field: Length
|
||||
if err != nil || length > buffer.Len() {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -234,13 +251,13 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Error Code
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Error Code
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Frame Type
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Frame Type
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := readShortQuicVarint(buffer) // Field: Reason Phrase Length
|
||||
length, err := readShortQUICVarint(buffer) // Field: Reason Phrase Length
|
||||
if err != nil {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -250,7 +267,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
default:
|
||||
// Only above frame types are permitted in initial packet.
|
||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||
return nil, errNotQuicInitial
|
||||
return nil, errNotQUICInitial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,35 +285,33 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
return nil, protocol.ErrProtoNeedMoreData
|
||||
}
|
||||
|
||||
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
||||
binary.BigEndian.PutUint16(b, uint16(length))
|
||||
b[2] = uint8(6 + len(label))
|
||||
b = append(b, []byte("tls13 ")...)
|
||||
b = append(b, []byte(label)...)
|
||||
b = b[:3+6+len(label)+1]
|
||||
b[3+6+len(label)] = uint8(len(context))
|
||||
b = append(b, context...)
|
||||
func hkdfExpandLabel(secret []byte, label string, length int) []byte {
|
||||
b := make([]byte, 0, 2+1+6+len(label)+1)
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(length))
|
||||
b = append(b, byte(6+len(label)))
|
||||
b = append(b, "tls13 "...)
|
||||
b = append(b, label...)
|
||||
b = append(b, 0) // context
|
||||
|
||||
out := make([]byte, length)
|
||||
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
||||
n, err := hkdf.Expand(crypto.SHA256.New, secret, b).Read(out)
|
||||
if err != nil || n != length {
|
||||
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// readShortQuicVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||
// readShortQUICVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||
// we only handle QUIC Initial so these numbers should not exceed 65535
|
||||
// returns int32 to reduce type conversion
|
||||
func readShortQuicVarint(reader io.ByteReader) (int32, error) {
|
||||
func readShortQUICVarint(reader io.ByteReader) (int32, error) {
|
||||
v, err := quicvarint.Read(reader)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v > 65535 {
|
||||
// not used(
|
||||
return 0, errNotQuicInitial
|
||||
return 0, errNotQUICInitial
|
||||
}
|
||||
return int32(v), nil
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -23,7 +23,7 @@ func (o *ObservatoryConfig) Build() (proto.Message, error) {
|
||||
type BurstObservatoryConfig struct {
|
||||
SubjectSelector []string `json:"subjectSelector"`
|
||||
// health check settings
|
||||
HealthCheck *healthCheckSettings `json:"pingConfig,omitempty"`
|
||||
HealthCheck *HealthCheckSettings `json:"pingConfig,omitempty"`
|
||||
}
|
||||
|
||||
func (b BurstObservatoryConfig) Build() (proto.Message, error) {
|
||||
|
||||
@@ -43,8 +43,8 @@ type strategyLeastLoadConfig struct {
|
||||
Tolerance float64 `json:"tolerance,omitempty"`
|
||||
}
|
||||
|
||||
// healthCheckSettings holds settings for health Checker
|
||||
type healthCheckSettings struct {
|
||||
// HealthCheckSettings holds settings for health Checker
|
||||
type HealthCheckSettings struct {
|
||||
Destination string `json:"destination"`
|
||||
Connectivity string `json:"connectivity"`
|
||||
Interval duration.Duration `json:"interval"`
|
||||
@@ -53,7 +53,7 @@ type healthCheckSettings struct {
|
||||
HttpMethod string `json:"httpMethod"`
|
||||
}
|
||||
|
||||
func (h healthCheckSettings) Build() (proto.Message, error) {
|
||||
func (h HealthCheckSettings) Build() (proto.Message, error) {
|
||||
var httpMethod string
|
||||
if h.HttpMethod == "" {
|
||||
httpMethod = "HEAD"
|
||||
|
||||
+729
-25
@@ -3,28 +3,60 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
_ "unsafe"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
xerrors "github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
const tunHeaderSize = 4
|
||||
const (
|
||||
tunHeaderSize = 4
|
||||
defaultFreeBSDGateway = "169.254.10.1/30"
|
||||
|
||||
// escapeFib is the routing table outbound sockets are switched to so
|
||||
// their traffic bypasses the TUN routes installed in the default FIB
|
||||
// (FreeBSD's substitute for the per-socket interface binding other
|
||||
// platforms use). Requires the boot tunable net.fibs >= 2.
|
||||
escapeFib = 1
|
||||
)
|
||||
|
||||
//go:linkname procyield runtime.procyield
|
||||
func procyield(cycles uint32)
|
||||
|
||||
type FreeBSDTun struct {
|
||||
device tun.Device
|
||||
mtu uint32
|
||||
device tun.Device
|
||||
options *Config
|
||||
tunIndex int
|
||||
autoInterface bool
|
||||
|
||||
systemRoutes []netip.Prefix
|
||||
escapeMu sync.Mutex
|
||||
escapeRoutes []escapeRoute
|
||||
routeMonitor *os.File
|
||||
routeMonitorOnce sync.Once
|
||||
}
|
||||
|
||||
// escapeRoute remembers one route written into the escape FIB, in the exact
|
||||
// shape needed to delete it again. A zero gateway means an interface route.
|
||||
type escapeRoute struct {
|
||||
prefix netip.Prefix
|
||||
ifIndex int
|
||||
gateway netip.Addr
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -34,20 +66,172 @@ var (
|
||||
|
||||
// NewTun builds new tun interface handler
|
||||
func NewTun(options *Config) (Tun, error) {
|
||||
gateway, local, err := selectFreeBSDGateway(options.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// net.fibs is a boot-time constant, so validate the escape routing table
|
||||
// before the shared handler registers a dialer controller that would
|
||||
// otherwise steer every outbound socket into a table that was never set up.
|
||||
if options.AutoOutboundsInterface != "" {
|
||||
if err := checkEscapeFib(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
tunDev, err := tun.CreateTUN(options.Name, int(options.MTU))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FreeBSDTun{device: tunDev, mtu: options.MTU}, nil
|
||||
name, err := tunDev.Name()
|
||||
if err != nil {
|
||||
_ = tunDev.Close()
|
||||
return nil, err
|
||||
}
|
||||
// From here the interface exists in the kernel; the wireguard library does
|
||||
// not remove it on Close, so every failure path must destroy it too or the
|
||||
// next start fails with "interface already exists".
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
_ = tunDev.Close()
|
||||
destroyInterface(name)
|
||||
return nil, err
|
||||
}
|
||||
if err := setIPAddress(name, gateway, local, iface.Index); err != nil {
|
||||
_ = tunDev.Close()
|
||||
destroyInterface(name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FreeBSDTun{
|
||||
device: tunDev,
|
||||
options: options,
|
||||
tunIndex: iface.Index,
|
||||
autoInterface: options.AutoOutboundsInterface != "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// selectFreeBSDGateway picks the first IPv4 prefix from the configured gateway
|
||||
// list and the local address derived from it (the darwin semantics: the
|
||||
// gateway is the remote side of the point-to-point pair, the local address is
|
||||
// the next one after it), falling back to the same link-local default.
|
||||
func selectFreeBSDGateway(configured []string) (netip.Prefix, netip.Addr, error) {
|
||||
gateway := netip.MustParsePrefix(defaultFreeBSDGateway)
|
||||
if len(configured) > 0 {
|
||||
found := false
|
||||
for _, value := range configured {
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, netip.Addr{}, xerrors.New("invalid FreeBSD gateway ", value).Base(err)
|
||||
}
|
||||
if prefix.Addr().Is4() {
|
||||
gateway, found = prefix, true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return netip.Prefix{}, netip.Addr{}, xerrors.New("FreeBSD gateway requires at least one IPv4 prefix")
|
||||
}
|
||||
}
|
||||
|
||||
local, ok := nextLocalIPv4(gateway)
|
||||
if !ok || !gateway.Contains(local) {
|
||||
return netip.Prefix{}, netip.Addr{}, xerrors.New("FreeBSD gateway ", gateway.String(), " must contain at least one usable local IPv4 address after the gateway address")
|
||||
}
|
||||
return gateway, local, nil
|
||||
}
|
||||
|
||||
func nextLocalIPv4(gateway netip.Prefix) (netip.Addr, bool) {
|
||||
local4 := gateway.Addr().As4()
|
||||
for i := len(local4) - 1; i >= 0; i-- {
|
||||
local4[i]++
|
||||
if local4[i] != 0 {
|
||||
return netip.AddrFrom4(local4), true
|
||||
}
|
||||
}
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) Start() error {
|
||||
if err := t.setSystemRoutes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Gate on this instance's own option, not the package-global updater,
|
||||
// which a previously-removed inbound may have left set. checkEscapeFib
|
||||
// already ran in NewTun, before the dialer controller was registered.
|
||||
if t.autoInterface {
|
||||
if err := t.syncEscapeFib(); err != nil {
|
||||
_ = t.unsetSystemRoutes()
|
||||
return err
|
||||
}
|
||||
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
t.unsetEscapeFib()
|
||||
_ = t.unsetSystemRoutes()
|
||||
return err
|
||||
}
|
||||
t.routeMonitor = os.NewFile(uintptr(fd), "xray-route-monitor")
|
||||
go t.monitorRouteChanges()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// monitorRouteChanges refreshes the outbound interface and the escape FIB
|
||||
// mirror whenever the system routing table changes.
|
||||
func (t *FreeBSDTun) monitorRouteChanges() {
|
||||
buffer := make([]byte, 64*1024)
|
||||
for {
|
||||
if _, err := t.routeMonitor.Read(buffer); err != nil {
|
||||
if !errors.Is(err, os.ErrClosed) {
|
||||
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to monitor route changes")
|
||||
}
|
||||
return
|
||||
}
|
||||
if updater != nil {
|
||||
updater.Update()
|
||||
}
|
||||
if err := t.syncEscapeFib(); err != nil {
|
||||
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to refresh escape routes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) Close() error {
|
||||
return t.device.Close()
|
||||
t.routeMonitorOnce.Do(func() {
|
||||
if t.routeMonitor != nil {
|
||||
_ = t.routeMonitor.Close()
|
||||
}
|
||||
})
|
||||
t.unsetEscapeFib()
|
||||
routeErr := t.unsetSystemRoutes()
|
||||
name, nameErr := t.Name()
|
||||
closeErr := t.device.Close()
|
||||
// The wireguard tun device does not tear the interface down on FreeBSD,
|
||||
// so an unclean shutdown would leave utun<n> behind and the next start
|
||||
// would fail with "interface already exists"; destroy it explicitly.
|
||||
if nameErr == nil {
|
||||
destroyInterface(name)
|
||||
}
|
||||
return xerrors.Combine(routeErr, closeErr)
|
||||
}
|
||||
|
||||
func destroyInterface(name string) {
|
||||
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
// struct ifreq: 16-byte name + a 16-byte union (SIOCIFDESTROY's encoded
|
||||
// length is 32 bytes on amd64, and the kernel copies in all of it).
|
||||
var req struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
_ [16]byte
|
||||
}
|
||||
copy(req.Name[:], name)
|
||||
_ = ioctlPtr(fd, unix.SIOCIFDESTROY, unsafe.Pointer(&req))
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) Name() (string, error) {
|
||||
@@ -55,21 +239,13 @@ func (t *FreeBSDTun) Name() (string, error) {
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) Index() (int, error) {
|
||||
name, err := t.Name()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return iface.Index, nil
|
||||
return t.tunIndex, nil
|
||||
}
|
||||
|
||||
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
||||
func (t *FreeBSDTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
||||
// request memory to write from reusable buffer pool
|
||||
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)
|
||||
b := buf.NewWithSize(int32(t.options.MTU) + tunHeaderSize)
|
||||
defer b.Release()
|
||||
|
||||
// prepare Unix specific packet header
|
||||
@@ -104,7 +280,7 @@ func (t *FreeBSDTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
||||
// which will make the stack call Wait which should implement desired push-back
|
||||
func (t *FreeBSDTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
||||
// request memory to write from reusable buffer pool
|
||||
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)
|
||||
b := buf.NewWithSize(int32(t.options.MTU) + tunHeaderSize)
|
||||
|
||||
// read the bytes to the interface file
|
||||
n, err := b.ReadFrom(t.device.File())
|
||||
@@ -141,23 +317,551 @@ func (t *FreeBSDTun) Wait() {
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &LinkEndpoint{deviceMTU: t.mtu, device: t}, nil
|
||||
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
||||
}
|
||||
|
||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||
const (
|
||||
IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h
|
||||
ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
|
||||
)
|
||||
|
||||
// ifAliasReq4 is struct in_aliasreq from netinet/in_var.h in the 64-byte
|
||||
// layout unix.SIOCAIFADDR encodes (name + addr/dstaddr/mask sockaddrs).
|
||||
type ifAliasReq4 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet4
|
||||
Dstaddr unix.RawSockaddrInet4
|
||||
Mask unix.RawSockaddrInet4
|
||||
}
|
||||
|
||||
// ifAliasReq6 is struct in6_aliasreq from netinet6/in6_var.h. The trailing
|
||||
// Vhid field matters: unix.SIOCAIFADDR_IN6 is not in x/sys/unix, so
|
||||
// siocaifaddrIn6 is derived from this struct's size, and the kernel only
|
||||
// accepts the ioctl whose encoded length matches the real struct.
|
||||
type ifAliasReq6 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet6
|
||||
Dstaddr unix.RawSockaddrInet6
|
||||
Prefixmask unix.RawSockaddrInet6
|
||||
Flags int32
|
||||
Lifetime addrLifetime6
|
||||
Vhid int32
|
||||
}
|
||||
|
||||
// addrLifetime6 is struct in6_addrlifetime (time_t is int64 on freebsd/amd64).
|
||||
type addrLifetime6 struct {
|
||||
Expire int64
|
||||
Preferred int64
|
||||
Vltime uint32
|
||||
Pltime uint32
|
||||
}
|
||||
|
||||
// SIOCAIFADDR_IN6 = _IOW('i', 27, struct in6_aliasreq); x/sys/unix does not
|
||||
// carry the netinet6 ioctls, so encode it from the struct size like the
|
||||
// header macro does.
|
||||
const siocaifaddrIn6 = 0x80000000 | (uintptr(unsafe.Sizeof(ifAliasReq6{})) << 16) | ('i' << 8) | 27
|
||||
|
||||
// setIPAddress assigns the local/remote point-to-point IPv4 pair and a
|
||||
// link-local IPv6 address to the interface, required for the routing to work
|
||||
// (same scheme as the darwin implementation: local address is the one right
|
||||
// after the gateway address).
|
||||
func setIPAddress(name string, gateway netip.Prefix, local netip.Addr, ifIndex int) error {
|
||||
socket4, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(socket4)
|
||||
|
||||
local4 := local.As4()
|
||||
|
||||
ifReq4 := ifAliasReq4{
|
||||
Addr: unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: local4,
|
||||
},
|
||||
Dstaddr: unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: gateway.Addr().As4(),
|
||||
},
|
||||
Mask: unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: prefixMask4(gateway.Bits()),
|
||||
},
|
||||
}
|
||||
copy(ifReq4.Name[:], name)
|
||||
if err = ioctlPtr(socket4, unix.SIOCAIFADDR, unsafe.Pointer(&ifReq4)); err != nil {
|
||||
return os.NewSyscallError("SIOCAIFADDR", err)
|
||||
}
|
||||
|
||||
socket6, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(socket6)
|
||||
|
||||
// link-local ipv6 address with suffix from ipv4, enough for v6 interface
|
||||
// routes to be attachable (darwin parity); a link-local address needs its
|
||||
// scope, which for FreeBSD ioctls is the interface index
|
||||
local6 := netip.AddrFrom16([16]byte{0: 0xfe, 1: 0x80, 12: local4[0], 13: local4[1], 14: local4[2], 15: local4[3]})
|
||||
|
||||
ifReq6 := ifAliasReq6{
|
||||
Addr: unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: local6.As16(),
|
||||
Scope_id: uint32(ifIndex),
|
||||
},
|
||||
Prefixmask: unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: prefixMask6(64),
|
||||
},
|
||||
Flags: IN6_IFF_NODAD,
|
||||
Lifetime: addrLifetime6{
|
||||
Vltime: ND6_INFINITE_LIFETIME,
|
||||
Pltime: ND6_INFINITE_LIFETIME,
|
||||
},
|
||||
}
|
||||
copy(ifReq6.Name[:], name)
|
||||
if err = ioctlPtr(socket6, uint(siocaifaddrIn6), unsafe.Pointer(&ifReq6)); err != nil {
|
||||
// non-fatal: FreeBSD auto-configures a link-local address on UP
|
||||
// interfaces, which is all the v6 interface routes need
|
||||
xerrors.LogInfoInner(context.Background(), os.NewSyscallError("SIOCAIFADDR_IN6", err), "[tun] failed to assign the IPv6 link-local address")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||
if fixedName == "" {
|
||||
return nil, errors.New("automatic outbound interface selection is not supported on this platform")
|
||||
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
iface, err := net.InterfaceByName(fixedName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func prefixMask4(bits int) [4]byte {
|
||||
var mask [4]byte
|
||||
copy(mask[:], net.CIDRMask(bits, 32))
|
||||
return mask
|
||||
}
|
||||
|
||||
func prefixMask6(bits int) [16]byte {
|
||||
var mask [16]byte
|
||||
copy(mask[:], net.CIDRMask(bits, 128))
|
||||
return mask
|
||||
}
|
||||
|
||||
// setinterface is the per-socket half of autoOutboundsInterface. FreeBSD has
|
||||
// no SO_BINDTODEVICE/IP_BOUND_IF equivalent, so the socket is pointed at the
|
||||
// escape FIB instead, where Start() mirrors the physical default route; the
|
||||
// iface argument is resolved by the shared updater but unused here (the escape
|
||||
// is table-based, not a per-socket interface bind). checkEscapeFib in NewTun
|
||||
// guarantees the FIB exists before this can run.
|
||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||
return unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SETFIB, escapeFib)
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) setSystemRoutes() error {
|
||||
routes, err := buildSystemRoutes(t.options.AutoSystemRoutingTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Route through the interface, not a gateway: the tun(4) device is a
|
||||
// broadcast interface here, so its point-to-point peer address doubles as
|
||||
// the subnet broadcast and the kernel refuses to route to it (EACCES).
|
||||
// Interface routes sidestep the gateway entirely (what wg-quick does on
|
||||
// FreeBSD).
|
||||
for _, destination := range routes {
|
||||
if err := execRoute(-1, unix.RTM_ADD, t.tunIndex, destination, netip.Addr{}); err != nil {
|
||||
_ = t.unsetSystemRoutes()
|
||||
return xerrors.New("failed to add system route ", destination).Base(err)
|
||||
}
|
||||
t.systemRoutes = append(t.systemRoutes, destination)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) unsetSystemRoutes() error {
|
||||
var errs []error
|
||||
for i := len(t.systemRoutes) - 1; i >= 0; i-- {
|
||||
destination := t.systemRoutes[i]
|
||||
if err := execRoute(-1, unix.RTM_DELETE, t.tunIndex, destination, netip.Addr{}); err != nil && !errors.Is(err, unix.ESRCH) {
|
||||
errs = append(errs, xerrors.New("failed to delete system route ", destination).Base(err))
|
||||
}
|
||||
}
|
||||
t.systemRoutes = nil
|
||||
return xerrors.Combine(errs...)
|
||||
}
|
||||
|
||||
func buildSystemRoutes(configured []string) ([]netip.Prefix, error) {
|
||||
routes := make([]netip.Prefix, 0, len(configured))
|
||||
seen := make(map[netip.Prefix]struct{})
|
||||
|
||||
appendRoute := func(prefix netip.Prefix) {
|
||||
prefix = prefix.Masked()
|
||||
if _, found := seen[prefix]; found {
|
||||
return
|
||||
}
|
||||
seen[prefix] = struct{}{}
|
||||
routes = append(routes, prefix)
|
||||
}
|
||||
|
||||
for _, value := range configured {
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
return nil, xerrors.New("invalid system route ", value).Base(err)
|
||||
}
|
||||
if prefix.Bits() == 0 {
|
||||
for _, protected := range protectedDefaultRoutes(prefix.Addr().Is4()) {
|
||||
appendRoute(protected)
|
||||
}
|
||||
continue
|
||||
}
|
||||
appendRoute(prefix)
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// protectedDefaultRoutes splits a full default route into eight more-specific
|
||||
// prefixes covering everything but the zero /8, so the system's real default
|
||||
// route stays in place for outbound interface discovery (darwin parity).
|
||||
func protectedDefaultRoutes(ipv4 bool) []netip.Prefix {
|
||||
routes := make([]netip.Prefix, 0, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
if ipv4 {
|
||||
var address [4]byte
|
||||
address[0] = 1 << i
|
||||
routes = append(routes, netip.PrefixFrom(netip.AddrFrom4(address), 8-i))
|
||||
} else {
|
||||
var address [16]byte
|
||||
address[0] = 1 << i
|
||||
routes = append(routes, netip.PrefixFrom(netip.AddrFrom16(address), 8-i))
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// execRoute writes one RTM message to a routing socket. fib >= 0 targets that
|
||||
// routing table via SO_SETFIB on the routing socket (what route(8) -fib
|
||||
// does); fib -1 leaves the process default table. An invalid gateway produces
|
||||
// an interface route pinned to interfaceIndex instead of a gateway route.
|
||||
func execRoute(fib int, messageType int, interfaceIndex int, destination netip.Prefix, gateway netip.Addr) error {
|
||||
message := route.RouteMessage{
|
||||
Type: messageType,
|
||||
Version: unix.RTM_VERSION,
|
||||
Flags: unix.RTF_STATIC | unix.RTF_GATEWAY,
|
||||
Seq: 1,
|
||||
}
|
||||
if messageType == unix.RTM_ADD {
|
||||
message.Flags |= unix.RTF_UP
|
||||
}
|
||||
|
||||
var gatewayAddr route.Addr
|
||||
switch {
|
||||
case !gateway.IsValid():
|
||||
message.Flags &^= unix.RTF_GATEWAY
|
||||
message.Index = interfaceIndex
|
||||
gatewayAddr = &route.LinkAddr{Index: interfaceIndex}
|
||||
case gateway.Is4():
|
||||
gatewayAddr = &route.Inet4Addr{IP: gateway.As4()}
|
||||
default:
|
||||
gatewayAddr = &route.Inet6Addr{IP: gateway.As16()}
|
||||
}
|
||||
|
||||
if destination.Addr().Is4() {
|
||||
message.Addrs = []route.Addr{
|
||||
unix.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()},
|
||||
unix.RTAX_NETMASK: &route.Inet4Addr{IP: prefixMask4(destination.Bits())},
|
||||
unix.RTAX_GATEWAY: gatewayAddr,
|
||||
}
|
||||
} else {
|
||||
message.Addrs = []route.Addr{
|
||||
unix.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()},
|
||||
unix.RTAX_NETMASK: &route.Inet6Addr{IP: prefixMask6(destination.Bits())},
|
||||
unix.RTAX_GATEWAY: gatewayAddr,
|
||||
}
|
||||
}
|
||||
|
||||
request, err := message.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
if fib >= 0 {
|
||||
if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SETFIB, fib); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = unix.Write(fd, request)
|
||||
return err
|
||||
}
|
||||
|
||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||
if fixedName != "" {
|
||||
iface, err := net.InterfaceByName(fixedName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iface.Index == tunIndex {
|
||||
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||
}
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
physical, err := physicalDefaultRoutes(tunIndex, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iface.Index == tunIndex {
|
||||
return nil, errors.New("outbound interface cannot be the TUN interface")
|
||||
for _, family := range []int{unix.AF_INET, unix.AF_INET6} {
|
||||
for _, route := range physical {
|
||||
if route.family == family {
|
||||
return route.iface, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("default route not found")
|
||||
}
|
||||
|
||||
// physicalRoute describes one physical default route: the interface it
|
||||
// leaves through, its gateway, and the connected prefix that makes the
|
||||
// gateway resolvable.
|
||||
type physicalRoute struct {
|
||||
family int
|
||||
iface *net.Interface
|
||||
gateway netip.Addr
|
||||
connected netip.Prefix
|
||||
}
|
||||
|
||||
// physicalDefaultRoutes scans the default routing table for default routes
|
||||
// that do not go through the TUN interface, at most one per address family
|
||||
// (the first usable one wins, matching the darwin implementation's
|
||||
// preference order). A non-zero onlyIndex restricts the scan to that
|
||||
// interface, for the fixed-name mode of autoOutboundsInterface.
|
||||
func physicalDefaultRoutes(tunIndex int, onlyIndex int) ([]physicalRoute, error) {
|
||||
rib, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messages, err := route.ParseRIB(route.RIBTypeRoute, rib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found := make([]physicalRoute, 0, 2)
|
||||
seen := make(map[int]bool)
|
||||
for _, message := range messages {
|
||||
routeMessage, ok := message.(*route.RouteMessage)
|
||||
if !ok || routeMessage.Index == tunIndex {
|
||||
continue
|
||||
}
|
||||
if onlyIndex != 0 && routeMessage.Index != onlyIndex {
|
||||
continue
|
||||
}
|
||||
if routeMessage.Flags&unix.RTF_UP == 0 || routeMessage.Flags&unix.RTF_GATEWAY == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
family, ok := defaultRouteFamily(routeMessage)
|
||||
if !ok || seen[family] {
|
||||
continue
|
||||
}
|
||||
iface, err := usableInterface(routeMessage.Index)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
gatewayAddr, ok := routeAddrToNetip(routeMessage.Addrs[unix.RTAX_GATEWAY])
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
connected, err := connectedPrefix(iface, gatewayAddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
seen[family] = true
|
||||
found = append(found, physicalRoute{
|
||||
family: family,
|
||||
iface: iface,
|
||||
gateway: gatewayAddr,
|
||||
connected: connected,
|
||||
})
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
return nil, errors.New("default route not found")
|
||||
}
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// defaultRouteFamily reports the address family of a RIB message that
|
||||
// represents a true default route (unspecified destination, zero mask).
|
||||
func defaultRouteFamily(message *route.RouteMessage) (int, bool) {
|
||||
if len(message.Addrs) <= unix.RTAX_NETMASK {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
||||
case *route.Inet4Addr:
|
||||
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr)
|
||||
if !ok || destination.IP != netip.IPv4Unspecified().As4() {
|
||||
return 0, false
|
||||
}
|
||||
ones, bits := net.IPMask(mask.IP[:]).Size()
|
||||
return unix.AF_INET, ones == 0 && bits == 32
|
||||
case *route.Inet6Addr:
|
||||
mask, ok := message.Addrs[unix.RTAX_NETMASK].(*route.Inet6Addr)
|
||||
if !ok || destination.IP != netip.IPv6Unspecified().As16() {
|
||||
return 0, false
|
||||
}
|
||||
ones, bits := net.IPMask(mask.IP[:]).Size()
|
||||
return unix.AF_INET6, ones == 0 && bits == 128
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func usableInterface(index int) (*net.Interface, error) {
|
||||
iface, err := net.InterfaceByIndex(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||
return nil, errors.New("default route interface is not usable")
|
||||
}
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func routeAddrToNetip(addr route.Addr) (netip.Addr, bool) {
|
||||
switch typed := addr.(type) {
|
||||
case *route.Inet4Addr:
|
||||
return netip.AddrFrom4(typed.IP), true
|
||||
case *route.Inet6Addr:
|
||||
return netip.AddrFrom16(typed.IP), true
|
||||
default:
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// checkEscapeFib verifies the system can host the escape routing table at
|
||||
// all: FIBs are a boot-time resource on FreeBSD.
|
||||
func checkEscapeFib() error {
|
||||
fibs, err := unix.SysctlUint32("net.fibs")
|
||||
if err != nil {
|
||||
return xerrors.New("failed to read net.fibs").Base(err)
|
||||
}
|
||||
if fibs < 2 {
|
||||
return errors.New("automatic outbound interface on FreeBSD needs a second routing table: add net.fibs=2 to /boot/loader.conf and reboot")
|
||||
}
|
||||
current, err := unix.SysctlUint32("net.my_fibnum")
|
||||
if err != nil {
|
||||
return xerrors.New("failed to read net.my_fibnum").Base(err)
|
||||
}
|
||||
if current == escapeFib {
|
||||
return errors.New("xray runs inside routing table 1, which is reserved as the escape table; start it in another FIB")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncEscapeFib mirrors the physical default routes (and the connected
|
||||
// prefixes their gateways resolve through) into the escape FIB, replacing
|
||||
// whatever mirror a previous call installed. On discovery failure the old
|
||||
// mirror is kept, since a stale escape route beats none during a transient
|
||||
// route flap.
|
||||
func (t *FreeBSDTun) syncEscapeFib() error {
|
||||
var onlyIndex int
|
||||
if t.options.AutoOutboundsInterface != "" && updater != nil {
|
||||
if iface := updater.Get(); iface != nil {
|
||||
onlyIndex = iface.Index
|
||||
}
|
||||
}
|
||||
physical, err := physicalDefaultRoutes(t.tunIndex, onlyIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
desired := make([]escapeRoute, 0, 2*len(physical))
|
||||
for _, p := range physical {
|
||||
desired = append(desired,
|
||||
escapeRoute{prefix: p.connected, ifIndex: p.iface.Index},
|
||||
escapeRoute{prefix: defaultPrefix(p.family), ifIndex: p.iface.Index, gateway: p.gateway},
|
||||
)
|
||||
}
|
||||
|
||||
t.escapeMu.Lock()
|
||||
defer t.escapeMu.Unlock()
|
||||
|
||||
// The route monitor hears our own escape FIB writes too; rewriting an
|
||||
// unchanged mirror on every wake-up would ping-pong forever.
|
||||
if slices.Equal(t.escapeRoutes, desired) {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.unsetEscapeFibLocked()
|
||||
for _, entry := range desired {
|
||||
err := execRoute(escapeFib, unix.RTM_ADD, entry.ifIndex, entry.prefix, entry.gateway)
|
||||
if err != nil && !errors.Is(err, unix.EEXIST) {
|
||||
return xerrors.New("failed to add escape route ", entry.prefix).Base(err)
|
||||
}
|
||||
t.escapeRoutes = append(t.escapeRoutes, entry)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) unsetEscapeFib() {
|
||||
t.escapeMu.Lock()
|
||||
defer t.escapeMu.Unlock()
|
||||
t.unsetEscapeFibLocked()
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) unsetEscapeFibLocked() {
|
||||
for i := len(t.escapeRoutes) - 1; i >= 0; i-- {
|
||||
entry := t.escapeRoutes[i]
|
||||
err := execRoute(escapeFib, unix.RTM_DELETE, entry.ifIndex, entry.prefix, entry.gateway)
|
||||
if err != nil && !errors.Is(err, unix.ESRCH) {
|
||||
xerrors.LogInfoInner(context.Background(), err, "[tun] failed to delete escape route ", entry.prefix)
|
||||
}
|
||||
}
|
||||
t.escapeRoutes = nil
|
||||
}
|
||||
|
||||
func defaultPrefix(family int) netip.Prefix {
|
||||
if family == unix.AF_INET {
|
||||
return netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
||||
}
|
||||
return netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||
}
|
||||
|
||||
// connectedPrefix finds the interface's address prefix containing the
|
||||
// gateway, which the escape FIB needs as an interface route so the mirrored
|
||||
// default route's gateway is resolvable there.
|
||||
func connectedPrefix(iface *net.Interface, gateway netip.Addr) (netip.Prefix, error) {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ip, ok := netip.AddrFromSlice(ipNet.IP)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ip = ip.Unmap()
|
||||
ones, _ := ipNet.Mask.Size()
|
||||
prefix := netip.PrefixFrom(ip, ones).Masked()
|
||||
if prefix.Contains(gateway.WithZone("").Unmap()) {
|
||||
return prefix, nil
|
||||
}
|
||||
}
|
||||
return netip.Prefix{}, errors.New("no connected prefix contains the gateway")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
//go:build freebsd
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestSelectFreeBSDGatewayDefault(t *testing.T) {
|
||||
gateway, local, err := selectFreeBSDGateway(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gateway != netip.MustParsePrefix(defaultFreeBSDGateway) {
|
||||
t.Fatal("expected default gateway, got ", gateway)
|
||||
}
|
||||
if local != netip.MustParseAddr("169.254.10.2") {
|
||||
t.Fatal("wrong local address: ", local)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectFreeBSDGatewayPicksFirstIPv4(t *testing.T) {
|
||||
gateway, local, err := selectFreeBSDGateway([]string{"fd00::1/64", "10.0.0.1/30", "10.9.9.9/24"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gateway != netip.MustParsePrefix("10.0.0.1/30") {
|
||||
t.Fatal("wrong gateway: ", gateway)
|
||||
}
|
||||
if local != netip.MustParseAddr("10.0.0.2") {
|
||||
t.Fatal("wrong local address: ", local)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectFreeBSDGatewayRequiresIPv4(t *testing.T) {
|
||||
if _, _, err := selectFreeBSDGateway([]string{"fd00::1/64"}); err == nil {
|
||||
t.Fatal("expected error when no IPv4 gateway is configured")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectFreeBSDGatewayRejectsGarbage(t *testing.T) {
|
||||
if _, _, err := selectFreeBSDGateway([]string{"not-a-prefix"}); err == nil {
|
||||
t.Fatal("expected error for invalid gateway")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectFreeBSDGatewayRejectsFullPrefix(t *testing.T) {
|
||||
// 10.0.0.255/30: the "next" local address falls outside the prefix
|
||||
if _, _, err := selectFreeBSDGateway([]string{"10.0.0.255/30"}); err == nil {
|
||||
t.Fatal("expected error when no usable local address follows the gateway")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextLocalIPv4(t *testing.T) {
|
||||
local, ok := nextLocalIPv4(netip.MustParsePrefix("169.254.10.1/30"))
|
||||
if !ok || local != netip.MustParseAddr("169.254.10.2") {
|
||||
t.Fatal("wrong local address: ", local)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSystemRoutesSplitsDefault(t *testing.T) {
|
||||
routes, err := buildSystemRoutes([]string{"0.0.0.0/0"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{
|
||||
"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5",
|
||||
"16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1",
|
||||
}
|
||||
if len(routes) != len(expected) {
|
||||
t.Fatal("expected ", len(expected), " routes, got ", routes)
|
||||
}
|
||||
for i, want := range expected {
|
||||
if routes[i] != netip.MustParsePrefix(want) {
|
||||
t.Fatal("route ", i, ": expected ", want, ", got ", routes[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSystemRoutesSplitsDefaultIPv6(t *testing.T) {
|
||||
routes, err := buildSystemRoutes([]string{"::/0"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(routes) != 8 || routes[7] != netip.MustParsePrefix("8000::/1") {
|
||||
t.Fatal("unexpected v6 split: ", routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSystemRoutesDeduplicates(t *testing.T) {
|
||||
routes, err := buildSystemRoutes([]string{"10.0.0.0/8", "10.1.2.3/8", "0.0.0.0/0", "0.0.0.0/0"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(routes) != 9 { // 10.0.0.0/8 once + 8 splits once
|
||||
t.Fatal("expected 9 routes, got ", routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSystemRoutesRejectsGarbage(t *testing.T) {
|
||||
if _, err := buildSystemRoutes([]string{"10.0.0.0/33"}); err == nil {
|
||||
t.Fatal("expected error for invalid route")
|
||||
}
|
||||
}
|
||||
|
||||
func routeMessage(dst, mask route.Addr) *route.RouteMessage {
|
||||
addrs := make([]route.Addr, unix.RTAX_NETMASK+1)
|
||||
addrs[unix.RTAX_DST] = dst
|
||||
addrs[unix.RTAX_NETMASK] = mask
|
||||
return &route.RouteMessage{Addrs: addrs}
|
||||
}
|
||||
|
||||
func TestDefaultRouteFamilyMatchesIPv4Default(t *testing.T) {
|
||||
family, ok := defaultRouteFamily(routeMessage(&route.Inet4Addr{}, &route.Inet4Addr{}))
|
||||
if !ok || family != unix.AF_INET {
|
||||
t.Fatal("expected IPv4 default route match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultRouteFamilyMatchesIPv6Default(t *testing.T) {
|
||||
family, ok := defaultRouteFamily(routeMessage(&route.Inet6Addr{}, &route.Inet6Addr{}))
|
||||
if !ok || family != unix.AF_INET6 {
|
||||
t.Fatal("expected IPv6 default route match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultRouteFamilyRejectsNonDefault(t *testing.T) {
|
||||
if _, ok := defaultRouteFamily(routeMessage(
|
||||
&route.Inet4Addr{IP: [4]byte{10, 0, 0, 0}},
|
||||
&route.Inet4Addr{IP: [4]byte{255, 0, 0, 0}},
|
||||
)); ok {
|
||||
t.Fatal("non-default destination must not match")
|
||||
}
|
||||
if _, ok := defaultRouteFamily(routeMessage(
|
||||
&route.Inet4Addr{},
|
||||
&route.Inet4Addr{IP: [4]byte{255, 0, 0, 0}},
|
||||
)); ok {
|
||||
t.Fatal("non-zero mask must not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultRouteFamilyRejectsShortAddrs(t *testing.T) {
|
||||
if _, ok := defaultRouteFamily(&route.RouteMessage{}); ok {
|
||||
t.Fatal("message without addresses must not match")
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,17 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
|
||||
to = len(data)
|
||||
}
|
||||
l := to - from
|
||||
// if l is 0, don't send empty tls record(will break the connection) but still sleep
|
||||
if l == 0 {
|
||||
if !mergeHello {
|
||||
delayMin, delayMax := c.delayForSegment(int(splitNum))
|
||||
if delayMax > 0 {
|
||||
time.Sleep(time.Duration(crypto.RandBetween(delayMin, delayMax)) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
splitNum++
|
||||
continue
|
||||
}
|
||||
if 5+l > len(buff) {
|
||||
buff = make([]byte, 5+l)
|
||||
}
|
||||
|
||||
@@ -50,18 +50,24 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
|
||||
errors.LogDebug(ctx, "dialing to "+dest.String())
|
||||
|
||||
if dest.Network == net.Network_UDP {
|
||||
srcAddr := resolveSrcAddr(net.Network_UDP, src)
|
||||
if srcAddr == nil {
|
||||
srcAddr = &net.UDPAddr{
|
||||
IP: []byte{0, 0, 0, 0},
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
var lc net.ListenConfig
|
||||
destAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcAddr := resolveSrcAddr(net.Network_UDP, src)
|
||||
if srcAddr == nil {
|
||||
// some OS don't support mapped IPv4 dual stack
|
||||
// and need to select 0.0.0.0 or [::] manually based on the destination
|
||||
wildcard := net.AnyIP.IP()
|
||||
if destAddr.IP.To4() == nil {
|
||||
wildcard = net.AnyIPv6.IP()
|
||||
}
|
||||
srcAddr = &net.UDPAddr{
|
||||
IP: wildcard,
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
var lc net.ListenConfig
|
||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||
for _, ctl := range Controllers {
|
||||
if err := ctl(network, address, c); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user