chore: improve authentication parsing logic in http listener (#1336)

This commit is contained in:
wwqgtxx 2024-07-25 19:49:56 +08:00
parent cc7823dad8
commit 4051ea522a
5 changed files with 44 additions and 51 deletions

View File

@ -69,3 +69,5 @@ func WithDSCP(dscp uint8) Addition {
metadata.DSCP = dscp metadata.DSCP = dscp
} }
} }
func Placeholder(metadata *C.Metadata) {}

View File

@ -13,7 +13,7 @@ import (
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) *http.Client { func newClient(srcConn net.Conn, tunnel C.Tunnel, additions []inbound.Addition) *http.Client { // additions using slice let caller can change its value (without size) after newClient return
return &http.Client{ return &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
// from http.DefaultTransport // from http.DefaultTransport

View File

@ -10,10 +10,9 @@ import (
"sync" "sync"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/lru"
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"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@ -31,8 +30,10 @@ func (b *bodyWrapper) Read(p []byte) (n int, err error) {
return n, err return n, err
} }
func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { func HandleConn(c net.Conn, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) {
client := newClient(c, tunnel, additions...) additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser
inUserIdx := len(additions) - 1
client := newClient(c, tunnel, additions)
defer client.CloseIdleConnections() defer client.CloseIdleConnections()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -41,7 +42,8 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
conn := N.NewBufferedConn(c) conn := N.NewBufferedConn(c)
keepAlive := true keepAlive := true
trusted := cache == nil // disable authenticate if lru is nil trusted := authenticator == nil // disable authenticate if lru is nil
lastUser := ""
for keepAlive { for keepAlive {
peekMutex.Lock() peekMutex.Lock()
@ -57,12 +59,10 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
var resp *http.Response var resp *http.Response
if !trusted { var user string
var user string resp, user = authenticate(request, authenticator) // always call authenticate function to get user
resp, user = authenticate(request, cache) trusted = trusted || resp == nil
additions = append(additions, inbound.WithInUser(user)) additions[inUserIdx] = inbound.WithInUser(user)
trusted = resp == nil
}
if trusted { if trusted {
if request.Method == http.MethodConnect { if request.Method == http.MethodConnect {
@ -89,6 +89,13 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
return // hijack connection return // hijack connection
} }
// ensure there is a client with correct additions
// when the authenticated user changed, outbound client should close idle connections
if user != lastUser {
client.CloseIdleConnections()
lastUser = user
}
removeHopByHopHeaders(request.Header) removeHopByHopHeaders(request.Header)
removeExtraHTTPHostPort(request) removeExtraHTTPHostPort(request)
@ -138,34 +145,24 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool],
_ = conn.Close() _ = conn.Close()
} }
func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) (resp *http.Response, u string) { func authenticate(request *http.Request, authenticator auth.Authenticator) (resp *http.Response, user string) {
authenticator := authStore.Authenticator()
if inbound.SkipAuthRemoteAddress(request.RemoteAddr) { if inbound.SkipAuthRemoteAddress(request.RemoteAddr) {
authenticator = nil authenticator = nil
} }
if authenticator != nil { credential := parseBasicProxyAuthorization(request)
credential := parseBasicProxyAuthorization(request) if credential == "" && authenticator != nil {
if credential == "" { resp = responseWith(request, http.StatusProxyAuthRequired)
resp := responseWith(request, http.StatusProxyAuthRequired) resp.Header.Set("Proxy-Authenticate", "Basic")
resp.Header.Set("Proxy-Authenticate", "Basic") return
return resp, ""
}
authed, exist := cache.Get(credential)
if !exist {
user, pass, err := decodeBasicProxyAuthorization(credential)
authed = err == nil && authenticator.Verify(user, pass)
u = user
cache.Set(credential, authed)
}
if !authed {
log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(request, http.StatusForbidden), u
}
} }
user, pass, err := decodeBasicProxyAuthorization(credential)
return nil, u authed := authenticator == nil || (err == nil && authenticator.Verify(user, pass))
if !authed {
log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(request, http.StatusForbidden), user
}
log.Debugln("Auth success from %s -> %s", request.RemoteAddr, user)
return
} }
func responseWith(request *http.Request, statusCode int) *http.Response { func responseWith(request *http.Request, statusCode int) *http.Response {

View File

@ -4,9 +4,10 @@ import (
"net" "net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
authStore "github.com/metacubex/mihomo/listener/auth"
) )
type Listener struct { type Listener struct {
@ -32,10 +33,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 NewWithAuthenticate(addr, tunnel, true, additions...) return NewWithAuthenticate(addr, tunnel, authStore.Authenticator(), additions...)
} }
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticator auth.Authenticator, additions ...inbound.Addition) (*Listener, error) {
isDefault := false isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true isDefault = true
@ -50,11 +51,6 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
return nil, err return nil, err
} }
var c *lru.LruCache[string, bool]
if authenticate {
c = lru.New[string, bool](lru.WithAge[string, bool](30))
}
hl := &Listener{ hl := &Listener{
listener: l, listener: l,
addr: addr, addr: addr,
@ -79,7 +75,7 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
continue continue
} }
} }
go HandleConn(conn, tunnel, c, additions...) go HandleConn(conn, tunnel, authenticator, additions...)
} }
}() }()

View File

@ -4,9 +4,9 @@ import (
"net" "net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
"github.com/metacubex/mihomo/listener/http" "github.com/metacubex/mihomo/listener/http"
"github.com/metacubex/mihomo/listener/socks" "github.com/metacubex/mihomo/listener/socks"
"github.com/metacubex/mihomo/transport/socks4" "github.com/metacubex/mihomo/transport/socks4"
@ -16,7 +16,6 @@ import (
type Listener struct { type Listener struct {
listener net.Listener listener net.Listener
addr string addr string
cache *lru.LruCache[string, bool]
closed bool closed bool
} }
@ -53,7 +52,6 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
ml := &Listener{ ml := &Listener{
listener: l, listener: l,
addr: addr, addr: addr,
cache: lru.New[string, bool](lru.WithAge[string, bool](30)),
} }
go func() { go func() {
for { for {
@ -70,14 +68,14 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
continue continue
} }
} }
go handleConn(c, tunnel, ml.cache, additions...) go handleConn(c, tunnel, additions...)
} }
}() }()
return ml, nil return ml, nil
} }
func handleConn(conn net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { func handleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
N.TCPKeepAlive(conn) N.TCPKeepAlive(conn)
bufConn := N.NewBufferedConn(conn) bufConn := N.NewBufferedConn(conn)
@ -92,6 +90,6 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool
case socks5.Version: case socks5.Version:
socks.HandleSocks5(bufConn, tunnel, additions...) socks.HandleSocks5(bufConn, tunnel, additions...)
default: default:
http.HandleConn(bufConn, tunnel, cache, additions...) http.HandleConn(bufConn, tunnel, authStore.Authenticator(), additions...)
} }
} }