Merge from remote branch

This commit is contained in:
yaling888
2022-02-23 01:00:27 +08:00
79 changed files with 1071 additions and 542 deletions

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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