Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
59968fff1c | |||
7c62fe41b4 | |||
2781090405 | |||
14c9cf1b97 | |||
3dfff84cc3 | |||
5f3db72422 | |||
18bb285a90 | |||
60bad66bc3 | |||
99b34e8d8b | |||
9f1d85ab6e | |||
4323dd24d0 | |||
59bda1d547 | |||
1c760935f4 | |||
4f674755ce | |||
f1b792bd26 | |||
58c077b45e | |||
1854199c47 | |||
ecac8eb8e5 | |||
48cff50a4c | |||
fb628e9c62 | |||
2dece02df6 | |||
8f32e6a60f | |||
98614a1f3f | |||
c1b4c94b9c | |||
7ddbc12cdb | |||
1a217e21e9 | |||
147a7ce779 | |||
fb0289bb4c | |||
3e7970612a | |||
46244a6496 | |||
71d30e6654 | |||
008731c249 | |||
5628f97da1 | |||
8d0c6c6e66 | |||
5073c3cde8 | |||
3a27cfc4a1 | |||
3638b077cd |
53
.github/workflows/docker.yml
vendored
Normal file
53
.github/workflows/docker.yml
vendored
Normal 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 .
|
6
.github/workflows/go.yml
vendored
6
.github/workflows/go.yml
vendored
@ -7,15 +7,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.14.x
|
go-version: 1.14.x
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
@ -3,10 +3,11 @@ FROM golang:alpine as builder
|
|||||||
RUN apk add --no-cache make git && \
|
RUN apk add --no-cache make git && \
|
||||||
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
|
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
|
||||||
WORKDIR /clash-src
|
WORKDIR /clash-src
|
||||||
|
COPY --from=tonistiigi/xx:golang / /
|
||||||
COPY . /clash-src
|
COPY . /clash-src
|
||||||
RUN go mod download && \
|
RUN go mod download && \
|
||||||
make linux-amd64 && \
|
make docker && \
|
||||||
mv ./bin/clash-linux-amd64 /clash
|
mv ./bin/clash-docker /clash
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
|
@ -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"]
|
|
@ -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"]
|
|
5
Makefile
5
Makefile
@ -2,7 +2,7 @@ NAME=clash
|
|||||||
BINDIR=bin
|
BINDIR=bin
|
||||||
VERSION=$(shell git describe --tags || echo "unknown 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 -trimpath -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)" \
|
||||||
-w -s'
|
-w -s'
|
||||||
|
|
||||||
@ -29,6 +29,9 @@ WINDOWS_ARCH_LIST = \
|
|||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||||
|
|
||||||
|
docker:
|
||||||
|
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64:
|
darwin-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
29
README.md
29
README.md
@ -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 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:
|
Check Clash version with:
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ $ clash -v
|
|||||||
|
|
||||||
## Daemonize Clash
|
## 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:
|
In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way:
|
||||||
|
|
||||||
@ -81,6 +81,9 @@ port: 7890
|
|||||||
# port of SOCKS5
|
# port of SOCKS5
|
||||||
socks-port: 7891
|
socks-port: 7891
|
||||||
|
|
||||||
|
# (HTTP and SOCKS5 in one port)
|
||||||
|
# mixed-port: 7890
|
||||||
|
|
||||||
# redir port for Linux and macOS
|
# redir port for Linux and macOS
|
||||||
# redir-port: 7892
|
# redir-port: 7892
|
||||||
|
|
||||||
@ -92,8 +95,10 @@ allow-lan: false
|
|||||||
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
|
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
|
||||||
# bind-address: "*"
|
# bind-address: "*"
|
||||||
|
|
||||||
# Rule / Global / Direct (default is 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
|
||||||
mode: Rule
|
|
||||||
|
# rule / global / direct (default is rule)
|
||||||
|
mode: rule
|
||||||
|
|
||||||
# set log level to stdout (default is info)
|
# set log level to stdout (default is info)
|
||||||
# info / warning / error / debug / silent
|
# info / warning / error / debug / silent
|
||||||
@ -109,9 +114,6 @@ external-controller: 127.0.0.1:9090
|
|||||||
# Secret for RESTful API (Optional)
|
# Secret for RESTful API (Optional)
|
||||||
# secret: ""
|
# 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 of local SOCKS5/HTTP(S) server
|
||||||
@ -119,16 +121,18 @@ experimental:
|
|||||||
# - "user1:pass1"
|
# - "user1:pass1"
|
||||||
# - "user2:pass2"
|
# - "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)
|
# # 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:
|
# hosts:
|
||||||
# '*.clash.dev': 127.0.0.1
|
# '*.clash.dev': 127.0.0.1
|
||||||
# '.dev': 127.0.0.1
|
# '.dev': 127.0.0.1
|
||||||
# 'alpha.clash.dev': '::1'
|
# 'alpha.clash.dev': '::1'
|
||||||
|
# '+.foo.dev': 127.0.0.1
|
||||||
|
|
||||||
# dns:
|
# dns:
|
||||||
# enable: true # set true to enable dns (default is false)
|
# enable: true # set true to enable dns (default is false)
|
||||||
# ipv6: false # default is false
|
# ipv6: false # it only affect the dns server response, default is false
|
||||||
# listen: 0.0.0.0:53
|
# listen: 0.0.0.0:53
|
||||||
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
|
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
|
||||||
# # - 114.114.114.114
|
# # - 114.114.114.114
|
||||||
@ -165,7 +169,6 @@ proxies:
|
|||||||
password: "password"
|
password: "password"
|
||||||
# udp: true
|
# udp: true
|
||||||
|
|
||||||
# old obfs configuration format remove after prerelease
|
|
||||||
- name: "ss2"
|
- name: "ss2"
|
||||||
type: ss
|
type: ss
|
||||||
server: server
|
server: server
|
||||||
@ -206,6 +209,7 @@ proxies:
|
|||||||
# udp: true
|
# udp: true
|
||||||
# tls: true
|
# tls: true
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
|
# servername: example.com # priority over wss host
|
||||||
# network: ws
|
# network: ws
|
||||||
# ws-path: /path
|
# ws-path: /path
|
||||||
# ws-headers:
|
# ws-headers:
|
||||||
@ -291,6 +295,7 @@ proxy-groups:
|
|||||||
- ss1
|
- ss1
|
||||||
- ss2
|
- ss2
|
||||||
- vmess1
|
- vmess1
|
||||||
|
# tolerance: 150
|
||||||
url: 'http://www.gstatic.com/generate_204'
|
url: 'http://www.gstatic.com/generate_204'
|
||||||
interval: 300
|
interval: 300
|
||||||
|
|
||||||
@ -362,8 +367,6 @@ rules:
|
|||||||
- GEOIP,CN,DIRECT
|
- GEOIP,CN,DIRECT
|
||||||
- DST-PORT,80,DIRECT
|
- DST-PORT,80,DIRECT
|
||||||
- SRC-PORT,7777,DIRECT
|
- SRC-PORT,7777,DIRECT
|
||||||
# FINAL would remove after prerelease
|
|
||||||
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
|
|
||||||
- MATCH,auto
|
- MATCH,auto
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
@ -390,4 +393,4 @@ https://clash.gitbook.io/
|
|||||||
- [x] Redir proxy
|
- [x] Redir proxy
|
||||||
- [x] UDP support
|
- [x] UDP support
|
||||||
- [x] Connection manager
|
- [x] Connection manager
|
||||||
- [ ] Event API
|
- ~~[ ] Event API~~
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/socks5"
|
"github.com/Dreamacro/clash/component/socks5"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -16,7 +17,8 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
|||||||
|
|
||||||
switch target[0] {
|
switch target[0] {
|
||||||
case socks5.AtypDomainName:
|
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]))
|
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||||
case socks5.AtypIPv4:
|
case socks5.AtypIPv4:
|
||||||
ip := net.IP(target[1 : 1+net.IPv4len])
|
ip := net.IP(target[1 : 1+net.IPv4len])
|
||||||
@ -38,6 +40,9 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
|||||||
port = "80"
|
port = "80"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trim FQDN (#737)
|
||||||
|
host = strings.TrimRight(host, ".")
|
||||||
|
|
||||||
metadata := &C.Metadata{
|
metadata := &C.Metadata{
|
||||||
NetWork: C.TCP,
|
NetWork: C.TCP,
|
||||||
AddrType: C.AtypDomainName,
|
AddrType: C.AtypDomainName,
|
||||||
|
@ -78,13 +78,8 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
|||||||
return &conn{c, []string{a.Name()}}
|
return &conn{c, []string{a.Name()}}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PacketConn interface {
|
|
||||||
net.PacketConn
|
|
||||||
WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetConn struct {
|
type packetConn struct {
|
||||||
PacketConn
|
net.PacketConn
|
||||||
chain C.Chain
|
chain C.Chain
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +91,7 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
|||||||
c.chain = append(c.chain, a.Name())
|
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()}}
|
return &packetConn{pc, []string{a.Name()}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,17 +35,6 @@ type directPacketConn struct {
|
|||||||
net.PacketConn
|
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 {
|
func NewDirect() *Direct {
|
||||||
return &Direct{
|
return &Direct{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
|
@ -37,10 +37,6 @@ type ShadowSocksOption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Plugin string `proxy:"plugin,omitempty"`
|
Plugin string `proxy:"plugin,omitempty"`
|
||||||
PluginOpts map[string]interface{} `proxy:"plugin-opts,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 {
|
type simpleObfsOption struct {
|
||||||
@ -122,17 +118,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
var obfsOption *simpleObfsOption
|
var obfsOption *simpleObfsOption
|
||||||
obfsMode := ""
|
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})
|
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||||
if option.Plugin == "obfs" {
|
if option.Plugin == "obfs" {
|
||||||
opts := simpleObfsOption{Host: "bing.com"}
|
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)
|
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) {
|
func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
n, _, e := spc.PacketConn.ReadFrom(b)
|
n, _, e := spc.PacketConn.ReadFrom(b)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
@ -164,14 +164,6 @@ func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|||||||
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
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) {
|
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
n, _, e := uc.PacketConn.ReadFrom(b)
|
n, _, e := uc.PacketConn.ReadFrom(b)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
@ -71,7 +71,7 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pc := t.instance.PacketConn(c)
|
pc := t.instance.PacketConn(c)
|
||||||
return newPacketConn(&trojanPacketConn{pc, c}, t), err
|
return newPacketConn(pc, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) MarshalJSON() ([]byte, error) {
|
func (t *Trojan) MarshalJSON() ([]byte, error) {
|
||||||
@ -105,12 +105,3 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
instance: trojan.New(tOption),
|
instance: trojan.New(tOption),
|
||||||
}, nil
|
}, 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)
|
|
||||||
}
|
|
||||||
|
@ -35,6 +35,7 @@ type VmessOption struct {
|
|||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
@ -66,6 +67,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
wsOpts.TLS = true
|
wsOpts.TLS = true
|
||||||
wsOpts.SessionCache = getClientSessionCache()
|
wsOpts.SessionCache = getClientSessionCache()
|
||||||
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
||||||
|
wsOpts.ServerName = v.option.ServerName
|
||||||
}
|
}
|
||||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||||
case "http":
|
case "http":
|
||||||
@ -87,6 +89,11 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
SessionCache: getClientSessionCache(),
|
SessionCache: getClientSessionCache(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.option.ServerName != "" {
|
||||||
|
tlsOpts.Host = v.option.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
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) {
|
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
if err != nil {
|
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)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
@ -123,7 +130,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||||
if err != nil {
|
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)
|
tcpKeepAlive(c)
|
||||||
c, err = v.StreamConn(c, metadata)
|
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)
|
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) {
|
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
n, err := uc.Conn.Read(b)
|
n, err := uc.Conn.Read(b)
|
||||||
return n, uc.rAddr, err
|
return n, uc.rAddr, err
|
||||||
|
@ -101,7 +101,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||||||
var group C.ProxyAdapter
|
var group C.ProxyAdapter
|
||||||
switch groupOption.Type {
|
switch groupOption.Type {
|
||||||
case "url-test":
|
case "url-test":
|
||||||
group = NewURLTest(groupName, providers)
|
opts := parseURLTestOption(config)
|
||||||
|
group = NewURLTest(groupName, providers, opts...)
|
||||||
case "select":
|
case "select":
|
||||||
group = NewSelector(groupName, providers)
|
group = NewSelector(groupName, providers)
|
||||||
case "fallback":
|
case "fallback":
|
||||||
|
@ -11,8 +11,19 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
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 {
|
type URLTest struct {
|
||||||
*outbound.Base
|
*outbound.Base
|
||||||
|
tolerance uint16
|
||||||
|
lastDelay uint16
|
||||||
|
fastNode C.Proxy
|
||||||
single *singledo.Single
|
single *singledo.Single
|
||||||
fastSingle *singledo.Single
|
fastSingle *singledo.Single
|
||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
@ -52,6 +63,13 @@ func (u *URLTest) proxies() []C.Proxy {
|
|||||||
|
|
||||||
func (u *URLTest) fast() C.Proxy {
|
func (u *URLTest) fast() C.Proxy {
|
||||||
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
|
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()
|
proxies := u.proxies()
|
||||||
fast := proxies[0]
|
fast := proxies[0]
|
||||||
min := fast.LastDelay()
|
min := fast.LastDelay()
|
||||||
@ -66,6 +84,9 @@ func (u *URLTest) fast() C.Proxy {
|
|||||||
min = delay
|
min = delay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.fastNode = fast
|
||||||
|
u.lastDelay = fast.LastDelay()
|
||||||
return fast, nil
|
return fast, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -88,11 +109,30 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
|
func parseURLTestOption(config map[string]interface{}) []urlTestOption {
|
||||||
return &URLTest{
|
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),
|
Base: outbound.NewBase(name, "", C.URLTest, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
fastSingle: singledo.NewSingle(time.Second * 10),
|
fastSingle: singledo.NewSingle(time.Second * 10),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
option(urlTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlTest
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
fileMode os.FileMode = 0666
|
fileMode os.FileMode = 0666
|
||||||
|
dirMode os.FileMode = 0755
|
||||||
)
|
)
|
||||||
|
|
||||||
type parser = func([]byte) (interface{}, error)
|
type parser = func([]byte) (interface{}, error)
|
||||||
@ -35,10 +37,12 @@ func (f *fetcher) VehicleType() VehicleType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) Initial() (interface{}, error) {
|
func (f *fetcher) Initial() (interface{}, error) {
|
||||||
var buf []byte
|
var (
|
||||||
var err error
|
buf []byte
|
||||||
var isLocal bool
|
err error
|
||||||
if stat, err := os.Stat(f.vehicle.Path()); err == nil {
|
isLocal bool
|
||||||
|
)
|
||||||
|
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||||
buf, err = ioutil.ReadFile(f.vehicle.Path())
|
buf, err = ioutil.ReadFile(f.vehicle.Path())
|
||||||
modTime := stat.ModTime()
|
modTime := stat.ModTime()
|
||||||
f.updatedAt = &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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +105,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
|||||||
return nil, false, err
|
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
|
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 {
|
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
|
||||||
var ticker *time.Ticker
|
var ticker *time.Ticker
|
||||||
if interval != 0 {
|
if interval != 0 {
|
||||||
|
@ -142,8 +142,10 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
|||||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||||
pp.proxies = proxies
|
pp.proxies = proxies
|
||||||
pp.healthCheck.setProxy(proxies)
|
pp.healthCheck.setProxy(proxies)
|
||||||
|
if pp.healthCheck.auto() {
|
||||||
go pp.healthCheck.check()
|
go pp.healthCheck.check()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func stopProxyProvider(pd *ProxySetProvider) {
|
func stopProxyProvider(pd *ProxySetProvider) {
|
||||||
pd.healthCheck.close()
|
pd.healthCheck.close()
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"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
|
// Pool is a implementation about fake ip generator without storage
|
||||||
@ -16,7 +16,7 @@ type Pool struct {
|
|||||||
gateway uint32
|
gateway uint32
|
||||||
offset uint32
|
offset uint32
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
host *trie.Trie
|
host *trie.DomainTrie
|
||||||
cache *cache.LruCache
|
cache *cache.LruCache
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ func uintToIP(v uint32) net.IP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New return Pool instance
|
// 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
|
min := ipToUint(ipnet.IP) + 2
|
||||||
|
|
||||||
ones, bits := ipnet.Mask.Size()
|
ones, bits := ipnet.Mask.Size()
|
||||||
|
@ -5,13 +5,17 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultResolver aim to resolve ip
|
// DefaultResolver aim to resolve ip
|
||||||
DefaultResolver Resolver
|
DefaultResolver Resolver
|
||||||
|
|
||||||
|
// DisableIPv6 means don't resolve ipv6 host
|
||||||
|
// default value is true
|
||||||
|
DisableIPv6 = true
|
||||||
|
|
||||||
// DefaultHosts aim to resolve hosts
|
// DefaultHosts aim to resolve hosts
|
||||||
DefaultHosts = trie.New()
|
DefaultHosts = trie.New()
|
||||||
)
|
)
|
||||||
@ -19,6 +23,7 @@ var (
|
|||||||
var (
|
var (
|
||||||
ErrIPNotFound = errors.New("couldn't find ip")
|
ErrIPNotFound = errors.New("couldn't find ip")
|
||||||
ErrIPVersion = errors.New("ip version error")
|
ErrIPVersion = errors.New("ip version error")
|
||||||
|
ErrIPv6Disabled = errors.New("ipv6 disabled")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resolver interface {
|
type Resolver interface {
|
||||||
@ -63,6 +68,10 @@ func ResolveIPv4(host string) (net.IP, error) {
|
|||||||
|
|
||||||
// ResolveIPv6 with a host, return ipv6
|
// ResolveIPv6 with a host, return ipv6
|
||||||
func ResolveIPv6(host string) (net.IP, error) {
|
func ResolveIPv6(host string) (net.IP, error) {
|
||||||
|
if DisableIPv6 {
|
||||||
|
return nil, ErrIPv6Disabled
|
||||||
|
}
|
||||||
|
|
||||||
if node := DefaultHosts.Search(host); node != nil {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
if ip := node.Data.(net.IP).To16(); ip != nil {
|
if ip := node.Data.(net.IP).To16(); ip != nil {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
@ -102,7 +111,12 @@ func ResolveIP(host string) (net.IP, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if DefaultResolver != nil {
|
if DefaultResolver != nil {
|
||||||
|
if DisableIPv6 {
|
||||||
|
return DefaultResolver.ResolveIPv4(host)
|
||||||
|
}
|
||||||
return DefaultResolver.ResolveIP(host)
|
return DefaultResolver.ResolveIP(host)
|
||||||
|
} else if DisableIPv6 {
|
||||||
|
return ResolveIPv4(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := net.ParseIP(host)
|
ip := net.ParseIP(host)
|
||||||
|
@ -21,6 +21,8 @@ func (err Error) Error() string {
|
|||||||
// Command is request commands as defined in RFC 1928 section 4.
|
// Command is request commands as defined in RFC 1928 section 4.
|
||||||
type Command = uint8
|
type Command = uint8
|
||||||
|
|
||||||
|
const Version = 5
|
||||||
|
|
||||||
// SOCKS request commands as defined in RFC 1928 section 4.
|
// SOCKS request commands as defined in RFC 1928 section 4.
|
||||||
const (
|
const (
|
||||||
CmdConnect Command = 1
|
CmdConnect Command = 1
|
||||||
@ -227,6 +229,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if buf[1] == 2 {
|
if buf[1] == 2 {
|
||||||
|
if user == nil {
|
||||||
|
return nil, ErrAuth
|
||||||
|
}
|
||||||
|
|
||||||
// password protocol version
|
// password protocol version
|
||||||
authMsg := &bytes.Buffer{}
|
authMsg := &bytes.Buffer{}
|
||||||
authMsg.WriteByte(1)
|
authMsg.WriteByte(1)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
wildcard = "*"
|
wildcard = "*"
|
||||||
dotWildcard = ""
|
dotWildcard = ""
|
||||||
|
complexWildcard = "+"
|
||||||
domainStep = "."
|
domainStep = "."
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,9 +17,9 @@ var (
|
|||||||
ErrInvalidDomain = errors.New("invalid domain")
|
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)
|
// support wildcard domain (e.g *.google.com)
|
||||||
type Trie struct {
|
type DomainTrie struct {
|
||||||
root *Node
|
root *Node
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,9 +30,13 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
|||||||
|
|
||||||
parts := strings.Split(domain, domainStep)
|
parts := strings.Split(domain, domainStep)
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
|
if parts[0] == "" {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return parts, true
|
||||||
|
}
|
||||||
|
|
||||||
for _, part := range parts[1:] {
|
for _, part := range parts[1:] {
|
||||||
if part == "" {
|
if part == "" {
|
||||||
return nil, false
|
return nil, false
|
||||||
@ -47,12 +52,25 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
|||||||
// 2. *.example.com
|
// 2. *.example.com
|
||||||
// 3. subdomain.*.example.com
|
// 3. subdomain.*.example.com
|
||||||
// 4. .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)
|
parts, valid := validAndSplitDomain(domain)
|
||||||
if !valid {
|
if !valid {
|
||||||
return ErrInvalidDomain
|
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
|
node := t.root
|
||||||
// reverse storage domain part to save space
|
// reverse storage domain part to save space
|
||||||
for i := len(parts) - 1; i >= 0; i-- {
|
for i := len(parts) - 1; i >= 0; i-- {
|
||||||
@ -65,7 +83,6 @@ func (t *Trie) Insert(domain string, data interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.Data = data
|
node.Data = data
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search is the most important part of the Trie.
|
// 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
|
// 1. static part
|
||||||
// 2. wildcard domain
|
// 2. wildcard domain
|
||||||
// 2. dot wildcard domain
|
// 2. dot wildcard domain
|
||||||
func (t *Trie) Search(domain string) *Node {
|
func (t *DomainTrie) Search(domain string) *Node {
|
||||||
parts, valid := validAndSplitDomain(domain)
|
parts, valid := validAndSplitDomain(domain)
|
||||||
if !valid || parts[0] == "" {
|
if !valid || parts[0] == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n := t.root
|
n := t.search(t.root, parts)
|
||||||
var dotWildcardNode *Node
|
|
||||||
var wildcardNode *Node
|
|
||||||
for i := len(parts) - 1; i >= 0; i-- {
|
|
||||||
part := parts[i]
|
|
||||||
|
|
||||||
if node := n.getChild(dotWildcard); node != nil {
|
if n == nil || n.Data == 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 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new, empty Trie.
|
func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
||||||
func New() *Trie {
|
if len(parts) == 0 {
|
||||||
return &Trie{root: newNode(nil)}
|
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)}
|
||||||
}
|
}
|
@ -14,6 +14,7 @@ func TestTrie_Basic(t *testing.T) {
|
|||||||
domains := []string{
|
domains := []string{
|
||||||
"example.com",
|
"example.com",
|
||||||
"google.com",
|
"google.com",
|
||||||
|
"localhost",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
@ -24,6 +25,9 @@ func TestTrie_Basic(t *testing.T) {
|
|||||||
assert.NotNil(t, node)
|
assert.NotNil(t, node)
|
||||||
assert.True(t, node.Data.(net.IP).Equal(localIP))
|
assert.True(t, node.Data.(net.IP).Equal(localIP))
|
||||||
assert.NotNil(t, tree.Insert("", 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) {
|
func TestTrie_Wildcard(t *testing.T) {
|
||||||
@ -35,6 +39,11 @@ func TestTrie_Wildcard(t *testing.T) {
|
|||||||
".org",
|
".org",
|
||||||
".example.net",
|
".example.net",
|
||||||
".apple.*",
|
".apple.*",
|
||||||
|
"+.foo.com",
|
||||||
|
"+.stun.*.*",
|
||||||
|
"+.stun.*.*.*",
|
||||||
|
"+.stun.*.*.*.*",
|
||||||
|
"stun.l.google.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range domains {
|
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.org"))
|
||||||
assert.NotNil(t, tree.Search("test.example.net"))
|
assert.NotNil(t, tree.Search("test.example.net"))
|
||||||
assert.NotNil(t, tree.Search("test.apple.com"))
|
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.sub.example.com"))
|
||||||
assert.Nil(t, tree.Search("foo.example.dev"))
|
assert.Nil(t, tree.Search("foo.example.dev"))
|
||||||
assert.Nil(t, tree.Search("example.com"))
|
assert.Nil(t, tree.Search("example.com"))
|
@ -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 {
|
func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
|
||||||
buf := bufPool.Get().(*bytes.Buffer)
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
defer buf.Reset()
|
|
||||||
defer bufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
|
defer buf.Reset()
|
||||||
|
|
||||||
buf.Write(t.hexPassword)
|
buf.Write(t.hexPassword)
|
||||||
buf.Write(crlf)
|
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) {
|
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||||
buf := bufPool.Get().(*bytes.Buffer)
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
defer buf.Reset()
|
|
||||||
defer bufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
|
defer buf.Reset()
|
||||||
|
|
||||||
buf.Write(socks5Addr)
|
buf.Write(socks5Addr)
|
||||||
binary.Write(buf, binary.BigEndian, uint16(len(payload)))
|
binary.Write(buf, binary.BigEndian, uint16(len(payload)))
|
||||||
|
@ -31,6 +31,7 @@ type WebsocketConfig struct {
|
|||||||
Headers http.Header
|
Headers http.Header
|
||||||
TLS bool
|
TLS bool
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
|
ServerName string
|
||||||
SessionCache tls.ClientSessionCache
|
SessionCache tls.ClientSessionCache
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +133,9 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
|||||||
ClientSessionCache: c.SessionCache,
|
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
|
dialer.TLSClientConfig.ServerName = host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
153
config/config.go
153
config/config.go
@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"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/fakeip"
|
||||||
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
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"
|
||||||
@ -25,14 +25,27 @@ import (
|
|||||||
|
|
||||||
// General config
|
// General config
|
||||||
type General struct {
|
type General struct {
|
||||||
|
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"`
|
Port int `json:"port"`
|
||||||
SocksPort int `json:"socks-port"`
|
SocksPort int `json:"socks-port"`
|
||||||
RedirPort int `json:"redir-port"`
|
RedirPort int `json:"redir-port"`
|
||||||
|
MixedPort int `json:"mixed-port"`
|
||||||
Authentication []string `json:"authentication"`
|
Authentication []string `json:"authentication"`
|
||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
BindAddress string `json:"bind-address"`
|
BindAddress string `json:"bind-address"`
|
||||||
Mode T.TunnelMode `json:"mode"`
|
}
|
||||||
LogLevel log.LogLevel `json:"log-level"`
|
|
||||||
|
// Controller
|
||||||
|
type Controller struct {
|
||||||
ExternalController string `json:"-"`
|
ExternalController string `json:"-"`
|
||||||
ExternalUI string `json:"-"`
|
ExternalUI string `json:"-"`
|
||||||
Secret string `json:"-"`
|
Secret string `json:"-"`
|
||||||
@ -58,17 +71,14 @@ type FallbackFilter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Experimental config
|
// Experimental config
|
||||||
type Experimental struct {
|
type Experimental struct{}
|
||||||
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
|
||||||
Interface string `yaml:"interface-name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is clash config manager
|
// Config is clash config manager
|
||||||
type Config struct {
|
type Config struct {
|
||||||
General *General
|
General *General
|
||||||
DNS *DNS
|
DNS *DNS
|
||||||
Experimental *Experimental
|
Experimental *Experimental
|
||||||
Hosts *trie.Trie
|
Hosts *trie.DomainTrie
|
||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
Users []auth.AuthUser
|
Users []auth.AuthUser
|
||||||
Proxies map[string]C.Proxy
|
Proxies map[string]C.Proxy
|
||||||
@ -97,14 +107,17 @@ 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"`
|
||||||
|
MixedPort int `yaml:"mixed-port"`
|
||||||
Authentication []string `yaml:"authentication"`
|
Authentication []string `yaml:"authentication"`
|
||||||
AllowLan bool `yaml:"allow-lan"`
|
AllowLan bool `yaml:"allow-lan"`
|
||||||
BindAddress string `yaml:"bind-address"`
|
BindAddress string `yaml:"bind-address"`
|
||||||
Mode T.TunnelMode `yaml:"mode"`
|
Mode T.TunnelMode `yaml:"mode"`
|
||||||
LogLevel log.LogLevel `yaml:"log-level"`
|
LogLevel log.LogLevel `yaml:"log-level"`
|
||||||
|
IPv6 bool `yaml:"ipv6"`
|
||||||
ExternalController string `yaml:"external-controller"`
|
ExternalController string `yaml:"external-controller"`
|
||||||
ExternalUI string `yaml:"external-ui"`
|
ExternalUI string `yaml:"external-ui"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
|
Interface string `yaml:"interface-name"`
|
||||||
|
|
||||||
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
||||||
Hosts map[string]string `yaml:"hosts"`
|
Hosts map[string]string `yaml:"hosts"`
|
||||||
@ -113,12 +126,6 @@ type RawConfig struct {
|
|||||||
Proxy []map[string]interface{} `yaml:"proxies"`
|
Proxy []map[string]interface{} `yaml:"proxies"`
|
||||||
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
||||||
Rule []string `yaml:"rules"`
|
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
|
// Parse config
|
||||||
@ -143,9 +150,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
Rule: []string{},
|
Rule: []string{},
|
||||||
Proxy: []map[string]interface{}{},
|
Proxy: []map[string]interface{}{},
|
||||||
ProxyGroup: []map[string]interface{}{},
|
ProxyGroup: []map[string]interface{}{},
|
||||||
Experimental: Experimental{
|
|
||||||
IgnoreResolveFail: true,
|
|
||||||
},
|
|
||||||
DNS: RawDNS{
|
DNS: RawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
FakeIPRange: "198.18.0.1/16",
|
||||||
@ -158,11 +162,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
"8.8.8.8",
|
"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 {
|
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
||||||
@ -214,17 +213,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseGeneral(cfg *RawConfig) (*General, 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
|
externalUI := cfg.ExternalUI
|
||||||
secret := cfg.Secret
|
|
||||||
mode := cfg.Mode
|
|
||||||
logLevel := cfg.LogLevel
|
|
||||||
|
|
||||||
|
// checkout externalUI exist
|
||||||
if externalUI != "" {
|
if externalUI != "" {
|
||||||
externalUI = C.Path.Resolve(externalUI)
|
externalUI = C.Path.Resolve(externalUI)
|
||||||
|
|
||||||
@ -233,19 +224,25 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
general := &General{
|
return &General{
|
||||||
Port: port,
|
Inbound: Inbound{
|
||||||
SocksPort: socksPort,
|
Port: cfg.Port,
|
||||||
RedirPort: redirPort,
|
SocksPort: cfg.SocksPort,
|
||||||
AllowLan: allowLan,
|
RedirPort: cfg.RedirPort,
|
||||||
BindAddress: bindAddress,
|
MixedPort: cfg.MixedPort,
|
||||||
Mode: mode,
|
AllowLan: cfg.AllowLan,
|
||||||
LogLevel: logLevel,
|
BindAddress: cfg.BindAddress,
|
||||||
ExternalController: externalController,
|
},
|
||||||
ExternalUI: externalUI,
|
Controller: Controller{
|
||||||
Secret: secret,
|
ExternalController: cfg.ExternalController,
|
||||||
}
|
ExternalUI: cfg.ExternalUI,
|
||||||
return general, nil
|
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) {
|
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
|
groupsConfig := cfg.ProxyGroup
|
||||||
providersConfig := cfg.ProxyProvider
|
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["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
||||||
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
||||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
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)
|
pd, err := provider.ParseProxyProvider(name, mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
providersMap[name] = pd
|
providersMap[name] = pd
|
||||||
@ -317,7 +302,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
for _, provider := range providersMap {
|
for _, provider := range providersMap {
|
||||||
log.Infoln("Start initial provider %s", provider.Name())
|
log.Infoln("Start initial provider %s", provider.Name())
|
||||||
if err := provider.Initial(); err != nil {
|
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) {
|
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||||
rules := []C.Rule{}
|
rules := []C.Rule{}
|
||||||
|
|
||||||
rulesConfig := cfg.Rule
|
rulesConfig := cfg.Rule
|
||||||
|
|
||||||
// remove after 1.0
|
|
||||||
if len(rulesConfig) == 0 {
|
|
||||||
rulesConfig = cfg.RuleOld
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse rules
|
// parse rules
|
||||||
for idx, line := range rulesConfig {
|
for idx, line := range rulesConfig {
|
||||||
rule := trimArr(strings.Split(line, ","))
|
rule := trimArr(strings.Split(line, ","))
|
||||||
@ -400,42 +379,8 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
|||||||
|
|
||||||
rule = trimArr(rule)
|
rule = trimArr(rule)
|
||||||
params = trimArr(params)
|
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 {
|
if parseErr != nil {
|
||||||
return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
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
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHosts(cfg *RawConfig) (*trie.Trie, error) {
|
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
|
||||||
tree := trie.New()
|
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 {
|
if len(cfg.Hosts) != 0 {
|
||||||
for domain, ipStr := range cfg.Hosts {
|
for domain, ipStr := range cfg.Hosts {
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
@ -582,7 +533,7 @@ func parseDNS(cfg RawDNS) (*DNS, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var host *trie.Trie
|
var host *trie.DomainTrie
|
||||||
// fake ip skip host filter
|
// fake ip skip host filter
|
||||||
if len(cfg.FakeIPFilter) != 0 {
|
if len(cfg.FakeIPFilter) != 0 {
|
||||||
host = trie.New()
|
host = trie.New()
|
||||||
|
@ -57,7 +57,8 @@ type Conn interface {
|
|||||||
type PacketConn interface {
|
type PacketConn interface {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
Connection
|
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 {
|
type ProxyAdapter interface {
|
||||||
|
@ -23,8 +23,8 @@ const (
|
|||||||
|
|
||||||
type NetWork int
|
type NetWork int
|
||||||
|
|
||||||
func (n *NetWork) String() string {
|
func (n NetWork) String() string {
|
||||||
if *n == TCP {
|
if n == TCP {
|
||||||
return "tcp"
|
return "tcp"
|
||||||
}
|
}
|
||||||
return "udp"
|
return "udp"
|
||||||
|
@ -44,7 +44,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
|||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
@ -76,6 +76,7 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
|||||||
url: url,
|
url: url,
|
||||||
transport: &http.Transport{
|
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) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
host, port, err := net.SplitHostPort(addr)
|
host, port, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,9 +58,23 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
|||||||
|
|
||||||
func withResolver(resolver *Resolver) handler {
|
func withResolver(resolver *Resolver) handler {
|
||||||
return func(w D.ResponseWriter, r *D.Msg) {
|
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)
|
msg, err := resolver.Exchange(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
q := r.Question[0]
|
|
||||||
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
|
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
|
||||||
D.HandleFailed(w, r)
|
D.HandleFailed(w, r)
|
||||||
return
|
return
|
||||||
|
@ -121,7 +121,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
putMsgToCache(r.lruCache, q.String(), msg)
|
putMsgToCache(r.lruCache, q.String(), msg)
|
||||||
if r.mapping {
|
if r.mapping || r.fakeip {
|
||||||
ips := r.msgToIP(msg)
|
ips := r.msgToIP(msg)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
putMsgToCache(r.lruCache, ip.String(), msg)
|
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
|
// IPToHost return fake-ip or redir-host mapping host
|
||||||
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
||||||
if r.fakeip {
|
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())
|
cache, _ := r.lruCache.Get(ip.String())
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/sockopt"
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@ -42,6 +43,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
|||||||
|
|
||||||
if server.Server != nil {
|
if server.Server != nil {
|
||||||
server.Shutdown()
|
server.Shutdown()
|
||||||
|
server = &Server{}
|
||||||
address = ""
|
address = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
|||||||
|
|
||||||
err = sockopt.UDPReuseaddr(p)
|
err = sockopt.UDPReuseaddr(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
address = addr
|
address = addr
|
||||||
|
@ -89,7 +89,7 @@ func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
|
|||||||
case len(msg.Extra) != 0:
|
case len(msg.Extra) != 0:
|
||||||
ttl = msg.Extra[0].Header().Ttl
|
ttl = msg.Extra[0].Header().Ttl
|
||||||
default:
|
default:
|
||||||
log.Debugln("[DNS] response msg error: %#v", msg)
|
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,10 +111,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isIPRequest(q D.Question) bool {
|
func isIPRequest(q D.Question) bool {
|
||||||
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
|
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||||
|
10
go.mod
10
go.mod
@ -5,7 +5,7 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.5
|
github.com/Dreamacro/go-shadowsocks2 v0.1.5
|
||||||
github.com/eapache/queue v1.1.0 // indirect
|
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/cors v1.1.1
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v3.3.0+incompatible
|
github.com/gofrs/uuid v3.3.0+incompatible
|
||||||
@ -13,10 +13,10 @@ require (
|
|||||||
github.com/miekg/dns v1.1.29
|
github.com/miekg/dns v1.1.29
|
||||||
github.com/oschwald/geoip2-golang v1.4.0
|
github.com/oschwald/geoip2-golang v1.4.0
|
||||||
github.com/sirupsen/logrus v1.6.0
|
github.com/sirupsen/logrus v1.6.0
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.6.1
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
|
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0
|
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
23
go.sum
@ -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/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 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
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.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||||
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
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 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
||||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
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=
|
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/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 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
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-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-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-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-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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/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-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-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-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-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
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-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 h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
|
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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=
|
||||||
|
@ -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
|
|
@ -4,13 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/provider"
|
"github.com/Dreamacro/clash/adapters/provider"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"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/resolver"
|
||||||
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
"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"
|
||||||
@ -20,30 +20,15 @@ import (
|
|||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// forward compatibility before 1.0
|
var (
|
||||||
func readRawConfig(path string) ([]byte, error) {
|
mux sync.Mutex
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfig(path string) ([]byte, error) {
|
func readConfig(path string) ([]byte, 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 := readRawConfig(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -77,10 +62,11 @@ func ParseWithBytes(buf []byte) (*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) {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
|
||||||
updateUsers(cfg.Users)
|
updateUsers(cfg.Users)
|
||||||
if force {
|
updateGeneral(cfg.General, force)
|
||||||
updateGeneral(cfg.General)
|
|
||||||
}
|
|
||||||
updateProxies(cfg.Proxies, cfg.Providers)
|
updateProxies(cfg.Proxies, cfg.Providers)
|
||||||
updateRules(cfg.Rules)
|
updateRules(cfg.Rules)
|
||||||
updateDNS(cfg.DNS)
|
updateDNS(cfg.DNS)
|
||||||
@ -96,12 +82,15 @@ func GetGeneral() *config.General {
|
|||||||
}
|
}
|
||||||
|
|
||||||
general := &config.General{
|
general := &config.General{
|
||||||
|
Inbound: config.Inbound{
|
||||||
Port: ports.Port,
|
Port: ports.Port,
|
||||||
SocksPort: ports.SocksPort,
|
SocksPort: ports.SocksPort,
|
||||||
RedirPort: ports.RedirPort,
|
RedirPort: ports.RedirPort,
|
||||||
|
MixedPort: ports.MixedPort,
|
||||||
Authentication: authenticator,
|
Authentication: authenticator,
|
||||||
AllowLan: P.AllowLan(),
|
AllowLan: P.AllowLan(),
|
||||||
BindAddress: P.BindAddress(),
|
BindAddress: P.BindAddress(),
|
||||||
|
},
|
||||||
Mode: tunnel.Mode(),
|
Mode: tunnel.Mode(),
|
||||||
LogLevel: log.Level(),
|
LogLevel: log.Level(),
|
||||||
}
|
}
|
||||||
@ -109,18 +98,7 @@ func GetGeneral() *config.General {
|
|||||||
return general
|
return general
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateExperimental(c *config.Config) {
|
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 updateDNS(c *config.DNS) {
|
func updateDNS(c *config.DNS) {
|
||||||
if c.Enable == false {
|
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
|
resolver.DefaultHosts = tree
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,9 +143,22 @@ func updateRules(rules []C.Rule) {
|
|||||||
tunnel.UpdateRules(rules)
|
tunnel.UpdateRules(rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General) {
|
func updateGeneral(general *config.General, force bool) {
|
||||||
log.SetLevel(general.LogLevel)
|
log.SetLevel(general.LogLevel)
|
||||||
tunnel.SetMode(general.Mode)
|
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
|
allowLan := general.AllowLan
|
||||||
P.SetAllowLan(allowLan)
|
P.SetAllowLan(allowLan)
|
||||||
@ -186,6 +177,10 @@ func updateGeneral(general *config.General) {
|
|||||||
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
||||||
log.Errorln("Start Redir server error: %s", err.Error())
|
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) {
|
func updateUsers(users []auth.AuthUser) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package hub
|
package hub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Dreamacro/clash/config"
|
||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
"github.com/Dreamacro/clash/hub/route"
|
"github.com/Dreamacro/clash/hub/route"
|
||||||
"github.com/Dreamacro/clash/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option func(*config.Config)
|
type Option func(*config.Config)
|
||||||
|
@ -26,6 +26,7 @@ type configSchema 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"`
|
||||||
|
MixedPort *int `json:"mixed-port"`
|
||||||
AllowLan *bool `json:"allow-lan"`
|
AllowLan *bool `json:"allow-lan"`
|
||||||
BindAddress *string `json:"bind-address"`
|
BindAddress *string `json:"bind-address"`
|
||||||
Mode *tunnel.TunnelMode `json:"mode"`
|
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.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||||
|
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||||
|
|
||||||
if general.Mode != nil {
|
if general.Mode != nil {
|
||||||
tunnel.SetMode(*general.Mode)
|
tunnel.SetMode(*general.Mode)
|
||||||
|
@ -36,7 +36,7 @@ type Traffic struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetUIPath(path string) {
|
func SetUIPath(path string) {
|
||||||
uiPath = path
|
uiPath = C.Path.Resolve(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(addr string, secret string) {
|
func Start(addr string, secret string) {
|
||||||
|
@ -41,7 +41,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
|
|||||||
}
|
}
|
||||||
continue
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConn(conn net.Conn, cache *cache.Cache) {
|
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 == "" {
|
||||||
|
@ -4,9 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/proxy/http"
|
"github.com/Dreamacro/clash/proxy/http"
|
||||||
|
"github.com/Dreamacro/clash/proxy/mixed"
|
||||||
"github.com/Dreamacro/clash/proxy/redir"
|
"github.com/Dreamacro/clash/proxy/redir"
|
||||||
"github.com/Dreamacro/clash/proxy/socks"
|
"github.com/Dreamacro/clash/proxy/socks"
|
||||||
)
|
)
|
||||||
@ -20,6 +22,15 @@ var (
|
|||||||
httpListener *http.HttpListener
|
httpListener *http.HttpListener
|
||||||
redirListener *redir.RedirListener
|
redirListener *redir.RedirListener
|
||||||
redirUDPListener *redir.RedirUDPListener
|
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 {
|
type listener interface {
|
||||||
@ -31,6 +42,7 @@ type Ports 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"`
|
||||||
|
MixedPort int `json:"mixed-port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func AllowLan() bool {
|
func AllowLan() bool {
|
||||||
@ -50,6 +62,9 @@ func SetBindAddress(host string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateHTTP(port int) error {
|
func ReCreateHTTP(port int) error {
|
||||||
|
httpMux.Lock()
|
||||||
|
defer httpMux.Unlock()
|
||||||
|
|
||||||
addr := genAddr(bindAddress, port, allowLan)
|
addr := genAddr(bindAddress, port, allowLan)
|
||||||
|
|
||||||
if httpListener != nil {
|
if httpListener != nil {
|
||||||
@ -74,6 +89,9 @@ func ReCreateHTTP(port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateSocks(port int) error {
|
func ReCreateSocks(port int) error {
|
||||||
|
socksMux.Lock()
|
||||||
|
defer socksMux.Unlock()
|
||||||
|
|
||||||
addr := genAddr(bindAddress, port, allowLan)
|
addr := genAddr(bindAddress, port, allowLan)
|
||||||
|
|
||||||
shouldTCPIgnore := false
|
shouldTCPIgnore := false
|
||||||
@ -123,6 +141,9 @@ func ReCreateSocks(port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateRedir(port int) error {
|
func ReCreateRedir(port int) error {
|
||||||
|
redirMux.Lock()
|
||||||
|
defer redirMux.Unlock()
|
||||||
|
|
||||||
addr := genAddr(bindAddress, port, allowLan)
|
addr := genAddr(bindAddress, port, allowLan)
|
||||||
|
|
||||||
if redirListener != nil {
|
if redirListener != nil {
|
||||||
@ -159,6 +180,55 @@ func ReCreateRedir(port int) error {
|
|||||||
return nil
|
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
|
// GetPorts return the ports of proxy servers
|
||||||
func GetPorts() *Ports {
|
func GetPorts() *Ports {
|
||||||
ports := &Ports{}
|
ports := &Ports{}
|
||||||
@ -181,6 +251,12 @@ func GetPorts() *Ports {
|
|||||||
ports.RedirPort = port
|
ports.RedirPort = port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mixedListener != nil {
|
||||||
|
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
|
||||||
|
port, _ := strconv.Atoi(portStr)
|
||||||
|
ports.MixedPort = port
|
||||||
|
}
|
||||||
|
|
||||||
return ports
|
return ports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
proxy/mixed/conn.go
Normal file
41
proxy/mixed/conn.go
Normal 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
69
proxy/mixed/mixed.go
Normal 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)
|
||||||
|
}
|
@ -36,7 +36,7 @@ func NewSocksProxy(addr string) (*SockListener, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go handleSocks(c)
|
go HandleSocks(c)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -52,13 +52,15 @@ func (l *SockListener) Address() string {
|
|||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSocks(conn net.Conn) {
|
func HandleSocks(conn net.Conn) {
|
||||||
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
if c, ok := conn.(*net.TCPConn); ok {
|
||||||
|
c.SetKeepAlive(true)
|
||||||
|
}
|
||||||
if command == socks5.CmdUDPAssociate {
|
if command == socks5.CmdUDPAssociate {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
io.Copy(ioutil.Discard, conn)
|
io.Copy(ioutil.Discard, conn)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/sockopt"
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
"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/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
|
|||||||
|
|
||||||
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
|
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &SockUDPListener{l, addr, false}
|
sl := &SockUDPListener{l, addr, false}
|
||||||
|
41
rules/parser.go
Normal file
41
rules/parser.go
Normal 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
|
||||||
|
}
|
@ -2,6 +2,7 @@ package tunnel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"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()
|
defer packet.Drop()
|
||||||
|
|
||||||
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
|
// local resolve UDP dns
|
||||||
return
|
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) {
|
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
|
||||||
|
@ -3,6 +3,7 @@ package tunnel
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TunnelMode int
|
type TunnelMode int
|
||||||
@ -26,7 +27,7 @@ const (
|
|||||||
func (m *TunnelMode) UnmarshalJSON(data []byte) error {
|
func (m *TunnelMode) UnmarshalJSON(data []byte) error {
|
||||||
var tp string
|
var tp string
|
||||||
json.Unmarshal(data, &tp)
|
json.Unmarshal(data, &tp)
|
||||||
mode, exist := ModeMapping[tp]
|
mode, exist := ModeMapping[strings.ToLower(tp)]
|
||||||
if !exist {
|
if !exist {
|
||||||
return errors.New("invalid mode")
|
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 {
|
func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
var tp string
|
var tp string
|
||||||
unmarshal(&tp)
|
unmarshal(&tp)
|
||||||
mode, exist := ModeMapping[tp]
|
mode, exist := ModeMapping[strings.ToLower(tp)]
|
||||||
if !exist {
|
if !exist {
|
||||||
return errors.New("invalid mode")
|
return errors.New("invalid mode")
|
||||||
}
|
}
|
||||||
@ -59,11 +60,11 @@ func (m TunnelMode) MarshalYAML() (interface{}, error) {
|
|||||||
func (m TunnelMode) String() string {
|
func (m TunnelMode) String() string {
|
||||||
switch m {
|
switch m {
|
||||||
case Global:
|
case Global:
|
||||||
return "Global"
|
return "global"
|
||||||
case Rule:
|
case Rule:
|
||||||
return "Rule"
|
return "rule"
|
||||||
case Direct:
|
case Direct:
|
||||||
return "Direct"
|
return "direct"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ type trackerInfo struct {
|
|||||||
Start time.Time `json:"start"`
|
Start time.Time `json:"start"`
|
||||||
Chain C.Chain `json:"chains"`
|
Chain C.Chain `json:"chains"`
|
||||||
Rule string `json:"rule"`
|
Rule string `json:"rule"`
|
||||||
|
RulePayload string `json:"rulePayload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcpTracker struct {
|
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 {
|
func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker {
|
||||||
uuid, _ := uuid.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
ruleType := ""
|
|
||||||
if rule != nil {
|
|
||||||
ruleType = rule.RuleType().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &tcpTracker{
|
t := &tcpTracker{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
@ -69,10 +66,15 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
|||||||
Start: time.Now(),
|
Start: time.Now(),
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: conn.Chains(),
|
Chain: conn.Chains(),
|
||||||
Rule: ruleType,
|
Rule: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule != nil {
|
||||||
|
t.trackerInfo.Rule = rule.RuleType().String()
|
||||||
|
t.trackerInfo.RulePayload = rule.Payload()
|
||||||
|
}
|
||||||
|
|
||||||
manager.Join(t)
|
manager.Join(t)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
@ -103,14 +105,6 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
|
|||||||
return n, err
|
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 {
|
func (ut *udpTracker) Close() error {
|
||||||
ut.manager.Leave(ut)
|
ut.manager.Leave(ut)
|
||||||
return ut.PacketConn.Close()
|
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 {
|
func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
|
||||||
uuid, _ := uuid.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
ruleType := ""
|
|
||||||
if rule != nil {
|
|
||||||
ruleType = rule.RuleType().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
ut := &udpTracker{
|
ut := &udpTracker{
|
||||||
PacketConn: conn,
|
PacketConn: conn,
|
||||||
@ -131,10 +121,15 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
|
|||||||
Start: time.Now(),
|
Start: time.Now(),
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: conn.Chains(),
|
Chain: conn.Chains(),
|
||||||
Rule: ruleType,
|
Rule: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule != nil {
|
||||||
|
ut.trackerInfo.Rule = rule.RuleType().String()
|
||||||
|
ut.trackerInfo.RulePayload = rule.Payload()
|
||||||
|
}
|
||||||
|
|
||||||
manager.Join(ut)
|
manager.Join(ut)
|
||||||
return ut
|
return ut
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,6 @@ var (
|
|||||||
configMux sync.RWMutex
|
configMux sync.RWMutex
|
||||||
enhancedMode *dns.Resolver
|
enhancedMode *dns.Resolver
|
||||||
|
|
||||||
// experimental features
|
|
||||||
ignoreResolveFail bool
|
|
||||||
|
|
||||||
// Outbound Rule
|
// Outbound Rule
|
||||||
mode = Rule
|
mode = Rule
|
||||||
|
|
||||||
@ -82,13 +79,6 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid
|
|||||||
configMux.Unlock()
|
configMux.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateExperimental handle update experimental config
|
|
||||||
func UpdateExperimental(value bool) {
|
|
||||||
configMux.Lock()
|
|
||||||
ignoreResolveFail = value
|
|
||||||
configMux.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode return current mode
|
// Mode return current mode
|
||||||
func Mode() TunnelMode {
|
func Mode() TunnelMode {
|
||||||
return mode
|
return mode
|
||||||
@ -318,9 +308,6 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
if !resolved && shouldResolveIP(rule, metadata) {
|
if !resolved && shouldResolveIP(rule, metadata) {
|
||||||
ip, err := resolver.ResolveIP(metadata.Host)
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
if err != nil {
|
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())
|
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
|
||||||
} else {
|
} else {
|
||||||
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
|
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
|
||||||
|
Reference in New Issue
Block a user