Compare commits

...

9 Commits

18 changed files with 233 additions and 364 deletions

View File

@ -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)

View File

@ -40,7 +40,7 @@ type ShadowSocksOption struct {
}
type simpleObfsOption struct {
Mode string `obfs:"mode"`
Mode string `obfs:"mode,omitempty"`
Host string `obfs:"host,omitempty"`
}

View File

@ -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)
}

View File

@ -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()
}

View 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)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View 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
}

View File

@ -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 {

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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))

View File

@ -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
View 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
}

View File

@ -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
}

View File

@ -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)