Compare commits

...

31 Commits

Author SHA1 Message Date
609869bf5a Change: make ping under authentication 2020-02-21 18:00:19 +08:00
d68339cea7 Fix: socks5 inbound return remote udp addr for identity 2020-02-20 11:29:16 +08:00
0f4cdbf187 Chore: remove unused code 2020-02-18 16:05:12 +08:00
f3f8e7e52f Chore: remove println 2020-02-18 14:26:42 +08:00
8d07c1eb3e Chore: initial config with port 2020-02-18 13:48:15 +08:00
46edae9896 Fix: domain dns crash 2020-02-17 22:13:15 +08:00
df0ab6aa8e Fix: ipv6 dns crash 2020-02-17 20:11:46 +08:00
7b48138ad0 Fix: vmess udp crash 2020-02-17 17:34:19 +08:00
e9032c55fa Chore: update dependencies 2020-02-17 12:25:55 +08:00
d75cb069d9 Feature: add default-nameserver and outbound interface 2020-02-15 21:42:46 +08:00
f69f635e0b Chore: add issue templates 2020-02-14 19:16:43 +08:00
8b5e511426 Fix: use the fastest whether the result is successful 2020-02-14 16:36:20 +08:00
6641bf7c07 Fix: vmessUDPConn should return a correctly address 2020-02-12 13:12:07 +08:00
afc9f3f59a Chore: use custom dialer 2020-02-09 17:02:48 +08:00
a55be58c01 Fix: provider should fallback to read remote when local file invalid 2020-02-08 16:21:52 +08:00
dcf97ff5b4 Fix: should prehandle metadata before resolve 2020-02-07 20:53:43 +08:00
72c0af9739 Chore: udp resolve ip on local 2020-01-31 19:26:33 +08:00
b0f9c6afa8 Fix: should close socks udp PacketConn 2020-01-31 15:03:59 +08:00
19bb0b655c Fix: match log display 2020-01-31 14:58:54 +08:00
26ce3e8814 Improve: udp NAT type 2020-01-31 14:43:54 +08:00
aa207ec664 Fix: qemu permission 2020-01-30 21:19:51 +08:00
c626b988a6 Fix: add docker hub pre build 2020-01-30 17:25:55 +08:00
82c387e92b Chore: fix typo (#490) 2020-01-30 17:03:10 +08:00
14fb789002 Feature: add arm32 and arm64 docker image 2020-01-28 16:39:52 +08:00
9071351022 Chore: aggregate mmdb (#474) 2020-01-11 21:07:01 +08:00
f688eda2c2 Chore: fix typo (#479) 2020-01-11 21:02:55 +08:00
2810533df4 Chore: export raw config struct (#475) 2020-01-11 00:22:34 +08:00
6b7144acce Chore: export reset manager statistic api (#476) 2020-01-11 00:20:10 +08:00
e68c0d088b Fix: upstream dns ExchangeContext workaround (#468) 2020-01-10 14:13:44 +08:00
2c0cc374d3 Fix: README typo 2020-01-09 18:13:15 +08:00
86d3d77a7f Chore: increase DNS timeout (#464) 2020-01-01 19:23:34 +08:00
61 changed files with 1202 additions and 625 deletions

96
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,96 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]"
labels: ''
assignees: ''
---
<!-- The English version is available. -->
感谢你向 Clash Core 提交 issue
在提交之前,请确认:
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的问题
- [ ] 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 Openclash、Koolclash 等)的特定问题
- [ ] 我已经使用 Clash core 的 dev 分支版本测试过,问题依旧存在
- [ ] 如果你可以自己 debug 并解决的话,提交 PR 吧!
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
<!--
Thanks for submitting an issue towards the Clash core!
But before so, please do the following checklist:
- [ ] Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
- [ ] Your issue may already be reported! Please search on the [issue tracker](……/) before creating one.
- [ ] I have tested using the dev branch, and the issue still exists.
- [ ] This is an issue related to the Clash core, not to the derivatives of Clash, like Openclash or Koolclash
Please understand that we close issues that fail to follow the issue template.
-->
我都确认过了,我要继续提交。
<!-- None of the above, create a bug report -->
------------------------------------------------------------------
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
<!-- Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. -->
### clash core config
<!--
在下方附上 Clash core 脱敏后配置文件的内容
Paste the Clash core configuration below.
-->
```
……
```
### Clash log
<!--
在下方附上 Clash Core 的日志log level 最好使用 DEBUG
Paste the Clash core log below with the log level set to `DEBUG`.
-->
```
……
```
### 环境 Environment
* Clash Core 的操作系统 (the OS that the Clash core is running on)
……
* 使用者的操作系统 (the OS running on the client)
……
* 网路环境或拓扑 (network conditions/topology)
……
* iptables如果适用 (if applicable)
……
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
……
* 其他
……
### 说明 Description
<!--
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?
-->
### 重现问题的具体布骤 Steps to Reproduce
1. [First Step]
2. [Second Step]
3. ……
**我预期会发生……?**
<!-- **Expected behavior:** [What you expected to happen] -->
**实际上发生了什麽?**
<!-- **Actual behavior:** [What actually happened] -->
### 可能的解决方案 Possible Solution
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
<!-- or ideas how to implement the addition or change -->
### 更多信息 More Information

View File

@ -0,0 +1,78 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature]"
labels: ''
assignees: ''
---
<!-- The English version is available. -->
感谢你向 Clash Core 提交 Feature Request
在提交之前,请确认:
- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求
请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。
<!--
Thanks for submitting a feature request towards the Clash core!
But before so, please do the following checklist:
- [ ] I have searched on the [issue tracker](……/) before creating the issue.
Please understand that we close issues that fail to follow the issue template.
-->
我都确认过了,我要继续提交。
<!-- None of the above, create a feature request -->
------------------------------------------------------------------
请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。
<!-- Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. -->
### Clash core config
<!--
在下方附上 Clash Core 脱敏后的配置内容
Paste the Clash core configuration below.
-->
```
……
```
### Clash log
<!--
在下方附上 Clash Core 的日志log level 请使用 DEBUG
Paste the Clash core log below with the log level set to `DEBUG`.
-->
```
……
```
### 环境 Environment
* Clash Core 的操作系统 (the OS that the Clash core is running on)
……
* 使用者的操作系统 (the OS running on the client)
……
* 网路环境或拓扑 (network conditions/topology)
……
* iptables如果适用 (if applicable)
……
* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?)
……
* 其他
……
### 说明 Description
<!--
请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
-->
### 可能的解决方案 Possible Solution
<!-- 此项非必须,但是如果你有想法的话欢迎提出。 -->
<!-- Not obligatory, but suggest a fix/reason for the bug, -->
<!-- or ideas how to implement the addition or change -->
### 更多信息 More Information

20
Dockerfile.arm32v7 Normal file
View File

@ -0,0 +1,20 @@
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"]

20
Dockerfile.arm64v8 Normal file
View File

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

View File

@ -114,6 +114,7 @@ external-controller: 127.0.0.1:9090
# experimental feature
experimental:
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
# interface-name: en0 # outbound interface name
# authentication of local SOCKS5/HTTP(S) server
# authentication:
@ -130,10 +131,13 @@ experimental:
# enable: true # set true to enable dns (default is false)
# ipv6: false # default is false
# listen: 0.0.0.0:53
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
# # - 114.114.114.114
# # - 8.8.8.8
# enhanced-mode: redir-host # or fake-ip
# # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it
# fake-ip-filter: # fake ip white domain list
# - *.lan
# - '*.lan'
# - localhost.ptlogin2.qq.com
# nameserver:
# - 114.114.114.114

View File

@ -17,9 +17,9 @@ func (s *PacketAdapter) Metadata() *C.Metadata {
}
// NewPacket is PacketAdapter generator
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, netType C.NetWork) *PacketAdapter {
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = netType
metadata.NetWork = C.UDP
metadata.Type = source
if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
metadata.SrcIP = ip

View File

@ -30,8 +30,8 @@ func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
return nil, nil, errors.New("no support")
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("no support")
}
func (b *Base) SupportUDP() bool {
@ -65,8 +65,13 @@ func newConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}}
}
type packetConn struct {
type PacketConn interface {
net.PacketConn
WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error)
}
type packetConn struct {
PacketConn
chain C.Chain
}
@ -78,8 +83,8 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(c net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{c, []string{a.Name()}}
func newPacketConn(pc PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}}
}
type Proxy struct {

View File

@ -4,6 +4,8 @@ import (
"context"
"net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
)
@ -17,7 +19,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
}
c, err := dialContext(ctx, "tcp", address)
c, err := dialer.DialContext(ctx, "tcp", address)
if err != nil {
return nil, err
}
@ -25,17 +27,27 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return newConn(c, d), nil
}
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
return nil, err
}
return newPacketConn(&directPacketConn{pc}, d), nil
}
addr, err := resolveUDPAddr("udp", metadata.RemoteAddress())
if err != nil {
return nil, nil, err
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 newPacketConn(pc, d), addr, nil
return dp.WriteTo(p, metadata.UDPAddr())
}
func NewDirect() *Direct {

View File

@ -13,6 +13,7 @@ import (
"net/url"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
@ -35,7 +36,7 @@ type HttpOption struct {
}
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", h.addr)
c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err == nil && h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig)
err = cc.Handshake()

View File

@ -18,8 +18,8 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return newConn(&NopConn{}, r), nil
}
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
return nil, nil, errors.New("match reject rule")
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, errors.New("match reject rule")
}
func NewReject() *Reject {

View File

@ -9,6 +9,7 @@ import (
"strconv"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/socks5"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
@ -59,7 +60,7 @@ type v2rayObfsOption struct {
}
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.server)
c, err := dialer.DialContext(ctx, "tcp", ss.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
}
@ -82,24 +83,19 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C
return newConn(c, ss), err
}
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
return nil, err
}
addr, err := resolveUDPAddr("udp", ss.server)
if err != nil {
return nil, nil, err
}
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
if targetAddr == nil {
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
return nil, err
}
pc = ss.cipher.PacketConn(pc)
return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
}
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
@ -187,27 +183,33 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
}, nil
}
type ssUDPConn struct {
type ssPacketConn struct {
net.PacketConn
rAddr socks5.Addr
rAddr net.Addr
}
func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet[3:], addr)
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
}
func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := uc.PacketConn.ReadFrom(b)
addr := socks5.SplitAddr(b[:n])
var from net.Addr
if e == nil {
// Get the source IP/Port of packet.
from = addr.UDPAddr()
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
}
copy(b, b[len(addr):])
return n - len(addr), from, e
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 {
return 0, nil, e
}
addr := socks5.SplitAddr(b[:n])
copy(b, b[len(addr):])
return n - len(addr), addr.UDPAddr(), e
}

View File

@ -7,6 +7,7 @@ import (
"strconv"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/snell"
C "github.com/Dreamacro/clash/constant"
@ -28,7 +29,7 @@ type SnellOption struct {
}
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", s.server)
c, err := dialer.DialContext(ctx, "tcp", s.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
}

View File

@ -9,6 +9,7 @@ import (
"net"
"strconv"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
@ -35,7 +36,7 @@ type Socks5Option struct {
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.addr)
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig)
@ -60,10 +61,10 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
return newConn(c, ss), nil
}
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) {
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", ss.addr)
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return
@ -96,17 +97,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
return
}
addr, err := net.ResolveUDPAddr("udp", bindAddr.String())
if err != nil {
return
}
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
if targetAddr == nil {
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
}
pc, err := net.ListenPacket("udp", "")
pc, err := dialer.ListenPacket("udp", "")
if err != nil {
return
}
@ -119,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
pc.Close()
}()
return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr, tcpConn: c}, ss), addr, nil
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil
}
func NewSocks5(option Socks5Option) *Socks5 {
@ -147,21 +138,29 @@ func NewSocks5(option Socks5Option) *Socks5 {
}
}
type socksUDPConn struct {
type socksPacketConn struct {
net.PacketConn
rAddr socks5.Addr
rAddr net.Addr
tcpConn net.Conn
}
func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet, addr)
return uc.PacketConn.WriteTo(packet, uc.rAddr)
}
func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
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, a, e := uc.PacketConn.ReadFrom(b)
if e != nil {
return 0, nil, e
@ -176,7 +175,7 @@ func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
return n - addrLength - 3, a, nil
}
func (uc *socksUDPConn) Close() error {
func (uc *socksPacketConn) Close() error {
uc.tcpConn.Close()
return uc.PacketConn.Close()
}

View File

@ -2,7 +2,6 @@ package outbound
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
@ -11,9 +10,9 @@ import (
"sync"
"time"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
)
const (
@ -87,92 +86,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
return bytes.Join(buf, nil)
}
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
net.Conn
error
resolved bool
ipv6 bool
done bool
}
results := make(chan dialResult)
var primary, fallback dialResult
startRacer := func(ctx context.Context, host string, ipv6 bool) {
dialer := net.Dialer{}
result := dialResult{ipv6: ipv6, done: true}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
result.Conn.Close()
}
}
}()
var ip net.IP
if ipv6 {
ip, result.error = dns.ResolveIPv6(host)
} else {
ip, result.error = dns.ResolveIPv4(host)
}
if result.error != nil {
return
}
result.resolved = true
if ipv6 {
result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port))
} else {
result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
}
}
go startRacer(ctx, host, false)
go startRacer(ctx, host, true)
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
if !res.ipv6 {
primary = res
} else {
fallback = res
}
if primary.done && fallback.done {
if primary.resolved {
return nil, primary.error
} else if fallback.resolved {
return nil, fallback.error
} else {
return nil, primary.error
}
}
}
}
}
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := dns.ResolveIP(host)
ip, err := resolver.ResolveIP(host)
if err != nil {
return nil, err
}

View File

@ -2,11 +2,14 @@ package outbound
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant"
)
@ -33,7 +36,7 @@ type VmessOption struct {
}
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", v.server)
c, err := dialer.DialContext(ctx, "tcp", v.server)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.server)
}
@ -42,19 +45,28 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
return newConn(c, v), err
}
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", v.server)
c, err := dialer.DialContext(ctx, "tcp", v.server)
if err != nil {
return nil, nil, fmt.Errorf("%s connect error", v.server)
return nil, fmt.Errorf("%s connect error", v.server)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
if err != nil {
return nil, nil, fmt.Errorf("new vmess client error: %v", err)
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return newPacketConn(&vmessUDPConn{Conn: c}, v), c.RemoteAddr(), nil
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
func NewVmess(option VmessOption) (*Vmess, error) {
@ -115,15 +127,20 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
}
}
type vmessUDPConn struct {
type vmessPacketConn struct {
net.Conn
rAddr net.Addr
}
func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return uc.Conn.Write(b)
}
func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.RemoteAddr(), err
func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
return uc.Conn.Write(p)
}
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.rAddr, err
}

View File

@ -3,7 +3,6 @@ package outboundgroup
import (
"context"
"encoding/json"
"net"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
@ -31,13 +30,13 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
return c, err
}
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
proxy := f.findAliveProxy()
pc, addr, err := proxy.DialUDP(metadata)
pc, err := proxy.DialUDP(metadata)
if err == nil {
pc.AppendToChains(f)
}
return pc, addr, err
return pc, err
}
func (f *Fallback) SupportUDP() bool {

View File

@ -74,7 +74,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
return
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) {
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)

View File

@ -56,7 +56,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
// if Use not empty, drop health check options
if len(groupOption.Use) != 0 {
hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
@ -66,7 +66,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
// select don't need health check
if groupOption.Type == "select" {
hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}
@ -79,7 +79,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, err
}

View File

@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"errors"
"net"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
@ -27,12 +26,12 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
return c, err
}
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := s.selected.DialUDP(metadata)
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := s.selected.DialUDP(metadata)
if err == nil {
pc.AppendToChains(s)
}
return pc, addr, err
return pc, err
}
func (s *Selector) SupportUDP() bool {

View File

@ -3,7 +3,6 @@ package outboundgroup
import (
"context"
"encoding/json"
"net"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
@ -31,12 +30,12 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
return c, err
}
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := u.fast().DialUDP(metadata)
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := u.fast().DialUDP(metadata)
if err == nil {
pc.AppendToChains(u)
}
return pc, addr, err
return pc, err
}
func (u *URLTest) proxies() []C.Proxy {

View File

@ -41,7 +41,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
}
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
path := C.Path.Reslove(schema.Path)
path := C.Path.Resolve(schema.Path)
var vehicle Vehicle
switch schema.Type {

View File

@ -128,7 +128,11 @@ func (pp *ProxySetProvider) Initial() error {
proxies, err := pp.parse(buf)
if err != nil {
return err
// parse local file error, fallback to remote
buf, err = pp.vehicle.Read()
if err != nil {
return err
}
}
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
@ -249,13 +253,13 @@ func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, h
}
}
type CompatibleProvier struct {
type CompatibleProvider struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
}
func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": cp.Name(),
"type": cp.Type().String(),
@ -264,44 +268,44 @@ func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
})
}
func (cp *CompatibleProvier) Name() string {
func (cp *CompatibleProvider) Name() string {
return cp.name
}
func (cp *CompatibleProvier) Reload() error {
func (cp *CompatibleProvider) Reload() error {
return nil
}
func (cp *CompatibleProvier) Destroy() error {
func (cp *CompatibleProvider) Destroy() error {
cp.healthCheck.close()
return nil
}
func (cp *CompatibleProvier) HealthCheck() {
func (cp *CompatibleProvider) HealthCheck() {
cp.healthCheck.check()
}
func (cp *CompatibleProvier) Update() error {
func (cp *CompatibleProvider) Update() error {
return nil
}
func (cp *CompatibleProvier) Initial() error {
func (cp *CompatibleProvider) Initial() error {
return nil
}
func (cp *CompatibleProvier) VehicleType() VehicleType {
func (cp *CompatibleProvider) VehicleType() VehicleType {
return Compatible
}
func (cp *CompatibleProvier) Type() ProviderType {
func (cp *CompatibleProvider) Type() ProviderType {
return Proxy
}
func (cp *CompatibleProvier) Proxies() []C.Proxy {
func (cp *CompatibleProvider) Proxies() []C.Proxy {
return cp.proxies
}
func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) {
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least")
}
@ -310,7 +314,7 @@ func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*Com
go hc.process()
}
return &CompatibleProvier{
return &CompatibleProvider{
name: name,
proxies: proxies,
healthCheck: hc,

View File

@ -5,6 +5,8 @@ import (
"io/ioutil"
"net/http"
"time"
"github.com/Dreamacro/clash/component/dialer"
)
// Vehicle Type
@ -85,6 +87,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DialContext: dialer.DialContext,
}
client := http.Client{Transport: transport}

View File

@ -12,7 +12,7 @@ import (
type Option func(*LruCache)
// EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback func(key interface{}, value interface{})
type EvictCallback = func(key interface{}, value interface{})
// WithEvict set the evict callback
func WithEvict(cb EvictCallback) Option {

View File

@ -17,15 +17,12 @@ type Picker struct {
once sync.Once
result interface{}
firstDone chan struct{}
}
func newPicker(ctx context.Context, cancel func()) *Picker {
return &Picker{
ctx: ctx,
cancel: cancel,
firstDone: make(chan struct{}, 1),
ctx: ctx,
cancel: cancel,
}
}
@ -42,12 +39,6 @@ func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.C
return newPicker(ctx, cancel), ctx
}
// WithoutAutoCancel returns a new Picker and an associated Context derived from ctx,
// but it wouldn't cancel context when the first element return.
func WithoutAutoCancel(ctx context.Context) *Picker {
return newPicker(ctx, nil)
}
// Wait blocks until all function calls from the Go method have returned,
// then returns the first nil error result (if any) from them.
func (p *Picker) Wait() interface{} {
@ -58,16 +49,6 @@ func (p *Picker) Wait() interface{} {
return p.result
}
// WaitWithoutCancel blocks until the first result return, if timeout will return nil.
func (p *Picker) WaitWithoutCancel() interface{} {
select {
case <-p.firstDone:
return p.result
case <-p.ctx.Done():
return p.result
}
}
// Go calls the given function in a new goroutine.
// The first call to return a nil error cancels the group; its result will be returned by Wait.
func (p *Picker) Go(f func() (interface{}, error)) {
@ -79,7 +60,6 @@ func (p *Picker) Go(f func() (interface{}, error)) {
if ret, err := f(); err == nil {
p.once.Do(func() {
p.result = ret
p.firstDone <- struct{}{}
if p.cancel != nil {
p.cancel()
}

View File

@ -37,30 +37,3 @@ func TestPicker_Timeout(t *testing.T) {
number := picker.Wait()
assert.Nil(t, number)
}
func TestPicker_WaitWithoutAutoCancel(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*60)
defer cancel()
picker := WithoutAutoCancel(ctx)
trigger := false
picker.Go(sleepAndSend(ctx, 10, 1))
picker.Go(func() (interface{}, error) {
timer := time.NewTimer(time.Millisecond * time.Duration(30))
select {
case <-timer.C:
trigger = true
return 2, nil
case <-ctx.Done():
return nil, ctx.Err()
}
})
elm := picker.WaitWithoutCancel()
assert.NotNil(t, elm)
assert.Equal(t, elm.(int), 1)
elm = picker.Wait()
assert.True(t, trigger)
assert.Equal(t, elm.(int), 1)
}

154
component/dialer/dialer.go Normal file
View File

@ -0,0 +1,154 @@
package dialer
import (
"context"
"errors"
"net"
"github.com/Dreamacro/clash/component/resolver"
)
func Dialer() *net.Dialer {
dialer := &net.Dialer{}
if DialerHook != nil {
DialerHook(dialer)
}
return dialer
}
func ListenConfig() *net.ListenConfig {
cfg := &net.ListenConfig{}
if ListenConfigHook != nil {
ListenConfigHook(cfg)
}
return cfg
}
func Dial(network, address string) (net.Conn, error) {
return DialContext(context.Background(), network, address)
}
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
switch network {
case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
dialer := Dialer()
var ip net.IP
switch network {
case "tcp4", "udp4":
ip, err = resolver.ResolveIPv4(host)
default:
ip, err = resolver.ResolveIPv6(host)
}
if err != nil {
return nil, err
}
if DialHook != nil {
DialHook(dialer, network, ip)
}
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
case "tcp", "udp":
return dualStackDailContext(ctx, network, address)
default:
return nil, errors.New("network invalid")
}
}
func ListenPacket(network, address string) (net.PacketConn, error) {
lc := ListenConfig()
if ListenPacketHook != nil && address == "" {
ip := ListenPacketHook()
if ip != nil {
address = net.JoinHostPort(ip.String(), "0")
}
}
return lc.ListenPacket(context.Background(), network, address)
}
func dualStackDailContext(ctx context.Context, network, address string) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
net.Conn
error
resolved bool
ipv6 bool
done bool
}
results := make(chan dialResult)
var primary, fallback dialResult
startRacer := func(ctx context.Context, network, host string, ipv6 bool) {
dialer := Dialer()
result := dialResult{ipv6: ipv6, done: true}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
result.Conn.Close()
}
}
}()
var ip net.IP
if ipv6 {
ip, result.error = resolver.ResolveIPv6(host)
} else {
ip, result.error = resolver.ResolveIPv4(host)
}
if result.error != nil {
return
}
result.resolved = true
if DialHook != nil {
DialHook(dialer, network, ip)
}
result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
}
go startRacer(ctx, network+"4", host, false)
go startRacer(ctx, network+"6", host, true)
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
if !res.ipv6 {
primary = res
} else {
fallback = res
}
if primary.done && fallback.done {
if primary.resolved {
return nil, primary.error
} else if fallback.resolved {
return nil, fallback.error
} else {
return nil, primary.error
}
}
}
}
}

142
component/dialer/hook.go Normal file
View File

@ -0,0 +1,142 @@
package dialer
import (
"errors"
"net"
"time"
"github.com/Dreamacro/clash/common/singledo"
)
type DialerHookFunc = func(dialer *net.Dialer)
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP)
type ListenConfigHookFunc = func(*net.ListenConfig)
type ListenPacketHookFunc = func() net.IP
var (
DialerHook DialerHookFunc
DialHook DialHookFunc
ListenConfigHook ListenConfigHookFunc
ListenPacketHook ListenPacketHookFunc
)
var (
ErrAddrNotFound = errors.New("addr not found")
ErrNetworkNotSupport = errors.New("network not support")
)
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
ipv4 := ip.To4() != nil
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok {
continue
}
addrV4 := addr.IP.To4() != nil
if addrV4 && ipv4 {
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
} else if !addrV4 && !ipv4 {
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
}
}
return nil, ErrAddrNotFound
}
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
ipv4 := ip.To4() != nil
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok {
continue
}
addrV4 := addr.IP.To4() != nil
if addrV4 && ipv4 {
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
} else if !addrV4 && !ipv4 {
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
}
}
return nil, ErrAddrNotFound
}
func ListenPacketWithInterface(name string) ListenPacketHookFunc {
single := singledo.NewSingle(5 * time.Second)
return func() net.IP {
elm, err, _ := single.Do(func() (interface{}, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
return addrs, nil
})
if err != nil {
return nil
}
addrs := elm.([]net.Addr)
for _, elm := range addrs {
addr, ok := elm.(*net.IPNet)
if !ok || addr.IP.To4() == nil {
continue
}
return addr.IP
}
return nil
}
}
func DialerWithInterface(name string) DialHookFunc {
single := singledo.NewSingle(5 * time.Second)
return func(dialer *net.Dialer, network string, ip net.IP) {
elm, err, _ := single.Do(func() (interface{}, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
return addrs, nil
})
if err != nil {
return
}
addrs := elm.([]net.Addr)
switch network {
case "tcp", "tcp4", "tcp6":
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
dialer.LocalAddr = addr
}
case "udp", "udp4", "udp6":
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
dialer.LocalAddr = addr
}
}
}
}

35
component/mmdb/mmdb.go Normal file
View File

@ -0,0 +1,35 @@
package mmdb
import (
"sync"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/oschwald/geoip2-golang"
)
var mmdb *geoip2.Reader
var once sync.Once
func LoadFromBytes(buffer []byte) {
once.Do(func() {
var err error
mmdb, err = geoip2.FromBytes(buffer)
if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error())
}
})
}
func Instance() *geoip2.Reader {
once.Do(func() {
var err error
mmdb, err = geoip2.Open(C.Path.MMDB())
if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error())
}
})
return mmdb
}

View File

@ -1,34 +1,25 @@
package nat
import (
"net"
"sync"
C "github.com/Dreamacro/clash/constant"
)
type Table struct {
mapping sync.Map
}
type element struct {
RemoteAddr net.Addr
RemoteConn net.PacketConn
func (t *Table) Set(key string, pc C.PacketConn) {
t.mapping.Store(key, pc)
}
func (t *Table) Set(key string, pc net.PacketConn, addr net.Addr) {
// set conn read timeout
t.mapping.Store(key, &element{
RemoteConn: pc,
RemoteAddr: addr,
})
}
func (t *Table) Get(key string) (net.PacketConn, net.Addr) {
func (t *Table) Get(key string) C.PacketConn {
item, exist := t.mapping.Load(key)
if !exist {
return nil, nil
return nil
}
elm := item.(*element)
return elm.RemoteConn, elm.RemoteAddr
return item.(C.PacketConn)
}
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) {

View File

@ -1,16 +1,32 @@
package dns
package resolver
import (
"errors"
"net"
"strings"
trie "github.com/Dreamacro/clash/component/domain-trie"
)
var (
errIPNotFound = errors.New("couldn't find ip")
errIPVersion = errors.New("ip version error")
// DefaultResolver aim to resolve ip
DefaultResolver Resolver
// DefaultHosts aim to resolve hosts
DefaultHosts = trie.New()
)
var (
ErrIPNotFound = errors.New("couldn't find ip")
ErrIPVersion = errors.New("ip version error")
)
type Resolver interface {
ResolveIP(host string) (ip net.IP, err error)
ResolveIPv4(host string) (ip net.IP, err error)
ResolveIPv6(host string) (ip net.IP, err error)
}
// ResolveIPv4 with a host, return ipv4
func ResolveIPv4(host string) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil {
@ -24,7 +40,7 @@ func ResolveIPv4(host string) (net.IP, error) {
if !strings.Contains(host, ":") {
return ip, nil
}
return nil, errIPVersion
return nil, ErrIPVersion
}
if DefaultResolver != nil {
@ -42,7 +58,7 @@ func ResolveIPv4(host string) (net.IP, error) {
}
}
return nil, errIPNotFound
return nil, ErrIPNotFound
}
// ResolveIPv6 with a host, return ipv6
@ -58,7 +74,7 @@ func ResolveIPv6(host string) (net.IP, error) {
if strings.Contains(host, ":") {
return ip, nil
}
return nil, errIPVersion
return nil, ErrIPVersion
}
if DefaultResolver != nil {
@ -76,7 +92,7 @@ func ResolveIPv6(host string) (net.IP, error) {
}
}
return nil, errIPNotFound
return nil, ErrIPNotFound
}
// ResolveIP with a host, return ip
@ -86,10 +102,7 @@ func ResolveIP(host string) (net.IP, error) {
}
if DefaultResolver != nil {
if DefaultResolver.ipv6 {
return DefaultResolver.ResolveIP(host)
}
return DefaultResolver.ResolveIPv4(host)
return DefaultResolver.ResolveIP(host)
}
ip := net.ParseIP(host)

View File

@ -1,6 +1,7 @@
package config
import (
"errors"
"fmt"
"net"
"net/url"
@ -30,7 +31,7 @@ type General struct {
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
Mode T.Mode `json:"mode"`
Mode T.TunnelMode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"`
ExternalController string `json:"-"`
ExternalUI string `json:"-"`
@ -39,14 +40,15 @@ type General struct {
// DNS config
type DNS struct {
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
NameServer []dns.NameServer `yaml:"nameserver"`
Fallback []dns.NameServer `yaml:"fallback"`
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
FakeIPRange *fakeip.Pool
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
NameServer []dns.NameServer `yaml:"nameserver"`
Fallback []dns.NameServer `yaml:"fallback"`
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool
}
// FallbackFilter config
@ -57,7 +59,8 @@ type FallbackFilter struct {
// Experimental config
type Experimental struct {
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
Interface string `yaml:"interface-name"`
}
// Config is clash config manager
@ -72,31 +75,32 @@ type Config struct {
Providers map[string]provider.ProxyProvider
}
type rawDNS struct {
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"`
FallbackFilter rawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"`
type RawDNS struct {
Enable bool `yaml:"enable"`
IPv6 bool `yaml:"ipv6"`
NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"`
}
type rawFallbackFilter struct {
type RawFallbackFilter struct {
GeoIP bool `yaml:"geoip"`
IPCIDR []string `yaml:"ipcidr"`
}
type rawConfig struct {
type RawConfig struct {
Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"`
Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address"`
Mode T.Mode `yaml:"mode"`
Mode T.TunnelMode `yaml:"mode"`
LogLevel log.LogLevel `yaml:"log-level"`
ExternalController string `yaml:"external-controller"`
ExternalUI string `yaml:"external-ui"`
@ -104,7 +108,7 @@ type rawConfig struct {
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"`
Hosts map[string]string `yaml:"hosts"`
DNS rawDNS `yaml:"dns"`
DNS RawDNS `yaml:"dns"`
Experimental Experimental `yaml:"experimental"`
Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
@ -113,10 +117,17 @@ type rawConfig struct {
// Parse config
func Parse(buf []byte) (*Config, error) {
config := &Config{}
rawCfg, err := UnmarshalRawConfig(buf)
if err != nil {
return nil, err
}
return ParseRawConfig(rawCfg)
}
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
// config with some default value
rawCfg := &rawConfig{
rawCfg := &RawConfig{
AllowLan: false,
BindAddress: "*",
Mode: T.Rule,
@ -129,19 +140,30 @@ func Parse(buf []byte) (*Config, error) {
Experimental: Experimental{
IgnoreResolveFail: true,
},
DNS: rawDNS{
DNS: RawDNS{
Enable: false,
FakeIPRange: "198.18.0.1/16",
FallbackFilter: rawFallbackFilter{
FallbackFilter: RawFallbackFilter{
GeoIP: true,
IPCIDR: []string{},
},
DefaultNameserver: []string{
"114.114.114.114",
"8.8.8.8",
},
},
}
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
return nil, err
}
return rawCfg, nil
}
func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config := &Config{}
config.Experimental = &rawCfg.Experimental
general, err := parseGeneral(rawCfg)
@ -176,10 +198,11 @@ func Parse(buf []byte) (*Config, error) {
config.Hosts = hosts
config.Users = parseAuthentication(rawCfg.Authentication)
return config, nil
}
func parseGeneral(cfg *rawConfig) (*General, error) {
func parseGeneral(cfg *RawConfig) (*General, error) {
port := cfg.Port
socksPort := cfg.SocksPort
redirPort := cfg.RedirPort
@ -192,7 +215,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
logLevel := cfg.LogLevel
if externalUI != "" {
externalUI = C.Path.Reslove(externalUI)
externalUI = C.Path.Resolve(externalUI)
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
@ -214,7 +237,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
return general, 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) {
proxies = make(map[string]C.Proxy)
providersMap = make(map[string]provider.ProxyProvider)
proxyList := []string{}
@ -299,7 +322,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
proxies[groupName] = outbound.NewProxy(group)
}
// initial compatible provier
// initial compatible provider
for _, pd := range providersMap {
if pd.VehicleType() != provider.Compatible {
continue
@ -316,7 +339,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
ps = append(ps, proxies[v])
}
hc := provider.NewHealthCheck(ps, "", 0)
pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, hc)
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
providersMap[provider.ReservedName] = pd
global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd})
@ -324,7 +347,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
return proxies, providersMap, nil
}
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{}
rulesConfig := cfg.Rule
@ -403,7 +426,7 @@ 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.Trie, error) {
tree := trie.New()
if len(cfg.Hosts) != 0 {
for domain, ipStr := range cfg.Hosts {
@ -448,20 +471,20 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
}
var host, dnsNetType string
var addr, dnsNetType string
switch u.Scheme {
case "udp":
host, err = hostWithDefaultPort(u.Host, "53")
addr, err = hostWithDefaultPort(u.Host, "53")
dnsNetType = "" // UDP
case "tcp":
host, err = hostWithDefaultPort(u.Host, "53")
addr, err = hostWithDefaultPort(u.Host, "53")
dnsNetType = "tcp" // TCP
case "tls":
host, err = hostWithDefaultPort(u.Host, "853")
addr, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "tcp-tls" // DNS over TLS
case "https":
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
host = clearURL.String()
addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS
default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
@ -475,7 +498,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers,
dns.NameServer{
Net: dnsNetType,
Addr: host,
Addr: addr,
},
)
}
@ -496,7 +519,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
return ipNets, nil
}
func parseDNS(cfg rawDNS) (*DNS, error) {
func parseDNS(cfg RawDNS) (*DNS, error) {
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
}
@ -519,6 +542,20 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver")
}
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver); err != nil {
return nil, err
}
// check default nameserver is pure ip addr
for _, ns := range dnsCfg.DefaultNameserver {
host, _, err := net.SplitHostPort(ns.Addr)
if err != nil || net.ParseIP(host) == nil {
return nil, errors.New("default nameserver should be pure IP")
}
}
if cfg.EnhancedMode == dns.FAKEIP {
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
if err != nil {

View File

@ -38,15 +38,19 @@ func Init(dir string) error {
// initial config.yaml
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
log.Infoln("Can't find config, create an empty file")
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
log.Infoln("Can't find config, create a initial config file")
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error())
}
f.Write([]byte(`port: 7890`))
f.Close()
}
// initial mmdb
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download")
err := downloadMMDB(C.Path.MMDB())
if err != nil {
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("Can't download MMDB: %s", err.Error())
}
}

View File

@ -53,13 +53,14 @@ type Conn interface {
type PacketConn interface {
net.PacketConn
Connection
WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
}
type ProxyAdapter interface {
Name() string
Type() AdapterType
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
DialUDP(metadata *Metadata) (PacketConn, net.Addr, error)
DialUDP(metadata *Metadata) (PacketConn, error)
SupportUDP() bool
MarshalJSON() ([]byte, error)
}

View File

@ -3,6 +3,7 @@ package constant
import (
"encoding/json"
"net"
"strconv"
)
// Socks addr type
@ -70,6 +71,25 @@ func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), m.DstPort)
}
func (m *Metadata) SourceAddress() string {
return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
}
func (m *Metadata) Resolved() bool {
return m.DstIP != nil
}
func (m *Metadata) UDPAddr() *net.UDPAddr {
if m.NetWork != UDP || m.DstIP == nil {
return nil
}
port, _ := strconv.Atoi(m.DstPort)
return &net.UDPAddr{
IP: m.DstIP,
Port: port,
}
}
func (m *Metadata) String() string {
if m.Host != "" {
return m.Host

View File

@ -44,8 +44,8 @@ func (p *path) Config() string {
return p.configFile
}
// Reslove return a absolute path or a relative path with homedir
func (p *path) Reslove(path string) string {
// Resolve return a absolute path or a relative path with homedir
func (p *path) Resolve(path string) string {
if !filepath.IsAbs(path) {
return filepath.Join(p.HomeDir(), path)
}

View File

@ -2,13 +2,20 @@ package dns
import (
"context"
"fmt"
"net"
"strings"
"github.com/Dreamacro/clash/component/dialer"
D "github.com/miekg/dns"
)
type client struct {
*D.Client
Address string
r *Resolver
port string
host string
}
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
@ -16,6 +23,44 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
}
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address)
return
var ip net.IP
if c.r == nil {
// a default ip dns
ip = net.ParseIP(c.host)
} else {
var err error
if ip, err = c.r.ResolveIP(c.host); err != nil {
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
}
}
d := dialer.Dialer()
if dialer.DialHook != nil {
network := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") {
network = "tcp"
}
dialer.DialHook(d, network, ip)
}
c.Client.Dialer = d
// miekg/dns ExchangeContext doesn't respond to context cancel.
// this is a workaround
type result struct {
msg *D.Msg
err error
}
ch := make(chan result, 1)
go func() {
msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port))
ch <- result{msg, err}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case ret := <-ch:
return ret.msg, ret.err
}
}

View File

@ -5,8 +5,11 @@ import (
"context"
"crypto/tls"
"io/ioutil"
"net"
"net/http"
"github.com/Dreamacro/clash/component/dialer"
D "github.com/miekg/dns"
)
@ -15,12 +18,9 @@ const (
dotMimeType = "application/dns-message"
)
var dohTransport = &http.Transport{
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
}
type dohClient struct {
url string
url string
transport *http.Transport
}
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
@ -55,7 +55,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
}
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
client := &http.Client{Transport: dohTransport}
client := &http.Client{Transport: dc.transport}
resp, err := client.Do(req)
if err != nil {
return nil, err
@ -70,3 +70,25 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
err = msg.Unpack(buf)
return msg, err
}
func newDoHClient(url string, r *Resolver) *dohClient {
return &dohClient{
url: url,
transport: &http.Transport{
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ip, err := r.ResolveIPv4(host)
if err != nil {
return nil, err
}
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
},
},
}
}

View File

@ -1,6 +1,10 @@
package dns
import "net"
import (
"net"
"github.com/Dreamacro/clash/component/mmdb"
)
type fallbackFilter interface {
Match(net.IP) bool
@ -9,11 +13,7 @@ type fallbackFilter interface {
type geoipFilter struct{}
func (gf *geoipFilter) Match(ip net.IP) bool {
if mmdb == nil {
return false
}
record, _ := mmdb.Country(ip)
record, _ := mmdb.Instance().Country(ip)
return record.Country.IsoCode == "CN" || record.Country.IsoCode == ""
}

View File

@ -39,7 +39,8 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
msg.Answer = []D.RR{rr}
setMsgTTL(msg, 1)
msg.SetReply(r)
msg.SetRcode(r, msg.Rcode)
msg.Authoritative = true
w.WriteMsg(msg)
return
}
@ -55,7 +56,8 @@ func withResolver(resolver *Resolver) handler {
D.HandleFailed(w, r)
return
}
msg.SetReply(r)
msg.SetRcode(r, msg.Rcode)
msg.Authoritative = true
w.WriteMsg(msg)
return
}

View File

@ -4,38 +4,25 @@ import (
"context"
"crypto/tls"
"errors"
"math/rand"
"net"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker"
trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/fakeip"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang"
"golang.org/x/sync/singleflight"
)
var (
// DefaultResolver aim to resolve ip
DefaultResolver *Resolver
// DefaultHosts aim to resolve hosts
DefaultHosts = trie.New()
)
var (
globalSessionCache = tls.NewLRUClientSessionCache(64)
mmdb *geoip2.Reader
once sync.Once
)
type resolver interface {
type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
}
@ -50,36 +37,33 @@ type Resolver struct {
mapping bool
fakeip bool
pool *fakeip.Pool
main []resolver
fallback []resolver
main []dnsClient
fallback []dnsClient
fallbackFilters []fallbackFilter
group singleflight.Group
cache *cache.Cache
}
// ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
ch := make(chan net.IP)
ch := make(chan net.IP, 1)
go func() {
defer close(ch)
ip, err := r.resolveIP(host, D.TypeA)
ip, err := r.resolveIP(host, D.TypeAAAA)
if err != nil {
return
}
ch <- ip
}()
ip, err = r.resolveIP(host, D.TypeAAAA)
ip, err = r.resolveIP(host, D.TypeA)
if err == nil {
go func() {
<-ch
}()
return
}
ip, open := <-ch
if !open {
return nil, errIPNotFound
return nil, resolver.ErrIPNotFound
}
return ip, nil
@ -179,16 +163,12 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool {
return false
}
func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) {
fast, ctx := picker.WithTimeout(context.Background(), time.Second)
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
for _, client := range clients {
r := client
fast.Go(func() (interface{}, error) {
msg, err := r.ExchangeContext(ctx, m)
if err != nil || msg.Rcode != D.RcodeSuccess {
return nil, errors.New("resolve error")
}
return msg, nil
return r.ExchangeContext(ctx, m)
})
}
@ -233,6 +213,8 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
return ip, nil
} else if dnsType == D.TypeA && isIPv4 {
return ip, nil
} else {
return nil, resolver.ErrIPVersion
}
}
@ -245,11 +227,12 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
}
ips := r.msgToIP(msg)
if len(ips) == 0 {
return nil, errIPNotFound
ipLength := len(ips)
if ipLength == 0 {
return nil, resolver.ErrIPNotFound
}
ip = ips[0]
ip = ips[rand.Intn(ipLength)]
return
}
@ -268,7 +251,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
return ips
}
func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result {
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
ch := make(chan *result)
go func() {
res, err := r.batchExchange(client, msg)
@ -289,6 +272,7 @@ type FallbackFilter struct {
type Config struct {
Main, Fallback []NameServer
Default []NameServer
IPv6 bool
EnhancedMode EnhancedMode
FallbackFilter FallbackFilter
@ -296,9 +280,14 @@ type Config struct {
}
func New(config Config) *Resolver {
defaultResolver := &Resolver{
main: transform(config.Default, nil),
cache: cache.New(time.Second * 60),
}
r := &Resolver{
ipv6: config.IPv6,
main: transform(config.Main),
main: transform(config.Main, defaultResolver),
cache: cache.New(time.Second * 60),
mapping: config.EnhancedMode == MAPPING,
fakeip: config.EnhancedMode == FAKEIP,
@ -306,15 +295,11 @@ func New(config Config) *Resolver {
}
if len(config.Fallback) != 0 {
r.fallback = transform(config.Fallback)
r.fallback = transform(config.Fallback, defaultResolver)
}
fallbackFilters := []fallbackFilter{}
if config.FallbackFilter.GeoIP {
once.Do(func() {
mmdb, _ = geoip2.Open(C.Path.MMDB())
})
fallbackFilters = append(fallbackFilters, &geoipFilter{})
}
for _, ipnet := range config.FallbackFilter.IPCIDR {

View File

@ -4,13 +4,14 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"net"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/log"
yaml "gopkg.in/yaml.v2"
D "github.com/miekg/dns"
yaml "gopkg.in/yaml.v2"
)
var (
@ -117,14 +118,15 @@ func isIPRequest(q D.Question) bool {
return false
}
func transform(servers []NameServer) []resolver {
ret := []resolver{}
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret := []dnsClient{}
for _, s := range servers {
if s.Net == "https" {
ret = append(ret, &dohClient{url: s.Addr})
ret = append(ret, newDoHClient(s.Addr, resolver))
continue
}
host, port, _ := net.SplitHostPort(s.Addr)
ret = append(ret, &client{
Client: &D.Client{
Net: s.Net,
@ -134,8 +136,11 @@ func transform(servers []NameServer) []resolver {
NextProtos: []string{"dns"},
},
UDPSize: 4096,
Timeout: 5 * time.Second,
},
Address: s.Addr,
port: port,
host: host,
r: resolver,
})
}
return ret

10
go.mod
View File

@ -5,18 +5,18 @@ go 1.13
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.5
github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v4.0.2+incompatible
github.com/go-chi/chi v4.0.3+incompatible
github.com/go-chi/cors v1.0.0
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.1
github.com/miekg/dns v1.1.26
github.com/miekg/dns v1.1.27
github.com/oschwald/geoip2-golang v1.4.0
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
gopkg.in/eapache/channels.v1 v1.1.0
gopkg.in/yaml.v2 v2.2.7
gopkg.in/yaml.v2 v2.2.8
)

29
go.sum
View File

@ -5,8 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY=
github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
@ -17,8 +17,8 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
@ -35,17 +35,19 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/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 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
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 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
@ -55,21 +57,18 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/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=

4
hooks/pre_build Normal file
View File

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

View File

@ -8,14 +8,16 @@ import (
"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/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
authStore "github.com/Dreamacro/clash/proxy/auth"
T "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel"
)
// forward compatibility before 1.0
@ -83,7 +85,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateRules(cfg.Rules)
updateDNS(cfg.DNS)
updateHosts(cfg.Hosts)
updateExperimental(cfg.Experimental)
updateExperimental(cfg)
}
func GetGeneral() *config.General {
@ -100,20 +102,30 @@ func GetGeneral() *config.General {
Authentication: authenticator,
AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(),
Mode: T.Instance().Mode(),
Mode: tunnel.Mode(),
LogLevel: log.Level(),
}
return general
}
func updateExperimental(c *config.Experimental) {
T.Instance().UpdateExperimental(c.IgnoreResolveFail)
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) {
if c.Enable == false {
dns.DefaultResolver = nil
resolver.DefaultResolver = nil
tunnel.SetResolver(nil)
dns.ReCreateServer("", nil)
return
}
@ -127,8 +139,10 @@ func updateDNS(c *config.DNS) {
GeoIP: c.FallbackFilter.GeoIP,
IPCIDR: c.FallbackFilter.IPCIDR,
},
Default: c.DefaultNameserver,
})
dns.DefaultResolver = r
resolver.DefaultResolver = r
tunnel.SetResolver(r)
if err := dns.ReCreateServer(c.Listen, r); err != nil {
log.Errorln("Start DNS server error: %s", err.Error())
return
@ -140,11 +154,10 @@ func updateDNS(c *config.DNS) {
}
func updateHosts(tree *trie.Trie) {
dns.DefaultHosts = tree
resolver.DefaultHosts = tree
}
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
tunnel := T.Instance()
oldProviders := tunnel.Providers()
// close providers goroutine
@ -156,12 +169,12 @@ func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.Pro
}
func updateRules(rules []C.Rule) {
T.Instance().UpdateRules(rules)
tunnel.UpdateRules(rules)
}
func updateGeneral(general *config.General) {
log.SetLevel(general.LogLevel)
T.Instance().SetMode(general.Mode)
tunnel.SetMode(general.Mode)
allowLan := general.AllowLan
P.SetAllowLan(allowLan)

View File

@ -8,7 +8,7 @@ import (
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy"
T "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
@ -23,13 +23,13 @@ func configRouter() http.Handler {
}
type configSchema struct {
Port *int `json:"port"`
SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"`
AllowLan *bool `json:"allow-lan"`
BindAddress *string `json:"bind-address"`
Mode *T.Mode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
Port *int `json:"port"`
SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"`
AllowLan *bool `json:"allow-lan"`
BindAddress *string `json:"bind-address"`
Mode *tunnel.TunnelMode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
@ -67,7 +67,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
if general.Mode != nil {
T.Instance().SetMode(*general.Mode)
tunnel.SetMode(*general.Mode)
}
if general.LogLevel != nil {

View File

@ -5,7 +5,7 @@ import (
"net/http"
"github.com/Dreamacro/clash/adapters/provider"
T "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
@ -25,7 +25,7 @@ func proxyProviderRouter() http.Handler {
}
func getProviders(w http.ResponseWriter, r *http.Request) {
providers := T.Instance().Providers()
providers := tunnel.Providers()
render.JSON(w, r, render.M{
"providers": providers,
})
@ -63,7 +63,7 @@ func parseProviderName(next http.Handler) http.Handler {
func findProviderByName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProviderName).(string)
providers := T.Instance().Providers()
providers := tunnel.Providers()
provider, exist := providers[name]
if !exist {
render.Status(r, http.StatusNotFound)

View File

@ -10,7 +10,7 @@ import (
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
C "github.com/Dreamacro/clash/constant"
T "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
@ -40,7 +40,7 @@ func parseProxyName(next http.Handler) http.Handler {
func findProxyByName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProxyName).(string)
proxies := T.Instance().Proxies()
proxies := tunnel.Proxies()
proxy, exist := proxies[name]
if !exist {
render.Status(r, http.StatusNotFound)
@ -54,7 +54,7 @@ func findProxyByName(next http.Handler) http.Handler {
}
func getProxies(w http.ResponseWriter, r *http.Request) {
proxies := T.Instance().Proxies()
proxies := tunnel.Proxies()
render.JSON(w, r, render.M{
"proxies": proxies,
})

View File

@ -3,7 +3,7 @@ package route
import (
"net/http"
T "github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi"
"github.com/go-chi/render"
@ -22,7 +22,7 @@ type Rule struct {
}
func getRules(w http.ResponseWriter, r *http.Request) {
rawRules := T.Instance().Rules()
rawRules := tunnel.Rules()
rules := []Rule{}
for _, rule := range rawRules {

View File

@ -57,10 +57,10 @@ func Start(addr string, secret string) {
})
r.Use(cors.Handler)
r.Get("/", hello)
r.Group(func(r chi.Router) {
r.Use(authentication)
r.Get("/", hello)
r.Get("/logs", getLogs)
r.Get("/traffic", traffic)
r.Get("/version", version)

View File

@ -16,10 +16,6 @@ import (
"github.com/Dreamacro/clash/tunnel"
)
var (
tun = tunnel.Instance()
)
type HttpListener struct {
net.Listener
address string
@ -100,9 +96,9 @@ func handleConn(conn net.Conn, cache *cache.Cache) {
if err != nil {
return
}
tun.Add(adapters.NewHTTPS(request, conn))
tunnel.Add(adapters.NewHTTPS(request, conn))
return
}
tun.Add(adapters.NewHTTP(request, conn))
tunnel.Add(adapters.NewHTTP(request, conn))
}

View File

@ -9,10 +9,6 @@ import (
"github.com/Dreamacro/clash/tunnel"
)
var (
tun = tunnel.Instance()
)
type RedirListener struct {
net.Listener
address string
@ -59,5 +55,5 @@ func handleRedir(conn net.Conn) {
return
}
conn.(*net.TCPConn).SetKeepAlive(true)
tun.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP))
tunnel.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP))
}

View File

@ -13,10 +13,6 @@ import (
"github.com/Dreamacro/clash/tunnel"
)
var (
tun = tunnel.Instance()
)
type SockListener struct {
net.Listener
address string
@ -68,5 +64,5 @@ func handleSocks(conn net.Conn) {
io.Copy(ioutil.Discard, conn)
return
}
tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP))
tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP))
}

View File

@ -7,6 +7,7 @@ import (
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel"
)
type SockUDPListener struct {
@ -58,10 +59,9 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
}
packet := &fakeConn{
PacketConn: pc,
remoteAddr: addr,
targetAddr: target,
rAddr: addr,
payload: payload,
bufRef: buf,
}
tun.AddPacket(adapters.NewPacket(target, packet, C.SOCKS, C.UDP))
tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS))
}

View File

@ -9,10 +9,9 @@ import (
type fakeConn struct {
net.PacketConn
remoteAddr net.Addr
targetAddr socks5.Addr
payload []byte
bufRef []byte
rAddr net.Addr
payload []byte
bufRef []byte
}
func (c *fakeConn) Data() []byte {
@ -21,21 +20,16 @@ func (c *fakeConn) Data() []byte {
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) {
from := c.targetAddr
if addr != nil {
// if addr is provided, use the parsed addr
from = socks5.ParseAddrToSocksAddr(addr)
}
packet, err := socks5.EncodeUDPPacket(from, b)
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil {
return
}
return c.PacketConn.WriteTo(packet, c.remoteAddr)
return c.PacketConn.WriteTo(packet, c.rAddr)
}
// LocalAddr returns the source IP/Port of UDP Packet
func (c *fakeConn) LocalAddr() net.Addr {
return c.remoteAddr
return c.rAddr
}
func (c *fakeConn) Close() error {

View File

@ -1,17 +1,8 @@
package rules
import (
"sync"
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/oschwald/geoip2-golang"
)
var (
mmdb *geoip2.Reader
once sync.Once
)
type GEOIP struct {
@ -29,7 +20,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool {
if ip == nil {
return false
}
record, _ := mmdb.Country(ip)
record, _ := mmdb.Instance().Country(ip)
return record.Country.IsoCode == g.country
}
@ -46,14 +37,6 @@ func (g *GEOIP) NoResolveIP() bool {
}
func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP {
once.Do(func() {
var err error
mmdb, err = geoip2.Open(C.Path.MMDB())
if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error())
}
})
geoip := &GEOIP{
country: country,
adapter: adapter,

View File

@ -14,7 +14,7 @@ import (
"github.com/Dreamacro/clash/common/pool"
)
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
req := request.R
host := req.Host
@ -81,28 +81,25 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
}
}
func (t *Tunnel) handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr net.Addr) {
if _, err := pc.WriteTo(packet.Data(), addr); err != nil {
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
return
}
DefaultManager.Upload() <- int64(len(packet.Data()))
}
func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, omitSrcAddr bool, timeout time.Duration) {
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) {
buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)])
defer t.natTable.Delete(key)
defer natTable.Delete(key)
defer pc.Close()
for {
pc.SetReadDeadline(time.Now().Add(timeout))
pc.SetReadDeadline(time.Now().Add(udpTimeout))
n, from, err := pc.ReadFrom(buf)
if err != nil {
return
}
if from != nil && omitSrcAddr {
from = nil
}
n, err = packet.WriteBack(buf[:n], from)
if err != nil {
@ -112,7 +109,7 @@ func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key str
}
}
func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
relay(request, outbound)
}

View File

@ -61,6 +61,15 @@ func (m *Manager) Snapshot() *Snapshot {
}
}
func (m *Manager) ResetStatistic() {
m.uploadTemp = 0
m.uploadBlip = 0
m.uploadTotal = 0
m.downloadTemp = 0
m.downloadBlip = 0
m.downloadTotal = 0
}
func (m *Manager) handle() {
go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal)
go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal)

View File

@ -5,11 +5,11 @@ import (
"errors"
)
type Mode int
type TunnelMode int
var (
// ModeMapping is a mapping for Mode enum
ModeMapping = map[string]Mode{
ModeMapping = map[string]TunnelMode{
Global.String(): Global,
Rule.String(): Rule,
Direct.String(): Direct,
@ -17,13 +17,13 @@ var (
)
const (
Global Mode = iota
Global TunnelMode = iota
Rule
Direct
)
// UnmarshalJSON unserialize Mode
func (m *Mode) UnmarshalJSON(data []byte) error {
func (m *TunnelMode) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := ModeMapping[tp]
@ -35,7 +35,7 @@ func (m *Mode) UnmarshalJSON(data []byte) error {
}
// UnmarshalYAML unserialize Mode with yaml
func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tp string
unmarshal(&tp)
mode, exist := ModeMapping[tp]
@ -47,11 +47,11 @@ func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
// MarshalJSON serialize Mode
func (m Mode) MarshalJSON() ([]byte, error) {
func (m TunnelMode) MarshalJSON() ([]byte, error) {
return json.Marshal(m.String())
}
func (m Mode) String() string {
func (m TunnelMode) String() string {
switch m {
case Global:
return "Global"

View File

@ -10,6 +10,7 @@ import (
"github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/component/nat"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
@ -18,149 +19,155 @@ import (
)
var (
tunnel *Tunnel
once sync.Once
// default timeout for UDP session
udpTimeout = 60 * time.Second
)
// Tunnel handle relay inbound proxy and outbound proxy
type Tunnel struct {
tcpQueue *channels.InfiniteChannel
udpQueue *channels.InfiniteChannel
natTable *nat.Table
rules []C.Rule
proxies map[string]C.Proxy
providers map[string]provider.ProxyProvider
configMux sync.RWMutex
tcpQueue = channels.NewInfiniteChannel()
udpQueue = channels.NewInfiniteChannel()
natTable = nat.New()
rules []C.Rule
proxies = make(map[string]C.Proxy)
providers map[string]provider.ProxyProvider
configMux sync.RWMutex
enhancedMode *dns.Resolver
// experimental features
ignoreResolveFail bool
// Outbound Rule
mode Mode
mode = Rule
// default timeout for UDP session
udpTimeout = 60 * time.Second
)
func init() {
go process()
}
// Add request to queue
func (t *Tunnel) Add(req C.ServerAdapter) {
t.tcpQueue.In() <- req
func Add(req C.ServerAdapter) {
tcpQueue.In() <- req
}
// AddPacket add udp Packet to queue
func (t *Tunnel) AddPacket(packet *inbound.PacketAdapter) {
t.udpQueue.In() <- packet
func AddPacket(packet *inbound.PacketAdapter) {
udpQueue.In() <- packet
}
// Rules return all rules
func (t *Tunnel) Rules() []C.Rule {
return t.rules
func Rules() []C.Rule {
return rules
}
// UpdateRules handle update rules
func (t *Tunnel) UpdateRules(rules []C.Rule) {
t.configMux.Lock()
t.rules = rules
t.configMux.Unlock()
func UpdateRules(newRules []C.Rule) {
configMux.Lock()
rules = newRules
configMux.Unlock()
}
// Proxies return all proxies
func (t *Tunnel) Proxies() map[string]C.Proxy {
return t.proxies
func Proxies() map[string]C.Proxy {
return proxies
}
// Providers return all compatible providers
func (t *Tunnel) Providers() map[string]provider.ProxyProvider {
return t.providers
func Providers() map[string]provider.ProxyProvider {
return providers
}
// UpdateProxies handle update proxies
func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
t.configMux.Lock()
t.proxies = proxies
t.providers = providers
t.configMux.Unlock()
func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provider.ProxyProvider) {
configMux.Lock()
proxies = newProxies
providers = newProviders
configMux.Unlock()
}
// UpdateExperimental handle update experimental config
func (t *Tunnel) UpdateExperimental(ignoreResolveFail bool) {
t.configMux.Lock()
t.ignoreResolveFail = ignoreResolveFail
t.configMux.Unlock()
func UpdateExperimental(value bool) {
configMux.Lock()
ignoreResolveFail = value
configMux.Unlock()
}
// Mode return current mode
func (t *Tunnel) Mode() Mode {
return t.mode
func Mode() TunnelMode {
return mode
}
// SetMode change the mode of tunnel
func (t *Tunnel) SetMode(mode Mode) {
t.mode = mode
func SetMode(m TunnelMode) {
mode = m
}
// SetResolver set custom dns resolver for enhanced mode
func SetResolver(r *dns.Resolver) {
enhancedMode = r
}
// processUDP starts a loop to handle udp packet
func (t *Tunnel) processUDP() {
queue := t.udpQueue.Out()
func processUDP() {
queue := udpQueue.Out()
for elm := range queue {
conn := elm.(*inbound.PacketAdapter)
t.handleUDPConn(conn)
handleUDPConn(conn)
}
}
func (t *Tunnel) process() {
func process() {
numUDPWorkers := 4
if runtime.NumCPU() > numUDPWorkers {
numUDPWorkers = runtime.NumCPU()
}
for i := 0; i < numUDPWorkers; i++ {
go t.processUDP()
go processUDP()
}
queue := t.tcpQueue.Out()
queue := tcpQueue.Out()
for elm := range queue {
conn := elm.(C.ServerAdapter)
go t.handleTCPConn(conn)
go handleTCPConn(conn)
}
}
func (t *Tunnel) resolveIP(host string) (net.IP, error) {
return dns.ResolveIP(host)
func needLookupIP(metadata *C.Metadata) bool {
return enhancedMode != nil && (enhancedMode.IsMapping() || enhancedMode.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil
}
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil
}
func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
// handle host equal IP string
func preHandleMetadata(metadata *C.Metadata) error {
// handle IP string on host
if ip := net.ParseIP(metadata.Host); ip != nil {
metadata.DstIP = ip
}
// preprocess enhanced-mode metadata
if t.needLookupIP(metadata) {
host, exist := dns.DefaultResolver.IPToHost(metadata.DstIP)
if needLookupIP(metadata) {
host, exist := enhancedMode.IPToHost(metadata.DstIP)
if exist {
metadata.Host = host
metadata.AddrType = C.AtypDomainName
if dns.DefaultResolver.FakeIPEnabled() {
if enhancedMode.FakeIPEnabled() {
metadata.DstIP = nil
}
} else if enhancedMode.IsFakeIP(metadata.DstIP) {
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
}
}
return nil
}
func resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
var proxy C.Proxy
var rule C.Rule
switch t.mode {
switch mode {
case Direct:
proxy = t.proxies["DIRECT"]
proxy = proxies["DIRECT"]
case Global:
proxy = t.proxies["GLOBAL"]
proxy = proxies["GLOBAL"]
// Rule
default:
var err error
proxy, rule, err = t.match(metadata)
proxy, rule, err = match(metadata)
if err != nil {
return nil, nil, err
}
@ -168,71 +175,74 @@ func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error)
return proxy, rule, nil
}
func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) {
func handleUDPConn(packet *inbound.PacketAdapter) {
metadata := packet.Metadata()
if !metadata.Valid() {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
src := packet.LocalAddr().String()
dst := metadata.RemoteAddress()
key := src + "-" + dst
if err := preHandleMetadata(metadata); err != nil {
log.Debugln("[Metadata PreHandle] error: %s", err)
return
}
pc, addr := t.natTable.Get(key)
key := packet.LocalAddr().String()
pc := natTable.Get(key)
if pc != nil {
t.handleUDPToRemote(packet, pc, addr)
handleUDPToRemote(packet, pc, metadata)
return
}
lockKey := key + "-lock"
wg, loaded := t.natTable.GetOrCreateLock(lockKey)
isFakeIP := dns.DefaultResolver != nil && dns.DefaultResolver.IsFakeIP(metadata.DstIP)
wg, loaded := natTable.GetOrCreateLock(lockKey)
go func() {
if !loaded {
wg.Add(1)
proxy, rule, err := t.resolveMetadata(metadata)
proxy, rule, err := resolveMetadata(metadata)
if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
t.natTable.Delete(lockKey)
natTable.Delete(lockKey)
wg.Done()
return
}
rawPc, nAddr, err := proxy.DialUDP(metadata)
rawPc, err := proxy.DialUDP(metadata)
if err != nil {
log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error())
t.natTable.Delete(lockKey)
natTable.Delete(lockKey)
wg.Done()
return
}
addr = nAddr
pc = newUDPTracker(rawPc, DefaultManager, metadata, rule)
if rule != nil {
log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String())
} else {
log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String())
switch true {
case rule != nil:
log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String())
case mode == Global:
log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String())
case mode == Direct:
log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String())
default:
log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String())
}
t.natTable.Set(key, pc, addr)
t.natTable.Delete(lockKey)
natTable.Set(key, pc)
natTable.Delete(lockKey)
wg.Done()
// in fake-ip mode, Full-Cone NAT can never achieve, fallback to omitting src Addr
go t.handleUDPToLocal(packet.UDPPacket, pc, key, isFakeIP, udpTimeout)
go handleUDPToLocal(packet.UDPPacket, pc, key)
}
wg.Wait()
pc, addr := t.natTable.Get(key)
pc := natTable.Get(key)
if pc != nil {
t.handleUDPToRemote(packet, pc, addr)
handleUDPToRemote(packet, pc, metadata)
}
}()
}
func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
func handleTCPConn(localConn C.ServerAdapter) {
defer localConn.Close()
metadata := localConn.Metadata()
@ -241,7 +251,12 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
return
}
proxy, rule, err := t.resolveMetadata(metadata)
if err := preHandleMetadata(metadata); err != nil {
log.Debugln("[Metadata PreHandle] error: %s", err)
return
}
proxy, rule, err := resolveMetadata(metadata)
if err != nil {
log.Warnln("Parse metadata failed: %v", err)
return
@ -255,41 +270,46 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule)
defer remoteConn.Close()
if rule != nil {
log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String())
} else {
log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String())
switch true {
case rule != nil:
log.Infoln("[TCP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String())
case mode == Global:
log.Infoln("[TCP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String())
case mode == Direct:
log.Infoln("[TCP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String())
default:
log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String())
}
switch adapter := localConn.(type) {
case *inbound.HTTPAdapter:
t.handleHTTP(adapter, remoteConn)
handleHTTP(adapter, remoteConn)
case *inbound.SocketAdapter:
t.handleSocket(adapter, remoteConn)
handleSocket(adapter, remoteConn)
}
}
func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
return !rule.NoResolveIP() && metadata.Host != "" && metadata.DstIP == nil
}
func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
t.configMux.RLock()
defer t.configMux.RUnlock()
func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
configMux.RLock()
defer configMux.RUnlock()
var resolved bool
if node := dns.DefaultHosts.Search(metadata.Host); node != nil {
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
ip := node.Data.(net.IP)
metadata.DstIP = ip
resolved = true
}
for _, rule := range t.rules {
if !resolved && t.shouldResolveIP(rule, metadata) {
ip, err := t.resolveIP(metadata.Host)
for _, rule := range rules {
if !resolved && shouldResolveIP(rule, metadata) {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
if !t.ignoreResolveFail {
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())
@ -301,7 +321,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
}
if rule.Match(metadata) {
adapter, ok := t.proxies[rule.Adapter()]
adapter, ok := proxies[rule.Adapter()]
if !ok {
continue
}
@ -313,24 +333,6 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
return adapter, rule, nil
}
}
return t.proxies["DIRECT"], nil, nil
}
func newTunnel() *Tunnel {
return &Tunnel{
tcpQueue: channels.NewInfiniteChannel(),
udpQueue: channels.NewInfiniteChannel(),
natTable: nat.New(),
proxies: make(map[string]C.Proxy),
mode: Rule,
}
}
// Instance return singleton instance of Tunnel
func Instance() *Tunnel {
once.Do(func() {
tunnel = newTunnel()
go tunnel.process()
})
return tunnel
return proxies["DIRECT"], nil, nil
}