Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[app/proxyman] DomainStrategy support for all outbounds #2334

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 129 additions & 56 deletions app/proxyman/config.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions app/proxyman/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,19 @@ message InboundHandlerConfig {
message OutboundConfig {}

message SenderConfig {
enum DomainStrategy {
AS_IS = 0;
USE_IP = 1;
USE_IP4 = 2;
USE_IP6 = 3;
}

// Send traffic through the given IP. Only IP is allowed.
v2ray.core.common.net.IPOrDomain via = 1;
v2ray.core.transport.internet.StreamConfig stream_settings = 2;
v2ray.core.transport.internet.ProxyConfig proxy_settings = 3;
MultiplexingConfig multiplex_settings = 4;
DomainStrategy domain_strategy = 5;
}

message MultiplexingConfig {
Expand Down
41 changes: 41 additions & 0 deletions app/proxyman/outbound/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
core "github.com/v2fly/v2ray-core/v5"
"github.com/v2fly/v2ray-core/v5/app/proxyman"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/dice"
"github.com/v2fly/v2ray-core/v5/common/mux"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
"github.com/v2fly/v2ray-core/v5/common/serial"
"github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/features/dns"
"github.com/v2fly/v2ray-core/v5/features/outbound"
"github.com/v2fly/v2ray-core/v5/features/policy"
"github.com/v2fly/v2ray-core/v5/features/stats"
Expand Down Expand Up @@ -56,6 +58,7 @@ type Handler struct {
mux *mux.ClientManager
uplinkCounter stats.Counter
downlinkCounter stats.Counter
dns dns.Client
}

// NewHandler create a new Handler based on the given configuration.
Expand Down Expand Up @@ -123,6 +126,16 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
}
}

if h.senderSettings != nil && h.senderSettings.DomainStrategy != proxyman.SenderConfig_AS_IS {
err := core.RequireFeatures(ctx, func(d dns.Client) error {
h.dns = d
return nil
})
if err != nil {
return nil, err
}
}

h.proxy = proxyHandler
return h, nil
}
Expand Down Expand Up @@ -208,7 +221,19 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn
}
outbound.Gateway = h.senderSettings.Via.AsAddress()
}

if h.senderSettings.DomainStrategy != proxyman.SenderConfig_AS_IS {
outbound := session.OutboundFromContext(ctx)
if outbound == nil {
outbound = new(session.Outbound)
ctx = session.ContextWithOutbound(ctx, outbound)
}
outbound.Resolver = func(ctx context.Context, domain string) net.Address {
return h.resolveIP(ctx, domain, h.Address())
}
}
}

enablePacketAddrCapture := true
if h.senderSettings != nil && h.senderSettings.ProxySettings != nil && h.senderSettings.ProxySettings.HasTag() && h.senderSettings.ProxySettings.TransportLayerProxy {
tag := h.senderSettings.ProxySettings.Tag
Expand All @@ -230,6 +255,22 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn
return h.getStatCouterConnection(conn), err
}

func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
strategy := h.senderSettings.DomainStrategy
ips, err := dns.LookupIPWithOption(h.dns, domain, dns.IPOption{
IPv4Enable: strategy == proxyman.SenderConfig_USE_IP || strategy == proxyman.SenderConfig_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()),
IPv6Enable: strategy == proxyman.SenderConfig_USE_IP || strategy == proxyman.SenderConfig_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()),
FakeEnable: false,
})
if err != nil {
newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx))
}
if len(ips) == 0 {
return nil
}
return net.IPAddress(ips[dice.Roll(len(ips))])
}

func (h *Handler) getStatCouterConnection(conn internet.Connection) internet.Connection {
if h.uplinkCounter != nil || h.downlinkCounter != nil {
return &internet.StatCouterConnection{
Expand Down
38 changes: 34 additions & 4 deletions common/buf/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@ const (

var pool = bytespool.GetPool(Size)

// ownership represents the data owner of the buffer.
type ownership uint8

const (
managed ownership = 0
unmanaged ownership = 1
bytespools ownership = 2
)

// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
// the buffer into an internal buffer pool, in order to recreate a buffer more
// quickly.
type Buffer struct {
v []byte
start int32
end int32
unmanaged bool
ownership ownership
}

// New creates a Buffer with 0 length and 2K capacity.
Expand All @@ -30,12 +39,20 @@ func New() *Buffer {
}
}

// NewWithSize creates a Buffer with 0 length and capacity with at least the given size.
func NewWithSize(size int32) *Buffer {
return &Buffer{
v: bytespool.Alloc(size),
ownership: bytespools,
}
}

// FromBytes creates a Buffer with an existed bytearray
func FromBytes(data []byte) *Buffer {
return &Buffer{
v: data,
end: int32(len(data)),
unmanaged: true,
ownership: unmanaged,
}
}

Expand All @@ -49,14 +66,19 @@ func StackNew() Buffer {

// Release recycles the buffer into an internal buffer pool.
func (b *Buffer) Release() {
if b == nil || b.v == nil || b.unmanaged {
if b == nil || b.v == nil || b.ownership == unmanaged {
return
}

p := b.v
b.v = nil
b.Clear()
pool.Put(p) // nolint: staticcheck
switch b.ownership {
case managed:
pool.Put(p) // nolint: staticcheck
case bytespools:
bytespool.Free(p) // nolint: staticcheck
}
}

// Clear clears the content of the buffer, results an empty buffer with
Expand Down Expand Up @@ -151,6 +173,14 @@ func (b *Buffer) Len() int32 {
return b.end - b.start
}

// Cap returns the capacity of the buffer content.
func (b *Buffer) Cap() int32 {
if b == nil {
return 0
}
return int32(len(b.v))
}

// IsEmpty returns true if the buffer is empty.
func (b *Buffer) IsEmpty() bool {
return b.Len() == 0
Expand Down
2 changes: 2 additions & 0 deletions common/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type Outbound struct {
Target net.Destination
// Gateway address
Gateway net.Address
// Domain resolver to use when dialing
Resolver func(ctx context.Context, domain string) net.Address
}

// SniffingRequest controls the behavior of content sniffing.
Expand Down
25 changes: 18 additions & 7 deletions infra/conf/v4/v2ray.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,14 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
}

type OutboundDetourConfig struct {
Protocol string `json:"protocol"`
SendThrough *cfgcommon.Address `json:"sendThrough"`
Tag string `json:"tag"`
Settings *json.RawMessage `json:"settings"`
StreamSetting *StreamConfig `json:"streamSettings"`
ProxySettings *proxycfg.ProxyConfig `json:"proxySettings"`
MuxSettings *muxcfg.MuxConfig `json:"mux"`
Protocol string `json:"protocol"`
SendThrough *cfgcommon.Address `json:"sendThrough"`
Tag string `json:"tag"`
Settings *json.RawMessage `json:"settings"`
StreamSetting *StreamConfig `json:"streamSettings"`
ProxySettings *proxycfg.ProxyConfig `json:"proxySettings"`
MuxSettings *muxcfg.MuxConfig `json:"mux"`
DomainStrategy string `json:"domainStrategy"`
}

// Build implements Buildable.
Expand Down Expand Up @@ -244,6 +245,16 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
senderSettings.MultiplexSettings = c.MuxSettings.Build()
}

senderSettings.DomainStrategy = proxyman.SenderConfig_AS_IS
switch strings.ToLower(c.DomainStrategy) {
case "useip", "use_ip", "use-ip":
senderSettings.DomainStrategy = proxyman.SenderConfig_USE_IP
case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
senderSettings.DomainStrategy = proxyman.SenderConfig_USE_IP4
case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
senderSettings.DomainStrategy = proxyman.SenderConfig_USE_IP6
}

settings := []byte("{}")
if c.Settings != nil {
settings = ([]byte)(*c.Settings)
Expand Down
20 changes: 6 additions & 14 deletions proxy/freedom/freedom.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,27 +104,19 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
destination.Port = net.Port(server.Port)
}
}
if h.config.useIP() {
outbound.Resolver = func(ctx context.Context, domain string) net.Address {
return h.resolveIP(ctx, domain, dialer.Address())
}
}
newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx))

input := link.Reader
output := link.Writer

var conn internet.Connection
err := retry.ExponentialBackoff(5, 100).On(func() error {
dialDest := destination
if h.config.useIP() && dialDest.Address.Family().IsDomain() {
ip := h.resolveIP(ctx, dialDest.Address.Domain(), dialer.Address())
if ip != nil {
dialDest = net.Destination{
Network: dialDest.Network,
Address: ip,
Port: dialDest.Port,
}
newError("dialing to ", dialDest).WriteToLog(session.ExportIDToError(ctx))
}
}

rawConn, err := dialer.Dial(ctx, dialDest)
rawConn, err := dialer.Dial(ctx, destination)
if err != nil {
return err
}
Expand Down
20 changes: 19 additions & 1 deletion transport/internet/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,33 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *MemoryStrea

// DialSystem calls system dialer to create a network connection.
func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {
outbound := session.OutboundFromContext(ctx)

var src net.Address
if outbound := session.OutboundFromContext(ctx); outbound != nil {
if outbound != nil {
src = outbound.Gateway
}

if transportLayerOutgoingTag := session.GetTransportLayerProxyTagFromContext(ctx); transportLayerOutgoingTag != "" {
return DialTaggedOutbound(ctx, dest, transportLayerOutgoingTag)
}

originalAddr := dest.Address
if outbound != nil && outbound.Resolver != nil && dest.Address.Family().IsDomain() {
if addr := outbound.Resolver(ctx, dest.Address.Domain()); addr != nil {
dest.Address = addr
}
}

switch {
case src != nil && dest.Address != originalAddr:
newError("dialing to ", dest, " resolved from ", originalAddr, " via ", src).WriteToLog(session.ExportIDToError(ctx))
case src != nil:
newError("dialing to ", dest, " via ", src).WriteToLog(session.ExportIDToError(ctx))
case dest.Address != originalAddr:
newError("dialing to ", dest, " resolved from ", originalAddr).WriteToLog(session.ExportIDToError(ctx))
}

return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
}

Expand Down