mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-11-16 11:42:43 +08:00
feat: add udp-over-stream
for tuic
only work with meta tuic server or sing-box 1.4.0-beta.6
This commit is contained in:
parent
b8a60261ef
commit
1181fd4560
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
@ -15,12 +16,15 @@ import (
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/tuic"
|
"github.com/Dreamacro/clash/transport/tuic"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tuic struct {
|
type Tuic struct {
|
||||||
|
@ -59,6 +63,9 @@ type TuicOption struct {
|
||||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
|
|
||||||
|
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
|
||||||
|
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
|
@ -82,6 +89,32 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op
|
||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||||
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
|
if t.option.UDPOverStream {
|
||||||
|
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
|
||||||
|
uotMetadata := *metadata
|
||||||
|
uotMetadata.Host = uotDestination.Fqdn
|
||||||
|
uotMetadata.DstPort = uotDestination.Port
|
||||||
|
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||||
|
if !metadata.Resolved() {
|
||||||
|
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("can't resolve ip")
|
||||||
|
}
|
||||||
|
metadata.DstIP = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||||
|
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
|
||||||
|
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
|
||||||
|
} else {
|
||||||
|
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -239,6 +272,14 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch option.UDPOverStreamVersion {
|
||||||
|
case uot.Version, uot.LegacyVersion:
|
||||||
|
case 0:
|
||||||
|
option.UDPOverStreamVersion = uot.LegacyVersion
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
|
||||||
|
}
|
||||||
|
|
||||||
t := &Tuic{
|
t := &Tuic{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
|
|
@ -681,6 +681,11 @@ proxies: # socks5
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||||
# sni: example.com
|
# sni: example.com
|
||||||
|
#
|
||||||
|
# meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效
|
||||||
|
# 警告,与原版tuic不兼容!!!
|
||||||
|
# udp-over-stream: false
|
||||||
|
# udp-over-stream-version: 1
|
||||||
|
|
||||||
# ShadowsocksR
|
# ShadowsocksR
|
||||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||||
|
|
|
@ -72,7 +72,27 @@ func UpstreamMetadata(metadata M.Metadata) M.Metadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func ConvertMetadata(metadata *C.Metadata) M.Metadata {
|
||||||
|
return M.Metadata{
|
||||||
|
Protocol: metadata.Type.String(),
|
||||||
|
Source: M.SocksaddrFrom(metadata.SrcIP, metadata.SrcPort),
|
||||||
|
Destination: M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) IsSpecialFqdn(fqdn string) bool {
|
||||||
|
switch fqdn {
|
||||||
|
case mux.Destination.Fqdn:
|
||||||
|
case vmess.MuxDestination.Fqdn:
|
||||||
|
case uot.MagicAddress:
|
||||||
|
case uot.LegacyMagicAddress:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) ParseSpecialFqdn(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
switch metadata.Destination.Fqdn {
|
switch metadata.Destination.Fqdn {
|
||||||
case mux.Destination.Fqdn:
|
case mux.Destination.Fqdn:
|
||||||
return mux.HandleConnection(ctx, h, log.SingLogger, conn, UpstreamMetadata(metadata))
|
return mux.HandleConnection(ctx, h, log.SingLogger, conn, UpstreamMetadata(metadata))
|
||||||
|
@ -89,6 +109,13 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
|
||||||
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||||
return h.NewPacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
return h.NewPacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
||||||
}
|
}
|
||||||
|
return errors.New("not special fqdn")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
if h.IsSpecialFqdn(metadata.Destination.Fqdn) {
|
||||||
|
return h.ParseSpecialFqdn(ctx, conn, metadata)
|
||||||
|
}
|
||||||
target := socks5.ParseAddr(metadata.Destination.String())
|
target := socks5.ParseAddr(metadata.Destination.String())
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
defer wg.Wait() // this goroutine must exit after conn.Close()
|
defer wg.Wait() // this goroutine must exit after conn.Close()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package tuic
|
package tuic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"github.com/Dreamacro/clash/common/sockopt"
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
LC "github.com/Dreamacro/clash/listener/config"
|
LC "github.com/Dreamacro/clash/listener/config"
|
||||||
|
"github.com/Dreamacro/clash/listener/sing"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/tuic"
|
"github.com/Dreamacro/clash/transport/tuic"
|
||||||
|
@ -36,6 +38,12 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||||
inbound.WithSpecialRules(""),
|
inbound.WithSpecialRules(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
h := &sing.ListenerHandler{
|
||||||
|
TcpIn: tcpIn,
|
||||||
|
UdpIn: udpIn,
|
||||||
|
Type: C.TUIC,
|
||||||
|
Additions: additions,
|
||||||
|
}
|
||||||
cert, err := CN.ParseCert(config.Certificate, config.PrivateKey)
|
cert, err := CN.ParseCert(config.Certificate, config.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -86,7 +94,19 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||||
newAdditions = slices.Clone(additions)
|
newAdditions = slices.Clone(additions)
|
||||||
newAdditions = append(newAdditions, _additions...)
|
newAdditions = append(newAdditions, _additions...)
|
||||||
}
|
}
|
||||||
tcpIn <- inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
connCtx := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
||||||
|
metadata := sing.ConvertMetadata(connCtx.Metadata())
|
||||||
|
if h.IsSpecialFqdn(metadata.Destination.Fqdn) {
|
||||||
|
go func() { // ParseSpecialFqdn will block, so open a new goroutine
|
||||||
|
_ = h.ParseSpecialFqdn(
|
||||||
|
sing.WithAdditions(context.Background(), newAdditions...),
|
||||||
|
conn,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tcpIn <- connCtx
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error {
|
handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user