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
|
||||
staticcheck -- $(go list ./...)
|
||||
|
||||
# init xgo
|
||||
docker pull techknowlogick/xgo:latest
|
||||
go install src.techknowlogick.com/xgo@latest
|
||||
|
||||
- name: SSH connection to Actions
|
||||
uses: P3TERX/ssh2actions@v1.0.0
|
||||
if: github.actor == github.repository_owner && contains(github.event.head_commit.message, '[ssh]')
|
||||
|
@ -43,15 +47,18 @@ jobs:
|
|||
env:
|
||||
NAME: clash
|
||||
BINDIR: bin
|
||||
run: make -j releases
|
||||
run: |
|
||||
make cleancache && make -j releases
|
||||
|
||||
- name: Prepare upload
|
||||
if: startsWith(github.ref, 'refs/tags/') == false
|
||||
run: |
|
||||
echo "FILE_DATE=_$(date +"%Y%m%d%H%M")" >> $GITHUB_ENV
|
||||
echo "FILE_SHA=$(git describe --tags --always 2>/dev/null)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: startsWith(github.ref, 'refs/tags/') == false
|
||||
with:
|
||||
name: clash_${{ env.FILE_SHA }}${{ env.FILE_DATE }}
|
||||
path: |
|
||||
|
@ -76,5 +83,6 @@ jobs:
|
|||
with:
|
||||
keep_latest: 1
|
||||
delete_tags: true
|
||||
delete_tag_pattern: premium
|
||||
env:
|
||||
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
|
||||
BINDIR=bin
|
||||
BINDIR=$(shell pwd)/bin
|
||||
VERSION=$(shell git describe --tags --always 2>/dev/null || date +%F)
|
||||
BUILDTIME=$(shell date -u)
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s -buildid='
|
||||
BUILD_PACKAGE=.
|
||||
RELEASE_LDFLAGS='-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-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 = \
|
||||
darwin-amd64 \
|
||||
darwin-arm64 \
|
||||
darwin-10.12-amd64 \
|
||||
darwin-10.15-arm64 \
|
||||
linux-386 \
|
||||
linux-amd64 \
|
||||
linux-armv5 \
|
||||
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
|
||||
linux-arm64
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
windows-amd64 \
|
||||
windows-arm64 \
|
||||
windows-arm32v7
|
||||
windows-4.0-amd64 \
|
||||
windows-4.0-386
|
||||
# windows-arm64
|
||||
|
||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||
all: linux-amd64 darwin-10.12-amd64 windows-4.0-amd64 # Most used
|
||||
|
||||
docker:
|
||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
build:
|
||||
$(GOBUILD) -ldflags $(RELEASE_LDFLAGS) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64:
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
darwin-10.12-amd64:
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.12/amd64 $(BUILD_PACKAGE)
|
||||
|
||||
darwin-arm64:
|
||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
darwin-10.15-arm64:
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=darwin-10.15/arm64 $(BUILD_PACKAGE)
|
||||
|
||||
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:
|
||||
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:
|
||||
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
linux-arm64:
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(STATIC_LDFLAGS) -targets=linux/arm64 $(BUILD_PACKAGE)
|
||||
|
||||
linux-armv6:
|
||||
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
windows-4.0-386:
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/386 $(BUILD_PACKAGE)
|
||||
|
||||
linux-armv7:
|
||||
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
windows-4.0-amd64:
|
||||
$(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows-4.0/amd64 $(BUILD_PACKAGE)
|
||||
|
||||
linux-armv8:
|
||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
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
|
||||
#windows-arm64:
|
||||
# $(XGOCMD) -dest=$(BINDIR) -out=$(NAME) -trimpath=true -ldflags=$(RELEASE_LDFLAGS) -targets=windows/arm64 $(BUILD_PACKAGE)
|
||||
|
||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||
|
@ -112,5 +70,13 @@ $(zip_releases): %.zip : %
|
|||
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||
|
||||
releases: $(gz_releases) $(zip_releases)
|
||||
|
||||
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
|
||||
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.
|
||||
```yaml
|
||||
# Enable the TUN listener
|
||||
tun:
|
||||
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
|
||||
auto-route: true # auto set global route
|
||||
```
|
||||
|
|
|
@ -183,7 +183,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||
ProxyGroup: []map[string]interface{}{},
|
||||
Tun: Tun{
|
||||
Enable: false,
|
||||
Stack: "system",
|
||||
Stack: "lwip",
|
||||
DNSListen: "0.0.0.0:53",
|
||||
AutoRoute: true,
|
||||
},
|
||||
|
|
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
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
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
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/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/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.27/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.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)
|
||||
} else {
|
||||
resolver.DefaultLocalServer = 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)
|
||||
if err != nil && !t.device.IsClose() {
|
||||
log.Errorln("can not read from tun: %v", err)
|
||||
continue
|
||||
}
|
||||
var p tcpip.NetworkProtocolNumber
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
|
||||
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"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) {
|
||||
go func() {
|
||||
answer, err := relayDnsPacket(pkt)
|
||||
answer, err := D.RelayDnsPacket(pkt)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -41,7 +38,9 @@ func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
|||
|
||||
func hijackTCPDns(conn net.Conn) {
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
defer func(conn net.Conn) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
|
||||
for {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||
|
@ -60,7 +59,7 @@ func hijackTCPDns(conn net.Conn) {
|
|||
return
|
||||
}
|
||||
|
||||
rb, err := relayDnsPacket(data)
|
||||
rb, err := D.RelayDnsPacket(data)
|
||||
if err != nil {
|
||||
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: "",
|
||||
}
|
||||
|
||||
addrType := C.AtypIPv4
|
||||
if dst.IP.To4() == nil {
|
||||
addrType = C.AtypIPv6
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.TUN,
|
||||
|
@ -29,7 +34,7 @@ func handleTCP(conn net.Conn, endpoint *binding.Endpoint, tcpIn chan<- C.ConnCon
|
|||
DstIP: dst.IP,
|
||||
SrcPort: strconv.Itoa(src.Port),
|
||||
DstPort: strconv.Itoa(dst.Port),
|
||||
AddrType: C.AtypIPv4,
|
||||
AddrType: addrType,
|
||||
Host: "",
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/Dreamacro/clash/listener/tun/dev"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
"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/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")
|
||||
}
|
||||
|
||||
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)
|
||||
} else if strings.EqualFold(stack, "gvisor") {
|
||||
tunAdapter, err = gvisor.NewAdapter(device, conf, tunAddress, tcpIn, udpIn)
|
||||
|
|
Loading…
Reference in New Issue
Block a user