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)
|
||||
# ipv6: false # default is false
|
||||
# listen: 0.0.0.0:53
|
||||
# enhanced-mode: redir-host
|
||||
# enhanced-mode: redir-host # or fake-ip
|
||||
# nameserver:
|
||||
# - 114.114.114.114
|
||||
# - 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"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
@ -41,6 +42,7 @@ type DNS struct {
|
|||
Fallback []dns.NameServer `yaml:"fallback"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||
FakeIPRange *fakeip.Pool
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
|
@ -64,6 +66,7 @@ type rawDNS struct {
|
|||
Fallback []string `yaml:"fallback"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||
FakeIPRange string `yaml:"fake-ip-range"`
|
||||
}
|
||||
|
||||
type rawConfig struct {
|
||||
|
@ -110,6 +113,7 @@ func readConfig(path string) (*rawConfig, error) {
|
|||
},
|
||||
DNS: rawDNS{
|
||||
Enable: false,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
},
|
||||
}
|
||||
err = yaml.Unmarshal([]byte(data), &rawConfig)
|
||||
|
@ -466,5 +470,18 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/picker"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
|
@ -28,6 +29,8 @@ var (
|
|||
type Resolver struct {
|
||||
ipv6 bool
|
||||
mapping bool
|
||||
fakeip bool
|
||||
pool *fakeip.Pool
|
||||
fallback []*nameserver
|
||||
main []*nameserver
|
||||
cache *cache.Cache
|
||||
|
@ -209,6 +212,10 @@ func (r *Resolver) IsMapping() bool {
|
|||
return r.mapping
|
||||
}
|
||||
|
||||
func (r *Resolver) IsFakeIP() bool {
|
||||
return r.fakeip
|
||||
}
|
||||
|
||||
type NameServer struct {
|
||||
Net string
|
||||
Addr string
|
||||
|
@ -223,6 +230,7 @@ type Config struct {
|
|||
Main, Fallback []NameServer
|
||||
IPv6 bool
|
||||
EnhancedMode EnhancedMode
|
||||
Pool *fakeip.Pool
|
||||
}
|
||||
|
||||
func transform(servers []NameServer) []*nameserver {
|
||||
|
@ -252,6 +260,8 @@ func New(config Config) *Resolver {
|
|||
ipv6: config.IPv6,
|
||||
cache: cache.New(time.Second * 60),
|
||||
mapping: config.EnhancedMode == MAPPING,
|
||||
fakeip: config.EnhancedMode == FAKEIP,
|
||||
pool: config.Pool,
|
||||
}
|
||||
if config.Fallback != nil {
|
||||
r.fallback = transform(config.Fallback)
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/miekg/dns"
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var (
|
||||
address string
|
||||
server = &Server{}
|
||||
|
||||
dnsDefaultTTL uint32 = 600
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
@ -19,6 +24,17 @@ type Server struct {
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
|
@ -34,6 +50,40 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.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) {
|
||||
s.r = r
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ func (e EnhancedMode) String() string {
|
|||
case NORMAL:
|
||||
return "normal"
|
||||
case FAKEIP:
|
||||
return "fakeip"
|
||||
return "fake-ip"
|
||||
case MAPPING:
|
||||
return "redir-host"
|
||||
default:
|
||||
|
|
|
@ -57,6 +57,7 @@ func updateDNS(c *config.DNS) {
|
|||
Fallback: c.Fallback,
|
||||
IPv6: c.IPv6,
|
||||
EnhancedMode: c.EnhancedMode,
|
||||
Pool: c.FakeIPRange,
|
||||
})
|
||||
T.Instance().SetResolver(r)
|
||||
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 {
|
||||
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) {
|
||||
|
@ -130,11 +130,15 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
|||
return
|
||||
}
|
||||
|
||||
// preprocess enhanced-mode metadata
|
||||
if t.needLookupIP(metadata) {
|
||||
host, exist := t.resolver.IPToHost(*metadata.IP)
|
||||
if exist {
|
||||
metadata.Host = host
|
||||
metadata.AddrType = C.AtypDomainName
|
||||
if t.resolver.IsFakeIP() {
|
||||
metadata.IP = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue