Feature Request: Option to Force SOCKS5 Connections by IP Instead of Domain (and Additional FakeIP Controls) #2369
Replies: 2 comments
-
|
I tried using the Grok AI to patch the socks5 code and add a use-ip: true/false parameter, and I managed to get it working. I tested it locally, and at first glance it seems to work without any issues. Below is the modified socks5.go file. I’m not claiming that this is the correct or optimal implementation — you will certainly know better how such functionality should be integrated properly — but I hope this helps and that similar functionality can be added officially. 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"
"github.com/metacubex/mihomo/component/resolver"
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
useIP bool
}
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"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
UseIP bool `proxy:"use-ip,omitempty"`
}
func (ss *Socks5) forceIPIfNeeded(ctx context.Context, metadata *C.Metadata) {
if !ss.useIP || metadata.Host == "" {
return
}
if !metadata.DstIP.IsValid() {
if ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver); err == nil {
metadata.DstIP = ip
}
}
metadata.Host = ""
}
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)
}
}
ss.forceIPIfNeeded(ctx, metadata)
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
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata)
}
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
}
func (ss *Socks5) SupportWithDialer() C.NetWork {
return C.TCP
}
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
}
ss.forceIPIfNeeded(ctx, metadata)
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
}
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()
pc.Close()
}()
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c, ss: ss}, ss), nil
}
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 {
var err error
tlsConfig, err = ca.GetTLSConfig(ca.Option{
TLSConfig: &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: option.Server,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
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,
useIP: option.UseIP,
}, nil
}
type socksPacketConn struct {
net.PacketConn
rAddr net.Addr
tcpConn net.Conn
ss *Socks5
}
func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
host, portStr, _ := net.SplitHostPort(addr.String())
port, _ := strconv.Atoi(portStr)
meta := &C.Metadata{
Host: host,
DstPort: uint16(port),
}
if ip := net.ParseIP(host); ip != nil {
meta.DstIP = netip.MustParseAddr(ip.String())
meta.Host = ""
}
uc.ss.forceIPIfNeeded(context.Background(), meta)
packet, err := socks5.EncodeUDPPacket(serializesSocksAddr(meta), b)
if err != nil {
return 0, err
}
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")
}
copy(b, payload)
return n - len(addr) - 3, udpAddr, nil
}
func (uc *socksPacketConn) Close() error {
uc.tcpConn.Close()
return uc.PacketConn.Close()
} |
Beta Was this translation helpful? Give feedback.
-
|
Please respond. This is one of the good features that will add flexibility to the proxy. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Currently, when using a SOCKS5 proxy in mihomo, connections are always made by domain if a domain name is available. This causes problems in setups where local tools such as byedpi are involved.
In my case, byedpi begins resolving the domain locally. The local system DNS then performs the lookup through mihomo, and byedpi ends up receiving a fake IP. To work around this behavior, I had to deploy an additional container running dnsproxy, which complicates the setup.
Proposal 1: Add Option to Force IP-Based Connections
I would like to request a new proxy-level parameter, for example:
with the default being false.
When set to true, the SOCKS5 connection should be established directly using the resolved IP, rather than passing the domain name to the proxy.
This would greatly improve flexibility in environments where domain-based SOCKS behavior leads to unwanted interactions with FakeIP or local DPI-bypass tools.
Proposal 2: Add fakeip-filter-src-cidr
Another useful enhancement would be a configuration option such as:
This would allow excluding FakeIP responses for DNS queries originating from specific source IP subnets.
Such functionality would remove the need for additional DNS containers and prevent unwanted FakeIP behavior in mixed setups.
Conclusion
Both features would significantly increase flexibility and simplify advanced configurations involving local resolvers, DPI bypass tools, and FakeIP logic. It would be great if mihomo could support these two capabilities.
Beta Was this translation helpful? Give feedback.
All reactions