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 feature
experimental: experimental:
ignore-resolve-fail: true # ignore dns resolve fail, default value is true 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 of local SOCKS5/HTTP(S) server
# authentication: # authentication:
@ -130,10 +131,13 @@ experimental:
# enable: true # set true to enable dns (default is false) # enable: true # set true to enable dns (default is false)
# ipv6: false # default is false # ipv6: false # default is false
# listen: 0.0.0.0:53 # listen: 0.0.0.0:53
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
# # - 114.114.114.114
# # - 8.8.8.8
# enhanced-mode: redir-host # or fake-ip # 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-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 # fake-ip-filter: # fake ip white domain list
# - *.lan # - '*.lan'
# - localhost.ptlogin2.qq.com # - localhost.ptlogin2.qq.com
# nameserver: # nameserver:
# - 114.114.114.114 # - 114.114.114.114

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" 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) { 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 { if err == nil && h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig) cc := tls.Client(c, h.tlsConfig)
err = cc.Handshake() 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 return newConn(&NopConn{}, r), nil
} }
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
return nil, nil, errors.New("match reject rule") return nil, errors.New("match reject rule")
} }
func NewReject() *Reject { func NewReject() *Reject {

View File

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

View File

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

View File

@ -2,7 +2,6 @@ package outbound
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net" "net"
@ -11,9 +10,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
) )
const ( const (
@ -87,92 +86,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
return bytes.Join(buf, nil) 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) { func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ip, err := dns.ResolveIP(host) ip, err := resolver.ResolveIP(host)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,11 +2,14 @@ package outbound
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/vmess" "github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant" 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) { 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 { if err != nil {
return nil, fmt.Errorf("%s connect error", v.server) 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 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) ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel() defer cancel()
c, err := dialContext(ctx, "tcp", v.server) c, err := dialer.DialContext(ctx, "tcp", v.server)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("%s connect error", v.server) return nil, fmt.Errorf("%s connect error", v.server)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata)) c, err = v.client.New(c, parseVmessAddr(metadata))
if err != nil { 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) { 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 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) return uc.Conn.Write(b)
} }
func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
n, err := uc.Conn.Read(b) return uc.Conn.Write(p)
return n, uc.RemoteAddr(), err }
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 ( import (
"context" "context"
"encoding/json" "encoding/json"
"net"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider" "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 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() proxy := f.findAliveProxy()
pc, addr, err := proxy.DialUDP(metadata) pc, err := proxy.DialUDP(metadata)
if err == nil { if err == nil {
pc.AppendToChains(f) pc.AppendToChains(f)
} }
return pc, addr, err return pc, err
} }
func (f *Fallback) SupportUDP() bool { func (f *Fallback) SupportUDP() bool {

View File

@ -74,7 +74,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
return 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() { defer func() {
if err == nil { if err == nil {
pc.AppendToChains(lb) 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 Use not empty, drop health check options
if len(groupOption.Use) != 0 { if len(groupOption.Use) != 0 {
hc := provider.NewHealthCheck(ps, "", 0) hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -66,7 +66,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
// select don't need health check // select don't need health check
if groupOption.Type == "select" { if groupOption.Type == "select" {
hc := provider.NewHealthCheck(ps, "", 0) hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, err 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)) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider" "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 return c, err
} }
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, addr, err := s.selected.DialUDP(metadata) pc, err := s.selected.DialUDP(metadata)
if err == nil { if err == nil {
pc.AppendToChains(s) pc.AppendToChains(s)
} }
return pc, addr, err return pc, err
} }
func (s *Selector) SupportUDP() bool { func (s *Selector) SupportUDP() bool {

View File

@ -3,7 +3,6 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"net"
"time" "time"
"github.com/Dreamacro/clash/adapters/outbound" "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 return c, err
} }
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, addr, err := u.fast().DialUDP(metadata) pc, err := u.fast().DialUDP(metadata)
if err == nil { if err == nil {
pc.AppendToChains(u) pc.AppendToChains(u)
} }
return pc, addr, err return pc, err
} }
func (u *URLTest) proxies() []C.Proxy { 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) hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
path := C.Path.Reslove(schema.Path) path := C.Path.Resolve(schema.Path)
var vehicle Vehicle var vehicle Vehicle
switch schema.Type { switch schema.Type {

View File

@ -128,7 +128,11 @@ func (pp *ProxySetProvider) Initial() error {
proxies, err := pp.parse(buf) proxies, err := pp.parse(buf)
if err != nil { 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 { 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 name string
healthCheck *HealthCheck healthCheck *HealthCheck
proxies []C.Proxy proxies []C.Proxy
} }
func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) { func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
"name": cp.Name(), "name": cp.Name(),
"type": cp.Type().String(), "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 return cp.name
} }
func (cp *CompatibleProvier) Reload() error { func (cp *CompatibleProvider) Reload() error {
return nil return nil
} }
func (cp *CompatibleProvier) Destroy() error { func (cp *CompatibleProvider) Destroy() error {
cp.healthCheck.close() cp.healthCheck.close()
return nil return nil
} }
func (cp *CompatibleProvier) HealthCheck() { func (cp *CompatibleProvider) HealthCheck() {
cp.healthCheck.check() cp.healthCheck.check()
} }
func (cp *CompatibleProvier) Update() error { func (cp *CompatibleProvider) Update() error {
return nil return nil
} }
func (cp *CompatibleProvier) Initial() error { func (cp *CompatibleProvider) Initial() error {
return nil return nil
} }
func (cp *CompatibleProvier) VehicleType() VehicleType { func (cp *CompatibleProvider) VehicleType() VehicleType {
return Compatible return Compatible
} }
func (cp *CompatibleProvier) Type() ProviderType { func (cp *CompatibleProvider) Type() ProviderType {
return Proxy return Proxy
} }
func (cp *CompatibleProvier) Proxies() []C.Proxy { func (cp *CompatibleProvider) Proxies() []C.Proxy {
return cp.proxies 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 { if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least") 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() go hc.process()
} }
return &CompatibleProvier{ return &CompatibleProvider{
name: name, name: name,
proxies: proxies, proxies: proxies,
healthCheck: hc, healthCheck: hc,

View File

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

View File

@ -12,7 +12,7 @@ import (
type Option func(*LruCache) type Option func(*LruCache)
// EvictCallback is used to get a callback when a cache entry is evicted // 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 // WithEvict set the evict callback
func WithEvict(cb EvictCallback) Option { func WithEvict(cb EvictCallback) Option {

View File

@ -17,15 +17,12 @@ type Picker struct {
once sync.Once once sync.Once
result interface{} result interface{}
firstDone chan struct{}
} }
func newPicker(ctx context.Context, cancel func()) *Picker { func newPicker(ctx context.Context, cancel func()) *Picker {
return &Picker{ return &Picker{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
firstDone: make(chan struct{}, 1),
} }
} }
@ -42,12 +39,6 @@ func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.C
return newPicker(ctx, cancel), ctx 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, // Wait blocks until all function calls from the Go method have returned,
// then returns the first nil error result (if any) from them. // then returns the first nil error result (if any) from them.
func (p *Picker) Wait() interface{} { func (p *Picker) Wait() interface{} {
@ -58,16 +49,6 @@ func (p *Picker) Wait() interface{} {
return p.result 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. // 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. // 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)) { 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 { if ret, err := f(); err == nil {
p.once.Do(func() { p.once.Do(func() {
p.result = ret p.result = ret
p.firstDone <- struct{}{}
if p.cancel != nil { if p.cancel != nil {
p.cancel() p.cancel()
} }

View File

@ -37,30 +37,3 @@ func TestPicker_Timeout(t *testing.T) {
number := picker.Wait() number := picker.Wait()
assert.Nil(t, number) 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 package nat
import ( import (
"net"
"sync" "sync"
C "github.com/Dreamacro/clash/constant"
) )
type Table struct { type Table struct {
mapping sync.Map mapping sync.Map
} }
type element struct { func (t *Table) Set(key string, pc C.PacketConn) {
RemoteAddr net.Addr t.mapping.Store(key, pc)
RemoteConn net.PacketConn
} }
func (t *Table) Set(key string, pc net.PacketConn, addr net.Addr) { func (t *Table) Get(key string) C.PacketConn {
// set conn read timeout
t.mapping.Store(key, &element{
RemoteConn: pc,
RemoteAddr: addr,
})
}
func (t *Table) Get(key string) (net.PacketConn, net.Addr) {
item, exist := t.mapping.Load(key) item, exist := t.mapping.Load(key)
if !exist { if !exist {
return nil, nil return nil
} }
elm := item.(*element) return item.(C.PacketConn)
return elm.RemoteConn, elm.RemoteAddr
} }
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) { func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) {

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package constant
import ( import (
"encoding/json" "encoding/json"
"net" "net"
"strconv"
) )
// Socks addr type // Socks addr type
@ -70,6 +71,25 @@ func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), m.DstPort) 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 { func (m *Metadata) String() string {
if m.Host != "" { if m.Host != "" {
return m.Host return m.Host

View File

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

View File

@ -2,13 +2,20 @@ package dns
import ( import (
"context" "context"
"fmt"
"net"
"strings"
"github.com/Dreamacro/clash/component/dialer"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
type client struct { type client struct {
*D.Client *D.Client
Address string r *Resolver
port string
host string
} }
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { 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) { func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address) var ip net.IP
return 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" "context"
"crypto/tls" "crypto/tls"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"github.com/Dreamacro/clash/component/dialer"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -15,12 +18,9 @@ const (
dotMimeType = "application/dns-message" dotMimeType = "application/dns-message"
) )
var dohTransport = &http.Transport{
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
}
type dohClient struct { type dohClient struct {
url string url string
transport *http.Transport
} }
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { 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) { 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) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -70,3 +70,25 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
err = msg.Unpack(buf) err = msg.Unpack(buf)
return msg, err 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 package dns
import "net" import (
"net"
"github.com/Dreamacro/clash/component/mmdb"
)
type fallbackFilter interface { type fallbackFilter interface {
Match(net.IP) bool Match(net.IP) bool
@ -9,11 +13,7 @@ type fallbackFilter interface {
type geoipFilter struct{} type geoipFilter struct{}
func (gf *geoipFilter) Match(ip net.IP) bool { func (gf *geoipFilter) Match(ip net.IP) bool {
if mmdb == nil { record, _ := mmdb.Instance().Country(ip)
return false
}
record, _ := mmdb.Country(ip)
return record.Country.IsoCode == "CN" || record.Country.IsoCode == "" 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} msg.Answer = []D.RR{rr}
setMsgTTL(msg, 1) setMsgTTL(msg, 1)
msg.SetReply(r) msg.SetRcode(r, msg.Rcode)
msg.Authoritative = true
w.WriteMsg(msg) w.WriteMsg(msg)
return return
} }
@ -55,7 +56,8 @@ func withResolver(resolver *Resolver) handler {
D.HandleFailed(w, r) D.HandleFailed(w, r)
return return
} }
msg.SetReply(r) msg.SetRcode(r, msg.Rcode)
msg.Authoritative = true
w.WriteMsg(msg) w.WriteMsg(msg)
return return
} }

View File

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

View File

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

10
go.mod
View File

@ -5,18 +5,18 @@ go 1.13
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.5 github.com/Dreamacro/go-shadowsocks2 v0.1.5
github.com/eapache/queue v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/chi v4.0.3+incompatible
github.com/go-chi/cors v1.0.0 github.com/go-chi/cors v1.0.0
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.2.0+incompatible github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.1 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/oschwald/geoip2-golang v1.4.0
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
gopkg.in/eapache/channels.v1 v1.1.0 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 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 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= 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= 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/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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 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 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= 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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= 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-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 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 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-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-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
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/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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 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-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 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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 h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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/adapters/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
trie "github.com/Dreamacro/clash/component/domain-trie" trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy" P "github.com/Dreamacro/clash/proxy"
authStore "github.com/Dreamacro/clash/proxy/auth" authStore "github.com/Dreamacro/clash/proxy/auth"
T "github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
) )
// forward compatibility before 1.0 // forward compatibility before 1.0
@ -83,7 +85,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateRules(cfg.Rules) updateRules(cfg.Rules)
updateDNS(cfg.DNS) updateDNS(cfg.DNS)
updateHosts(cfg.Hosts) updateHosts(cfg.Hosts)
updateExperimental(cfg.Experimental) updateExperimental(cfg)
} }
func GetGeneral() *config.General { func GetGeneral() *config.General {
@ -100,20 +102,30 @@ func GetGeneral() *config.General {
Authentication: authenticator, Authentication: authenticator,
AllowLan: P.AllowLan(), AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(), BindAddress: P.BindAddress(),
Mode: T.Instance().Mode(), Mode: tunnel.Mode(),
LogLevel: log.Level(), LogLevel: log.Level(),
} }
return general return general
} }
func updateExperimental(c *config.Experimental) { func updateExperimental(c *config.Config) {
T.Instance().UpdateExperimental(c.IgnoreResolveFail) cfg := c.Experimental
tunnel.UpdateExperimental(cfg.IgnoreResolveFail)
if cfg.Interface != "" && c.DNS.Enable {
dialer.DialHook = dialer.DialerWithInterface(cfg.Interface)
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface)
} else {
dialer.DialHook = nil
dialer.ListenPacketHook = nil
}
} }
func updateDNS(c *config.DNS) { func updateDNS(c *config.DNS) {
if c.Enable == false { if c.Enable == false {
dns.DefaultResolver = nil resolver.DefaultResolver = nil
tunnel.SetResolver(nil)
dns.ReCreateServer("", nil) dns.ReCreateServer("", nil)
return return
} }
@ -127,8 +139,10 @@ func updateDNS(c *config.DNS) {
GeoIP: c.FallbackFilter.GeoIP, GeoIP: c.FallbackFilter.GeoIP,
IPCIDR: c.FallbackFilter.IPCIDR, 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 { if err := dns.ReCreateServer(c.Listen, r); err != nil {
log.Errorln("Start DNS server error: %s", err.Error()) log.Errorln("Start DNS server error: %s", err.Error())
return return
@ -140,11 +154,10 @@ func updateDNS(c *config.DNS) {
} }
func updateHosts(tree *trie.Trie) { func updateHosts(tree *trie.Trie) {
dns.DefaultHosts = tree resolver.DefaultHosts = tree
} }
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
tunnel := T.Instance()
oldProviders := tunnel.Providers() oldProviders := tunnel.Providers()
// close providers goroutine // close providers goroutine
@ -156,12 +169,12 @@ func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.Pro
} }
func updateRules(rules []C.Rule) { func updateRules(rules []C.Rule) {
T.Instance().UpdateRules(rules) tunnel.UpdateRules(rules)
} }
func updateGeneral(general *config.General) { func updateGeneral(general *config.General) {
log.SetLevel(general.LogLevel) log.SetLevel(general.LogLevel)
T.Instance().SetMode(general.Mode) tunnel.SetMode(general.Mode)
allowLan := general.AllowLan allowLan := general.AllowLan
P.SetAllowLan(allowLan) P.SetAllowLan(allowLan)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,10 +16,6 @@ import (
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
) )
var (
tun = tunnel.Instance()
)
type HttpListener struct { type HttpListener struct {
net.Listener net.Listener
address string address string
@ -100,9 +96,9 @@ func handleConn(conn net.Conn, cache *cache.Cache) {
if err != nil { if err != nil {
return return
} }
tun.Add(adapters.NewHTTPS(request, conn)) tunnel.Add(adapters.NewHTTPS(request, conn))
return 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" "github.com/Dreamacro/clash/tunnel"
) )
var (
tun = tunnel.Instance()
)
type RedirListener struct { type RedirListener struct {
net.Listener net.Listener
address string address string
@ -59,5 +55,5 @@ func handleRedir(conn net.Conn) {
return return
} }
conn.(*net.TCPConn).SetKeepAlive(true) 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" "github.com/Dreamacro/clash/tunnel"
) )
var (
tun = tunnel.Instance()
)
type SockListener struct { type SockListener struct {
net.Listener net.Listener
address string address string
@ -68,5 +64,5 @@ func handleSocks(conn net.Conn) {
io.Copy(ioutil.Discard, conn) io.Copy(ioutil.Discard, conn)
return 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/common/pool"
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel"
) )
type SockUDPListener struct { type SockUDPListener struct {
@ -58,10 +59,9 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
} }
packet := &fakeConn{ packet := &fakeConn{
PacketConn: pc, PacketConn: pc,
remoteAddr: addr, rAddr: addr,
targetAddr: target,
payload: payload, payload: payload,
bufRef: buf, 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 { type fakeConn struct {
net.PacketConn net.PacketConn
remoteAddr net.Addr rAddr net.Addr
targetAddr socks5.Addr payload []byte
payload []byte bufRef []byte
bufRef []byte
} }
func (c *fakeConn) Data() []byte { func (c *fakeConn) Data() []byte {
@ -21,21 +20,16 @@ func (c *fakeConn) Data() []byte {
// WriteBack wirtes UDP packet with source(ip, port) = `addr` // WriteBack wirtes UDP packet with source(ip, port) = `addr`
func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) {
from := c.targetAddr packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if addr != nil {
// if addr is provided, use the parsed addr
from = socks5.ParseAddrToSocksAddr(addr)
}
packet, err := socks5.EncodeUDPPacket(from, b)
if err != nil { if err != nil {
return return
} }
return c.PacketConn.WriteTo(packet, c.remoteAddr) return c.PacketConn.WriteTo(packet, c.rAddr)
} }
// LocalAddr returns the source IP/Port of UDP Packet // LocalAddr returns the source IP/Port of UDP Packet
func (c *fakeConn) LocalAddr() net.Addr { func (c *fakeConn) LocalAddr() net.Addr {
return c.remoteAddr return c.rAddr
} }
func (c *fakeConn) Close() error { func (c *fakeConn) Close() error {

View File

@ -1,17 +1,8 @@
package rules package rules
import ( import (
"sync" "github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant" 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 { type GEOIP struct {
@ -29,7 +20,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool {
if ip == nil { if ip == nil {
return false return false
} }
record, _ := mmdb.Country(ip) record, _ := mmdb.Instance().Country(ip)
return record.Country.IsoCode == g.country return record.Country.IsoCode == g.country
} }
@ -46,14 +37,6 @@ func (g *GEOIP) NoResolveIP() bool {
} }
func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { 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{ geoip := &GEOIP{
country: country, country: country,
adapter: adapter, adapter: adapter,

View File

@ -14,7 +14,7 @@ import (
"github.com/Dreamacro/clash/common/pool" "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 req := request.R
host := req.Host 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) { func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
if _, err := pc.WriteTo(packet.Data(), addr); err != nil { if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
return return
} }
DefaultManager.Upload() <- int64(len(packet.Data())) 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) buf := pool.BufPool.Get().([]byte)
defer pool.BufPool.Put(buf[:cap(buf)]) defer pool.BufPool.Put(buf[:cap(buf)])
defer t.natTable.Delete(key) defer natTable.Delete(key)
defer pc.Close() defer pc.Close()
for { for {
pc.SetReadDeadline(time.Now().Add(timeout)) pc.SetReadDeadline(time.Now().Add(udpTimeout))
n, from, err := pc.ReadFrom(buf) n, from, err := pc.ReadFrom(buf)
if err != nil { if err != nil {
return return
} }
if from != nil && omitSrcAddr {
from = nil
}
n, err = packet.WriteBack(buf[:n], from) n, err = packet.WriteBack(buf[:n], from)
if err != nil { 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) 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() { func (m *Manager) handle() {
go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal) go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal)
go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal) go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal)

View File

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

View File

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