feat: socks5, http and mixed listeners support independence users
Some checks are pending
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run

This commit is contained in:
wwqgtxx 2024-08-25 19:26:06 +08:00
parent 27bcb26ecd
commit 518e9bdb0b
10 changed files with 94 additions and 34 deletions

View File

@ -1005,6 +1005,9 @@ listeners:
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
# udp: false # 默认 true # udp: false # 默认 true
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
- name: http-in-1 - name: http-in-1
type: http type: http
@ -1012,6 +1015,9 @@ listeners:
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
- name: mixed-in-1 - name: mixed-in-1
type: mixed # HTTP(S) 和 SOCKS 代理混合 type: mixed # HTTP(S) 和 SOCKS 代理混合
@ -1020,6 +1026,9 @@ listeners:
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# udp: false # 默认 true # udp: false # 默认 true
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
- name: reidr-in-1 - name: reidr-in-1
type: redir type: redir

View File

@ -13,3 +13,5 @@ func Authenticator() auth.Authenticator {
func SetAuthenticator(au auth.Authenticator) { func SetAuthenticator(au auth.Authenticator) {
authenticator = au authenticator = au
} }
func Nil() auth.Authenticator { return nil }

View File

@ -30,7 +30,7 @@ func (b *bodyWrapper) Read(p []byte) (n int, err error) {
return n, err return n, err
} }
func HandleConn(c net.Conn, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) { func HandleConn(c net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser
inUserIdx := len(additions) - 1 inUserIdx := len(additions) - 1
client := newClient(c, tunnel, additions) client := newClient(c, tunnel, additions)
@ -41,6 +41,7 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, authenticator auth.Authenticator, a
conn := N.NewBufferedConn(c) conn := N.NewBufferedConn(c)
authenticator := getAuth()
keepAlive := true keepAlive := true
trusted := authenticator == nil // disable authenticate if lru is nil trusted := authenticator == nil // disable authenticate if lru is nil
lastUser := "" lastUser := ""
@ -146,9 +147,6 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, authenticator auth.Authenticator, a
} }
func authenticate(request *http.Request, authenticator auth.Authenticator) (resp *http.Response, user string) { func authenticate(request *http.Request, authenticator auth.Authenticator) (resp *http.Response, user string) {
if inbound.SkipAuthRemoteAddress(request.RemoteAddr) {
authenticator = nil
}
credential := parseBasicProxyAuthorization(request) credential := parseBasicProxyAuthorization(request)
if credential == "" && authenticator != nil { if credential == "" && authenticator != nil {
resp = responseWith(request, http.StatusProxyAuthRequired) resp = responseWith(request, http.StatusProxyAuthRequired)

View File

@ -33,20 +33,20 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator(), additions...) return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...)
} }
// NewWithAuthenticate // NewWithAuthenticate
// never change type traits because it's used in CFMA // never change type traits because it's used in CFMA
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
authenticator := authStore.Authenticator() getAuth := authStore.Authenticator
if !authenticate { if !authenticate {
authenticator = nil getAuth = authStore.Nil
} }
return NewWithAuthenticator(addr, tunnel, authenticator, additions...) return NewWithAuthenticator(addr, tunnel, getAuth, additions...)
} }
func NewWithAuthenticator(addr string, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) {
isDefault := false isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true isDefault = true
@ -75,13 +75,18 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, authenticator auth.Authe
continue continue
} }
N.TCPKeepAlive(conn) N.TCPKeepAlive(conn)
getAuth := getAuth
if isDefault { // only apply on default listener if isDefault { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
_ = conn.Close() _ = conn.Close()
continue continue
} }
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
getAuth = authStore.Nil
}
} }
go HandleConn(conn, tunnel, authenticator, additions...) go HandleConn(conn, tunnel, getAuth, additions...)
} }
}() }()

31
listener/inbound/auth.go Normal file
View File

@ -0,0 +1,31 @@
package inbound
import (
"github.com/metacubex/mihomo/component/auth"
authStore "github.com/metacubex/mihomo/listener/auth"
)
type AuthUser struct {
Username string `inbound:"username"`
Password string `inbound:"password"`
}
type AuthUsers []AuthUser
func (a AuthUsers) GetAuth() func() auth.Authenticator {
if a != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
if len(a) == 0 {
return authStore.Nil
}
users := make([]auth.AuthUser, len(a))
for i, user := range a {
users[i] = auth.AuthUser{
User: user.Username,
Pass: user.Password,
}
}
authenticator := auth.NewAuthenticator(users)
return func() auth.Authenticator { return authenticator }
}
return authStore.Authenticator
}

View File

@ -8,6 +8,7 @@ import (
type HTTPOption struct { type HTTPOption struct {
BaseOption BaseOption
Users AuthUsers `inbound:"users,omitempty"`
} }
func (o HTTPOption) Equal(config C.InboundConfig) bool { func (o HTTPOption) Equal(config C.InboundConfig) bool {
@ -44,7 +45,7 @@ func (h *HTTP) Address() string {
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (h *HTTP) Listen(tunnel C.Tunnel) error { func (h *HTTP) Listen(tunnel C.Tunnel) error {
var err error var err error
h.l, err = http.New(h.RawAddress(), tunnel, h.Additions()...) h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuth(), h.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

@ -12,7 +12,8 @@ import (
type MixedOption struct { type MixedOption struct {
BaseOption BaseOption
UDP bool `inbound:"udp,omitempty"` Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
} }
func (o MixedOption) Equal(config C.InboundConfig) bool { func (o MixedOption) Equal(config C.InboundConfig) bool {
@ -52,7 +53,7 @@ func (m *Mixed) Address() string {
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (m *Mixed) Listen(tunnel C.Tunnel) error { func (m *Mixed) Listen(tunnel C.Tunnel) error {
var err error var err error
m.l, err = mixed.New(m.RawAddress(), tunnel, m.Additions()...) m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuth(), m.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

@ -9,7 +9,8 @@ import (
type SocksOption struct { type SocksOption struct {
BaseOption BaseOption
UDP bool `inbound:"udp,omitempty"` Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
} }
func (o SocksOption) Equal(config C.InboundConfig) bool { func (o SocksOption) Equal(config C.InboundConfig) bool {
@ -70,7 +71,7 @@ func (s *Socks) Address() string {
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (s *Socks) Listen(tunnel C.Tunnel) error { func (s *Socks) Listen(tunnel C.Tunnel) error {
var err error var err error
if s.stl, err = socks.New(s.RawAddress(), tunnel, s.Additions()...); err != nil { if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuth(), s.Additions()...); err != nil {
return err return err
} }
if s.udp { if s.udp {

View File

@ -5,6 +5,7 @@ import (
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
"github.com/metacubex/mihomo/listener/http" "github.com/metacubex/mihomo/listener/http"
@ -36,6 +37,10 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...)
}
func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) {
isDefault := false isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true isDefault = true
@ -62,20 +67,24 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
} }
continue continue
} }
getAuth := getAuth
if isDefault { // only apply on default listener if isDefault { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close() _ = c.Close()
continue continue
} }
if inbound.SkipAuthRemoteAddr(c.RemoteAddr()) {
getAuth = authStore.Nil
}
} }
go handleConn(c, tunnel, additions...) go handleConn(c, tunnel, getAuth, additions...)
} }
}() }()
return ml, nil return ml, nil
} }
func handleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
N.TCPKeepAlive(conn) N.TCPKeepAlive(conn)
bufConn := N.NewBufferedConn(conn) bufConn := N.NewBufferedConn(conn)
@ -86,10 +95,10 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
switch head[0] { switch head[0] {
case socks4.Version: case socks4.Version:
socks.HandleSocks4(bufConn, tunnel, additions...) socks.HandleSocks4(bufConn, tunnel, getAuth, additions...)
case socks5.Version: case socks5.Version:
socks.HandleSocks5(bufConn, tunnel, additions...) socks.HandleSocks5(bufConn, tunnel, getAuth, additions...)
default: default:
http.HandleConn(bufConn, tunnel, authStore.Authenticator(), additions...) http.HandleConn(bufConn, tunnel, getAuth, additions...)
} }
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
"github.com/metacubex/mihomo/transport/socks4" "github.com/metacubex/mihomo/transport/socks4"
@ -35,6 +36,10 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...)
}
func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) {
isDefault := false isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true isDefault = true
@ -61,20 +66,24 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
} }
continue continue
} }
getAuth := getAuth
if isDefault { // only apply on default listener if isDefault { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close() _ = c.Close()
continue continue
} }
if inbound.SkipAuthRemoteAddr(c.RemoteAddr()) {
getAuth = authStore.Nil
}
} }
go handleSocks(c, tunnel, additions...) go handleSocks(c, tunnel, getAuth, additions...)
} }
}() }()
return sl, nil return sl, nil
} }
func handleSocks(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { func handleSocks(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
N.TCPKeepAlive(conn) N.TCPKeepAlive(conn)
bufConn := N.NewBufferedConn(conn) bufConn := N.NewBufferedConn(conn)
head, err := bufConn.Peek(1) head, err := bufConn.Peek(1)
@ -85,19 +94,16 @@ func handleSocks(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
switch head[0] { switch head[0] {
case socks4.Version: case socks4.Version:
HandleSocks4(bufConn, tunnel, additions...) HandleSocks4(bufConn, tunnel, getAuth, additions...)
case socks5.Version: case socks5.Version:
HandleSocks5(bufConn, tunnel, additions...) HandleSocks5(bufConn, tunnel, getAuth, additions...)
default: default:
conn.Close() conn.Close()
} }
} }
func HandleSocks4(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { func HandleSocks4(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
authenticator := authStore.Authenticator() authenticator := getAuth()
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
authenticator = nil
}
addr, _, user, err := socks4.ServerHandshake(conn, authenticator) addr, _, user, err := socks4.ServerHandshake(conn, authenticator)
if err != nil { if err != nil {
conn.Close() conn.Close()
@ -107,11 +113,8 @@ func HandleSocks4(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...)) tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...))
} }
func HandleSocks5(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { func HandleSocks5(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
authenticator := authStore.Authenticator() authenticator := getAuth()
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
authenticator = nil
}
target, command, user, err := socks5.ServerHandshake(conn, authenticator) target, command, user, err := socks5.ServerHandshake(conn, authenticator)
if err != nil { if err != nil {
conn.Close() conn.Close()