chore: Something update from clash :) (#606)
This commit is contained in:
parent
53be3f82cc
commit
ea5a231145
11 changed files with 100 additions and 31 deletions
5
Makefile
5
Makefile
|
@ -31,6 +31,8 @@ PLATFORM_LIST = \
|
||||||
linux-mips-hardfloat \
|
linux-mips-hardfloat \
|
||||||
linux-mipsle-softfloat \
|
linux-mipsle-softfloat \
|
||||||
linux-mipsle-hardfloat \
|
linux-mipsle-hardfloat \
|
||||||
|
linux-riscv64 \
|
||||||
|
linux-loong64 \
|
||||||
android-arm64 \
|
android-arm64 \
|
||||||
freebsd-386 \
|
freebsd-386 \
|
||||||
freebsd-amd64 \
|
freebsd-amd64 \
|
||||||
|
@ -104,6 +106,9 @@ linux-mips64le:
|
||||||
linux-riscv64:
|
linux-riscv64:
|
||||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
linux-loong64:
|
||||||
|
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
android-arm64:
|
android-arm64:
|
||||||
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
|
|
@ -56,12 +56,12 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||||
providers := []types.ProxyProvider{}
|
providers := []types.ProxyProvider{}
|
||||||
|
|
||||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||||
return nil, errMissProxy
|
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedStatus, err := utils.NewIntRanges[uint16](groupOption.ExpectedStatus)
|
expectedStatus, err := utils.NewIntRanges[uint16](groupOption.ExpectedStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
status := strings.TrimSpace(groupOption.ExpectedStatus)
|
status := strings.TrimSpace(groupOption.ExpectedStatus)
|
||||||
|
@ -74,17 +74,17 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||||
if len(groupOption.Proxies) != 0 {
|
if len(groupOption.Proxies) != 0 {
|
||||||
ps, err := getProxies(proxyMap, groupOption.Proxies)
|
ps, err := getProxies(proxyMap, groupOption.Proxies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := providersMap[groupName]; ok {
|
if _, ok := providersMap[groupName]; ok {
|
||||||
return nil, errDuplicateProvider
|
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
hc := provider.NewHealthCheck(ps, "", 0, true, nil)
|
hc := provider.NewHealthCheck(ps, "", 0, true, nil)
|
||||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// select don't need health check
|
// select don't need health check
|
||||||
|
@ -107,7 +107,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||||
if len(groupOption.Use) != 0 {
|
if len(groupOption.Use) != 0 {
|
||||||
list, err := getProviders(providersMap, groupOption.Use)
|
list, err := getProviders(providersMap, groupOption.Use)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// different proxy groups use different test URL
|
// different proxy groups use different test URL
|
||||||
|
|
|
@ -12,7 +12,10 @@ import (
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errVehicleType = errors.New("unsupport vehicle type")
|
var (
|
||||||
|
errVehicleType = errors.New("unsupport vehicle type")
|
||||||
|
errSubPath = errors.New("path is not subpath of home directory")
|
||||||
|
)
|
||||||
|
|
||||||
type healthCheckSchema struct {
|
type healthCheckSchema struct {
|
||||||
Enable bool `provider:"enable"`
|
Enable bool `provider:"enable"`
|
||||||
|
@ -64,6 +67,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||||
case "file":
|
case "file":
|
||||||
vehicle = resource.NewFileVehicle(path)
|
vehicle = resource.NewFileVehicle(path)
|
||||||
case "http":
|
case "http":
|
||||||
|
if !C.Path.IsSubPath(path) {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||||
|
}
|
||||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||||
|
|
|
@ -67,7 +67,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
|
||||||
err := initWin32API()
|
err := initWin32API()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||||
log.Warnln("All PROCESS-NAMES rules will be skiped")
|
log.Warnln("All PROCESS-NAMES rules will be skipped")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -913,7 +913,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||||
addr, err = hostWithDefaultPort(u.Host, "443")
|
addr, err = hostWithDefaultPort(u.Host, "443")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
proxyName = ""
|
proxyName = ""
|
||||||
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
|
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path, User: u.User}
|
||||||
addr = clearURL.String()
|
addr = clearURL.String()
|
||||||
dnsNetType = "https" // DNS over HTTPS
|
dnsNetType = "https" // DNS over HTTPS
|
||||||
if len(u.Fragment) != 0 {
|
if len(u.Fragment) != 0 {
|
||||||
|
|
|
@ -56,6 +56,18 @@ func (p *path) Resolve(path string) string {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSubPath return true if path is a subpath of homedir
|
||||||
|
func (p *path) IsSubPath(path string) bool {
|
||||||
|
homedir := p.HomeDir()
|
||||||
|
path = p.Resolve(path)
|
||||||
|
rel, err := filepath.Rel(homedir, path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !strings.Contains(rel, "..")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *path) MMDB() string {
|
func (p *path) MMDB() string {
|
||||||
files, err := os.ReadDir(p.homeDir)
|
files, err := os.ReadDir(p.homeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -165,7 +165,8 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
|
||||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||||
continueFetch = true
|
continueFetch = true
|
||||||
} else {
|
} else {
|
||||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
// updating TTL by subtracting common delta time from each DNS record
|
||||||
|
updateMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
29
dns/util.go
29
dns/util.go
|
@ -21,12 +21,29 @@ import (
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MaxMsgSize = 65535
|
MaxMsgSize = 65535
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func minimalTTL(records []D.RR) uint32 {
|
||||||
|
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||||
|
return r1.Header().Ttl < r2.Header().Ttl
|
||||||
|
}).Header().Ttl
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTTL(records []D.RR, ttl uint32) {
|
||||||
|
if len(records) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delta := minimalTTL(records) - ttl
|
||||||
|
for i := range records {
|
||||||
|
records[i].Header().Ttl = lo.Clamp(records[i].Header().Ttl-delta, 1, records[i].Header().Ttl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||||
// skip dns cache for acme challenge
|
// skip dns cache for acme challenge
|
||||||
if len(msg.Question) != 0 {
|
if len(msg.Question) != 0 {
|
||||||
|
@ -38,11 +55,11 @@ func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||||
var ttl uint32
|
var ttl uint32
|
||||||
switch {
|
switch {
|
||||||
case len(msg.Answer) != 0:
|
case len(msg.Answer) != 0:
|
||||||
ttl = msg.Answer[0].Header().Ttl
|
ttl = minimalTTL(msg.Answer)
|
||||||
case len(msg.Ns) != 0:
|
case len(msg.Ns) != 0:
|
||||||
ttl = msg.Ns[0].Header().Ttl
|
ttl = minimalTTL(msg.Ns)
|
||||||
case len(msg.Extra) != 0:
|
case len(msg.Extra) != 0:
|
||||||
ttl = msg.Extra[0].Header().Ttl
|
ttl = minimalTTL(msg.Extra)
|
||||||
default:
|
default:
|
||||||
log.Debugln("[DNS] response msg empty: %#v", msg)
|
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||||
return
|
return
|
||||||
|
@ -65,6 +82,12 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateMsgTTL(msg *D.Msg, ttl uint32) {
|
||||||
|
updateTTL(msg.Answer, ttl)
|
||||||
|
updateTTL(msg.Ns, ttl)
|
||||||
|
updateTTL(msg.Extra, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
func isIPRequest(q D.Question) bool {
|
func isIPRequest(q D.Question) bool {
|
||||||
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
|
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module github.com/Dreamacro/clash
|
module github.com/Dreamacro/clash
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/3andne/restls-client-go v0.1.4
|
github.com/3andne/restls-client-go v0.1.4
|
||||||
|
|
|
@ -2,12 +2,14 @@ package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/subtle"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
CN "github.com/Dreamacro/clash/common/net"
|
CN "github.com/Dreamacro/clash/common/net"
|
||||||
|
@ -149,6 +151,12 @@ func Start(addr string, tlsAddr string, secret string,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func safeEuqal(a, b string) bool {
|
||||||
|
aBuf := unsafe.Slice(unsafe.StringData(a), len(a))
|
||||||
|
bBuf := unsafe.Slice(unsafe.StringData(b), len(b))
|
||||||
|
return subtle.ConstantTimeCompare(aBuf, bBuf) == 1
|
||||||
|
}
|
||||||
|
|
||||||
func authentication(next http.Handler) http.Handler {
|
func authentication(next http.Handler) http.Handler {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
if serverSecret == "" {
|
if serverSecret == "" {
|
||||||
|
@ -159,7 +167,7 @@ func authentication(next http.Handler) http.Handler {
|
||||||
// Browser websocket not support custom header
|
// Browser websocket not support custom header
|
||||||
if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" {
|
if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" {
|
||||||
token := r.URL.Query().Get("token")
|
token := r.URL.Query().Get("token")
|
||||||
if token != serverSecret {
|
if !safeEuqal(token, serverSecret) {
|
||||||
render.Status(r, http.StatusUnauthorized)
|
render.Status(r, http.StatusUnauthorized)
|
||||||
render.JSON(w, r, ErrUnauthorized)
|
render.JSON(w, r, ErrUnauthorized)
|
||||||
return
|
return
|
||||||
|
@ -172,7 +180,7 @@ func authentication(next http.Handler) http.Handler {
|
||||||
bearer, token, found := strings.Cut(header, " ")
|
bearer, token, found := strings.Cut(header, " ")
|
||||||
|
|
||||||
hasInvalidHeader := bearer != "Bearer"
|
hasInvalidHeader := bearer != "Bearer"
|
||||||
hasInvalidSecret := !found || token != serverSecret
|
hasInvalidSecret := !found || !safeEuqal(token, serverSecret)
|
||||||
if hasInvalidHeader || hasInvalidSecret {
|
if hasInvalidHeader || hasInvalidSecret {
|
||||||
render.Status(r, http.StatusUnauthorized)
|
render.Status(r, http.StatusUnauthorized)
|
||||||
render.JSON(w, r, ErrUnauthorized)
|
render.JSON(w, r, ErrUnauthorized)
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package redir
|
package redir
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -25,28 +29,38 @@ func parserPacket(conn net.Conn) (socks5.Addr, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var addr socks5.Addr
|
var addr netip.AddrPort
|
||||||
|
|
||||||
rc.Control(func(fd uintptr) {
|
rc.Control(func(fd uintptr) {
|
||||||
|
if ip4 := c.LocalAddr().(*net.TCPAddr).IP.To4(); ip4 != nil {
|
||||||
addr, err = getorigdst(fd)
|
addr, err = getorigdst(fd)
|
||||||
|
} else {
|
||||||
|
addr, err = getorigdst6(fd)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return addr, err
|
return socks5.AddrFromStdAddrPort(addr), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
||||||
func getorigdst(fd uintptr) (socks5.Addr, error) {
|
func getorigdst(fd uintptr) (netip.AddrPort, error) {
|
||||||
raw := syscall.RawSockaddrInet4{}
|
addr := unix.RawSockaddrInet4{}
|
||||||
siz := unsafe.Sizeof(raw)
|
size := uint32(unsafe.Sizeof(addr))
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0)
|
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0)
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, err
|
return netip.AddrPort{}, err
|
||||||
}
|
}
|
||||||
|
port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:])
|
||||||
addr := make([]byte, 1+net.IPv4len+2)
|
return netip.AddrPortFrom(netip.AddrFrom4(addr.Addr), port), nil
|
||||||
addr[0] = socks5.AtypIPv4
|
}
|
||||||
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
|
||||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
func getorigdst6(fd uintptr) (netip.AddrPort, error) {
|
||||||
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
addr := unix.RawSockaddrInet6{}
|
||||||
return addr, nil
|
size := uint32(unsafe.Sizeof(addr))
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0)
|
||||||
|
if err != 0 {
|
||||||
|
return netip.AddrPort{}, err
|
||||||
|
}
|
||||||
|
port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:])
|
||||||
|
return netip.AddrPortFrom(netip.AddrFrom16(addr.Addr), port), nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue