Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
3600077f3b | |||
de7656a787 | |||
5dfe7f8561 | |||
ed27898a33 | |||
532396d25c | |||
4b1b494164 | |||
0d33dc3eb9 | |||
994cbff215 | |||
bea2ee8bf2 |
@ -28,9 +28,18 @@
|
||||
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
||||
- Comprehensive HTTP RESTful API controller
|
||||
|
||||
## Premium Features
|
||||
|
||||
- TUN mode on macOS, Linux and Windows. [Doc](https://github.com/Dreamacro/clash/wiki/premium-core-features#tun-device)
|
||||
- Match your tunnel by [Script](https://github.com/Dreamacro/clash/wiki/premium-core-features#script)
|
||||
- [Rule Provider](https://github.com/Dreamacro/clash/wiki/premium-core-features#rule-providers)
|
||||
|
||||
## Getting Started
|
||||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||
|
||||
## Premium Release
|
||||
[Release](https://github.com/Dreamacro/clash/releases/tag/premium)
|
||||
|
||||
## Credits
|
||||
|
||||
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||
|
@ -40,7 +40,7 @@ type ShadowSocksOption struct {
|
||||
}
|
||||
|
||||
type simpleObfsOption struct {
|
||||
Mode string `obfs:"mode"`
|
||||
Mode string `obfs:"mode,omitempty"`
|
||||
Host string `obfs:"host,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,10 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
|
||||
}
|
||||
|
||||
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
|
||||
switch obfsOption.Mode {
|
||||
case "tls", "http", "":
|
||||
break
|
||||
default:
|
||||
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -59,11 +60,18 @@ func (hc *HealthCheck) touch() {
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for _, proxy := range hc.proxies {
|
||||
go proxy.URLTest(ctx, hc.url)
|
||||
wg.Add(1)
|
||||
|
||||
go func(p C.Proxy) {
|
||||
p.URLTest(ctx, hc.url)
|
||||
wg.Done()
|
||||
}(proxy)
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
wg.Wait()
|
||||
cancel()
|
||||
}
|
||||
|
||||
|
21
component/process/process.go
Normal file
21
component/process/process.go
Normal file
@ -0,0 +1,21 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidNetwork = errors.New("invalid network")
|
||||
ErrPlatformNotSupport = errors.New("not support on this platform")
|
||||
ErrNotFound = errors.New("process not found")
|
||||
)
|
||||
|
||||
const (
|
||||
TCP = "tcp"
|
||||
UDP = "udp"
|
||||
)
|
||||
|
||||
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
|
||||
return findProcessName(network, srcIP, srcPort)
|
||||
}
|
@ -1,109 +1,26 @@
|
||||
package rules
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
// store process name for when dealing with multiple PROCESS-NAME rules
|
||||
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||
|
||||
type Process struct {
|
||||
adapter string
|
||||
process string
|
||||
}
|
||||
|
||||
func (ps *Process) RuleType() C.RuleType {
|
||||
return C.Process
|
||||
}
|
||||
|
||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
name, err := getExecPathFromAddress(metadata)
|
||||
if err != nil {
|
||||
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
|
||||
}
|
||||
|
||||
processCache.Set(key, name)
|
||||
|
||||
cached = name
|
||||
}
|
||||
|
||||
return strings.EqualFold(cached.(string), ps.process)
|
||||
}
|
||||
|
||||
func (p *Process) Adapter() string {
|
||||
return p.adapter
|
||||
}
|
||||
|
||||
func (p *Process) Payload() string {
|
||||
return p.process
|
||||
}
|
||||
|
||||
func (p *Process) ShouldResolveIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewProcess(process string, adapter string) (*Process, error) {
|
||||
return &Process{
|
||||
adapter: adapter,
|
||||
process: process,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
procpidpathinfo = 0xb
|
||||
procpidpathinfosize = 1024
|
||||
proccallnumpidinfo = 0x2
|
||||
)
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
buf := make([]byte, procpidpathinfosize)
|
||||
_, _, errno := syscall.Syscall6(
|
||||
syscall.SYS_PROC_INFO,
|
||||
proccallnumpidinfo,
|
||||
uintptr(pid),
|
||||
procpidpathinfo,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
procpidpathinfosize)
|
||||
if errno != 0 {
|
||||
return "", errno
|
||||
}
|
||||
firstZero := bytes.IndexByte(buf, 0)
|
||||
if firstZero <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return filepath.Base(string(buf[:firstZero])), nil
|
||||
}
|
||||
|
||||
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
ip := metadata.SrcIP
|
||||
port, err := strconv.Atoi(metadata.SrcPort)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func findProcessName(network string, ip net.IP, port int) (string, error) {
|
||||
var spath string
|
||||
switch metadata.NetWork {
|
||||
case C.TCP:
|
||||
switch network {
|
||||
case TCP:
|
||||
spath = "net.inet.tcp.pcblist_n"
|
||||
case C.UDP:
|
||||
case UDP:
|
||||
spath = "net.inet.udp.pcblist_n"
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
@ -123,7 +40,7 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
||||
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
||||
itemSize := 384
|
||||
if metadata.NetWork == C.TCP {
|
||||
if network == TCP {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
itemSize += 208
|
||||
}
|
||||
@ -161,7 +78,28 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
return "", errors.New("process not found")
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
buf := make([]byte, procpidpathinfosize)
|
||||
_, _, errno := syscall.Syscall6(
|
||||
syscall.SYS_PROC_INFO,
|
||||
proccallnumpidinfo,
|
||||
uintptr(pid),
|
||||
procpidpathinfo,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
procpidpathinfosize)
|
||||
if errno != 0 {
|
||||
return "", errno
|
||||
}
|
||||
firstZero := bytes.IndexByte(buf, 0)
|
||||
if firstZero <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return filepath.Base(string(buf[:firstZero])), nil
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
@ -1,8 +1,7 @@
|
||||
package rules
|
||||
package process
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
@ -12,78 +11,48 @@ import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
// store process name for when dealing with multiple PROCESS-NAME rules
|
||||
var (
|
||||
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||
errNotFound = errors.New("process not found")
|
||||
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
|
||||
|
||||
defaultSearcher *searcher
|
||||
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type Process struct {
|
||||
adapter string
|
||||
process string
|
||||
}
|
||||
|
||||
func (ps *Process) RuleType() C.RuleType {
|
||||
return C.Process
|
||||
}
|
||||
|
||||
func match(ps *Process, metadata *C.Metadata) bool {
|
||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
name, err := getExecPathFromAddress(metadata)
|
||||
if err != nil {
|
||||
log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error())
|
||||
}
|
||||
|
||||
processCache.Set(key, name)
|
||||
|
||||
cached = name
|
||||
}
|
||||
|
||||
return strings.EqualFold(cached.(string), ps.process)
|
||||
}
|
||||
|
||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
return matchMeta(ps, metadata)
|
||||
}
|
||||
|
||||
func (p *Process) Adapter() string {
|
||||
return p.adapter
|
||||
}
|
||||
|
||||
func (p *Process) Payload() string {
|
||||
return p.process
|
||||
}
|
||||
|
||||
func (p *Process) ShouldResolveIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewProcess(process string, adapter string) (*Process, error) {
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
once.Do(func() {
|
||||
err := initSearcher()
|
||||
if err != nil {
|
||||
if err := initSearcher(); err != nil {
|
||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||
log.Warnln("All PROCESS-NAME rules will be skipped")
|
||||
return
|
||||
}
|
||||
matchMeta = match
|
||||
})
|
||||
return &Process{
|
||||
adapter: adapter,
|
||||
process: process,
|
||||
}, nil
|
||||
|
||||
var spath string
|
||||
isTCP := network == TCP
|
||||
switch network {
|
||||
case TCP:
|
||||
spath = "net.inet.tcp.pcblist"
|
||||
case UDP:
|
||||
spath = "net.inet.udp.pcblist"
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
@ -107,41 +76,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
return filepath.Base(string(buf[:size-1])), nil
|
||||
}
|
||||
|
||||
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
ip := metadata.SrcIP
|
||||
port, err := strconv.Atoi(metadata.SrcPort)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var spath string
|
||||
var isTCP bool
|
||||
switch metadata.NetWork {
|
||||
case C.TCP:
|
||||
spath = "net.inet.tcp.pcblist"
|
||||
isTCP = true
|
||||
case C.UDP:
|
||||
spath = "net.inet.udp.pcblist"
|
||||
isTCP = false
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
|
||||
pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
@ -213,7 +147,7 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3
|
||||
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
|
||||
return s.searchSocketPid(socket)
|
||||
}
|
||||
return 0, errNotFound
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||
@ -235,7 +169,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||
return pid, nil
|
||||
}
|
||||
}
|
||||
return 0, errNotFound
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func newSearcher(major int) *searcher {
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -9,15 +9,10 @@ import (
|
||||
"net"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
|
||||
@ -30,7 +25,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
type SocketResolver func(metadata *C.Metadata) (inode, uid int, err error)
|
||||
type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error)
|
||||
type ProcessNameResolver func(inode, uid int) (name string, err error)
|
||||
|
||||
// export for android
|
||||
@ -39,51 +34,6 @@ var (
|
||||
DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch
|
||||
)
|
||||
|
||||
type Process struct {
|
||||
adapter string
|
||||
process string
|
||||
}
|
||||
|
||||
func (p *Process) RuleType() C.RuleType {
|
||||
return C.Process
|
||||
}
|
||||
|
||||
func (p *Process) Match(metadata *C.Metadata) bool {
|
||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
processName, err := resolveProcessName(metadata)
|
||||
if err != nil {
|
||||
log.Debugln("[%s] Resolve process of %s failure: %s", C.Process.String(), key, err.Error())
|
||||
}
|
||||
|
||||
processCache.Set(key, processName)
|
||||
|
||||
cached = processName
|
||||
}
|
||||
|
||||
return strings.EqualFold(cached.(string), p.process)
|
||||
}
|
||||
|
||||
func (p *Process) Adapter() string {
|
||||
return p.adapter
|
||||
}
|
||||
|
||||
func (p *Process) Payload() string {
|
||||
return p.process
|
||||
}
|
||||
|
||||
func (p *Process) ShouldResolveIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewProcess(process string, adapter string) (*Process, error) {
|
||||
return &Process{
|
||||
adapter: adapter,
|
||||
process: process,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
|
||||
socketDiagByFamily = 20
|
||||
@ -92,10 +42,8 @@ const (
|
||||
|
||||
var nativeEndian binary.ByteOrder = binary.LittleEndian
|
||||
|
||||
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||
|
||||
func resolveProcessName(metadata *C.Metadata) (string, error) {
|
||||
inode, uid, err := DefaultSocketResolver(metadata)
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
inode, uid, err := DefaultSocketResolver(network, ip, srcPort)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -103,31 +51,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
|
||||
return DefaultProcessNameResolver(inode, uid)
|
||||
}
|
||||
|
||||
func resolveSocketByNetlink(metadata *C.Metadata) (int, int, error) {
|
||||
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) {
|
||||
var family byte
|
||||
var protocol byte
|
||||
|
||||
switch metadata.NetWork {
|
||||
case C.TCP:
|
||||
switch network {
|
||||
case TCP:
|
||||
protocol = syscall.IPPROTO_TCP
|
||||
case C.UDP:
|
||||
case UDP:
|
||||
protocol = syscall.IPPROTO_UDP
|
||||
default:
|
||||
return 0, 0, ErrInvalidNetwork
|
||||
}
|
||||
|
||||
if metadata.SrcIP.To4() != nil {
|
||||
if ip.To4() != nil {
|
||||
family = syscall.AF_INET
|
||||
} else {
|
||||
family = syscall.AF_INET6
|
||||
}
|
||||
|
||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort))
|
||||
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
|
||||
|
||||
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
|
||||
if err != nil {
|
10
component/process/process_other.go
Normal file
10
component/process/process_other.go
Normal file
@ -0,0 +1,10 @@
|
||||
// +build !darwin,!linux,!windows
|
||||
// +build !freebsd !amd64
|
||||
|
||||
package process
|
||||
|
||||
import "net"
|
||||
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
@ -1,18 +1,13 @@
|
||||
package rules
|
||||
package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@ -27,10 +22,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||
errNotFound = errors.New("process not found")
|
||||
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
|
||||
|
||||
getExTcpTable uintptr
|
||||
getExUdpTable uintptr
|
||||
queryProcName uintptr
|
||||
@ -67,47 +58,7 @@ func initWin32API() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
adapter string
|
||||
process string
|
||||
}
|
||||
|
||||
func (p *Process) RuleType() C.RuleType {
|
||||
return C.Process
|
||||
}
|
||||
|
||||
func (p *Process) Adapter() string {
|
||||
return p.adapter
|
||||
}
|
||||
|
||||
func (p *Process) Payload() string {
|
||||
return p.process
|
||||
}
|
||||
|
||||
func (p *Process) ShouldResolveIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func match(p *Process, metadata *C.Metadata) bool {
|
||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
processName, err := resolveProcessName(metadata)
|
||||
if err != nil {
|
||||
log.Debugln("[%s] Resolve process of %s failed: %s", C.Process.String(), key, err.Error())
|
||||
}
|
||||
|
||||
processCache.Set(key, processName)
|
||||
cached = processName
|
||||
}
|
||||
return strings.EqualFold(cached.(string), p.process)
|
||||
}
|
||||
|
||||
func (p *Process) Match(metadata *C.Metadata) bool {
|
||||
return matchMeta(p, metadata)
|
||||
}
|
||||
|
||||
func NewProcess(process string, adapter string) (*Process, error) {
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
once.Do(func() {
|
||||
err := initWin32API()
|
||||
if err != nil {
|
||||
@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) {
|
||||
log.Warnln("All PROCESS-NAMES rules will be skiped")
|
||||
return
|
||||
}
|
||||
matchMeta = match
|
||||
})
|
||||
return &Process{
|
||||
adapter: adapter,
|
||||
process: process,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resolveProcessName(metadata *C.Metadata) (string, error) {
|
||||
ip := metadata.SrcIP
|
||||
family := windows.AF_INET
|
||||
if ip.To4() == nil {
|
||||
family = windows.AF_INET6
|
||||
@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) {
|
||||
|
||||
var class int
|
||||
var fn uintptr
|
||||
switch metadata.NetWork {
|
||||
case C.TCP:
|
||||
switch network {
|
||||
case TCP:
|
||||
fn = getExTcpTable
|
||||
class = tcpTablePidConn
|
||||
case C.UDP:
|
||||
case UDP:
|
||||
fn = getExUdpTable
|
||||
class = udpTablePid
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf, err := getTransportTable(fn, family, class)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s := newSearcher(family == windows.AF_INET, metadata.NetWork == C.TCP)
|
||||
s := newSearcher(family == windows.AF_INET, network == TCP)
|
||||
|
||||
pid, err := s.Search(buf, ip, uint16(srcPort))
|
||||
if err != nil {
|
||||
@ -196,14 +133,15 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) {
|
||||
}
|
||||
|
||||
srcIP := net.IP(row[s.ip : s.ip+s.ipSize])
|
||||
if !ip.Equal(srcIP) {
|
||||
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
||||
if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
||||
continue
|
||||
}
|
||||
|
||||
pid := readNativeUint32(row[s.pid : s.pid+4])
|
||||
return pid, nil
|
||||
}
|
||||
return 0, errNotFound
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func newSearcher(isV4, isTCP bool) *searcher {
|
@ -395,10 +395,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
|
||||
parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
|
||||
if parseErr != nil {
|
||||
if parseErr == R.ErrPlatformNotSupport {
|
||||
log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
}
|
||||
|
||||
|
10
go.mod
10
go.mod
@ -14,9 +14,9 @@ require (
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
go.uber.org/atomic v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
21
go.sum
21
go.sum
@ -34,27 +34,28 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
@ -65,7 +66,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -72,21 +72,29 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache
|
||||
|
||||
func HandleConn(conn net.Conn, cache *cache.Cache) {
|
||||
br := bufio.NewReader(conn)
|
||||
|
||||
keepAlive:
|
||||
request, err := http.ReadRequest(br)
|
||||
if err != nil || request.URL.Host == "" {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
|
||||
authenticator := authStore.Authenticator()
|
||||
if authenticator != nil {
|
||||
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
|
||||
conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
||||
conn.Close()
|
||||
if keepAlive {
|
||||
goto keepAlive
|
||||
}
|
||||
return
|
||||
} else if !canActivate(authStrings[1], authenticator, cache) {
|
||||
conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n"))
|
||||
log.Infoln("Auth failed from %s", conn.RemoteAddr().String())
|
||||
if keepAlive {
|
||||
goto keepAlive
|
||||
}
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
@ -95,6 +103,7 @@ func HandleConn(conn net.Conn, cache *cache.Cache) {
|
||||
if request.Method == http.MethodConnect {
|
||||
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
tunnel.Add(adapters.NewHTTPS(request, conn))
|
||||
|
@ -5,9 +5,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errPayload = errors.New("payload error")
|
||||
ErrPlatformNotSupport = errors.New("not support on this platform")
|
||||
ErrInvalidNetwork = errors.New("invalid network")
|
||||
errPayload = errors.New("payload error")
|
||||
|
||||
noResolve = "no-resolve"
|
||||
)
|
||||
|
65
rules/process.go
Normal file
65
rules/process.go
Normal file
@ -0,0 +1,65 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/process"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||
|
||||
type Process struct {
|
||||
adapter string
|
||||
process string
|
||||
}
|
||||
|
||||
func (ps *Process) RuleType() C.RuleType {
|
||||
return C.Process
|
||||
}
|
||||
|
||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
||||
if err != nil {
|
||||
processCache.Set(key, "")
|
||||
return false
|
||||
}
|
||||
|
||||
name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
|
||||
if err != nil {
|
||||
log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error())
|
||||
}
|
||||
|
||||
processCache.Set(key, name)
|
||||
|
||||
cached = name
|
||||
}
|
||||
|
||||
return strings.EqualFold(cached.(string), ps.process)
|
||||
}
|
||||
|
||||
func (p *Process) Adapter() string {
|
||||
return p.adapter
|
||||
}
|
||||
|
||||
func (p *Process) Payload() string {
|
||||
return p.process
|
||||
}
|
||||
|
||||
func (p *Process) ShouldResolveIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewProcess(process string, adapter string) (*Process, error) {
|
||||
return &Process{
|
||||
adapter: adapter,
|
||||
process: process,
|
||||
}, nil
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
// +build !darwin,!linux,!windows
|
||||
// +build !freebsd !amd64
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func NewProcess(process string, adapter string) (C.Rule, error) {
|
||||
return nil, ErrPlatformNotSupport
|
||||
}
|
@ -218,7 +218,11 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
||||
|
||||
rawPc, err := proxy.DialUDP(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
|
||||
if rule == nil {
|
||||
log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
|
||||
} else {
|
||||
log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
pc := newUDPTracker(rawPc, DefaultManager, metadata, rule)
|
||||
@ -263,7 +267,11 @@ func handleTCPConn(localConn C.ServerAdapter) {
|
||||
|
||||
remoteConn, err := proxy.Dial(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
|
||||
if rule == nil {
|
||||
log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error())
|
||||
} else {
|
||||
log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule)
|
||||
|
Reference in New Issue
Block a user