diff --git a/Makefile b/Makefile index ecd1f5e6..028b986c 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,8 @@ PLATFORM_LIST = \ linux-mips-hardfloat \ linux-mipsle-softfloat \ linux-mipsle-hardfloat \ + linux-riscv64 \ + linux-loong64 \ android-arm64 \ freebsd-386 \ freebsd-amd64 \ @@ -103,6 +105,9 @@ linux-mips64le: linux-riscv64: GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-loong64: + GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ android-arm64: GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index 7ebaa6c0..684960b3 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -56,12 +56,12 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide providers := []types.ProxyProvider{} 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) if err != nil { - return nil, err + return nil, fmt.Errorf("%s: %w", groupName, err) } 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 { ps, err := getProxies(proxyMap, groupOption.Proxies) if err != nil { - return nil, err + return nil, fmt.Errorf("%s: %w", groupName, err) } if _, ok := providersMap[groupName]; ok { - return nil, errDuplicateProvider + return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) } hc := provider.NewHealthCheck(ps, "", 0, true, nil) pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { - return nil, err + return nil, fmt.Errorf("%s: %w", groupName, err) } // 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 { list, err := getProviders(providersMap, groupOption.Use) if err != nil { - return nil, err + return nil, fmt.Errorf("%s: %w", groupName, err) } // different proxy groups use different test URL diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index 954db5cb..07bef4e5 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -12,7 +12,10 @@ import ( 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 { Enable bool `provider:"enable"` @@ -64,6 +67,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide case "file": vehicle = resource.NewFileVehicle(path) case "http": + if !C.Path.IsSubPath(path) { + return nil, fmt.Errorf("%w: %s", errSubPath, path) + } vehicle = resource.NewHTTPVehicle(schema.URL, path) default: return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) diff --git a/component/process/process_windows.go b/component/process/process_windows.go index cce08e30..21878bf6 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -67,7 +67,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string err := initWin32API() if err != nil { 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 } }) diff --git a/config/config.go b/config/config.go index 191dbf13..5f1c6710 100644 --- a/config/config.go +++ b/config/config.go @@ -913,7 +913,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) addr, err = hostWithDefaultPort(u.Host, "443") if err == nil { 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() dnsNetType = "https" // DNS over HTTPS if len(u.Fragment) != 0 { diff --git a/constant/path.go b/constant/path.go index 29ac9872..e724e6b4 100644 --- a/constant/path.go +++ b/constant/path.go @@ -56,6 +56,18 @@ func (p *path) Resolve(path string) string { 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 { files, err := os.ReadDir(p.homeDir) if err != nil { diff --git a/dns/resolver.go b/dns/resolver.go index 7e1b007d..5ae7ba33 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -165,7 +165,8 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e setMsgTTL(msg, uint32(1)) // Continue fetch continueFetch = true } 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 } diff --git a/dns/util.go b/dns/util.go index d85deb17..1b8f9635 100644 --- a/dns/util.go +++ b/dns/util.go @@ -21,12 +21,29 @@ import ( "github.com/Dreamacro/clash/tunnel" D "github.com/miekg/dns" + "github.com/samber/lo" ) const ( 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) { // skip dns cache for acme challenge 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 switch { case len(msg.Answer) != 0: - ttl = msg.Answer[0].Header().Ttl + ttl = minimalTTL(msg.Answer) case len(msg.Ns) != 0: - ttl = msg.Ns[0].Header().Ttl + ttl = minimalTTL(msg.Ns) case len(msg.Extra) != 0: - ttl = msg.Extra[0].Header().Ttl + ttl = minimalTTL(msg.Extra) default: log.Debugln("[DNS] response msg empty: %#v", msg) 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 { return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME) } diff --git a/go.mod b/go.mod index 89071513..29dd4b86 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Dreamacro/clash -go 1.19 +go 1.20 require ( github.com/3andne/restls-client-go v0.1.4 diff --git a/hub/route/server.go b/hub/route/server.go index d8124d33..8ccb79f5 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -2,12 +2,14 @@ package route import ( "bytes" + "crypto/subtle" "crypto/tls" "encoding/json" "net/http" "runtime/debug" "strings" "time" + "unsafe" "github.com/Dreamacro/clash/adapter/inbound" 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 { fn := func(w http.ResponseWriter, r *http.Request) { if serverSecret == "" { @@ -159,7 +167,7 @@ func authentication(next http.Handler) http.Handler { // Browser websocket not support custom header if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" { token := r.URL.Query().Get("token") - if token != serverSecret { + if !safeEuqal(token, serverSecret) { render.Status(r, http.StatusUnauthorized) render.JSON(w, r, ErrUnauthorized) return @@ -172,7 +180,7 @@ func authentication(next http.Handler) http.Handler { bearer, token, found := strings.Cut(header, " ") hasInvalidHeader := bearer != "Bearer" - hasInvalidSecret := !found || token != serverSecret + hasInvalidSecret := !found || !safeEuqal(token, serverSecret) if hasInvalidHeader || hasInvalidSecret { render.Status(r, http.StatusUnauthorized) render.JSON(w, r, ErrUnauthorized) diff --git a/listener/redir/tcp_freebsd.go b/listener/redir/tcp_freebsd.go index 12c4ba6a..6ecb2496 100644 --- a/listener/redir/tcp_freebsd.go +++ b/listener/redir/tcp_freebsd.go @@ -1,12 +1,16 @@ package redir import ( + "encoding/binary" "errors" "net" + "net/netip" "syscall" "unsafe" "github.com/Dreamacro/clash/transport/socks5" + + "golang.org/x/sys/unix" ) const ( @@ -25,28 +29,38 @@ func parserPacket(conn net.Conn) (socks5.Addr, error) { return nil, err } - var addr socks5.Addr + var addr netip.AddrPort rc.Control(func(fd uintptr) { - addr, err = getorigdst(fd) + if ip4 := c.LocalAddr().(*net.TCPAddr).IP.To4(); ip4 != nil { + 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 -func getorigdst(fd uintptr) (socks5.Addr, error) { - raw := syscall.RawSockaddrInet4{} - siz := unsafe.Sizeof(raw) - _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0) +func getorigdst(fd uintptr) (netip.AddrPort, error) { + addr := unix.RawSockaddrInet4{} + size := uint32(unsafe.Sizeof(addr)) + _, _, 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 { - return nil, err + return netip.AddrPort{}, err } - - addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks5.AtypIPv4 - copy(addr[1:1+net.IPv4len], raw.Addr[:]) - port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian - addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] - return addr, nil + port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:]) + return netip.AddrPortFrom(netip.AddrFrom4(addr.Addr), port), nil +} + +func getorigdst6(fd uintptr) (netip.AddrPort, error) { + addr := unix.RawSockaddrInet6{} + 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 }