Skip to content
Open
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
7 changes: 5 additions & 2 deletions docs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,7 @@ proxies: # socks5
# max-connections: 1 # Maximum connections. Conflict with max-streams.
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.

reality-opts:
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
short-id: 10f897e26c4b9478
Expand Down Expand Up @@ -1440,7 +1440,7 @@ proxies: # socks5
- name: sudoku
type: sudoku
server: server_ip/domain # 1.2.3.4 or domain
port: 443
port: 443
key: "<client_key>" # 如果你使用sudoku生成的ED25519密钥对,请填写密钥对中的私钥,否则填入和服务端相同的uuid
aead-method: chacha20-poly1305 # 可选:chacha20-poly1305、aes-128-gcm、none(不建议;none 不提供 AEAD 保护)
padding-min: 2 # 最小填充率(0-100)
Expand Down Expand Up @@ -1955,6 +1955,7 @@ listeners:
# - 0123456789abcdef
# server-names:
# - test.com
# proxy-protocol: 0 # 可选,默认 0 为关闭;1/2 会在连接 Reality dest 后立即发送 Proxy Protocol v1/v2,向后端传递原始来源/目标地址,后端必须启用 Proxy Protocol
# #下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
# limit-fallback-upload:
Expand Down Expand Up @@ -2093,6 +2094,7 @@ listeners:
- 0123456789abcdef
server-names:
- test.com
proxy-protocol: 0 # 可选,默认 0 为关闭;1/2 会在连接 Reality dest 后立即发送 Proxy Protocol v1/v2,向后端传递原始来源/目标地址,后端必须启用 Proxy Protocol
#下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
#回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
limit-fallback-upload:
Expand Down Expand Up @@ -2202,6 +2204,7 @@ listeners:
# - 0123456789abcdef
# server-names:
# - test.com
# proxy-protocol: 0 # 可选,默认 0 为关闭;1/2 会在连接 Reality dest 后立即发送 Proxy Protocol v1/v2,向后端传递原始来源/目标地址,后端必须启用 Proxy Protocol
# #下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
# limit-fallback-upload:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ require (
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21
github.com/pires/go-proxyproto v0.8.0
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e
github.com/samber/lo v1.53.0
github.com/sirupsen/logrus v1.9.4
Expand Down Expand Up @@ -122,7 +123,6 @@ require (
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/pierrec/lz4/v4 v4.1.27 // indirect
github.com/pires/go-proxyproto v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
Expand Down
2 changes: 2 additions & 0 deletions listener/inbound/reality.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type RealityConfig struct {
ServerNames []string `inbound:"server-names"`
MaxTimeDifference int `inbound:"max-time-difference,omitempty"`
Proxy string `inbound:"proxy,omitempty"`
ProxyProtocol int `inbound:"proxy-protocol,omitempty"`

LimitFallbackUpload RealityLimitFallback `inbound:"limit-fallback-upload,omitempty"`
LimitFallbackDownload RealityLimitFallback `inbound:"limit-fallback-download,omitempty"`
Expand All @@ -28,6 +29,7 @@ func (c RealityConfig) Build() reality.Config {
ServerNames: c.ServerNames,
MaxTimeDifference: c.MaxTimeDifference,
Proxy: c.Proxy,
ProxyProtocol: c.ProxyProtocol,

LimitFallbackUpload: reality.LimitFallback{
AfterBytes: c.LimitFallbackUpload.AfterBytes,
Expand Down
90 changes: 89 additions & 1 deletion listener/reality/reality.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/metacubex/mihomo/ntp"

utls "github.com/metacubex/utls"
proxyproto "github.com/pires/go-proxyproto"
)

type Conn = utls.Conn
Expand All @@ -29,12 +30,17 @@ type Config struct {
ServerNames []string
MaxTimeDifference int
Proxy string
ProxyProtocol int

LimitFallbackUpload LimitFallback
LimitFallbackDownload LimitFallback
}

func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
if c.ProxyProtocol < 0 || c.ProxyProtocol > 2 {
return nil, fmt.Errorf("invalid proxy-protocol version: %d", c.ProxyProtocol)
}

realityConfig := &utls.RealityConfig{}
realityConfig.SessionTicketsDisabled = true
realityConfig.Type = "tcp"
Expand Down Expand Up @@ -74,7 +80,15 @@ func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
}

realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return inner.HandleTcp(tunnel, address, c.Proxy)
target, err := inner.HandleTcp(tunnel, address, c.Proxy)
if err != nil {
return nil, err
}
if err := writeProxyProtocolHeader(ctx, target, c.ProxyProtocol); err != nil {
_ = target.Close()
return nil, err
}
return target, nil
}

realityConfig.LimitFallbackUpload = c.LimitFallbackUpload
Expand All @@ -89,6 +103,8 @@ type Builder struct {

func (b Builder) NewListener(l net.Listener) net.Listener {
return N.NewHandleContextListener(context.Background(), l, func(ctx context.Context, conn net.Conn) (net.Conn, error) {
ctx = context.WithValue(ctx, sourceAddrContextKey{}, conn.RemoteAddr())
ctx = context.WithValue(ctx, destinationAddrContextKey{}, conn.LocalAddr())
c, err := utls.RealityServer(ctx, conn, b.realityConfig)
if err != nil {
return nil, err
Expand Down Expand Up @@ -121,3 +137,75 @@ func (c realityConnWrapper) ReaderReplaceable() bool {
func (c realityConnWrapper) WriterReplaceable() bool {
return true
}

type sourceAddrContextKey struct{}

type destinationAddrContextKey struct{}

func writeProxyProtocolHeader(ctx context.Context, conn net.Conn, version int) error {
switch version {
case 0:
return nil
case 1, 2:
default:
return fmt.Errorf("invalid proxy-protocol version: %d", version)
}

sourceAddr, destinationAddr := sourceAndDestinationAddrsFromContext(ctx)
header, degraded := buildProxyProtocolHeader(byte(version), sourceAddr, destinationAddr)
if degraded {
log.Warnln("REALITY proxy-protocol degraded to UNKNOWN/LOCAL for source=%T(%v) destination=%T(%v)", sourceAddr, sourceAddr, destinationAddr, destinationAddr)
}
_, err := header.WriteTo(conn)
if err != nil {
return fmt.Errorf("write proxy-protocol header: %w", err)
}
return nil
}

func sourceAndDestinationAddrsFromContext(ctx context.Context) (net.Addr, net.Addr) {
sourceAddr, _ := ctx.Value(sourceAddrContextKey{}).(net.Addr)
destinationAddr, _ := ctx.Value(destinationAddrContextKey{}).(net.Addr)
return sourceAddr, destinationAddr
}

func buildProxyProtocolHeader(version byte, sourceAddr, destinationAddr net.Addr) (*proxyproto.Header, bool) {
sourceTCPAddr, destinationTCPAddr, ok := proxyProtocolTCPAddrPair(sourceAddr, destinationAddr)
if !ok {
return unknownProxyProtocolHeader(version), true
}

return proxyproto.HeaderProxyFromAddrs(version, sourceTCPAddr, destinationTCPAddr), false
}

func proxyProtocolTCPAddrPair(sourceAddr, destinationAddr net.Addr) (*net.TCPAddr, *net.TCPAddr, bool) {
sourceTCPAddr, ok := sourceAddr.(*net.TCPAddr)
if !ok || sourceTCPAddr == nil {
return nil, nil, false
}
destinationTCPAddr, ok := destinationAddr.(*net.TCPAddr)
if !ok || destinationTCPAddr == nil {
return nil, nil, false
}
if sourceTCPAddr.Port < 0 || sourceTCPAddr.Port > 65535 || destinationTCPAddr.Port < 0 || destinationTCPAddr.Port > 65535 {
return nil, nil, false
}

sourceIPv4 := sourceTCPAddr.IP.To4()
destinationIPv4 := destinationTCPAddr.IP.To4()
if sourceIPv4 != nil || destinationIPv4 != nil {
return sourceTCPAddr, destinationTCPAddr, sourceIPv4 != nil && destinationIPv4 != nil
}

sourceIPv6 := sourceTCPAddr.IP.To16()
destinationIPv6 := destinationTCPAddr.IP.To16()
return sourceTCPAddr, destinationTCPAddr, sourceIPv6 != nil && destinationIPv6 != nil
}

func unknownProxyProtocolHeader(version byte) *proxyproto.Header {
return &proxyproto.Header{
Version: version,
Command: proxyproto.LOCAL,
TransportProtocol: proxyproto.UNSPEC,
}
}
Loading