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 .
|
8
.github/workflows/go.yml
vendored
8
.github/workflows/go.yml
vendored
@ -7,15 +7,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.14.x
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
|
@ -3,10 +3,11 @@ FROM golang:alpine as builder
|
||||
RUN apk add --no-cache make git && \
|
||||
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
|
||||
WORKDIR /clash-src
|
||||
COPY --from=tonistiigi/xx:golang / /
|
||||
COPY . /clash-src
|
||||
RUN go mod download && \
|
||||
make linux-amd64 && \
|
||||
mv ./bin/clash-linux-amd64 /clash
|
||||
make docker && \
|
||||
mv ./bin/clash-docker /clash
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
|
@ -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
|
||||
VERSION=$(shell git describe --tags || echo "unknown version")
|
||||
BUILDTIME=$(shell date -u)
|
||||
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s'
|
||||
|
||||
@ -29,6 +29,9 @@ WINDOWS_ARCH_LIST = \
|
||||
|
||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||
|
||||
docker:
|
||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64:
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
|
31
README.md
31
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 TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN). Source is not currently available.
|
||||
Pre-built Premium binaries are available here: [premium release](https://github.com/Dreamacro/clash/releases/tag/premium). Source is not currently available.
|
||||
|
||||
Check Clash version with:
|
||||
|
||||
@ -47,7 +47,7 @@ $ clash -v
|
||||
|
||||
## Daemonize Clash
|
||||
|
||||
Unfortunately, there is no native or elegant way to implement daemons on Golang. We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service.
|
||||
We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service. ([Wiki](https://github.com/Dreamacro/clash/wiki/Clash-as-a-daemon))
|
||||
|
||||
In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way:
|
||||
|
||||
@ -81,6 +81,9 @@ port: 7890
|
||||
# port of SOCKS5
|
||||
socks-port: 7891
|
||||
|
||||
# (HTTP and SOCKS5 in one port)
|
||||
# mixed-port: 7890
|
||||
|
||||
# redir port for Linux and macOS
|
||||
# redir-port: 7892
|
||||
|
||||
@ -92,8 +95,10 @@ allow-lan: false
|
||||
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
|
||||
# bind-address: "*"
|
||||
|
||||
# Rule / Global / Direct (default is Rule)
|
||||
mode: Rule
|
||||
# ipv6: false # when ipv6 is false, each clash dial with ipv6, but it's not affect the response of the dns server, default is false
|
||||
|
||||
# rule / global / direct (default is rule)
|
||||
mode: rule
|
||||
|
||||
# set log level to stdout (default is info)
|
||||
# info / warning / error / debug / silent
|
||||
@ -109,26 +114,25 @@ external-controller: 127.0.0.1:9090
|
||||
# Secret for RESTful API (Optional)
|
||||
# secret: ""
|
||||
|
||||
# experimental feature
|
||||
experimental:
|
||||
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
|
||||
# interface-name: en0 # outbound interface name
|
||||
# interface-name: en0 # outbound interface name
|
||||
|
||||
# authentication of local SOCKS5/HTTP(S) server
|
||||
# authentication:
|
||||
# - "user1:pass1"
|
||||
# - "user2:pass2"
|
||||
|
||||
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
|
||||
# # hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
|
||||
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com)
|
||||
# # +.foo.com equal .foo.com and foo.com
|
||||
# hosts:
|
||||
# '*.clash.dev': 127.0.0.1
|
||||
# '.dev': 127.0.0.1
|
||||
# 'alpha.clash.dev': '::1'
|
||||
# '+.foo.dev': 127.0.0.1
|
||||
|
||||
# dns:
|
||||
# enable: true # set true to enable dns (default is false)
|
||||
# ipv6: false # default is false
|
||||
# ipv6: false # it only affect the dns server response, default is false
|
||||
# listen: 0.0.0.0:53
|
||||
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
|
||||
# # - 114.114.114.114
|
||||
@ -165,7 +169,6 @@ proxies:
|
||||
password: "password"
|
||||
# udp: true
|
||||
|
||||
# old obfs configuration format remove after prerelease
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
@ -206,6 +209,7 @@ proxies:
|
||||
# udp: true
|
||||
# tls: true
|
||||
# skip-cert-verify: true
|
||||
# servername: example.com # priority over wss host
|
||||
# network: ws
|
||||
# ws-path: /path
|
||||
# ws-headers:
|
||||
@ -291,6 +295,7 @@ proxy-groups:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
# tolerance: 150
|
||||
url: 'http://www.gstatic.com/generate_204'
|
||||
interval: 300
|
||||
|
||||
@ -362,8 +367,6 @@ rules:
|
||||
- GEOIP,CN,DIRECT
|
||||
- DST-PORT,80,DIRECT
|
||||
- SRC-PORT,7777,DIRECT
|
||||
# FINAL would remove after prerelease
|
||||
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
|
||||
- MATCH,auto
|
||||
```
|
||||
</details>
|
||||
@ -390,4 +393,4 @@ https://clash.gitbook.io/
|
||||
- [x] Redir proxy
|
||||
- [x] UDP support
|
||||
- [x] Connection manager
|
||||
- [ ] Event API
|
||||
- ~~[ ] Event API~~
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -16,7 +17,8 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
||||
|
||||
switch target[0] {
|
||||
case socks5.AtypDomainName:
|
||||
metadata.Host = string(target[2 : 2+target[1]])
|
||||
// trim for FQDN
|
||||
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
|
||||
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||
case socks5.AtypIPv4:
|
||||
ip := net.IP(target[1 : 1+net.IPv4len])
|
||||
@ -38,6 +40,9 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
port = "80"
|
||||
}
|
||||
|
||||
// trim FQDN (#737)
|
||||
host = strings.TrimRight(host, ".")
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
AddrType: C.AtypDomainName,
|
||||
|
@ -78,13 +78,8 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
return &conn{c, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type PacketConn interface {
|
||||
net.PacketConn
|
||||
WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error)
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
PacketConn
|
||||
net.PacketConn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
@ -96,7 +91,7 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func newPacketConn(pc PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -36,17 +35,6 @@ type directPacketConn struct {
|
||||
net.PacketConn
|
||||
}
|
||||
|
||||
func (dp *directPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
return dp.WriteTo(p, metadata.UDPAddr())
|
||||
}
|
||||
|
||||
func NewDirect() *Direct {
|
||||
return &Direct{
|
||||
Base: &Base{
|
||||
|
@ -37,10 +37,6 @@ type ShadowSocksOption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Plugin string `proxy:"plugin,omitempty"`
|
||||
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
|
||||
|
||||
// deprecated when bump to 1.0
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
ObfsHost string `proxy:"obfs-host,omitempty"`
|
||||
}
|
||||
|
||||
type simpleObfsOption struct {
|
||||
@ -122,17 +118,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
var obfsOption *simpleObfsOption
|
||||
obfsMode := ""
|
||||
|
||||
// forward compatibility before 1.0
|
||||
if option.Obfs != "" {
|
||||
obfsMode = option.Obfs
|
||||
obfsOption = &simpleObfsOption{
|
||||
Host: "bing.com",
|
||||
}
|
||||
if option.ObfsHost != "" {
|
||||
obfsOption.Host = option.ObfsHost
|
||||
}
|
||||
}
|
||||
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||
if option.Plugin == "obfs" {
|
||||
opts := simpleObfsOption{Host: "bing.com"}
|
||||
@ -197,14 +182,6 @@ func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
|
||||
}
|
||||
|
||||
func (spc *ssPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
|
||||
}
|
||||
|
||||
func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, _, e := spc.PacketConn.ReadFrom(b)
|
||||
if e != nil {
|
||||
|
@ -164,14 +164,6 @@ func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
||||
}
|
||||
|
||||
func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
||||
}
|
||||
|
||||
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, _, e := uc.PacketConn.ReadFrom(b)
|
||||
if e != nil {
|
||||
|
@ -71,7 +71,7 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
return newPacketConn(&trojanPacketConn{pc, c}, t), err
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
func (t *Trojan) MarshalJSON() ([]byte, error) {
|
||||
@ -105,12 +105,3 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
instance: trojan.New(tOption),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type trojanPacketConn struct {
|
||||
net.PacketConn
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (tpc *trojanPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
return trojan.WritePacket(tpc.conn, serializesSocksAddr(metadata), p)
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ type VmessOption struct {
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPOptions struct {
|
||||
@ -66,6 +67,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
wsOpts.TLS = true
|
||||
wsOpts.SessionCache = getClientSessionCache()
|
||||
wsOpts.SkipCertVerify = v.option.SkipCertVerify
|
||||
wsOpts.ServerName = v.option.ServerName
|
||||
}
|
||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||
case "http":
|
||||
@ -87,6 +89,11 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||
}
|
||||
}
|
||||
@ -101,7 +108,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error", v.addr)
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
|
||||
@ -123,7 +130,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
defer cancel()
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error", v.addr)
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = v.StreamConn(c, metadata)
|
||||
@ -195,10 +202,6 @@ func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
return uc.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
return uc.Conn.Write(p)
|
||||
}
|
||||
|
||||
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, err := uc.Conn.Read(b)
|
||||
return n, uc.rAddr, err
|
||||
|
@ -101,7 +101,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
var group C.ProxyAdapter
|
||||
switch groupOption.Type {
|
||||
case "url-test":
|
||||
group = NewURLTest(groupName, providers)
|
||||
opts := parseURLTestOption(config)
|
||||
group = NewURLTest(groupName, providers, opts...)
|
||||
case "select":
|
||||
group = NewSelector(groupName, providers)
|
||||
case "fallback":
|
||||
|
@ -11,8 +11,19 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type urlTestOption func(*URLTest)
|
||||
|
||||
func urlTestWithTolerance(tolerance uint16) urlTestOption {
|
||||
return func(u *URLTest) {
|
||||
u.tolerance = tolerance
|
||||
}
|
||||
}
|
||||
|
||||
type URLTest struct {
|
||||
*outbound.Base
|
||||
tolerance uint16
|
||||
lastDelay uint16
|
||||
fastNode C.Proxy
|
||||
single *singledo.Single
|
||||
fastSingle *singledo.Single
|
||||
providers []provider.ProxyProvider
|
||||
@ -52,6 +63,13 @@ func (u *URLTest) proxies() []C.Proxy {
|
||||
|
||||
func (u *URLTest) fast() C.Proxy {
|
||||
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
|
||||
// tolerance
|
||||
if u.tolerance != 0 && u.fastNode != nil {
|
||||
if u.fastNode.LastDelay() < u.lastDelay+u.tolerance {
|
||||
return u.fastNode, nil
|
||||
}
|
||||
}
|
||||
|
||||
proxies := u.proxies()
|
||||
fast := proxies[0]
|
||||
min := fast.LastDelay()
|
||||
@ -66,6 +84,9 @@ func (u *URLTest) fast() C.Proxy {
|
||||
min = delay
|
||||
}
|
||||
}
|
||||
|
||||
u.fastNode = fast
|
||||
u.lastDelay = fast.LastDelay()
|
||||
return fast, nil
|
||||
})
|
||||
|
||||
@ -88,11 +109,30 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
|
||||
return &URLTest{
|
||||
func parseURLTestOption(config map[string]interface{}) []urlTestOption {
|
||||
opts := []urlTestOption{}
|
||||
|
||||
// tolerance
|
||||
if elm, ok := config["tolerance"]; ok {
|
||||
if tolerance, ok := elm.(int); ok {
|
||||
opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
|
||||
}
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
|
||||
urlTest := &URLTest{
|
||||
Base: outbound.NewBase(name, "", C.URLTest, false),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
fastSingle: singledo.NewSingle(time.Second * 10),
|
||||
providers: providers,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(urlTest)
|
||||
}
|
||||
|
||||
return urlTest
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/md5"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
|
||||
var (
|
||||
fileMode os.FileMode = 0666
|
||||
dirMode os.FileMode = 0755
|
||||
)
|
||||
|
||||
type parser = func([]byte) (interface{}, error)
|
||||
@ -35,10 +37,12 @@ func (f *fetcher) VehicleType() VehicleType {
|
||||
}
|
||||
|
||||
func (f *fetcher) Initial() (interface{}, error) {
|
||||
var buf []byte
|
||||
var err error
|
||||
var isLocal bool
|
||||
if stat, err := os.Stat(f.vehicle.Path()); err == nil {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
isLocal bool
|
||||
)
|
||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||
buf, err = ioutil.ReadFile(f.vehicle.Path())
|
||||
modTime := stat.ModTime()
|
||||
f.updatedAt = &modTime
|
||||
@ -69,7 +73,7 @@ func (f *fetcher) Initial() (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -101,7 +105,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
@ -138,6 +142,18 @@ func (f *fetcher) pullLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
func safeWrite(path string, buf []byte) error {
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, dirMode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(path, buf, fileMode)
|
||||
}
|
||||
|
||||
func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher {
|
||||
var ticker *time.Ticker
|
||||
if interval != 0 {
|
||||
|
@ -142,7 +142,9 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
go pp.healthCheck.check()
|
||||
if pp.healthCheck.auto() {
|
||||
go pp.healthCheck.check()
|
||||
}
|
||||
}
|
||||
|
||||
func stopProxyProvider(pd *ProxySetProvider) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
)
|
||||
|
||||
// Pool is a implementation about fake ip generator without storage
|
||||
@ -16,7 +16,7 @@ type Pool struct {
|
||||
gateway uint32
|
||||
offset uint32
|
||||
mux sync.Mutex
|
||||
host *trie.Trie
|
||||
host *trie.DomainTrie
|
||||
cache *cache.LruCache
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ func uintToIP(v uint32) net.IP {
|
||||
}
|
||||
|
||||
// New return Pool instance
|
||||
func New(ipnet *net.IPNet, size int, host *trie.Trie) (*Pool, error) {
|
||||
func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) {
|
||||
min := ipToUint(ipnet.IP) + 2
|
||||
|
||||
ones, bits := ipnet.Mask.Size()
|
||||
|
@ -5,20 +5,25 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultResolver aim to resolve ip
|
||||
DefaultResolver Resolver
|
||||
|
||||
// DisableIPv6 means don't resolve ipv6 host
|
||||
// default value is true
|
||||
DisableIPv6 = true
|
||||
|
||||
// DefaultHosts aim to resolve hosts
|
||||
DefaultHosts = trie.New()
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIPNotFound = errors.New("couldn't find ip")
|
||||
ErrIPVersion = errors.New("ip version error")
|
||||
ErrIPNotFound = errors.New("couldn't find ip")
|
||||
ErrIPVersion = errors.New("ip version error")
|
||||
ErrIPv6Disabled = errors.New("ipv6 disabled")
|
||||
)
|
||||
|
||||
type Resolver interface {
|
||||
@ -63,6 +68,10 @@ func ResolveIPv4(host string) (net.IP, error) {
|
||||
|
||||
// ResolveIPv6 with a host, return ipv6
|
||||
func ResolveIPv6(host string) (net.IP, error) {
|
||||
if DisableIPv6 {
|
||||
return nil, ErrIPv6Disabled
|
||||
}
|
||||
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
if ip := node.Data.(net.IP).To16(); ip != nil {
|
||||
return ip, nil
|
||||
@ -102,7 +111,12 @@ func ResolveIP(host string) (net.IP, error) {
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
if DisableIPv6 {
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
}
|
||||
return DefaultResolver.ResolveIP(host)
|
||||
} else if DisableIPv6 {
|
||||
return ResolveIPv4(host)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
|
@ -21,6 +21,8 @@ func (err Error) Error() string {
|
||||
// Command is request commands as defined in RFC 1928 section 4.
|
||||
type Command = uint8
|
||||
|
||||
const Version = 5
|
||||
|
||||
// SOCKS request commands as defined in RFC 1928 section 4.
|
||||
const (
|
||||
CmdConnect Command = 1
|
||||
@ -227,6 +229,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
|
||||
}
|
||||
|
||||
if buf[1] == 2 {
|
||||
if user == nil {
|
||||
return nil, ErrAuth
|
||||
}
|
||||
|
||||
// password protocol version
|
||||
authMsg := &bytes.Buffer{}
|
||||
authMsg.WriteByte(1)
|
||||
|
@ -6,9 +6,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
wildcard = "*"
|
||||
dotWildcard = ""
|
||||
domainStep = "."
|
||||
wildcard = "*"
|
||||
dotWildcard = ""
|
||||
complexWildcard = "+"
|
||||
domainStep = "."
|
||||
)
|
||||
|
||||
var (
|
||||
@ -16,9 +17,9 @@ var (
|
||||
ErrInvalidDomain = errors.New("invalid domain")
|
||||
)
|
||||
|
||||
// Trie contains the main logic for adding and searching nodes for domain segments.
|
||||
// DomainTrie contains the main logic for adding and searching nodes for domain segments.
|
||||
// support wildcard domain (e.g *.google.com)
|
||||
type Trie struct {
|
||||
type DomainTrie struct {
|
||||
root *Node
|
||||
}
|
||||
|
||||
@ -29,7 +30,11 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
||||
|
||||
parts := strings.Split(domain, domainStep)
|
||||
if len(parts) == 1 {
|
||||
return nil, false
|
||||
if parts[0] == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return parts, true
|
||||
}
|
||||
|
||||
for _, part := range parts[1:] {
|
||||
@ -47,12 +52,25 @@ func validAndSplitDomain(domain string) ([]string, bool) {
|
||||
// 2. *.example.com
|
||||
// 3. subdomain.*.example.com
|
||||
// 4. .example.com
|
||||
func (t *Trie) Insert(domain string, data interface{}) error {
|
||||
// 5. +.example.com
|
||||
func (t *DomainTrie) Insert(domain string, data interface{}) error {
|
||||
parts, valid := validAndSplitDomain(domain)
|
||||
if !valid {
|
||||
return ErrInvalidDomain
|
||||
}
|
||||
|
||||
if parts[0] == complexWildcard {
|
||||
t.insert(parts[1:], data)
|
||||
parts[0] = dotWildcard
|
||||
t.insert(parts, data)
|
||||
} else {
|
||||
t.insert(parts, data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DomainTrie) insert(parts []string, data interface{}) {
|
||||
node := t.root
|
||||
// reverse storage domain part to save space
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
@ -65,7 +83,6 @@ func (t *Trie) Insert(domain string, data interface{}) error {
|
||||
}
|
||||
|
||||
node.Data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// Search is the most important part of the Trie.
|
||||
@ -73,54 +90,46 @@ func (t *Trie) Insert(domain string, data interface{}) error {
|
||||
// 1. static part
|
||||
// 2. wildcard domain
|
||||
// 2. dot wildcard domain
|
||||
func (t *Trie) Search(domain string) *Node {
|
||||
func (t *DomainTrie) Search(domain string) *Node {
|
||||
parts, valid := validAndSplitDomain(domain)
|
||||
if !valid || parts[0] == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := t.root
|
||||
var dotWildcardNode *Node
|
||||
var wildcardNode *Node
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
part := parts[i]
|
||||
n := t.search(t.root, parts)
|
||||
|
||||
if node := n.getChild(dotWildcard); node != nil {
|
||||
dotWildcardNode = node
|
||||
}
|
||||
|
||||
child := n.getChild(part)
|
||||
if child == nil && wildcardNode != nil {
|
||||
child = wildcardNode.getChild(part)
|
||||
}
|
||||
wildcardNode = n.getChild(wildcard)
|
||||
|
||||
n = child
|
||||
if n == nil {
|
||||
n = wildcardNode
|
||||
wildcardNode = nil
|
||||
}
|
||||
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if n == nil {
|
||||
if dotWildcardNode != nil {
|
||||
return dotWildcardNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if n.Data == nil {
|
||||
if n == nil || n.Data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// New returns a new, empty Trie.
|
||||
func New() *Trie {
|
||||
return &Trie{root: newNode(nil)}
|
||||
func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
||||
if len(parts) == 0 {
|
||||
return node
|
||||
}
|
||||
|
||||
if c := node.getChild(parts[len(parts)-1]); c != nil {
|
||||
if n := t.search(c, parts[:len(parts)-1]); n != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
if c := node.getChild(wildcard); c != nil {
|
||||
if n := t.search(c, parts[:len(parts)-1]); n != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
if c := node.getChild(dotWildcard); c != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns a new, empty Trie.
|
||||
func New() *DomainTrie {
|
||||
return &DomainTrie{root: newNode(nil)}
|
||||
}
|
@ -14,6 +14,7 @@ func TestTrie_Basic(t *testing.T) {
|
||||
domains := []string{
|
||||
"example.com",
|
||||
"google.com",
|
||||
"localhost",
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
@ -24,6 +25,9 @@ func TestTrie_Basic(t *testing.T) {
|
||||
assert.NotNil(t, node)
|
||||
assert.True(t, node.Data.(net.IP).Equal(localIP))
|
||||
assert.NotNil(t, tree.Insert("", localIP))
|
||||
assert.Nil(t, tree.Search(""))
|
||||
assert.NotNil(t, tree.Search("localhost"))
|
||||
assert.Nil(t, tree.Search("www.google.com"))
|
||||
}
|
||||
|
||||
func TestTrie_Wildcard(t *testing.T) {
|
||||
@ -35,6 +39,11 @@ func TestTrie_Wildcard(t *testing.T) {
|
||||
".org",
|
||||
".example.net",
|
||||
".apple.*",
|
||||
"+.foo.com",
|
||||
"+.stun.*.*",
|
||||
"+.stun.*.*.*",
|
||||
"+.stun.*.*.*.*",
|
||||
"stun.l.google.com",
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
@ -46,6 +55,9 @@ func TestTrie_Wildcard(t *testing.T) {
|
||||
assert.NotNil(t, tree.Search("test.org"))
|
||||
assert.NotNil(t, tree.Search("test.example.net"))
|
||||
assert.NotNil(t, tree.Search("test.apple.com"))
|
||||
assert.NotNil(t, tree.Search("test.foo.com"))
|
||||
assert.NotNil(t, tree.Search("foo.com"))
|
||||
assert.NotNil(t, tree.Search("global.stun.website.com"))
|
||||
assert.Nil(t, tree.Search("foo.sub.example.com"))
|
||||
assert.Nil(t, tree.Search("foo.example.dev"))
|
||||
assert.Nil(t, tree.Search("example.com"))
|
@ -70,8 +70,8 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
||||
|
||||
func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
defer buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
defer buf.Reset()
|
||||
|
||||
buf.Write(t.hexPassword)
|
||||
buf.Write(crlf)
|
||||
@ -92,8 +92,8 @@ func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn {
|
||||
|
||||
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
defer buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
defer buf.Reset()
|
||||
|
||||
buf.Write(socks5Addr)
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(payload)))
|
||||
|
@ -31,6 +31,7 @@ type WebsocketConfig struct {
|
||||
Headers http.Header
|
||||
TLS bool
|
||||
SkipCertVerify bool
|
||||
ServerName string
|
||||
SessionCache tls.ClientSessionCache
|
||||
}
|
||||
|
||||
@ -132,7 +133,9 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
||||
ClientSessionCache: c.SessionCache,
|
||||
}
|
||||
|
||||
if host := c.Headers.Get("Host"); host != "" {
|
||||
if c.ServerName != "" {
|
||||
dialer.TLSClientConfig.ServerName = c.ServerName
|
||||
} else if host := c.Headers.Get("Host"); host != "" {
|
||||
dialer.TLSClientConfig.ServerName = host
|
||||
}
|
||||
}
|
||||
|
171
config/config.go
171
config/config.go
@ -12,8 +12,8 @@ import (
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -25,17 +25,30 @@ import (
|
||||
|
||||
// General config
|
||||
type General struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
Mode T.TunnelMode `json:"mode"`
|
||||
LogLevel log.LogLevel `json:"log-level"`
|
||||
ExternalController string `json:"-"`
|
||||
ExternalUI string `json:"-"`
|
||||
Secret string `json:"-"`
|
||||
Inbound
|
||||
Controller
|
||||
Mode T.TunnelMode `json:"mode"`
|
||||
LogLevel log.LogLevel `json:"log-level"`
|
||||
IPv6 bool `json:"ipv6"`
|
||||
Interface string `json:"interface-name"`
|
||||
}
|
||||
|
||||
// Inbound
|
||||
type Inbound struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
}
|
||||
|
||||
// Controller
|
||||
type Controller struct {
|
||||
ExternalController string `json:"-"`
|
||||
ExternalUI string `json:"-"`
|
||||
Secret string `json:"-"`
|
||||
}
|
||||
|
||||
// DNS config
|
||||
@ -58,17 +71,14 @@ type FallbackFilter struct {
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
type Experimental struct {
|
||||
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
||||
Interface string `yaml:"interface-name"`
|
||||
}
|
||||
type Experimental struct{}
|
||||
|
||||
// Config is clash config manager
|
||||
type Config struct {
|
||||
General *General
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.Trie
|
||||
Hosts *trie.DomainTrie
|
||||
Rules []C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
@ -97,14 +107,17 @@ type RawConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port"`
|
||||
MixedPort int `yaml:"mixed-port"`
|
||||
Authentication []string `yaml:"authentication"`
|
||||
AllowLan bool `yaml:"allow-lan"`
|
||||
BindAddress string `yaml:"bind-address"`
|
||||
Mode T.TunnelMode `yaml:"mode"`
|
||||
LogLevel log.LogLevel `yaml:"log-level"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
ExternalController string `yaml:"external-controller"`
|
||||
ExternalUI string `yaml:"external-ui"`
|
||||
Secret string `yaml:"secret"`
|
||||
Interface string `yaml:"interface-name"`
|
||||
|
||||
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"`
|
||||
Hosts map[string]string `yaml:"hosts"`
|
||||
@ -113,12 +126,6 @@ type RawConfig struct {
|
||||
Proxy []map[string]interface{} `yaml:"proxies"`
|
||||
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
|
||||
// remove after 1.0
|
||||
ProxyProviderOld map[string]map[string]interface{} `yaml:"proxy-provider"`
|
||||
ProxyOld []map[string]interface{} `yaml:"Proxy"`
|
||||
ProxyGroupOld []map[string]interface{} `yaml:"Proxy Group"`
|
||||
RuleOld []string `yaml:"Rule"`
|
||||
}
|
||||
|
||||
// Parse config
|
||||
@ -143,9 +150,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Rule: []string{},
|
||||
Proxy: []map[string]interface{}{},
|
||||
ProxyGroup: []map[string]interface{}{},
|
||||
Experimental: Experimental{
|
||||
IgnoreResolveFail: true,
|
||||
},
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
@ -158,11 +162,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
"8.8.8.8",
|
||||
},
|
||||
},
|
||||
|
||||
// remove after 1.0
|
||||
RuleOld: []string{},
|
||||
ProxyOld: []map[string]interface{}{},
|
||||
ProxyGroupOld: []map[string]interface{}{},
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
||||
@ -214,17 +213,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
|
||||
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
port := cfg.Port
|
||||
socksPort := cfg.SocksPort
|
||||
redirPort := cfg.RedirPort
|
||||
allowLan := cfg.AllowLan
|
||||
bindAddress := cfg.BindAddress
|
||||
externalController := cfg.ExternalController
|
||||
externalUI := cfg.ExternalUI
|
||||
secret := cfg.Secret
|
||||
mode := cfg.Mode
|
||||
logLevel := cfg.LogLevel
|
||||
|
||||
// checkout externalUI exist
|
||||
if externalUI != "" {
|
||||
externalUI = C.Path.Resolve(externalUI)
|
||||
|
||||
@ -233,19 +224,25 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
}
|
||||
}
|
||||
|
||||
general := &General{
|
||||
Port: port,
|
||||
SocksPort: socksPort,
|
||||
RedirPort: redirPort,
|
||||
AllowLan: allowLan,
|
||||
BindAddress: bindAddress,
|
||||
Mode: mode,
|
||||
LogLevel: logLevel,
|
||||
ExternalController: externalController,
|
||||
ExternalUI: externalUI,
|
||||
Secret: secret,
|
||||
}
|
||||
return general, nil
|
||||
return &General{
|
||||
Inbound: Inbound{
|
||||
Port: cfg.Port,
|
||||
SocksPort: cfg.SocksPort,
|
||||
RedirPort: cfg.RedirPort,
|
||||
MixedPort: cfg.MixedPort,
|
||||
AllowLan: cfg.AllowLan,
|
||||
BindAddress: cfg.BindAddress,
|
||||
},
|
||||
Controller: Controller{
|
||||
ExternalController: cfg.ExternalController,
|
||||
ExternalUI: cfg.ExternalUI,
|
||||
Secret: cfg.Secret,
|
||||
},
|
||||
Mode: cfg.Mode,
|
||||
LogLevel: cfg.LogLevel,
|
||||
IPv6: cfg.IPv6,
|
||||
Interface: cfg.Interface,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) {
|
||||
@ -256,18 +253,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
groupsConfig := cfg.ProxyGroup
|
||||
providersConfig := cfg.ProxyProvider
|
||||
|
||||
if len(proxiesConfig) == 0 {
|
||||
proxiesConfig = cfg.ProxyOld
|
||||
}
|
||||
|
||||
if len(groupsConfig) == 0 {
|
||||
groupsConfig = cfg.ProxyGroupOld
|
||||
}
|
||||
|
||||
if len(providersConfig) == 0 {
|
||||
providersConfig = cfg.ProxyProviderOld
|
||||
}
|
||||
|
||||
proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect())
|
||||
proxies["REJECT"] = outbound.NewProxy(outbound.NewReject())
|
||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||
@ -308,7 +293,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
|
||||
pd, err := provider.ParseProxyProvider(name, mapping)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err)
|
||||
}
|
||||
|
||||
providersMap[name] = pd
|
||||
@ -317,7 +302,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for _, provider := range providersMap {
|
||||
log.Infoln("Start initial provider %s", provider.Name())
|
||||
if err := provider.Initial(); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", provider.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,14 +348,8 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
rules := []C.Rule{}
|
||||
|
||||
rulesConfig := cfg.Rule
|
||||
|
||||
// remove after 1.0
|
||||
if len(rulesConfig) == 0 {
|
||||
rulesConfig = cfg.RuleOld
|
||||
}
|
||||
|
||||
// parse rules
|
||||
for idx, line := range rulesConfig {
|
||||
rule := trimArr(strings.Split(line, ","))
|
||||
@ -400,42 +379,8 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
|
||||
rule = trimArr(rule)
|
||||
params = trimArr(params)
|
||||
var (
|
||||
parseErr error
|
||||
parsed C.Rule
|
||||
)
|
||||
|
||||
switch rule[0] {
|
||||
case "DOMAIN":
|
||||
parsed = R.NewDomain(payload, target)
|
||||
case "DOMAIN-SUFFIX":
|
||||
parsed = R.NewDomainSuffix(payload, target)
|
||||
case "DOMAIN-KEYWORD":
|
||||
parsed = R.NewDomainKeyword(payload, target)
|
||||
case "GEOIP":
|
||||
noResolve := R.HasNoResolve(params)
|
||||
parsed = R.NewGEOIP(payload, target, noResolve)
|
||||
case "IP-CIDR", "IP-CIDR6":
|
||||
noResolve := R.HasNoResolve(params)
|
||||
parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRNoResolve(noResolve))
|
||||
// deprecated when bump to 1.0
|
||||
case "SOURCE-IP-CIDR":
|
||||
fallthrough
|
||||
case "SRC-IP-CIDR":
|
||||
parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true), R.WithIPCIDRNoResolve(true))
|
||||
case "SRC-PORT":
|
||||
parsed, parseErr = R.NewPort(payload, target, true)
|
||||
case "DST-PORT":
|
||||
parsed, parseErr = R.NewPort(payload, target, false)
|
||||
case "MATCH":
|
||||
fallthrough
|
||||
// deprecated when bump to 1.0
|
||||
case "FINAL":
|
||||
parsed = R.NewMatch(target)
|
||||
default:
|
||||
parseErr = fmt.Errorf("unsupported rule type %s", rule[0])
|
||||
}
|
||||
|
||||
parsed, parseErr := R.ParseRule(rule[0], payload, target, params)
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
}
|
||||
@ -446,8 +391,14 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func parseHosts(cfg *RawConfig) (*trie.Trie, error) {
|
||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
|
||||
tree := trie.New()
|
||||
|
||||
// add default hosts
|
||||
if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
|
||||
if len(cfg.Hosts) != 0 {
|
||||
for domain, ipStr := range cfg.Hosts {
|
||||
ip := net.ParseIP(ipStr)
|
||||
@ -582,7 +533,7 @@ func parseDNS(cfg RawDNS) (*DNS, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var host *trie.Trie
|
||||
var host *trie.DomainTrie
|
||||
// fake ip skip host filter
|
||||
if len(cfg.FakeIPFilter) != 0 {
|
||||
host = trie.New()
|
||||
|
@ -57,7 +57,8 @@ type Conn interface {
|
||||
type PacketConn interface {
|
||||
net.PacketConn
|
||||
Connection
|
||||
WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
|
||||
// Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed
|
||||
// WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
|
||||
}
|
||||
|
||||
type ProxyAdapter interface {
|
||||
|
@ -23,8 +23,8 @@ const (
|
||||
|
||||
type NetWork int
|
||||
|
||||
func (n *NetWork) String() string {
|
||||
if *n == TCP {
|
||||
func (n NetWork) String() string {
|
||||
if n == TCP {
|
||||
return "tcp"
|
||||
}
|
||||
return "udp"
|
||||
|
@ -44,7 +44,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, dc.url+"?bla=foo:443", bytes.NewReader(buf))
|
||||
req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return req, err
|
||||
}
|
||||
@ -75,7 +75,8 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return &dohClient{
|
||||
url: url,
|
||||
transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
|
@ -58,9 +58,23 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
|
||||
func withResolver(resolver *Resolver) handler {
|
||||
return func(w D.ResponseWriter, r *D.Msg) {
|
||||
q := r.Question[0]
|
||||
|
||||
// return a empty AAAA msg when ipv6 disabled
|
||||
if !resolver.ipv6 && q.Qtype == D.TypeAAAA {
|
||||
msg := &D.Msg{}
|
||||
msg.Answer = []D.RR{}
|
||||
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := resolver.Exchange(r)
|
||||
if err != nil {
|
||||
q := r.Question[0]
|
||||
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
|
@ -121,7 +121,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
}
|
||||
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
if r.mapping {
|
||||
if r.mapping || r.fakeip {
|
||||
ips := r.msgToIP(msg)
|
||||
for _, ip := range ips {
|
||||
putMsgToCache(r.lruCache, ip.String(), msg)
|
||||
@ -151,7 +151,10 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
// IPToHost return fake-ip or redir-host mapping host
|
||||
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
||||
if r.fakeip {
|
||||
return r.pool.LookBack(ip)
|
||||
record, existed := r.pool.LookBack(ip)
|
||||
if existed {
|
||||
return record, true
|
||||
}
|
||||
}
|
||||
|
||||
cache, _ := r.lruCache.Get(ip.String())
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
@ -42,6 +43,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
||||
|
||||
if server.Server != nil {
|
||||
server.Shutdown()
|
||||
server = &Server{}
|
||||
address = ""
|
||||
}
|
||||
|
||||
@ -62,7 +64,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
||||
|
||||
err = sockopt.UDPReuseaddr(p)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||
}
|
||||
|
||||
address = addr
|
||||
|
@ -89,7 +89,7 @@ func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
|
||||
case len(msg.Extra) != 0:
|
||||
ttl = msg.Extra[0].Header().Ttl
|
||||
default:
|
||||
log.Debugln("[DNS] response msg error: %#v", msg)
|
||||
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||
return
|
||||
}
|
||||
|
||||
@ -111,10 +111,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
}
|
||||
|
||||
func isIPRequest(q D.Question) bool {
|
||||
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA)
|
||||
}
|
||||
|
||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
|
10
go.mod
10
go.mod
@ -5,7 +5,7 @@ go 1.14
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.5
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/go-chi/chi v4.1.1+incompatible
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/cors v1.1.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
@ -13,10 +13,10 @@ require (
|
||||
github.com/miekg/dns v1.1.29
|
||||
github.com/oschwald/geoip2-golang v1.4.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
gopkg.in/eapache/channels.v1 v1.1.0
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
23
go.sum
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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
|
||||
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
@ -27,22 +27,23 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -61,5 +62,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
||||
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
@ -20,30 +20,15 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
// forward compatibility before 1.0
|
||||
func readRawConfig(path string) ([]byte, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err == nil && len(data) != 0 {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
if filepath.Ext(path) != ".yaml" {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path = path[:len(path)-5] + ".yml"
|
||||
if _, fallbackErr := os.Stat(path); fallbackErr == nil {
|
||||
return ioutil.ReadFile(path)
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
var (
|
||||
mux sync.Mutex
|
||||
)
|
||||
|
||||
func readConfig(path string) ([]byte, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
data, err := readRawConfig(path)
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -77,10 +62,11 @@ func ParseWithBytes(buf []byte) (*config.Config, error) {
|
||||
|
||||
// ApplyConfig dispatch configure to all parts
|
||||
func ApplyConfig(cfg *config.Config, force bool) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
|
||||
updateUsers(cfg.Users)
|
||||
if force {
|
||||
updateGeneral(cfg.General)
|
||||
}
|
||||
updateGeneral(cfg.General, force)
|
||||
updateProxies(cfg.Proxies, cfg.Providers)
|
||||
updateRules(cfg.Rules)
|
||||
updateDNS(cfg.DNS)
|
||||
@ -96,31 +82,23 @@ func GetGeneral() *config.General {
|
||||
}
|
||||
|
||||
general := &config.General{
|
||||
Port: ports.Port,
|
||||
SocksPort: ports.SocksPort,
|
||||
RedirPort: ports.RedirPort,
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
BindAddress: P.BindAddress(),
|
||||
Mode: tunnel.Mode(),
|
||||
LogLevel: log.Level(),
|
||||
Inbound: config.Inbound{
|
||||
Port: ports.Port,
|
||||
SocksPort: ports.SocksPort,
|
||||
RedirPort: ports.RedirPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
BindAddress: P.BindAddress(),
|
||||
},
|
||||
Mode: tunnel.Mode(),
|
||||
LogLevel: log.Level(),
|
||||
}
|
||||
|
||||
return general
|
||||
}
|
||||
|
||||
func updateExperimental(c *config.Config) {
|
||||
cfg := c.Experimental
|
||||
|
||||
tunnel.UpdateExperimental(cfg.IgnoreResolveFail)
|
||||
if cfg.Interface != "" && c.DNS.Enable {
|
||||
dialer.DialHook = dialer.DialerWithInterface(cfg.Interface)
|
||||
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface)
|
||||
} else {
|
||||
dialer.DialHook = nil
|
||||
dialer.ListenPacketHook = nil
|
||||
}
|
||||
}
|
||||
func updateExperimental(c *config.Config) {}
|
||||
|
||||
func updateDNS(c *config.DNS) {
|
||||
if c.Enable == false {
|
||||
@ -153,7 +131,7 @@ func updateDNS(c *config.DNS) {
|
||||
}
|
||||
}
|
||||
|
||||
func updateHosts(tree *trie.Trie) {
|
||||
func updateHosts(tree *trie.DomainTrie) {
|
||||
resolver.DefaultHosts = tree
|
||||
}
|
||||
|
||||
@ -165,9 +143,22 @@ func updateRules(rules []C.Rule) {
|
||||
tunnel.UpdateRules(rules)
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General) {
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
log.SetLevel(general.LogLevel)
|
||||
tunnel.SetMode(general.Mode)
|
||||
resolver.DisableIPv6 = !general.IPv6
|
||||
|
||||
if general.Interface != "" {
|
||||
dialer.DialHook = dialer.DialerWithInterface(general.Interface)
|
||||
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface)
|
||||
} else {
|
||||
dialer.DialHook = nil
|
||||
dialer.ListenPacketHook = nil
|
||||
}
|
||||
|
||||
if !force {
|
||||
return
|
||||
}
|
||||
|
||||
allowLan := general.AllowLan
|
||||
P.SetAllowLan(allowLan)
|
||||
@ -186,6 +177,10 @@ func updateGeneral(general *config.General) {
|
||||
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
||||
log.Errorln("Start Redir server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func updateUsers(users []auth.AuthUser) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/hub/route"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
)
|
||||
|
||||
type Option func(*config.Config)
|
||||
|
@ -26,6 +26,7 @@ type configSchema struct {
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
@ -65,6 +66,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||
|
||||
if general.Mode != nil {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
|
@ -36,7 +36,7 @@ type Traffic struct {
|
||||
}
|
||||
|
||||
func SetUIPath(path string) {
|
||||
uiPath = path
|
||||
uiPath = C.Path.Resolve(path)
|
||||
}
|
||||
|
||||
func Start(addr string, secret string) {
|
||||
|
@ -41,7 +41,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
go handleConn(c, hl.cache)
|
||||
go HandleConn(c, hl.cache)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -69,7 +69,7 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache
|
||||
return
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, cache *cache.Cache) {
|
||||
func HandleConn(conn net.Conn, cache *cache.Cache) {
|
||||
br := bufio.NewReader(conn)
|
||||
request, err := http.ReadRequest(br)
|
||||
if err != nil || request.URL.Host == "" {
|
||||
|
@ -4,9 +4,11 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/proxy/http"
|
||||
"github.com/Dreamacro/clash/proxy/mixed"
|
||||
"github.com/Dreamacro/clash/proxy/redir"
|
||||
"github.com/Dreamacro/clash/proxy/socks"
|
||||
)
|
||||
@ -20,6 +22,15 @@ var (
|
||||
httpListener *http.HttpListener
|
||||
redirListener *redir.RedirListener
|
||||
redirUDPListener *redir.RedirUDPListener
|
||||
mixedListener *mixed.MixedListener
|
||||
mixedUDPLister *socks.SockUDPListener
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
httpMux sync.Mutex
|
||||
redirMux sync.Mutex
|
||||
mixedMux sync.Mutex
|
||||
tunMux sync.Mutex
|
||||
)
|
||||
|
||||
type listener interface {
|
||||
@ -31,6 +42,7 @@ type Ports struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
}
|
||||
|
||||
func AllowLan() bool {
|
||||
@ -50,6 +62,9 @@ func SetBindAddress(host string) {
|
||||
}
|
||||
|
||||
func ReCreateHTTP(port int) error {
|
||||
httpMux.Lock()
|
||||
defer httpMux.Unlock()
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
if httpListener != nil {
|
||||
@ -74,6 +89,9 @@ func ReCreateHTTP(port int) error {
|
||||
}
|
||||
|
||||
func ReCreateSocks(port int) error {
|
||||
socksMux.Lock()
|
||||
defer socksMux.Unlock()
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
shouldTCPIgnore := false
|
||||
@ -123,6 +141,9 @@ func ReCreateSocks(port int) error {
|
||||
}
|
||||
|
||||
func ReCreateRedir(port int) error {
|
||||
redirMux.Lock()
|
||||
defer redirMux.Unlock()
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
if redirListener != nil {
|
||||
@ -159,6 +180,55 @@ func ReCreateRedir(port int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReCreateMixed(port int) error {
|
||||
mixedMux.Lock()
|
||||
defer mixedMux.Unlock()
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
shouldTCPIgnore := false
|
||||
shouldUDPIgnore := false
|
||||
|
||||
if mixedListener != nil {
|
||||
if mixedListener.Address() != addr {
|
||||
mixedListener.Close()
|
||||
mixedListener = nil
|
||||
} else {
|
||||
shouldTCPIgnore = true
|
||||
}
|
||||
}
|
||||
if mixedUDPLister != nil {
|
||||
if mixedUDPLister.Address() != addr {
|
||||
mixedUDPLister.Close()
|
||||
mixedUDPLister = nil
|
||||
} else {
|
||||
shouldUDPIgnore = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldTCPIgnore && shouldUDPIgnore {
|
||||
return nil
|
||||
}
|
||||
|
||||
if portIsZero(addr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
mixedListener, err = mixed.NewMixedProxy(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mixedUDPLister, err = socks.NewSocksUDPProxy(addr)
|
||||
if err != nil {
|
||||
mixedListener.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPorts return the ports of proxy servers
|
||||
func GetPorts() *Ports {
|
||||
ports := &Ports{}
|
||||
@ -181,6 +251,12 @@ func GetPorts() *Ports {
|
||||
ports.RedirPort = port
|
||||
}
|
||||
|
||||
if mixedListener != nil {
|
||||
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
ports.MixedPort = port
|
||||
}
|
||||
|
||||
return ports
|
||||
}
|
||||
|
||||
|
41
proxy/mixed/conn.go
Normal file
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
|
||||
}
|
||||
go handleSocks(c)
|
||||
go HandleSocks(c)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -52,13 +52,15 @@ func (l *SockListener) Address() string {
|
||||
return l.address
|
||||
}
|
||||
|
||||
func handleSocks(conn net.Conn) {
|
||||
func HandleSocks(conn net.Conn) {
|
||||
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
if c, ok := conn.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
if command == socks5.CmdUDPAssociate {
|
||||
defer conn.Close()
|
||||
io.Copy(ioutil.Discard, conn)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
@ -25,7 +26,7 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
|
||||
|
||||
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||
}
|
||||
|
||||
sl := &SockUDPListener{l, addr, false}
|
||||
|
41
rules/parser.go
Normal file
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 (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -9,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
@ -81,12 +83,25 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
|
||||
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error {
|
||||
defer packet.Drop()
|
||||
|
||||
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
|
||||
return
|
||||
// local resolve UDP dns
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
addr := metadata.UDPAddr()
|
||||
if addr == nil {
|
||||
return errors.New("udp addr invalid")
|
||||
}
|
||||
|
||||
_, err := pc.WriteTo(packet.Data(), addr)
|
||||
return err
|
||||
}
|
||||
|
||||
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) {
|
||||
|
@ -3,6 +3,7 @@ package tunnel
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TunnelMode int
|
||||
@ -26,7 +27,7 @@ const (
|
||||
func (m *TunnelMode) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
json.Unmarshal(data, &tp)
|
||||
mode, exist := ModeMapping[tp]
|
||||
mode, exist := ModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
@ -38,7 +39,7 @@ func (m *TunnelMode) UnmarshalJSON(data []byte) error {
|
||||
func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var tp string
|
||||
unmarshal(&tp)
|
||||
mode, exist := ModeMapping[tp]
|
||||
mode, exist := ModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
@ -59,11 +60,11 @@ func (m TunnelMode) MarshalYAML() (interface{}, error) {
|
||||
func (m TunnelMode) String() string {
|
||||
switch m {
|
||||
case Global:
|
||||
return "Global"
|
||||
return "global"
|
||||
case Rule:
|
||||
return "Rule"
|
||||
return "rule"
|
||||
case Direct:
|
||||
return "Direct"
|
||||
return "direct"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ type trackerInfo struct {
|
||||
Start time.Time `json:"start"`
|
||||
Chain C.Chain `json:"chains"`
|
||||
Rule string `json:"rule"`
|
||||
RulePayload string `json:"rulePayload"`
|
||||
}
|
||||
|
||||
type tcpTracker struct {
|
||||
@ -56,10 +57,6 @@ func (tt *tcpTracker) Close() error {
|
||||
|
||||
func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker {
|
||||
uuid, _ := uuid.NewV4()
|
||||
ruleType := ""
|
||||
if rule != nil {
|
||||
ruleType = rule.RuleType().String()
|
||||
}
|
||||
|
||||
t := &tcpTracker{
|
||||
Conn: conn,
|
||||
@ -69,10 +66,15 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: conn.Chains(),
|
||||
Rule: ruleType,
|
||||
Rule: "",
|
||||
},
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
t.trackerInfo.Rule = rule.RuleType().String()
|
||||
t.trackerInfo.RulePayload = rule.Payload()
|
||||
}
|
||||
|
||||
manager.Join(t)
|
||||
return t
|
||||
}
|
||||
@ -103,14 +105,6 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (ut *udpTracker) WriteWithMetadata(p []byte, metadata *C.Metadata) (int, error) {
|
||||
n, err := ut.PacketConn.WriteWithMetadata(p, metadata)
|
||||
upload := int64(n)
|
||||
ut.manager.Upload() <- upload
|
||||
ut.UploadTotal += upload
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Close() error {
|
||||
ut.manager.Leave(ut)
|
||||
return ut.PacketConn.Close()
|
||||
@ -118,10 +112,6 @@ func (ut *udpTracker) Close() error {
|
||||
|
||||
func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
|
||||
uuid, _ := uuid.NewV4()
|
||||
ruleType := ""
|
||||
if rule != nil {
|
||||
ruleType = rule.RuleType().String()
|
||||
}
|
||||
|
||||
ut := &udpTracker{
|
||||
PacketConn: conn,
|
||||
@ -131,10 +121,15 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: conn.Chains(),
|
||||
Rule: ruleType,
|
||||
Rule: "",
|
||||
},
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
ut.trackerInfo.Rule = rule.RuleType().String()
|
||||
ut.trackerInfo.RulePayload = rule.Payload()
|
||||
}
|
||||
|
||||
manager.Join(ut)
|
||||
return ut
|
||||
}
|
||||
|
@ -28,9 +28,6 @@ var (
|
||||
configMux sync.RWMutex
|
||||
enhancedMode *dns.Resolver
|
||||
|
||||
// experimental features
|
||||
ignoreResolveFail bool
|
||||
|
||||
// Outbound Rule
|
||||
mode = Rule
|
||||
|
||||
@ -82,13 +79,6 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid
|
||||
configMux.Unlock()
|
||||
}
|
||||
|
||||
// UpdateExperimental handle update experimental config
|
||||
func UpdateExperimental(value bool) {
|
||||
configMux.Lock()
|
||||
ignoreResolveFail = value
|
||||
configMux.Unlock()
|
||||
}
|
||||
|
||||
// Mode return current mode
|
||||
func Mode() TunnelMode {
|
||||
return mode
|
||||
@ -318,9 +308,6 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
if !resolved && shouldResolveIP(rule, metadata) {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
if !ignoreResolveFail {
|
||||
return nil, nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error())
|
||||
}
|
||||
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
|
||||
} else {
|
||||
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
|
||||
|
Reference in New Issue
Block a user