Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
609869bf5a | |||
d68339cea7 | |||
0f4cdbf187 | |||
f3f8e7e52f | |||
8d07c1eb3e | |||
46edae9896 | |||
df0ab6aa8e | |||
7b48138ad0 | |||
e9032c55fa | |||
d75cb069d9 | |||
f69f635e0b | |||
8b5e511426 | |||
6641bf7c07 | |||
afc9f3f59a | |||
a55be58c01 | |||
dcf97ff5b4 | |||
72c0af9739 | |||
b0f9c6afa8 | |||
19bb0b655c | |||
26ce3e8814 | |||
aa207ec664 | |||
c626b988a6 | |||
82c387e92b | |||
14fb789002 | |||
9071351022 | |||
f688eda2c2 | |||
2810533df4 | |||
6b7144acce | |||
e68c0d088b | |||
2c0cc374d3 | |||
86d3d77a7f |
96
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
96
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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
|
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
78
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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
20
Dockerfile.arm32v7
Normal 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
20
Dockerfile.arm64v8
Normal 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"]
|
@ -114,6 +114,7 @@ external-controller: 127.0.0.1:9090
|
||||
# experimental feature
|
||||
experimental:
|
||||
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
|
||||
# interface-name: en0 # outbound interface name
|
||||
|
||||
# authentication of local SOCKS5/HTTP(S) server
|
||||
# authentication:
|
||||
@ -130,10 +131,13 @@ experimental:
|
||||
# enable: true # set true to enable dns (default is false)
|
||||
# ipv6: false # default is false
|
||||
# listen: 0.0.0.0:53
|
||||
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
|
||||
# # - 114.114.114.114
|
||||
# # - 8.8.8.8
|
||||
# enhanced-mode: redir-host # or fake-ip
|
||||
# # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it
|
||||
# fake-ip-filter: # fake ip white domain list
|
||||
# - *.lan
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
# nameserver:
|
||||
# - 114.114.114.114
|
||||
|
@ -17,9 +17,9 @@ func (s *PacketAdapter) Metadata() *C.Metadata {
|
||||
}
|
||||
|
||||
// NewPacket is PacketAdapter generator
|
||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, netType C.NetWork) *PacketAdapter {
|
||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = netType
|
||||
metadata.NetWork = C.UDP
|
||||
metadata.Type = source
|
||||
if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
|
@ -30,8 +30,8 @@ func (b *Base) Type() C.AdapterType {
|
||||
return b.tp
|
||||
}
|
||||
|
||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("no support")
|
||||
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
func (b *Base) SupportUDP() bool {
|
||||
@ -65,8 +65,13 @@ func newConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
return &conn{c, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
type PacketConn interface {
|
||||
net.PacketConn
|
||||
WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error)
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
PacketConn
|
||||
chain C.Chain
|
||||
}
|
||||
|
||||
@ -78,8 +83,8 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
c.chain = append(c.chain, a.Name())
|
||||
}
|
||||
|
||||
func newPacketConn(c net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{c, []string{a.Name()}}
|
||||
func newPacketConn(pc PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -17,7 +19,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
|
||||
}
|
||||
|
||||
c, err := dialContext(ctx, "tcp", address)
|
||||
c, err := dialer.DialContext(ctx, "tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -25,17 +27,27 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return newConn(c, d), nil
|
||||
}
|
||||
|
||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
pc, err := net.ListenPacket("udp", "")
|
||||
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := dialer.ListenPacket("udp", "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
return newPacketConn(&directPacketConn{pc}, d), nil
|
||||
}
|
||||
|
||||
addr, err := resolveUDPAddr("udp", metadata.RemoteAddress())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
type directPacketConn struct {
|
||||
net.PacketConn
|
||||
}
|
||||
|
||||
func (dp *directPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
return newPacketConn(pc, d), addr, nil
|
||||
return dp.WriteTo(p, metadata.UDPAddr())
|
||||
}
|
||||
|
||||
func NewDirect() *Direct {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -35,7 +36,7 @@ type HttpOption struct {
|
||||
}
|
||||
|
||||
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialContext(ctx, "tcp", h.addr)
|
||||
c, err := dialer.DialContext(ctx, "tcp", h.addr)
|
||||
if err == nil && h.tlsConfig != nil {
|
||||
cc := tls.Client(c, h.tlsConfig)
|
||||
err = cc.Handshake()
|
||||
|
@ -18,8 +18,8 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return newConn(&NopConn{}, r), nil
|
||||
}
|
||||
|
||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("match reject rule")
|
||||
func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
return nil, errors.New("match reject rule")
|
||||
}
|
||||
|
||||
func NewReject() *Reject {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
|
||||
@ -59,7 +60,7 @@ type v2rayObfsOption struct {
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialContext(ctx, "tcp", ss.server)
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.server)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
|
||||
}
|
||||
@ -82,24 +83,19 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C
|
||||
return newConn(c, ss), err
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
pc, err := net.ListenPacket("udp", "")
|
||||
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := dialer.ListenPacket("udp", "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := resolveUDPAddr("udp", ss.server)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
|
||||
if targetAddr == nil {
|
||||
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc = ss.cipher.PacketConn(pc)
|
||||
return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil
|
||||
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
|
||||
}
|
||||
|
||||
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
|
||||
@ -187,27 +183,33 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ssUDPConn struct {
|
||||
type ssPacketConn struct {
|
||||
net.PacketConn
|
||||
rAddr socks5.Addr
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
|
||||
func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return uc.PacketConn.WriteTo(packet[3:], addr)
|
||||
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
|
||||
}
|
||||
|
||||
func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, _, e := uc.PacketConn.ReadFrom(b)
|
||||
addr := socks5.SplitAddr(b[:n])
|
||||
var from net.Addr
|
||||
if e == nil {
|
||||
// Get the source IP/Port of packet.
|
||||
from = addr.UDPAddr()
|
||||
func (spc *ssPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
copy(b, b[len(addr):])
|
||||
return n - len(addr), from, e
|
||||
return spc.PacketConn.WriteTo(packet[3:], spc.rAddr)
|
||||
}
|
||||
|
||||
func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, _, e := spc.PacketConn.ReadFrom(b)
|
||||
if e != nil {
|
||||
return 0, nil, e
|
||||
}
|
||||
addr := socks5.SplitAddr(b[:n])
|
||||
copy(b, b[len(addr):])
|
||||
return n - len(addr), addr.UDPAddr(), e
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
obfs "github.com/Dreamacro/clash/component/simple-obfs"
|
||||
"github.com/Dreamacro/clash/component/snell"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -28,7 +29,7 @@ type SnellOption struct {
|
||||
}
|
||||
|
||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialContext(ctx, "tcp", s.server)
|
||||
c, err := dialer.DialContext(ctx, "tcp", s.server)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -35,7 +36,7 @@ type Socks5Option struct {
|
||||
}
|
||||
|
||||
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialContext(ctx, "tcp", ss.addr)
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
|
||||
if err == nil && ss.tls {
|
||||
cc := tls.Client(c, ss.tlsConfig)
|
||||
@ -60,10 +61,10 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
|
||||
return newConn(c, ss), nil
|
||||
}
|
||||
|
||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) {
|
||||
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
defer cancel()
|
||||
c, err := dialContext(ctx, "tcp", ss.addr)
|
||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
return
|
||||
@ -96,17 +97,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
|
||||
return
|
||||
}
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", bindAddr.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
|
||||
if targetAddr == nil {
|
||||
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
|
||||
}
|
||||
|
||||
pc, err := net.ListenPacket("udp", "")
|
||||
pc, err := dialer.ListenPacket("udp", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -119,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err
|
||||
pc.Close()
|
||||
}()
|
||||
|
||||
return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr, tcpConn: c}, ss), addr, nil
|
||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil
|
||||
}
|
||||
|
||||
func NewSocks5(option Socks5Option) *Socks5 {
|
||||
@ -147,21 +138,29 @@ func NewSocks5(option Socks5Option) *Socks5 {
|
||||
}
|
||||
}
|
||||
|
||||
type socksUDPConn struct {
|
||||
type socksPacketConn struct {
|
||||
net.PacketConn
|
||||
rAddr socks5.Addr
|
||||
rAddr net.Addr
|
||||
tcpConn net.Conn
|
||||
}
|
||||
|
||||
func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
|
||||
func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return uc.PacketConn.WriteTo(packet, addr)
|
||||
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
||||
}
|
||||
|
||||
func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return uc.PacketConn.WriteTo(packet, uc.rAddr)
|
||||
}
|
||||
|
||||
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, a, e := uc.PacketConn.ReadFrom(b)
|
||||
if e != nil {
|
||||
return 0, nil, e
|
||||
@ -176,7 +175,7 @@ func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
return n - addrLength - 3, a, nil
|
||||
}
|
||||
|
||||
func (uc *socksUDPConn) Close() error {
|
||||
func (uc *socksPacketConn) Close() error {
|
||||
uc.tcpConn.Close()
|
||||
return uc.PacketConn.Close()
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -11,9 +10,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -87,92 +86,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||
return bytes.Join(buf, nil)
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
resolved bool
|
||||
ipv6 bool
|
||||
done bool
|
||||
}
|
||||
results := make(chan dialResult)
|
||||
var primary, fallback dialResult
|
||||
|
||||
startRacer := func(ctx context.Context, host string, ipv6 bool) {
|
||||
dialer := net.Dialer{}
|
||||
result := dialResult{ipv6: ipv6, done: true}
|
||||
defer func() {
|
||||
select {
|
||||
case results <- result:
|
||||
case <-returned:
|
||||
if result.Conn != nil {
|
||||
result.Conn.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var ip net.IP
|
||||
if ipv6 {
|
||||
ip, result.error = dns.ResolveIPv6(host)
|
||||
} else {
|
||||
ip, result.error = dns.ResolveIPv4(host)
|
||||
}
|
||||
if result.error != nil {
|
||||
return
|
||||
}
|
||||
result.resolved = true
|
||||
|
||||
if ipv6 {
|
||||
result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port))
|
||||
} else {
|
||||
result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
||||
}
|
||||
}
|
||||
|
||||
go startRacer(ctx, host, false)
|
||||
go startRacer(ctx, host, true)
|
||||
|
||||
for {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
|
||||
if !res.ipv6 {
|
||||
primary = res
|
||||
} else {
|
||||
fallback = res
|
||||
}
|
||||
|
||||
if primary.done && fallback.done {
|
||||
if primary.resolved {
|
||||
return nil, primary.error
|
||||
} else if fallback.resolved {
|
||||
return nil, fallback.error
|
||||
} else {
|
||||
return nil, primary.error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := dns.ResolveIP(host)
|
||||
ip, err := resolver.ResolveIP(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,11 +2,14 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/vmess"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
@ -33,7 +36,7 @@ type VmessOption struct {
|
||||
}
|
||||
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := dialContext(ctx, "tcp", v.server)
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.server)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error", v.server)
|
||||
}
|
||||
@ -42,19 +45,28 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
||||
return newConn(c, v), err
|
||||
}
|
||||
|
||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
// vmess use stream-oriented udp, so clash needs a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
|
||||
defer cancel()
|
||||
c, err := dialContext(ctx, "tcp", v.server)
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.server)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%s connect error", v.server)
|
||||
return nil, fmt.Errorf("%s connect error", v.server)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
c, err = v.client.New(c, parseVmessAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
return newPacketConn(&vmessUDPConn{Conn: c}, v), c.RemoteAddr(), nil
|
||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
}
|
||||
|
||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
@ -115,15 +127,20 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
||||
}
|
||||
}
|
||||
|
||||
type vmessUDPConn struct {
|
||||
type vmessPacketConn struct {
|
||||
net.Conn
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
return uc.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, err := uc.Conn.Read(b)
|
||||
return n, uc.RemoteAddr(), err
|
||||
func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) {
|
||||
return uc.Conn.Write(p)
|
||||
}
|
||||
|
||||
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, err := uc.Conn.Read(b)
|
||||
return n, uc.rAddr, err
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package outboundgroup
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
@ -31,13 +30,13 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
proxy := f.findAliveProxy()
|
||||
pc, addr, err := proxy.DialUDP(metadata)
|
||||
pc, err := proxy.DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(f)
|
||||
}
|
||||
return pc, addr, err
|
||||
return pc, err
|
||||
}
|
||||
|
||||
func (f *Fallback) SupportUDP() bool {
|
||||
|
@ -74,7 +74,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c
|
||||
return
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) {
|
||||
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
pc.AppendToChains(lb)
|
||||
|
@ -56,7 +56,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
// if Use not empty, drop health check options
|
||||
if len(groupOption.Use) != 0 {
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -66,7 +66,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
// select don't need health check
|
||||
if groupOption.Type == "select" {
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -79,7 +79,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
|
||||
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
@ -27,12 +26,12 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
pc, addr, err := s.selected.DialUDP(metadata)
|
||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := s.selected.DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(s)
|
||||
}
|
||||
return pc, addr, err
|
||||
return pc, err
|
||||
}
|
||||
|
||||
func (s *Selector) SupportUDP() bool {
|
||||
|
@ -3,7 +3,6 @@ package outboundgroup
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
@ -31,12 +30,12 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
|
||||
pc, addr, err := u.fast().DialUDP(metadata)
|
||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := u.fast().DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(u)
|
||||
}
|
||||
return pc, addr, err
|
||||
return pc, err
|
||||
}
|
||||
|
||||
func (u *URLTest) proxies() []C.Proxy {
|
||||
|
@ -41,7 +41,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
|
||||
|
||||
path := C.Path.Reslove(schema.Path)
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
|
||||
var vehicle Vehicle
|
||||
switch schema.Type {
|
||||
|
@ -128,7 +128,11 @@ func (pp *ProxySetProvider) Initial() error {
|
||||
|
||||
proxies, err := pp.parse(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
// parse local file error, fallback to remote
|
||||
buf, err = pp.vehicle.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
|
||||
@ -249,13 +253,13 @@ func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, h
|
||||
}
|
||||
}
|
||||
|
||||
type CompatibleProvier struct {
|
||||
type CompatibleProvider struct {
|
||||
name string
|
||||
healthCheck *HealthCheck
|
||||
proxies []C.Proxy
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
|
||||
func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"name": cp.Name(),
|
||||
"type": cp.Type().String(),
|
||||
@ -264,44 +268,44 @@ func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) Name() string {
|
||||
func (cp *CompatibleProvider) Name() string {
|
||||
return cp.name
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) Reload() error {
|
||||
func (cp *CompatibleProvider) Reload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) Destroy() error {
|
||||
func (cp *CompatibleProvider) Destroy() error {
|
||||
cp.healthCheck.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) HealthCheck() {
|
||||
func (cp *CompatibleProvider) HealthCheck() {
|
||||
cp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) Update() error {
|
||||
func (cp *CompatibleProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) Initial() error {
|
||||
func (cp *CompatibleProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) VehicleType() VehicleType {
|
||||
func (cp *CompatibleProvider) VehicleType() VehicleType {
|
||||
return Compatible
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) Type() ProviderType {
|
||||
func (cp *CompatibleProvider) Type() ProviderType {
|
||||
return Proxy
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvier) Proxies() []C.Proxy {
|
||||
func (cp *CompatibleProvider) Proxies() []C.Proxy {
|
||||
return cp.proxies
|
||||
}
|
||||
|
||||
func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) {
|
||||
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("Provider need one proxy at least")
|
||||
}
|
||||
@ -310,7 +314,7 @@ func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*Com
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
return &CompatibleProvier{
|
||||
return &CompatibleProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
)
|
||||
|
||||
// Vehicle Type
|
||||
@ -85,6 +87,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: dialer.DialContext,
|
||||
}
|
||||
|
||||
client := http.Client{Transport: transport}
|
||||
|
2
common/cache/lrucache.go
vendored
2
common/cache/lrucache.go
vendored
@ -12,7 +12,7 @@ import (
|
||||
type Option func(*LruCache)
|
||||
|
||||
// EvictCallback is used to get a callback when a cache entry is evicted
|
||||
type EvictCallback func(key interface{}, value interface{})
|
||||
type EvictCallback = func(key interface{}, value interface{})
|
||||
|
||||
// WithEvict set the evict callback
|
||||
func WithEvict(cb EvictCallback) Option {
|
||||
|
@ -17,15 +17,12 @@ type Picker struct {
|
||||
|
||||
once sync.Once
|
||||
result interface{}
|
||||
|
||||
firstDone chan struct{}
|
||||
}
|
||||
|
||||
func newPicker(ctx context.Context, cancel func()) *Picker {
|
||||
return &Picker{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
firstDone: make(chan struct{}, 1),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,12 +39,6 @@ func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.C
|
||||
return newPicker(ctx, cancel), ctx
|
||||
}
|
||||
|
||||
// WithoutAutoCancel returns a new Picker and an associated Context derived from ctx,
|
||||
// but it wouldn't cancel context when the first element return.
|
||||
func WithoutAutoCancel(ctx context.Context) *Picker {
|
||||
return newPicker(ctx, nil)
|
||||
}
|
||||
|
||||
// Wait blocks until all function calls from the Go method have returned,
|
||||
// then returns the first nil error result (if any) from them.
|
||||
func (p *Picker) Wait() interface{} {
|
||||
@ -58,16 +49,6 @@ func (p *Picker) Wait() interface{} {
|
||||
return p.result
|
||||
}
|
||||
|
||||
// WaitWithoutCancel blocks until the first result return, if timeout will return nil.
|
||||
func (p *Picker) WaitWithoutCancel() interface{} {
|
||||
select {
|
||||
case <-p.firstDone:
|
||||
return p.result
|
||||
case <-p.ctx.Done():
|
||||
return p.result
|
||||
}
|
||||
}
|
||||
|
||||
// Go calls the given function in a new goroutine.
|
||||
// The first call to return a nil error cancels the group; its result will be returned by Wait.
|
||||
func (p *Picker) Go(f func() (interface{}, error)) {
|
||||
@ -79,7 +60,6 @@ func (p *Picker) Go(f func() (interface{}, error)) {
|
||||
if ret, err := f(); err == nil {
|
||||
p.once.Do(func() {
|
||||
p.result = ret
|
||||
p.firstDone <- struct{}{}
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
}
|
||||
|
@ -37,30 +37,3 @@ func TestPicker_Timeout(t *testing.T) {
|
||||
number := picker.Wait()
|
||||
assert.Nil(t, number)
|
||||
}
|
||||
|
||||
func TestPicker_WaitWithoutAutoCancel(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*60)
|
||||
defer cancel()
|
||||
picker := WithoutAutoCancel(ctx)
|
||||
|
||||
trigger := false
|
||||
picker.Go(sleepAndSend(ctx, 10, 1))
|
||||
picker.Go(func() (interface{}, error) {
|
||||
timer := time.NewTimer(time.Millisecond * time.Duration(30))
|
||||
select {
|
||||
case <-timer.C:
|
||||
trigger = true
|
||||
return 2, nil
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
})
|
||||
elm := picker.WaitWithoutCancel()
|
||||
|
||||
assert.NotNil(t, elm)
|
||||
assert.Equal(t, elm.(int), 1)
|
||||
|
||||
elm = picker.Wait()
|
||||
assert.True(t, trigger)
|
||||
assert.Equal(t, elm.(int), 1)
|
||||
}
|
||||
|
154
component/dialer/dialer.go
Normal file
154
component/dialer/dialer.go
Normal 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
142
component/dialer/hook.go
Normal 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
35
component/mmdb/mmdb.go
Normal 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
|
||||
}
|
@ -1,34 +1,25 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
mapping sync.Map
|
||||
}
|
||||
|
||||
type element struct {
|
||||
RemoteAddr net.Addr
|
||||
RemoteConn net.PacketConn
|
||||
func (t *Table) Set(key string, pc C.PacketConn) {
|
||||
t.mapping.Store(key, pc)
|
||||
}
|
||||
|
||||
func (t *Table) Set(key string, pc net.PacketConn, addr net.Addr) {
|
||||
// set conn read timeout
|
||||
t.mapping.Store(key, &element{
|
||||
RemoteConn: pc,
|
||||
RemoteAddr: addr,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Table) Get(key string) (net.PacketConn, net.Addr) {
|
||||
func (t *Table) Get(key string) C.PacketConn {
|
||||
item, exist := t.mapping.Load(key)
|
||||
if !exist {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
elm := item.(*element)
|
||||
return elm.RemoteConn, elm.RemoteAddr
|
||||
return item.(C.PacketConn)
|
||||
}
|
||||
|
||||
func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) {
|
||||
|
@ -1,16 +1,32 @@
|
||||
package dns
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
)
|
||||
|
||||
var (
|
||||
errIPNotFound = errors.New("couldn't find ip")
|
||||
errIPVersion = errors.New("ip version error")
|
||||
// DefaultResolver aim to resolve ip
|
||||
DefaultResolver Resolver
|
||||
|
||||
// DefaultHosts aim to resolve hosts
|
||||
DefaultHosts = trie.New()
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIPNotFound = errors.New("couldn't find ip")
|
||||
ErrIPVersion = errors.New("ip version error")
|
||||
)
|
||||
|
||||
type Resolver interface {
|
||||
ResolveIP(host string) (ip net.IP, err error)
|
||||
ResolveIPv4(host string) (ip net.IP, err error)
|
||||
ResolveIPv6(host string) (ip net.IP, err error)
|
||||
}
|
||||
|
||||
// ResolveIPv4 with a host, return ipv4
|
||||
func ResolveIPv4(host string) (net.IP, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
@ -24,7 +40,7 @@ func ResolveIPv4(host string) (net.IP, error) {
|
||||
if !strings.Contains(host, ":") {
|
||||
return ip, nil
|
||||
}
|
||||
return nil, errIPVersion
|
||||
return nil, ErrIPVersion
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
@ -42,7 +58,7 @@ func ResolveIPv4(host string) (net.IP, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errIPNotFound
|
||||
return nil, ErrIPNotFound
|
||||
}
|
||||
|
||||
// ResolveIPv6 with a host, return ipv6
|
||||
@ -58,7 +74,7 @@ func ResolveIPv6(host string) (net.IP, error) {
|
||||
if strings.Contains(host, ":") {
|
||||
return ip, nil
|
||||
}
|
||||
return nil, errIPVersion
|
||||
return nil, ErrIPVersion
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
@ -76,7 +92,7 @@ func ResolveIPv6(host string) (net.IP, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errIPNotFound
|
||||
return nil, ErrIPNotFound
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
@ -86,10 +102,7 @@ func ResolveIP(host string) (net.IP, error) {
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
if DefaultResolver.ipv6 {
|
||||
return DefaultResolver.ResolveIP(host)
|
||||
}
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
return DefaultResolver.ResolveIP(host)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
121
config/config.go
121
config/config.go
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@ -30,7 +31,7 @@ type General struct {
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
Mode T.Mode `json:"mode"`
|
||||
Mode T.TunnelMode `json:"mode"`
|
||||
LogLevel log.LogLevel `json:"log-level"`
|
||||
ExternalController string `json:"-"`
|
||||
ExternalUI string `json:"-"`
|
||||
@ -39,14 +40,15 @@ type General struct {
|
||||
|
||||
// DNS config
|
||||
type DNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||
Fallback []dns.NameServer `yaml:"fallback"`
|
||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||
FakeIPRange *fakeip.Pool
|
||||
Enable bool `yaml:"enable"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||
Fallback []dns.NameServer `yaml:"fallback"`
|
||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||
FakeIPRange *fakeip.Pool
|
||||
}
|
||||
|
||||
// FallbackFilter config
|
||||
@ -57,7 +59,8 @@ type FallbackFilter struct {
|
||||
|
||||
// Experimental config
|
||||
type Experimental struct {
|
||||
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
||||
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
||||
Interface string `yaml:"interface-name"`
|
||||
}
|
||||
|
||||
// Config is clash config manager
|
||||
@ -72,31 +75,32 @@ type Config struct {
|
||||
Providers map[string]provider.ProxyProvider
|
||||
}
|
||||
|
||||
type rawDNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
NameServer []string `yaml:"nameserver"`
|
||||
Fallback []string `yaml:"fallback"`
|
||||
FallbackFilter rawFallbackFilter `yaml:"fallback-filter"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||
FakeIPRange string `yaml:"fake-ip-range"`
|
||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||
type RawDNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
NameServer []string `yaml:"nameserver"`
|
||||
Fallback []string `yaml:"fallback"`
|
||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||
FakeIPRange string `yaml:"fake-ip-range"`
|
||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||
}
|
||||
|
||||
type rawFallbackFilter struct {
|
||||
type RawFallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
IPCIDR []string `yaml:"ipcidr"`
|
||||
}
|
||||
|
||||
type rawConfig struct {
|
||||
type RawConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port"`
|
||||
Authentication []string `yaml:"authentication"`
|
||||
AllowLan bool `yaml:"allow-lan"`
|
||||
BindAddress string `yaml:"bind-address"`
|
||||
Mode T.Mode `yaml:"mode"`
|
||||
Mode T.TunnelMode `yaml:"mode"`
|
||||
LogLevel log.LogLevel `yaml:"log-level"`
|
||||
ExternalController string `yaml:"external-controller"`
|
||||
ExternalUI string `yaml:"external-ui"`
|
||||
@ -104,7 +108,7 @@ type rawConfig struct {
|
||||
|
||||
ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"`
|
||||
Hosts map[string]string `yaml:"hosts"`
|
||||
DNS rawDNS `yaml:"dns"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Proxy []map[string]interface{} `yaml:"Proxy"`
|
||||
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
|
||||
@ -113,10 +117,17 @@ type rawConfig struct {
|
||||
|
||||
// Parse config
|
||||
func Parse(buf []byte) (*Config, error) {
|
||||
config := &Config{}
|
||||
rawCfg, err := UnmarshalRawConfig(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseRawConfig(rawCfg)
|
||||
}
|
||||
|
||||
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
// config with some default value
|
||||
rawCfg := &rawConfig{
|
||||
rawCfg := &RawConfig{
|
||||
AllowLan: false,
|
||||
BindAddress: "*",
|
||||
Mode: T.Rule,
|
||||
@ -129,19 +140,30 @@ func Parse(buf []byte) (*Config, error) {
|
||||
Experimental: Experimental{
|
||||
IgnoreResolveFail: true,
|
||||
},
|
||||
DNS: rawDNS{
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
FallbackFilter: rawFallbackFilter{
|
||||
FallbackFilter: RawFallbackFilter{
|
||||
GeoIP: true,
|
||||
IPCIDR: []string{},
|
||||
},
|
||||
DefaultNameserver: []string{
|
||||
"114.114.114.114",
|
||||
"8.8.8.8",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawCfg, nil
|
||||
}
|
||||
|
||||
func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
config := &Config{}
|
||||
|
||||
config.Experimental = &rawCfg.Experimental
|
||||
|
||||
general, err := parseGeneral(rawCfg)
|
||||
@ -176,10 +198,11 @@ func Parse(buf []byte) (*Config, error) {
|
||||
config.Hosts = hosts
|
||||
|
||||
config.Users = parseAuthentication(rawCfg.Authentication)
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parseGeneral(cfg *rawConfig) (*General, error) {
|
||||
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
port := cfg.Port
|
||||
socksPort := cfg.SocksPort
|
||||
redirPort := cfg.RedirPort
|
||||
@ -192,7 +215,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
|
||||
logLevel := cfg.LogLevel
|
||||
|
||||
if externalUI != "" {
|
||||
externalUI = C.Path.Reslove(externalUI)
|
||||
externalUI = C.Path.Resolve(externalUI)
|
||||
|
||||
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
||||
@ -214,7 +237,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
|
||||
return general, nil
|
||||
}
|
||||
|
||||
func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) {
|
||||
func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) {
|
||||
proxies = make(map[string]C.Proxy)
|
||||
providersMap = make(map[string]provider.ProxyProvider)
|
||||
proxyList := []string{}
|
||||
@ -299,7 +322,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
proxies[groupName] = outbound.NewProxy(group)
|
||||
}
|
||||
|
||||
// initial compatible provier
|
||||
// initial compatible provider
|
||||
for _, pd := range providersMap {
|
||||
if pd.VehicleType() != provider.Compatible {
|
||||
continue
|
||||
@ -316,7 +339,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
ps = append(ps, proxies[v])
|
||||
}
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, hc)
|
||||
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
|
||||
providersMap[provider.ReservedName] = pd
|
||||
|
||||
global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd})
|
||||
@ -324,7 +347,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
rules := []C.Rule{}
|
||||
|
||||
rulesConfig := cfg.Rule
|
||||
@ -403,7 +426,7 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func parseHosts(cfg *rawConfig) (*trie.Trie, error) {
|
||||
func parseHosts(cfg *RawConfig) (*trie.Trie, error) {
|
||||
tree := trie.New()
|
||||
if len(cfg.Hosts) != 0 {
|
||||
for domain, ipStr := range cfg.Hosts {
|
||||
@ -448,20 +471,20 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||
}
|
||||
|
||||
var host, dnsNetType string
|
||||
var addr, dnsNetType string
|
||||
switch u.Scheme {
|
||||
case "udp":
|
||||
host, err = hostWithDefaultPort(u.Host, "53")
|
||||
addr, err = hostWithDefaultPort(u.Host, "53")
|
||||
dnsNetType = "" // UDP
|
||||
case "tcp":
|
||||
host, err = hostWithDefaultPort(u.Host, "53")
|
||||
addr, err = hostWithDefaultPort(u.Host, "53")
|
||||
dnsNetType = "tcp" // TCP
|
||||
case "tls":
|
||||
host, err = hostWithDefaultPort(u.Host, "853")
|
||||
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||
dnsNetType = "tcp-tls" // DNS over TLS
|
||||
case "https":
|
||||
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
||||
host = clearURL.String()
|
||||
addr = clearURL.String()
|
||||
dnsNetType = "https" // DNS over HTTPS
|
||||
default:
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||
@ -475,7 +498,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||
nameservers,
|
||||
dns.NameServer{
|
||||
Net: dnsNetType,
|
||||
Addr: host,
|
||||
Addr: addr,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -496,7 +519,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||
return ipNets, nil
|
||||
}
|
||||
|
||||
func parseDNS(cfg rawDNS) (*DNS, error) {
|
||||
func parseDNS(cfg RawDNS) (*DNS, error) {
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
|
||||
}
|
||||
@ -519,6 +542,20 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cfg.DefaultNameserver) == 0 {
|
||||
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||
}
|
||||
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check default nameserver is pure ip addr
|
||||
for _, ns := range dnsCfg.DefaultNameserver {
|
||||
host, _, err := net.SplitHostPort(ns.Addr)
|
||||
if err != nil || net.ParseIP(host) == nil {
|
||||
return nil, errors.New("default nameserver should be pure IP")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.EnhancedMode == dns.FAKEIP {
|
||||
_, ipnet, err := net.ParseCIDR(cfg.FakeIPRange)
|
||||
if err != nil {
|
||||
|
@ -38,15 +38,19 @@ func Init(dir string) error {
|
||||
|
||||
// initial config.yaml
|
||||
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find config, create an empty file")
|
||||
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
||||
log.Infoln("Can't find config, create a initial config file")
|
||||
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error())
|
||||
}
|
||||
f.Write([]byte(`port: 7890`))
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// initial mmdb
|
||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find MMDB, start download")
|
||||
err := downloadMMDB(C.Path.MMDB())
|
||||
if err != nil {
|
||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -53,13 +53,14 @@ type Conn interface {
|
||||
type PacketConn interface {
|
||||
net.PacketConn
|
||||
Connection
|
||||
WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
|
||||
}
|
||||
|
||||
type ProxyAdapter interface {
|
||||
Name() string
|
||||
Type() AdapterType
|
||||
DialContext(ctx context.Context, metadata *Metadata) (Conn, error)
|
||||
DialUDP(metadata *Metadata) (PacketConn, net.Addr, error)
|
||||
DialUDP(metadata *Metadata) (PacketConn, error)
|
||||
SupportUDP() bool
|
||||
MarshalJSON() ([]byte, error)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package constant
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Socks addr type
|
||||
@ -70,6 +71,25 @@ func (m *Metadata) RemoteAddress() string {
|
||||
return net.JoinHostPort(m.String(), m.DstPort)
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceAddress() string {
|
||||
return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
|
||||
}
|
||||
|
||||
func (m *Metadata) Resolved() bool {
|
||||
return m.DstIP != nil
|
||||
}
|
||||
|
||||
func (m *Metadata) UDPAddr() *net.UDPAddr {
|
||||
if m.NetWork != UDP || m.DstIP == nil {
|
||||
return nil
|
||||
}
|
||||
port, _ := strconv.Atoi(m.DstPort)
|
||||
return &net.UDPAddr{
|
||||
IP: m.DstIP,
|
||||
Port: port,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metadata) String() string {
|
||||
if m.Host != "" {
|
||||
return m.Host
|
||||
|
@ -44,8 +44,8 @@ func (p *path) Config() string {
|
||||
return p.configFile
|
||||
}
|
||||
|
||||
// Reslove return a absolute path or a relative path with homedir
|
||||
func (p *path) Reslove(path string) string {
|
||||
// Resolve return a absolute path or a relative path with homedir
|
||||
func (p *path) Resolve(path string) string {
|
||||
if !filepath.IsAbs(path) {
|
||||
return filepath.Join(p.HomeDir(), path)
|
||||
}
|
||||
|
@ -2,13 +2,20 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
*D.Client
|
||||
Address string
|
||||
r *Resolver
|
||||
port string
|
||||
host string
|
||||
}
|
||||
|
||||
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
@ -16,6 +23,44 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
}
|
||||
|
||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address)
|
||||
return
|
||||
var ip net.IP
|
||||
if c.r == nil {
|
||||
// a default ip dns
|
||||
ip = net.ParseIP(c.host)
|
||||
} else {
|
||||
var err error
|
||||
if ip, err = c.r.ResolveIP(c.host); err != nil {
|
||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
d := dialer.Dialer()
|
||||
if dialer.DialHook != nil {
|
||||
network := "udp"
|
||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||
network = "tcp"
|
||||
}
|
||||
dialer.DialHook(d, network, ip)
|
||||
}
|
||||
|
||||
c.Client.Dialer = d
|
||||
|
||||
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
||||
// this is a workaround
|
||||
type result struct {
|
||||
msg *D.Msg
|
||||
err error
|
||||
}
|
||||
ch := make(chan result, 1)
|
||||
go func() {
|
||||
msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port))
|
||||
ch <- result{msg, err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case ret := <-ch:
|
||||
return ret.msg, ret.err
|
||||
}
|
||||
}
|
||||
|
34
dns/doh.go
34
dns/doh.go
@ -5,8 +5,11 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@ -15,12 +18,9 @@ const (
|
||||
dotMimeType = "application/dns-message"
|
||||
)
|
||||
|
||||
var dohTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
}
|
||||
|
||||
type dohClient struct {
|
||||
url string
|
||||
url string
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
@ -55,7 +55,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
||||
}
|
||||
|
||||
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
client := &http.Client{Transport: dohTransport}
|
||||
client := &http.Client{Transport: dc.transport}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -70,3 +70,25 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
err = msg.Unpack(buf)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return &dohClient{
|
||||
url: url,
|
||||
transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := r.ResolveIPv4(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
package dns
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
)
|
||||
|
||||
type fallbackFilter interface {
|
||||
Match(net.IP) bool
|
||||
@ -9,11 +13,7 @@ type fallbackFilter interface {
|
||||
type geoipFilter struct{}
|
||||
|
||||
func (gf *geoipFilter) Match(ip net.IP) bool {
|
||||
if mmdb == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
record, _ := mmdb.Country(ip)
|
||||
record, _ := mmdb.Instance().Country(ip)
|
||||
return record.Country.IsoCode == "CN" || record.Country.IsoCode == ""
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,8 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
msg.Answer = []D.RR{rr}
|
||||
|
||||
setMsgTTL(msg, 1)
|
||||
msg.SetReply(r)
|
||||
msg.SetRcode(r, msg.Rcode)
|
||||
msg.Authoritative = true
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
}
|
||||
@ -55,7 +56,8 @@ func withResolver(resolver *Resolver) handler {
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
}
|
||||
msg.SetReply(r)
|
||||
msg.SetRcode(r, msg.Rcode)
|
||||
msg.Authoritative = true
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
}
|
||||
|
@ -4,38 +4,25 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/picker"
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
geoip2 "github.com/oschwald/geoip2-golang"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultResolver aim to resolve ip
|
||||
DefaultResolver *Resolver
|
||||
|
||||
// DefaultHosts aim to resolve hosts
|
||||
DefaultHosts = trie.New()
|
||||
)
|
||||
|
||||
var (
|
||||
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
||||
|
||||
mmdb *geoip2.Reader
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type resolver interface {
|
||||
type dnsClient interface {
|
||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
}
|
||||
@ -50,36 +37,33 @@ type Resolver struct {
|
||||
mapping bool
|
||||
fakeip bool
|
||||
pool *fakeip.Pool
|
||||
main []resolver
|
||||
fallback []resolver
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
fallbackFilters []fallbackFilter
|
||||
group singleflight.Group
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA
|
||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
|
||||
ch := make(chan net.IP)
|
||||
ch := make(chan net.IP, 1)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
ip, err := r.resolveIP(host, D.TypeA)
|
||||
ip, err := r.resolveIP(host, D.TypeAAAA)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch <- ip
|
||||
}()
|
||||
|
||||
ip, err = r.resolveIP(host, D.TypeAAAA)
|
||||
ip, err = r.resolveIP(host, D.TypeA)
|
||||
if err == nil {
|
||||
go func() {
|
||||
<-ch
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
ip, open := <-ch
|
||||
if !open {
|
||||
return nil, errIPNotFound
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
@ -179,16 +163,12 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) {
|
||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second)
|
||||
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||
for _, client := range clients {
|
||||
r := client
|
||||
fast.Go(func() (interface{}, error) {
|
||||
msg, err := r.ExchangeContext(ctx, m)
|
||||
if err != nil || msg.Rcode != D.RcodeSuccess {
|
||||
return nil, errors.New("resolve error")
|
||||
}
|
||||
return msg, nil
|
||||
return r.ExchangeContext(ctx, m)
|
||||
})
|
||||
}
|
||||
|
||||
@ -233,6 +213,8 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
|
||||
return ip, nil
|
||||
} else if dnsType == D.TypeA && isIPv4 {
|
||||
return ip, nil
|
||||
} else {
|
||||
return nil, resolver.ErrIPVersion
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,11 +227,12 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
|
||||
}
|
||||
|
||||
ips := r.msgToIP(msg)
|
||||
if len(ips) == 0 {
|
||||
return nil, errIPNotFound
|
||||
ipLength := len(ips)
|
||||
if ipLength == 0 {
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
|
||||
ip = ips[0]
|
||||
ip = ips[rand.Intn(ipLength)]
|
||||
return
|
||||
}
|
||||
|
||||
@ -268,7 +251,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
||||
return ips
|
||||
}
|
||||
|
||||
func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result {
|
||||
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
|
||||
ch := make(chan *result)
|
||||
go func() {
|
||||
res, err := r.batchExchange(client, msg)
|
||||
@ -289,6 +272,7 @@ type FallbackFilter struct {
|
||||
|
||||
type Config struct {
|
||||
Main, Fallback []NameServer
|
||||
Default []NameServer
|
||||
IPv6 bool
|
||||
EnhancedMode EnhancedMode
|
||||
FallbackFilter FallbackFilter
|
||||
@ -296,9 +280,14 @@ type Config struct {
|
||||
}
|
||||
|
||||
func New(config Config) *Resolver {
|
||||
defaultResolver := &Resolver{
|
||||
main: transform(config.Default, nil),
|
||||
cache: cache.New(time.Second * 60),
|
||||
}
|
||||
|
||||
r := &Resolver{
|
||||
ipv6: config.IPv6,
|
||||
main: transform(config.Main),
|
||||
main: transform(config.Main, defaultResolver),
|
||||
cache: cache.New(time.Second * 60),
|
||||
mapping: config.EnhancedMode == MAPPING,
|
||||
fakeip: config.EnhancedMode == FAKEIP,
|
||||
@ -306,15 +295,11 @@ func New(config Config) *Resolver {
|
||||
}
|
||||
|
||||
if len(config.Fallback) != 0 {
|
||||
r.fallback = transform(config.Fallback)
|
||||
r.fallback = transform(config.Fallback, defaultResolver)
|
||||
}
|
||||
|
||||
fallbackFilters := []fallbackFilter{}
|
||||
if config.FallbackFilter.GeoIP {
|
||||
once.Do(func() {
|
||||
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
||||
})
|
||||
|
||||
fallbackFilters = append(fallbackFilters, &geoipFilter{})
|
||||
}
|
||||
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||
|
15
dns/util.go
15
dns/util.go
@ -4,13 +4,14 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -117,14 +118,15 @@ func isIPRequest(q D.Question) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func transform(servers []NameServer) []resolver {
|
||||
ret := []resolver{}
|
||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
ret := []dnsClient{}
|
||||
for _, s := range servers {
|
||||
if s.Net == "https" {
|
||||
ret = append(ret, &dohClient{url: s.Addr})
|
||||
ret = append(ret, newDoHClient(s.Addr, resolver))
|
||||
continue
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(s.Addr)
|
||||
ret = append(ret, &client{
|
||||
Client: &D.Client{
|
||||
Net: s.Net,
|
||||
@ -134,8 +136,11 @@ func transform(servers []NameServer) []resolver {
|
||||
NextProtos: []string{"dns"},
|
||||
},
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
Address: s.Addr,
|
||||
port: port,
|
||||
host: host,
|
||||
r: resolver,
|
||||
})
|
||||
}
|
||||
return ret
|
||||
|
10
go.mod
10
go.mod
@ -5,18 +5,18 @@ go 1.13
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.5
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/chi v4.0.3+incompatible
|
||||
github.com/go-chi/cors v1.0.0
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v3.2.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/miekg/dns v1.1.26
|
||||
github.com/miekg/dns v1.1.27
|
||||
github.com/oschwald/geoip2-golang v1.4.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
gopkg.in/eapache/channels.v1 v1.1.0
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
29
go.sum
29
go.sum
@ -5,8 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY=
|
||||
github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
|
||||
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
@ -17,8 +17,8 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
@ -35,17 +35,19 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
@ -55,21 +57,18 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4=
|
||||
gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
4
hooks/pre_build
Normal file
4
hooks/pre_build
Normal 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
|
@ -8,14 +8,16 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
// forward compatibility before 1.0
|
||||
@ -83,7 +85,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateRules(cfg.Rules)
|
||||
updateDNS(cfg.DNS)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateExperimental(cfg.Experimental)
|
||||
updateExperimental(cfg)
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
@ -100,20 +102,30 @@ func GetGeneral() *config.General {
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
BindAddress: P.BindAddress(),
|
||||
Mode: T.Instance().Mode(),
|
||||
Mode: tunnel.Mode(),
|
||||
LogLevel: log.Level(),
|
||||
}
|
||||
|
||||
return general
|
||||
}
|
||||
|
||||
func updateExperimental(c *config.Experimental) {
|
||||
T.Instance().UpdateExperimental(c.IgnoreResolveFail)
|
||||
func updateExperimental(c *config.Config) {
|
||||
cfg := c.Experimental
|
||||
|
||||
tunnel.UpdateExperimental(cfg.IgnoreResolveFail)
|
||||
if cfg.Interface != "" && c.DNS.Enable {
|
||||
dialer.DialHook = dialer.DialerWithInterface(cfg.Interface)
|
||||
dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface)
|
||||
} else {
|
||||
dialer.DialHook = nil
|
||||
dialer.ListenPacketHook = nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateDNS(c *config.DNS) {
|
||||
if c.Enable == false {
|
||||
dns.DefaultResolver = nil
|
||||
resolver.DefaultResolver = nil
|
||||
tunnel.SetResolver(nil)
|
||||
dns.ReCreateServer("", nil)
|
||||
return
|
||||
}
|
||||
@ -127,8 +139,10 @@ func updateDNS(c *config.DNS) {
|
||||
GeoIP: c.FallbackFilter.GeoIP,
|
||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||
},
|
||||
Default: c.DefaultNameserver,
|
||||
})
|
||||
dns.DefaultResolver = r
|
||||
resolver.DefaultResolver = r
|
||||
tunnel.SetResolver(r)
|
||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
||||
log.Errorln("Start DNS server error: %s", err.Error())
|
||||
return
|
||||
@ -140,11 +154,10 @@ func updateDNS(c *config.DNS) {
|
||||
}
|
||||
|
||||
func updateHosts(tree *trie.Trie) {
|
||||
dns.DefaultHosts = tree
|
||||
resolver.DefaultHosts = tree
|
||||
}
|
||||
|
||||
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||
tunnel := T.Instance()
|
||||
oldProviders := tunnel.Providers()
|
||||
|
||||
// close providers goroutine
|
||||
@ -156,12 +169,12 @@ func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.Pro
|
||||
}
|
||||
|
||||
func updateRules(rules []C.Rule) {
|
||||
T.Instance().UpdateRules(rules)
|
||||
tunnel.UpdateRules(rules)
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General) {
|
||||
log.SetLevel(general.LogLevel)
|
||||
T.Instance().SetMode(general.Mode)
|
||||
tunnel.SetMode(general.Mode)
|
||||
|
||||
allowLan := general.AllowLan
|
||||
P.SetAllowLan(allowLan)
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
P "github.com/Dreamacro/clash/proxy"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
@ -23,13 +23,13 @@ func configRouter() http.Handler {
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *T.Mode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
@ -67,7 +67,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
|
||||
if general.Mode != nil {
|
||||
T.Instance().SetMode(*general.Mode)
|
||||
tunnel.SetMode(*general.Mode)
|
||||
}
|
||||
|
||||
if general.LogLevel != nil {
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
@ -25,7 +25,7 @@ func proxyProviderRouter() http.Handler {
|
||||
}
|
||||
|
||||
func getProviders(w http.ResponseWriter, r *http.Request) {
|
||||
providers := T.Instance().Providers()
|
||||
providers := tunnel.Providers()
|
||||
render.JSON(w, r, render.M{
|
||||
"providers": providers,
|
||||
})
|
||||
@ -63,7 +63,7 @@ func parseProviderName(next http.Handler) http.Handler {
|
||||
func findProviderByName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.Context().Value(CtxKeyProviderName).(string)
|
||||
providers := T.Instance().Providers()
|
||||
providers := tunnel.Providers()
|
||||
provider, exist := providers[name]
|
||||
if !exist {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
@ -40,7 +40,7 @@ func parseProxyName(next http.Handler) http.Handler {
|
||||
func findProxyByName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||
proxies := T.Instance().Proxies()
|
||||
proxies := tunnel.Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
@ -54,7 +54,7 @@ func findProxyByName(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
func getProxies(w http.ResponseWriter, r *http.Request) {
|
||||
proxies := T.Instance().Proxies()
|
||||
proxies := tunnel.Proxies()
|
||||
render.JSON(w, r, render.M{
|
||||
"proxies": proxies,
|
||||
})
|
||||
|
@ -3,7 +3,7 @@ package route
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
@ -22,7 +22,7 @@ type Rule struct {
|
||||
}
|
||||
|
||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
rawRules := T.Instance().Rules()
|
||||
rawRules := tunnel.Rules()
|
||||
|
||||
rules := []Rule{}
|
||||
for _, rule := range rawRules {
|
||||
|
@ -57,10 +57,10 @@ func Start(addr string, secret string) {
|
||||
})
|
||||
|
||||
r.Use(cors.Handler)
|
||||
r.Get("/", hello)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(authentication)
|
||||
|
||||
r.Get("/", hello)
|
||||
r.Get("/logs", getLogs)
|
||||
r.Get("/traffic", traffic)
|
||||
r.Get("/version", version)
|
||||
|
@ -16,10 +16,6 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
var (
|
||||
tun = tunnel.Instance()
|
||||
)
|
||||
|
||||
type HttpListener struct {
|
||||
net.Listener
|
||||
address string
|
||||
@ -100,9 +96,9 @@ func handleConn(conn net.Conn, cache *cache.Cache) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tun.Add(adapters.NewHTTPS(request, conn))
|
||||
tunnel.Add(adapters.NewHTTPS(request, conn))
|
||||
return
|
||||
}
|
||||
|
||||
tun.Add(adapters.NewHTTP(request, conn))
|
||||
tunnel.Add(adapters.NewHTTP(request, conn))
|
||||
}
|
||||
|
@ -9,10 +9,6 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
var (
|
||||
tun = tunnel.Instance()
|
||||
)
|
||||
|
||||
type RedirListener struct {
|
||||
net.Listener
|
||||
address string
|
||||
@ -59,5 +55,5 @@ func handleRedir(conn net.Conn) {
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
tun.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP))
|
||||
tunnel.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP))
|
||||
}
|
||||
|
@ -13,10 +13,6 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
var (
|
||||
tun = tunnel.Instance()
|
||||
)
|
||||
|
||||
type SockListener struct {
|
||||
net.Listener
|
||||
address string
|
||||
@ -68,5 +64,5 @@ func handleSocks(conn net.Conn) {
|
||||
io.Copy(ioutil.Discard, conn)
|
||||
return
|
||||
}
|
||||
tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP))
|
||||
tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP))
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type SockUDPListener struct {
|
||||
@ -58,10 +59,9 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
|
||||
}
|
||||
packet := &fakeConn{
|
||||
PacketConn: pc,
|
||||
remoteAddr: addr,
|
||||
targetAddr: target,
|
||||
rAddr: addr,
|
||||
payload: payload,
|
||||
bufRef: buf,
|
||||
}
|
||||
tun.AddPacket(adapters.NewPacket(target, packet, C.SOCKS, C.UDP))
|
||||
tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS))
|
||||
}
|
||||
|
@ -9,10 +9,9 @@ import (
|
||||
|
||||
type fakeConn struct {
|
||||
net.PacketConn
|
||||
remoteAddr net.Addr
|
||||
targetAddr socks5.Addr
|
||||
payload []byte
|
||||
bufRef []byte
|
||||
rAddr net.Addr
|
||||
payload []byte
|
||||
bufRef []byte
|
||||
}
|
||||
|
||||
func (c *fakeConn) Data() []byte {
|
||||
@ -21,21 +20,16 @@ func (c *fakeConn) Data() []byte {
|
||||
|
||||
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
||||
func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
from := c.targetAddr
|
||||
if addr != nil {
|
||||
// if addr is provided, use the parsed addr
|
||||
from = socks5.ParseAddrToSocksAddr(addr)
|
||||
}
|
||||
packet, err := socks5.EncodeUDPPacket(from, b)
|
||||
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return c.PacketConn.WriteTo(packet, c.remoteAddr)
|
||||
return c.PacketConn.WriteTo(packet, c.rAddr)
|
||||
}
|
||||
|
||||
// LocalAddr returns the source IP/Port of UDP Packet
|
||||
func (c *fakeConn) LocalAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
return c.rAddr
|
||||
}
|
||||
|
||||
func (c *fakeConn) Close() error {
|
||||
|
@ -1,17 +1,8 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
var (
|
||||
mmdb *geoip2.Reader
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type GEOIP struct {
|
||||
@ -29,7 +20,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
record, _ := mmdb.Country(ip)
|
||||
record, _ := mmdb.Instance().Country(ip)
|
||||
return record.Country.IsoCode == g.country
|
||||
}
|
||||
|
||||
@ -46,14 +37,6 @@ func (g *GEOIP) NoResolveIP() bool {
|
||||
}
|
||||
|
||||
func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP {
|
||||
once.Do(func() {
|
||||
var err error
|
||||
mmdb, err = geoip2.Open(C.Path.MMDB())
|
||||
if err != nil {
|
||||
log.Fatalln("Can't load mmdb: %s", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
geoip := &GEOIP{
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
req := request.R
|
||||
host := req.Host
|
||||
|
||||
@ -81,28 +81,25 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr net.Addr) {
|
||||
if _, err := pc.WriteTo(packet.Data(), addr); err != nil {
|
||||
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) {
|
||||
if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil {
|
||||
return
|
||||
}
|
||||
DefaultManager.Upload() <- int64(len(packet.Data()))
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, omitSrcAddr bool, timeout time.Duration) {
|
||||
func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) {
|
||||
buf := pool.BufPool.Get().([]byte)
|
||||
defer pool.BufPool.Put(buf[:cap(buf)])
|
||||
defer t.natTable.Delete(key)
|
||||
defer natTable.Delete(key)
|
||||
defer pc.Close()
|
||||
|
||||
for {
|
||||
pc.SetReadDeadline(time.Now().Add(timeout))
|
||||
pc.SetReadDeadline(time.Now().Add(udpTimeout))
|
||||
n, from, err := pc.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if from != nil && omitSrcAddr {
|
||||
from = nil
|
||||
}
|
||||
|
||||
n, err = packet.WriteBack(buf[:n], from)
|
||||
if err != nil {
|
||||
@ -112,7 +109,7 @@ func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key str
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
|
||||
func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
|
||||
relay(request, outbound)
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,15 @@ func (m *Manager) Snapshot() *Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ResetStatistic() {
|
||||
m.uploadTemp = 0
|
||||
m.uploadBlip = 0
|
||||
m.uploadTotal = 0
|
||||
m.downloadTemp = 0
|
||||
m.downloadBlip = 0
|
||||
m.downloadTotal = 0
|
||||
}
|
||||
|
||||
func (m *Manager) handle() {
|
||||
go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal)
|
||||
go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal)
|
||||
|
@ -5,11 +5,11 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Mode int
|
||||
type TunnelMode int
|
||||
|
||||
var (
|
||||
// ModeMapping is a mapping for Mode enum
|
||||
ModeMapping = map[string]Mode{
|
||||
ModeMapping = map[string]TunnelMode{
|
||||
Global.String(): Global,
|
||||
Rule.String(): Rule,
|
||||
Direct.String(): Direct,
|
||||
@ -17,13 +17,13 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
Global Mode = iota
|
||||
Global TunnelMode = iota
|
||||
Rule
|
||||
Direct
|
||||
)
|
||||
|
||||
// UnmarshalJSON unserialize Mode
|
||||
func (m *Mode) UnmarshalJSON(data []byte) error {
|
||||
func (m *TunnelMode) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
json.Unmarshal(data, &tp)
|
||||
mode, exist := ModeMapping[tp]
|
||||
@ -35,7 +35,7 @@ func (m *Mode) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
// UnmarshalYAML unserialize Mode with yaml
|
||||
func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var tp string
|
||||
unmarshal(&tp)
|
||||
mode, exist := ModeMapping[tp]
|
||||
@ -47,11 +47,11 @@ func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
}
|
||||
|
||||
// MarshalJSON serialize Mode
|
||||
func (m Mode) MarshalJSON() ([]byte, error) {
|
||||
func (m TunnelMode) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(m.String())
|
||||
}
|
||||
|
||||
func (m Mode) String() string {
|
||||
func (m TunnelMode) String() string {
|
||||
switch m {
|
||||
case Global:
|
||||
return "Global"
|
||||
|
256
tunnel/tunnel.go
256
tunnel/tunnel.go
@ -10,6 +10,7 @@ import (
|
||||
"github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/component/nat"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -18,149 +19,155 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
tunnel *Tunnel
|
||||
once sync.Once
|
||||
|
||||
// default timeout for UDP session
|
||||
udpTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
// Tunnel handle relay inbound proxy and outbound proxy
|
||||
type Tunnel struct {
|
||||
tcpQueue *channels.InfiniteChannel
|
||||
udpQueue *channels.InfiniteChannel
|
||||
natTable *nat.Table
|
||||
rules []C.Rule
|
||||
proxies map[string]C.Proxy
|
||||
providers map[string]provider.ProxyProvider
|
||||
configMux sync.RWMutex
|
||||
tcpQueue = channels.NewInfiniteChannel()
|
||||
udpQueue = channels.NewInfiniteChannel()
|
||||
natTable = nat.New()
|
||||
rules []C.Rule
|
||||
proxies = make(map[string]C.Proxy)
|
||||
providers map[string]provider.ProxyProvider
|
||||
configMux sync.RWMutex
|
||||
enhancedMode *dns.Resolver
|
||||
|
||||
// experimental features
|
||||
ignoreResolveFail bool
|
||||
|
||||
// Outbound Rule
|
||||
mode Mode
|
||||
mode = Rule
|
||||
|
||||
// default timeout for UDP session
|
||||
udpTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func init() {
|
||||
go process()
|
||||
}
|
||||
|
||||
// Add request to queue
|
||||
func (t *Tunnel) Add(req C.ServerAdapter) {
|
||||
t.tcpQueue.In() <- req
|
||||
func Add(req C.ServerAdapter) {
|
||||
tcpQueue.In() <- req
|
||||
}
|
||||
|
||||
// AddPacket add udp Packet to queue
|
||||
func (t *Tunnel) AddPacket(packet *inbound.PacketAdapter) {
|
||||
t.udpQueue.In() <- packet
|
||||
func AddPacket(packet *inbound.PacketAdapter) {
|
||||
udpQueue.In() <- packet
|
||||
}
|
||||
|
||||
// Rules return all rules
|
||||
func (t *Tunnel) Rules() []C.Rule {
|
||||
return t.rules
|
||||
func Rules() []C.Rule {
|
||||
return rules
|
||||
}
|
||||
|
||||
// UpdateRules handle update rules
|
||||
func (t *Tunnel) UpdateRules(rules []C.Rule) {
|
||||
t.configMux.Lock()
|
||||
t.rules = rules
|
||||
t.configMux.Unlock()
|
||||
func UpdateRules(newRules []C.Rule) {
|
||||
configMux.Lock()
|
||||
rules = newRules
|
||||
configMux.Unlock()
|
||||
}
|
||||
|
||||
// Proxies return all proxies
|
||||
func (t *Tunnel) Proxies() map[string]C.Proxy {
|
||||
return t.proxies
|
||||
func Proxies() map[string]C.Proxy {
|
||||
return proxies
|
||||
}
|
||||
|
||||
// Providers return all compatible providers
|
||||
func (t *Tunnel) Providers() map[string]provider.ProxyProvider {
|
||||
return t.providers
|
||||
func Providers() map[string]provider.ProxyProvider {
|
||||
return providers
|
||||
}
|
||||
|
||||
// UpdateProxies handle update proxies
|
||||
func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||
t.configMux.Lock()
|
||||
t.proxies = proxies
|
||||
t.providers = providers
|
||||
t.configMux.Unlock()
|
||||
func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provider.ProxyProvider) {
|
||||
configMux.Lock()
|
||||
proxies = newProxies
|
||||
providers = newProviders
|
||||
configMux.Unlock()
|
||||
}
|
||||
|
||||
// UpdateExperimental handle update experimental config
|
||||
func (t *Tunnel) UpdateExperimental(ignoreResolveFail bool) {
|
||||
t.configMux.Lock()
|
||||
t.ignoreResolveFail = ignoreResolveFail
|
||||
t.configMux.Unlock()
|
||||
func UpdateExperimental(value bool) {
|
||||
configMux.Lock()
|
||||
ignoreResolveFail = value
|
||||
configMux.Unlock()
|
||||
}
|
||||
|
||||
// Mode return current mode
|
||||
func (t *Tunnel) Mode() Mode {
|
||||
return t.mode
|
||||
func Mode() TunnelMode {
|
||||
return mode
|
||||
}
|
||||
|
||||
// SetMode change the mode of tunnel
|
||||
func (t *Tunnel) SetMode(mode Mode) {
|
||||
t.mode = mode
|
||||
func SetMode(m TunnelMode) {
|
||||
mode = m
|
||||
}
|
||||
|
||||
// SetResolver set custom dns resolver for enhanced mode
|
||||
func SetResolver(r *dns.Resolver) {
|
||||
enhancedMode = r
|
||||
}
|
||||
|
||||
// processUDP starts a loop to handle udp packet
|
||||
func (t *Tunnel) processUDP() {
|
||||
queue := t.udpQueue.Out()
|
||||
func processUDP() {
|
||||
queue := udpQueue.Out()
|
||||
for elm := range queue {
|
||||
conn := elm.(*inbound.PacketAdapter)
|
||||
t.handleUDPConn(conn)
|
||||
handleUDPConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tunnel) process() {
|
||||
func process() {
|
||||
numUDPWorkers := 4
|
||||
if runtime.NumCPU() > numUDPWorkers {
|
||||
numUDPWorkers = runtime.NumCPU()
|
||||
}
|
||||
for i := 0; i < numUDPWorkers; i++ {
|
||||
go t.processUDP()
|
||||
go processUDP()
|
||||
}
|
||||
|
||||
queue := t.tcpQueue.Out()
|
||||
queue := tcpQueue.Out()
|
||||
for elm := range queue {
|
||||
conn := elm.(C.ServerAdapter)
|
||||
go t.handleTCPConn(conn)
|
||||
go handleTCPConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tunnel) resolveIP(host string) (net.IP, error) {
|
||||
return dns.ResolveIP(host)
|
||||
func needLookupIP(metadata *C.Metadata) bool {
|
||||
return enhancedMode != nil && (enhancedMode.IsMapping() || enhancedMode.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
|
||||
return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
// handle host equal IP string
|
||||
func preHandleMetadata(metadata *C.Metadata) error {
|
||||
// handle IP string on host
|
||||
if ip := net.ParseIP(metadata.Host); ip != nil {
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
// preprocess enhanced-mode metadata
|
||||
if t.needLookupIP(metadata) {
|
||||
host, exist := dns.DefaultResolver.IPToHost(metadata.DstIP)
|
||||
if needLookupIP(metadata) {
|
||||
host, exist := enhancedMode.IPToHost(metadata.DstIP)
|
||||
if exist {
|
||||
metadata.Host = host
|
||||
metadata.AddrType = C.AtypDomainName
|
||||
if dns.DefaultResolver.FakeIPEnabled() {
|
||||
if enhancedMode.FakeIPEnabled() {
|
||||
metadata.DstIP = nil
|
||||
}
|
||||
} else if enhancedMode.IsFakeIP(metadata.DstIP) {
|
||||
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
var proxy C.Proxy
|
||||
var rule C.Rule
|
||||
switch t.mode {
|
||||
switch mode {
|
||||
case Direct:
|
||||
proxy = t.proxies["DIRECT"]
|
||||
proxy = proxies["DIRECT"]
|
||||
case Global:
|
||||
proxy = t.proxies["GLOBAL"]
|
||||
proxy = proxies["GLOBAL"]
|
||||
// Rule
|
||||
default:
|
||||
var err error
|
||||
proxy, rule, err = t.match(metadata)
|
||||
proxy, rule, err = match(metadata)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -168,71 +175,74 @@ func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error)
|
||||
return proxy, rule, nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) {
|
||||
func handleUDPConn(packet *inbound.PacketAdapter) {
|
||||
metadata := packet.Metadata()
|
||||
if !metadata.Valid() {
|
||||
log.Warnln("[Metadata] not valid: %#v", metadata)
|
||||
return
|
||||
}
|
||||
|
||||
src := packet.LocalAddr().String()
|
||||
dst := metadata.RemoteAddress()
|
||||
key := src + "-" + dst
|
||||
if err := preHandleMetadata(metadata); err != nil {
|
||||
log.Debugln("[Metadata PreHandle] error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
pc, addr := t.natTable.Get(key)
|
||||
key := packet.LocalAddr().String()
|
||||
pc := natTable.Get(key)
|
||||
if pc != nil {
|
||||
t.handleUDPToRemote(packet, pc, addr)
|
||||
handleUDPToRemote(packet, pc, metadata)
|
||||
return
|
||||
}
|
||||
|
||||
lockKey := key + "-lock"
|
||||
wg, loaded := t.natTable.GetOrCreateLock(lockKey)
|
||||
|
||||
isFakeIP := dns.DefaultResolver != nil && dns.DefaultResolver.IsFakeIP(metadata.DstIP)
|
||||
wg, loaded := natTable.GetOrCreateLock(lockKey)
|
||||
|
||||
go func() {
|
||||
if !loaded {
|
||||
wg.Add(1)
|
||||
proxy, rule, err := t.resolveMetadata(metadata)
|
||||
proxy, rule, err := resolveMetadata(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
|
||||
t.natTable.Delete(lockKey)
|
||||
natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
rawPc, nAddr, err := proxy.DialUDP(metadata)
|
||||
rawPc, err := proxy.DialUDP(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error())
|
||||
t.natTable.Delete(lockKey)
|
||||
natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
addr = nAddr
|
||||
pc = newUDPTracker(rawPc, DefaultManager, metadata, rule)
|
||||
|
||||
if rule != nil {
|
||||
log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String())
|
||||
} else {
|
||||
log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String())
|
||||
switch true {
|
||||
case rule != nil:
|
||||
log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String())
|
||||
case mode == Global:
|
||||
log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String())
|
||||
case mode == Direct:
|
||||
log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
default:
|
||||
log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
}
|
||||
|
||||
t.natTable.Set(key, pc, addr)
|
||||
t.natTable.Delete(lockKey)
|
||||
natTable.Set(key, pc)
|
||||
natTable.Delete(lockKey)
|
||||
wg.Done()
|
||||
// in fake-ip mode, Full-Cone NAT can never achieve, fallback to omitting src Addr
|
||||
go t.handleUDPToLocal(packet.UDPPacket, pc, key, isFakeIP, udpTimeout)
|
||||
go handleUDPToLocal(packet.UDPPacket, pc, key)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
pc, addr := t.natTable.Get(key)
|
||||
pc := natTable.Get(key)
|
||||
if pc != nil {
|
||||
t.handleUDPToRemote(packet, pc, addr)
|
||||
handleUDPToRemote(packet, pc, metadata)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
|
||||
func handleTCPConn(localConn C.ServerAdapter) {
|
||||
defer localConn.Close()
|
||||
|
||||
metadata := localConn.Metadata()
|
||||
@ -241,7 +251,12 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
|
||||
return
|
||||
}
|
||||
|
||||
proxy, rule, err := t.resolveMetadata(metadata)
|
||||
if err := preHandleMetadata(metadata); err != nil {
|
||||
log.Debugln("[Metadata PreHandle] error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxy, rule, err := resolveMetadata(metadata)
|
||||
if err != nil {
|
||||
log.Warnln("Parse metadata failed: %v", err)
|
||||
return
|
||||
@ -255,41 +270,46 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
|
||||
remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule)
|
||||
defer remoteConn.Close()
|
||||
|
||||
if rule != nil {
|
||||
log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String())
|
||||
} else {
|
||||
log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String())
|
||||
switch true {
|
||||
case rule != nil:
|
||||
log.Infoln("[TCP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String())
|
||||
case mode == Global:
|
||||
log.Infoln("[TCP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String())
|
||||
case mode == Direct:
|
||||
log.Infoln("[TCP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
default:
|
||||
log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String())
|
||||
}
|
||||
|
||||
switch adapter := localConn.(type) {
|
||||
case *inbound.HTTPAdapter:
|
||||
t.handleHTTP(adapter, remoteConn)
|
||||
handleHTTP(adapter, remoteConn)
|
||||
case *inbound.SocketAdapter:
|
||||
t.handleSocket(adapter, remoteConn)
|
||||
handleSocket(adapter, remoteConn)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
|
||||
func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
|
||||
return !rule.NoResolveIP() && metadata.Host != "" && metadata.DstIP == nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
t.configMux.RLock()
|
||||
defer t.configMux.RUnlock()
|
||||
func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
configMux.RLock()
|
||||
defer configMux.RUnlock()
|
||||
|
||||
var resolved bool
|
||||
|
||||
if node := dns.DefaultHosts.Search(metadata.Host); node != nil {
|
||||
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
||||
ip := node.Data.(net.IP)
|
||||
metadata.DstIP = ip
|
||||
resolved = true
|
||||
}
|
||||
|
||||
for _, rule := range t.rules {
|
||||
if !resolved && t.shouldResolveIP(rule, metadata) {
|
||||
ip, err := t.resolveIP(metadata.Host)
|
||||
for _, rule := range rules {
|
||||
if !resolved && shouldResolveIP(rule, metadata) {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
if err != nil {
|
||||
if !t.ignoreResolveFail {
|
||||
if !ignoreResolveFail {
|
||||
return nil, nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error())
|
||||
}
|
||||
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
|
||||
@ -301,7 +321,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
}
|
||||
|
||||
if rule.Match(metadata) {
|
||||
adapter, ok := t.proxies[rule.Adapter()]
|
||||
adapter, ok := proxies[rule.Adapter()]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@ -313,24 +333,6 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
return adapter, rule, nil
|
||||
}
|
||||
}
|
||||
return t.proxies["DIRECT"], nil, nil
|
||||
}
|
||||
|
||||
func newTunnel() *Tunnel {
|
||||
return &Tunnel{
|
||||
tcpQueue: channels.NewInfiniteChannel(),
|
||||
udpQueue: channels.NewInfiniteChannel(),
|
||||
natTable: nat.New(),
|
||||
proxies: make(map[string]C.Proxy),
|
||||
mode: Rule,
|
||||
}
|
||||
}
|
||||
|
||||
// Instance return singleton instance of Tunnel
|
||||
func Instance() *Tunnel {
|
||||
once.Do(func() {
|
||||
tunnel = newTunnel()
|
||||
go tunnel.process()
|
||||
})
|
||||
return tunnel
|
||||
return proxies["DIRECT"], nil, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user