Add redir tproxy and dns inbound

This commit is contained in:
世界 2022-07-15 08:42:02 +08:00
parent e13b72afca
commit 2c2eb31e18
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
19 changed files with 723 additions and 20 deletions

View file

@ -17,6 +17,10 @@ type PacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
} }
type OOBPacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
}
type PacketConnectionHandler interface { type PacketConnectionHandler interface {
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
} }

View file

@ -0,0 +1,37 @@
package redir
import (
"net"
"net/netip"
"syscall"
M "github.com/sagernet/sing/common/metadata"
)
func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {
rawConn, err := conn.(syscall.Conn).SyscallConn()
if err != nil {
return
}
var rawFd uintptr
err = rawConn.Control(func(fd uintptr) {
rawFd = fd
})
if err != nil {
return
}
const SO_ORIGINAL_DST = 80
if conn.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {
raw, err := syscall.GetsockoptIPv6Mreq(int(rawFd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if err != nil {
return netip.AddrPort{}, err
}
return netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3])), nil
} else {
raw, err := syscall.GetsockoptIPv6MTUInfo(int(rawFd), syscall.IPPROTO_IPV6, SO_ORIGINAL_DST)
if err != nil {
return netip.AddrPort{}, err
}
return netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port), nil
}
}

View file

@ -0,0 +1,13 @@
//go:build !linux
package redir
import (
"net"
"net/netip"
"os"
)
func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) {
return netip.AddrPort{}, os.ErrInvalid
}

View file

@ -0,0 +1,132 @@
package redir
import (
"encoding/binary"
"net"
"net/netip"
"os"
"strconv"
"syscall"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
"golang.org/x/sys/unix"
)
func TProxy(fd uintptr, isIPv6 bool) error {
err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
if err != nil {
return err
}
if isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
}
return err
}
func TProxyUDP(fd uintptr, isIPv6 bool) error {
err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
if err != nil {
return err
}
if isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
if err != nil {
return err
}
}
return nil
}
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
controlMessages, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return netip.AddrPort{}, err
}
for _, message := range controlMessages {
if message.Header.Level == unix.SOL_IP && message.Header.Type == unix.IP_RECVORIGDSTADDR {
return netip.AddrPortFrom(M.AddrFromIP(message.Data[4:8]), binary.BigEndian.Uint16(message.Data[2:4])), nil
} else if message.Header.Level == unix.SOL_IPV6 && message.Header.Type == unix.IPV6_RECVORIGDSTADDR {
return netip.AddrPortFrom(M.AddrFromIP(message.Data[8:24]), binary.BigEndian.Uint16(message.Data[2:4])), nil
}
}
return netip.AddrPort{}, E.New("not found")
}
func DialUDP(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
rSockAddr, err := udpAddrToSockAddr(rAddr)
if err != nil {
return nil, err
}
lSockAddr, err := udpAddrToSockAddr(lAddr)
if err != nil {
return nil, err
}
fd, err := syscall.Socket(udpAddrFamily(lAddr, rAddr), syscall.SOCK_DGRAM, 0)
if err != nil {
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Bind(fd, lSockAddr); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Connect(fd, rSockAddr); err != nil {
syscall.Close(fd)
return nil, err
}
fdFile := os.NewFile(uintptr(fd), F.ToString("net-udp-dial-", rAddr))
defer fdFile.Close()
c, err := net.FileConn(fdFile)
if err != nil {
syscall.Close(fd)
return nil, err
}
return c.(*net.UDPConn), nil
}
func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {
switch {
case addr.IP.To4() != nil:
ip := [4]byte{}
copy(ip[:], addr.IP.To4())
return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil
default:
ip := [16]byte{}
copy(ip[:], addr.IP.To16())
zoneID, err := strconv.ParseUint(addr.Zone, 10, 32)
if err != nil {
zoneID = 0
}
return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil
}
}
func udpAddrFamily(lAddr, rAddr *net.UDPAddr) int {
if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) {
return syscall.AF_INET
}
return syscall.AF_INET6
}

View file

@ -0,0 +1,25 @@
//go:build !linux
package redir
import (
"net"
"net/netip"
"os"
)
func TProxy(fd uintptr, isIPv6 bool) error {
return os.ErrInvalid
}
func TProxyUDP(fd uintptr, isIPv6 bool) error {
return os.ErrInvalid
}
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
return netip.AddrPort{}, os.ErrInvalid
}
func DialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
return nil, os.ErrInvalid
}

View file

@ -8,4 +8,7 @@ const (
TypeMixed = "mixed" TypeMixed = "mixed"
TypeShadowsocks = "shadowsocks" TypeShadowsocks = "shadowsocks"
TypeTun = "tun" TypeTun = "tun"
TypeRedirect = "redirect"
TypeTProxy = "tproxy"
TypeDNS = "dns"
) )

View file

@ -0,0 +1,44 @@
`dns` inbound is a DNS server.
### Structure
```json
{
"inbounds": [
{
"type": "dns",
"tag": "dns-in",
"listen": "::",
"listen_port": 5353,
"network": "udp"
}
]
}
```
!!! note ""
There are no outbound connections by the DNS inbound, all requests are handled internally.
### Listen Fields
#### listen
==Required==
Listen address.
#### listen_port
==Required==
Listen port.
### DNS Fields
#### network
Listen network, one of `tcp` `udp`.
Both if empty.

View file

@ -15,12 +15,15 @@
| Type | Format | | Type | Format |
|---------------|------------------------------| |---------------|------------------------------|
| `tun` | [Tun](./tun) |
| `direct` | [Direct](./direct) | | `direct` | [Direct](./direct) |
| `mixed` | [Mixed](./mixed) | | `mixed` | [Mixed](./mixed) |
| `socks` | [Socks](./socks) | | `socks` | [Socks](./socks) |
| `http` | [HTTP](./http) | | `http` | [HTTP](./http) |
| `shadowsocks` | [Shadowsocks](./shadowsocks) | | `shadowsocks` | [Shadowsocks](./shadowsocks) |
| `tun` | [Tun](./tun) |
| `redirect` | [Redirect](./redirect) |
| `tproxy` | [TProxy](./tproxy) |
| `dns` | [DNS](./dns) |
#### tag #### tag

View file

@ -0,0 +1,61 @@
`redirect` inbound is a linux Redirect server.
### Structure
```json
{
"inbounds": [
{
"type": "redirect",
"tag": "redirect-in",
"listen": "::",
"listen_port": 5353,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"udp_timeout": 300
}
]
}
```
### Listen Fields
#### listen
==Required==
Listen address.
#### listen_port
==Required==
Listen port.
#### sniff
Enable sniffing.
Reads domain names for routing, supports HTTP TLS for TCP, QUIC for UDP.
This does not break zero copy, like splice.
#### sniff_override_destination
Override the connection destination address with the sniffed domain.
If the domain name is invalid (like tor), this will not work.
#### domain_strategy
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
If set, the requested domain name will be resolved to IP before routing.
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
#### udp_timeout
UDP NAT expiration time in seconds, default is 300 (5 minutes).

View file

@ -0,0 +1,71 @@
`tproxy` inbound is a linux TProxy server.
### Structure
```json
{
"inbounds": [
{
"type": "tproxy",
"tag": "tproxy-in",
"listen": "::",
"listen_port": 5353,
"sniff": false,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv6",
"udp_timeout": 300,
"network": "udp"
}
]
}
```
### Listen Fields
#### listen
==Required==
Listen address.
#### listen_port
==Required==
Listen port.
#### sniff
Enable sniffing.
Reads domain names for routing, supports HTTP TLS for TCP, QUIC for UDP.
This does not break zero copy, like splice.
#### sniff_override_destination
Override the connection destination address with the sniffed domain.
If the domain name is invalid (like tor), this will not work.
#### domain_strategy
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
If set, the requested domain name will be resolved to IP before routing.
If `sniff_override_destination` is in effect, its value will be taken as a fallback.
#### udp_timeout
UDP NAT expiration time in seconds, default is 300 (5 minutes).
### TProxy Fields
#### network
Listen network, one of `tcp` `udp`.
Both if empty.

View file

@ -28,6 +28,12 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions) return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions)
case C.TypeTun: case C.TypeTun:
return NewTun(ctx, router, logger, options.Tag, options.TunOptions) return NewTun(ctx, router, logger, options.Tag, options.TunOptions)
case C.TypeRedirect:
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
case C.TypeTProxy:
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
case C.TypeDNS:
return NewDNS(ctx, router, logger, options.Tag, options.DNSOptions), nil
default: default:
return nil, E.New("unknown inbound type: ", options.Type) return nil, E.New("unknown inbound type: ", options.Type)
} }

View file

@ -26,16 +26,17 @@ import (
var _ adapter.Inbound = (*myInboundAdapter)(nil) var _ adapter.Inbound = (*myInboundAdapter)(nil)
type myInboundAdapter struct { type myInboundAdapter struct {
protocol string protocol string
network []string network []string
ctx context.Context ctx context.Context
router adapter.Router router adapter.Router
logger log.ContextLogger logger log.ContextLogger
tag string tag string
listenOptions option.ListenOptions listenOptions option.ListenOptions
connHandler adapter.ConnectionHandler connHandler adapter.ConnectionHandler
packetHandler adapter.PacketHandler packetHandler adapter.PacketHandler
packetUpstream any oobPacketHandler adapter.OOBPacketHandler
packetUpstream any
// http mixed // http mixed
@ -85,12 +86,20 @@ func (a *myInboundAdapter) Start() error {
a.packetForce6 = M.SocksaddrFromNet(udpConn.LocalAddr()).Addr.Is6() a.packetForce6 = M.SocksaddrFromNet(udpConn.LocalAddr()).Addr.Is6()
a.packetOutboundClosed = make(chan struct{}) a.packetOutboundClosed = make(chan struct{})
a.packetOutbound = make(chan *myInboundPacket) a.packetOutbound = make(chan *myInboundPacket)
if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { if a.oobPacketHandler != nil {
go a.loopUDPIn() if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler {
go a.loopUDPOOBIn()
} else {
go a.loopUDPOOBInThreadSafe()
}
} else { } else {
go a.loopUDPInThreadSafe() if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler {
go a.loopUDPIn()
} else {
go a.loopUDPInThreadSafe()
}
go a.loopUDPOut()
} }
go a.loopUDPOut()
a.logger.Info("udp server started at ", udpConn.LocalAddr()) a.logger.Info("udp server started at ", udpConn.LocalAddr())
} }
if a.setSystemProxy { if a.setSystemProxy {
@ -194,6 +203,37 @@ func (a *myInboundAdapter) loopUDPIn() {
} }
} }
func (a *myInboundAdapter) loopUDPOOBIn() {
defer close(a.packetOutboundClosed)
_buffer := buf.StackNewPacket()
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
buffer.IncRef()
defer buffer.DecRef()
packetService := (*myInboundPacketAdapter)(a)
oob := make([]byte, 1024)
for {
buffer.Reset()
n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
if err != nil {
return
}
buffer.Truncate(n)
var metadata adapter.InboundContext
metadata.Inbound = a.tag
metadata.SniffEnabled = a.listenOptions.SniffEnabled
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
metadata.Network = C.NetworkUDP
metadata.Source = M.SocksaddrFromNetIP(addr)
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
if err != nil {
a.newError(E.Cause(err, "process packet from ", metadata.Source))
}
}
}
func (a *myInboundAdapter) loopUDPInThreadSafe() { func (a *myInboundAdapter) loopUDPInThreadSafe() {
defer close(a.packetOutboundClosed) defer close(a.packetOutboundClosed)
packetService := (*myInboundPacketAdapter)(a) packetService := (*myInboundPacketAdapter)(a)
@ -220,6 +260,33 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() {
} }
} }
func (a *myInboundAdapter) loopUDPOOBInThreadSafe() {
defer close(a.packetOutboundClosed)
packetService := (*myInboundPacketAdapter)(a)
oob := make([]byte, 1024)
for {
buffer := buf.NewPacket()
n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
if err != nil {
buffer.Release()
return
}
buffer.Truncate(n)
var metadata adapter.InboundContext
metadata.Inbound = a.tag
metadata.SniffEnabled = a.listenOptions.SniffEnabled
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
metadata.Network = C.NetworkUDP
metadata.Source = M.SocksaddrFromNetIP(addr)
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
if err != nil {
buffer.Release()
a.newError(E.Cause(err, "process packet from ", metadata.Source))
}
}
}
func (a *myInboundAdapter) loopUDPOut() { func (a *myInboundAdapter) loopUDPOut() {
for { for {
select { select {

View file

@ -46,7 +46,13 @@ func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLog
inbound.overrideOption = 3 inbound.overrideOption = 3
inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort}
} }
inbound.udpNat = udpnat.New[netip.AddrPort](options.UDPTimeout, inbound.upstreamContextHandler()) var udpTimeout int64
if options.UDPTimeout != 0 {
udpTimeout = options.UDPTimeout
} else {
udpTimeout = 300
}
inbound.udpNat = udpnat.New[netip.AddrPort](udpTimeout, inbound.upstreamContextHandler())
inbound.connHandler = inbound inbound.connHandler = inbound
inbound.packetHandler = inbound inbound.packetHandler = inbound
inbound.packetUpstream = inbound.udpNat inbound.packetUpstream = inbound.udpNat
@ -79,6 +85,8 @@ func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.B
case 3: case 3:
metadata.Destination.Port = d.overrideDestination.Port metadata.Destination.Port = d.overrideDestination.Port
} }
d.udpNat.NewPacketDirect(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), metadata.Source.AddrPort(), conn, buffer, adapter.UpstreamMetadata(metadata)) d.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) {
return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), natConn
})
return nil return nil
} }

View file

@ -5,16 +5,59 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"net" "net"
"net/netip"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/udpnat"
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
) )
type DNS struct {
myInboundAdapter
udpNat *udpnat.Service[netip.AddrPort]
}
func NewDNS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DNSInboundOptions) *DNS {
dns := &DNS{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeTProxy,
network: options.Network.Build(),
ctx: ctx,
router: router,
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
}
dns.connHandler = dns
dns.packetHandler = dns
dns.udpNat = udpnat.New[netip.AddrPort](10, adapter.NewUpstreamContextHandler(nil, dns.newPacketConnection, dns))
dns.packetUpstream = dns.udpNat
return dns
}
func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewDNSConnection(ctx, d.router, d.logger, conn, metadata)
}
func (d *DNS) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
d.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) {
return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), natConn
})
return nil
}
func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewDNSPacketConnection(ctx, d.router, d.logger, conn, metadata)
}
func NewDNSConnection(ctx context.Context, router adapter.Router, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error { func NewDNSConnection(ctx context.Context, router adapter.Router, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata) ctx = adapter.WithContext(ctx, &metadata)
_buffer := buf.StackNewSize(1024) _buffer := buf.StackNewSize(1024)

43
inbound/redirect.go Normal file
View file

@ -0,0 +1,43 @@
package inbound
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
type Redirect struct {
myInboundAdapter
}
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect {
redirect := &Redirect{
myInboundAdapter{
protocol: C.TypeRedirect,
network: []string{C.NetworkTCP},
ctx: ctx,
router: router,
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
}
redirect.connHandler = redirect
return redirect
}
func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
destination, err := redir.GetOriginalDestination(conn)
if err != nil {
return E.Cause(err, "get redirect destination")
}
metadata.Destination = M.SocksaddrFromNetIP(destination)
return r.newConnection(ctx, conn, metadata)
}

108
inbound/tproxy.go Normal file
View file

@ -0,0 +1,108 @@
package inbound
import (
"context"
"net"
"net/netip"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/udpnat"
)
type TProxy struct {
myInboundAdapter
udpNat *udpnat.Service[netip.AddrPort]
}
func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy {
tproxy := &TProxy{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeTProxy,
network: options.Network.Build(),
ctx: ctx,
router: router,
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
}
var udpTimeout int64
if options.UDPTimeout != 0 {
udpTimeout = options.UDPTimeout
} else {
udpTimeout = 300
}
tproxy.connHandler = tproxy
tproxy.oobPacketHandler = tproxy
tproxy.udpNat = udpnat.New[netip.AddrPort](udpTimeout, tproxy.upstreamContextHandler())
tproxy.packetUpstream = tproxy.udpNat
return tproxy
}
func (t *TProxy) Start() error {
err := t.myInboundAdapter.Start()
if err != nil {
return err
}
if t.tcpListener != nil {
tcpFd, err := common.GetFileDescriptor(t.tcpListener)
if err != nil {
return err
}
err = redir.TProxy(tcpFd, M.SocksaddrFromNet(t.tcpListener.Addr()).Addr.Is6())
if err != nil {
return E.Cause(err, "configure tproxy TCP listener")
}
}
if t.udpConn != nil {
udpFd, err := common.GetFileDescriptor(t.udpConn)
if err != nil {
return err
}
err = redir.TProxyUDP(udpFd, M.SocksaddrFromNet(t.udpConn.LocalAddr()).Addr.Is6())
if err != nil {
return E.Cause(err, "configure tproxy UDP listener")
}
}
return nil
}
func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr())
return t.newConnection(ctx, conn, metadata)
}
func (t *TProxy) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata adapter.InboundContext) error {
destination, err := redir.GetOriginalDestinationFromOOB(oob)
if err != nil {
return E.Cause(err, "get tproxy destination")
}
metadata.Destination = M.SocksaddrFromNetIP(destination)
t.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) {
return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{natConn}
})
return nil
}
type tproxyPacketWriter struct {
source N.PacketConn
}
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
udpConn, err := redir.DialUDP(destination.UDPAddr(), M.SocksaddrFromNet(w.source.LocalAddr()).UDPAddr())
if err != nil {
return E.Cause(err, "tproxy udp write back")
}
defer udpConn.Close()
return common.Error(udpConn.Write(buffer.Bytes()))
}

View file

@ -40,12 +40,15 @@ nav:
- DNS Rule: configuration/dns/rule.md - DNS Rule: configuration/dns/rule.md
- Inbound: - Inbound:
- configuration/inbound/index.md - configuration/inbound/index.md
- Tun: configuration/inbound/tun.md
- Direct: configuration/inbound/direct.md - Direct: configuration/inbound/direct.md
- Mixed: configuration/inbound/mixed.md - Mixed: configuration/inbound/mixed.md
- Socks: configuration/inbound/socks.md - Socks: configuration/inbound/socks.md
- HTTP: configuration/inbound/http.md - HTTP: configuration/inbound/http.md
- Shadowsocks: configuration/inbound/shadowsocks.md - Shadowsocks: configuration/inbound/shadowsocks.md
- Tun: configuration/inbound/tun.md
- Redirect: configuration/inbound/redirect.md
- TProxy: configuration/inbound/tproxy.md
- DNS: configuration/inbound/dns.md
- Outbound: - Outbound:
- configuration/outbound/index.md - configuration/outbound/index.md
- Direct: configuration/outbound/direct.md - Direct: configuration/outbound/direct.md

View file

@ -18,6 +18,9 @@ type _Inbound struct {
MixedOptions HTTPMixedInboundOptions `json:"-"` MixedOptions HTTPMixedInboundOptions `json:"-"`
ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
TunOptions TunInboundOptions `json:"-"` TunOptions TunInboundOptions `json:"-"`
RedirectOptions RedirectInboundOptions `json:"-"`
TProxyOptions TProxyInboundOptions `json:"-"`
DNSOptions DNSInboundOptions `json:"-"`
} }
type Inbound _Inbound type Inbound _Inbound
@ -30,7 +33,10 @@ func (h Inbound) Equals(other Inbound) bool {
h.HTTPOptions.Equals(other.HTTPOptions) && h.HTTPOptions.Equals(other.HTTPOptions) &&
h.MixedOptions.Equals(other.MixedOptions) && h.MixedOptions.Equals(other.MixedOptions) &&
h.ShadowsocksOptions.Equals(other.ShadowsocksOptions) && h.ShadowsocksOptions.Equals(other.ShadowsocksOptions) &&
h.TunOptions == other.TunOptions h.TunOptions == other.TunOptions &&
h.RedirectOptions == other.RedirectOptions &&
h.TProxyOptions == other.TProxyOptions &&
h.DNSOptions == other.DNSOptions
} }
func (h Inbound) MarshalJSON() ([]byte, error) { func (h Inbound) MarshalJSON() ([]byte, error) {
@ -48,6 +54,12 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
v = h.ShadowsocksOptions v = h.ShadowsocksOptions
case C.TypeTun: case C.TypeTun:
v = h.TunOptions v = h.TunOptions
case C.TypeRedirect:
v = h.RedirectOptions
case C.TypeTProxy:
v = h.TProxyOptions
case C.TypeDNS:
v = h.DNSOptions
default: default:
return nil, E.New("unknown inbound type: ", h.Type) return nil, E.New("unknown inbound type: ", h.Type)
} }
@ -73,6 +85,12 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
v = &h.ShadowsocksOptions v = &h.ShadowsocksOptions
case C.TypeTun: case C.TypeTun:
v = &h.TunOptions v = &h.TunOptions
case C.TypeRedirect:
v = &h.RedirectOptions
case C.TypeTProxy:
v = &h.TProxyOptions
case C.TypeDNS:
v = &h.DNSOptions
default: default:
return nil return nil
} }
@ -164,3 +182,17 @@ type TunInboundOptions struct {
HijackDNS bool `json:"hijack_dns,omitempty"` HijackDNS bool `json:"hijack_dns,omitempty"`
InboundOptions InboundOptions
} }
type RedirectInboundOptions struct {
ListenOptions
}
type TProxyInboundOptions struct {
ListenOptions
Network NetworkList `json:"network,omitempty"`
}
type DNSInboundOptions struct {
ListenOptions
Network NetworkList `json:"network,omitempty"`
}

View file

@ -13,13 +13,13 @@ import (
"testing" "testing"
"time" "time"
"github.com/sagernet/sing-box/log"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/sagernet/sing-box/log"
) )
// kanged from clash // kanged from clash