Chore: merge branch 'with-tun' into plus-pro
This commit is contained in:
@@ -70,11 +70,11 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string,
|
||||
RemoveExtraHTTPHostPort(request)
|
||||
|
||||
if request.URL.Scheme == "" || request.URL.Host == "" {
|
||||
resp = ResponseWith(request, http.StatusBadRequest)
|
||||
resp = responseWith(request, http.StatusBadRequest)
|
||||
} else {
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
resp = ResponseWith(request, http.StatusBadGateway)
|
||||
resp = responseWith(request, http.StatusBadGateway)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func Authenticate(request *http.Request, cache *cache.Cache[string, bool]) *http
|
||||
if authenticator != nil {
|
||||
credential := parseBasicProxyAuthorization(request)
|
||||
if credential == "" {
|
||||
resp := ResponseWith(request, http.StatusProxyAuthRequired)
|
||||
resp := responseWith(request, http.StatusProxyAuthRequired)
|
||||
resp.Header.Set("Proxy-Authenticate", "Basic")
|
||||
return resp
|
||||
}
|
||||
@@ -117,14 +117,14 @@ func Authenticate(request *http.Request, cache *cache.Cache[string, bool]) *http
|
||||
if !authed {
|
||||
log.Infoln("Auth failed from %s", request.RemoteAddr)
|
||||
|
||||
return ResponseWith(request, http.StatusForbidden)
|
||||
return responseWith(request, http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ResponseWith(request *http.Request, statusCode int) *http.Response {
|
||||
func responseWith(request *http.Request, statusCode int) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: statusCode,
|
||||
Status: http.StatusText(statusCode),
|
||||
|
||||
@@ -40,7 +40,7 @@ func RemoveExtraHTTPHostPort(req *http.Request) {
|
||||
host = req.URL.Host
|
||||
}
|
||||
|
||||
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
|
||||
if pHost, port, err := net.SplitHostPort(host); err == nil && (port == "80" || port == "443") {
|
||||
host = pHost
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -319,7 +320,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
||||
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
||||
}
|
||||
|
||||
func ReCreateTun(tunConf *config.Tun, tunAddressPrefix string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
tunMux.Lock()
|
||||
defer tunMux.Unlock()
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
httpL "github.com/Dreamacro/clash/listener/http"
|
||||
H "github.com/Dreamacro/clash/listener/http"
|
||||
)
|
||||
|
||||
func HandleConn(c net.Conn, opt *Option, in chan<- C.ConnContext, cache *cache.Cache[string, bool]) {
|
||||
@@ -48,9 +48,12 @@ startOver:
|
||||
|
||||
readLoop:
|
||||
for {
|
||||
_ = conn.SetDeadline(time.Now().Add(30 * time.Second)) // use SetDeadline instead of Proxy-Connection keep-alive
|
||||
err := conn.SetDeadline(time.Now().Add(30 * time.Second)) // use SetDeadline instead of Proxy-Connection keep-alive
|
||||
if err != nil {
|
||||
break readLoop
|
||||
}
|
||||
|
||||
request, err := httpL.ReadRequest(conn.Reader())
|
||||
request, err := H.ReadRequest(conn.Reader())
|
||||
if err != nil {
|
||||
handleError(opt, nil, err)
|
||||
break readLoop
|
||||
@@ -58,15 +61,15 @@ readLoop:
|
||||
|
||||
var response *http.Response
|
||||
|
||||
session := NewSession(conn, request, response)
|
||||
session := newSession(conn, request, response)
|
||||
|
||||
source = parseSourceAddress(session.request, c, source)
|
||||
request.RemoteAddr = source.String()
|
||||
session.request.RemoteAddr = source.String()
|
||||
|
||||
if !trusted {
|
||||
response = httpL.Authenticate(request, cache)
|
||||
session.response = H.Authenticate(session.request, cache)
|
||||
|
||||
trusted = response == nil
|
||||
trusted = session.response == nil
|
||||
}
|
||||
|
||||
if trusted {
|
||||
@@ -84,19 +87,18 @@ readLoop:
|
||||
break readLoop // close connection
|
||||
}
|
||||
|
||||
buf := make([]byte, session.conn.(*N.BufferedConn).Buffered())
|
||||
_, _ = session.conn.Read(buf)
|
||||
buff := make([]byte, session.conn.(*N.BufferedConn).Buffered())
|
||||
_, _ = session.conn.Read(buff)
|
||||
|
||||
mc := &MultiReaderConn{
|
||||
mrc := &multiReaderConn{
|
||||
Conn: session.conn,
|
||||
reader: io.MultiReader(bytes.NewReader(b), bytes.NewReader(buf), session.conn),
|
||||
reader: io.MultiReader(bytes.NewReader(b), bytes.NewReader(buff), session.conn),
|
||||
}
|
||||
|
||||
// 22 is the TLS handshake.
|
||||
// https://tools.ietf.org/html/rfc5246#section-6.2.1
|
||||
if b[0] == 22 {
|
||||
// TLS handshake.
|
||||
if b[0] == 0x16 {
|
||||
// TODO serve by generic host name maybe better?
|
||||
tlsConn := tls.Server(mc, opt.CertConfig.NewTLSConfigForHost(session.request.URL.Host))
|
||||
tlsConn := tls.Server(mrc, opt.CertConfig.NewTLSConfigForHost(session.request.URL.Host))
|
||||
|
||||
// Handshake with the local client
|
||||
if err = tlsConn.Handshake(); err != nil {
|
||||
@@ -109,15 +111,17 @@ readLoop:
|
||||
}
|
||||
|
||||
// maybe it's the others encrypted connection
|
||||
in <- inbound.NewHTTPS(request, mc)
|
||||
in <- inbound.NewHTTPS(session.request, mrc)
|
||||
}
|
||||
|
||||
// maybe it's a http connection
|
||||
goto readLoop
|
||||
}
|
||||
|
||||
prepareRequest(c, session.request)
|
||||
|
||||
// hijack api
|
||||
if getHostnameWithoutPort(session.request) == opt.ApiHost {
|
||||
if session.request.URL.Host == opt.ApiHost {
|
||||
if err = handleApiRequest(session, opt); err != nil {
|
||||
handleError(opt, session, err)
|
||||
break readLoop
|
||||
@@ -125,8 +129,6 @@ readLoop:
|
||||
return
|
||||
}
|
||||
|
||||
prepareRequest(c, session.request)
|
||||
|
||||
// hijack custom request and write back custom response if necessary
|
||||
if opt.Handler != nil {
|
||||
newReq, newRes := opt.Handler.HandleRequest(session)
|
||||
@@ -144,12 +146,9 @@ readLoop:
|
||||
}
|
||||
}
|
||||
|
||||
httpL.RemoveHopByHopHeaders(session.request.Header)
|
||||
httpL.RemoveExtraHTTPHostPort(request)
|
||||
|
||||
session.request.RequestURI = ""
|
||||
|
||||
if session.request.URL.Scheme == "" || session.request.URL.Host == "" {
|
||||
if session.request.URL.Host == "" {
|
||||
session.response = session.NewErrorResponse(errors.New("invalid URL"))
|
||||
} else {
|
||||
client = newClientBySourceAndUserAgentIfNil(client, session.request, source, in)
|
||||
@@ -162,6 +161,8 @@ readLoop:
|
||||
session.response = session.NewErrorResponse(err)
|
||||
if errors.Is(err, ErrCertUnsupported) || strings.Contains(err.Error(), "x509: ") {
|
||||
// TODO block unsupported host?
|
||||
_ = writeResponse(session, false)
|
||||
break readLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +195,7 @@ func writeResponseWithHandler(session *Session, opt *Option) error {
|
||||
}
|
||||
|
||||
func writeResponse(session *Session, keepAlive bool) error {
|
||||
httpL.RemoveHopByHopHeaders(session.response.Header)
|
||||
H.RemoveHopByHopHeaders(session.response.Header)
|
||||
|
||||
if keepAlive {
|
||||
session.response.Header.Set("Connection", "keep-alive")
|
||||
@@ -226,17 +227,15 @@ func handleApiRequest(session *Session, opt *Option) error {
|
||||
return session.response.Write(session.conn)
|
||||
}
|
||||
|
||||
b := `<!DOCTYPE HTML PUBLIC "-
|
||||
<html>
|
||||
<head>
|
||||
<title>Clash ManInTheMiddle Proxy Services - 404 Not Found</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Not Found</h1>
|
||||
<p>The requested URL %s was not found on this server.</p>
|
||||
</body>
|
||||
</html>
|
||||
b := `<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
||||
<html><head>
|
||||
<title>Clash MITM Proxy Services - 404 Not Found</title>
|
||||
</head><body>
|
||||
<h1>Not Found</h1>
|
||||
<p>The requested URL %s was not found on this server.</p>
|
||||
</body></html>
|
||||
`
|
||||
|
||||
if opt.Handler != nil {
|
||||
if opt.Handler.HandleApiRequest(session) {
|
||||
return nil
|
||||
@@ -261,10 +260,7 @@ func handleApiRequest(session *Session, opt *Option) error {
|
||||
func handleError(opt *Option, session *Session, err error) {
|
||||
if opt.Handler != nil {
|
||||
opt.Handler.HandleError(session, err)
|
||||
return
|
||||
}
|
||||
|
||||
// log.Errorln("[MITM] process mitm error: %v", err)
|
||||
}
|
||||
|
||||
func prepareRequest(conn net.Conn, request *http.Request) {
|
||||
@@ -277,7 +273,9 @@ func prepareRequest(conn net.Conn, request *http.Request) {
|
||||
request.URL.Host = request.Host
|
||||
}
|
||||
|
||||
request.URL.Scheme = "http"
|
||||
if request.URL.Scheme == "" {
|
||||
request.URL.Scheme = "http"
|
||||
}
|
||||
|
||||
if tlsConn, ok := conn.(*tls.Conn); ok {
|
||||
cs := tlsConn.ConnectionState()
|
||||
@@ -289,6 +287,9 @@ func prepareRequest(conn net.Conn, request *http.Request) {
|
||||
if request.Header.Get("Accept-Encoding") != "" {
|
||||
request.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
|
||||
H.RemoveHopByHopHeaders(request.Header)
|
||||
H.RemoveExtraHTTPHostPort(request)
|
||||
}
|
||||
|
||||
func couldBeWithManInTheMiddleAttack(hostname string, opt *Option) bool {
|
||||
@@ -303,19 +304,6 @@ func couldBeWithManInTheMiddleAttack(hostname string, opt *Option) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getHostnameWithoutPort(req *http.Request) string {
|
||||
host := req.Host
|
||||
if host == "" {
|
||||
host = req.URL.Host
|
||||
}
|
||||
|
||||
if pHost, _, err := net.SplitHostPort(host); err == nil {
|
||||
host = pHost
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
func parseSourceAddress(req *http.Request, c net.Conn, source net.Addr) net.Addr {
|
||||
if source != nil {
|
||||
return source
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package mitm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
var serverName = fmt.Sprintf("Clash server (%s)", C.Version)
|
||||
|
||||
type Session struct {
|
||||
conn net.Conn
|
||||
request *http.Request
|
||||
@@ -37,16 +32,14 @@ func (s *Session) SetProperties(key string, val any) {
|
||||
}
|
||||
|
||||
func (s *Session) NewResponse(code int, body io.Reader) *http.Response {
|
||||
res := NewResponse(code, body, s.request)
|
||||
res.Header.Set("Server", serverName)
|
||||
return res
|
||||
return NewResponse(code, body, s.request)
|
||||
}
|
||||
|
||||
func (s *Session) NewErrorResponse(err error) *http.Response {
|
||||
return NewErrorResponse(s.request, err)
|
||||
}
|
||||
|
||||
func NewSession(conn net.Conn, request *http.Request, response *http.Response) *Session {
|
||||
func newSession(conn net.Conn, request *http.Request, response *http.Response) *Session {
|
||||
return &Session{
|
||||
conn: conn,
|
||||
request: request,
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
type MultiReaderConn struct {
|
||||
type multiReaderConn struct {
|
||||
net.Conn
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (c *MultiReaderConn) Read(buf []byte) (int, error) {
|
||||
func (c *multiReaderConn) Read(buf []byte) (int, error) {
|
||||
return c.reader.Read(buf)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ func NewErrorResponse(req *http.Request, err error) *http.Response {
|
||||
|
||||
w := fmt.Sprintf(`199 "clash" %q %q`, err.Error(), date)
|
||||
res.Header.Add("Warning", w)
|
||||
res.Header.Set("Server", serverName)
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ import (
|
||||
)
|
||||
|
||||
// New TunAdapter
|
||||
func New(tunConf *config.Tun, tunAddressPrefix string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
||||
func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
||||
var (
|
||||
tunAddress, _ = netip.ParsePrefix(tunAddressPrefix)
|
||||
devName = tunConf.Device
|
||||
stackType = tunConf.Stack
|
||||
autoRoute = tunConf.AutoRoute
|
||||
mtu = 9000
|
||||
tunAddress = netip.Prefix{}
|
||||
devName = tunConf.Device
|
||||
stackType = tunConf.Stack
|
||||
autoRoute = tunConf.AutoRoute
|
||||
mtu = 9000
|
||||
|
||||
tunDevice device.Device
|
||||
tunStack ipstack.Stack
|
||||
@@ -42,6 +42,10 @@ func New(tunConf *config.Tun, tunAddressPrefix string, tcpIn chan<- C.ConnContex
|
||||
devName = generateDeviceName()
|
||||
}
|
||||
|
||||
if tunAddressPrefix != nil {
|
||||
tunAddress = *tunAddressPrefix
|
||||
}
|
||||
|
||||
if !tunAddress.IsValid() || !tunAddress.Addr().Is4() {
|
||||
tunAddress = netip.MustParsePrefix("198.18.0.1/16")
|
||||
}
|
||||
@@ -144,6 +148,8 @@ func setAtLatest(stackType C.TUNStack, devName string) {
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
_, _ = cmd.ExecCmd("sysctl net.inet.ip.forwarding=1")
|
||||
case "windows":
|
||||
_, _ = cmd.ExecCmd("ipconfig /renew")
|
||||
case "linux":
|
||||
|
||||
Reference in New Issue
Block a user