Compare commits

..

52 Commits

Author SHA1 Message Date
be8eb7c17c feat: add port whitelist, empty is all port 2022-04-21 06:50:20 -07:00
58cd8f9ac1 fix:force-domain invalid 2022-04-17 21:17:21 +08:00
ea0d236259 chore: change comments 2022-04-17 20:03:53 +08:00
48a01adb7a refactor: sniffer param force and reverses deprecated, will be removed when release version, replace force-domain and skip-sni,
force-domain add '+' equivalent to force is true
sniffer:
  enable: true
  force-domain:
    - "google.com"
  skip-sni:
    - www.baidu.com
  sniffing:
    - tls
2022-04-17 20:02:13 +08:00
f8d7f29856 fix: PASS policy inconsistent names 2022-04-17 14:11:58 +08:00
1cf9321aa0 fix: domain tree match failed 2022-04-16 11:55:49 +08:00
71a1f5dfbd fix: domain type fix Mapping 2022-04-16 09:51:31 +08:00
25426cba33 chore: log style 2022-04-16 09:04:43 +08:00
9d364f66e9 fix: reverse error when force is false 2022-04-16 08:53:31 +08:00
7c23fa2bd4 fix: sniffer npe 2022-04-16 08:45:18 +08:00
0658ecadd3 fix: adjust loading timing 2022-04-16 08:29:38 +08:00
efbc334b3b Merge branch 'logic-rule' into Alpha 2022-04-16 08:22:08 +08:00
80764217c2 feat: add domain list for sniffer, reverse force logic
when force is false, if domain in the list, will force replace
when force is true, if sniff domain in the list, will skip it
2022-04-16 08:21:31 +08:00
45fe6e996b fix: npe when parse rule 2022-04-16 00:21:08 +08:00
36a719e2f8 feat: support http headers 2022-04-14 13:07:39 +08:00
1b6b0052c2 chore:adjust sniffer debuglog info 2022-04-13 08:38:55 +08:00
c981ef0f28 chore: update dependencies 2022-04-13 02:32:55 +08:00
1bf291d240 chore: update dependencies 2022-04-13 02:24:21 +08:00
b179d09efb Chore: adjust ipstack 2022-04-13 02:20:53 +08:00
4be17653e0 Fix: fakeip pool cycle used 2022-04-13 02:19:42 +08:00
21446ba5d4 chore: adjust code 2022-04-12 21:39:31 +08:00
75ce6b59bf Refactor: fakeip pool use netip.Prefix, supports ipv6 range 2022-04-12 20:32:08 +08:00
ce96ac35fb chore:merge & adjust code 2022-04-12 20:20:04 +08:00
173e10abe6 Chore: fix typos 2022-04-12 19:08:13 +08:00
a6eb11ce18 Refactor: DomainTrie use generics 2022-04-12 18:45:47 +08:00
0c65f6962a Refactor: queue use generics 2022-04-12 18:44:13 +08:00
baa9e02af6 Refactor: cache use generics 2022-04-12 18:44:10 +08:00
673541e2a8 Refactor: lrucache use generics 2022-04-12 18:44:07 +08:00
14878b37f6 fix: trojan fail may panic 2022-04-12 18:43:55 +08:00
83e0abaa8c chore: adjust code 2022-04-11 13:23:59 +08:00
7166db2ac9 fix: code logic error 2022-04-10 20:01:35 +08:00
815a060309 Update metadata.go
revet commit 13012a9
2022-04-10 00:47:22 +08:00
544e0f137d feat: sniffer support
sniffer:
  enable: true
  force: false # Overwrite domain
  sniffing:
    - tls
2022-04-09 22:30:36 +08:00
07906c0aa5 fix: parse logic rule error 2022-04-09 22:25:39 +08:00
b2981f921c chore: reduce a little memory 2022-04-09 22:24:48 +08:00
7be3e617ab disable process name on android 2022-04-09 17:54:01 +08:00
9a3bc8ef9e fix: auto detect interface add param[auto-detect-interface], default is true, only use it when tun is enabled 2022-04-07 21:36:19 +08:00
e083d1c57d Revert "Add docker workflow"
This reverts commit ce4902e5e6.
2022-04-07 10:24:23 +08:00
ce4902e5e6 Add docker workflow 2022-04-06 08:31:34 +08:00
4b9edc3b66 revert:the name of tun device on mac 2022-04-05 23:04:59 +08:00
91e48b707b Merge remote-tracking branch 'yaling888/with-tun' into Alpha 2022-04-05 14:44:40 +08:00
7a8af90b86 feat: add SMTPS/POP3S/IMAPS port to sni detect 2022-04-05 03:26:23 +08:00
93d2cfa091 fix: when ssh connect to a ip, if this ip map to a domain in clash, change ip to host may redirect to a diffrent ip 2022-04-05 03:26:23 +08:00
5719b9d22f fix: npe panic 2022-04-04 22:28:47 +08:00
b553dd749b refactor: Some adjustments 2022-04-03 19:15:16 +08:00
9461bcd44e fix: default-nameserver allow DOT and DOH with host is ip 2022-04-03 19:14:21 +08:00
6548dc90fa Merge remote-tracking branch 'Plus/with-tun' into Alpha 2022-04-02 20:48:11 +08:00
13012a9f89 fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 17:32:37 +08:00
afdcb6cfc7 fix: log level ajust and lint fix 2022-03-31 21:27:25 +08:00
c495d314d4 feat: 添加tls sni 嗅探
# Conflicts:
#	tunnel/statistic/tracker.go
#	tunnel/tunnel.go
2022-03-31 21:27:25 +08:00
e877b68179 Chore: revert "Feature: add tls SNI sniffing (#68)"
This reverts commit 24ce6622a2.
2022-03-31 21:20:46 +08:00
24ce6622a2 Feature: add tls SNI sniffing (#68) 2022-03-31 19:34:40 +08:00
67 changed files with 1631 additions and 841 deletions

View File

@ -1,4 +1,4 @@
name: Release
name: Alpha
on: [push]
jobs:
Feature-build:
@ -44,18 +44,18 @@ jobs:
- name: Tag Repo
uses: richardsimko/update-tag@v1
with:
tag_name: v1.10.0
tag_name: alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Release
- name: Upload Alpha
uses: softprops/action-gh-release@v1
if: ${{ env.GIT_BRANCH == 'Meta' && success() }}
if: ${{ env.GIT_BRANCH != 'Meta' && success() }}
with:
tag: ${{ github.ref }}
tag_name: v1.10.0
tag_name: alpha
files: bin/*
prerelease: false
prerelease: true
- name: send telegram message on push
uses: appleboy/telegram-action@master

View File

@ -1,7 +1,7 @@
NAME=Clash.Meta
BINDIR=bin
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
VERSION=$(shell git describe --tags || echo "unknown version")
VERSION=alpha-$(shell git rev-parse --short HEAD)
BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \

View File

@ -21,7 +21,7 @@ var UnifiedDelay = atomic.NewBool(false)
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
}
@ -67,7 +67,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queue {
histories = append(histories, item.(C.DelayHistory))
histories = append(histories, item)
}
return histories
}
@ -80,11 +80,7 @@ func (p *Proxy) LastDelay() (delay uint16) {
return max
}
last := p.history.Last()
if last == nil {
return max
}
history := last.(C.DelayHistory)
history := p.history.Last()
if history.Delay == 0 {
return max
}
@ -178,7 +174,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
return &Proxy{adapter, queue.New[C.DelayHistory](10), atomic.NewBool(true)}
}
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {

View File

@ -13,10 +13,14 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
remoteAddr := conn.RemoteAddr()
// Filter when net.Addr interface is nil
if remoteAddr != nil {
if ip, port, err := parseAddr(remoteAddr.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
}
return context.NewConnContext(conn, metadata)
}

View File

@ -60,7 +60,7 @@ func NewCompatible() *Direct {
func NewPass() *Direct {
return &Direct{
Base: &Base{
name: "Pass",
name: "PASS",
tp: C.Pass,
udp: true,
},

View File

@ -22,6 +22,7 @@ type Http struct {
user string
pass string
tlsConfig *tls.Config
option *HttpOption
}
type HttpOption struct {
@ -34,6 +35,7 @@ type HttpOption struct {
TLS bool `proxy:"tls,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
}
// StreamConn implements C.ProxyAdapter
@ -84,6 +86,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
},
}
//增加headers
if len(h.option.Headers) != 0 {
for key, value := range h.option.Headers {
req.Header.Add(key, value)
}
}
if h.user != "" && h.pass != "" {
auth := h.user + ":" + h.pass
req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
@ -141,5 +150,6 @@ func NewHttp(option HttpOption) *Http {
user: option.UserName,
pass: option.Password,
tlsConfig: tlsConfig,
option: &option,
}
}

View File

@ -58,7 +58,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
}
req, err := http.NewRequest(http.MethodGet, uri.String(), nil)
req.Header.Set("user-agent", netHttp.UA)
req.Header.Set("User-Agent", netHttp.UA)
if err != nil {
return nil, err

48
common/cache/cache.go vendored
View File

@ -7,50 +7,50 @@ import (
)
// Cache store element with a expired time
type Cache struct {
*cache
type Cache[K comparable, V any] struct {
*cache[K, V]
}
type cache struct {
type cache[K comparable, V any] struct {
mapping sync.Map
janitor *janitor
janitor *janitor[K, V]
}
type element struct {
type element[V any] struct {
Expired time.Time
Payload any
Payload V
}
// Put element in Cache with its ttl
func (c *cache) Put(key any, payload any, ttl time.Duration) {
c.mapping.Store(key, &element{
func (c *cache[K, V]) Put(key K, payload V, ttl time.Duration) {
c.mapping.Store(key, &element[V]{
Payload: payload,
Expired: time.Now().Add(ttl),
})
}
// Get element in Cache, and drop when it expired
func (c *cache) Get(key any) any {
func (c *cache[K, V]) Get(key K) V {
item, exist := c.mapping.Load(key)
if !exist {
return nil
return getZero[V]()
}
elm := item.(*element)
elm := item.(*element[V])
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return nil
return getZero[V]()
}
return elm.Payload
}
// GetWithExpire element in Cache with Expire Time
func (c *cache) GetWithExpire(key any) (payload any, expired time.Time) {
func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
item, exist := c.mapping.Load(key)
if !exist {
return
}
elm := item.(*element)
elm := item.(*element[V])
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
@ -59,10 +59,10 @@ func (c *cache) GetWithExpire(key any) (payload any, expired time.Time) {
return elm.Payload, elm.Expired
}
func (c *cache) cleanup() {
func (c *cache[K, V]) cleanup() {
c.mapping.Range(func(k, v any) bool {
key := k.(string)
elm := v.(*element)
elm := v.(*element[V])
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
}
@ -70,12 +70,12 @@ func (c *cache) cleanup() {
})
}
type janitor struct {
type janitor[K comparable, V any] struct {
interval time.Duration
stop chan struct{}
}
func (j *janitor) process(c *cache) {
func (j *janitor[K, V]) process(c *cache[K, V]) {
ticker := time.NewTicker(j.interval)
for {
select {
@ -88,19 +88,19 @@ func (j *janitor) process(c *cache) {
}
}
func stopJanitor(c *Cache) {
func stopJanitor[K comparable, V any](c *Cache[K, V]) {
c.janitor.stop <- struct{}{}
}
// New return *Cache
func New(interval time.Duration) *Cache {
j := &janitor{
func New[K comparable, V any](interval time.Duration) *Cache[K, V] {
j := &janitor[K, V]{
interval: interval,
stop: make(chan struct{}),
}
c := &cache{janitor: j}
c := &cache[K, V]{janitor: j}
go j.process(c)
C := &Cache{c}
runtime.SetFinalizer(C, stopJanitor)
C := &Cache[K, V]{c}
runtime.SetFinalizer(C, stopJanitor[K, V])
return C
}

View File

@ -11,48 +11,50 @@ import (
func TestCache_Basic(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
c.Put("string", "a", ttl)
d := New[string, string](interval)
d.Put("string", "a", ttl)
i := c.Get("int")
assert.Equal(t, i.(int), 1, "should recv 1")
assert.Equal(t, i, 1, "should recv 1")
s := c.Get("string")
assert.Equal(t, s.(string), "a", "should recv 'a'")
s := d.Get("string")
assert.Equal(t, s, "a", "should recv 'a'")
}
func TestCache_TTL(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
now := time.Now()
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
c.Put("int2", 2, ttl)
i := c.Get("int")
_, expired := c.GetWithExpire("int2")
assert.Equal(t, i.(int), 1, "should recv 1")
assert.Equal(t, i, 1, "should recv 1")
assert.True(t, now.Before(expired))
time.Sleep(ttl * 2)
i = c.Get("int")
j, _ := c.GetWithExpire("int2")
assert.Nil(t, i, "should recv nil")
assert.Nil(t, j, "should recv nil")
assert.True(t, i == 0, "should recv 0")
assert.True(t, j == 0, "should recv 0")
}
func TestCache_AutoCleanup(t *testing.T) {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
time.Sleep(ttl * 2)
i := c.Get("int")
j, _ := c.GetWithExpire("int")
assert.Nil(t, i, "should recv nil")
assert.Nil(t, j, "should recv nil")
assert.True(t, i == 0, "should recv 0")
assert.True(t, j == 0, "should recv 0")
}
func TestCache_AutoGC(t *testing.T) {
@ -60,7 +62,7 @@ func TestCache_AutoGC(t *testing.T) {
go func() {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c := New[string, int](interval)
c.Put("int", 1, ttl)
sign <- struct{}{}
}()

View File

@ -9,43 +9,43 @@ import (
)
// Option is part of Functional Options Pattern
type Option func(*LruCache)
type Option[K comparable, V any] func(*LruCache[K, V])
// EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback = func(key any, value any)
// WithEvict set the evict callback
func WithEvict(cb EvictCallback) Option {
return func(l *LruCache) {
func WithEvict[K comparable, V any](cb EvictCallback) Option[K, V] {
return func(l *LruCache[K, V]) {
l.onEvict = cb
}
}
// WithUpdateAgeOnGet update expires when Get element
func WithUpdateAgeOnGet() Option {
return func(l *LruCache) {
func WithUpdateAgeOnGet[K comparable, V any]() Option[K, V] {
return func(l *LruCache[K, V]) {
l.updateAgeOnGet = true
}
}
// WithAge defined element max age (second)
func WithAge(maxAge int64) Option {
return func(l *LruCache) {
func WithAge[K comparable, V any](maxAge int64) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxAge = maxAge
}
}
// WithSize defined max length of LruCache
func WithSize(maxSize int) Option {
return func(l *LruCache) {
func WithSize[K comparable, V any](maxSize int) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxSize = maxSize
}
}
// WithStale decide whether Stale return is enabled.
// If this feature is enabled, element will not get Evicted according to `WithAge`.
func WithStale(stale bool) Option {
return func(l *LruCache) {
func WithStale[K comparable, V any](stale bool) Option[K, V] {
return func(l *LruCache[K, V]) {
l.staleReturn = stale
}
}
@ -53,7 +53,7 @@ func WithStale(stale bool) Option {
// LruCache is a thread-safe, in-memory lru-cache that evicts the
// least recently used entries from memory when (if set) the entries are
// older than maxAge (in seconds). Use the New constructor to create one.
type LruCache struct {
type LruCache[K comparable, V any] struct {
maxAge int64
maxSize int
mu sync.Mutex
@ -65,8 +65,8 @@ type LruCache struct {
}
// NewLRUCache creates an LruCache
func NewLRUCache(options ...Option) *LruCache {
lc := &LruCache{
func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{
lru: list.New(),
cache: make(map[any]*list.Element),
}
@ -80,12 +80,12 @@ func NewLRUCache(options ...Option) *LruCache {
// Get returns the any representation of a cached response and a bool
// set to true if the key was found.
func (c *LruCache) Get(key any) (any, bool) {
entry := c.get(key)
if entry == nil {
return nil, false
func (c *LruCache[K, V]) Get(key K) (V, bool) {
el := c.get(key)
if el == nil {
return getZero[V](), false
}
value := entry.value
value := el.value
return value, true
}
@ -94,17 +94,17 @@ func (c *LruCache) Get(key any) (any, bool) {
// a time.Time Give expected expires,
// and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires.
func (c *LruCache) GetWithExpire(key any) (any, time.Time, bool) {
entry := c.get(key)
if entry == nil {
return nil, time.Time{}, false
func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
el := c.get(key)
if el == nil {
return getZero[V](), time.Time{}, false
}
return entry.value, time.Unix(entry.expires, 0), true
return el.value, time.Unix(el.expires, 0), true
}
// Exist returns if key exist in cache but not put item to the head of linked list
func (c *LruCache) Exist(key any) bool {
func (c *LruCache[K, V]) Exist(key K) bool {
c.mu.Lock()
defer c.mu.Unlock()
@ -113,7 +113,7 @@ func (c *LruCache) Exist(key any) bool {
}
// Set stores the any representation of a response for a given key.
func (c *LruCache) Set(key any, value any) {
func (c *LruCache[K, V]) Set(key K, value V) {
expires := int64(0)
if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge
@ -123,21 +123,21 @@ func (c *LruCache) Set(key any, value any) {
// SetWithExpire stores the any representation of a response for a given key and given expires.
// The expires time will round to second.
func (c *LruCache) SetWithExpire(key any, value any, expires time.Time) {
func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le)
e := le.Value.(*entry)
e := le.Value.(*entry[K, V])
e.value = value
e.expires = expires.Unix()
} else {
e := &entry{key: key, value: value, expires: expires.Unix()}
e := &entry[K, V]{key: key, value: value, expires: expires.Unix()}
c.cache[key] = c.lru.PushBack(e)
if c.maxSize > 0 {
if len := c.lru.Len(); len > c.maxSize {
if elLen := c.lru.Len(); elLen > c.maxSize {
c.deleteElement(c.lru.Front())
}
}
@ -147,7 +147,7 @@ func (c *LruCache) SetWithExpire(key any, value any, expires time.Time) {
}
// CloneTo clone and overwrite elements to another LruCache
func (c *LruCache) CloneTo(n *LruCache) {
func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
c.mu.Lock()
defer c.mu.Unlock()
@ -158,12 +158,12 @@ func (c *LruCache) CloneTo(n *LruCache) {
n.cache = make(map[any]*list.Element)
for e := c.lru.Front(); e != nil; e = e.Next() {
elm := e.Value.(*entry)
elm := e.Value.(*entry[K, V])
n.cache[elm.key] = n.lru.PushBack(elm)
}
}
func (c *LruCache) get(key any) *entry {
func (c *LruCache[K, V]) get(key K) *entry[K, V] {
c.mu.Lock()
defer c.mu.Unlock()
@ -172,7 +172,7 @@ func (c *LruCache) get(key any) *entry {
return nil
}
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry[K, V]).expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
@ -180,15 +180,15 @@ func (c *LruCache) get(key any) *entry {
}
c.lru.MoveToBack(le)
entry := le.Value.(*entry)
el := le.Value.(*entry[K, V])
if c.maxAge > 0 && c.updateAgeOnGet {
entry.expires = time.Now().Unix() + c.maxAge
el.expires = time.Now().Unix() + c.maxAge
}
return entry
return el
}
// Delete removes the value associated with a key.
func (c *LruCache) Delete(key any) {
func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock()
if le, ok := c.cache[key]; ok {
@ -198,25 +198,25 @@ func (c *LruCache) Delete(key any) {
c.mu.Unlock()
}
func (c *LruCache) maybeDeleteOldest() {
func (c *LruCache[K, V]) maybeDeleteOldest() {
if !c.staleReturn && c.maxAge > 0 {
now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
for le := c.lru.Front(); le != nil && le.Value.(*entry[K, V]).expires <= now; le = c.lru.Front() {
c.deleteElement(le)
}
}
}
func (c *LruCache) deleteElement(le *list.Element) {
func (c *LruCache[K, V]) deleteElement(le *list.Element) {
c.lru.Remove(le)
e := le.Value.(*entry)
e := le.Value.(*entry[K, V])
delete(c.cache, e.key)
if c.onEvict != nil {
c.onEvict(e.key, e.value)
}
}
func (c *LruCache) Clear() error {
func (c *LruCache[K, V]) Clear() error {
c.mu.Lock()
c.cache = make(map[any]*list.Element)
@ -225,8 +225,13 @@ func (c *LruCache) Clear() error {
return nil
}
type entry struct {
key any
value any
type entry[K comparable, V any] struct {
key K
value V
expires int64
}
func getZero[T any]() T {
var result T
return result
}

View File

@ -19,7 +19,7 @@ var entries = []struct {
}
func TestLRUCache(t *testing.T) {
c := NewLRUCache()
c := NewLRUCache[string, string]()
for _, e := range entries {
c.Set(e.key, e.value)
@ -32,7 +32,7 @@ func TestLRUCache(t *testing.T) {
for _, e := range entries {
value, ok := c.Get(e.key)
if assert.True(t, ok) {
assert.Equal(t, e.value, value.(string))
assert.Equal(t, e.value, value)
}
}
@ -45,25 +45,25 @@ func TestLRUCache(t *testing.T) {
}
func TestLRUMaxAge(t *testing.T) {
c := NewLRUCache(WithAge(86400))
c := NewLRUCache[string, string](WithAge[string, string](86400))
now := time.Now().Unix()
expected := now + 86400
// Add one expired entry
c.Set("foo", "bar")
c.lru.Back().Value.(*entry).expires = now
c.lru.Back().Value.(*entry[string, string]).expires = now
// Reset
c.Set("foo", "bar")
e := c.lru.Back().Value.(*entry)
e := c.lru.Back().Value.(*entry[string, string])
assert.True(t, e.expires >= now)
c.lru.Back().Value.(*entry).expires = now
c.lru.Back().Value.(*entry[string, string]).expires = now
// Set a few and verify expiration times
for _, s := range entries {
c.Set(s.key, s.value)
e := c.lru.Back().Value.(*entry)
e := c.lru.Back().Value.(*entry[string, string])
assert.True(t, e.expires >= expected && e.expires <= expected+10)
}
@ -77,7 +77,7 @@ func TestLRUMaxAge(t *testing.T) {
for _, s := range entries {
le, ok := c.cache[s.key]
if assert.True(t, ok) {
le.Value.(*entry).expires = now
le.Value.(*entry[string, string]).expires = now
}
}
@ -88,22 +88,22 @@ func TestLRUMaxAge(t *testing.T) {
}
func TestLRUpdateOnGet(t *testing.T) {
c := NewLRUCache(WithAge(86400), WithUpdateAgeOnGet())
c := NewLRUCache[string, string](WithAge[string, string](86400), WithUpdateAgeOnGet[string, string]())
now := time.Now().Unix()
expires := now + 86400/2
// Add one expired entry
c.Set("foo", "bar")
c.lru.Back().Value.(*entry).expires = expires
c.lru.Back().Value.(*entry[string, string]).expires = expires
_, ok := c.Get("foo")
assert.True(t, ok)
assert.True(t, c.lru.Back().Value.(*entry).expires > expires)
assert.True(t, c.lru.Back().Value.(*entry[string, string]).expires > expires)
}
func TestMaxSize(t *testing.T) {
c := NewLRUCache(WithSize(2))
c := NewLRUCache[string, string](WithSize[string, string](2))
// Add one expired entry
c.Set("foo", "bar")
_, ok := c.Get("foo")
@ -117,7 +117,7 @@ func TestMaxSize(t *testing.T) {
}
func TestExist(t *testing.T) {
c := NewLRUCache(WithSize(1))
c := NewLRUCache[int, int](WithSize[int, int](1))
c.Set(1, 2)
assert.True(t, c.Exist(1))
c.Set(2, 3)
@ -130,7 +130,7 @@ func TestEvict(t *testing.T) {
temp = key.(int) + value.(int)
}
c := NewLRUCache(WithEvict(evict), WithSize(1))
c := NewLRUCache[int, int](WithEvict[int, int](evict), WithSize[int, int](1))
c.Set(1, 2)
c.Set(2, 3)
@ -138,21 +138,22 @@ func TestEvict(t *testing.T) {
}
func TestSetWithExpire(t *testing.T) {
c := NewLRUCache(WithAge(1))
c := NewLRUCache[int, *struct{}](WithAge[int, *struct{}](1))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
c.SetWithExpire(1, 2, tenSecBefore)
c.SetWithExpire(1, &struct{}{}, tenSecBefore)
// res is expected not to exist, and expires should be empty time.Time
res, expires, exist := c.GetWithExpire(1)
assert.Equal(t, nil, res)
assert.True(t, nil == res)
assert.Equal(t, time.Time{}, expires)
assert.Equal(t, false, exist)
}
func TestStale(t *testing.T) {
c := NewLRUCache(WithAge(1), WithStale(true))
c := NewLRUCache[int, int](WithAge[int, int](1), WithStale[int, int](true))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
@ -165,11 +166,11 @@ func TestStale(t *testing.T) {
}
func TestCloneTo(t *testing.T) {
o := NewLRUCache(WithSize(10))
o := NewLRUCache[string, int](WithSize[string, int](10))
o.Set("1", 1)
o.Set("2", 2)
n := NewLRUCache(WithSize(2))
n := NewLRUCache[string, int](WithSize[string, int](2))
n.Set("3", 3)
n.Set("4", 4)

View File

@ -5,13 +5,13 @@ import (
)
// Queue is a simple concurrent safe queue
type Queue struct {
items []any
type Queue[T any] struct {
items []T
lock sync.RWMutex
}
// Put add the item to the queue.
func (q *Queue) Put(items ...any) {
func (q *Queue[T]) Put(items ...T) {
if len(items) == 0 {
return
}
@ -22,9 +22,9 @@ func (q *Queue) Put(items ...any) {
}
// Pop returns the head of items.
func (q *Queue) Pop() any {
func (q *Queue[T]) Pop() T {
if len(q.items) == 0 {
return nil
return GetZero[T]()
}
q.lock.Lock()
@ -35,9 +35,9 @@ func (q *Queue) Pop() any {
}
// Last returns the last of item.
func (q *Queue) Last() any {
func (q *Queue[T]) Last() T {
if len(q.items) == 0 {
return nil
return GetZero[T]()
}
q.lock.RLock()
@ -47,8 +47,8 @@ func (q *Queue) Last() any {
}
// Copy get the copy of queue.
func (q *Queue) Copy() []any {
items := []any{}
func (q *Queue[T]) Copy() []T {
items := []T{}
q.lock.RLock()
items = append(items, q.items...)
q.lock.RUnlock()
@ -56,7 +56,7 @@ func (q *Queue) Copy() []any {
}
// Len returns the number of items in this queue.
func (q *Queue) Len() int64 {
func (q *Queue[T]) Len() int64 {
q.lock.Lock()
defer q.lock.Unlock()
@ -64,8 +64,13 @@ func (q *Queue) Len() int64 {
}
// New is a constructor for a new concurrent safe queue.
func New(hint int64) *Queue {
return &Queue{
items: make([]any, 0, hint),
func New[T any](hint int64) *Queue[T] {
return &Queue[T]{
items: make([]T, 0, hint),
}
}
func GetZero[T any]() T {
var result T
return result
}

44
common/utils/range.go Normal file
View File

@ -0,0 +1,44 @@
package utils
import (
"golang.org/x/exp/constraints"
)
type Range[T constraints.Ordered] struct {
start T
end T
}
func NewRange[T constraints.Ordered](start, end T) *Range[T] {
if start > end {
return &Range[T]{
start: end,
end: start,
}
}
return &Range[T]{
start: start,
end: end,
}
}
func (r *Range[T]) Contains(t T) bool {
return t >= r.start && t <= r.end
}
func (r *Range[T]) LeftContains(t T) bool {
return t >= r.start && t < r.end
}
func (r *Range[T]) RightContains(t T) bool {
return t > r.start && t <= r.end
}
func (r *Range[T]) Start() T {
return r.start
}
func (r *Range[T]) End() T {
return r.end
}

View File

@ -1,7 +1,7 @@
package fakeip
import (
"net"
"net/netip"
"github.com/Dreamacro/clash/component/profile/cachefile"
)
@ -11,22 +11,27 @@ type cachefileStore struct {
}
// GetByHost implements store.GetByHost
func (c *cachefileStore) GetByHost(host string) (net.IP, bool) {
func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) {
elm := c.cache.GetFakeip([]byte(host))
if elm == nil {
return nil, false
return netip.Addr{}, false
}
if len(elm) == 4 {
return netip.AddrFrom4(*(*[4]byte)(elm)), true
} else {
return netip.AddrFrom16(*(*[16]byte)(elm)), true
}
return net.IP(elm), true
}
// PutByHost implements store.PutByHost
func (c *cachefileStore) PutByHost(host string, ip net.IP) {
c.cache.PutFakeip([]byte(host), ip)
func (c *cachefileStore) PutByHost(host string, ip netip.Addr) {
c.cache.PutFakeip([]byte(host), ip.AsSlice())
}
// GetByIP implements store.GetByIP
func (c *cachefileStore) GetByIP(ip net.IP) (string, bool) {
elm := c.cache.GetFakeip(ip.To4())
func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) {
elm := c.cache.GetFakeip(ip.AsSlice())
if elm == nil {
return "", false
}
@ -34,18 +39,18 @@ func (c *cachefileStore) GetByIP(ip net.IP) (string, bool) {
}
// PutByIP implements store.PutByIP
func (c *cachefileStore) PutByIP(ip net.IP, host string) {
c.cache.PutFakeip(ip.To4(), []byte(host))
func (c *cachefileStore) PutByIP(ip netip.Addr, host string) {
c.cache.PutFakeip(ip.AsSlice(), []byte(host))
}
// DelByIP implements store.DelByIP
func (c *cachefileStore) DelByIP(ip net.IP) {
ip = ip.To4()
c.cache.DelFakeipPair(ip, c.cache.GetFakeip(ip.To4()))
func (c *cachefileStore) DelByIP(ip netip.Addr) {
addr := ip.AsSlice()
c.cache.DelFakeipPair(addr, c.cache.GetFakeip(addr))
}
// Exist implements store.Exist
func (c *cachefileStore) Exist(ip net.IP) bool {
func (c *cachefileStore) Exist(ip netip.Addr) bool {
_, exist := c.GetByIP(ip)
return exist
}

View File

@ -1,40 +1,37 @@
package fakeip
import (
"net"
"net/netip"
"github.com/Dreamacro/clash/common/cache"
)
type memoryStore struct {
cache *cache.LruCache
cacheIP *cache.LruCache[string, netip.Addr]
cacheHost *cache.LruCache[netip.Addr, string]
}
// GetByHost implements store.GetByHost
func (m *memoryStore) GetByHost(host string) (net.IP, bool) {
if elm, exist := m.cache.Get(host); exist {
ip := elm.(net.IP)
func (m *memoryStore) GetByHost(host string) (netip.Addr, bool) {
if ip, exist := m.cacheIP.Get(host); exist {
// ensure ip --> host on head of linked list
m.cache.Get(ipToUint(ip.To4()))
m.cacheHost.Get(ip)
return ip, true
}
return nil, false
return netip.Addr{}, false
}
// PutByHost implements store.PutByHost
func (m *memoryStore) PutByHost(host string, ip net.IP) {
m.cache.Set(host, ip)
func (m *memoryStore) PutByHost(host string, ip netip.Addr) {
m.cacheIP.Set(host, ip)
}
// GetByIP implements store.GetByIP
func (m *memoryStore) GetByIP(ip net.IP) (string, bool) {
if elm, exist := m.cache.Get(ipToUint(ip.To4())); exist {
host := elm.(string)
func (m *memoryStore) GetByIP(ip netip.Addr) (string, bool) {
if host, exist := m.cacheHost.Get(ip); exist {
// ensure host --> ip on head of linked list
m.cache.Get(host)
m.cacheIP.Get(host)
return host, true
}
@ -42,33 +39,41 @@ func (m *memoryStore) GetByIP(ip net.IP) (string, bool) {
}
// PutByIP implements store.PutByIP
func (m *memoryStore) PutByIP(ip net.IP, host string) {
m.cache.Set(ipToUint(ip.To4()), host)
func (m *memoryStore) PutByIP(ip netip.Addr, host string) {
m.cacheHost.Set(ip, host)
}
// DelByIP implements store.DelByIP
func (m *memoryStore) DelByIP(ip net.IP) {
ipNum := ipToUint(ip.To4())
if elm, exist := m.cache.Get(ipNum); exist {
m.cache.Delete(elm.(string))
func (m *memoryStore) DelByIP(ip netip.Addr) {
if host, exist := m.cacheHost.Get(ip); exist {
m.cacheIP.Delete(host)
}
m.cache.Delete(ipNum)
m.cacheHost.Delete(ip)
}
// Exist implements store.Exist
func (m *memoryStore) Exist(ip net.IP) bool {
return m.cache.Exist(ipToUint(ip.To4()))
func (m *memoryStore) Exist(ip netip.Addr) bool {
return m.cacheHost.Exist(ip)
}
// CloneTo implements store.CloneTo
// only for memoryStore to memoryStore
func (m *memoryStore) CloneTo(store store) {
if ms, ok := store.(*memoryStore); ok {
m.cache.CloneTo(ms.cache)
m.cacheIP.CloneTo(ms.cacheIP)
m.cacheHost.CloneTo(ms.cacheHost)
}
}
// FlushFakeIP implements store.FlushFakeIP
func (m *memoryStore) FlushFakeIP() error {
return m.cache.Clear()
_ = m.cacheIP.Clear()
return m.cacheHost.Clear()
}
func newMemoryStore(size int) *memoryStore {
return &memoryStore{
cacheIP: cache.NewLRUCache[string, netip.Addr](cache.WithSize[string, netip.Addr](size)),
cacheHost: cache.NewLRUCache[netip.Addr, string](cache.WithSize[netip.Addr, string](size)),
}
}

View File

@ -1,41 +1,47 @@
package fakeip
import (
"encoding/binary"
"errors"
"net"
"math/bits"
"net/netip"
"sync"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/trie"
)
type uint128 struct {
hi uint64
lo uint64
}
type store interface {
GetByHost(host string) (net.IP, bool)
PutByHost(host string, ip net.IP)
GetByIP(ip net.IP) (string, bool)
PutByIP(ip net.IP, host string)
DelByIP(ip net.IP)
Exist(ip net.IP) bool
GetByHost(host string) (netip.Addr, bool)
PutByHost(host string, ip netip.Addr)
GetByIP(ip netip.Addr) (string, bool)
PutByIP(ip netip.Addr, host string)
DelByIP(ip netip.Addr)
Exist(ip netip.Addr) bool
CloneTo(store)
FlushFakeIP() error
}
// Pool is a implementation about fake ip generator without storage
type Pool struct {
max uint32
min uint32
gateway uint32
broadcast uint32
offset uint32
gateway netip.Addr
first netip.Addr
last netip.Addr
offset netip.Addr
cycle bool
mux sync.Mutex
host *trie.DomainTrie
ipnet *net.IPNet
host *trie.DomainTrie[bool]
ipnet *netip.Prefix
store store
}
// Lookup return a fake ip with host
func (p *Pool) Lookup(host string) net.IP {
func (p *Pool) Lookup(host string) netip.Addr {
p.mux.Lock()
defer p.mux.Unlock()
if ip, exist := p.store.GetByHost(host); exist {
@ -48,14 +54,10 @@ func (p *Pool) Lookup(host string) net.IP {
}
// LookBack return host with the fake ip
func (p *Pool) LookBack(ip net.IP) (string, bool) {
func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
p.mux.Lock()
defer p.mux.Unlock()
if ip = ip.To4(); ip == nil {
return "", false
}
return p.store.GetByIP(ip)
}
@ -68,29 +70,25 @@ func (p *Pool) ShouldSkipped(domain string) bool {
}
// Exist returns if given ip exists in fake-ip pool
func (p *Pool) Exist(ip net.IP) bool {
func (p *Pool) Exist(ip netip.Addr) bool {
p.mux.Lock()
defer p.mux.Unlock()
if ip = ip.To4(); ip == nil {
return false
}
return p.store.Exist(ip)
}
// Gateway return gateway ip
func (p *Pool) Gateway() net.IP {
return uintToIP(p.gateway)
func (p *Pool) Gateway() netip.Addr {
return p.gateway
}
// Broadcast return broadcast ip
func (p *Pool) Broadcast() net.IP {
return uintToIP(p.broadcast)
// Broadcast return the last ip
func (p *Pool) Broadcast() netip.Addr {
return p.last
}
// IPNet return raw ipnet
func (p *Pool) IPNet() *net.IPNet {
func (p *Pool) IPNet() *netip.Prefix {
return p.ipnet
}
@ -99,47 +97,36 @@ func (p *Pool) CloneFrom(o *Pool) {
o.store.CloneTo(p.store)
}
func (p *Pool) get(host string) net.IP {
current := p.offset
func (p *Pool) get(host string) netip.Addr {
for {
p.offset = (p.offset + 1) % (p.max - p.min)
// Avoid infinite loops
if p.offset == current {
p.offset = (p.offset + 1) % (p.max - p.min)
ip := uintToIP(p.min + p.offset - 1)
p.store.DelByIP(ip)
p.offset = p.offset.Next()
if !p.offset.Less(p.last) {
p.cycle = true
p.offset = p.first
}
if p.cycle {
p.store.DelByIP(p.offset)
break
}
ip := uintToIP(p.min + p.offset - 1)
if !p.store.Exist(ip) {
if !p.store.Exist(p.offset) {
break
}
}
ip := uintToIP(p.min + p.offset - 1)
p.store.PutByIP(ip, host)
return ip
p.store.PutByIP(p.offset, host)
return p.offset
}
func (p *Pool) FlushFakeIP() error {
return p.store.FlushFakeIP()
}
func ipToUint(ip net.IP) uint32 {
v := uint32(ip[0]) << 24
v += uint32(ip[1]) << 16
v += uint32(ip[2]) << 8
v += uint32(ip[3])
return v
}
func uintToIP(v uint32) net.IP {
return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
type Options struct {
IPNet *net.IPNet
Host *trie.DomainTrie
IPNet *netip.Prefix
Host *trie.DomainTrie[bool]
// Size sets the maximum number of entries in memory
// and does not work if Persistence is true
@ -152,21 +139,23 @@ type Options struct {
// New return Pool instance
func New(options Options) (*Pool, error) {
min := ipToUint(options.IPNet.IP) + 3
var (
hostAddr = options.IPNet.Masked().Addr()
gateway = hostAddr.Next()
first = gateway.Next().Next()
last = add(hostAddr, 1<<uint64(hostAddr.BitLen()-options.IPNet.Bits())-1)
)
ones, bits := options.IPNet.Mask.Size()
total := 1<<uint(bits-ones) - 4
if total <= 0 {
if !options.IPNet.IsValid() || !first.Less(last) || !options.IPNet.Contains(last) {
return nil, errors.New("ipnet don't have valid ip")
}
max := min + uint32(total) - 1
pool := &Pool{
min: min,
max: max,
gateway: min - 2,
broadcast: max + 1,
gateway: gateway,
first: first,
last: last,
offset: first.Prev(),
cycle: false,
host: options.Host,
ipnet: options.IPNet,
}
@ -175,10 +164,34 @@ func New(options Options) (*Pool, error) {
cache: cachefile.Cache(),
}
} else {
pool.store = &memoryStore{
cache: cache.NewLRUCache(cache.WithSize(options.Size * 2)),
}
pool.store = newMemoryStore(options.Size)
}
return pool, nil
}
// add returns addr + n.
func add(addr netip.Addr, n uint64) netip.Addr {
buf := addr.As16()
u := uint128{
binary.BigEndian.Uint64(buf[:8]),
binary.BigEndian.Uint64(buf[8:]),
}
lo, carry := bits.Add64(u.lo, n, 0)
u.hi = u.hi + carry
u.lo = lo
binary.BigEndian.PutUint64(buf[:8], u.hi)
binary.BigEndian.PutUint64(buf[8:], u.lo)
a := netip.AddrFrom16(buf)
if addr.Is4() {
return a.Unmap()
}
return a
}

View File

@ -2,7 +2,7 @@ package fakeip
import (
"fmt"
"net"
"net/netip"
"os"
"testing"
"time"
@ -49,9 +49,9 @@ func createCachefileStore(options Options) (*Pool, string, error) {
}
func TestPool_Basic(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.0/28")
ipnet := netip.MustParsePrefix("192.168.0.0/28")
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -62,24 +62,52 @@ func TestPool_Basic(t *testing.T) {
last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last)
assert.True(t, first.Equal(net.IP{192, 168, 0, 3}))
assert.Equal(t, pool.Lookup("foo.com"), net.IP{192, 168, 0, 3})
assert.True(t, last.Equal(net.IP{192, 168, 0, 4}))
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.True(t, exist)
assert.Equal(t, bar, "bar.com")
assert.Equal(t, pool.Gateway(), net.IP{192, 168, 0, 1})
assert.Equal(t, pool.Broadcast(), net.IP{192, 168, 0, 15})
assert.True(t, pool.Gateway() == netip.AddrFrom4([4]byte{192, 168, 0, 1}))
assert.True(t, pool.Broadcast() == netip.AddrFrom4([4]byte{192, 168, 0, 15}))
assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(net.IP{192, 168, 0, 4}))
assert.False(t, pool.Exist(net.IP{192, 168, 0, 5}))
assert.False(t, pool.Exist(net.ParseIP("::1")))
assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 4})))
assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5})))
assert.False(t, pool.Exist(netip.MustParseAddr("::1")))
}
}
func TestPool_BasicV6(t *testing.T) {
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
Size: 10,
})
assert.Nil(t, err)
defer os.Remove(tempfile)
for _, pool := range pools {
first := pool.Lookup("foo.com")
last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last)
assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803"))
assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803"))
assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
assert.True(t, exist)
assert.Equal(t, bar, "bar.com")
assert.True(t, pool.Gateway() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801"))
assert.True(t, pool.Broadcast() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff"))
assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")))
assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")))
assert.False(t, pool.Exist(netip.MustParseAddr("127.0.0.1")))
}
}
func TestPool_CycleUsed(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.16/28")
ipnet := netip.MustParsePrefix("192.168.0.16/28")
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -88,22 +116,22 @@ func TestPool_CycleUsed(t *testing.T) {
for _, pool := range pools {
foo := pool.Lookup("foo.com")
bar := pool.Lookup("bar.com")
for i := 0; i < 9; i++ {
for i := 0; i < 10; i++ {
pool.Lookup(fmt.Sprintf("%d.com", i))
}
baz := pool.Lookup("baz.com")
next := pool.Lookup("foo.com")
assert.True(t, foo.Equal(baz))
assert.True(t, next.Equal(bar))
assert.True(t, foo == baz)
assert.True(t, next == bar)
}
}
func TestPool_Skip(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/29")
tree := trie.New()
tree.Insert("example.com", tree)
ipnet := netip.MustParsePrefix("192.168.0.1/29")
tree := trie.New[bool]()
tree.Insert("example.com", true)
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
Host: tree,
})
@ -117,9 +145,9 @@ func TestPool_Skip(t *testing.T) {
}
func TestPool_MaxCacheSize(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
@ -128,13 +156,13 @@ func TestPool_MaxCacheSize(t *testing.T) {
pool.Lookup("baz.com")
next := pool.Lookup("foo.com")
assert.False(t, first.Equal(next))
assert.False(t, first == next)
}
func TestPool_DoubleMapping(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
@ -158,23 +186,23 @@ func TestPool_DoubleMapping(t *testing.T) {
assert.False(t, bazExist)
assert.True(t, barExist)
assert.False(t, bazIP.Equal(newBazIP))
assert.False(t, bazIP == newBazIP)
}
func TestPool_Clone(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/24")
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
first := pool.Lookup("foo.com")
last := pool.Lookup("bar.com")
assert.True(t, first.Equal(net.IP{192, 168, 0, 3}))
assert.True(t, last.Equal(net.IP{192, 168, 0, 4}))
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
newPool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 2,
})
newPool.CloneFrom(pool)
@ -185,9 +213,9 @@ func TestPool_Clone(t *testing.T) {
}
func TestPool_Error(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/31")
ipnet := netip.MustParsePrefix("192.168.0.1/31")
_, err := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
@ -195,9 +223,9 @@ func TestPool_Error(t *testing.T) {
}
func TestPool_FlushFileCache(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/28")
ipnet := netip.MustParsePrefix("192.168.0.1/28")
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -216,18 +244,18 @@ func TestPool_FlushFileCache(t *testing.T) {
next := pool.Lookup("baz.com")
nero := pool.Lookup("foo.com")
assert.Equal(t, foo, fox)
assert.NotEqual(t, foo, baz)
assert.Equal(t, bar, bax)
assert.NotEqual(t, bar, next)
assert.Equal(t, baz, nero)
assert.True(t, foo == fox)
assert.False(t, foo == baz)
assert.True(t, bar == bax)
assert.False(t, bar == next)
assert.True(t, baz == nero)
}
}
func TestPool_FlushMemoryCache(t *testing.T) {
_, ipnet, _ := net.ParseCIDR("192.168.0.1/28")
ipnet := netip.MustParsePrefix("192.168.0.1/28")
pool, _ := New(Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 10,
})
@ -243,9 +271,9 @@ func TestPool_FlushMemoryCache(t *testing.T) {
next := pool.Lookup("baz.com")
nero := pool.Lookup("foo.com")
assert.Equal(t, foo, fox)
assert.NotEqual(t, foo, baz)
assert.Equal(t, bar, bax)
assert.NotEqual(t, bar, next)
assert.Equal(t, baz, nero)
assert.True(t, foo == fox)
assert.False(t, foo == baz)
assert.True(t, bar == bax)
assert.False(t, bar == next)
assert.True(t, baz == nero)
}

View File

@ -15,6 +15,9 @@ func LoaderName() string {
}
func SetLoader(newLoader string) {
if newLoader == "memc" {
newLoader = "memconservative"
}
geoLoaderName = newLoader
}

View File

@ -3,6 +3,7 @@ package process
import (
"errors"
"net"
"runtime"
C "github.com/Dreamacro/clash/constant"
)
@ -23,6 +24,9 @@ func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error)
}
func ShouldFindProcess(metadata *C.Metadata) bool {
if runtime.GOOS == "android" {
return false
}
if metadata.Process != "" {
return false
}

View File

@ -1,230 +0,0 @@
package process
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"unicode"
"unsafe"
"github.com/Dreamacro/clash/common/pool"
)
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
var nativeEndian = func() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}()
const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20
pathProc = "/proc"
)
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil {
return "", err
}
return resolveProcessNameByProcSearch(inode, uid)
}
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int32, error) {
var family byte
var protocol byte
switch network {
case TCP:
protocol = syscall.IPPROTO_TCP
case UDP:
protocol = syscall.IPPROTO_UDP
default:
return 0, 0, ErrInvalidNetwork
}
if ip.To4() != nil {
family = syscall.AF_INET
} else {
family = syscall.AF_INET6
}
req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort))
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil {
return 0, 0, fmt.Errorf("dial netlink: %w", err)
}
defer syscall.Close(socket)
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,
Pad: 0,
Pid: 0,
Groups: 0,
}); err != nil {
return 0, 0, err
}
if _, err := syscall.Write(socket, req); err != nil {
return 0, 0, fmt.Errorf("write request: %w", err)
}
rb := pool.Get(pool.RelayBufferSize)
defer pool.Put(rb)
n, err := syscall.Read(socket, rb)
if err != nil {
return 0, 0, fmt.Errorf("read response: %w", err)
}
messages, err := syscall.ParseNetlinkMessage(rb[:n])
if err != nil {
return 0, 0, fmt.Errorf("parse netlink message: %w", err)
} else if len(messages) == 0 {
return 0, 0, fmt.Errorf("unexcepted netlink response")
}
message := messages[0]
if message.Header.Type&syscall.NLMSG_ERROR != 0 {
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 uid, inode, nil
}
func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte {
s := make([]byte, 16)
if v4 := source.To4(); v4 != nil {
copy(s, v4)
} else {
copy(s, source)
}
buf := make([]byte, sizeOfSocketDiagRequest)
nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
nativeEndian.PutUint32(buf[8:12], 0)
nativeEndian.PutUint32(buf[12:16], 0)
buf[16] = family
buf[17] = protocol
buf[18] = 0
buf[19] = 0
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
binary.BigEndian.PutUint16(buf[24:26], sourcePort)
binary.BigEndian.PutUint16(buf[26:28], 0)
copy(buf[28:44], s)
copy(buf[44:60], net.IPv6zero)
nativeEndian.PutUint32(buf[60:64], 0)
nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
return buf
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid int32) {
if len(msg.Data) < 72 {
return 0, 0
}
data := msg.Data
uid = int32(nativeEndian.Uint32(data[64:68]))
inode = int32(nativeEndian.Uint32(data[68:72]))
return
}
func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return "", err
}
buffer := make([]byte, syscall.PathMax)
socket := []byte(fmt.Sprintf("socket:[%d]", inode))
for _, f := range files {
if !f.IsDir() || !isPid(f.Name()) {
continue
}
info, err := f.Info()
if err != nil {
return "", err
}
if info.Sys().(*syscall.Stat_t).Uid != uint32(uid) {
continue
}
processPath := path.Join(pathProc, f.Name())
fdPath := path.Join(processPath, "fd")
fds, err := os.ReadDir(fdPath)
if err != nil {
continue
}
for _, fd := range fds {
n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer)
if err != nil {
continue
}
if bytes.Equal(buffer[:n], socket) {
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
if err != nil {
return "", err
}
return splitCmdline(cmdline), nil
}
}
}
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
}
func splitCmdline(cmdline []byte) string {
cmdline = bytes.Trim(cmdline, " ")
idx := bytes.IndexFunc(cmdline, func(r rune) bool {
return unicode.IsControl(r) || unicode.IsSpace(r)
})
if idx == -1 {
return filepath.Base(string(cmdline))
}
return filepath.Base(string(cmdline[:idx]))
}
func isPid(s string) bool {
return strings.IndexFunc(s, func(r rune) bool {
return !unicode.IsDigit(r)
}) == -1
}

View File

@ -1,5 +1,3 @@
//go:build !android
package process
import (

View File

@ -14,6 +14,7 @@ type Enhancer interface {
IsExistFakeIP(net.IP) bool
FindHostByIP(net.IP) (string, bool)
FlushFakeIP() error
InsertHostByIP(net.IP, string)
}
func FakeIPEnabled() bool {
@ -56,6 +57,12 @@ func IsExistFakeIP(ip net.IP) bool {
return false
}
func InsertHostByIP(ip net.IP, host string) {
if mapper := DefaultHostMapper; mapper != nil {
mapper.InsertHostByIP(ip, host)
}
}
func FindHostByIP(ip net.IP) (string, bool) {
if mapper := DefaultHostMapper; mapper != nil {
return mapper.FindHostByIP(ip)

View File

@ -5,6 +5,7 @@ import (
"errors"
"math/rand"
"net"
"net/netip"
"strings"
"time"
@ -23,7 +24,7 @@ var (
DisableIPv6 = true
// DefaultHosts aim to resolve hosts
DefaultHosts = trie.New()
DefaultHosts = trie.New[netip.Addr]()
// DefaultDNSTimeout defined the default dns request timeout
DefaultDNSTimeout = time.Second * 5
@ -48,8 +49,8 @@ func ResolveIPv4(host string) (net.IP, error) {
func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data.(net.IP).To4(); ip != nil {
return ip, nil
if ip := node.Data; ip.Is4() {
return ip.AsSlice(), nil
}
}
@ -92,8 +93,8 @@ func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
}
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data.(net.IP).To16(); ip != nil {
return ip, nil
if ip := node.Data; ip.Is6() {
return ip.AsSlice(), nil
}
}
@ -128,7 +129,8 @@ func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
// ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil {
return node.Data.(net.IP), nil
ip := node.Data
return ip.Unmap().AsSlice(), nil
}
if r != nil {

View File

@ -0,0 +1,149 @@
package sniffer
import (
"errors"
"net"
"strconv"
"github.com/Dreamacro/clash/component/trie"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
)
var (
ErrorUnsupportedSniffer = errors.New("unsupported sniffer")
ErrorSniffFailed = errors.New("all sniffer failed")
)
var Dispatcher SnifferDispatcher
type SnifferDispatcher struct {
enable bool
sniffers []C.Sniffer
foreDomain *trie.DomainTrie[bool]
skipSNI *trie.DomainTrie[bool]
portRanges *[]utils.Range[uint16]
}
func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
bufConn, ok := conn.(*CN.BufferedConn)
if !ok {
return
}
if metadata.Host == "" || sd.foreDomain.Search(metadata.Host) != nil {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil {
log.Debugln("[Sniffer] Dst port is error")
return
}
for _, portRange := range *sd.portRanges {
if !portRange.Contains(uint16(port)) {
return
}
}
if host, err := sd.sniffDomain(bufConn, metadata); err != nil {
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return
} else {
if sd.skipSNI.Search(host) != nil {
log.Debugln("[Sniffer] Skip sni[%s]", host)
return
}
sd.replaceDomain(metadata, host)
}
}
}
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) {
log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
metadata.SrcIP, metadata.SrcPort,
metadata.DstIP, metadata.DstPort,
metadata.Host, host)
metadata.AddrType = C.AtypDomainName
metadata.Host = host
metadata.DNSMode = C.DNSMapping
resolver.InsertHostByIP(metadata.DstIP, host)
metadata.DstIP = nil
}
func (sd *SnifferDispatcher) Enable() bool {
return sd.enable
}
func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Metadata) (string, error) {
for _, sniffer := range sd.sniffers {
if sniffer.SupportNetwork() == C.TCP {
_, err := conn.Peek(1)
if err != nil {
return "", err
}
bufferedLen := conn.Buffered()
bytes, err := conn.Peek(bufferedLen)
if err != nil {
log.Debugln("[Sniffer] the data length not enough")
continue
}
host, err := sniffer.SniffTCP(bytes)
if err != nil {
log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP)
continue
}
return host, nil
}
}
return "", ErrorSniffFailed
}
func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{
enable: false,
}
return &dispatcher, nil
}
func NewSnifferDispatcher(needSniffer []C.SnifferType, forceDomain *trie.DomainTrie[bool],
skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16]) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{
enable: true,
foreDomain: forceDomain,
skipSNI: skipSNI,
portRanges: ports,
}
for _, snifferName := range needSniffer {
sniffer, err := NewSniffer(snifferName)
if err != nil {
log.Errorln("Sniffer name[%s] is error", snifferName)
return &SnifferDispatcher{enable: false}, err
}
dispatcher.sniffers = append(dispatcher.sniffers, sniffer)
}
return &dispatcher, nil
}
func NewSniffer(name C.SnifferType) (C.Sniffer, error) {
switch name {
case C.TLS:
return &TLSSniffer{}, nil
default:
return nil, ErrorUnsupportedSniffer
}
}

View File

@ -0,0 +1,159 @@
package sniffer
import (
"testing"
)
func TestTLSHeaders(t *testing.T) {
cases := []struct {
input []byte
domain string
err bool
}{
{
input: []byte{
0x16, 0x03, 0x01, 0x00, 0xc8, 0x01, 0x00, 0x00,
0xc4, 0x03, 0x03, 0x1a, 0xac, 0xb2, 0xa8, 0xfe,
0xb4, 0x96, 0x04, 0x5b, 0xca, 0xf7, 0xc1, 0xf4,
0x2e, 0x53, 0x24, 0x6e, 0x34, 0x0c, 0x58, 0x36,
0x71, 0x97, 0x59, 0xe9, 0x41, 0x66, 0xe2, 0x43,
0xa0, 0x13, 0xb6, 0x00, 0x00, 0x20, 0x1a, 0x1a,
0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
0x00, 0x7b, 0xba, 0xba, 0x00, 0x00, 0xff, 0x01,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
0x14, 0x00, 0x00, 0x11, 0x63, 0x2e, 0x73, 0x2d,
0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,
0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00,
0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04,
0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08,
0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00,
0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c,
0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70,
0x2f, 0x31, 0x2e, 0x31, 0x00, 0x0b, 0x00, 0x02,
0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08,
0xaa, 0xaa, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
0xaa, 0xaa, 0x00, 0x01, 0x00,
},
domain: "c.s-microsoft.com",
err: false,
},
{
input: []byte{
0x16, 0x03, 0x01, 0x00, 0xee, 0x01, 0x00, 0x00,
0xea, 0x03, 0x03, 0xe7, 0x91, 0x9e, 0x93, 0xca,
0x78, 0x1b, 0x3c, 0xe0, 0x65, 0x25, 0x58, 0xb5,
0x93, 0xe1, 0x0f, 0x85, 0xec, 0x9a, 0x66, 0x8e,
0x61, 0x82, 0x88, 0xc8, 0xfc, 0xae, 0x1e, 0xca,
0xd7, 0xa5, 0x63, 0x20, 0xbd, 0x1c, 0x00, 0x00,
0x8b, 0xee, 0x09, 0xe3, 0x47, 0x6a, 0x0e, 0x74,
0xb0, 0xbc, 0xa3, 0x02, 0xa7, 0x35, 0xe8, 0x85,
0x70, 0x7c, 0x7a, 0xf0, 0x00, 0xdf, 0x4a, 0xea,
0x87, 0x01, 0x14, 0x91, 0x00, 0x20, 0xea, 0xea,
0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,
0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
0x00, 0x81, 0x9a, 0x9a, 0x00, 0x00, 0xff, 0x01,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
0x16, 0x00, 0x00, 0x13, 0x77, 0x77, 0x77, 0x30,
0x37, 0x2e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x74,
0x61, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x00,
0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,
0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05,
0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00,
0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e,
0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74,
0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x75, 0x50,
0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,
0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x9a, 0x9a,
0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x8a, 0x8a,
0x00, 0x01, 0x00,
},
domain: "www07.clicktale.net",
err: false,
},
{
input: []byte{
0x16, 0x03, 0x01, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe2, 0x03, 0x03, 0x81, 0x47, 0xc1,
0x66, 0xd5, 0x1b, 0xfa, 0x4b, 0xb5, 0xe0, 0x2a, 0xe1, 0xa7, 0x87, 0x13, 0x1d, 0x11, 0xaa, 0xc6,
0xce, 0xfc, 0x7f, 0xab, 0x94, 0xc8, 0x62, 0xad, 0xc8, 0xab, 0x0c, 0xdd, 0xcb, 0x20, 0x6f, 0x9d,
0x07, 0xf1, 0x95, 0x3e, 0x99, 0xd8, 0xf3, 0x6d, 0x97, 0xee, 0x19, 0x0b, 0x06, 0x1b, 0xf4, 0x84,
0x0b, 0xb6, 0x8f, 0xcc, 0xde, 0xe2, 0xd0, 0x2d, 0x6b, 0x0c, 0x1f, 0x52, 0x53, 0x13, 0x00, 0x08,
0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x0c,
0x00, 0x0a, 0x00, 0x00, 0x07, 0x64, 0x6f, 0x67, 0x66, 0x69, 0x73, 0x68, 0x00, 0x0b, 0x00, 0x04,
0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e,
0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00,
0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08,
0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01,
0x06, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x1c, 0x7f, 0x1b, 0x7f, 0x1a, 0x00, 0x2d, 0x00,
0x02, 0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x2f, 0x35, 0x0c,
0xb6, 0x90, 0x0a, 0xb7, 0xd5, 0xc4, 0x1b, 0x2f, 0x60, 0xaa, 0x56, 0x7b, 0x3f, 0x71, 0xc8, 0x01,
0x7e, 0x86, 0xd3, 0xb7, 0x0c, 0x29, 0x1a, 0x9e, 0x5b, 0x38, 0x3f, 0x01, 0x72,
},
domain: "dogfish",
err: false,
},
{
input: []byte{
0x16, 0x03, 0x01, 0x01, 0x03, 0x01, 0x00, 0x00,
0xff, 0x03, 0x03, 0x3d, 0x89, 0x52, 0x9e, 0xee,
0xbe, 0x17, 0x63, 0x75, 0xef, 0x29, 0xbd, 0x14,
0x6a, 0x49, 0xe0, 0x2c, 0x37, 0x57, 0x71, 0x62,
0x82, 0x44, 0x94, 0x8f, 0x6e, 0x94, 0x08, 0x45,
0x7f, 0xdb, 0xc1, 0x00, 0x00, 0x3e, 0xc0, 0x2c,
0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8,
0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e,
0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23,
0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14,
0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33,
0x00, 0x9d, 0x00, 0x9c, 0x13, 0x02, 0x13, 0x03,
0x13, 0x01, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35,
0x00, 0x2f, 0x00, 0xff, 0x01, 0x00, 0x00, 0x98,
0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00,
0x0b, 0x31, 0x30, 0x2e, 0x34, 0x32, 0x2e, 0x30,
0x2e, 0x32, 0x34, 0x33, 0x00, 0x0b, 0x00, 0x04,
0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0a,
0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19,
0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d,
0x00, 0x20, 0x00, 0x1e, 0x04, 0x03, 0x05, 0x03,
0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06,
0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03,
0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02,
0x06, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17,
0x00, 0x00, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x7f,
0x14, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00,
0x2d, 0x00, 0x03, 0x02, 0x01, 0x00, 0x00, 0x28,
0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,
0x13, 0x7c, 0x6e, 0x97, 0xc4, 0xfd, 0x09, 0x2e,
0x70, 0x2f, 0x73, 0x5a, 0x9b, 0x57, 0x4d, 0x5f,
0x2b, 0x73, 0x2c, 0xa5, 0x4a, 0x98, 0x40, 0x3d,
0x75, 0x6e, 0xb4, 0x76, 0xf9, 0x48, 0x8f, 0x36,
},
domain: "10.42.0.243",
err: false,
},
}
for _, test := range cases {
domain, err := SniffTLS(test.input)
if test.err {
if err == nil {
t.Errorf("Exepct error but nil in test %v", test)
}
} else {
if err != nil {
t.Errorf("Expect no error but actually %s in test %v", err.Error(), test)
}
if *domain != test.domain {
t.Error("expect domain ", test.domain, " but got ", domain)
}
}
}
}

View File

@ -0,0 +1,157 @@
package sniffer
import (
"encoding/binary"
"errors"
"strings"
C "github.com/Dreamacro/clash/constant"
)
var (
errNotTLS = errors.New("not TLS header")
errNotClientHello = errors.New("not client hello")
ErrNoClue = errors.New("not enough information for making a decision")
)
type TLSSniffer struct {
}
func (tls *TLSSniffer) Protocol() string {
return "tls"
}
func (tls *TLSSniffer) SupportNetwork() C.NetWork {
return C.TCP
}
func (tls *TLSSniffer) SniffTCP(bytes []byte) (string, error) {
domain, err := SniffTLS(bytes)
if err == nil {
return *domain, nil
} else {
return "", err
}
}
func IsValidTLSVersion(major, minor byte) bool {
return major == 3
}
// ReadClientHello returns server name (if any) from TLS client hello message.
// https://github.com/golang/go/blob/master/src/crypto/tls/handshake_messages.go#L300
func ReadClientHello(data []byte) (*string, error) {
if len(data) < 42 {
return nil, ErrNoClue
}
sessionIDLen := int(data[38])
if sessionIDLen > 32 || len(data) < 39+sessionIDLen {
return nil, ErrNoClue
}
data = data[39+sessionIDLen:]
if len(data) < 2 {
return nil, ErrNoClue
}
// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
// they are uint16s, the number must be even.
cipherSuiteLen := int(data[0])<<8 | int(data[1])
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
return nil, errNotClientHello
}
data = data[2+cipherSuiteLen:]
if len(data) < 1 {
return nil, ErrNoClue
}
compressionMethodsLen := int(data[0])
if len(data) < 1+compressionMethodsLen {
return nil, ErrNoClue
}
data = data[1+compressionMethodsLen:]
if len(data) == 0 {
return nil, errNotClientHello
}
if len(data) < 2 {
return nil, errNotClientHello
}
extensionsLength := int(data[0])<<8 | int(data[1])
data = data[2:]
if extensionsLength != len(data) {
return nil, errNotClientHello
}
for len(data) != 0 {
if len(data) < 4 {
return nil, errNotClientHello
}
extension := uint16(data[0])<<8 | uint16(data[1])
length := int(data[2])<<8 | int(data[3])
data = data[4:]
if len(data) < length {
return nil, errNotClientHello
}
if extension == 0x00 { /* extensionServerName */
d := data[:length]
if len(d) < 2 {
return nil, errNotClientHello
}
namesLen := int(d[0])<<8 | int(d[1])
d = d[2:]
if len(d) != namesLen {
return nil, errNotClientHello
}
for len(d) > 0 {
if len(d) < 3 {
return nil, errNotClientHello
}
nameType := d[0]
nameLen := int(d[1])<<8 | int(d[2])
d = d[3:]
if len(d) < nameLen {
return nil, errNotClientHello
}
if nameType == 0 {
serverName := string(d[:nameLen])
// An SNI value may not include a
// trailing dot. See
// https://tools.ietf.org/html/rfc6066#section-3.
if strings.HasSuffix(serverName, ".") {
return nil, errNotClientHello
}
return &serverName, nil
}
d = d[nameLen:]
}
}
data = data[length:]
}
return nil, errNotTLS
}
func SniffTLS(b []byte) (*string, error) {
if len(b) < 5 {
return nil, ErrNoClue
}
if b[0] != 0x16 /* TLS Handshake */ {
return nil, errNotTLS
}
if !IsValidTLSVersion(b[1], b[2]) {
return nil, errNotTLS
}
headerLen := int(binary.BigEndian.Uint16(b[3:5]))
if 5+headerLen > len(b) {
return nil, ErrNoClue
}
domain, err := ReadClientHello(b[5 : 5+headerLen])
if err == nil {
return domain, nil
}
return nil, err
}

View File

@ -17,8 +17,8 @@ var ErrInvalidDomain = errors.New("invalid domain")
// DomainTrie contains the main logic for adding and searching nodes for domain segments.
// support wildcard domain (e.g *.google.com)
type DomainTrie struct {
root *Node
type DomainTrie[T comparable] struct {
root *Node[T]
}
func ValidAndSplitDomain(domain string) ([]string, bool) {
@ -51,7 +51,7 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
// 3. subdomain.*.example.com
// 4. .example.com
// 5. +.example.com
func (t *DomainTrie) Insert(domain string, data any) error {
func (t *DomainTrie[T]) Insert(domain string, data T) error {
parts, valid := ValidAndSplitDomain(domain)
if !valid {
return ErrInvalidDomain
@ -68,13 +68,13 @@ func (t *DomainTrie) Insert(domain string, data any) error {
return nil
}
func (t *DomainTrie) insert(parts []string, data any) {
func (t *DomainTrie[T]) insert(parts []string, data T) {
node := t.root
// reverse storage domain part to save space
for i := len(parts) - 1; i >= 0; i-- {
part := parts[i]
if !node.hasChild(part) {
node.addChild(part, newNode(nil))
node.addChild(part, newNode(getZero[T]()))
}
node = node.getChild(part)
@ -88,7 +88,7 @@ func (t *DomainTrie) insert(parts []string, data any) {
// 1. static part
// 2. wildcard domain
// 2. dot wildcard domain
func (t *DomainTrie) Search(domain string) *Node {
func (t *DomainTrie[T]) Search(domain string) *Node[T] {
parts, valid := ValidAndSplitDomain(domain)
if !valid || parts[0] == "" {
return nil
@ -96,26 +96,26 @@ func (t *DomainTrie) Search(domain string) *Node {
n := t.search(t.root, parts)
if n == nil || n.Data == nil {
if n == nil || n.Data == getZero[T]() {
return nil
}
return n
}
func (t *DomainTrie) search(node *Node, parts []string) *Node {
func (t *DomainTrie[T]) search(node *Node[T], parts []string) *Node[T] {
if len(parts) == 0 {
return node
}
if c := node.getChild(parts[len(parts)-1]); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != getZero[T]() {
return n
}
}
if c := node.getChild(wildcard); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != getZero[T]() {
return n
}
}
@ -124,6 +124,6 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
}
// New returns a new, empty Trie.
func New() *DomainTrie {
return &DomainTrie{root: newNode(nil)}
func New[T comparable]() *DomainTrie[T] {
return &DomainTrie[T]{root: newNode[T](getZero[T]())}
}

View File

@ -1,16 +1,16 @@
package trie
import (
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
var localIP = net.IP{127, 0, 0, 1}
var localIP = netip.AddrFrom4([4]byte{127, 0, 0, 1})
func TestTrie_Basic(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
domains := []string{
"example.com",
"google.com",
@ -23,7 +23,7 @@ func TestTrie_Basic(t *testing.T) {
node := tree.Search("example.com")
assert.NotNil(t, node)
assert.True(t, node.Data.(net.IP).Equal(localIP))
assert.True(t, node.Data == localIP)
assert.NotNil(t, tree.Insert("", localIP))
assert.Nil(t, tree.Search(""))
assert.NotNil(t, tree.Search("localhost"))
@ -31,7 +31,7 @@ func TestTrie_Basic(t *testing.T) {
}
func TestTrie_Wildcard(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
domains := []string{
"*.example.com",
"sub.*.example.com",
@ -64,7 +64,7 @@ func TestTrie_Wildcard(t *testing.T) {
}
func TestTrie_Priority(t *testing.T) {
tree := New()
tree := New[int]()
domains := []string{
".dev",
"example.dev",
@ -79,18 +79,18 @@ func TestTrie_Priority(t *testing.T) {
}
for idx, domain := range domains {
tree.Insert(domain, idx)
tree.Insert(domain, idx+1)
}
assertFn("test.dev", 0)
assertFn("foo.bar.dev", 0)
assertFn("example.dev", 1)
assertFn("foo.example.dev", 2)
assertFn("test.example.dev", 3)
assertFn("test.dev", 1)
assertFn("foo.bar.dev", 1)
assertFn("example.dev", 2)
assertFn("foo.example.dev", 3)
assertFn("test.example.dev", 4)
}
func TestTrie_Boundary(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
tree.Insert("*.dev", localIP)
assert.NotNil(t, tree.Insert(".", localIP))
@ -99,7 +99,7 @@ func TestTrie_Boundary(t *testing.T) {
}
func TestTrie_WildcardBoundary(t *testing.T) {
tree := New()
tree := New[netip.Addr]()
tree.Insert("+.*", localIP)
tree.Insert("stun.*.*.*", localIP)

View File

@ -1,26 +1,31 @@
package trie
// Node is the trie's node
type Node struct {
children map[string]*Node
Data any
type Node[T comparable] struct {
children map[string]*Node[T]
Data T
}
func (n *Node) getChild(s string) *Node {
func (n *Node[T]) getChild(s string) *Node[T] {
return n.children[s]
}
func (n *Node) hasChild(s string) bool {
func (n *Node[T]) hasChild(s string) bool {
return n.getChild(s) != nil
}
func (n *Node) addChild(s string, child *Node) {
func (n *Node[T]) addChild(s string, child *Node[T]) {
n.children[s] = child
}
func newNode(data any) *Node {
return &Node{
func newNode[T comparable](data T) *Node[T] {
return &Node[T]{
Data: data,
children: map[string]*Node{},
children: map[string]*Node[T]{},
}
}
func getZero[T comparable]() T {
var result T
return result
}

View File

@ -4,16 +4,19 @@ import (
"container/list"
"errors"
"fmt"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"net"
"net/netip"
"net/url"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/Dreamacro/clash/common/utils"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/outboundgroup"
@ -78,7 +81,7 @@ type DNS struct {
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie
Hosts *trie.DomainTrie[netip.Addr]
NameServerPolicy map[string]dns.NameServer
ProxyServerNameserver []dns.NameServer
}
@ -113,12 +116,6 @@ type Tun struct {
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
}
// Script config
type Script struct {
MainCode string `yaml:"code" json:"code"`
ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"`
}
// IPTables config
type IPTables struct {
Enable bool `yaml:"enable" json:"enable"`
@ -126,6 +123,16 @@ type IPTables struct {
Bypass []string `yaml:"bypass" json:"bypass"`
}
type Sniffer struct {
Enable bool
Force bool
Sniffers []C.SnifferType
Reverses *trie.DomainTrie[bool]
ForceDomain *trie.DomainTrie[bool]
SkipSNI *trie.DomainTrie[bool]
Ports *[]utils.Range[uint16]
}
// Experimental config
type Experimental struct{}
@ -136,13 +143,14 @@ type Config struct {
IPTables *IPTables
DNS *DNS
Experimental *Experimental
Hosts *trie.DomainTrie
Hosts *trie.DomainTrie[netip.Addr]
Profile *Profile
Rules []C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy
Providers map[string]providerTypes.ProxyProvider
RuleProviders map[string]*providerTypes.RuleProvider
Sniffer *Sniffer
}
type RawDNS struct {
@ -175,6 +183,7 @@ type RawTun struct {
Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
AutoDetectInterface bool `yaml:"auto-detect-interface"`
}
type RawConfig struct {
@ -198,6 +207,7 @@ type RawConfig struct {
GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"`
Sniffer SnifferRaw `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]string `yaml:"hosts"`
@ -211,6 +221,16 @@ type RawConfig struct {
Rule []string `yaml:"rules"`
}
type SnifferRaw struct {
Enable bool `yaml:"enable" json:"enable"`
Sniffing []string `yaml:"sniffing" json:"sniffing"`
Force bool `yaml:"force" json:"force"`
Reverse []string `yaml:"reverses" json:"reverses"`
ForceDomain []string `yaml:"force-domain" json:"force-domain"`
SkipSNI []string `yaml:"skip-sni" json:"skip-sni"`
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
}
// Parse config
func Parse(buf []byte) (*Config, error) {
rawCfg, err := UnmarshalRawConfig(buf)
@ -239,6 +259,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Tun: RawTun{
Enable: false,
Device: "",
AutoDetectInterface: true,
Stack: C.TunGvisor,
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true,
@ -275,6 +296,15 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"www.msftconnecttest.com",
},
},
Sniffer: SnifferRaw{
Enable: false,
Force: false,
Sniffing: []string{},
Reverse: []string{},
ForceDomain: []string{},
SkipSNI: []string{},
Ports: []string{},
},
Profile: Profile{
StoreSelected: true,
},
@ -337,6 +367,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Users = parseAuthentication(rawCfg.Authentication)
config.Sniffer, err = parseSniffer(rawCfg.Sniffer)
if err != nil {
return nil, err
}
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
return config, nil
@ -538,7 +573,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
params = rule[l:]
}
if _, ok := proxies[target]; mode != T.Script && !ok {
if _, ok := proxies[target]; !ok {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
}
@ -555,28 +590,26 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
}
if mode != T.Script {
rules = append(rules, parsed)
}
}
runtime.GC()
return rules, ruleProviders, nil
}
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
tree := trie.New()
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
tree := trie.New[netip.Addr]()
// add default hosts
if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil {
if err := tree.Insert("localhost", netip.AddrFrom4([4]byte{127, 0, 0, 1})); err != nil {
log.Errorln("insert localhost to host error: %s", err.Error())
}
if len(cfg.Hosts) != 0 {
for domain, ipStr := range cfg.Hosts {
ip := net.ParseIP(ipStr)
if ip == nil {
ip, err := netip.ParseAddr(ipStr)
if err != nil {
return nil, fmt.Errorf("%s is not a valid IP", ipStr)
}
_ = tree.Insert(domain, ip)
@ -724,7 +757,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
return sites, nil
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS, error) {
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.Rule) (*DNS, error) {
cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@ -767,20 +800,23 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
for _, ns := range dnsCfg.DefaultNameserver {
host, _, err := net.SplitHostPort(ns.Addr)
if err != nil || net.ParseIP(host) == nil {
u, err := url.Parse(ns.Addr)
if err != nil || net.ParseIP(u.Host) == nil {
return nil, errors.New("default nameserver should be pure IP")
}
}
}
if cfg.EnhancedMode == C.DNSFakeIP {
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
ipnet, err := netip.ParsePrefix(cfg.FakeIPRange)
if err != nil {
return nil, err
}
var host *trie.DomainTrie
var host *trie.DomainTrie[bool]
// fake ip skip host filter
if len(cfg.FakeIPFilter) != 0 {
host = trie.New()
host = trie.New[bool]()
for _, domain := range cfg.FakeIPFilter {
_ = host.Insert(domain, true)
}
@ -788,7 +824,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
if len(dnsCfg.Fallback) != 0 {
if host == nil {
host = trie.New()
host = trie.New[bool]()
}
for _, fb := range dnsCfg.Fallback {
if net.ParseIP(fb.Addr) != nil {
@ -799,7 +835,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS,
}
pool, err := fakeip.New(fakeip.Options{
IPNet: ipnet,
IPNet: &ipnet,
Size: 1000,
Host: host,
Persistence: rawCfg.Profile.StoreFakeIP,
@ -843,7 +879,7 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
}
func parseTun(rawTun RawTun, general *General) (*Tun, error) {
if (rawTun.Enable || general.TProxyPort != 0) && general.Interface == "" {
if rawTun.Enable && rawTun.AutoDetectInterface {
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
if err != nil || autoDetectInterfaceName == "" {
log.Warnln("Can not find auto detect interface.[%s]", err)
@ -877,3 +913,97 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
AutoRoute: rawTun.AutoRoute,
}, nil
}
func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
sniffer := &Sniffer{
Enable: snifferRaw.Enable,
Force: snifferRaw.Force,
}
ports := []utils.Range[uint16]{}
if len(snifferRaw.Ports) == 0 {
ports = append(ports, *utils.NewRange[uint16](0, 65535))
} else {
for _, portRange := range snifferRaw.Ports {
portRaws := strings.Split(portRange, "-")
if len(portRaws) > 1 {
p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
start := uint16(p)
p, err = strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
end := uint16(p)
ports = append(ports, *utils.NewRange(start, end))
}
}
}
sniffer.Ports = &ports
loadSniffer := make(map[C.SnifferType]struct{})
for _, snifferName := range snifferRaw.Sniffing {
find := false
for _, snifferType := range C.SnifferList {
if snifferType.String() == strings.ToUpper(snifferName) {
find = true
loadSniffer[snifferType] = struct{}{}
}
}
if !find {
return nil, fmt.Errorf("not find the sniffer[%s]", snifferName)
}
}
for st := range loadSniffer {
sniffer.Sniffers = append(sniffer.Sniffers, st)
}
sniffer.ForceDomain = trie.New[bool]()
for _, domain := range snifferRaw.ForceDomain {
err := sniffer.ForceDomain.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
sniffer.SkipSNI = trie.New[bool]()
for _, domain := range snifferRaw.SkipSNI {
err := sniffer.SkipSNI.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
// Compatibility, remove it when release
if strings.Contains(C.Version, "alpha") || strings.Contains(C.Version, "develop") || strings.Contains(C.Version, "1.10.0") {
log.Warnln("Sniffer param force and reverses deprecated, will be removed in the release version, see https://github.com/MetaCubeX/Clash.Meta/commit/48a01adb7a4f38974b9d9639f931d0d245aebf28")
if snifferRaw.Force {
// match all domain
sniffer.ForceDomain.Insert("+", true)
for _, domain := range snifferRaw.Reverse {
err := sniffer.SkipSNI.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s], error:%v", domain, err)
}
}
} else {
for _, domain := range snifferRaw.Reverse {
err := sniffer.ForceDomain.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s], error:%v", domain, err)
}
}
}
}
return sniffer, nil
}

View File

@ -88,6 +88,7 @@ type Metadata struct {
func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), m.DstPort)
}
func (m *Metadata) SourceAddress() string {

26
constant/sniffer.go Normal file
View File

@ -0,0 +1,26 @@
package constant
type Sniffer interface {
SupportNetwork() NetWork
SniffTCP(bytes []byte) (string, error)
Protocol() string
}
const (
TLS SnifferType = iota
)
var (
SnifferList = []SnifferType{TLS}
)
type SnifferType int
func (rt SnifferType) String() string {
switch rt {
case TLS:
return "TLS"
default:
return "Unknown"
}
}

View File

@ -3,8 +3,8 @@ package context
import (
"net"
CN "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid"
)
@ -19,7 +19,7 @@ func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
return &ConnContext{
id: id,
metadata: metadata,
conn: conn,
conn: CN.NewBufferedConn(conn),
}
}

View File

@ -2,6 +2,7 @@ package dns
import (
"net"
"net/netip"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip"
@ -11,7 +12,7 @@ import (
type ResolverEnhancer struct {
mode C.DNSMode
fakePool *fakeip.Pool
mapping *cache.LruCache
mapping *cache.LruCache[netip.Addr, string]
}
func (h *ResolverEnhancer) FakeIPEnabled() bool {
@ -28,7 +29,7 @@ func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool {
}
if pool := h.fakePool; pool != nil {
return pool.Exist(ip)
return pool.Exist(ipToAddr(ip))
}
return false
@ -39,8 +40,10 @@ func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool {
return false
}
addr := ipToAddr(ip)
if pool := h.fakePool; pool != nil {
return pool.IPNet().Contains(ip) && !pool.Gateway().Equal(ip) && !pool.Broadcast().Equal(ip)
return pool.IPNet().Contains(addr) && addr != pool.Gateway() && addr != pool.Broadcast()
}
return false
@ -52,28 +55,35 @@ func (h *ResolverEnhancer) IsFakeBroadcastIP(ip net.IP) bool {
}
if pool := h.fakePool; pool != nil {
return pool.Broadcast().Equal(ip)
return pool.Broadcast() == ipToAddr(ip)
}
return false
}
func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) {
addr := ipToAddr(ip)
if pool := h.fakePool; pool != nil {
if host, existed := pool.LookBack(ip); existed {
if host, existed := pool.LookBack(addr); existed {
return host, true
}
}
if mapping := h.mapping; mapping != nil {
if host, existed := h.mapping.Get(ip.String()); existed {
return host.(string), true
if host, existed := h.mapping.Get(addr); existed {
return host, true
}
}
return "", false
}
func (h *ResolverEnhancer) InsertHostByIP(ip net.IP, host string) {
if mapping := h.mapping; mapping != nil {
h.mapping.Set(ipToAddr(ip), host)
}
}
func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) {
if h.mapping != nil && o.mapping != nil {
o.mapping.CloneTo(h.mapping)
@ -93,11 +103,11 @@ func (h *ResolverEnhancer) FlushFakeIP() error {
func NewEnhancer(cfg Config) *ResolverEnhancer {
var fakePool *fakeip.Pool
var mapping *cache.LruCache
var mapping *cache.LruCache[netip.Addr, string]
if cfg.EnhancedMode != C.DNSNormal {
fakePool = cfg.Pool
mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true))
mapping = cache.NewLRUCache[netip.Addr, string](cache.WithSize[netip.Addr, string](4096), cache.WithStale[netip.Addr, string](true))
}
return &ResolverEnhancer{

View File

@ -70,13 +70,13 @@ type fallbackDomainFilter interface {
}
type domainFilter struct {
tree *trie.DomainTrie
tree *trie.DomainTrie[bool]
}
func NewDomainFilter(domains []string) *domainFilter {
df := domainFilter{tree: trie.New()}
df := domainFilter{tree: trie.New[bool]()}
for _, domain := range domains {
df.tree.Insert(domain, "")
df.tree.Insert(domain, true)
}
return &df
}

View File

@ -2,6 +2,7 @@ package dns
import (
"net"
"net/netip"
"strings"
"time"
@ -20,7 +21,7 @@ type (
middleware func(next handler) handler
)
func withHosts(hosts *trie.DomainTrie) middleware {
func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip.Addr, string]) middleware {
return func(next handler) handler {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0]
@ -29,30 +30,36 @@ func withHosts(hosts *trie.DomainTrie) middleware {
return next(ctx, r)
}
record := hosts.Search(strings.TrimRight(q.Name, "."))
host := strings.TrimRight(q.Name, ".")
record := hosts.Search(host)
if record == nil {
return next(ctx, r)
}
ip := record.Data.(net.IP)
ip := record.Data
msg := r.Copy()
if v4 := ip.To4(); v4 != nil && q.Qtype == D.TypeA {
if ip.Is4() && q.Qtype == D.TypeA {
rr := &D.A{}
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL}
rr.A = v4
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
rr.A = ip.AsSlice()
msg.Answer = []D.RR{rr}
} else if v6 := ip.To16(); v6 != nil && q.Qtype == D.TypeAAAA {
} else if ip.Is6() && q.Qtype == D.TypeAAAA {
rr := &D.AAAA{}
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: dnsDefaultTTL}
rr.AAAA = v6
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
rr.AAAA = ip.AsSlice()
msg.Answer = []D.RR{rr}
} else {
return next(ctx, r)
}
if mapping != nil {
mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*10))
}
ctx.SetType(context.DNSTypeHost)
msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true
@ -63,7 +70,7 @@ func withHosts(hosts *trie.DomainTrie) middleware {
}
}
func withMapping(mapping *cache.LruCache) middleware {
func withMapping(mapping *cache.LruCache[netip.Addr, string]) middleware {
return func(next handler) handler {
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
q := r.Question[0]
@ -94,7 +101,7 @@ func withMapping(mapping *cache.LruCache) middleware {
continue
}
mapping.SetWithExpire(ip.String(), host, time.Now().Add(time.Second*time.Duration(ttl)))
mapping.SetWithExpire(ipToAddr(ip), host, time.Now().Add(time.Second*time.Duration(ttl)))
}
return msg, nil
@ -124,7 +131,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
rr := &D.A{}
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL}
ip := fakePool.Lookup(host)
rr.A = ip
rr.A = ip.AsSlice()
msg := r.Copy()
msg.Answer = []D.RR{rr}
@ -176,7 +183,7 @@ func NewHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
middlewares := []middleware{}
if resolver.hosts != nil {
middlewares = append(middlewares, withHosts(resolver.hosts))
middlewares = append(middlewares, withHosts(resolver.hosts, mapper.mapping))
}
if mapper.mode == C.DNSFakeIP {

30
dns/policy.go Normal file
View File

@ -0,0 +1,30 @@
package dns
type Policy struct {
data []dnsClient
}
func (p *Policy) GetData() []dnsClient {
return p.data
}
func (p *Policy) Compare(p2 *Policy) int {
if p2 == nil {
return 1
}
l1 := len(p.data)
l2 := len(p2.data)
if l1 == l2 {
return 0
}
if l1 > l2 {
return 1
}
return -1
}
func NewPolicy(data []dnsClient) *Policy {
return &Policy{
data: data,
}
}

View File

@ -19,7 +19,7 @@ var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
type quicClient struct {
addr string
session quic.Session
session quic.Connection
sync.RWMutex // protects session and bytesPool
}
@ -67,7 +67,7 @@ func (dc *quicClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg
return reply, nil
}
func isActive(s quic.Session) bool {
func isActive(s quic.Connection) bool {
select {
case <-s.Context().Done():
return false
@ -76,11 +76,11 @@ func isActive(s quic.Session) bool {
}
}
// getSession - opens or returns an existing quic.Session
// getSession - opens or returns an existing quic.Connection
// useCached - if true and cached session exists, return it right away
// otherwise - forcibly creates a new session
func (dc *quicClient) getSession() (quic.Session, error) {
var session quic.Session
func (dc *quicClient) getSession() (quic.Connection, error) {
var session quic.Connection
dc.RLock()
session = dc.session
if session != nil && isActive(session) {
@ -113,7 +113,7 @@ func (dc *quicClient) getSession() (quic.Session, error) {
return session, nil
}
func (dc *quicClient) openSession() (quic.Session, error) {
func (dc *quicClient) openSession() (quic.Connection, error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{

View File

@ -6,6 +6,7 @@ import (
"fmt"
"math/rand"
"net"
"net/netip"
"strings"
"time"
@ -33,14 +34,14 @@ type result struct {
type Resolver struct {
ipv6 bool
hosts *trie.DomainTrie
hosts *trie.DomainTrie[netip.Addr]
main []dnsClient
fallback []dnsClient
fallbackDomainFilters []fallbackDomainFilter
fallbackIPFilters []fallbackIPFilter
group singleflight.Group
lruCache *cache.LruCache
policy *trie.DomainTrie
lruCache *cache.LruCache[string, *D.Msg]
policy *trie.DomainTrie[*Policy]
proxyServer []dnsClient
}
@ -103,7 +104,7 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
cache, expireTime, hit := r.lruCache.GetWithExpire(q.String())
if hit {
now := time.Now()
msg = cache.(*D.Msg).Copy()
msg = cache.Copy()
if expireTime.Before(now) {
setMsgTTL(msg, uint32(1)) // Continue fetch
go r.exchangeWithoutCache(ctx, m)
@ -194,7 +195,8 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
return nil
}
return record.Data.([]dnsClient)
p := record.Data
return p.GetData()
}
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
@ -329,20 +331,20 @@ type Config struct {
EnhancedMode C.DNSMode
FallbackFilter FallbackFilter
Pool *fakeip.Pool
Hosts *trie.DomainTrie
Hosts *trie.DomainTrie[netip.Addr]
Policy map[string]NameServer
}
func NewResolver(config Config) *Resolver {
defaultResolver := &Resolver{
main: transform(config.Default, nil),
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
lruCache: cache.NewLRUCache[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
}
r := &Resolver{
ipv6: config.IPv6,
main: transform(config.Main, defaultResolver),
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
lruCache: cache.NewLRUCache[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
hosts: config.Hosts,
}
@ -355,9 +357,9 @@ func NewResolver(config Config) *Resolver {
}
if len(config.Policy) != 0 {
r.policy = trie.New()
r.policy = trie.New[*Policy]()
for domain, nameserver := range config.Policy {
r.policy.Insert(domain, transform([]NameServer{nameserver}, defaultResolver))
r.policy.Insert(domain, NewPolicy(transform([]NameServer{nameserver}, defaultResolver)))
}
}

View File

@ -5,6 +5,7 @@ import (
"crypto/tls"
"fmt"
"net"
"net/netip"
"time"
"github.com/Dreamacro/clash/common/cache"
@ -16,7 +17,7 @@ import (
D "github.com/miekg/dns"
)
func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
var ttl uint32
switch {
case len(msg.Answer) != 0:
@ -114,6 +115,22 @@ func msgToIP(msg *D.Msg) []net.IP {
return ips
}
func ipToAddr(ip net.IP) netip.Addr {
if ip == nil {
return netip.Addr{}
}
l := len(ip)
if l == 4 {
return netip.AddrFrom4(*(*[4]byte)(ip))
} else if l == 16 {
return netip.AddrFrom16(*(*[16]byte)(ip))
} else {
return netip.Addr{}
}
}
type wrapPacketConn struct {
net.PacketConn
rAddr net.Addr

27
go.mod
View File

@ -3,33 +3,34 @@ module github.com/Dreamacro/clash
go 1.18
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/Dreamacro/go-shadowsocks2 v0.1.8
github.com/dlclark/regexp2 v1.4.0
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd
github.com/lucas-clemente/quic-go v0.26.0
github.com/miekg/dns v1.1.47
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
github.com/lucas-clemente/quic-go v0.27.0
github.com/miekg/dns v1.1.48
github.com/oschwald/geoip2-golang v1.7.0
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.1
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.9.0
go.uber.org/automaxprocs v1.4.0
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/net v0.0.0-20220325170049-de3da57026de
go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd
golang.org/x/net v0.0.0-20220412020605-290c469a71a5
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6
golang.org/x/sys v0.0.0-20220412071739-889880a91fd5
golang.org/x/time v0.0.0-20220411224347-583f2d630306
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0
gvisor.dev/gvisor v0.0.0-20220326024801-5d1f3d24cb84
gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b
)
require (
@ -50,10 +51,10 @@ require (
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
replace golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 => github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e

54
go.sum
View File

@ -8,8 +8,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviKgwb1quWw3g=
github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y=
github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e h1:GRfT5Lf8HP7RNczKIwTYLoCh1PPuIs/sY9hj+W+3deg=
github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e/go.mod h1:ARUuShAtcziEJ/vnZ2hgoP+zc0J7Ukcca2S/NPDoQCc=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@ -88,8 +88,8 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd h1:efcJu2Vzz6DoSq245deWNzTz6l/gsqdphm3FjmI88/g=
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I=
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
@ -106,8 +106,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucas-clemente/quic-go v0.26.0 h1:ALBQXr9UJ8A1LyzvceX4jd9QFsHvlI0RR6BkV16o00A=
github.com/lucas-clemente/quic-go v0.26.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
github.com/lucas-clemente/quic-go v0.27.0 h1:v6WY87q9zD4dKASbG8hy/LpzAVNzEQzw8sEIeloJsc4=
github.com/lucas-clemente/quic-go v0.27.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
@ -126,8 +126,8 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.47 h1:J9bWiXbqMbnZPcY8Qi2E3EWIBsIm6MZzzJB9VRg5gL8=
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@ -154,6 +154,7 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
@ -191,7 +192,6 @@ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
@ -211,8 +211,8 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -221,9 +221,8 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -253,12 +252,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -304,8 +302,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412071739-889880a91fd5 h1:NubxfvTRuNb4RVzWrIDAUzUvREH1HkCD4JjyQTSG9As=
golang.org/x/sys v0.0.0-20220412071739-889880a91fd5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -316,8 +314,8 @@ golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9G
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -334,10 +332,11 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 h1:kgBK1EGuTIYbwoKROmsoV0FQp08gnCcVa110A4Unqhk=
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 h1:SEYkJAIuYAsSAPkCffOiYLtq5brBDSI+L0mRjSsvSTY=
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
@ -379,11 +378,12 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20220326024801-5d1f3d24cb84 h1:nENO+rT8Nx+Vtp/VK+K7g9VpHdvJwJYCFgdN5yaoAzA=
gvisor.dev/gvisor v0.0.0-20220326024801-5d1f3d24cb84/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b h1:JW1pUBe6A3H+b0B9DwEOcfK+TLS/04A3A9cettPpfV0=
gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -3,6 +3,7 @@ package executor
import (
"fmt"
"net"
"net/netip"
"os"
"runtime"
"strconv"
@ -17,6 +18,7 @@ import (
"github.com/Dreamacro/clash/component/profile"
"github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/resolver"
SNI "github.com/Dreamacro/clash/component/sniffer"
"github.com/Dreamacro/clash/component/trie"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
@ -75,6 +77,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateUsers(cfg.Users)
updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules, cfg.RuleProviders)
updateSniffer(cfg.Sniffer)
updateDNS(cfg.DNS, cfg.Tun)
updateGeneral(cfg.General, force)
updateIPTables(cfg)
@ -169,7 +172,7 @@ func updateDNS(c *config.DNS, t *config.Tun) {
}
}
func updateHosts(tree *trie.DomainTrie) {
func updateHosts(tree *trie.DomainTrie[netip.Addr]) {
resolver.DefaultHosts = tree
}
@ -217,6 +220,26 @@ func updateTun(tun *config.Tun, dns *config.DNS) {
P.ReCreateTun(tun, dns, tunnel.TCPIn(), tunnel.UDPIn())
}
func updateSniffer(sniffer *config.Sniffer) {
if sniffer.Enable {
dispatcher, err := SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipSNI, sniffer.Ports)
if err != nil {
log.Warnln("initial sniffer failed, err:%v", err)
}
tunnel.UpdateSniffer(dispatcher)
log.Infoln("Sniffer is loaded and working")
} else {
dispatcher, err := SNI.NewCloseSnifferDispatcher()
if err != nil {
log.Warnln("initial sniffer failed, err:%v", err)
}
tunnel.UpdateSniffer(dispatcher)
log.Infoln("Sniffer is closed")
}
}
func updateGeneral(general *config.General, force bool) {
log.SetLevel(general.LogLevel)
tunnel.SetMode(general.Mode)

View File

@ -15,7 +15,7 @@ import (
"github.com/Dreamacro/clash/log"
)
func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string, bool]) {
client := newClient(c.RemoteAddr(), in)
defer client.CloseIdleConnections()
@ -98,7 +98,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
conn.Close()
}
func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
func authenticate(request *http.Request, cache *cache.Cache[string, bool]) *http.Response {
authenticator := authStore.Authenticator()
if authenticator != nil {
credential := parseBasicProxyAuthorization(request)
@ -108,13 +108,13 @@ func authenticate(request *http.Request, cache *cache.Cache) *http.Response {
return resp
}
var authed any
if authed = cache.Get(credential); authed == nil {
var authed bool
if authed = cache.Get(credential); !authed {
user, pass, err := decodeBasicProxyAuthorization(credential)
authed = err == nil && authenticator.Verify(user, pass)
cache.Put(credential, authed, time.Minute)
}
if !authed.(bool) {
if !authed {
log.Infoln("Auth failed from %s", request.RemoteAddr)
return responseWith(request, http.StatusForbidden)

View File

@ -40,9 +40,9 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool
return nil, err
}
var c *cache.Cache
var c *cache.Cache[string, bool]
if authenticate {
c = cache.New(time.Second * 30)
c = cache.New[string, bool](time.Second * 30)
}
hl := &Listener{

View File

@ -40,7 +40,7 @@ func removeExtraHTTPHostPort(req *http.Request) {
host = req.URL.Host
}
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
if pHost, port, err := net.SplitHostPort(host); err == nil && (port == "80" || port == "443") {
host = pHost
}

View File

@ -16,7 +16,7 @@ import (
type Listener struct {
listener net.Listener
addr string
cache *cache.Cache
cache *cache.Cache[string, bool]
closed bool
}
@ -45,7 +45,7 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
ml := &Listener{
listener: l,
addr: addr,
cache: cache.New(30 * time.Second),
cache: cache.New[string, bool](30 * time.Second),
}
go func() {
for {
@ -63,7 +63,7 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
return ml, nil
}
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string, bool]) {
conn.(*net.TCPConn).SetKeepAlive(true)
bufConn := N.NewBufferedConn(conn)

View File

@ -29,7 +29,4 @@ type Device interface {
// UseIOBased work for other ip stack
UseIOBased() error
// Wait waits for the device to close.
Wait()
}

View File

@ -36,6 +36,9 @@ type Endpoint struct {
// once is used to perform the init action once when attaching.
once sync.Once
// wg keeps track of running goroutines.
wg sync.WaitGroup
}
// New returns stack.LinkEndpoint(.*Endpoint) and error.
@ -60,19 +63,26 @@ func New(rw io.ReadWriter, mtu uint32, offset int) (*Endpoint, error) {
}, nil
}
func (e *Endpoint) Close() {
e.Endpoint.Close()
func (e *Endpoint) Wait() {
e.wg.Wait()
}
// Attach launches the goroutine that reads packets from io.Reader and
// dispatches them via the provided dispatcher.
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.Endpoint.Attach(dispatcher)
e.once.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
go e.dispatchLoop(cancel)
go e.outboundLoop(ctx)
e.wg.Add(2)
go func() {
e.outboundLoop(ctx)
e.wg.Done()
}()
go func() {
e.dispatchLoop(cancel)
e.wg.Done()
}()
})
e.Endpoint.Attach(dispatcher)
}
// dispatchLoop dispatches packets to upper layer.
@ -81,14 +91,19 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
// gracefully after (*Endpoint).dispatchLoop(context.CancelFunc) returns.
defer cancel()
mtu := int(e.mtu)
for {
data := make([]byte, int(e.mtu))
data := make([]byte, mtu)
n, err := e.rw.Read(data)
if err != nil {
break
}
if n == 0 || n > mtu {
continue
}
if !e.IsAttached() {
continue /* unattached, drop packet */
}

View File

@ -32,15 +32,6 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
}
}()
var (
offset = 4 /* 4 bytes TUN_PI */
defaultMTU = 1500
)
if runtime.GOOS == "windows" {
offset = 0
defaultMTU = 0 /* auto */
}
t := &TUN{
name: name,
mtu: mtu,
@ -101,9 +92,11 @@ func (t *TUN) Write(packet []byte) (int, error) {
}
func (t *TUN) Close() error {
if t.Endpoint != nil {
t.Endpoint.Close()
defer func(ep *iobased.Endpoint) {
if ep != nil {
ep.Close()
}
}(t.Endpoint)
return t.nt.Close()
}

View File

@ -0,0 +1,8 @@
//go:build !linux && !windows
package tun
const (
offset = 4 /* 4 bytes TUN_PI */
defaultMTU = 1500
)

View File

@ -5,6 +5,11 @@ import (
"golang.zx2c4.com/wireguard/tun"
)
const (
offset = 0
defaultMTU = 0 /* auto */
)
func init() {
guid, _ := windows.GUIDFromString("{330EAEF8-7578-5DF2-D97B-8DADC0EA85CB}")

View File

@ -27,7 +27,7 @@ type GVHandler struct {
func (gh *GVHandler) HandleTCP(tunConn adapter.TCPConn) {
id := tunConn.ID()
rAddr := &net.UDPAddr{
rAddr := &net.TCPAddr{
IP: net.IP(id.LocalAddress),
Port: int(id.LocalPort),
Zone: "",

View File

@ -37,7 +37,7 @@ const (
// tcpModerateReceiveBufferEnabled is the value used by stack to
// enable or disable tcp receive buffer auto-tuning option.
tcpModerateReceiveBufferEnabled = true
tcpModerateReceiveBufferEnabled = false
// tcpSACKEnabled is the value used by stack to enable or disable
// tcp selective ACK.
@ -47,14 +47,18 @@ const (
tcpRecovery = tcpip.TCPRACKLossDetection
// tcpMinBufferSize is the smallest size of a send/recv buffer.
tcpMinBufferSize = tcp.MinBufferSize // 4 KiB
tcpMinBufferSize = tcp.MinBufferSize
// tcpMaxBufferSize is the maximum permitted size of a send/recv buffer.
tcpMaxBufferSize = tcp.MaxBufferSize // 4 MiB
tcpMaxBufferSize = tcp.MaxBufferSize
// tcpDefaultBufferSize is the default size of the send/recv buffer for
// tcpDefaultBufferSize is the default size of the send buffer for
// a transport endpoint.
tcpDefaultBufferSize = 212 << 10 // 212 KiB
tcpDefaultSendBufferSize = tcp.DefaultSendBufferSize
// tcpDefaultReceiveBufferSize is the default size of the receive buffer
// for a transport endpoint.
tcpDefaultReceiveBufferSize = tcp.DefaultReceiveBufferSize
)
type Option func(*stack.Stack) error
@ -74,7 +78,8 @@ func WithDefault() Option {
// in too large buffers.
//
// Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go
WithTCPBufferSizeRange(tcpMinBufferSize, tcpDefaultBufferSize, tcpMaxBufferSize),
WithTCPSendBufferSizeRange(tcpMinBufferSize, tcpDefaultSendBufferSize, tcpMaxBufferSize),
WithTCPReceiveBufferSizeRange(tcpMinBufferSize, tcpDefaultReceiveBufferSize, tcpMaxBufferSize),
WithTCPCongestionControl(tcpCongestionControlAlgorithm),
WithTCPDelay(tcpDelayEnabled),
@ -154,17 +159,46 @@ func WithICMPLimit(limit rate.Limit) Option {
}
}
// WithTCPBufferSizeRange sets the receive and send buffer size range for TCP.
func WithTCPBufferSizeRange(a, b, c int) Option {
// WithTCPSendBufferSize sets default the send buffer size for TCP.
func WithTCPSendBufferSize(size int) Option {
return func(s *stack.Stack) error {
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
return fmt.Errorf("set TCP send buffer size range: %s", err)
}
return nil
}
}
// WithTCPSendBufferSizeRange sets the send buffer size range for TCP.
func WithTCPSendBufferSizeRange(a, b, c int) Option {
return func(s *stack.Stack) error {
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: a, Default: b, Max: c}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
return fmt.Errorf("set TCP send buffer size range: %s", err)
}
return nil
}
}
// WithTCPReceiveBufferSize sets the default receive buffer size for TCP.
func WithTCPReceiveBufferSize(size int) Option {
return func(s *stack.Stack) error {
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
return fmt.Errorf("set TCP receive buffer size range: %s", err)
}
return nil
}
}
// WithTCPReceiveBufferSizeRange sets the receive buffer size range for TCP.
func WithTCPReceiveBufferSizeRange(a, b, c int) Option {
return func(s *stack.Stack) error {
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: a, Default: b, Max: c}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
return fmt.Errorf("set TCP receive buffer size range: %s", err)
}
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: a, Default: b, Max: c}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
return fmt.Errorf("set TCP send buffer size range: %s", err)
}
return nil
}
}

View File

@ -25,7 +25,6 @@ func (s *gvStack) Close() error {
var err error
if s.device != nil {
err = s.device.Close()
s.device.Wait()
}
if s.Stack != nil {
s.Stack.Close()

View File

@ -1,14 +1,15 @@
package gvisor
import (
"fmt"
"time"
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
"github.com/Dreamacro/clash/log"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/waiter"
@ -43,8 +44,21 @@ const (
func withTCPHandler(handle adapter.TCPHandleFunc) option.Option {
return func(s *stack.Stack) error {
tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
var wq waiter.Queue
ep, err := r.CreateEndpoint(&wq)
var (
wq waiter.Queue
ep tcpip.Endpoint
err tcpip.Error
id = r.ID()
)
defer func() {
if err != nil {
log.Warnln("[STACK] forward tcp request %s:%d->%s:%d: %s", id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
}
}()
// Perform a TCP three-way handshake.
ep, err = r.CreateEndpoint(&wq)
if err != nil {
// RST: prevent potential half-open TCP connection leak.
r.Complete(true)
@ -52,11 +66,11 @@ func withTCPHandler(handle adapter.TCPHandleFunc) option.Option {
}
defer r.Complete(false)
setKeepalive(ep)
err = setSocketOptions(s, ep)
conn := &tcpConn{
TCPConn: gonet.NewTCPConn(&wq, ep),
id: r.ID(),
id: id,
}
handle(conn)
})
@ -65,21 +79,34 @@ func withTCPHandler(handle adapter.TCPHandleFunc) option.Option {
}
}
func setKeepalive(ep tcpip.Endpoint) error {
func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
{ /* TCP keepalive options */
ep.SocketOptions().SetKeepAlive(true)
idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle)
if err := ep.SetSockOpt(&idle); err != nil {
return fmt.Errorf("set keepalive idle: %s", err)
return err
}
interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval)
if err := ep.SetSockOpt(&interval); err != nil {
return fmt.Errorf("set keepalive interval: %s", err)
return err
}
if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil {
return fmt.Errorf("set keepalive count: %s", err)
return err
}
}
{ /* TCP recv/send buffer size */
var ss tcpip.TCPSendBufferSizeRangeOption
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil {
ep.SocketOptions().SetReceiveBufferSize(int64(ss.Default), false)
}
var rs tcpip.TCPReceiveBufferSizeRangeOption
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &rs); err == nil {
ep.SocketOptions().SetReceiveBufferSize(int64(rs.Default), false)
}
}
return nil
}

View File

@ -6,6 +6,7 @@ import (
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
"github.com/Dreamacro/clash/log"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@ -16,16 +17,19 @@ import (
func withUDPHandler(handle adapter.UDPHandleFunc) option.Option {
return func(s *stack.Stack) error {
udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) {
var wq waiter.Queue
var (
wq waiter.Queue
id = r.ID()
)
ep, err := r.CreateEndpoint(&wq)
if err != nil {
// TODO: handler errors in the future.
log.Warnln("[STACK] udp forwarder request %s:%d->%s:%d: %s", id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
return
}
conn := &udpConn{
UDPConn: gonet.NewUDPConn(s, &wq, ep),
id: r.ID(),
id: id,
}
handle(conn)
})
@ -54,7 +58,7 @@ func (c *packet) Data() []byte {
}
// WriteBack write UDP packet with source(ip, port) = `addr`
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
func (c *packet) WriteBack(b []byte, _ net.Addr) (n int, err error) {
return c.pc.WriteTo(b, c.rAddr)
}
@ -64,5 +68,5 @@ func (c *packet) LocalAddr() net.Addr {
}
func (c *packet) Drop() {
pool.Put(c.payload)
_ = pool.Put(c.payload)
}

View File

@ -17,6 +17,7 @@ import (
"github.com/Dreamacro/clash/listener/tun/ipstack"
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars"
"github.com/Dreamacro/clash/listener/tun/ipstack/system/mars/nat"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/socks5"
)
@ -24,15 +25,21 @@ import (
type sysStack struct {
stack io.Closer
device device.Device
closed bool
}
func (s sysStack) Close() error {
if s.stack != nil {
_ = s.stack.Close()
}
func (s *sysStack) Close() error {
defer func() {
if s.device != nil {
_ = s.device.Close()
}
}()
s.closed = true
if s.stack != nil {
return s.stack.Close()
}
return nil
}
@ -49,17 +56,25 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
return nil, err
}
ipStack := &sysStack{stack: stack, device: device}
dnsAddr := dnsHijack
tcp := func() {
defer stack.TCP().Close()
defer func(tcp *nat.TCP) {
_ = tcp.Close()
}(stack.TCP())
defer log.Debugln("TCP: closed")
for stack.TCP().SetDeadline(time.Time{}) == nil {
for !ipStack.closed {
if err = stack.TCP().SetDeadline(time.Time{}); err != nil {
break
}
conn, err := stack.TCP().Accept()
if err != nil {
log.Debugln("Accept connection: %v", err)
continue
}
@ -73,13 +88,19 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
go func() {
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
defer conn.Close()
defer func(conn net.Conn) {
_ = conn.Close()
}(conn)
buf := pool.Get(pool.UDPBufferSize)
defer pool.Put(buf)
defer func(buf []byte) {
_ = pool.Put(buf)
}(buf)
for {
conn.SetReadDeadline(time.Now().Add(C.DefaultTCPTimeout))
if err = conn.SetReadDeadline(time.Now().Add(C.DefaultTCPTimeout)); err != nil {
break
}
length := uint16(0)
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
@ -123,10 +144,13 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
}
udp := func() {
defer stack.UDP().Close()
defer func(udp *nat.UDP) {
_ = udp.Close()
}(stack.UDP())
defer log.Debugln("UDP: closed")
for {
for !ipStack.closed {
buf := pool.Get(pool.UDPBufferSize)
n, lRAddr, rRAddr, err := stack.UDP().ReadFrom(buf)
@ -143,15 +167,16 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
if D.ShouldHijackDns(dnsAddr, rAddrPort) {
go func() {
defer pool.Put(buf)
msg, err := D.RelayDnsPacket(raw)
if err != nil {
_ = pool.Put(buf)
return
}
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
_ = pool.Put(buf)
log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
}()
@ -165,7 +190,7 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
return stack.UDP().WriteTo(b, rAddr, lAddr)
},
drop: func() {
pool.Put(buf)
_ = pool.Put(buf)
},
}
@ -186,5 +211,5 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
go udp()
}
return &sysStack{stack: stack, device: device}, nil
return ipStack, nil
}

View File

@ -53,7 +53,6 @@ func New(tunConf *config.Tun, dnsConf *config.DNS, tcpIn chan<- C.ConnContext, u
process.AppendLocalIPs(tunAddress.Masked().Addr().Next().AsSlice())
// open tun device
tunDevice, err = parseDevice(devName, uint32(mtu))
if err != nil {
return nil, fmt.Errorf("can't open tun: %w", err)
@ -112,7 +111,7 @@ func New(tunConf *config.Tun, dnsConf *config.DNS, tcpIn chan<- C.ConnContext, u
func generateDeviceName() string {
switch runtime.GOOS {
case "darwin":
return tun.Driver + "://Meta"
return tun.Driver + "://utun"
case "windows":
return tun.Driver + "://Meta"
default:
@ -149,9 +148,10 @@ func setAtLatest(stackType C.TUNStack, devName string) {
}
switch runtime.GOOS {
case "darwin":
_, _ = cmd.ExecCmd("sysctl net.inet.ip.forwarding=1")
case "windows":
_, _ = cmd.ExecCmd("ipconfig /renew")
case "linux":
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.ip_forward=1")
// _, _ = cmd.ExecCmd("sysctl -w net.ipv4.conf.all.forwarding = 1")

View File

@ -13,22 +13,23 @@ type NetworkType struct {
}
func NewNetworkType(network, adapter string) (*NetworkType, error) {
var netType C.NetWork
ntType := NetworkType{
Base: &Base{},
}
ntType.adapter = adapter
switch strings.ToUpper(network) {
case "TCP":
netType = C.TCP
ntType.network = C.TCP
break
case "UDP":
netType = C.UDP
ntType.network = C.UDP
break
default:
return nil, fmt.Errorf("unsupported network type, only TCP/UDP")
}
return &NetworkType{
Base: &Base{},
network: netType,
adapter: adapter,
}, nil
return &ntType, nil
}
func (n *NetworkType) RuleType() C.RuleType {

View File

@ -5,20 +5,16 @@ import (
"strconv"
"strings"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
)
type portReal struct {
portStart int
portEnd int
}
type Port struct {
*Base
adapter string
port string
isSource bool
portList []portReal
portList []utils.Range[uint16]
}
func (p *Port) RuleType() C.RuleType {
@ -45,17 +41,13 @@ func (p *Port) Payload() string {
func (p *Port) matchPortReal(portRef string) bool {
port, _ := strconv.Atoi(portRef)
var rs bool
for _, pr := range p.portList {
if pr.portEnd == -1 {
rs = port == pr.portStart
} else {
rs = port >= pr.portStart && port <= pr.portEnd
}
if rs {
if pr.Contains(uint16(port)) {
return true
}
}
return false
}
@ -65,7 +57,7 @@ func NewPort(port string, adapter string, isSource bool) (*Port, error) {
return nil, fmt.Errorf("%s, too many ports to use, maximum support 28 ports", errPayload.Error())
}
var portList []portReal
var portRange []utils.Range[uint16]
for _, p := range ports {
if p == "" {
continue
@ -84,23 +76,18 @@ func NewPort(port string, adapter string, isSource bool) (*Port, error) {
switch subPortsLen {
case 1:
portList = append(portList, portReal{int(portStart), -1})
portRange = append(portRange, *utils.NewRange(uint16(portStart), uint16(portStart)))
case 2:
portEnd, err := strconv.ParseUint(strings.Trim(subPorts[1], "[ ]"), 10, 16)
if err != nil {
return nil, errPayload
}
shouldReverse := portStart > portEnd
if shouldReverse {
portList = append(portList, portReal{int(portEnd), int(portStart)})
} else {
portList = append(portList, portReal{int(portStart), int(portEnd)})
}
portRange = append(portRange, *utils.NewRange(uint16(portStart), uint16(portEnd)))
}
}
if len(portList) == 0 {
if len(portRange) == 0 {
return nil, errPayload
}
@ -109,7 +96,7 @@ func NewPort(port string, adapter string, isSource bool) (*Port, error) {
adapter: adapter,
port: port,
isSource: isSource,
portList: portList,
portList: portRange,
}, nil
}

View File

@ -51,6 +51,10 @@ func containRange(r Range, preStart, preEnd int) bool {
func payloadToRule(subPayload string) (C.Rule, error) {
splitStr := strings.SplitN(subPayload, ",", 2)
if len(splitStr) < 2 {
return nil, fmt.Errorf("[%s] format is error", subPayload)
}
tp := splitStr[0]
payload := splitStr[1]
if tp == "NOT" || tp == "OR" || tp == "AND" {
@ -86,9 +90,9 @@ func parseRule(tp, payload string, params []string) (C.Rule, error) {
parsed, parseErr = RC.NewGEOIP(payload, "", noResolve)
case "IP-CIDR", "IP-CIDR6":
noResolve := RC.HasNoResolve(params)
parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRNoResolve(noResolve))
parsed, parseErr = RC.NewIPCIDR(payload, "", RC.WithIPCIDRNoResolve(noResolve))
case "SRC-IP-CIDR":
parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
parsed, parseErr = RC.NewIPCIDR(payload, "", RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
case "SRC-PORT":
parsed, parseErr = RC.NewPort(payload, "", true)
case "DST-PORT":
@ -108,7 +112,7 @@ func parseRule(tp, payload string, params []string) (C.Rule, error) {
case "NETWORK":
parsed, parseErr = RC.NewNetworkType(payload, "")
default:
parseErr = fmt.Errorf("unsupported rule type %s", tp)
parsed, parseErr = nil, fmt.Errorf("unsupported rule type %s", tp)
}
if parseErr != nil {
@ -146,6 +150,10 @@ func format(payload string) ([]Range, error) {
num++
stack.Push(sr)
} else if c == ')' {
if stack.Len() == 0 {
return nil, fmt.Errorf("missing '('")
}
sr := stack.Pop().(Range)
sr.end = i
subRanges = append(subRanges, sr)

49
rule/logic/logic_test.go Normal file
View File

@ -0,0 +1,49 @@
package logic
import (
"github.com/Dreamacro/clash/constant"
"github.com/stretchr/testify/assert"
"testing"
)
func TestAND(t *testing.T) {
and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
assert.Equal(t, nil, err)
assert.Equal(t, "DIRECT", and.adapter)
assert.Equal(t, false, and.ShouldResolveIP())
assert.Equal(t, true, and.Match(&constant.Metadata{
Host: "baidu.com",
AddrType: constant.AtypDomainName,
NetWork: constant.TCP,
DstPort: "20000",
}))
and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
assert.NotEqual(t, nil, err)
and, err = NewAND("((AND,(DOMAIN,baidu.com),(NETWORK,TCP)),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
assert.Equal(t, nil, err)
}
func TestNOT(t *testing.T) {
not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT")
assert.Equal(t, nil, err)
assert.Equal(t, false, not.Match(&constant.Metadata{
DstPort: "6100",
}))
_, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT")
assert.NotEqual(t, nil, err)
_, err = NewNOT("(())", "DIRECT")
assert.NotEqual(t, nil, err)
}
func TestOR(t *testing.T) {
or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
assert.Equal(t, nil, err)
assert.Equal(t, true, or.Match(&constant.Metadata{
NetWork: constant.TCP,
}))
assert.Equal(t, false, or.ShouldResolveIP())
}

View File

@ -24,11 +24,14 @@ func NewNOT(payload string, adapter string) (*NOT, error) {
return nil, err
}
if len(rule) < 1 {
return nil, fmt.Errorf("the parsed rule is empty")
if len(rule) > 1 {
return nil, fmt.Errorf("not rule can contain at most one rule")
}
if len(rule) > 0 {
not.rule = rule[0]
}
return not, nil
}
@ -37,7 +40,7 @@ func (not *NOT) RuleType() C.RuleType {
}
func (not *NOT) Match(metadata *C.Metadata) bool {
return !not.rule.Match(metadata)
return not.rule == nil || !not.rule.Match(metadata)
}
func (not *NOT) Adapter() string {
@ -49,5 +52,5 @@ func (not *NOT) Payload() string {
}
func (not *NOT) ShouldResolveIP() bool {
return not.rule.ShouldResolveIP()
return not.rule != nil && not.rule.ShouldResolveIP()
}

View File

@ -10,7 +10,7 @@ import (
type domainStrategy struct {
shouldResolveIP bool
count int
domainRules *trie.DomainTrie
domainRules *trie.DomainTrie[bool]
}
func (d *domainStrategy) Match(metadata *C.Metadata) bool {
@ -26,9 +26,9 @@ func (d *domainStrategy) ShouldResolveIP() bool {
}
func (d *domainStrategy) OnUpdate(rules []string) {
domainTrie := trie.New()
domainTrie := trie.New[bool]()
for _, rule := range rules {
err := domainTrie.Insert(rule, "")
err := domainTrie.Insert(rule, true)
if err != nil {
log.Warnln("invalid domain:[%s]", rule)
} else {

View File

@ -148,7 +148,7 @@ func (t *Trojan) PresetXTLSConn(conn net.Conn) (net.Conn, error) {
xtlsConn.DirectMode = true
}
} else {
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", t.option.Flow)
return conn, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", t.option.Flow)
}
}

View File

@ -14,6 +14,7 @@ import (
"github.com/Dreamacro/clash/component/nat"
P "github.com/Dreamacro/clash/component/process"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/sniffer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
icontext "github.com/Dreamacro/clash/context"
@ -36,6 +37,8 @@ var (
// default timeout for UDP session
udpTimeout = 60 * time.Second
snifferDispatcher *sniffer.SnifferDispatcher
)
func init() {
@ -88,6 +91,12 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid
configMux.Unlock()
}
func UpdateSniffer(dispatcher *sniffer.SnifferDispatcher) {
configMux.Lock()
sniffer.Dispatcher = *dispatcher
configMux.Unlock()
}
// Mode return current mode
func Mode() TunnelMode {
return mode
@ -149,7 +158,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
metadata.DNSMode = C.DNSFakeIP
} else if node := resolver.DefaultHosts.Search(host); node != nil {
// redir-host should lookup the hosts
metadata.DstIP = node.Data.(net.IP)
metadata.DstIP = node.Data.AsSlice()
}
} else if resolver.IsFakeIP(metadata.DstIP) {
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
@ -172,7 +181,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
return nil
}
func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
func resolveMetadata(_ C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
switch mode {
case Direct:
proxy = proxies["DIRECT"]
@ -208,7 +217,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
handle := func() bool {
pc := natTable.Get(key)
if pc != nil {
handleUDPToRemote(packet, pc, metadata)
_ = handleUDPToRemote(packet, pc, metadata)
return true
}
return false
@ -281,7 +290,9 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
}
func handleTCPConn(connCtx C.ConnContext) {
defer connCtx.Conn().Close()
defer func(conn net.Conn) {
_ = conn.Close()
}(connCtx.Conn())
metadata := connCtx.Metadata()
if !metadata.Valid() {
@ -294,6 +305,10 @@ func handleTCPConn(connCtx C.ConnContext) {
return
}
if sniffer.Dispatcher.Enable() {
sniffer.Dispatcher.TCPSniff(connCtx.Conn(), metadata)
}
proxy, rule, err := resolveMetadata(connCtx, metadata)
if err != nil {
log.Warnln("[Metadata] parse failed: %s", err.Error())
@ -312,7 +327,9 @@ func handleTCPConn(connCtx C.ConnContext) {
return
}
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule)
defer remoteConn.Close()
defer func(remoteConn C.Conn) {
_ = remoteConn.Close()
}(remoteConn)
switch true {
case rule != nil:
@ -345,8 +362,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
var resolved bool
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
ip := node.Data.(net.IP)
metadata.DstIP = ip
metadata.DstIP = node.Data.AsSlice()
resolved = true
}