mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 16:30:07 +08:00
Some checks failed
Test / test (1.20, macos-13) (push) Waiting to run
Test / test (1.20, macos-latest) (push) Waiting to run
Test / test (1.20, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.20, windows-latest) (push) Waiting to run
Test / test (1.21, macos-13) (push) Waiting to run
Test / test (1.21, macos-latest) (push) Waiting to run
Test / test (1.21, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.21, windows-latest) (push) Waiting to run
Test / test (1.22, macos-13) (push) Waiting to run
Test / test (1.22, macos-latest) (push) Waiting to run
Test / test (1.22, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.22, windows-latest) (push) Waiting to run
Test / test (1.23, macos-13) (push) Waiting to run
Test / test (1.23, macos-latest) (push) Waiting to run
Test / test (1.23, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.23, windows-latest) (push) Waiting to run
Test / test (1.24, macos-13) (push) Waiting to run
Test / test (1.24, macos-latest) (push) Waiting to run
Test / test (1.24, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.24, windows-latest) (push) Waiting to run
Test / test (1.20, ubuntu-latest) (push) Failing after 1s
Test / test (1.21, ubuntu-latest) (push) Failing after 1s
Test / test (1.22, ubuntu-latest) (push) Failing after 1s
Test / test (1.23, ubuntu-latest) (push) Failing after 1s
Test / test (1.24, ubuntu-latest) (push) Failing after 1s
Trigger CMFA Update / trigger-CMFA-update (push) Failing after 1s
The DNS resolution of the overall UDP part has been delayed to the connection initiation stage. During the rule matching process, it will only be triggered when the IP rule without no-resolve is matched. For direct and wireguard outbound, the same logic as the TCP part will be followed, that is, when direct-nameserver (or DNS configured by wireguard) exists, the result of the matching process will be discarded and the domain name will be re-resolved. This re-resolution logic is only effective for fakeip. For reject and DNS outbound, no resolution is required. For other outbound, resolution will still be performed when the connection is initiated, and the domain name will not be sent directly to the remote server at present.
267 lines
6.7 KiB
Go
267 lines
6.7 KiB
Go
package outbound
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/netip"
|
|
"strconv"
|
|
|
|
N "github.com/metacubex/mihomo/common/net"
|
|
"github.com/metacubex/mihomo/component/ca"
|
|
"github.com/metacubex/mihomo/component/dialer"
|
|
"github.com/metacubex/mihomo/component/proxydialer"
|
|
C "github.com/metacubex/mihomo/constant"
|
|
"github.com/metacubex/mihomo/transport/socks5"
|
|
)
|
|
|
|
type Socks5 struct {
|
|
*Base
|
|
option *Socks5Option
|
|
user string
|
|
pass string
|
|
tls bool
|
|
skipCertVerify bool
|
|
tlsConfig *tls.Config
|
|
}
|
|
|
|
type Socks5Option struct {
|
|
BasicOption
|
|
Name string `proxy:"name"`
|
|
Server string `proxy:"server"`
|
|
Port int `proxy:"port"`
|
|
UserName string `proxy:"username,omitempty"`
|
|
Password string `proxy:"password,omitempty"`
|
|
TLS bool `proxy:"tls,omitempty"`
|
|
UDP bool `proxy:"udp,omitempty"`
|
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
}
|
|
|
|
// StreamConnContext implements C.ProxyAdapter
|
|
func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
if ss.tls {
|
|
cc := tls.Client(c, ss.tlsConfig)
|
|
err := cc.HandshakeContext(ctx)
|
|
c = cc
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
|
}
|
|
}
|
|
|
|
var user *socks5.User
|
|
if ss.user != "" {
|
|
user = &socks5.User{
|
|
Username: ss.user,
|
|
Password: ss.pass,
|
|
}
|
|
}
|
|
if _, err := ss.clientHandshakeContext(ctx, c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// DialContext implements C.ProxyAdapter
|
|
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata)
|
|
}
|
|
|
|
// DialContextWithDialer implements C.ProxyAdapter
|
|
func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
|
if len(ss.option.DialerProxy) > 0 {
|
|
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
|
}
|
|
|
|
defer func(c net.Conn) {
|
|
safeConnClose(c, err)
|
|
}(c)
|
|
|
|
c, err = ss.StreamConnContext(ctx, c, metadata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewConn(c, ss), nil
|
|
}
|
|
|
|
// SupportWithDialer implements C.ProxyAdapter
|
|
func (ss *Socks5) SupportWithDialer() C.NetWork {
|
|
return C.TCP
|
|
}
|
|
|
|
// ListenPacketContext implements C.ProxyAdapter
|
|
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
|
var cDialer C.Dialer = dialer.NewDialer(ss.DialOptions()...)
|
|
if len(ss.option.DialerProxy) > 0 {
|
|
cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err = ss.ResolveUDP(ctx, metadata); err != nil {
|
|
return nil, err
|
|
}
|
|
c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
|
|
if err != nil {
|
|
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
|
return
|
|
}
|
|
|
|
if ss.tls {
|
|
cc := tls.Client(c, ss.tlsConfig)
|
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
|
defer cancel()
|
|
err = cc.HandshakeContext(ctx)
|
|
c = cc
|
|
}
|
|
|
|
defer func(c net.Conn) {
|
|
safeConnClose(c, err)
|
|
}(c)
|
|
|
|
var user *socks5.User
|
|
if ss.user != "" {
|
|
user = &socks5.User{
|
|
Username: ss.user,
|
|
Password: ss.pass,
|
|
}
|
|
}
|
|
|
|
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
|
bindAddr, err := ss.clientHandshakeContext(ctx, c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
|
|
if err != nil {
|
|
err = fmt.Errorf("client hanshake error: %w", err)
|
|
return
|
|
}
|
|
|
|
// Support unspecified UDP bind address.
|
|
bindUDPAddr := bindAddr.UDPAddr()
|
|
if bindUDPAddr == nil {
|
|
err = errors.New("invalid UDP bind address")
|
|
return
|
|
} else if bindUDPAddr.IP.IsUnspecified() {
|
|
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr(), C.IPv4Prefer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bindUDPAddr.IP = serverAddr.IP
|
|
}
|
|
|
|
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
io.Copy(io.Discard, c)
|
|
c.Close()
|
|
// A UDP association terminates when the TCP connection that the UDP
|
|
// ASSOCIATE request arrived on terminates. RFC1928
|
|
pc.Close()
|
|
}()
|
|
|
|
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
|
}
|
|
|
|
// ProxyInfo implements C.ProxyAdapter
|
|
func (ss *Socks5) ProxyInfo() C.ProxyInfo {
|
|
info := ss.Base.ProxyInfo()
|
|
info.DialerProxy = ss.option.DialerProxy
|
|
return info
|
|
}
|
|
|
|
func (ss *Socks5) clientHandshakeContext(ctx context.Context, c net.Conn, addr socks5.Addr, command socks5.Command, user *socks5.User) (_ socks5.Addr, err error) {
|
|
if ctx.Done() != nil {
|
|
done := N.SetupContextForConn(ctx, c)
|
|
defer done(&err)
|
|
}
|
|
return socks5.ClientHandshake(c, addr, command, user)
|
|
}
|
|
|
|
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
|
var tlsConfig *tls.Config
|
|
if option.TLS {
|
|
tlsConfig = &tls.Config{
|
|
InsecureSkipVerify: option.SkipCertVerify,
|
|
ServerName: option.Server,
|
|
}
|
|
|
|
var err error
|
|
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &Socks5{
|
|
Base: &Base{
|
|
name: option.Name,
|
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
|
tp: C.Socks5,
|
|
udp: option.UDP,
|
|
tfo: option.TFO,
|
|
mpTcp: option.MPTCP,
|
|
iface: option.Interface,
|
|
rmark: option.RoutingMark,
|
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
},
|
|
option: &option,
|
|
user: option.UserName,
|
|
pass: option.Password,
|
|
tls: option.TLS,
|
|
skipCertVerify: option.SkipCertVerify,
|
|
tlsConfig: tlsConfig,
|
|
}, nil
|
|
}
|
|
|
|
type socksPacketConn struct {
|
|
net.PacketConn
|
|
rAddr net.Addr
|
|
tcpConn net.Conn
|
|
}
|
|
|
|
func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
|
}
|
|
|
|
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|
n, _, e := uc.PacketConn.ReadFrom(b)
|
|
if e != nil {
|
|
return 0, nil, e
|
|
}
|
|
addr, payload, err := socks5.DecodeUDPPacket(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
udpAddr := addr.UDPAddr()
|
|
if udpAddr == nil {
|
|
return 0, nil, errors.New("parse udp addr error")
|
|
}
|
|
|
|
// due to DecodeUDPPacket is mutable, record addr length
|
|
copy(b, payload)
|
|
return n - len(addr) - 3, udpAddr, nil
|
|
}
|
|
|
|
func (uc *socksPacketConn) Close() error {
|
|
uc.tcpConn.Close()
|
|
return uc.PacketConn.Close()
|
|
}
|