Merge from remote branch
This commit is contained in:
@ -4,9 +4,9 @@ import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||
@ -27,14 +27,21 @@ func bindControl(ifaceIdx int, chain controlFn) controlFn {
|
||||
}
|
||||
}
|
||||
|
||||
return c.Control(func(fd uintptr) {
|
||||
var innerErr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
|
||||
innerErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
|
||||
case "tcp6", "udp6":
|
||||
unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx)
|
||||
innerErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx)
|
||||
}
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
err = innerErr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,9 +25,16 @@ func bindControl(ifaceName string, chain controlFn) controlFn {
|
||||
}
|
||||
}
|
||||
|
||||
return c.Control(func(fd uintptr) {
|
||||
unix.BindToDevice(int(fd), ifaceName)
|
||||
var innerErr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
innerErr = unix.BindToDevice(int(fd), ifaceName)
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
err = innerErr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,15 +58,15 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
|
||||
return nil
|
||||
}
|
||||
|
||||
local := 0
|
||||
local := uint64(0)
|
||||
if dialer.LocalAddr != nil {
|
||||
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
||||
if err == nil {
|
||||
local, _ = strconv.Atoi(port)
|
||||
local, _ = strconv.ParseUint(port, 10, 16)
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, destination, local)
|
||||
addr, err := lookupLocalAddr(ifaceName, network, destination, int(local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -82,9 +82,9 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
|
||||
port = "0"
|
||||
}
|
||||
|
||||
local, _ := strconv.Atoi(port)
|
||||
local, _ := strconv.ParseUint(port, 10, 16)
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, nil, local)
|
||||
addr, err := lookupLocalAddr(ifaceName, network, nil, int(local))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -36,12 +36,13 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
||||
}
|
||||
|
||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||
cfg := &config{}
|
||||
cfg := &option{
|
||||
interfaceName: DefaultInterface.Load(),
|
||||
routingMark: int(DefaultRoutingMark.Load()),
|
||||
}
|
||||
|
||||
if !cfg.skipDefault {
|
||||
for _, o := range DefaultOptions {
|
||||
o(cfg)
|
||||
}
|
||||
for _, o := range DefaultOptions {
|
||||
o(cfg)
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
@ -59,17 +60,21 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
||||
if cfg.addrReuse {
|
||||
addrReuseToListenConfig(lc)
|
||||
}
|
||||
if cfg.routingMark != 0 {
|
||||
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
||||
}
|
||||
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) {
|
||||
opt := &config{}
|
||||
opt := &option{
|
||||
interfaceName: DefaultInterface.Load(),
|
||||
routingMark: int(DefaultRoutingMark.Load()),
|
||||
}
|
||||
|
||||
if !opt.skipDefault {
|
||||
for _, o := range DefaultOptions {
|
||||
o(opt)
|
||||
}
|
||||
for _, o := range DefaultOptions {
|
||||
o(opt)
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
@ -82,6 +87,9 @@ func dialContext(ctx context.Context, network string, destination net.IP, port s
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||
}
|
||||
|
44
component/dialer/mark_linux.go
Normal file
44
component/dialer/mark_linux.go
Normal file
@ -0,0 +1,44 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
|
||||
dialer.Control = bindMarkToControl(mark, dialer.Control)
|
||||
}
|
||||
|
||||
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
|
||||
lc.Control = bindMarkToControl(mark, lc.Control)
|
||||
}
|
||||
|
||||
func bindMarkToControl(mark int, chain controlFn) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) (err error) {
|
||||
defer func() {
|
||||
if err == nil && chain != nil {
|
||||
err = chain(network, address, c)
|
||||
}
|
||||
}()
|
||||
|
||||
ipStr, _, err := net.SplitHostPort(address)
|
||||
if err == nil {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip != nil && !ip.IsGlobalUnicast() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return c.Control(func(fd uintptr) {
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
|
||||
case "tcp6", "udp6":
|
||||
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
27
component/dialer/mark_nonlinux.go
Normal file
27
component/dialer/mark_nonlinux.go
Normal file
@ -0,0 +1,27 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var printMarkWarnOnce sync.Once
|
||||
|
||||
func printMarkWarn() {
|
||||
printMarkWarnOnce.Do(func() {
|
||||
log.Warnln("Routing mark on socket is not supported on current platform")
|
||||
})
|
||||
}
|
||||
|
||||
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
|
||||
printMarkWarn()
|
||||
}
|
||||
|
||||
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
|
||||
printMarkWarn()
|
||||
}
|
@ -1,29 +1,35 @@
|
||||
package dialer
|
||||
|
||||
var DefaultOptions []Option
|
||||
import "go.uber.org/atomic"
|
||||
|
||||
type config struct {
|
||||
skipDefault bool
|
||||
var (
|
||||
DefaultOptions []Option
|
||||
DefaultInterface = atomic.NewString("")
|
||||
DefaultRoutingMark = atomic.NewInt32(0)
|
||||
)
|
||||
|
||||
type option struct {
|
||||
interfaceName string
|
||||
addrReuse bool
|
||||
routingMark int
|
||||
}
|
||||
|
||||
type Option func(opt *config)
|
||||
type Option func(opt *option)
|
||||
|
||||
func WithInterface(name string) Option {
|
||||
return func(opt *config) {
|
||||
return func(opt *option) {
|
||||
opt.interfaceName = name
|
||||
}
|
||||
}
|
||||
|
||||
func WithAddrReuse(reuse bool) Option {
|
||||
return func(opt *config) {
|
||||
return func(opt *option) {
|
||||
opt.addrReuse = reuse
|
||||
}
|
||||
}
|
||||
|
||||
func WithSkipDefault(skip bool) Option {
|
||||
return func(opt *config) {
|
||||
opt.skipDefault = skip
|
||||
func WithRoutingMark(mark int) Option {
|
||||
return func(opt *option) {
|
||||
opt.routingMark = mark
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,12 @@ func (c *cachefileStore) PutByIP(ip net.IP, host string) {
|
||||
c.cache.PutFakeip(ip.To4(), []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()))
|
||||
}
|
||||
|
||||
// Exist implements store.Exist
|
||||
func (c *cachefileStore) Exist(ip net.IP) bool {
|
||||
_, exist := c.GetByIP(ip)
|
||||
|
@ -46,6 +46,15 @@ func (m *memoryStore) PutByIP(ip net.IP, host string) {
|
||||
m.cache.Set(ipToUint(ip.To4()), 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))
|
||||
}
|
||||
m.cache.Delete(ipNum)
|
||||
}
|
||||
|
||||
// Exist implements store.Exist
|
||||
func (m *memoryStore) Exist(ip net.IP) bool {
|
||||
return m.cache.Exist(ipToUint(ip.To4()))
|
||||
|
@ -15,6 +15,7 @@ type store interface {
|
||||
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
|
||||
CloneTo(store)
|
||||
}
|
||||
@ -97,6 +98,9 @@ func (p *Pool) get(host string) net.IP {
|
||||
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)
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package fakeip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
@ -75,7 +76,7 @@ func TestPool_Basic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPool_CycleUsed(t *testing.T) {
|
||||
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
|
||||
_, ipnet, _ := net.ParseCIDR("192.168.0.1/29")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
@ -84,9 +85,15 @@ func TestPool_CycleUsed(t *testing.T) {
|
||||
defer os.Remove(tempfile)
|
||||
|
||||
for _, pool := range pools {
|
||||
first := pool.Lookup("foo.com")
|
||||
same := pool.Lookup("baz.com")
|
||||
assert.True(t, first.Equal(same))
|
||||
foo := pool.Lookup("foo.com")
|
||||
bar := pool.Lookup("bar.com")
|
||||
for i := 0; i < 3; 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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
package cachefile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
@ -90,6 +88,31 @@ func (c *CacheFile) PutFakeip(key, value []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CacheFile) DelFakeipPair(ip, host []byte) error {
|
||||
if c.DB == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Delete(ip)
|
||||
if len(host) > 0 {
|
||||
if err := bucket.Delete(host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CacheFile) GetFakeip(key []byte) []byte {
|
||||
if c.DB == nil {
|
||||
return nil
|
||||
@ -113,69 +136,30 @@ func (c *CacheFile) Close() error {
|
||||
return c.DB.Close()
|
||||
}
|
||||
|
||||
// TODO: remove migrateCache until 2022
|
||||
func migrateCache() {
|
||||
defer func() {
|
||||
options := bbolt.Options{Timeout: time.Second}
|
||||
db, err := bbolt.Open(C.Path.Cache(), fileMode, &options)
|
||||
switch err {
|
||||
case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch:
|
||||
if err = os.Remove(C.Path.Cache()); err != nil {
|
||||
log.Warnln("[CacheFile] remove invalid cache file error: %s", err.Error())
|
||||
break
|
||||
}
|
||||
log.Infoln("[CacheFile] remove invalid cache file and create new one")
|
||||
db, err = bbolt.Open(C.Path.Cache(), fileMode, &options)
|
||||
func initCache() {
|
||||
options := bbolt.Options{Timeout: time.Second}
|
||||
db, err := bbolt.Open(C.Path.Cache(), fileMode, &options)
|
||||
switch err {
|
||||
case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch:
|
||||
if err = os.Remove(C.Path.Cache()); err != nil {
|
||||
log.Warnln("[CacheFile] remove invalid cache file error: %s", err.Error())
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] can't open cache file: %s", err.Error())
|
||||
}
|
||||
|
||||
defaultCache = &CacheFile{
|
||||
DB: db,
|
||||
}
|
||||
}()
|
||||
|
||||
buf, err := os.ReadFile(C.Path.OldCache())
|
||||
log.Infoln("[CacheFile] remove invalid cache file and create new one")
|
||||
db, err = bbolt.Open(C.Path.Cache(), fileMode, &options)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
log.Warnln("[CacheFile] can't open cache file: %s", err.Error())
|
||||
}
|
||||
defer os.Remove(C.Path.OldCache())
|
||||
|
||||
// read old cache file
|
||||
type cache struct {
|
||||
Selected map[string]string
|
||||
defaultCache = &CacheFile{
|
||||
DB: db,
|
||||
}
|
||||
model := &cache{
|
||||
Selected: map[string]string{},
|
||||
}
|
||||
bufReader := bytes.NewBuffer(buf)
|
||||
gob.NewDecoder(bufReader).Decode(model)
|
||||
|
||||
// write to new cache file
|
||||
db, err := bbolt.Open(C.Path.Cache(), fileMode, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
db.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := t.CreateBucketIfNotExists(bucketSelected)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for group, selected := range model.Selected {
|
||||
if err := bucket.Put([]byte(group), []byte(selected)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Cache return singleton of CacheFile
|
||||
func Cache() *CacheFile {
|
||||
initOnce.Do(migrateCache)
|
||||
initOnce.Do(initCache)
|
||||
|
||||
return defaultCache
|
||||
}
|
||||
|
@ -109,13 +109,13 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
||||
}
|
||||
|
||||
if c := node.getChild(parts[len(parts)-1]); c != nil {
|
||||
if n := t.search(c, parts[:len(parts)-1]); n != nil {
|
||||
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
if c := node.getChild(wildcard); c != nil {
|
||||
if n := t.search(c, parts[:len(parts)-1]); n != nil {
|
||||
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
@ -97,3 +97,11 @@ func TestTrie_Boundary(t *testing.T) {
|
||||
assert.NotNil(t, tree.Insert("..dev", localIP))
|
||||
assert.Nil(t, tree.Search("dev"))
|
||||
}
|
||||
|
||||
func TestTrie_WildcardBoundary(t *testing.T) {
|
||||
tree := New()
|
||||
tree.Insert("+.*", localIP)
|
||||
tree.Insert("stun.*.*.*", localIP)
|
||||
|
||||
assert.NotNil(t, tree.Search("example.com"))
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package trie
|
||||
|
||||
// Node is the trie's node
|
||||
type Node struct {
|
||||
Data interface{}
|
||||
children map[string]*Node
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func (n *Node) getChild(s string) *Node {
|
||||
|
Reference in New Issue
Block a user