mihomo/listener/http/proxy.go

172 lines
4.2 KiB
Go
Raw Normal View History

2021-06-15 17:13:40 +08:00
package http
import (
"context"
"fmt"
"io"
2021-06-15 17:13:40 +08:00
"net"
"net/http"
"strings"
"sync"
_ "unsafe"
2021-06-15 17:13:40 +08:00
2023-11-03 21:01:45 +08:00
"github.com/metacubex/mihomo/adapter/inbound"
2023-12-02 17:07:36 +08:00
"github.com/metacubex/mihomo/common/lru"
2023-11-03 21:01:45 +08:00
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
"github.com/metacubex/mihomo/log"
2021-06-15 17:13:40 +08:00
)
//go:linkname registerOnHitEOF net/http.registerOnHitEOF
func registerOnHitEOF(rc io.ReadCloser, fn func())
//go:linkname requestBodyRemains net/http.requestBodyRemains
func requestBodyRemains(rc io.ReadCloser) bool
2023-12-02 17:07:36 +08:00
func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) {
2023-10-23 16:45:22 +08:00
client := newClient(c, tunnel, additions...)
2021-06-15 17:13:40 +08:00
defer client.CloseIdleConnections()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
peekMutex := sync.Mutex{}
2021-06-15 17:13:40 +08:00
conn := N.NewBufferedConn(c)
2021-06-15 17:13:40 +08:00
keepAlive := true
2023-12-02 17:07:36 +08:00
trusted := cache == nil // disable authenticate if lru is nil
2021-06-15 17:13:40 +08:00
for keepAlive {
peekMutex.Lock()
request, err := ReadRequest(conn.Reader())
peekMutex.Unlock()
2021-06-15 17:13:40 +08:00
if err != nil {
break
}
request.RemoteAddr = conn.RemoteAddr().String()
keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
var resp *http.Response
if !trusted {
resp = authenticate(request, cache)
trusted = resp == nil
}
if trusted {
if request.Method == http.MethodConnect {
// Manual writing to support CONNECT for http 1.0 (workaround for uplay client)
if _, err = fmt.Fprintf(conn, "HTTP/%d.%d %03d %s\r\n\r\n", request.ProtoMajor, request.ProtoMinor, http.StatusOK, "Connection established"); err != nil {
2021-06-15 17:13:40 +08:00
break // close connection
}
tunnel.HandleTCPConn(inbound.NewHTTPS(request, conn, additions...))
2021-06-15 17:13:40 +08:00
return // hijack connection
}
host := request.Header.Get("Host")
if host != "" {
request.Host = host
}
request.RequestURI = ""
if isUpgradeRequest(request) {
handleUpgrade(conn, request, tunnel, additions...)
return // hijack connection
}
removeHopByHopHeaders(request.Header)
removeExtraHTTPHostPort(request)
2021-06-15 17:13:40 +08:00
if request.URL.Scheme == "" || request.URL.Host == "" {
resp = responseWith(request, http.StatusBadRequest)
2021-06-15 17:13:40 +08:00
} else {
request = request.WithContext(ctx)
startBackgroundRead := func() {
go func() {
peekMutex.Lock()
defer peekMutex.Unlock()
_, err := conn.Peek(1)
if err != nil {
cancel()
}
}()
}
if requestBodyRemains(request.Body) {
registerOnHitEOF(request.Body, startBackgroundRead)
} else {
startBackgroundRead()
}
2021-06-15 17:13:40 +08:00
resp, err = client.Do(request)
if err != nil {
resp = responseWith(request, http.StatusBadGateway)
2021-06-15 17:13:40 +08:00
}
}
removeHopByHopHeaders(resp.Header)
}
2021-06-15 17:13:40 +08:00
if keepAlive {
resp.Header.Set("Proxy-Connection", "keep-alive")
resp.Header.Set("Connection", "keep-alive")
resp.Header.Set("Keep-Alive", "timeout=4")
}
resp.Close = !keepAlive
err = resp.Write(conn)
if err != nil {
break // close connection
}
}
2022-04-27 05:14:03 +08:00
_ = conn.Close()
2021-06-15 17:13:40 +08:00
}
2023-12-02 17:07:36 +08:00
func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) *http.Response {
2021-06-15 17:13:40 +08:00
authenticator := authStore.Authenticator()
2023-10-11 22:54:19 +08:00
if inbound.SkipAuthRemoteAddress(request.RemoteAddr) {
2023-10-10 19:43:26 +08:00
authenticator = nil
}
2021-06-15 17:13:40 +08:00
if authenticator != nil {
credential := parseBasicProxyAuthorization(request)
2021-06-15 17:13:40 +08:00
if credential == "" {
resp := responseWith(request, http.StatusProxyAuthRequired)
2021-06-15 17:13:40 +08:00
resp.Header.Set("Proxy-Authenticate", "Basic")
return resp
}
2022-08-17 11:43:13 +08:00
authed, exist := cache.Get(credential)
if !exist {
user, pass, err := decodeBasicProxyAuthorization(credential)
2021-06-15 17:13:40 +08:00
authed = err == nil && authenticator.Verify(user, pass)
2022-08-17 11:43:13 +08:00
cache.Set(credential, authed)
2021-06-15 17:13:40 +08:00
}
2022-04-05 23:29:52 +08:00
if !authed {
2021-06-15 17:13:40 +08:00
log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(request, http.StatusForbidden)
2021-06-15 17:13:40 +08:00
}
}
return nil
}
func responseWith(request *http.Request, statusCode int) *http.Response {
2021-06-15 17:13:40 +08:00
return &http.Response{
StatusCode: statusCode,
Status: http.StatusText(statusCode),
Proto: request.Proto,
ProtoMajor: request.ProtoMajor,
ProtoMinor: request.ProtoMinor,
2021-06-15 17:13:40 +08:00
Header: http.Header{},
}
}