Merge remote-tracking branch 'clash/dev' into Alpha

# Conflicts:
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/docker.yml
#	.github/workflows/linter.yml
#	.github/workflows/stale.yml
#	Makefile
#	component/dialer/dialer.go
#	config/config.go
#	constant/metadata.go
#	constant/rule.go
#	rule/common/domain.go
#	rule/common/domain_keyword.go
#	rule/common/domain_suffix.go
#	rule/common/final.go
#	rule/common/ipcidr.go
#	rule/geoip.go
#	rule/parser.go
#	rule/port.go
#	rule/process.go
This commit is contained in:
MetaCubeX 2022-03-15 23:13:41 +08:00
commit f01ac69654
32 changed files with 123 additions and 175 deletions

View file

@ -15,7 +15,7 @@ jobs:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Cache go module
uses: actions/cache@v2

14
.golangci.yaml Normal file
View file

@ -0,0 +1,14 @@
linters:
disable-all: true
enable:
- gofumpt
- megacheck
- govet
- gci
linters-settings:
gci:
sections:
- standard
- prefix(github.com/Dreamacro/clash)
- default

View file

@ -12,7 +12,7 @@ RUN go mod download && \
FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash"
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /Country.mmdb /root/.config/clash/
COPY --from=builder /clash /
ENTRYPOINT ["/clash"]

View file

@ -4,9 +4,9 @@ import (
"net"
"syscall"
"golang.org/x/sys/unix"
"github.com/Dreamacro/clash/component/iface"
"golang.org/x/sys/unix"
)
type controlFn = func(network, address string, c syscall.RawConn) error

View file

@ -3,7 +3,6 @@ package process
import (
"encoding/binary"
"net"
"path/filepath"
"syscall"
"unsafe"
@ -96,7 +95,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
return "", errno
}
return filepath.Base(unix.ByteSliceToString(buf)), nil
return unix.ByteSliceToString(buf), nil
}
func readNativeUint32(b []byte) uint32 {

View file

@ -4,7 +4,6 @@ import (
"encoding/binary"
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
"sync"
@ -77,7 +76,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
return "", errno
}
return filepath.Base(string(buf[:size-1])), nil
return string(buf[:size-1]), nil
}
func readNativeUint32(b []byte) uint32 {

View file

@ -4,12 +4,12 @@ import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"unicode"
"unsafe"
"github.com/Dreamacro/clash/common/pool"
@ -25,17 +25,6 @@ var nativeEndian = func() binary.ByteOrder {
return binary.LittleEndian
}()
type (
SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
ProcessNameResolver func(inode, uid int) (name string, err error)
)
// export for android
var (
DefaultSocketResolver SocketResolver = resolveSocketByNetlink
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
)
const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20
@ -43,15 +32,15 @@ const (
)
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil {
return "", err
}
return DefaultProcessNameResolver(inode, uid)
return resolveProcessNameByProcSearch(inode, uid)
}
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) {
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int32, error) {
var family byte
var protocol byte
@ -74,13 +63,12 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("dial netlink: %w", err)
}
defer syscall.Close(socket)
syscall.SetNonblock(socket, true)
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 50})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 50})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
if err := syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
@ -92,7 +80,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
}
if _, err := syscall.Write(socket, req); err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("write request: %w", err)
}
rb := pool.Get(pool.RelayBufferSize)
@ -100,24 +88,27 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, e
n, err := syscall.Read(socket, rb)
if err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("read response: %w", err)
}
messages, err := syscall.ParseNetlinkMessage(rb[:n])
if err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("parse netlink message: %w", err)
} else if len(messages) == 0 {
return 0, 0, io.ErrUnexpectedEOF
return 0, 0, fmt.Errorf("unexcepted netlink response")
}
message := messages[0]
if message.Header.Type&syscall.NLMSG_ERROR != 0 {
return 0, 0, syscall.ESRCH
return 0, 0, fmt.Errorf("netlink message: NLMSG_ERROR")
}
uid, inode := unpackSocketDiagResponse(&messages[0])
if uid < 0 || inode < 0 {
return 0, 0, fmt.Errorf("invalid uid(%d) or inode(%d)", uid, inode)
}
return int(uid), int(inode), nil
return uid, inode, nil
}
func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte {
@ -155,20 +146,20 @@ func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint
return buf
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
if len(msg.Data) < 72 {
return 0, 0
}
data := msg.Data
uid = nativeEndian.Uint32(data[64:68])
inode = nativeEndian.Uint32(data[68:72])
uid = int32(nativeEndian.Uint32(data[64:68]))
inode = int32(nativeEndian.Uint32(data[68:72]))
return
}
func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return "", err
@ -205,38 +196,16 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
}
if bytes.Equal(buffer[:n], socket) {
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
if err != nil {
return "", err
}
return splitCmdline(cmdline), nil
return os.Readlink(path.Join(processPath, "exe"))
}
}
}
return "", syscall.ESRCH
}
func splitCmdline(cmdline []byte) string {
indexOfEndOfString := len(cmdline)
for i, c := range cmdline {
if c == 0 {
indexOfEndOfString = i
break
}
}
return filepath.Base(string(cmdline[:indexOfEndOfString]))
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
}
func isPid(s string) bool {
for _, s := range s {
if s < '0' || s > '9' {
return false
}
}
return true
return strings.IndexFunc(s, func(r rune) bool {
return !unicode.IsDigit(r)
}) == -1
}

View file

@ -3,7 +3,6 @@ package process
import (
"fmt"
"net"
"path/filepath"
"sync"
"syscall"
"unsafe"
@ -220,5 +219,5 @@ func getExecPathFromPID(pid uint32) (string, error) {
if r1 == 0 {
return "", err
}
return filepath.Base(syscall.UTF16ToString(buf[:size])), nil
return syscall.UTF16ToString(buf[:size]), nil
}

View file

@ -176,6 +176,7 @@ type RawConfig struct {
GeodataMode string `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"`
AutoIptables bool `yaml:"auto-iptables"`
RoutingMark int `yaml:"routing-mark"`
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
RuleProvider map[string]map[string]interface{} `yaml:"rule-providers"`
@ -240,7 +241,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
},
NameServer: []string{
"223.5.5.5",
"119.29.29",
"119.29.29.29",
},
FakeIPFilter: []string{
"dns.msftnsci.com",
@ -351,6 +352,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
Interface: cfg.Interface,
GeodataLoader: cfg.GeodataLoader,
AutoIptables: cfg.AutoIptables,
RoutingMark: cfg.RoutingMark,
}, nil
}

View file

@ -83,6 +83,7 @@ type Metadata struct {
Host string `json:"host"`
Process string `json:"process"`
DNSMode DNSMode `json:"dnsMode"`
ProcessPath string `json:"processPath"`
}
func (m *Metadata) RemoteAddress() string {

View file

@ -12,6 +12,7 @@ const (
SrcPort
DstPort
Process
ProcessPath
Script
RuleSet
Network
@ -46,6 +47,8 @@ func (rt RuleType) String() string {
return "DstPort"
case Process:
return "Process"
case ProcessPath:
return "ProcessPath"
case Script:
return "Script"
case MATCH:
@ -71,5 +74,6 @@ type Rule interface {
Adapter() string
Payload() string
ShouldResolveIP() bool
ShouldFindProcess() bool
RuleExtra() *RuleExtra
}

View file

@ -232,6 +232,7 @@ func updateGeneral(general *config.General, Tun *config.Tun, force bool) {
}
dialer.DefaultInterface.Store(general.Interface)
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
log.Infoln("Use interface name: %s", general.Interface)

View file

@ -19,7 +19,12 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
client := newClient(c.RemoteAddr(), in)
defer client.CloseIdleConnections()
conn := N.NewBufferedConn(c)
var conn *N.BufferedConn
if bufConn, ok := c.(*N.BufferedConn); ok {
conn = bufConn
} else {
conn = N.NewBufferedConn(c)
}
keepAlive := true
trusted := cache == nil // disable authenticate if cache is nil

View file

@ -64,6 +64,8 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
}
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
conn.(*net.TCPConn).SetKeepAlive(true)
bufConn := N.NewBufferedConn(conn)
head, err := bufConn.Peek(1)
if err != nil {

View file

@ -61,6 +61,7 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
}
func handleSocks(conn net.Conn, in chan<- C.ConnContext) {
conn.(*net.TCPConn).SetKeepAlive(true)
bufConn := N.NewBufferedConn(conn)
head, err := bufConn.Peek(1)
if err != nil {
@ -84,9 +85,6 @@ func HandleSocks4(conn net.Conn, in chan<- C.ConnContext) {
conn.Close()
return
}
if c, ok := conn.(*net.TCPConn); ok {
c.SetKeepAlive(true)
}
in <- inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4)
}
@ -96,9 +94,6 @@ func HandleSocks5(conn net.Conn, in chan<- C.ConnContext) {
conn.Close()
return
}
if c, ok := conn.(*net.TCPConn); ok {
c.SetKeepAlive(true)
}
if command == socks5.CmdUDPAssociate {
defer conn.Close()
io.Copy(io.Discard, conn)

View file

@ -39,6 +39,10 @@ func (d *Domain) RuleExtra() *C.RuleExtra {
return d.ruleExtra
}
func (d *Domain) ShouldFindProcess() bool {
return false
}
func NewDomain(domain string, adapter string, ruleExtra *C.RuleExtra) *Domain {
return &Domain{
domain: strings.ToLower(domain),

View file

@ -36,11 +36,15 @@ func (dk *DomainKeyword) ShouldResolveIP() bool {
return false
}
func (dk *DomainKeyword) ShouldFindProcess() bool {
return false
}
func (dk *DomainKeyword) RuleExtra() *C.RuleExtra {
return dk.ruleExtra
}
func NewDomainKeyword(keyword string, adapter string, ruleExtra *C.RuleExtra) *DomainKeyword {
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
return &DomainKeyword{
keyword: strings.ToLower(keyword),
adapter: adapter,

View file

@ -40,6 +40,11 @@ func (ds *DomainSuffix) RuleExtra() *C.RuleExtra {
return ds.ruleExtra
}
func (ds *DomainSuffix) ShouldFindProcess() bool {
return false
}
func NewDomainSuffix(suffix string, adapter string, ruleExtra *C.RuleExtra) *DomainSuffix {
return &DomainSuffix{
suffix: strings.ToLower(suffix),

View file

@ -29,6 +29,10 @@ func (f *Match) ShouldResolveIP() bool {
return false
}
func (f *Match) ShouldFindProcess() bool {
return false
}
func (f *Match) RuleExtra() *C.RuleExtra {
return f.ruleExtra
}

View file

@ -55,6 +55,10 @@ func (i *IPCIDR) ShouldResolveIP() bool {
return !i.noResolveIP
}
func (i *IPCIDR) ShouldFindProcess() bool {
return false
}
func (i *IPCIDR) RuleExtra() *C.RuleExtra {
return i.ruleExtra
}

View file

@ -1,4 +1,4 @@
package rule
package rules
import (
"fmt"
@ -42,7 +42,9 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
case "DST-PORT":
parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
case "PROCESS-NAME":
parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
parsed, parseErr = RC.NewProcess(payload, target, true,ruleExtra)
case "PROCESS-PATH":
parsed, parseErr = RC.NewProcess(payload, target, false,ruleExtra)
case "MATCH":
parsed = RC.NewMatch(target, ruleExtra)
case "RULE-SET":

View file

@ -1,28 +0,0 @@
{
"inbounds": [
{
"port": 10002,
"listen": "0.0.0.0",
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 0
}
]
},
"streamSettings": {
"network": "tcp"
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
],
"log": {
"loglevel": "debug"
}
}

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -7,8 +7,7 @@
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"alterId": 32
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
},

View file

@ -34,51 +34,12 @@ func TestClash_Vmess(t *testing.T) {
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
Server: localIP.String(),
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
time.Sleep(waitTime)
testSuit(t, proxy)
}
func TestClash_VmessAEAD(t *testing.T) {
configPath := C.Path.Resolve("vmess-aead.json")
cfg := &container.Config{
Image: ImageVmess,
ExposedPorts: defaultExposedPorts,
}
hostCfg := &container.HostConfig{
PortBindings: defaultPortBindings,
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
}
id, err := startContainer(cfg, hostCfg, "vmess-aead")
if err != nil {
assert.FailNow(t, err.Error())
}
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
Server: localIP.String(),
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 0,
UDP: true,
Name: "vmess",
Server: localIP.String(),
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
@ -114,7 +75,6 @@ func TestClash_VmessTLS(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
TLS: true,
SkipCertVerify: true,
ServerName: "example.org",
@ -154,7 +114,6 @@ func TestClash_VmessHTTP2(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
Network: "h2",
TLS: true,
SkipCertVerify: true,
@ -197,7 +156,6 @@ func TestClash_VmessHTTP(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
Network: "http",
UDP: true,
HTTPOpts: outbound.HTTPOptions{
@ -250,7 +208,6 @@ func TestClash_VmessWebsocket(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
Network: "ws",
UDP: true,
})
@ -288,7 +245,6 @@ func TestClash_VmessWebsocketTLS(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
Network: "ws",
TLS: true,
SkipCertVerify: true,
@ -328,7 +284,6 @@ func TestClash_VmessGrpc(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
Network: "grpc",
TLS: true,
SkipCertVerify: true,
@ -370,7 +325,6 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
Network: "ws",
UDP: true,
ServerName: "example.org",
@ -411,7 +365,6 @@ func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
Port: 10002,
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
Cipher: "auto",
AlterID: 32,
Network: "ws",
UDP: true,
ServerName: "example.org",

View file

@ -6,11 +6,13 @@ import (
R "github.com/Dreamacro/clash/rule/common"
"net"
"runtime"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/component/nat"
P "github.com/Dreamacro/clash/component/process"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
@ -329,6 +331,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
defer configMux.RUnlock()
var resolved bool
var processFound bool
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
ip := node.Data.(net.IP)
@ -351,6 +354,21 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
resolved = true
}
if !processFound && rule.ShouldFindProcess() {
processFound = true
srcPort, err := strconv.Atoi(metadata.SrcPort)
if err == nil {
path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
if err != nil {
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
} else {
log.Debugln("[Process] %s from process %s", metadata.String(), path)
metadata.ProcessPath = path
}
}
}
if rule.Match(metadata) {
adapter, ok := proxies[rule.Adapter()]
if !ok {