Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
34338e7107 | |||
57fdd223f1 | |||
bc3fc0c840 | |||
662038e40e | |||
53528f8275 | |||
1c792b46c9 | |||
2417cfda12 | |||
aa3516ca24 | |||
bcf5b21208 | |||
ba5eefb6dd | |||
407de7388c | |||
6adafde9a0 | |||
cba548114f | |||
016e7bd0b4 | |||
ad13ad8dba | |||
89168e6c96 | |||
a4b8e286db | |||
e837470a6a | |||
b589754485 | |||
0eccbb023c | |||
71a08ad8e2 | |||
0d4a999707 |
@ -18,6 +18,4 @@ RUN apk add --no-cache ca-certificates
|
|||||||
COPY --from=builder /Country.mmdb /root/.config/clash/
|
COPY --from=builder /Country.mmdb /root/.config/clash/
|
||||||
COPY --from=builder /clash /
|
COPY --from=builder /clash /
|
||||||
|
|
||||||
EXPOSE 7890 7891
|
|
||||||
|
|
||||||
ENTRYPOINT ["/clash"]
|
ENTRYPOINT ["/clash"]
|
||||||
|
2
Makefile
2
Makefile
@ -1,6 +1,6 @@
|
|||||||
NAME=clash
|
NAME=clash
|
||||||
BINDIR=bin
|
BINDIR=bin
|
||||||
VERSION=$(shell git describe --tags || echo "unkown version")
|
VERSION=$(shell git describe --tags || echo "unknown version")
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||||
|
10
README.md
10
README.md
@ -64,7 +64,7 @@ If you have Docker installed, you can run clash directly using `docker-compose`.
|
|||||||
|
|
||||||
The default configuration directory is `$HOME/.config/clash`
|
The default configuration directory is `$HOME/.config/clash`
|
||||||
|
|
||||||
The name of the configuration file is `config.yml`
|
The name of the configuration file is `config.yaml`
|
||||||
|
|
||||||
If you want to use another directory, you can use `-d` to control the configuration directory
|
If you want to use another directory, you can use `-d` to control the configuration directory
|
||||||
|
|
||||||
@ -107,7 +107,12 @@ external-controller: 127.0.0.1:9090
|
|||||||
|
|
||||||
# experimental feature
|
# experimental feature
|
||||||
experimental:
|
experimental:
|
||||||
ignore-resolve-fail: true # ignore dns reslove fail, default value is true
|
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
|
||||||
|
|
||||||
|
# authentication of local SOCKS5/HTTP(S) server
|
||||||
|
# authentication:
|
||||||
|
# - "user1:pass1"
|
||||||
|
# - "user2:pass2"
|
||||||
|
|
||||||
# dns:
|
# dns:
|
||||||
# enable: true # set true to enable dns (default is false)
|
# enable: true # set true to enable dns (default is false)
|
||||||
@ -118,6 +123,7 @@ experimental:
|
|||||||
# 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
|
||||||
|
# - https://1.1.1.1/dns-query # dns over https
|
||||||
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
|
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
|
||||||
# - tcp://1.1.1.1
|
# - tcp://1.1.1.1
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) {
|
|||||||
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
|
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := net.DialTimeout("tcp", address, tcpTimeout)
|
c, err := dialTimeout("tcp", address, tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error)
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort))
|
addr, err := resolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ type HttpOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) {
|
func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := net.DialTimeout("tcp", h.addr, tcpTimeout)
|
c, err := dialTimeout("tcp", h.addr, tcpTimeout)
|
||||||
if err == nil && h.tls {
|
if err == nil && h.tls {
|
||||||
cc := tls.Client(c, h.tlsConfig)
|
cc := tls.Client(c, h.tlsConfig)
|
||||||
err = cc.Handshake()
|
err = cc.Handshake()
|
||||||
|
@ -4,15 +4,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
*Base
|
*Base
|
||||||
selected C.Proxy
|
selected C.Proxy
|
||||||
proxies map[string]C.Proxy
|
proxies map[string]C.Proxy
|
||||||
|
proxyList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectorOption struct {
|
type SelectorOption struct {
|
||||||
@ -33,15 +33,10 @@ func (s *Selector) SupportUDP() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
|
||||||
for k := range s.proxies {
|
|
||||||
all = append(all, k)
|
|
||||||
}
|
|
||||||
sort.Strings(all)
|
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"type": s.Type().String(),
|
"type": s.Type().String(),
|
||||||
"now": s.Now(),
|
"now": s.Now(),
|
||||||
"all": all,
|
"all": s.proxyList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,8 +59,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapping := make(map[string]C.Proxy)
|
mapping := make(map[string]C.Proxy)
|
||||||
for _, proxy := range proxies {
|
proxyList := make([]string, len(proxies))
|
||||||
|
for idx, proxy := range proxies {
|
||||||
mapping[proxy.Name()] = proxy
|
mapping[proxy.Name()] = proxy
|
||||||
|
proxyList[idx] = proxy.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &Selector{
|
s := &Selector{
|
||||||
@ -73,8 +70,9 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
|
|||||||
name: name,
|
name: name,
|
||||||
tp: C.Selector,
|
tp: C.Selector,
|
||||||
},
|
},
|
||||||
proxies: mapping,
|
proxies: mapping,
|
||||||
selected: proxies[0],
|
selected: proxies[0],
|
||||||
|
proxyList: proxyList,
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ type v2rayObfsOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) {
|
func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
|
c, err := dialTimeout("tcp", ss.server, tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr,
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", ss.server)
|
addr, err := resolveUDPAddr("udp", ss.server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -137,6 +137,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error())
|
return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Mode != "tls" && opts.Mode != "http" {
|
||||||
|
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
|
||||||
|
}
|
||||||
obfsMode = opts.Mode
|
obfsMode = opts.Mode
|
||||||
obfsOption = &opts
|
obfsOption = &opts
|
||||||
} else if option.Plugin == "v2ray-plugin" {
|
} else if option.Plugin == "v2ray-plugin" {
|
||||||
@ -144,7 +148,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error())
|
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Mode != "websocket" {
|
||||||
|
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
|
||||||
|
}
|
||||||
obfsMode = opts.Mode
|
obfsMode = opts.Mode
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if opts.TLS {
|
if opts.TLS {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
|
@ -32,7 +32,7 @@ type Socks5Option struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) {
|
func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
|
c, err := dialTimeout("tcp", ss.addr, tcpTimeout)
|
||||||
|
|
||||||
if err == nil && ss.tls {
|
if err == nil && ss.tls {
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
@ -58,7 +58,7 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
|
func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
|
||||||
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout)
|
c, err := dialTimeout("tcp", ss.addr, tcpTimeout)
|
||||||
|
|
||||||
if err == nil && ss.tls {
|
if err == nil && ss.tls {
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -56,7 +55,6 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
|||||||
for _, proxy := range u.proxies {
|
for _, proxy := range u.proxies {
|
||||||
all = append(all, proxy.Name())
|
all = append(all, proxy.Name())
|
||||||
}
|
}
|
||||||
sort.Strings(all)
|
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"type": u.Type().String(),
|
"type": u.Type().String(),
|
||||||
"now": u.Now(),
|
"now": u.Now(),
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -96,3 +97,30 @@ func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|||||||
n, err := fuc.Conn.Read(b)
|
n, err := fuc.Conn.Read(b)
|
||||||
return n, fuc.RemoteAddr(), err
|
return n, fuc.RemoteAddr(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := dns.ResolveIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.DialTimeout(network, net.JoinHostPort(ip.String(), port), timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := dns.ResolveIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ type VmessOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := net.DialTimeout("tcp", v.server, tcpTimeout)
|
c, err := dialTimeout("tcp", v.server, tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error", v.server)
|
return nil, fmt.Errorf("%s connect error", v.server)
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
|
func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
|
||||||
c, err := net.DialTimeout("tcp", v.server, tcpTimeout)
|
c, err := dialTimeout("tcp", v.server, tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("%s connect error", v.server)
|
return nil, nil, fmt.Errorf("%s connect error", v.server)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ package murmur3
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"hash"
|
"hash"
|
||||||
|
"math/bits"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,11 +55,11 @@ func (d *digest32) bmix(p []byte) (tail []byte) {
|
|||||||
k1 := *(*uint32)(unsafe.Pointer(&p[i*4]))
|
k1 := *(*uint32)(unsafe.Pointer(&p[i*4]))
|
||||||
|
|
||||||
k1 *= c1_32
|
k1 *= c1_32
|
||||||
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
k1 = bits.RotateLeft32(k1, 15)
|
||||||
k1 *= c2_32
|
k1 *= c2_32
|
||||||
|
|
||||||
h1 ^= k1
|
h1 ^= k1
|
||||||
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
|
h1 = bits.RotateLeft32(h1, 13)
|
||||||
h1 = h1*4 + h1 + 0xe6546b64
|
h1 = h1*4 + h1 + 0xe6546b64
|
||||||
}
|
}
|
||||||
d.h1 = h1
|
d.h1 = h1
|
||||||
@ -80,7 +81,7 @@ func (d *digest32) Sum32() (h1 uint32) {
|
|||||||
case 1:
|
case 1:
|
||||||
k1 ^= uint32(d.tail[0])
|
k1 ^= uint32(d.tail[0])
|
||||||
k1 *= c1_32
|
k1 *= c1_32
|
||||||
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
k1 = bits.RotateLeft32(k1, 15)
|
||||||
k1 *= c2_32
|
k1 *= c2_32
|
||||||
h1 ^= k1
|
h1 ^= k1
|
||||||
}
|
}
|
||||||
@ -102,20 +103,15 @@ func Sum32WithSeed(data []byte, seed uint32) uint32 {
|
|||||||
h1 := seed
|
h1 := seed
|
||||||
|
|
||||||
nblocks := len(data) / 4
|
nblocks := len(data) / 4
|
||||||
var p uintptr
|
for i := 0; i < nblocks; i++ {
|
||||||
if len(data) > 0 {
|
k1 := *(*uint32)(unsafe.Pointer(&data[i*4]))
|
||||||
p = uintptr(unsafe.Pointer(&data[0]))
|
|
||||||
}
|
|
||||||
p1 := p + uintptr(4*nblocks)
|
|
||||||
for ; p < p1; p += 4 {
|
|
||||||
k1 := *(*uint32)(unsafe.Pointer(p))
|
|
||||||
|
|
||||||
k1 *= c1_32
|
k1 *= c1_32
|
||||||
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
k1 = bits.RotateLeft32(k1, 15)
|
||||||
k1 *= c2_32
|
k1 *= c2_32
|
||||||
|
|
||||||
h1 ^= k1
|
h1 ^= k1
|
||||||
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
|
h1 = bits.RotateLeft32(h1, 13)
|
||||||
h1 = h1*4 + h1 + 0xe6546b64
|
h1 = h1*4 + h1 + 0xe6546b64
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +128,7 @@ func Sum32WithSeed(data []byte, seed uint32) uint32 {
|
|||||||
case 1:
|
case 1:
|
||||||
k1 ^= uint32(tail[0])
|
k1 ^= uint32(tail[0])
|
||||||
k1 *= c1_32
|
k1 *= c1_32
|
||||||
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
k1 = bits.RotateLeft32(k1, 15)
|
||||||
k1 *= c2_32
|
k1 *= c2_32
|
||||||
h1 ^= k1
|
h1 ^= k1
|
||||||
}
|
}
|
||||||
|
46
component/auth/auth.go
Normal file
46
component/auth/auth.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authenticator interface {
|
||||||
|
Verify(user string, pass string) bool
|
||||||
|
Users() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthUser struct {
|
||||||
|
User string
|
||||||
|
Pass string
|
||||||
|
}
|
||||||
|
|
||||||
|
type inMemoryAuthenticator struct {
|
||||||
|
storage *sync.Map
|
||||||
|
usernames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
|
||||||
|
realPass, ok := au.storage.Load(user)
|
||||||
|
return ok && realPass == pass
|
||||||
|
}
|
||||||
|
|
||||||
|
func (au *inMemoryAuthenticator) Users() []string { return au.usernames }
|
||||||
|
|
||||||
|
func NewAuthenticator(users []AuthUser) Authenticator {
|
||||||
|
if len(users) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
au := &inMemoryAuthenticator{storage: &sync.Map{}}
|
||||||
|
for _, user := range users {
|
||||||
|
au.storage.Store(user.User, user.Pass)
|
||||||
|
}
|
||||||
|
usernames := make([]string, 0, len(users))
|
||||||
|
au.storage.Range(func(key, value interface{}) bool {
|
||||||
|
usernames = append(usernames, key.(string))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
au.usernames = usernames
|
||||||
|
|
||||||
|
return au
|
||||||
|
}
|
@ -3,6 +3,7 @@ package fakeip
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pool is a implementation about fake ip generator without storage
|
// Pool is a implementation about fake ip generator without storage
|
||||||
@ -10,10 +11,13 @@ type Pool struct {
|
|||||||
max uint32
|
max uint32
|
||||||
min uint32
|
min uint32
|
||||||
offset uint32
|
offset uint32
|
||||||
|
mux *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get return a new fake ip
|
// Get return a new fake ip
|
||||||
func (p *Pool) Get() net.IP {
|
func (p *Pool) Get() net.IP {
|
||||||
|
p.mux.Lock()
|
||||||
|
defer p.mux.Unlock()
|
||||||
ip := uintToIP(p.min + p.offset)
|
ip := uintToIP(p.min + p.offset)
|
||||||
p.offset = (p.offset + 1) % (p.max - p.min)
|
p.offset = (p.offset + 1) % (p.max - p.min)
|
||||||
return ip
|
return ip
|
||||||
@ -46,5 +50,6 @@ func New(ipnet *net.IPNet) (*Pool, error) {
|
|||||||
return &Pool{
|
return &Pool{
|
||||||
min: min,
|
min: min,
|
||||||
max: max,
|
max: max,
|
||||||
|
mux: &sync.Mutex{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error represents a SOCKS error
|
// Error represents a SOCKS error
|
||||||
@ -35,6 +37,9 @@ const (
|
|||||||
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
// MaxAddrLen is the maximum size of SOCKS address in bytes.
|
||||||
const MaxAddrLen = 1 + 1 + 255 + 2
|
const MaxAddrLen = 1 + 1 + 255 + 2
|
||||||
|
|
||||||
|
// MaxAuthLen is the maximum size of user/password field in SOCKS5 Auth
|
||||||
|
const MaxAuthLen = 255
|
||||||
|
|
||||||
// Addr represents a SOCKS address as defined in RFC 1928 section 5.
|
// Addr represents a SOCKS address as defined in RFC 1928 section 5.
|
||||||
type Addr = []byte
|
type Addr = []byte
|
||||||
|
|
||||||
@ -50,13 +55,16 @@ const (
|
|||||||
ErrAddressNotSupported = Error(8)
|
ErrAddressNotSupported = Error(8)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Auth errors used to return a specific "Auth failed" error
|
||||||
|
var ErrAuth = errors.New("auth failed")
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side.
|
// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side.
|
||||||
func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) {
|
func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, err error) {
|
||||||
// Read RFC 1928 for request and reply structure and sizes.
|
// Read RFC 1928 for request and reply structure and sizes.
|
||||||
buf := make([]byte, MaxAddrLen)
|
buf := make([]byte, MaxAddrLen)
|
||||||
// read VER, NMETHODS, METHODS
|
// read VER, NMETHODS, METHODS
|
||||||
@ -67,10 +75,64 @@ func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) {
|
|||||||
if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
|
if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// write VER METHOD
|
// write VER METHOD
|
||||||
if _, err = rw.Write([]byte{5, 0}); err != nil {
|
if authenticator != nil {
|
||||||
return
|
if _, err = rw.Write([]byte{5, 2}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get header
|
||||||
|
header := make([]byte, 2)
|
||||||
|
if _, err = io.ReadFull(rw, header); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authBuf := make([]byte, MaxAuthLen)
|
||||||
|
// Get username
|
||||||
|
userLen := int(header[1])
|
||||||
|
if userLen <= 0 {
|
||||||
|
rw.Write([]byte{1, 1})
|
||||||
|
err = ErrAuth
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := string(authBuf[:userLen])
|
||||||
|
|
||||||
|
// Get password
|
||||||
|
if _, err = rw.Read(header[:1]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
passLen := int(header[0])
|
||||||
|
if passLen <= 0 {
|
||||||
|
rw.Write([]byte{1, 1})
|
||||||
|
err = ErrAuth
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.ReadFull(rw, authBuf[:passLen]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pass := string(authBuf[:passLen])
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if ok := authenticator.Verify(string(user), string(pass)); !ok {
|
||||||
|
rw.Write([]byte{1, 1})
|
||||||
|
err = ErrAuth
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response auth state
|
||||||
|
if _, err = rw.Write([]byte{1, 0}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err = rw.Write([]byte{5, 0}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
||||||
if _, err = io.ReadFull(rw, buf[:3]); err != nil {
|
if _, err = io.ReadFull(rw, buf[:3]); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -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/auth"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"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"
|
||||||
@ -26,6 +27,7 @@ type General struct {
|
|||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
SocksPort int `json:"socks-port"`
|
SocksPort int `json:"socks-port"`
|
||||||
RedirPort int `json:"redir-port"`
|
RedirPort int `json:"redir-port"`
|
||||||
|
Authentication []string `json:"authentication"`
|
||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
Mode T.Mode `json:"mode"`
|
Mode T.Mode `json:"mode"`
|
||||||
LogLevel log.LogLevel `json:"log-level"`
|
LogLevel log.LogLevel `json:"log-level"`
|
||||||
@ -56,6 +58,7 @@ type Config struct {
|
|||||||
DNS *DNS
|
DNS *DNS
|
||||||
Experimental *Experimental
|
Experimental *Experimental
|
||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
|
Users []auth.AuthUser
|
||||||
Proxies map[string]C.Proxy
|
Proxies map[string]C.Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +76,7 @@ type rawConfig struct {
|
|||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
RedirPort int `yaml:"redir-port"`
|
RedirPort int `yaml:"redir-port"`
|
||||||
|
Authentication []string `yaml:"authentication"`
|
||||||
AllowLan bool `yaml:"allow-lan"`
|
AllowLan bool `yaml:"allow-lan"`
|
||||||
Mode T.Mode `yaml:"mode"`
|
Mode T.Mode `yaml:"mode"`
|
||||||
LogLevel log.LogLevel `yaml:"log-level"`
|
LogLevel log.LogLevel `yaml:"log-level"`
|
||||||
@ -87,27 +91,43 @@ type rawConfig struct {
|
|||||||
Rule []string `yaml:"Rule"`
|
Rule []string `yaml:"Rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forward compatibility before 1.0
|
||||||
|
func readRawConfig(path string) ([]byte, error) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err == nil && len(data) != 0 {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.Ext(path) != ".yaml" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path[:len(path)-5] + ".yml"
|
||||||
|
return ioutil.ReadFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
func readConfig(path string) (*rawConfig, error) {
|
func readConfig(path string) (*rawConfig, error) {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := readRawConfig(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil, fmt.Errorf("Configuration file %s is empty", C.Path.Config())
|
return nil, fmt.Errorf("Configuration file %s is empty", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// config with some default value
|
// config with some default value
|
||||||
rawConfig := &rawConfig{
|
rawConfig := &rawConfig{
|
||||||
AllowLan: false,
|
AllowLan: false,
|
||||||
Mode: T.Rule,
|
Mode: T.Rule,
|
||||||
LogLevel: log.INFO,
|
Authentication: []string{},
|
||||||
Rule: []string{},
|
LogLevel: log.INFO,
|
||||||
Proxy: []map[string]interface{}{},
|
Rule: []string{},
|
||||||
ProxyGroup: []map[string]interface{}{},
|
Proxy: []map[string]interface{}{},
|
||||||
|
ProxyGroup: []map[string]interface{}{},
|
||||||
Experimental: Experimental{
|
Experimental: Experimental{
|
||||||
IgnoreResolveFail: true,
|
IgnoreResolveFail: true,
|
||||||
},
|
},
|
||||||
@ -142,7 +162,7 @@ func Parse(path string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.Proxies = proxies
|
config.Proxies = proxies
|
||||||
|
|
||||||
rules, err := parseRules(rawCfg)
|
rules, err := parseRules(rawCfg, proxies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -154,6 +174,8 @@ func Parse(path string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.DNS = dnsCfg
|
config.DNS = dnsCfg
|
||||||
|
|
||||||
|
config.Users = parseAuthentication(rawCfg.Authentication)
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +216,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
|
|||||||
|
|
||||||
func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
||||||
proxies := make(map[string]C.Proxy)
|
proxies := make(map[string]C.Proxy)
|
||||||
|
proxyList := []string{}
|
||||||
proxiesConfig := cfg.Proxy
|
proxiesConfig := cfg.Proxy
|
||||||
groupsConfig := cfg.ProxyGroup
|
groupsConfig := cfg.ProxyGroup
|
||||||
|
|
||||||
@ -201,6 +224,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
|
|
||||||
proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
|
proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
|
||||||
proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
|
proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
|
||||||
|
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||||
|
|
||||||
// parse proxy
|
// parse proxy
|
||||||
for idx, mapping := range proxiesConfig {
|
for idx, mapping := range proxiesConfig {
|
||||||
@ -210,7 +234,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var proxy C.ProxyAdapter
|
var proxy C.ProxyAdapter
|
||||||
err := fmt.Errorf("can't parse")
|
err := fmt.Errorf("cannot parse")
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &adapters.ShadowSocksOption{}
|
ssOption := &adapters.ShadowSocksOption{}
|
||||||
@ -252,6 +276,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
|
return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
|
||||||
}
|
}
|
||||||
proxies[proxy.Name()] = adapters.NewProxy(proxy)
|
proxies[proxy.Name()] = adapters.NewProxy(proxy)
|
||||||
|
proxyList = append(proxyList, proxy.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse proxy group
|
// parse proxy group
|
||||||
@ -268,7 +293,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
var group C.ProxyAdapter
|
var group C.ProxyAdapter
|
||||||
ps := []C.Proxy{}
|
ps := []C.Proxy{}
|
||||||
|
|
||||||
err := fmt.Errorf("can't parse")
|
err := fmt.Errorf("cannot parse")
|
||||||
switch groupType {
|
switch groupType {
|
||||||
case "url-test":
|
case "url-test":
|
||||||
urlTestOption := &adapters.URLTestOption{}
|
urlTestOption := &adapters.URLTestOption{}
|
||||||
@ -323,11 +348,12 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
|
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
|
||||||
}
|
}
|
||||||
proxies[groupName] = adapters.NewProxy(group)
|
proxies[groupName] = adapters.NewProxy(group)
|
||||||
|
proxyList = append(proxyList, groupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := []C.Proxy{}
|
ps := []C.Proxy{}
|
||||||
for _, v := range proxies {
|
for _, v := range proxyList {
|
||||||
ps = append(ps, v)
|
ps = append(ps, proxies[v])
|
||||||
}
|
}
|
||||||
|
|
||||||
global, _ := adapters.NewSelector("GLOBAL", ps)
|
global, _ := adapters.NewSelector("GLOBAL", ps)
|
||||||
@ -335,7 +361,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRules(cfg *rawConfig) ([]C.Rule, error) {
|
func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||||
rules := []C.Rule{}
|
rules := []C.Rule{}
|
||||||
|
|
||||||
rulesConfig := cfg.Rule
|
rulesConfig := cfg.Rule
|
||||||
@ -357,6 +383,10 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
|
|||||||
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
|
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := proxies[target]; !ok {
|
||||||
|
return nil, fmt.Errorf("Rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||||
|
}
|
||||||
|
|
||||||
rule = trimArr(rule)
|
rule = trimArr(rule)
|
||||||
var parsed C.Rule
|
var parsed C.Rule
|
||||||
switch rule[0] {
|
switch rule[0] {
|
||||||
@ -445,9 +475,14 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
case "tls":
|
case "tls":
|
||||||
host, err = hostWithDefaultPort(u.Host, "853")
|
host, err = hostWithDefaultPort(u.Host, "853")
|
||||||
dnsNetType = "tcp-tls" // DNS over TLS
|
dnsNetType = "tcp-tls" // DNS over TLS
|
||||||
|
case "https":
|
||||||
|
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
||||||
|
host = clearURL.String()
|
||||||
|
dnsNetType = "https" // DNS over HTTPS
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||||
}
|
}
|
||||||
@ -471,6 +506,7 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
|||||||
dnsCfg := &DNS{
|
dnsCfg := &DNS{
|
||||||
Enable: cfg.Enable,
|
Enable: cfg.Enable,
|
||||||
Listen: cfg.Listen,
|
Listen: cfg.Listen,
|
||||||
|
IPv6: cfg.IPv6,
|
||||||
EnhancedMode: cfg.EnhancedMode,
|
EnhancedMode: cfg.EnhancedMode,
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -497,3 +533,14 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
|||||||
|
|
||||||
return dnsCfg, nil
|
return dnsCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
||||||
|
users := make([]auth.AuthUser, 0)
|
||||||
|
for _, line := range rawRecords {
|
||||||
|
userData := strings.SplitN(line, ":", 2)
|
||||||
|
if len(userData) == 2 {
|
||||||
|
users = append(users, auth.AuthUser{User: userData[0], Pass: userData[1]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
@ -63,7 +63,7 @@ func Init(dir string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial config.yml
|
// initial config.yaml
|
||||||
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
||||||
log.Info("Can't find config, create an empty file")
|
log.Info("Can't find config, create an empty file")
|
||||||
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
@ -42,7 +42,7 @@ func (p *path) HomeDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *path) Config() string {
|
func (p *path) Config() string {
|
||||||
return P.Join(p.homedir, "config.yml")
|
return P.Join(p.homedir, "config.yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *path) MMDB() string {
|
func (p *path) MMDB() string {
|
||||||
|
263
dns/client.go
263
dns/client.go
@ -2,269 +2,20 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
D "github.com/miekg/dns"
|
||||||
geoip2 "github.com/oschwald/geoip2-golang"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type client struct {
|
||||||
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
*D.Client
|
||||||
|
|
||||||
mmdb *geoip2.Reader
|
|
||||||
once sync.Once
|
|
||||||
resolver *Resolver
|
|
||||||
)
|
|
||||||
|
|
||||||
type Resolver struct {
|
|
||||||
ipv6 bool
|
|
||||||
mapping bool
|
|
||||||
fakeip bool
|
|
||||||
pool *fakeip.Pool
|
|
||||||
fallback []*nameserver
|
|
||||||
main []*nameserver
|
|
||||||
cache *cache.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
type result struct {
|
|
||||||
Msg *D.Msg
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIPRequest(q D.Question) bool {
|
|
||||||
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
if len(m.Question) == 0 {
|
|
||||||
return nil, errors.New("should have one question at least")
|
|
||||||
}
|
|
||||||
|
|
||||||
q := m.Question[0]
|
|
||||||
cache, expireTime := r.cache.GetWithExpire(q.String())
|
|
||||||
if cache != nil {
|
|
||||||
msg = cache.(*D.Msg).Copy()
|
|
||||||
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if msg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
putMsgToCache(r.cache, q.String(), msg)
|
|
||||||
if r.mapping {
|
|
||||||
ips := r.msgToIP(msg)
|
|
||||||
for _, ip := range ips {
|
|
||||||
putMsgToCache(r.cache, ip.String(), msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
isIPReq := isIPRequest(q)
|
|
||||||
if isIPReq {
|
|
||||||
msg, err = r.resolveIP(m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err = r.exchange(r.main, m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) exchange(servers []*nameserver, m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
in := make(chan interface{})
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
||||||
defer cancel()
|
|
||||||
fast := picker.SelectFast(ctx, in)
|
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
go func(s *nameserver) {
|
|
||||||
defer wg.Done()
|
|
||||||
msg, _, err := s.Client.Exchange(m, s.Address)
|
|
||||||
if err != nil || msg.Rcode != D.RcodeSuccess {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
in <- &result{Msg: msg, Error: err}
|
|
||||||
}(server)
|
|
||||||
}
|
|
||||||
|
|
||||||
// release in channel
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(in)
|
|
||||||
}()
|
|
||||||
|
|
||||||
elm, exist := <-fast
|
|
||||||
if !exist {
|
|
||||||
return nil, errors.New("All DNS requests failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := elm.(*result)
|
|
||||||
msg, err = resp.Msg, resp.Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
msgCh := r.resolve(r.main, m)
|
|
||||||
if r.fallback == nil {
|
|
||||||
res := <-msgCh
|
|
||||||
msg, err = res.Msg, res.Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fallbackMsg := r.resolve(r.fallback, m)
|
|
||||||
res := <-msgCh
|
|
||||||
if res.Error == nil {
|
|
||||||
if mmdb == nil {
|
|
||||||
return nil, errors.New("GeoIP can't use")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
|
||||||
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
|
|
||||||
// release channel
|
|
||||||
go func() { <-fallbackMsg }()
|
|
||||||
msg = res.Msg
|
|
||||||
return msg, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res = <-fallbackMsg
|
|
||||||
msg, err = res.Msg, res.Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
|
|
||||||
ip = net.ParseIP(host)
|
|
||||||
if ip != nil {
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
query := &D.Msg{}
|
|
||||||
dnsType := D.TypeA
|
|
||||||
if r.ipv6 {
|
|
||||||
dnsType = D.TypeAAAA
|
|
||||||
}
|
|
||||||
query.SetQuestion(D.Fqdn(host), dnsType)
|
|
||||||
|
|
||||||
msg, err := r.Exchange(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ips := r.msgToIP(msg)
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return nil, errors.New("can't found ip")
|
|
||||||
}
|
|
||||||
|
|
||||||
ip = ips[0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
|
||||||
ips := []net.IP{}
|
|
||||||
|
|
||||||
for _, answer := range msg.Answer {
|
|
||||||
switch ans := answer.(type) {
|
|
||||||
case *D.AAAA:
|
|
||||||
ips = append(ips, ans.AAAA)
|
|
||||||
case *D.A:
|
|
||||||
ips = append(ips, ans.A)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
|
||||||
cache := r.cache.Get(ip.String())
|
|
||||||
if cache == nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
fqdn := cache.(*D.Msg).Question[0].Name
|
|
||||||
return strings.TrimRight(fqdn, "."), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result {
|
|
||||||
ch := make(chan *result)
|
|
||||||
go func() {
|
|
||||||
res, err := r.exchange(client, msg)
|
|
||||||
ch <- &result{Msg: res, Error: err}
|
|
||||||
}()
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) IsMapping() bool {
|
|
||||||
return r.mapping
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) IsFakeIP() bool {
|
|
||||||
return r.fakeip
|
|
||||||
}
|
|
||||||
|
|
||||||
type NameServer struct {
|
|
||||||
Net string
|
|
||||||
Addr string
|
|
||||||
}
|
|
||||||
|
|
||||||
type nameserver struct {
|
|
||||||
Client *D.Client
|
|
||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
Main, Fallback []NameServer
|
return c.ExchangeContext(context.Background(), m)
|
||||||
IPv6 bool
|
|
||||||
EnhancedMode EnhancedMode
|
|
||||||
Pool *fakeip.Pool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func transform(servers []NameServer) []*nameserver {
|
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
var ret []*nameserver
|
msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address)
|
||||||
for _, s := range servers {
|
return
|
||||||
ret = append(ret, &nameserver{
|
|
||||||
Client: &D.Client{
|
|
||||||
Net: s.Net,
|
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
ClientSessionCache: globalSessionCache,
|
|
||||||
},
|
|
||||||
UDPSize: 4096,
|
|
||||||
},
|
|
||||||
Address: s.Addr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(config Config) *Resolver {
|
|
||||||
once.Do(func() {
|
|
||||||
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
|
||||||
})
|
|
||||||
|
|
||||||
r := &Resolver{
|
|
||||||
main: transform(config.Main),
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
75
dns/doh.go
Normal file
75
dns/doh.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
D "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// dotMimeType is the DoH mimetype that should be used.
|
||||||
|
dotMimeType = "application/dns-message"
|
||||||
|
|
||||||
|
// dotPath is the URL path that should be used.
|
||||||
|
dotPath = "/dns-query"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dohTransport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||||
|
}
|
||||||
|
|
||||||
|
type dohClient struct {
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
return dc.ExchangeContext(context.Background(), m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
req, err := dc.newRequest(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
return dc.doRequest(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRequest returns a new DoH request given a dns.Msg.
|
||||||
|
func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
||||||
|
buf, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, dc.url+"?bla=foo:443", bytes.NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
return req, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("content-type", dotMimeType)
|
||||||
|
req.Header.Set("accept", dotMimeType)
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||||
|
client := &http.Client{Transport: dohTransport}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg = &D.Msg{}
|
||||||
|
err = msg.Unpack(buf)
|
||||||
|
return msg, err
|
||||||
|
}
|
32
dns/iputil.go
Normal file
32
dns/iputil.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errIPNotFound = errors.New("cannot found ip")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResolveIP with a host, return ip
|
||||||
|
func ResolveIP(host string) (net.IP, error) {
|
||||||
|
if DefaultResolver != nil {
|
||||||
|
if DefaultResolver.ipv6 {
|
||||||
|
return DefaultResolver.ResolveIP(host)
|
||||||
|
}
|
||||||
|
return DefaultResolver.ResolveIPv4(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip != nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddr, err := net.ResolveIPAddr("ip", host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipAddr.IP, nil
|
||||||
|
}
|
299
dns/resolver.go
Normal file
299
dns/resolver.go
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
geoip2 "github.com/oschwald/geoip2-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultResolver aim to resolve ip with host
|
||||||
|
DefaultResolver *Resolver
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
||||||
|
|
||||||
|
mmdb *geoip2.Reader
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolver interface {
|
||||||
|
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||||
|
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
Msg *D.Msg
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
ipv6 bool
|
||||||
|
mapping bool
|
||||||
|
fakeip bool
|
||||||
|
pool *fakeip.Pool
|
||||||
|
fallback []resolver
|
||||||
|
main []resolver
|
||||||
|
cache *cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA
|
||||||
|
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
|
||||||
|
ip = net.ParseIP(host)
|
||||||
|
if ip != nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan net.IP)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
ip, err := r.resolveIP(host, D.TypeA)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- ip
|
||||||
|
}()
|
||||||
|
|
||||||
|
ip, err = r.resolveIP(host, D.TypeAAAA)
|
||||||
|
if err == nil {
|
||||||
|
go func() {
|
||||||
|
<-ch
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, open := <-ch
|
||||||
|
if !open {
|
||||||
|
return nil, errIPNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveIPv4 request with TypeA
|
||||||
|
func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) {
|
||||||
|
ip = net.ParseIP(host)
|
||||||
|
if ip != nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &D.Msg{}
|
||||||
|
query.SetQuestion(D.Fqdn(host), D.TypeA)
|
||||||
|
|
||||||
|
msg, err := r.Exchange(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ips := r.msgToIP(msg)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return nil, errIPNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ips[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange a batch of dns request, and it use cache
|
||||||
|
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
if len(m.Question) == 0 {
|
||||||
|
return nil, errors.New("should have one question at least")
|
||||||
|
}
|
||||||
|
|
||||||
|
q := m.Question[0]
|
||||||
|
cache, expireTime := r.cache.GetWithExpire(q.String())
|
||||||
|
if cache != nil {
|
||||||
|
msg = cache.(*D.Msg).Copy()
|
||||||
|
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if msg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
putMsgToCache(r.cache, q.String(), msg)
|
||||||
|
if r.mapping {
|
||||||
|
ips := r.msgToIP(msg)
|
||||||
|
for _, ip := range ips {
|
||||||
|
putMsgToCache(r.cache, ip.String(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
isIPReq := isIPRequest(q)
|
||||||
|
if isIPReq {
|
||||||
|
msg, err = r.fallbackExchange(m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err = r.batchExchange(r.main, m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPToHost return fake-ip or redir-host mapping host
|
||||||
|
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
||||||
|
cache := r.cache.Get(ip.String())
|
||||||
|
if cache == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
fqdn := cache.(*D.Msg).Question[0].Name
|
||||||
|
return strings.TrimRight(fqdn, "."), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) IsMapping() bool {
|
||||||
|
return r.mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) IsFakeIP() bool {
|
||||||
|
return r.fakeip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
in := make(chan interface{})
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
fast := picker.SelectFast(ctx, in)
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(clients))
|
||||||
|
for _, r := range clients {
|
||||||
|
go func(r resolver) {
|
||||||
|
defer wg.Done()
|
||||||
|
msg, err := r.ExchangeContext(ctx, m)
|
||||||
|
if err != nil || msg.Rcode != D.RcodeSuccess {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
in <- msg
|
||||||
|
}(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// release in channel
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(in)
|
||||||
|
}()
|
||||||
|
|
||||||
|
elm, exist := <-fast
|
||||||
|
if !exist {
|
||||||
|
return nil, errors.New("All DNS requests failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = elm.(*D.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
msgCh := r.asyncExchange(r.main, m)
|
||||||
|
if r.fallback == nil {
|
||||||
|
res := <-msgCh
|
||||||
|
msg, err = res.Msg, res.Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallbackMsg := r.asyncExchange(r.fallback, m)
|
||||||
|
res := <-msgCh
|
||||||
|
if res.Error == nil {
|
||||||
|
if mmdb == nil {
|
||||||
|
return nil, errors.New("GeoIP cannot use")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||||
|
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
|
||||||
|
// release channel
|
||||||
|
go func() { <-fallbackMsg }()
|
||||||
|
msg = res.Msg
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = <-fallbackMsg
|
||||||
|
msg, err = res.Msg, res.Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) {
|
||||||
|
query := &D.Msg{}
|
||||||
|
query.SetQuestion(D.Fqdn(host), dnsType)
|
||||||
|
|
||||||
|
msg, err := r.Exchange(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ips := r.msgToIP(msg)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return nil, errIPNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ips[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
||||||
|
ips := []net.IP{}
|
||||||
|
|
||||||
|
for _, answer := range msg.Answer {
|
||||||
|
switch ans := answer.(type) {
|
||||||
|
case *D.AAAA:
|
||||||
|
ips = append(ips, ans.AAAA)
|
||||||
|
case *D.A:
|
||||||
|
ips = append(ips, ans.A)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result {
|
||||||
|
ch := make(chan *result)
|
||||||
|
go func() {
|
||||||
|
res, err := r.batchExchange(client, msg)
|
||||||
|
ch <- &result{Msg: res, Error: err}
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameServer struct {
|
||||||
|
Net string
|
||||||
|
Addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Main, Fallback []NameServer
|
||||||
|
IPv6 bool
|
||||||
|
EnhancedMode EnhancedMode
|
||||||
|
Pool *fakeip.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config) *Resolver {
|
||||||
|
once.Do(func() {
|
||||||
|
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
||||||
|
})
|
||||||
|
|
||||||
|
r := &Resolver{
|
||||||
|
ipv6: config.IPv6,
|
||||||
|
main: transform(config.Main),
|
||||||
|
cache: cache.New(time.Second * 60),
|
||||||
|
mapping: config.EnhancedMode == MAPPING,
|
||||||
|
fakeip: config.EnhancedMode == FAKEIP,
|
||||||
|
pool: config.Pool,
|
||||||
|
}
|
||||||
|
if len(config.Fallback) != 0 {
|
||||||
|
r.fallback = transform(config.Fallback)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@ -58,10 +57,10 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) {
|
|||||||
|
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
|
|
||||||
cache, expireTime := s.r.cache.GetWithExpire("fakeip:" + q.String())
|
cache := s.r.cache.Get("fakeip:" + q.String())
|
||||||
if cache != nil {
|
if cache != nil {
|
||||||
msg = cache.(*D.Msg).Copy()
|
msg = cache.(*D.Msg).Copy()
|
||||||
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
setMsgTTL(msg, 1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +72,8 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) {
|
|||||||
|
|
||||||
putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg)
|
putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg)
|
||||||
putMsgToCache(s.r.cache, ip.String(), msg)
|
putMsgToCache(s.r.cache, ip.String(), msg)
|
||||||
|
|
||||||
|
setMsgTTL(msg, 1)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rr := &D.A{}
|
rr := &D.A{}
|
||||||
@ -96,6 +97,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
|||||||
|
|
||||||
if server.Server != nil {
|
if server.Server != nil {
|
||||||
server.Shutdown()
|
server.Shutdown()
|
||||||
|
address = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
_, port, err := net.SplitHostPort(addr)
|
_, port, err := net.SplitHostPort(addr)
|
||||||
|
32
dns/util.go
32
dns/util.go
@ -1,6 +1,7 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
@ -107,3 +108,34 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
|||||||
extra.Header().Ttl = ttl
|
extra.Header().Ttl = ttl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isIPRequest(q D.Question) bool {
|
||||||
|
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func transform(servers []NameServer) []resolver {
|
||||||
|
ret := []resolver{}
|
||||||
|
for _, s := range servers {
|
||||||
|
if s.Net == "https" {
|
||||||
|
ret = append(ret, &dohClient{url: s.Addr})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, &client{
|
||||||
|
Client: &D.Client{
|
||||||
|
Net: s.Net,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
ClientSessionCache: globalSessionCache,
|
||||||
|
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
|
||||||
|
NextProtos: []string{"dns"},
|
||||||
|
},
|
||||||
|
UDPSize: 4096,
|
||||||
|
},
|
||||||
|
Address: s.Addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
10
go.mod
10
go.mod
@ -8,13 +8,13 @@ require (
|
|||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible
|
github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.0
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/miekg/dns v1.1.9
|
github.com/miekg/dns v1.1.14
|
||||||
github.com/oschwald/geoip2-golang v1.2.1
|
github.com/oschwald/geoip2-golang v1.3.0
|
||||||
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.3.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.4.1
|
github.com/sirupsen/logrus v1.4.2
|
||||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0
|
gopkg.in/eapache/channels.v1 v1.1.0
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
)
|
)
|
||||||
|
23
go.sum
23
go.sum
@ -18,16 +18,16 @@ github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH
|
|||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/miekg/dns v1.1.9 h1:OIdC9wT96RzuZMf2PfKRhFgsStHUUBZLM/lo1LqiM9E=
|
github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA=
|
||||||
github.com/miekg/dns v1.1.9/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
|
github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8=
|
||||||
github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
|
github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
|
||||||
github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE=
|
github.com/oschwald/maxminddb-golang v1.3.1 h1:kPc5+ieL5CC/Zn0IaXJPxDFlUxKTQEU8QBTtmfQDAIo=
|
||||||
github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
|
github.com/oschwald/maxminddb-golang v1.3.1/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
@ -36,12 +36,13 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/Le
|
|||||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package executor
|
package executor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
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"
|
||||||
P "github.com/Dreamacro/clash/proxy"
|
P "github.com/Dreamacro/clash/proxy"
|
||||||
|
authStore "github.com/Dreamacro/clash/proxy/auth"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,6 +23,7 @@ func ParseWithPath(path string) (*config.Config, error) {
|
|||||||
|
|
||||||
// ApplyConfig dispatch configure to all parts
|
// ApplyConfig dispatch configure to all parts
|
||||||
func ApplyConfig(cfg *config.Config, force bool) {
|
func ApplyConfig(cfg *config.Config, force bool) {
|
||||||
|
updateUsers(cfg.Users)
|
||||||
if force {
|
if force {
|
||||||
updateGeneral(cfg.General)
|
updateGeneral(cfg.General)
|
||||||
}
|
}
|
||||||
@ -32,14 +35,22 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
|
|
||||||
func GetGeneral() *config.General {
|
func GetGeneral() *config.General {
|
||||||
ports := P.GetPorts()
|
ports := P.GetPorts()
|
||||||
return &config.General{
|
authenticator := []string{}
|
||||||
Port: ports.Port,
|
if auth := authStore.Authenticator(); auth != nil {
|
||||||
SocksPort: ports.SocksPort,
|
authenticator = auth.Users()
|
||||||
RedirPort: ports.RedirPort,
|
|
||||||
AllowLan: P.AllowLan(),
|
|
||||||
Mode: T.Instance().Mode(),
|
|
||||||
LogLevel: log.Level(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
general := &config.General{
|
||||||
|
Port: ports.Port,
|
||||||
|
SocksPort: ports.SocksPort,
|
||||||
|
RedirPort: ports.RedirPort,
|
||||||
|
Authentication: authenticator,
|
||||||
|
AllowLan: P.AllowLan(),
|
||||||
|
Mode: T.Instance().Mode(),
|
||||||
|
LogLevel: log.Level(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return general
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateExperimental(c *config.Experimental) {
|
func updateExperimental(c *config.Experimental) {
|
||||||
@ -48,7 +59,7 @@ func updateExperimental(c *config.Experimental) {
|
|||||||
|
|
||||||
func updateDNS(c *config.DNS) {
|
func updateDNS(c *config.DNS) {
|
||||||
if c.Enable == false {
|
if c.Enable == false {
|
||||||
T.Instance().SetResolver(nil)
|
dns.DefaultResolver = nil
|
||||||
dns.ReCreateServer("", nil)
|
dns.ReCreateServer("", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -59,12 +70,15 @@ func updateDNS(c *config.DNS) {
|
|||||||
EnhancedMode: c.EnhancedMode,
|
EnhancedMode: c.EnhancedMode,
|
||||||
Pool: c.FakeIPRange,
|
Pool: c.FakeIPRange,
|
||||||
})
|
})
|
||||||
T.Instance().SetResolver(r)
|
dns.DefaultResolver = r
|
||||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
||||||
log.Errorln("Start DNS server error: %s", err.Error())
|
log.Errorln("Start DNS server error: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Infoln("DNS server listening at: %s", c.Listen)
|
|
||||||
|
if c.Listen != "" {
|
||||||
|
log.Infoln("DNS server listening at: %s", c.Listen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateProxies(proxies map[string]C.Proxy) {
|
func updateProxies(proxies map[string]C.Proxy) {
|
||||||
@ -90,6 +104,7 @@ func updateGeneral(general *config.General) {
|
|||||||
allowLan := general.AllowLan
|
allowLan := general.AllowLan
|
||||||
|
|
||||||
P.SetAllowLan(allowLan)
|
P.SetAllowLan(allowLan)
|
||||||
|
|
||||||
if err := P.ReCreateHTTP(general.Port); err != nil {
|
if err := P.ReCreateHTTP(general.Port); err != nil {
|
||||||
log.Errorln("Start HTTP server error: %s", err.Error())
|
log.Errorln("Start HTTP server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -102,3 +117,11 @@ func updateGeneral(general *config.General) {
|
|||||||
log.Errorln("Start Redir server error: %s", err.Error())
|
log.Errorln("Start Redir server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateUsers(users []auth.AuthUser) {
|
||||||
|
authenticator := auth.NewAuthenticator(users)
|
||||||
|
authStore.SetAuthenticator(authenticator)
|
||||||
|
if authenticator != nil {
|
||||||
|
log.Infoln("Authentication of local server updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/observable"
|
"github.com/Dreamacro/clash/common/observable"
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
proxy/auth/auth.go
Normal file
17
proxy/auth/auth.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
authenticator auth.Authenticator
|
||||||
|
)
|
||||||
|
|
||||||
|
func Authenticator() auth.Authenticator {
|
||||||
|
return authenticator
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetAuthenticator(au auth.Authenticator) {
|
||||||
|
authenticator = au
|
||||||
|
}
|
@ -2,11 +2,17 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||||
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
authStore "github.com/Dreamacro/clash/proxy/auth"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,6 +24,7 @@ type HttpListener struct {
|
|||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
|
cache *cache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpProxy(addr string) (*HttpListener, error) {
|
func NewHttpProxy(addr string) (*HttpListener, error) {
|
||||||
@ -25,10 +32,11 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hl := &HttpListener{l, addr, false}
|
hl := &HttpListener{l, addr, false, cache.New(30 * time.Second)}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Infoln("HTTP proxy listening at: %s", addr)
|
log.Infoln("HTTP proxy listening at: %s", addr)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c, err := hl.Accept()
|
c, err := hl.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -37,7 +45,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go handleConn(c)
|
go handleConn(c, hl.cache)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -53,7 +61,19 @@ func (l *HttpListener) Address() string {
|
|||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConn(conn net.Conn) {
|
func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
|
||||||
|
if result := cache.Get(loginStr); result != nil {
|
||||||
|
ret = result.(bool)
|
||||||
|
}
|
||||||
|
loginData, err := base64.StdEncoding.DecodeString(loginStr)
|
||||||
|
login := strings.Split(string(loginData), ":")
|
||||||
|
ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1])
|
||||||
|
|
||||||
|
cache.Put(loginStr, ret, time.Minute)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleConn(conn net.Conn, cache *cache.Cache) {
|
||||||
br := bufio.NewReader(conn)
|
br := bufio.NewReader(conn)
|
||||||
request, err := http.ReadRequest(br)
|
request, err := http.ReadRequest(br)
|
||||||
if err != nil || request.URL.Host == "" {
|
if err != nil || request.URL.Host == "" {
|
||||||
@ -61,6 +81,20 @@ func handleConn(conn net.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authenticator := authStore.Authenticator()
|
||||||
|
if authenticator != nil {
|
||||||
|
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
|
||||||
|
_, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
} else if !canActivate(authStrings[1], authenticator, cache) {
|
||||||
|
conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n"))
|
||||||
|
log.Infoln("Auth failed from %s", conn.RemoteAddr().String())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if request.Method == http.MethodConnect {
|
if request.Method == http.MethodConnect {
|
||||||
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
authStore "github.com/Dreamacro/clash/proxy/auth"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ func (l *SockListener) Address() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleSocks(conn net.Conn) {
|
func handleSocks(conn net.Conn) {
|
||||||
target, command, err := socks5.ServerHandshake(conn)
|
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
|
@ -26,7 +26,6 @@ type Tunnel struct {
|
|||||||
proxies map[string]C.Proxy
|
proxies map[string]C.Proxy
|
||||||
configMux *sync.RWMutex
|
configMux *sync.RWMutex
|
||||||
traffic *C.Traffic
|
traffic *C.Traffic
|
||||||
resolver *dns.Resolver
|
|
||||||
|
|
||||||
// experimental features
|
// experimental features
|
||||||
ignoreResolveFail bool
|
ignoreResolveFail bool
|
||||||
@ -86,15 +85,6 @@ func (t *Tunnel) SetMode(mode Mode) {
|
|||||||
t.mode = mode
|
t.mode = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetResolver change the resolver of tunnel
|
|
||||||
func (t *Tunnel) SetResolver(resolver *dns.Resolver) {
|
|
||||||
t.resolver = resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tunnel) hasResolver() bool {
|
|
||||||
return t.resolver != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tunnel) process() {
|
func (t *Tunnel) process() {
|
||||||
queue := t.queue.Out()
|
queue := t.queue.Out()
|
||||||
for {
|
for {
|
||||||
@ -105,20 +95,11 @@ func (t *Tunnel) process() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) resolveIP(host string) (net.IP, error) {
|
func (t *Tunnel) resolveIP(host string) (net.IP, error) {
|
||||||
if t.resolver == nil {
|
return dns.ResolveIP(host)
|
||||||
ipAddr, err := net.ResolveIPAddr("ip", host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipAddr.IP, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.resolver.ResolveIP(host)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
|
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
|
||||||
return t.hasResolver() && (t.resolver.IsMapping() || t.resolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil
|
return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||||
@ -132,11 +113,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
|||||||
|
|
||||||
// preprocess enhanced-mode metadata
|
// preprocess enhanced-mode metadata
|
||||||
if t.needLookupIP(metadata) {
|
if t.needLookupIP(metadata) {
|
||||||
host, exist := t.resolver.IPToHost(*metadata.DstIP)
|
host, exist := dns.DefaultResolver.IPToHost(*metadata.DstIP)
|
||||||
if exist {
|
if exist {
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
metadata.AddrType = C.AtypDomainName
|
metadata.AddrType = C.AtypDomainName
|
||||||
if t.resolver.IsFakeIP() {
|
if dns.DefaultResolver.IsFakeIP() {
|
||||||
metadata.DstIP = nil
|
metadata.DstIP = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,10 +140,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
|||||||
|
|
||||||
if metadata.NetWork == C.UDP {
|
if metadata.NetWork == C.UDP {
|
||||||
pc, addr, err := proxy.DialUDP(metadata)
|
pc, addr, err := proxy.DialUDP(metadata)
|
||||||
defer pc.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error())
|
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
defer pc.Close()
|
||||||
|
|
||||||
t.handleUDPOverTCP(localConn, pc, addr)
|
t.handleUDPOverTCP(localConn, pc, addr)
|
||||||
return
|
return
|
||||||
|
Reference in New Issue
Block a user