mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-11-16 11:42:43 +08:00
Feature: add lwIP TCP/IP stack to tun listener
This commit is contained in:
parent
433d35e866
commit
862174d21b
10
.github/workflows/go.yml
vendored
10
.github/workflows/go.yml
vendored
|
@ -34,6 +34,10 @@ jobs:
|
||||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
staticcheck -- $(go list ./...)
|
staticcheck -- $(go list ./...)
|
||||||
|
|
||||||
|
# init xgo
|
||||||
|
docker pull techknowlogick/xgo:latest
|
||||||
|
go install src.techknowlogick.com/xgo@latest
|
||||||
|
|
||||||
- name: SSH connection to Actions
|
- name: SSH connection to Actions
|
||||||
uses: P3TERX/ssh2actions@v1.0.0
|
uses: P3TERX/ssh2actions@v1.0.0
|
||||||
if: github.actor == github.repository_owner && contains(github.event.head_commit.message, '[ssh]')
|
if: github.actor == github.repository_owner && contains(github.event.head_commit.message, '[ssh]')
|
||||||
|
@ -43,15 +47,18 @@ jobs:
|
||||||
env:
|
env:
|
||||||
NAME: clash
|
NAME: clash
|
||||||
BINDIR: bin
|
BINDIR: bin
|
||||||
run: make -j releases
|
run: |
|
||||||
|
make cleancache && make -j releases
|
||||||
|
|
||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') == false
|
||||||
run: |
|
run: |
|
||||||
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||||
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload files to Artifacts
|
- name: Upload files to Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') == false
|
||||||
with:
|
with:
|
||||||
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||||
path: |
|
path: |
|
||||||
|
@ -76,5 +83,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
keep_latest: 1
|
keep_latest: 1
|
||||||
delete_tags: true
|
delete_tags: true
|
||||||
|
delete_tag_pattern: premium
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
126
Makefile
126
Makefile
|
@ -1,103 +1,61 @@
|
||||||
|
GOCMD=go
|
||||||
|
XGOCMD=xgo -go go-1.17.x
|
||||||
|
GOBUILD=CGO_ENABLED=1 $(GOCMD) build -a -trimpath
|
||||||
|
GOCLEAN=$(GOCMD) clean
|
||||||
NAME=clash
|
NAME=clash
|
||||||
BINDIR=bin
|
BINDIR=$(shell pwd)/bin
|
||||||
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
BUILD_PACKAGE=.
|
||||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
RELEASE_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
-w -s -buildid='
|
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||||
|
-w -s -buildid='
|
||||||
|
STATIC_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
|
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||||
|
-extldflags "-static" \
|
||||||
|
-w -s -buildid='
|
||||||
|
|
||||||
PLATFORM_LIST = \
|
PLATFORM_LIST = \
|
||||||
darwin-amd64 \
|
darwin-10.12-amd64 \
|
||||||
darwin-arm64 \
|
darwin-10.15-arm64 \
|
||||||
linux-386 \
|
linux-386 \
|
||||||
linux-amd64 \
|
linux-amd64 \
|
||||||
linux-armv5 \
|
linux-arm64
|
||||||
linux-armv6 \
|
|
||||||
linux-armv7 \
|
|
||||||
linux-armv8 \
|
|
||||||
linux-mips-softfloat \
|
|
||||||
linux-mips-hardfloat \
|
|
||||||
linux-mipsle-softfloat \
|
|
||||||
linux-mipsle-hardfloat \
|
|
||||||
linux-mips64 \
|
|
||||||
linux-mips64le \
|
|
||||||
freebsd-386 \
|
|
||||||
freebsd-amd64 \
|
|
||||||
freebsd-arm64
|
|
||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-4.0-amd64 \
|
||||||
windows-amd64 \
|
windows-4.0-386
|
||||||
windows-arm64 \
|
# windows-arm64
|
||||||
windows-arm32v7
|
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-10.12-amd64 windows-4.0-amd64 # Most used
|
||||||
|
|
||||||
docker:
|
build:
|
||||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64:
|
darwin-10.12-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.12/amd64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
darwin-arm64:
|
darwin-10.15-arm64:
|
||||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.15/arm64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-386:
|
linux-386:
|
||||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/386 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-amd64:
|
linux-amd64:
|
||||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=linux $(GOBUILD) -ldflags $(STATIC_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
#$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/amd64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-armv5:
|
linux-arm64:
|
||||||
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-armv6:
|
windows-4.0-386:
|
||||||
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/386 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-armv7:
|
windows-4.0-amd64:
|
||||||
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/amd64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-armv8:
|
#windows-arm64:
|
||||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
# $(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows/arm64 $(BUILD_PACKAGE)
|
||||||
|
|
||||||
linux-mips-softfloat:
|
|
||||||
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips-hardfloat:
|
|
||||||
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mipsle-softfloat:
|
|
||||||
GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mipsle-hardfloat:
|
|
||||||
GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips64:
|
|
||||||
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-mips64le:
|
|
||||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-386:
|
|
||||||
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-amd64:
|
|
||||||
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
freebsd-arm64:
|
|
||||||
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
windows-386:
|
|
||||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
windows-amd64:
|
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
windows-arm64:
|
|
||||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
windows-arm32v7:
|
|
||||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||||
|
@ -112,5 +70,13 @@ $(zip_releases): %.zip : %
|
||||||
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||||
|
|
||||||
releases: $(gz_releases) $(zip_releases)
|
releases: $(gz_releases) $(zip_releases)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm $(BINDIR)/*
|
rm -rf $(BINDIR)
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
cleancache:
|
||||||
|
# go build cache may need to cleanup if changing C source code
|
||||||
|
$(GOCLEAN) -cache
|
||||||
|
rm -rf $(BINDIR)
|
||||||
|
mkdir -p $(BINDIR)
|
|
@ -39,12 +39,14 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash
|
||||||
### TUN configuration
|
### TUN configuration
|
||||||
Supports macOS, Linux and Windows.
|
Supports macOS, Linux and Windows.
|
||||||
|
|
||||||
|
Support lwIP stack, a lightweight TCP/IP stack, recommend set to tun.
|
||||||
|
|
||||||
On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into Clash home directory.
|
On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into Clash home directory.
|
||||||
```yaml
|
```yaml
|
||||||
# Enable the TUN listener
|
# Enable the TUN listener
|
||||||
tun:
|
tun:
|
||||||
enable: true
|
enable: true
|
||||||
stack: system # system or gvisor
|
stack: lwip # lwip(recommend), system or gvisor
|
||||||
dns-listen: 0.0.0.0:53 # additional dns server listen on TUN
|
dns-listen: 0.0.0.0:53 # additional dns server listen on TUN
|
||||||
auto-route: true # auto set global route
|
auto-route: true # auto set global route
|
||||||
```
|
```
|
||||||
|
|
|
@ -183,7 +183,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
ProxyGroup: []map[string]interface{}{},
|
ProxyGroup: []map[string]interface{}{},
|
||||||
Tun: Tun{
|
Tun: Tun{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
Stack: "system",
|
Stack: "lwip",
|
||||||
DNSListen: "0.0.0.0:53",
|
DNSListen: "0.0.0.0:53",
|
||||||
AutoRoute: true,
|
AutoRoute: true,
|
||||||
},
|
},
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499
|
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499
|
||||||
|
github.com/yaling888/go-lwip v0.0.0-20210928231210-94b50cb51cc1
|
||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f
|
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -566,6 +566,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499 h1:QHESTXtfgc1ABV+ArlbPVqUx9Ht5I0dDkYhxYoXFxNo=
|
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499 h1:QHESTXtfgc1ABV+ArlbPVqUx9Ht5I0dDkYhxYoXFxNo=
|
||||||
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs=
|
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs=
|
||||||
|
github.com/yaling888/go-lwip v0.0.0-20210928231210-94b50cb51cc1 h1:bhAo5qI3SrfsNP0/91NJ5Rl4cqauE4CeNRplsIuRZTE=
|
||||||
|
github.com/yaling888/go-lwip v0.0.0-20210928231210-94b50cb51cc1/go.mod h1:Y+f95PkWh183q1oDJxdlxTHa2mpdHG5zvBhV0TUhhSY=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
|
@ -151,8 +151,10 @@ func updateDNS(c *config.DNS, general *config.General) {
|
||||||
|
|
||||||
resolver.DefaultResolver = r
|
resolver.DefaultResolver = r
|
||||||
resolver.DefaultHostMapper = m
|
resolver.DefaultHostMapper = m
|
||||||
if general.Tun.Enable && strings.EqualFold(general.Tun.Stack, "system") {
|
if general.Tun.Enable && !strings.EqualFold(general.Tun.Stack, "gvisor") {
|
||||||
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
||||||
|
} else {
|
||||||
|
resolver.DefaultLocalServer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dns.ReCreateServer(c.Listen, r, m); err != nil {
|
if err := dns.ReCreateServer(c.Listen, r, m); err != nil {
|
||||||
|
|
30
listener/tun/ipstack/commons/dns.go
Normal file
30
listener/tun/ipstack/commons/dns.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
D "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
||||||
|
msg := &D.Msg{}
|
||||||
|
if err := msg.Unpack(payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := resolver.ServeMsg(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ans := range r.Answer {
|
||||||
|
header := ans.Header()
|
||||||
|
|
||||||
|
if header.Class == D.ClassINET && (header.Rrtype == D.TypeA || header.Rrtype == D.TypeAAAA) {
|
||||||
|
header.Ttl = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.SetRcode(msg, r.Rcode)
|
||||||
|
r.Compress = true
|
||||||
|
return r.Pack()
|
||||||
|
}
|
|
@ -195,6 +195,7 @@ func (t *gvisorAdapter) AsLinkEndpoint() (result stack.LinkEndpoint, err error)
|
||||||
n, err := t.device.Read(packet)
|
n, err := t.device.Read(packet)
|
||||||
if err != nil && !t.device.IsClose() {
|
if err != nil && !t.device.IsClose() {
|
||||||
log.Errorln("can not read from tun: %v", err)
|
log.Errorln("can not read from tun: %v", err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
var p tcpip.NetworkProtocolNumber
|
var p tcpip.NetworkProtocolNumber
|
||||||
switch header.IPVersion(packet) {
|
switch header.IPVersion(packet) {
|
||||||
|
|
88
listener/tun/ipstack/lwip/dns.go
Normal file
88
listener/tun/ipstack/lwip/dns.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package lwip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/yaling888/go-lwip"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultDnsReadTimeout = time.Second * 30
|
||||||
|
|
||||||
|
func shouldHijackDns(dnsIP net.IP, targetIp net.IP, targetPort int) bool {
|
||||||
|
if targetPort != 53 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnsIP.Equal(net.IPv4zero) || dnsIP.Equal(targetIp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hijackUDPDns(conn golwip.UDPConn, pkt []byte, addr *net.UDPAddr) {
|
||||||
|
go func() {
|
||||||
|
defer func(conn golwip.UDPConn) {
|
||||||
|
_ = conn.Close()
|
||||||
|
}(conn)
|
||||||
|
|
||||||
|
answer, err := D.RelayDnsPacket(pkt)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = conn.WriteFrom(answer, addr)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hijackTCPDns(conn net.Conn) {
|
||||||
|
go func() {
|
||||||
|
defer func(conn net.Conn) {
|
||||||
|
_ = conn.Close()
|
||||||
|
}(conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := conn.SetDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var length uint16
|
||||||
|
if binary.Read(conn, binary.BigEndian, &length) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, length)
|
||||||
|
|
||||||
|
_, err := io.ReadFull(conn, data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rb, err := D.RelayDnsPacket(data)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if binary.Write(conn, binary.BigEndian, uint16(len(rb))) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Write(rb); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsHandler struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDnsHandler() golwip.DnsHandler {
|
||||||
|
return &dnsHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dnsHandler) ResolveIP(host string) (net.IP, error) {
|
||||||
|
log.Debugln("[TUN] lwip resolve ip for host: %s", host)
|
||||||
|
return resolver.ResolveIP(host)
|
||||||
|
}
|
65
listener/tun/ipstack/lwip/tcp.go
Normal file
65
listener/tun/ipstack/lwip/tcp.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package lwip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/context"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/yaling888/go-lwip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tcpHandler struct {
|
||||||
|
dnsIP net.IP
|
||||||
|
tcpIn chan<- C.ConnContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTCPHandler(dnsIP net.IP, tcpIn chan<- C.ConnContext) golwip.TCPConnHandler {
|
||||||
|
return &tcpHandler{dnsIP, tcpIn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tcpHandler) Handle(conn net.Conn, target *net.TCPAddr) error {
|
||||||
|
if shouldHijackDns(h.dnsIP, target.IP, target.Port) {
|
||||||
|
hijackTCPDns(conn)
|
||||||
|
|
||||||
|
if log.Level() == log.DEBUG {
|
||||||
|
log.Debugln("[TUN] hijack dns tcp: %s:%d", target.IP.String(), target.Port)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.RemoteAddr() == nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//if err := conn.SetDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||||
|
// _ = conn.Close()
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
src, _ := conn.LocalAddr().(*net.TCPAddr)
|
||||||
|
dst, _ := conn.RemoteAddr().(*net.TCPAddr)
|
||||||
|
addrType := C.AtypIPv4
|
||||||
|
if dst.IP.To4() == nil {
|
||||||
|
addrType = C.AtypIPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := &C.Metadata{
|
||||||
|
NetWork: C.TCP,
|
||||||
|
Type: C.TUN,
|
||||||
|
SrcIP: src.IP,
|
||||||
|
DstIP: dst.IP,
|
||||||
|
SrcPort: strconv.Itoa(src.Port),
|
||||||
|
DstPort: strconv.Itoa(dst.Port),
|
||||||
|
AddrType: addrType,
|
||||||
|
Host: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(conn net.Conn, metadata *C.Metadata) {
|
||||||
|
h.tcpIn <- context.NewConnContext(conn, metadata)
|
||||||
|
}(conn, metadata)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
101
listener/tun/ipstack/lwip/tun.go
Normal file
101
listener/tun/ipstack/lwip/tun.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package lwip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
"github.com/Dreamacro/clash/config"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/dev"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/yaling888/go-lwip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lwipAdapter struct {
|
||||||
|
device dev.TunDevice
|
||||||
|
lwipStack golwip.LWIPStack
|
||||||
|
lock sync.Mutex
|
||||||
|
mtu int
|
||||||
|
stackName string
|
||||||
|
dnsListen string
|
||||||
|
autoRoute bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(device dev.TunDevice, conf config.Tun, mtu int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
|
||||||
|
adapter := &lwipAdapter{
|
||||||
|
device: device,
|
||||||
|
mtu: mtu,
|
||||||
|
stackName: conf.Stack,
|
||||||
|
dnsListen: conf.DNSListen,
|
||||||
|
autoRoute: conf.AutoRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.lock.Lock()
|
||||||
|
defer adapter.lock.Unlock()
|
||||||
|
|
||||||
|
//adapter.stopLocked()
|
||||||
|
|
||||||
|
dnsHost, _, err := net.SplitHostPort(conf.DNSListen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsIP := net.ParseIP(dnsHost)
|
||||||
|
|
||||||
|
golwip.RegisterOutputFn(func(data []byte) (int, error) {
|
||||||
|
return device.Write(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Setup TCP/IP stack.
|
||||||
|
lwipStack := golwip.NewLWIPStack(mtu)
|
||||||
|
adapter.lwipStack = lwipStack
|
||||||
|
|
||||||
|
golwip.RegisterDnsHandler(NewDnsHandler())
|
||||||
|
golwip.RegisterTCPConnHandler(NewTCPHandler(dnsIP, tcpIn))
|
||||||
|
golwip.RegisterUDPConnHandler(NewUDPHandler(dnsIP, udpIn))
|
||||||
|
|
||||||
|
// Copy packets from tun device to lwip stack, it's the loop.
|
||||||
|
go func(lwipStack golwip.LWIPStack, device dev.TunDevice, mtu int) {
|
||||||
|
_, err := io.CopyBuffer(lwipStack.(io.Writer), device, make([]byte, mtu))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("copying data failed: %v", err)
|
||||||
|
}
|
||||||
|
}(lwipStack, device, mtu)
|
||||||
|
|
||||||
|
return adapter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lwipAdapter) Stack() string {
|
||||||
|
return l.stackName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lwipAdapter) AutoRoute() bool {
|
||||||
|
return l.autoRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lwipAdapter) DNSListen() string {
|
||||||
|
return l.dnsListen
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lwipAdapter) Close() {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
l.stopLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lwipAdapter) stopLocked() {
|
||||||
|
if l.lwipStack != nil {
|
||||||
|
l.lwipStack.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.device != nil {
|
||||||
|
_ = l.device.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
l.lwipStack = nil
|
||||||
|
l.device = nil
|
||||||
|
}
|
77
listener/tun/ipstack/lwip/udp.go
Normal file
77
listener/tun/ipstack/lwip/udp.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package lwip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
"github.com/yaling888/go-lwip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type udpPacket struct {
|
||||||
|
source *net.UDPAddr
|
||||||
|
payload []byte
|
||||||
|
sender golwip.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpPacket) Data() []byte {
|
||||||
|
return u.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
_, ok := addr.(*net.UDPAddr)
|
||||||
|
if !ok {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.sender.WriteFrom(b, u.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpPacket) Drop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpPacket) LocalAddr() net.Addr {
|
||||||
|
return u.source
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpHandler struct {
|
||||||
|
dnsIP net.IP
|
||||||
|
udpIn chan<- *inbound.PacketAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPHandler(dnsIP net.IP, udpIn chan<- *inbound.PacketAdapter) golwip.UDPConnHandler {
|
||||||
|
return &udpHandler{dnsIP, udpIn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *udpHandler) Connect(conn golwip.UDPConn, target *net.UDPAddr) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *udpHandler) ReceiveTo(conn golwip.UDPConn, data []byte, addr *net.UDPAddr) error {
|
||||||
|
if shouldHijackDns(h.dnsIP, addr.IP, addr.Port) {
|
||||||
|
hijackUDPDns(conn, data, addr)
|
||||||
|
|
||||||
|
if log.Level() == log.DEBUG {
|
||||||
|
log.Debugln("[TUN] hijack dns udp: %s:%d", addr.IP.String(), addr.Port)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := &udpPacket{
|
||||||
|
source: conn.LocalAddr(),
|
||||||
|
payload: data,
|
||||||
|
sender: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(addr *net.UDPAddr, packet *udpPacket) {
|
||||||
|
select {
|
||||||
|
case h.udpIn <- inbound.NewPacket(socks5.ParseAddrToSocksAddr(addr), packet, C.TUN):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}(addr, packet)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,10 +6,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
|
||||||
|
|
||||||
"github.com/kr328/tun2socket/binding"
|
"github.com/kr328/tun2socket/binding"
|
||||||
"github.com/kr328/tun2socket/redirect"
|
"github.com/kr328/tun2socket/redirect"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +23,7 @@ func shouldHijackDns(dnsAddr binding.Address, targetAddr binding.Address) bool {
|
||||||
|
|
||||||
func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
||||||
go func() {
|
go func() {
|
||||||
answer, err := relayDnsPacket(pkt)
|
answer, err := D.RelayDnsPacket(pkt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -41,7 +38,9 @@ func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
||||||
|
|
||||||
func hijackTCPDns(conn net.Conn) {
|
func hijackTCPDns(conn net.Conn) {
|
||||||
go func() {
|
go func() {
|
||||||
defer conn.Close()
|
defer func(conn net.Conn) {
|
||||||
|
_ = conn.Close()
|
||||||
|
}(conn)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||||
|
@ -60,7 +59,7 @@ func hijackTCPDns(conn net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rb, err := relayDnsPacket(data)
|
rb, err := D.RelayDnsPacket(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -75,27 +74,3 @@ func hijackTCPDns(conn net.Conn) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func relayDnsPacket(payload []byte) ([]byte, error) {
|
|
||||||
msg := &D.Msg{}
|
|
||||||
if err := msg.Unpack(payload); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := resolver.ServeMsg(msg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ans := range r.Answer {
|
|
||||||
header := ans.Header()
|
|
||||||
|
|
||||||
if header.Class == D.ClassINET && (header.Rrtype == D.TypeA || header.Rrtype == D.TypeAAAA) {
|
|
||||||
header.Ttl = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SetRcode(msg, r.Rcode)
|
|
||||||
r.Compress = true
|
|
||||||
return r.Pack()
|
|
||||||
}
|
|
||||||
|
|
|
@ -22,6 +22,11 @@ func handleTCP(conn net.Conn, endpoint *binding.Endpoint, tcpIn chan<- C.ConnCon
|
||||||
Zone: "",
|
Zone: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addrType := C.AtypIPv4
|
||||||
|
if dst.IP.To4() == nil {
|
||||||
|
addrType = C.AtypIPv6
|
||||||
|
}
|
||||||
|
|
||||||
metadata := &C.Metadata{
|
metadata := &C.Metadata{
|
||||||
NetWork: C.TCP,
|
NetWork: C.TCP,
|
||||||
Type: C.TUN,
|
Type: C.TUN,
|
||||||
|
@ -29,7 +34,7 @@ func handleTCP(conn net.Conn, endpoint *binding.Endpoint, tcpIn chan<- C.ConnCon
|
||||||
DstIP: dst.IP,
|
DstIP: dst.IP,
|
||||||
SrcPort: strconv.Itoa(src.Port),
|
SrcPort: strconv.Itoa(src.Port),
|
||||||
DstPort: strconv.Itoa(dst.Port),
|
DstPort: strconv.Itoa(dst.Port),
|
||||||
AddrType: C.AtypIPv4,
|
AddrType: addrType,
|
||||||
Host: "",
|
Host: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/Dreamacro/clash/listener/tun/dev"
|
"github.com/Dreamacro/clash/listener/tun/dev"
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor"
|
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor"
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/ipstack/lwip"
|
||||||
"github.com/Dreamacro/clash/listener/tun/ipstack/system"
|
"github.com/Dreamacro/clash/listener/tun/ipstack/system"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
@ -33,7 +34,9 @@ func New(conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.Pack
|
||||||
return nil, errors.New("unable to get device mtu")
|
return nil, errors.New("unable to get device mtu")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.EqualFold(stack, "system") {
|
if strings.EqualFold(stack, "lwip") {
|
||||||
|
tunAdapter, err = lwip.NewAdapter(device, conf, mtu, tcpIn, udpIn)
|
||||||
|
} else if strings.EqualFold(stack, "system") {
|
||||||
tunAdapter, err = system.NewAdapter(device, conf, mtu, tunAddress, tunAddress, func() {}, tcpIn, udpIn)
|
tunAdapter, err = system.NewAdapter(device, conf, mtu, tunAddress, tunAddress, func() {}, tcpIn, udpIn)
|
||||||
} else if strings.EqualFold(stack, "gvisor") {
|
} else if strings.EqualFold(stack, "gvisor") {
|
||||||
tunAdapter, err = gvisor.NewAdapter(device, conf, tunAddress, tcpIn, udpIn)
|
tunAdapter, err = gvisor.NewAdapter(device, conf, tunAddress, tcpIn, udpIn)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user