Feature: support fakeip
This commit is contained in:
parent
9f955015d1
commit
9c315339fd
9 changed files with 180 additions and 4 deletions
|
@ -113,7 +113,7 @@ experimental:
|
||||||
# enable: true # set true to enable dns (default is false)
|
# enable: true # set true to enable dns (default is false)
|
||||||
# ipv6: false # default is false
|
# ipv6: false # default is false
|
||||||
# listen: 0.0.0.0:53
|
# listen: 0.0.0.0:53
|
||||||
# enhanced-mode: redir-host
|
# enhanced-mode: redir-host # or fake-ip
|
||||||
# nameserver:
|
# nameserver:
|
||||||
# - 114.114.114.114
|
# - 114.114.114.114
|
||||||
# - tls://dns.rubyfish.cn:853 # dns over tls
|
# - tls://dns.rubyfish.cn:853 # dns over tls
|
||||||
|
|
50
component/fakeip/pool.go
Normal file
50
component/fakeip/pool.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package fakeip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pool is a implementation about fake ip generator without storage
|
||||||
|
type Pool struct {
|
||||||
|
max uint32
|
||||||
|
min uint32
|
||||||
|
offset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return a new fake ip
|
||||||
|
func (p *Pool) Get() net.IP {
|
||||||
|
ip := uintToIP(p.min + p.offset)
|
||||||
|
p.offset = (p.offset + 1) % (p.max - p.min)
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
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.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// New return Pool instance
|
||||||
|
func New(ipnet *net.IPNet) (*Pool, error) {
|
||||||
|
min := ipToUint(ipnet.IP) + 1
|
||||||
|
|
||||||
|
ones, bits := ipnet.Mask.Size()
|
||||||
|
total := 1<<uint(bits-ones) - 2
|
||||||
|
|
||||||
|
if total <= 0 {
|
||||||
|
return nil, errors.New("ipnet don't have valid ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
max := min + uint32(total)
|
||||||
|
return &Pool{
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
}, nil
|
||||||
|
}
|
44
component/fakeip/pool_test.go
Normal file
44
component/fakeip/pool_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package fakeip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPool_Basic(t *testing.T) {
|
||||||
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
|
||||||
|
pool, _ := New(ipnet)
|
||||||
|
|
||||||
|
first := pool.Get()
|
||||||
|
last := pool.Get()
|
||||||
|
|
||||||
|
if !first.Equal(net.IP{192, 168, 0, 1}) {
|
||||||
|
t.Error("should get right first ip, instead of", first.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !last.Equal(net.IP{192, 168, 0, 2}) {
|
||||||
|
t.Error("should get right last ip, instead of", first.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPool_Cycle(t *testing.T) {
|
||||||
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/30")
|
||||||
|
pool, _ := New(ipnet)
|
||||||
|
|
||||||
|
first := pool.Get()
|
||||||
|
pool.Get()
|
||||||
|
same := pool.Get()
|
||||||
|
|
||||||
|
if !first.Equal(same) {
|
||||||
|
t.Error("should return same ip", first.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPool_Error(t *testing.T) {
|
||||||
|
_, ipnet, _ := net.ParseCIDR("192.168.0.1/31")
|
||||||
|
_, err := New(ipnet)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Error("should return err")
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/outbound"
|
adapters "github.com/Dreamacro/clash/adapters/outbound"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
@ -41,6 +42,7 @@ type DNS struct {
|
||||||
Fallback []dns.NameServer `yaml:"fallback"`
|
Fallback []dns.NameServer `yaml:"fallback"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||||
|
FakeIPRange *fakeip.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Experimental config
|
// Experimental config
|
||||||
|
@ -64,6 +66,7 @@ type rawDNS struct {
|
||||||
Fallback []string `yaml:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||||
|
FakeIPRange string `yaml:"fake-ip-range"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
|
@ -109,7 +112,8 @@ func readConfig(path string) (*rawConfig, error) {
|
||||||
IgnoreResolveFail: true,
|
IgnoreResolveFail: true,
|
||||||
},
|
},
|
||||||
DNS: rawDNS{
|
DNS: rawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
|
FakeIPRange: "198.18.0.1/16",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal([]byte(data), &rawConfig)
|
err = yaml.Unmarshal([]byte(data), &rawConfig)
|
||||||
|
@ -466,5 +470,18 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.EnhancedMode == dns.FAKEIP {
|
||||||
|
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pool, err := fakeip.New(ipnet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCfg.FakeIPRange = pool
|
||||||
|
}
|
||||||
|
|
||||||
return dnsCfg, nil
|
return dnsCfg, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/common/picker"
|
"github.com/Dreamacro/clash/common/picker"
|
||||||
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
|
@ -28,6 +29,8 @@ var (
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
ipv6 bool
|
ipv6 bool
|
||||||
mapping bool
|
mapping bool
|
||||||
|
fakeip bool
|
||||||
|
pool *fakeip.Pool
|
||||||
fallback []*nameserver
|
fallback []*nameserver
|
||||||
main []*nameserver
|
main []*nameserver
|
||||||
cache *cache.Cache
|
cache *cache.Cache
|
||||||
|
@ -209,6 +212,10 @@ func (r *Resolver) IsMapping() bool {
|
||||||
return r.mapping
|
return r.mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) IsFakeIP() bool {
|
||||||
|
return r.fakeip
|
||||||
|
}
|
||||||
|
|
||||||
type NameServer struct {
|
type NameServer struct {
|
||||||
Net string
|
Net string
|
||||||
Addr string
|
Addr string
|
||||||
|
@ -223,6 +230,7 @@ type Config struct {
|
||||||
Main, Fallback []NameServer
|
Main, Fallback []NameServer
|
||||||
IPv6 bool
|
IPv6 bool
|
||||||
EnhancedMode EnhancedMode
|
EnhancedMode EnhancedMode
|
||||||
|
Pool *fakeip.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func transform(servers []NameServer) []*nameserver {
|
func transform(servers []NameServer) []*nameserver {
|
||||||
|
@ -252,6 +260,8 @@ func New(config Config) *Resolver {
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
cache: cache.New(time.Second * 60),
|
cache: cache.New(time.Second * 60),
|
||||||
mapping: config.EnhancedMode == MAPPING,
|
mapping: config.EnhancedMode == MAPPING,
|
||||||
|
fakeip: config.EnhancedMode == FAKEIP,
|
||||||
|
pool: config.Pool,
|
||||||
}
|
}
|
||||||
if config.Fallback != nil {
|
if config.Fallback != nil {
|
||||||
r.fallback = transform(config.Fallback)
|
r.fallback = transform(config.Fallback)
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/miekg/dns"
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
address string
|
address string
|
||||||
server = &Server{}
|
server = &Server{}
|
||||||
|
|
||||||
|
dnsDefaultTTL uint32 = 600
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
@ -19,6 +24,17 @@ type Server struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
||||||
|
if s.r.IsFakeIP() {
|
||||||
|
msg, err := s.handleFakeIP(r)
|
||||||
|
if err != nil {
|
||||||
|
D.HandleFailed(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg.SetReply(r)
|
||||||
|
w.WriteMsg(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
msg, err := s.r.Exchange(r)
|
msg, err := s.r.Exchange(r)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -34,6 +50,40 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
||||||
w.WriteMsg(msg)
|
w.WriteMsg(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
if len(r.Question) == 0 {
|
||||||
|
err = errors.New("should have one question at least")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
q := r.Question[0]
|
||||||
|
|
||||||
|
cache, expireTime := s.r.cache.GetWithExpire("fakeip:" + q.String())
|
||||||
|
if cache != nil {
|
||||||
|
msg = cache.(*D.Msg).Copy()
|
||||||
|
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
defer func() {
|
||||||
|
if msg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg)
|
||||||
|
putMsgToCache(s.r.cache, ip.String(), msg)
|
||||||
|
}()
|
||||||
|
|
||||||
|
rr := &D.A{}
|
||||||
|
rr.Hdr = dns.RR_Header{Name: r.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: dnsDefaultTTL}
|
||||||
|
ip = s.r.pool.Get()
|
||||||
|
rr.A = ip
|
||||||
|
msg = r.Copy()
|
||||||
|
msg.Answer = []D.RR{rr}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) setReslover(r *Resolver) {
|
func (s *Server) setReslover(r *Resolver) {
|
||||||
s.r = r
|
s.r = r
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (e EnhancedMode) String() string {
|
||||||
case NORMAL:
|
case NORMAL:
|
||||||
return "normal"
|
return "normal"
|
||||||
case FAKEIP:
|
case FAKEIP:
|
||||||
return "fakeip"
|
return "fake-ip"
|
||||||
case MAPPING:
|
case MAPPING:
|
||||||
return "redir-host"
|
return "redir-host"
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -57,6 +57,7 @@ func updateDNS(c *config.DNS) {
|
||||||
Fallback: c.Fallback,
|
Fallback: c.Fallback,
|
||||||
IPv6: c.IPv6,
|
IPv6: c.IPv6,
|
||||||
EnhancedMode: c.EnhancedMode,
|
EnhancedMode: c.EnhancedMode,
|
||||||
|
Pool: c.FakeIPRange,
|
||||||
})
|
})
|
||||||
T.Instance().SetResolver(r)
|
T.Instance().SetResolver(r)
|
||||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
||||||
|
|
|
@ -118,7 +118,7 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
|
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
|
||||||
return t.hasResolver() && t.resolver.IsMapping() && metadata.Host == "" && metadata.IP != nil
|
return t.hasResolver() && (t.resolver.IsMapping() || t.resolver.IsFakeIP()) && metadata.Host == "" && metadata.IP != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||||
|
@ -130,11 +130,15 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preprocess enhanced-mode metadata
|
||||||
if t.needLookupIP(metadata) {
|
if t.needLookupIP(metadata) {
|
||||||
host, exist := t.resolver.IPToHost(*metadata.IP)
|
host, exist := t.resolver.IPToHost(*metadata.IP)
|
||||||
if exist {
|
if exist {
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
metadata.AddrType = C.AtypDomainName
|
metadata.AddrType = C.AtypDomainName
|
||||||
|
if t.resolver.IsFakeIP() {
|
||||||
|
metadata.IP = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue