Compare commits

...

37 Commits

Author SHA1 Message Date
59968fff1c Fix: github actions tag build 2020-06-27 21:09:04 +08:00
7c62fe41b4 Chore: remove forward compatibility code 2020-06-27 14:28:10 +08:00
2781090405 Chore: move experimental features to stable 2020-06-27 14:19:31 +08:00
14c9cf1b97 Fix: domain trie crash if not match in #758 (#762) 2020-06-24 19:46:37 +08:00
3dfff84cc3 Fix: domain trie should backtrack to parent if match fail (#758) 2020-06-24 18:41:23 +08:00
5f3db72422 Fix: docker multiplatform build 2020-06-21 12:38:14 +08:00
18bb285a90 Fix: external-ui should relative with clash HomeDir 2020-06-18 21:33:57 +08:00
60bad66bc3 Change: ipv6 logic 2020-06-18 18:11:02 +08:00
99b34e8d8b Fix: cannot listen socks5 port on wsl (#748) 2020-06-15 10:34:15 +08:00
9f1d85ab6e Fix: fake-ip-filter on fakeip mode should lookup ip-host mapping (#743) 2020-06-14 00:41:53 +08:00
4323dd24d0 Fix: don't auto health check on provider health check disabled 2020-06-14 00:32:04 +08:00
59bda1d547 Change: local resolve DNS in UDP request due to TURN failed 2020-06-12 23:39:03 +08:00
1c760935f4 Chore: add error msg when dial vmess 2020-06-11 22:19:47 +08:00
4f674755ce Fix: trim . for socks5 host 2020-06-11 12:11:44 +08:00
f1b792bd26 Fix: trim FQDN on http proxy request 2020-06-11 11:10:08 +08:00
58c077b45e Fix: actions tag replace 2020-06-08 13:53:04 +08:00
1854199c47 Chore: update dependencies 2020-06-07 18:14:04 +08:00
ecac8eb8e5 Fix: add lock for inbound proxy recreate 2020-06-07 17:57:41 +08:00
48cff50a4c Feature: connections add rule payload 2020-06-07 17:28:56 +08:00
fb628e9c62 Feature: add default hosts localhost 2020-06-07 17:25:51 +08:00
2dece02df6 Chore: code adjustments 2020-06-07 16:54:41 +08:00
8f32e6a60f Improve: safe write provider file 2020-06-07 00:36:54 +08:00
98614a1f3f Chore: move rule parser to rules 2020-06-05 17:43:50 +08:00
c1b4c94b9c Chore: remove unused hooks directory 2020-06-05 12:49:24 +08:00
7ddbc12cdb Chore: rm unused Dockerfile 2020-06-04 10:57:43 +08:00
1a217e21e9 Chore: use actions build docker image 2020-06-04 10:38:30 +08:00
147a7ce779 Fix: panic of socks5 client missing authentication 2020-06-03 18:49:57 +08:00
fb0289bb4c Chore: open ForceAttemptHTTP2 on DoH 2020-06-01 13:43:26 +08:00
3e7970612a Chore: provider error adjust 2020-06-01 00:39:41 +08:00
46244a6496 Chore: mode use lower case (backward compatible) 2020-06-01 00:32:37 +08:00
71d30e6654 Feature: support vmess tls custom servername 2020-06-01 00:27:04 +08:00
008731c249 Fix: make os.Stat return correct err on provider 2020-05-29 21:56:29 +08:00
5628f97da1 Feature: add tolerance for url-test 2020-05-29 17:47:50 +08:00
8d0c6c6e66 Feature: domain trie support wildcard alias 2020-05-28 12:13:05 +08:00
5073c3cde8 Chore: add trimpath for go build 2020-05-20 15:13:33 +08:00
3a27cfc4a1 Feature: add Mixed(http+socks5) proxy listening (#685) 2020-05-12 11:29:53 +08:00
3638b077cd Chore: update premium link 2020-05-08 21:52:17 +08:00
52 changed files with 696 additions and 429 deletions

53
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,53 @@
name: Publish Docker Image
on:
push:
branches:
- dev
tags:
- '*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up docker buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v2
with:
buildx-version: latest
skip-cache: false
qemu-version: latest
- name: Docker login
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
- name: Docker buildx image and push on dev branch
if: github.ref == 'refs/heads/dev'
run: |
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev .
- name: Replace tag without `v`
if: startsWith(github.ref, 'refs/tags/')
uses: actions/github-script@v1
id: version
with:
script: |
return context.payload.ref.replace(/\/?refs\/tags\/v/, '')
result-encoding: string
- name: Docker buildx image and push on release
if: startsWith(github.ref, 'refs/tags/')
run: |
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} .
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest .

View File

@ -7,15 +7,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v2
with:
go-version: 1.14.x
- name: Check out code into the Go module directory
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Cache go module
uses: actions/cache@v1
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

View File

@ -3,10 +3,11 @@ FROM golang:alpine as builder
RUN apk add --no-cache make git && \
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
WORKDIR /clash-src
COPY --from=tonistiigi/xx:golang / /
COPY . /clash-src
RUN go mod download && \
make linux-amd64 && \
mv ./bin/clash-linux-amd64 /clash
make docker && \
mv ./bin/clash-docker /clash
FROM alpine:latest

View File

@ -1,20 +0,0 @@
FROM golang:alpine as builder
RUN apk add --no-cache make git && \
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \
wget -O /qemu-arm-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static && \
chmod +x /qemu-arm-static
WORKDIR /clash-src
COPY . /clash-src
RUN go mod download && \
make linux-armv7 && \
mv ./bin/clash-linux-armv7 /clash
FROM arm32v7/alpine:latest
COPY --from=builder /qemu-arm-static /usr/bin/
COPY --from=builder /Country.mmdb /root/.config/clash/
COPY --from=builder /clash /
RUN apk add --no-cache ca-certificates
ENTRYPOINT ["/clash"]

View File

@ -1,20 +0,0 @@
FROM golang:alpine as builder
RUN apk add --no-cache make git && \
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \
wget -O /qemu-aarch64-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static && \
chmod +x /qemu-aarch64-static
WORKDIR /clash-src
COPY . /clash-src
RUN go mod download && \
make linux-armv8 && \
mv ./bin/clash-linux-armv8 /clash
FROM arm64v8/alpine:latest
COPY --from=builder /qemu-aarch64-static /usr/bin/
COPY --from=builder /Country.mmdb /root/.config/clash/
COPY --from=builder /clash /
RUN apk add --no-cache ca-certificates
ENTRYPOINT ["/clash"]

View File

@ -2,7 +2,7 @@ NAME=clash
BINDIR=bin
VERSION=$(shell git describe --tags || echo "unknown version")
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 -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-w -s'
@ -29,6 +29,9 @@ WINDOWS_ARCH_LIST = \
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
docker:
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@

View File

@ -37,7 +37,7 @@ $ go get -u -v github.com/Dreamacro/clash
```
Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases)
Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN). Source is not currently available.
Pre-built Premium binaries are available here: [premium release](https://github.com/Dreamacro/clash/releases/tag/premium). Source is not currently available.
Check Clash version with:
@ -47,7 +47,7 @@ $ clash -v
## Daemonize Clash
Unfortunately, there is no native or elegant way to implement daemons on Golang. We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service.
We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service. ([Wiki](https://github.com/Dreamacro/clash/wiki/Clash-as-a-daemon))
In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way:
@ -81,6 +81,9 @@ port: 7890
# port of SOCKS5
socks-port: 7891
# (HTTP and SOCKS5 in one port)
# mixed-port: 7890
# redir port for Linux and macOS
# redir-port: 7892
@ -92,8 +95,10 @@ allow-lan: false
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
# bind-address: "*"
# Rule / Global / Direct (default is Rule)
mode: Rule
# ipv6: false # when ipv6 is false, each clash dial with ipv6, but it's not affect the response of the dns server, default is false
# rule / global / direct (default is rule)
mode: rule
# set log level to stdout (default is info)
# info / warning / error / debug / silent
@ -109,26 +114,25 @@ external-controller: 127.0.0.1:9090
# Secret for RESTful API (Optional)
# secret: ""
# experimental feature
experimental:
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
# interface-name: en0 # outbound interface name
# interface-name: en0 # outbound interface name
# authentication of local SOCKS5/HTTP(S) server
# authentication:
# - "user1:pass1"
# - "user2:pass2"
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
# # hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com)
# # +.foo.com equal .foo.com and foo.com
# hosts:
# '*.clash.dev': 127.0.0.1
# '.dev': 127.0.0.1
# 'alpha.clash.dev': '::1'
# '+.foo.dev': 127.0.0.1
# dns:
# enable: true # set true to enable dns (default is false)
# ipv6: false # default is false
# ipv6: false # it only affect the dns server response, default is false
# listen: 0.0.0.0:53
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
# # - 114.114.114.114
@ -165,7 +169,6 @@ proxies:
password: "password"
# udp: true
# old obfs configuration format remove after prerelease
- name: "ss2"
type: ss
server: server
@ -206,6 +209,7 @@ proxies:
# udp: true
# tls: true
# skip-cert-verify: true
# servername: example.com # priority over wss host
# network: ws
# ws-path: /path
# ws-headers:
@ -291,6 +295,7 @@ proxy-groups:
- ss1
- ss2
- vmess1
# tolerance: 150
url: 'http://www.gstatic.com/generate_204'
interval: 300
@ -362,8 +367,6 @@ rules:
- GEOIP,CN,DIRECT
- DST-PORT,80,DIRECT
- SRC-PORT,7777,DIRECT
# FINAL would remove after prerelease
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
- MATCH,auto
```
</details>
@ -390,4 +393,4 @@ https://clash.gitbook.io/
- [x] Redir proxy
- [x] UDP support
- [x] Connection manager
- [ ] Event API
- ~~[ ] Event API~~

View File

@ -4,6 +4,7 @@ import (
"net"
"net/http"
"strconv"
"strings"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
@ -16,7 +17,8 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
switch target[0] {
case socks5.AtypDomainName:
metadata.Host = string(target[2 : 2+target[1]])
// trim for FQDN
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4:
ip := net.IP(target[1 : 1+net.IPv4len])
@ -38,6 +40,9 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
port = "80"
}
// trim FQDN (#737)
host = strings.TrimRight(host, ".")
metadata := &C.Metadata{
NetWork: C.TCP,
AddrType: C.AtypDomainName,

View File

@ -78,13 +78,8 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}}
}
type PacketConn interface {
net.PacketConn
WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error)
}
type packetConn struct {
PacketConn
net.PacketConn
chain C.Chain
}
@ -96,7 +91,7 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(pc PacketConn, a C.ProxyAdapter) C.PacketConn {
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}}
}

View File

@ -5,7 +5,6 @@ import (
"net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
)
@ -36,17 +35,6 @@ type directPacketConn struct {
net.PacketConn
}
func (dp *directPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return 0, err
}
metadata.DstIP = ip
}
return dp.WriteTo(p, metadata.UDPAddr())
}
func NewDirect() *Direct {
return &Direct{
Base: &Base{

View File

@ -37,10 +37,6 @@ type ShadowSocksOption struct {
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
// deprecated when bump to 1.0
Obfs string `proxy:"obfs,omitempty"`
ObfsHost string `proxy:"obfs-host,omitempty"`
}
type simpleObfsOption struct {
@ -122,17 +118,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var obfsOption *simpleObfsOption
obfsMode := ""
// forward compatibility before 1.0
if option.Obfs != "" {
obfsMode = option.Obfs
obfsOption = &simpleObfsOption{
Host: "bing.com",
}
if option.ObfsHost != "" {
obfsOption.Host = option.ObfsHost
}
}
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
@ -197,14 +182,6 @@ func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
}
func (spc *ssPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
if err != nil {
return
}
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
}
func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := spc.PacketConn.ReadFrom(b)
if e != nil {

View File

@ -164,14 +164,6 @@ func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return uc.PacketConn.WriteTo(packet, uc.rAddr)
}
func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet, uc.rAddr)
}
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := uc.PacketConn.ReadFrom(b)
if e != nil {

View File

@ -71,7 +71,7 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
}
pc := t.instance.PacketConn(c)
return newPacketConn(&trojanPacketConn{pc, c}, t), err
return newPacketConn(pc, t), err
}
func (t *Trojan) MarshalJSON() ([]byte, error) {
@ -105,12 +105,3 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
instance: trojan.New(tOption),
}, nil
}
type trojanPacketConn struct {
net.PacketConn
conn net.Conn
}
func (tpc *trojanPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
return trojan.WritePacket(tpc.conn, serializesSocksAddr(metadata), p)
}

View File

@ -35,6 +35,7 @@ type VmessOption struct {
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
ServerName string `proxy:"servername,omitempty"`
}
type HTTPOptions struct {
@ -66,6 +67,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
wsOpts.TLS = true
wsOpts.SessionCache = getClientSessionCache()
wsOpts.SkipCertVerify = v.option.SkipCertVerify
wsOpts.ServerName = v.option.ServerName
}
c, err = vmess.StreamWebsocketConn(c, wsOpts)
case "http":
@ -87,6 +89,11 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
SkipCertVerify: v.option.SkipCertVerify,
SessionCache: getClientSessionCache(),
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = vmess.StreamTLSConn(c, tlsOpts)
}
}
@ -101,7 +108,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.addr)
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
@ -123,7 +130,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.addr)
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
c, err = v.StreamConn(c, metadata)
@ -195,10 +202,6 @@ func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return uc.Conn.Write(b)
}
func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
return uc.Conn.Write(p)
}
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.rAddr, err

View File

@ -101,7 +101,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
var group C.ProxyAdapter
switch groupOption.Type {
case "url-test":
group = NewURLTest(groupName, providers)
opts := parseURLTestOption(config)
group = NewURLTest(groupName, providers, opts...)
case "select":
group = NewSelector(groupName, providers)
case "fallback":

View File

@ -11,8 +11,19 @@ import (
C "github.com/Dreamacro/clash/constant"
)
type urlTestOption func(*URLTest)
func urlTestWithTolerance(tolerance uint16) urlTestOption {
return func(u *URLTest) {
u.tolerance = tolerance
}
}
type URLTest struct {
*outbound.Base
tolerance uint16
lastDelay uint16
fastNode C.Proxy
single *singledo.Single
fastSingle *singledo.Single
providers []provider.ProxyProvider
@ -52,6 +63,13 @@ func (u *URLTest) proxies() []C.Proxy {
func (u *URLTest) fast() C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
// tolerance
if u.tolerance != 0 && u.fastNode != nil {
if u.fastNode.LastDelay() < u.lastDelay+u.tolerance {
return u.fastNode, nil
}
}
proxies := u.proxies()
fast := proxies[0]
min := fast.LastDelay()
@ -66,6 +84,9 @@ func (u *URLTest) fast() C.Proxy {
min = delay
}
}
u.fastNode = fast
u.lastDelay = fast.LastDelay()
return fast, nil
})
@ -88,11 +109,30 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
})
}
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
return &URLTest{
func parseURLTestOption(config map[string]interface{}) []urlTestOption {
opts := []urlTestOption{}
// tolerance
if elm, ok := config["tolerance"]; ok {
if tolerance, ok := elm.(int); ok {
opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
}
}
return opts
}
func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{
Base: outbound.NewBase(name, "", C.URLTest, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers,
}
for _, option := range options {
option(urlTest)
}
return urlTest
}

View File

@ -5,6 +5,7 @@ import (
"crypto/md5"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/Dreamacro/clash/log"
@ -12,6 +13,7 @@ import (
var (
fileMode os.FileMode = 0666
dirMode os.FileMode = 0755
)
type parser = func([]byte) (interface{}, error)
@ -35,10 +37,12 @@ func (f *fetcher) VehicleType() VehicleType {
}
func (f *fetcher) Initial() (interface{}, error) {
var buf []byte
var err error
var isLocal bool
if stat, err := os.Stat(f.vehicle.Path()); err == nil {
var (
buf []byte
err error
isLocal bool
)
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = ioutil.ReadFile(f.vehicle.Path())
modTime := stat.ModTime()
f.updatedAt = &modTime
@ -69,7 +73,7 @@ func (f *fetcher) Initial() (interface{}, error) {
}
}
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, err
}
@ -101,7 +105,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
return nil, false, err
}
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err
}
@ -138,6 +142,18 @@ func (f *fetcher) pullLoop() {
}
}
func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, dirMode); err != nil {
return err
}
}
return ioutil.WriteFile(path, buf, fileMode)
}
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
var ticker *time.Ticker
if interval != 0 {

View File

@ -142,7 +142,9 @@ func proxiesParse(buf []byte) (interface{}, error) {
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
go pp.healthCheck.check()
if pp.healthCheck.auto() {
go pp.healthCheck.check()
}
}
func stopProxyProvider(pd *ProxySetProvider) {

View File

@ -6,7 +6,7 @@ import (
"sync"
"github.com/Dreamacro/clash/common/cache"
trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/trie"
)
// Pool is a implementation about fake ip generator without storage
@ -16,7 +16,7 @@ type Pool struct {
gateway uint32
offset uint32
mux sync.Mutex
host *trie.Trie
host *trie.DomainTrie
cache *cache.LruCache
}
@ -120,7 +120,7 @@ func uintToIP(v uint32) net.IP {
}
// New return Pool instance
func New(ipnet *net.IPNet, size int, host *trie.Trie) (*Pool, error) {
func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) {
min := ipToUint(ipnet.IP) + 2
ones, bits := ipnet.Mask.Size()

View File

@ -5,20 +5,25 @@ import (
"net"
"strings"
trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/trie"
)
var (
// DefaultResolver aim to resolve ip
DefaultResolver Resolver
// DisableIPv6 means don't resolve ipv6 host
// default value is true
DisableIPv6 = true
// DefaultHosts aim to resolve hosts
DefaultHosts = trie.New()
)
var (
ErrIPNotFound = errors.New("couldn't find ip")
ErrIPVersion = errors.New("ip version error")
ErrIPNotFound = errors.New("couldn't find ip")
ErrIPVersion = errors.New("ip version error")
ErrIPv6Disabled = errors.New("ipv6 disabled")
)
type Resolver interface {
@ -63,6 +68,10 @@ func ResolveIPv4(host string) (net.IP, error) {
// ResolveIPv6 with a host, return ipv6
func ResolveIPv6(host string) (net.IP, error) {
if DisableIPv6 {
return nil, ErrIPv6Disabled
}
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data.(net.IP).To16(); ip != nil {
return ip, nil
@ -102,7 +111,12 @@ func ResolveIP(host string) (net.IP, error) {
}
if DefaultResolver != nil {
if DisableIPv6 {
return DefaultResolver.ResolveIPv4(host)
}
return DefaultResolver.ResolveIP(host)
} else if DisableIPv6 {
return ResolveIPv4(host)
}
ip := net.ParseIP(host)

View File

@ -21,6 +21,8 @@ func (err Error) Error() string {
// Command is request commands as defined in RFC 1928 section 4.
type Command = uint8
const Version = 5
// SOCKS request commands as defined in RFC 1928 section 4.
const (
CmdConnect Command = 1
@ -227,6 +229,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
}
if buf[1] == 2 {
if user == nil {
return nil, ErrAuth
}
// password protocol version
authMsg := &bytes.Buffer{}
authMsg.WriteByte(1)

View File

@ -6,9 +6,10 @@ import (
)
const (
wildcard = "*"
dotWildcard = ""
domainStep = "."
wildcard = "*"
dotWildcard = ""
complexWildcard = "+"
domainStep = "."
)
var (
@ -16,9 +17,9 @@ var (
ErrInvalidDomain = errors.New("invalid domain")
)
// Trie contains the main logic for adding and searching nodes for domain segments.
// DomainTrie contains the main logic for adding and searching nodes for domain segments.
// support wildcard domain (e.g *.google.com)
type Trie struct {
type DomainTrie struct {
root *Node
}
@ -29,7 +30,11 @@ func validAndSplitDomain(domain string) ([]string, bool) {
parts := strings.Split(domain, domainStep)
if len(parts) == 1 {
return nil, false
if parts[0] == "" {
return nil, false
}
return parts, true
}
for _, part := range parts[1:] {
@ -47,12 +52,25 @@ func validAndSplitDomain(domain string) ([]string, bool) {
// 2. *.example.com
// 3. subdomain.*.example.com
// 4. .example.com
func (t *Trie) Insert(domain string, data interface{}) error {
// 5. +.example.com
func (t *DomainTrie) Insert(domain string, data interface{}) error {
parts, valid := validAndSplitDomain(domain)
if !valid {
return ErrInvalidDomain
}
if parts[0] == complexWildcard {
t.insert(parts[1:], data)
parts[0] = dotWildcard
t.insert(parts, data)
} else {
t.insert(parts, data)
}
return nil
}
func (t *DomainTrie) insert(parts []string, data interface{}) {
node := t.root
// reverse storage domain part to save space
for i := len(parts) - 1; i >= 0; i-- {
@ -65,7 +83,6 @@ func (t *Trie) Insert(domain string, data interface{}) error {
}
node.Data = data
return nil
}
// Search is the most important part of the Trie.
@ -73,54 +90,46 @@ func (t *Trie) Insert(domain string, data interface{}) error {
// 1. static part
// 2. wildcard domain
// 2. dot wildcard domain
func (t *Trie) Search(domain string) *Node {
func (t *DomainTrie) Search(domain string) *Node {
parts, valid := validAndSplitDomain(domain)
if !valid || parts[0] == "" {
return nil
}
n := t.root
var dotWildcardNode *Node
var wildcardNode *Node
for i := len(parts) - 1; i >= 0; i-- {
part := parts[i]
n := t.search(t.root, parts)
if node := n.getChild(dotWildcard); node != nil {
dotWildcardNode = node
}
child := n.getChild(part)
if child == nil && wildcardNode != nil {
child = wildcardNode.getChild(part)
}
wildcardNode = n.getChild(wildcard)
n = child
if n == nil {
n = wildcardNode
wildcardNode = nil
}
if n == nil {
break
}
}
if n == nil {
if dotWildcardNode != nil {
return dotWildcardNode
}
return nil
}
if n.Data == nil {
if n == nil || n.Data == nil {
return nil
}
return n
}
// New returns a new, empty Trie.
func New() *Trie {
return &Trie{root: newNode(nil)}
func (t *DomainTrie) search(node *Node, parts []string) *Node {
if len(parts) == 0 {
return node
}
if c := node.getChild(parts[len(parts)-1]); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil {
return n
}
}
if c := node.getChild(wildcard); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil {
return n
}
}
if c := node.getChild(dotWildcard); c != nil {
return c
}
return nil
}
// New returns a new, empty Trie.
func New() *DomainTrie {
return &DomainTrie{root: newNode(nil)}
}

View File

@ -14,6 +14,7 @@ func TestTrie_Basic(t *testing.T) {
domains := []string{
"example.com",
"google.com",
"localhost",
}
for _, domain := range domains {
@ -24,6 +25,9 @@ func TestTrie_Basic(t *testing.T) {
assert.NotNil(t, node)
assert.True(t, node.Data.(net.IP).Equal(localIP))
assert.NotNil(t, tree.Insert("", localIP))
assert.Nil(t, tree.Search(""))
assert.NotNil(t, tree.Search("localhost"))
assert.Nil(t, tree.Search("www.google.com"))
}
func TestTrie_Wildcard(t *testing.T) {
@ -35,6 +39,11 @@ func TestTrie_Wildcard(t *testing.T) {
".org",
".example.net",
".apple.*",
"+.foo.com",
"+.stun.*.*",
"+.stun.*.*.*",
"+.stun.*.*.*.*",
"stun.l.google.com",
}
for _, domain := range domains {
@ -46,6 +55,9 @@ func TestTrie_Wildcard(t *testing.T) {
assert.NotNil(t, tree.Search("test.org"))
assert.NotNil(t, tree.Search("test.example.net"))
assert.NotNil(t, tree.Search("test.apple.com"))
assert.NotNil(t, tree.Search("test.foo.com"))
assert.NotNil(t, tree.Search("foo.com"))
assert.NotNil(t, tree.Search("global.stun.website.com"))
assert.Nil(t, tree.Search("foo.sub.example.com"))
assert.Nil(t, tree.Search("foo.example.dev"))
assert.Nil(t, tree.Search("example.com"))

View File

@ -70,8 +70,8 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
buf := bufPool.Get().(*bytes.Buffer)
defer buf.Reset()
defer bufPool.Put(buf)
defer buf.Reset()
buf.Write(t.hexPassword)
buf.Write(crlf)
@ -92,8 +92,8 @@ func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn {
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
buf := bufPool.Get().(*bytes.Buffer)
defer buf.Reset()
defer bufPool.Put(buf)
defer buf.Reset()
buf.Write(socks5Addr)
binary.Write(buf, binary.BigEndian, uint16(len(payload)))

View File

@ -31,6 +31,7 @@ type WebsocketConfig struct {
Headers http.Header
TLS bool
SkipCertVerify bool
ServerName string
SessionCache tls.ClientSessionCache
}
@ -132,7 +133,9 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
ClientSessionCache: c.SessionCache,
}
if host := c.Headers.Get("Host"); host != "" {
if c.ServerName != "" {
dialer.TLSClientConfig.ServerName = c.ServerName
} else if host := c.Headers.Get("Host"); host != "" {
dialer.TLSClientConfig.ServerName = host
}
}

View File

@ -12,8 +12,8 @@ import (
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/component/auth"
trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
@ -25,17 +25,30 @@ import (
// General config
type General struct {
Port int `json:"port"`
SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"`
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
Mode T.TunnelMode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"`
ExternalController string `json:"-"`
ExternalUI string `json:"-"`
Secret string `json:"-"`
Inbound
Controller
Mode T.TunnelMode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"`
IPv6 bool `json:"ipv6"`
Interface string `json:"interface-name"`
}
// Inbound
type Inbound struct {
Port int `json:"port"`
SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"`
MixedPort int `json:"mixed-port"`
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
}
// Controller
type Controller struct {
ExternalController string `json:"-"`
ExternalUI string `json:"-"`
Secret string `json:"-"`
}
// DNS config
@ -58,17 +71,14 @@ type FallbackFilter struct {
}
// Experimental config
type Experimental struct {
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
Interface string `yaml:"interface-name"`
}
type Experimental struct{}
// Config is clash config manager
type Config struct {
General *General
DNS *DNS
Experimental *Experimental
Hosts *trie.Trie
Hosts *trie.DomainTrie
Rules []C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy
@ -97,14 +107,17 @@ type RawConfig struct {
Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"`
MixedPort int `yaml:"mixed-port"`
Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address"`
Mode T.TunnelMode `yaml:"mode"`
LogLevel log.LogLevel `yaml:"log-level"`
IPv6 bool `yaml:"ipv6"`
ExternalController string `yaml:"external-controller"`
ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"`
Interface string `yaml:"interface-name"`
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
Hosts map[string]string `yaml:"hosts"`
@ -113,12 +126,6 @@ type RawConfig struct {
Proxy []map[string]interface{} `yaml:"proxies"`
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
Rule []string `yaml:"rules"`
// remove after 1.0
ProxyProviderOld map[string]map[string]interface{} `yaml:"proxy-provider"`
ProxyOld []map[string]interface{} `yaml:"Proxy"`
ProxyGroupOld []map[string]interface{} `yaml:"Proxy Group"`
RuleOld []string `yaml:"Rule"`
}
// Parse config
@ -143,9 +150,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Rule: []string{},
Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{},
Experimental: Experimental{
IgnoreResolveFail: true,
},
DNS: RawDNS{
Enable: false,
FakeIPRange: "198.18.0.1/16",
@ -158,11 +162,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"8.8.8.8",
},
},
// remove after 1.0
RuleOld: []string{},
ProxyOld: []map[string]interface{}{},
ProxyGroupOld: []map[string]interface{}{},
}
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
@ -214,17 +213,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
func parseGeneral(cfg *RawConfig) (*General, error) {
port := cfg.Port
socksPort := cfg.SocksPort
redirPort := cfg.RedirPort
allowLan := cfg.AllowLan
bindAddress := cfg.BindAddress
externalController := cfg.ExternalController
externalUI := cfg.ExternalUI
secret := cfg.Secret
mode := cfg.Mode
logLevel := cfg.LogLevel
// checkout externalUI exist
if externalUI != "" {
externalUI = C.Path.Resolve(externalUI)
@ -233,19 +224,25 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
}
}
general := &General{
Port: port,
SocksPort: socksPort,
RedirPort: redirPort,
AllowLan: allowLan,
BindAddress: bindAddress,
Mode: mode,
LogLevel: logLevel,
ExternalController: externalController,
ExternalUI: externalUI,
Secret: secret,
}
return general, nil
return &General{
Inbound: Inbound{
Port: cfg.Port,
SocksPort: cfg.SocksPort,
RedirPort: cfg.RedirPort,
MixedPort: cfg.MixedPort,
AllowLan: cfg.AllowLan,
BindAddress: cfg.BindAddress,
},
Controller: Controller{
ExternalController: cfg.ExternalController,
ExternalUI: cfg.ExternalUI,
Secret: cfg.Secret,
},
Mode: cfg.Mode,
LogLevel: cfg.LogLevel,
IPv6: cfg.IPv6,
Interface: cfg.Interface,
}, nil
}
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) {
@ -256,18 +253,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider
if len(proxiesConfig) == 0 {
proxiesConfig = cfg.ProxyOld
}
if len(groupsConfig) == 0 {
groupsConfig = cfg.ProxyGroupOld
}
if len(providersConfig) == 0 {
providersConfig = cfg.ProxyProviderOld
}
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
proxyList = append(proxyList, "DIRECT", "REJECT")
@ -308,7 +293,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
pd, err := provider.ParseProxyProvider(name, mapping)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err)
}
providersMap[name] = pd
@ -317,7 +302,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
for _, provider := range providersMap {
log.Infoln("Start initial provider %s", provider.Name())
if err := provider.Initial(); err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", provider.Name(), err)
}
}
@ -363,14 +348,8 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
rules := []C.Rule{}
rulesConfig := cfg.Rule
// remove after 1.0
if len(rulesConfig) == 0 {
rulesConfig = cfg.RuleOld
}
// parse rules
for idx, line := range rulesConfig {
rule := trimArr(strings.Split(line, ","))
@ -400,42 +379,8 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
rule = trimArr(rule)
params = trimArr(params)
var (
parseErr error
parsed C.Rule
)
switch rule[0] {
case "DOMAIN":
parsed = R.NewDomain(payload, target)
case "DOMAIN-SUFFIX":
parsed = R.NewDomainSuffix(payload, target)
case "DOMAIN-KEYWORD":
parsed = R.NewDomainKeyword(payload, target)
case "GEOIP":
noResolve := R.HasNoResolve(params)
parsed = R.NewGEOIP(payload, target, noResolve)
case "IP-CIDR", "IP-CIDR6":
noResolve := R.HasNoResolve(params)
parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRNoResolve(noResolve))
// deprecated when bump to 1.0
case "SOURCE-IP-CIDR":
fallthrough
case "SRC-IP-CIDR":
parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true), R.WithIPCIDRNoResolve(true))
case "SRC-PORT":
parsed, parseErr = R.NewPort(payload, target, true)
case "DST-PORT":
parsed, parseErr = R.NewPort(payload, target, false)
case "MATCH":
fallthrough
// deprecated when bump to 1.0
case "FINAL":
parsed = R.NewMatch(target)
default:
parseErr = fmt.Errorf("unsupported rule type %s", rule[0])
}
parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
if parseErr != nil {
return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error())
}
@ -446,8 +391,14 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
return rules, nil
}
func parseHosts(cfg *RawConfig) (*trie.Trie, error) {
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
tree := trie.New()
// add default hosts
if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil {
println(err.Error())
}
if len(cfg.Hosts) != 0 {
for domain, ipStr := range cfg.Hosts {
ip := net.ParseIP(ipStr)
@ -582,7 +533,7 @@ func parseDNS(cfg RawDNS) (*DNS, error) {
return nil, err
}
var host *trie.Trie
var host *trie.DomainTrie
// fake ip skip host filter
if len(cfg.FakeIPFilter) != 0 {
host = trie.New()

View File

@ -57,7 +57,8 @@ type Conn interface {
type PacketConn interface {
net.PacketConn
Connection
WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
// Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed
// WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
}
type ProxyAdapter interface {

View File

@ -23,8 +23,8 @@ const (
type NetWork int
func (n *NetWork) String() string {
if *n == TCP {
func (n NetWork) String() string {
if n == TCP {
return "tcp"
}
return "udp"

View File

@ -44,7 +44,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, dc.url+"?bla=foo:443", bytes.NewReader(buf))
req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf))
if err != nil {
return req, err
}
@ -75,7 +75,8 @@ func newDoHClient(url string, r *Resolver) *dohClient {
return &dohClient{
url: url,
transport: &http.Transport{
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {

View File

@ -58,9 +58,23 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
func withResolver(resolver *Resolver) handler {
return func(w D.ResponseWriter, r *D.Msg) {
q := r.Question[0]
// return a empty AAAA msg when ipv6 disabled
if !resolver.ipv6 && q.Qtype == D.TypeAAAA {
msg := &D.Msg{}
msg.Answer = []D.RR{}
msg.SetRcode(r, D.RcodeSuccess)
msg.Authoritative = true
msg.RecursionAvailable = true
w.WriteMsg(msg)
return
}
msg, err := resolver.Exchange(r)
if err != nil {
q := r.Question[0]
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
D.HandleFailed(w, r)
return

View File

@ -121,7 +121,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
}
putMsgToCache(r.lruCache, q.String(), msg)
if r.mapping {
if r.mapping || r.fakeip {
ips := r.msgToIP(msg)
for _, ip := range ips {
putMsgToCache(r.lruCache, ip.String(), msg)
@ -151,7 +151,10 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
// IPToHost return fake-ip or redir-host mapping host
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
if r.fakeip {
return r.pool.LookBack(ip)
record, existed := r.pool.LookBack(ip)
if existed {
return record, true
}
}
cache, _ := r.lruCache.Get(ip.String())

View File

@ -4,6 +4,7 @@ import (
"net"
"github.com/Dreamacro/clash/common/sockopt"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
)
@ -42,6 +43,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
if server.Server != nil {
server.Shutdown()
server = &Server{}
address = ""
}
@ -62,7 +64,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
err = sockopt.UDPReuseaddr(p)
if err != nil {
return err
log.Warnln("Failed to Reuse UDP Address: %s", err)
}
address = addr

View File

@ -89,7 +89,7 @@ func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
case len(msg.Extra) != 0:
ttl = msg.Extra[0].Header().Ttl
default:
log.Debugln("[DNS] response msg error: %#v", msg)
log.Debugln("[DNS] response msg empty: %#v", msg)
return
}
@ -111,10 +111,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
}
func isIPRequest(q D.Question) bool {
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
return true
}
return false
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA)
}
func transform(servers []NameServer, resolver *Resolver) []dnsClient {

10
go.mod
View File

@ -5,7 +5,7 @@ go 1.14
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.5
github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v4.1.1+incompatible
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.1.1
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.3.0+incompatible
@ -13,10 +13,10 @@ require (
github.com/miekg/dns v1.1.29
github.com/oschwald/geoip2-golang v1.4.0
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
gopkg.in/eapache/channels.v1 v1.1.0
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2 v2.3.0
)

23
go.sum
View File

@ -5,8 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
@ -27,22 +27,23 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -61,5 +62,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,4 +0,0 @@
#!/bin/bash
# Register qemu-*-static for all supported processors except the
# current one, but also remove all registered binfmt_misc before
docker run --rm --privileged multiarch/qemu-user-static:register --reset --credential yes

View File

@ -4,13 +4,13 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
@ -20,30 +20,15 @@ import (
"github.com/Dreamacro/clash/tunnel"
)
// 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"
if _, fallbackErr := os.Stat(path); fallbackErr == nil {
return ioutil.ReadFile(path)
}
return data, err
}
var (
mux sync.Mutex
)
func readConfig(path string) ([]byte, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
data, err := readRawConfig(path)
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
@ -77,10 +62,11 @@ func ParseWithBytes(buf []byte) (*config.Config, error) {
// ApplyConfig dispatch configure to all parts
func ApplyConfig(cfg *config.Config, force bool) {
mux.Lock()
defer mux.Unlock()
updateUsers(cfg.Users)
if force {
updateGeneral(cfg.General)
}
updateGeneral(cfg.General, force)
updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules)
updateDNS(cfg.DNS)
@ -96,31 +82,23 @@ func GetGeneral() *config.General {
}
general := &config.General{
Port: ports.Port,
SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort,
Authentication: authenticator,
AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(),
Mode: tunnel.Mode(),
LogLevel: log.Level(),
Inbound: config.Inbound{
Port: ports.Port,
SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort,
MixedPort: ports.MixedPort,
Authentication: authenticator,
AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(),
},
Mode: tunnel.Mode(),
LogLevel: log.Level(),
}
return general
}
func updateExperimental(c *config.Config) {
cfg := c.Experimental
tunnel.UpdateExperimental(cfg.IgnoreResolveFail)
if cfg.Interface != "" && c.DNS.Enable {
dialer.DialHook = dialer.DialerWithInterface(cfg.Interface)
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface)
} else {
dialer.DialHook = nil
dialer.ListenPacketHook = nil
}
}
func updateExperimental(c *config.Config) {}
func updateDNS(c *config.DNS) {
if c.Enable == false {
@ -153,7 +131,7 @@ func updateDNS(c *config.DNS) {
}
}
func updateHosts(tree *trie.Trie) {
func updateHosts(tree *trie.DomainTrie) {
resolver.DefaultHosts = tree
}
@ -165,9 +143,22 @@ func updateRules(rules []C.Rule) {
tunnel.UpdateRules(rules)
}
func updateGeneral(general *config.General) {
func updateGeneral(general *config.General, force bool) {
log.SetLevel(general.LogLevel)
tunnel.SetMode(general.Mode)
resolver.DisableIPv6 = !general.IPv6
if general.Interface != "" {
dialer.DialHook = dialer.DialerWithInterface(general.Interface)
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface)
} else {
dialer.DialHook = nil
dialer.ListenPacketHook = nil
}
if !force {
return
}
allowLan := general.AllowLan
P.SetAllowLan(allowLan)
@ -186,6 +177,10 @@ func updateGeneral(general *config.General) {
if err := P.ReCreateRedir(general.RedirPort); err != nil {
log.Errorln("Start Redir server error: %s", err.Error())
}
if err := P.ReCreateMixed(general.MixedPort); err != nil {
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
}
}
func updateUsers(users []auth.AuthUser) {

View File

@ -1,9 +1,9 @@
package hub
import (
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/hub/route"
"github.com/Dreamacro/clash/config"
)
type Option func(*config.Config)

View File

@ -26,6 +26,7 @@ type configSchema struct {
Port *int `json:"port"`
SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"`
MixedPort *int `json:"mixed-port"`
AllowLan *bool `json:"allow-lan"`
BindAddress *string `json:"bind-address"`
Mode *tunnel.TunnelMode `json:"mode"`
@ -65,6 +66,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
if general.Mode != nil {
tunnel.SetMode(*general.Mode)

View File

@ -36,7 +36,7 @@ type Traffic struct {
}
func SetUIPath(path string) {
uiPath = path
uiPath = C.Path.Resolve(path)
}
func Start(addr string, secret string) {

View File

@ -41,7 +41,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
}
continue
}
go handleConn(c, hl.cache)
go HandleConn(c, hl.cache)
}
}()
@ -69,7 +69,7 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache
return
}
func handleConn(conn net.Conn, cache *cache.Cache) {
func HandleConn(conn net.Conn, cache *cache.Cache) {
br := bufio.NewReader(conn)
request, err := http.ReadRequest(br)
if err != nil || request.URL.Host == "" {

View File

@ -4,9 +4,11 @@ import (
"fmt"
"net"
"strconv"
"sync"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/proxy/http"
"github.com/Dreamacro/clash/proxy/mixed"
"github.com/Dreamacro/clash/proxy/redir"
"github.com/Dreamacro/clash/proxy/socks"
)
@ -20,6 +22,15 @@ var (
httpListener *http.HttpListener
redirListener *redir.RedirListener
redirUDPListener *redir.RedirUDPListener
mixedListener *mixed.MixedListener
mixedUDPLister *socks.SockUDPListener
// lock for recreate function
socksMux sync.Mutex
httpMux sync.Mutex
redirMux sync.Mutex
mixedMux sync.Mutex
tunMux sync.Mutex
)
type listener interface {
@ -31,6 +42,7 @@ type Ports struct {
Port int `json:"port"`
SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"`
MixedPort int `json:"mixed-port"`
}
func AllowLan() bool {
@ -50,6 +62,9 @@ func SetBindAddress(host string) {
}
func ReCreateHTTP(port int) error {
httpMux.Lock()
defer httpMux.Unlock()
addr := genAddr(bindAddress, port, allowLan)
if httpListener != nil {
@ -74,6 +89,9 @@ func ReCreateHTTP(port int) error {
}
func ReCreateSocks(port int) error {
socksMux.Lock()
defer socksMux.Unlock()
addr := genAddr(bindAddress, port, allowLan)
shouldTCPIgnore := false
@ -123,6 +141,9 @@ func ReCreateSocks(port int) error {
}
func ReCreateRedir(port int) error {
redirMux.Lock()
defer redirMux.Unlock()
addr := genAddr(bindAddress, port, allowLan)
if redirListener != nil {
@ -159,6 +180,55 @@ func ReCreateRedir(port int) error {
return nil
}
func ReCreateMixed(port int) error {
mixedMux.Lock()
defer mixedMux.Unlock()
addr := genAddr(bindAddress, port, allowLan)
shouldTCPIgnore := false
shouldUDPIgnore := false
if mixedListener != nil {
if mixedListener.Address() != addr {
mixedListener.Close()
mixedListener = nil
} else {
shouldTCPIgnore = true
}
}
if mixedUDPLister != nil {
if mixedUDPLister.Address() != addr {
mixedUDPLister.Close()
mixedUDPLister = nil
} else {
shouldUDPIgnore = true
}
}
if shouldTCPIgnore && shouldUDPIgnore {
return nil
}
if portIsZero(addr) {
return nil
}
var err error
mixedListener, err = mixed.NewMixedProxy(addr)
if err != nil {
return err
}
mixedUDPLister, err = socks.NewSocksUDPProxy(addr)
if err != nil {
mixedListener.Close()
return err
}
return nil
}
// GetPorts return the ports of proxy servers
func GetPorts() *Ports {
ports := &Ports{}
@ -181,6 +251,12 @@ func GetPorts() *Ports {
ports.RedirPort = port
}
if mixedListener != nil {
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
port, _ := strconv.Atoi(portStr)
ports.MixedPort = port
}
return ports
}

41
proxy/mixed/conn.go Normal file
View File

@ -0,0 +1,41 @@
package mixed
import (
"bufio"
"net"
)
type BufferedConn struct {
r *bufio.Reader
net.Conn
}
func NewBufferedConn(c net.Conn) *BufferedConn {
return &BufferedConn{bufio.NewReader(c), c}
}
// Reader returns the internal bufio.Reader.
func (c *BufferedConn) Reader() *bufio.Reader {
return c.r
}
// Peek returns the next n bytes without advancing the reader.
func (c *BufferedConn) Peek(n int) ([]byte, error) {
return c.r.Peek(n)
}
func (c *BufferedConn) Read(p []byte) (int, error) {
return c.r.Read(p)
}
func (c *BufferedConn) ReadByte() (byte, error) {
return c.r.ReadByte()
}
func (c *BufferedConn) UnreadByte() error {
return c.r.UnreadByte()
}
func (c *BufferedConn) Buffered() int {
return c.r.Buffered()
}

69
proxy/mixed/mixed.go Normal file
View File

@ -0,0 +1,69 @@
package mixed
import (
"net"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/socks5"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/proxy/http"
"github.com/Dreamacro/clash/proxy/socks"
)
type MixedListener struct {
net.Listener
address string
closed bool
cache *cache.Cache
}
func NewMixedProxy(addr string) (*MixedListener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)}
go func() {
log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr)
for {
c, err := ml.Accept()
if err != nil {
if ml.closed {
break
}
continue
}
go handleConn(c, ml.cache)
}
}()
return ml, nil
}
func (l *MixedListener) Close() {
l.closed = true
l.Listener.Close()
}
func (l *MixedListener) Address() string {
return l.address
}
func handleConn(conn net.Conn, cache *cache.Cache) {
bufConn := NewBufferedConn(conn)
head, err := bufConn.Peek(1)
if err != nil {
return
}
if head[0] == socks5.Version {
socks.HandleSocks(bufConn)
return
}
http.HandleConn(bufConn, cache)
}

View File

@ -36,7 +36,7 @@ func NewSocksProxy(addr string) (*SockListener, error) {
}
continue
}
go handleSocks(c)
go HandleSocks(c)
}
}()
@ -52,13 +52,15 @@ func (l *SockListener) Address() string {
return l.address
}
func handleSocks(conn net.Conn) {
func HandleSocks(conn net.Conn) {
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
if err != nil {
conn.Close()
return
}
conn.(*net.TCPConn).SetKeepAlive(true)
if c, ok := conn.(*net.TCPConn); ok {
c.SetKeepAlive(true)
}
if command == socks5.CmdUDPAssociate {
defer conn.Close()
io.Copy(ioutil.Discard, conn)

View File

@ -8,6 +8,7 @@ import (
"github.com/Dreamacro/clash/common/sockopt"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
)
@ -25,7 +26,7 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
if err != nil {
return nil, err
log.Warnln("Failed to Reuse UDP Address: %s", err)
}
sl := &SockUDPListener{l, addr, false}

41
rules/parser.go Normal file
View File

@ -0,0 +1,41 @@
package rules
import (
"fmt"
C "github.com/Dreamacro/clash/constant"
)
func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
var (
parseErr error
parsed C.Rule
)
switch tp {
case "DOMAIN":
parsed = NewDomain(payload, target)
case "DOMAIN-SUFFIX":
parsed = NewDomainSuffix(payload, target)
case "DOMAIN-KEYWORD":
parsed = NewDomainKeyword(payload, target)
case "GEOIP":
noResolve := HasNoResolve(params)
parsed = NewGEOIP(payload, target, noResolve)
case "IP-CIDR", "IP-CIDR6":
noResolve := HasNoResolve(params)
parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve))
case "SRC-IP-CIDR":
parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true))
case "SRC-PORT":
parsed, parseErr = NewPort(payload, target, true)
case "DST-PORT":
parsed, parseErr = NewPort(payload, target, false)
case "MATCH":
parsed = NewMatch(target)
default:
parseErr = fmt.Errorf("unsupported rule type %s", tp)
}
return parsed, parseErr
}

View File

@ -2,6 +2,7 @@ package tunnel
import (
"bufio"
"errors"
"io"
"net"
"net/http"
@ -9,6 +10,7 @@ import (
"time"
adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/common/pool"
@ -81,12 +83,25 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
}
}
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error {
defer packet.Drop()
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
return
// local resolve UDP dns
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return err
}
metadata.DstIP = ip
}
addr := metadata.UDPAddr()
if addr == nil {
return errors.New("udp addr invalid")
}
_, err := pc.WriteTo(packet.Data(), addr)
return err
}
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {

View File

@ -3,6 +3,7 @@ package tunnel
import (
"encoding/json"
"errors"
"strings"
)
type TunnelMode int
@ -26,7 +27,7 @@ const (
func (m *TunnelMode) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := ModeMapping[tp]
mode, exist := ModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
@ -38,7 +39,7 @@ func (m *TunnelMode) UnmarshalJSON(data []byte) error {
func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tp string
unmarshal(&tp)
mode, exist := ModeMapping[tp]
mode, exist := ModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
@ -59,11 +60,11 @@ func (m TunnelMode) MarshalYAML() (interface{}, error) {
func (m TunnelMode) String() string {
switch m {
case Global:
return "Global"
return "global"
case Rule:
return "Rule"
return "rule"
case Direct:
return "Direct"
return "direct"
default:
return "Unknown"
}

View File

@ -21,6 +21,7 @@ type trackerInfo struct {
Start time.Time `json:"start"`
Chain C.Chain `json:"chains"`
Rule string `json:"rule"`
RulePayload string `json:"rulePayload"`
}
type tcpTracker struct {
@ -56,10 +57,6 @@ func (tt *tcpTracker) Close() error {
func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker {
uuid, _ := uuid.NewV4()
ruleType := ""
if rule != nil {
ruleType = rule.RuleType().String()
}
t := &tcpTracker{
Conn: conn,
@ -69,10 +66,15 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
Start: time.Now(),
Metadata: metadata,
Chain: conn.Chains(),
Rule: ruleType,
Rule: "",
},
}
if rule != nil {
t.trackerInfo.Rule = rule.RuleType().String()
t.trackerInfo.RulePayload = rule.Payload()
}
manager.Join(t)
return t
}
@ -103,14 +105,6 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
return n, err
}
func (ut *udpTracker) WriteWithMetadata(p []byte, metadata *C.Metadata) (int, error) {
n, err := ut.PacketConn.WriteWithMetadata(p, metadata)
upload := int64(n)
ut.manager.Upload() <- upload
ut.UploadTotal += upload
return n, err
}
func (ut *udpTracker) Close() error {
ut.manager.Leave(ut)
return ut.PacketConn.Close()
@ -118,10 +112,6 @@ func (ut *udpTracker) Close() error {
func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
uuid, _ := uuid.NewV4()
ruleType := ""
if rule != nil {
ruleType = rule.RuleType().String()
}
ut := &udpTracker{
PacketConn: conn,
@ -131,10 +121,15 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
Start: time.Now(),
Metadata: metadata,
Chain: conn.Chains(),
Rule: ruleType,
Rule: "",
},
}
if rule != nil {
ut.trackerInfo.Rule = rule.RuleType().String()
ut.trackerInfo.RulePayload = rule.Payload()
}
manager.Join(ut)
return ut
}

View File

@ -28,9 +28,6 @@ var (
configMux sync.RWMutex
enhancedMode *dns.Resolver
// experimental features
ignoreResolveFail bool
// Outbound Rule
mode = Rule
@ -82,13 +79,6 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid
configMux.Unlock()
}
// UpdateExperimental handle update experimental config
func UpdateExperimental(value bool) {
configMux.Lock()
ignoreResolveFail = value
configMux.Unlock()
}
// Mode return current mode
func Mode() TunnelMode {
return mode
@ -318,9 +308,6 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
if !resolved && shouldResolveIP(rule, metadata) {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
if !ignoreResolveFail {
return nil, nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error())
}
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
} else {
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())