mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 00:21:30 +00:00
Add redir tproxy and dns inbound
This commit is contained in:
parent
e13b72afca
commit
2c2eb31e18
|
@ -17,6 +17,10 @@ type PacketHandler interface {
|
|||
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 {
|
||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||
}
|
||||
|
|
37
common/redir/redir_linux.go
Normal file
37
common/redir/redir_linux.go
Normal 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
|
||||
}
|
||||
}
|
13
common/redir/redir_other.go
Normal file
13
common/redir/redir_other.go
Normal 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
|
||||
}
|
132
common/redir/tproxy_linux.go
Normal file
132
common/redir/tproxy_linux.go
Normal 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
|
||||
}
|
25
common/redir/tproxy_other.go
Normal file
25
common/redir/tproxy_other.go
Normal 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
|
||||
}
|
|
@ -8,4 +8,7 @@ const (
|
|||
TypeMixed = "mixed"
|
||||
TypeShadowsocks = "shadowsocks"
|
||||
TypeTun = "tun"
|
||||
TypeRedirect = "redirect"
|
||||
TypeTProxy = "tproxy"
|
||||
TypeDNS = "dns"
|
||||
)
|
||||
|
|
44
docs/configuration/inbound/dns.md
Normal file
44
docs/configuration/inbound/dns.md
Normal 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.
|
|
@ -15,12 +15,15 @@
|
|||
|
||||
| Type | Format |
|
||||
|---------------|------------------------------|
|
||||
| `tun` | [Tun](./tun) |
|
||||
| `direct` | [Direct](./direct) |
|
||||
| `mixed` | [Mixed](./mixed) |
|
||||
| `socks` | [Socks](./socks) |
|
||||
| `http` | [HTTP](./http) |
|
||||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||
| `tun` | [Tun](./tun) |
|
||||
| `redirect` | [Redirect](./redirect) |
|
||||
| `tproxy` | [TProxy](./tproxy) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
|
61
docs/configuration/inbound/redirect.md
Normal file
61
docs/configuration/inbound/redirect.md
Normal 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).
|
71
docs/configuration/inbound/tproxy.md
Normal file
71
docs/configuration/inbound/tproxy.md
Normal 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.
|
|
@ -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)
|
||||
case C.TypeTun:
|
||||
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:
|
||||
return nil, E.New("unknown inbound type: ", options.Type)
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ type myInboundAdapter struct {
|
|||
listenOptions option.ListenOptions
|
||||
connHandler adapter.ConnectionHandler
|
||||
packetHandler adapter.PacketHandler
|
||||
oobPacketHandler adapter.OOBPacketHandler
|
||||
packetUpstream any
|
||||
|
||||
// http mixed
|
||||
|
@ -85,12 +86,20 @@ func (a *myInboundAdapter) Start() error {
|
|||
a.packetForce6 = M.SocksaddrFromNet(udpConn.LocalAddr()).Addr.Is6()
|
||||
a.packetOutboundClosed = make(chan struct{})
|
||||
a.packetOutbound = make(chan *myInboundPacket)
|
||||
if a.oobPacketHandler != nil {
|
||||
if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler {
|
||||
go a.loopUDPOOBIn()
|
||||
} else {
|
||||
go a.loopUDPOOBInThreadSafe()
|
||||
}
|
||||
} else {
|
||||
if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler {
|
||||
go a.loopUDPIn()
|
||||
} else {
|
||||
go a.loopUDPInThreadSafe()
|
||||
}
|
||||
go a.loopUDPOut()
|
||||
}
|
||||
a.logger.Info("udp server started at ", udpConn.LocalAddr())
|
||||
}
|
||||
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() {
|
||||
defer close(a.packetOutboundClosed)
|
||||
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() {
|
||||
for {
|
||||
select {
|
||||
|
|
|
@ -46,7 +46,13 @@ func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLog
|
|||
inbound.overrideOption = 3
|
||||
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.packetHandler = inbound
|
||||
inbound.packetUpstream = inbound.udpNat
|
||||
|
@ -79,6 +85,8 @@ func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.B
|
|||
case 3:
|
||||
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
|
||||
}
|
||||
|
|
|
@ -5,16 +5,59 @@ import (
|
|||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
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"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/udpnat"
|
||||
|
||||
"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 {
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
_buffer := buf.StackNewSize(1024)
|
||||
|
|
43
inbound/redirect.go
Normal file
43
inbound/redirect.go
Normal 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
108
inbound/tproxy.go
Normal 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()))
|
||||
}
|
|
@ -40,12 +40,15 @@ nav:
|
|||
- DNS Rule: configuration/dns/rule.md
|
||||
- Inbound:
|
||||
- configuration/inbound/index.md
|
||||
- Tun: configuration/inbound/tun.md
|
||||
- Direct: configuration/inbound/direct.md
|
||||
- Mixed: configuration/inbound/mixed.md
|
||||
- Socks: configuration/inbound/socks.md
|
||||
- HTTP: configuration/inbound/http.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:
|
||||
- configuration/outbound/index.md
|
||||
- Direct: configuration/outbound/direct.md
|
||||
|
|
|
@ -18,6 +18,9 @@ type _Inbound struct {
|
|||
MixedOptions HTTPMixedInboundOptions `json:"-"`
|
||||
ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
|
||||
TunOptions TunInboundOptions `json:"-"`
|
||||
RedirectOptions RedirectInboundOptions `json:"-"`
|
||||
TProxyOptions TProxyInboundOptions `json:"-"`
|
||||
DNSOptions DNSInboundOptions `json:"-"`
|
||||
}
|
||||
|
||||
type Inbound _Inbound
|
||||
|
@ -30,7 +33,10 @@ func (h Inbound) Equals(other Inbound) bool {
|
|||
h.HTTPOptions.Equals(other.HTTPOptions) &&
|
||||
h.MixedOptions.Equals(other.MixedOptions) &&
|
||||
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) {
|
||||
|
@ -48,6 +54,12 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
|
|||
v = h.ShadowsocksOptions
|
||||
case C.TypeTun:
|
||||
v = h.TunOptions
|
||||
case C.TypeRedirect:
|
||||
v = h.RedirectOptions
|
||||
case C.TypeTProxy:
|
||||
v = h.TProxyOptions
|
||||
case C.TypeDNS:
|
||||
v = h.DNSOptions
|
||||
default:
|
||||
return nil, E.New("unknown inbound type: ", h.Type)
|
||||
}
|
||||
|
@ -73,6 +85,12 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
|||
v = &h.ShadowsocksOptions
|
||||
case C.TypeTun:
|
||||
v = &h.TunOptions
|
||||
case C.TypeRedirect:
|
||||
v = &h.RedirectOptions
|
||||
case C.TypeTProxy:
|
||||
v = &h.TProxyOptions
|
||||
case C.TypeDNS:
|
||||
v = &h.DNSOptions
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
@ -164,3 +182,17 @@ type TunInboundOptions struct {
|
|||
HijackDNS bool `json:"hijack_dns,omitempty"`
|
||||
InboundOptions
|
||||
}
|
||||
|
||||
type RedirectInboundOptions struct {
|
||||
ListenOptions
|
||||
}
|
||||
|
||||
type TProxyInboundOptions struct {
|
||||
ListenOptions
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
}
|
||||
|
||||
type DNSInboundOptions struct {
|
||||
ListenOptions
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
// kanged from clash
|
||||
|
|
Loading…
Reference in a new issue