diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index b5ce67f..c91b8ae 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -8,15 +8,15 @@ labels: kind/bug Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! --> -## What happened: +## What happened -## What you expected to happen: +## What you expected to happen ## How to reproduce it (as minimally and precisely as possible): ## Anything else we need to know -## Environment: +## Environment - Dae version (use `dae --version`): - OS (e.g `cat /etc/os-release`): diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 8c24108..b9608cb 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -16,4 +16,4 @@ You may also post your idea on the Discussions or the Dae Telegram channel (http ## What feature you would like us to integrate into the dae project -## Why is this needed: +## Why is this needed diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 247ee56..6018de5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,8 +4,6 @@ on: push: branches: - main - - fix* - - feat* paths: - "**/*.go" - "**/*.c" diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml index 421d9a0..3d76653 100644 --- a/.github/workflows/check-docs.yml +++ b/.github/workflows/check-docs.yml @@ -1,17 +1,13 @@ name: Check document on: - push: - branches: [main] - paths: - - 'README.md' - - 'docs/**' - - 'package.json' - - '.autocorrectrc' - - '.markdownlint-cli2.jsonc' - - '.github/workflows/check-docs.yml' + workflow_dispatch: + workflow_call: pull_request: branches: [main] + types: + - opened + - synchronize paths: - 'README.md' - 'docs/**' diff --git a/.github/workflows/sync-docs.yml b/.github/workflows/sync-docs.yml new file mode 100644 index 0000000..bce9f2d --- /dev/null +++ b/.github/workflows/sync-docs.yml @@ -0,0 +1,110 @@ +name: Synchronize document + +on: + workflow_dispatch: + push: + branches: + - main + - 'ci/**' + - 'release/**' + tags: + - 'v*' + paths: + - "README*.md" + - "docs/**" + - "example.dae" + - "package.json" + - ".autocorrectrc" + - ".markdownlint-cli2.jsonc" + - '.github/workflows/sync-docs.yml' + +jobs: + check-docs: + name: Check document + uses: ./.github/workflows/check-docs.yml + + replicate-config: + needs: [check-docs] + name: Replicate relevant document + runs-on: ubuntu-latest + outputs: + git_sha_long: ${{ steps.export.outputs.git_sha_long }} + git_sha_short: ${{ steps.export.outputs.git_sha_short }} + git_commit_msg: ${{ steps.export.outputs.git_commit_msg }} + git_run_number: ${{ steps.export.outputs.git_run_number }} + steps: + - uses: actions/checkout@v3 + - name: Get metadata from HEAD sha + id: export + run: | + echo "git_sha_long=${{ github.sha }}" >> $GITHUB_OUTPUT + echo "git_sha_short=$(echo ${{ github.sha }} | cut -c1-6)" >> $GITHUB_OUTPUT + echo "git_commit_msg=$(git log --format=%s -n 1 ${{ github.sha }})" >> $GITHUB_OUTPUT + echo "git_run_number=${{ github.run_number }}" >> $GITHUB_OUTPUT + + - name: Generate artifacts + run: | + python3 hack/ci/config-doc-generator.py + + - name: Export artifacts + uses: actions/upload-artifact@v3 + with: + name: pre_flight_artifacts + path: | + ./docs/sync/*.md + + sync-to-dae-docs: + needs: [replicate-config] + name: Synchronize changes to dae-docs + runs-on: ubuntu-latest + steps: + - name: Checkout to daeuniverse/dae-docs + uses: actions/checkout@v3 + with: + repository: daeuniverse/dae-docs + fetch-depth: 0 + ref: master + + - name: Copy artifacts from build job to local path + uses: actions/download-artifact@v3 + with: + name: pre_flight_artifacts + path: docs/ + + - name: Create commits + run: | + git config user.name 'daebot' + git config user.email 'daebot@users.noreply.github.com' + git ls-files --others --exclude-standard -- "*.md" + git add "*.md" + git commit -m "ci(auto-release): push proposed documentation changes from upstream dae project" + + - name: Create Pull Request + id: create_pr + uses: peter-evans/create-pull-request@v5 + with: + author: daebot + committer: daebot + token: ${{ secrets.GH_TOKEN }} + signoff: false + branch: automated-pr-sync + delete-branch: true + title: 'ci(auto-release): sync changes from upstream' + body: | + ## Summary + + Update documentation - Auto-generated by [${{ github.repository }} #${{ needs.replicate-config.outputs.git_run_number }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) @daebot + + Commit message: ${{ needs.replicate-config.outputs.git_commit_msg }} + + ## Context + + 🏗 daeuniverse/dae(${{ needs.replicate-config.outputs.git_sha_short }}): Build [#${{ needs.replicate-config.outputs.git_run_number }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) succeeded! + + labels: | + automated-pr + assignees: daebot + team-reviewers: | + docs + draft: false + diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 5cfdb02..eb9a796 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -12,9 +12,9 @@ curl --silent "https://api.github.com/repos/daeuniverse/dae/releases" | jq -r '. ## Releases -- [0.1.10rc1 (Pre-release)](#0110rc1-pre-release) -- [0.1.10rc (Pre-release)](#0110rc-pre-release) -- [0.1.9-patch.1 (Current)](#019-patch1-current) +- [0.2.0rc1 (Pre-release)](#020rc1-pre-release) +- [0.1.10 (Current)](#0110-current) +- [0.1.9-patch.1](#019-patch1) - [0.1.9](#019) - [0.1.8](#018) - [0.1.7](#017) @@ -26,29 +26,68 @@ curl --silent "https://api.github.com/repos/daeuniverse/dae/releases" | jq -r '. - [0.1.1](#011) - [0.1.0](#010) -### 0.1.10rc1 (Pre-release) +### 0.2.0rc1 (Pre-release) -> Release date: 2023/05/15 +> Release date: 2023/06/04 #### 功能变更 -- patch(geodata): 修复由 #84 导致的错误的 geodata 搜索路径 `/etc/dae/dae` by @mzz2017 in https://github.com/daeuniverse/dae/pull/90 +- feat: 支持 iptables/nftables 的 mangle 表 tproxy by @mzz2017 in https://github.com/daeuniverse/dae/pull/80 +- feat: 支持 uTLS by @AkinoKaede in https://github.com/daeuniverse/dae/pull/94 +- feat: 支持在 geosite 使用属性标签 `@` 符号 by @mzz2017 in https://github.com/daeuniverse/dae/pull/98 +- feat(dns): 支持为特定域名设定固定的 ttl,这对 DDNS 场景较为有用 by @mzz2017 in https://github.com/daeuniverse/dae/pull/100 +- fix(dns): 修复 DNS 中 qname 匹配规则失效的问题 by @mzz2017 in https://github.com/daeuniverse/dae/pull/99 +- fix: 修复启动时网络检查链接列表的随机排布问题 by @mzz2017 in https://github.com/daeuniverse/dae/pull/106 +- fix(config_parser): 修复配置文件格式错误时潜在的崩溃问题 by @mzz2017 in https://github.com/daeuniverse/dae/pull/108 +- fix(trojan): 修复 trojan 崩溃问题,该问题由 ReadFrom 返回的 n 可能不正确导致 by @mzz2017 in https://github.com/daeuniverse/dae/pull/109 + +#### 其他变更 + +- ci: 添加文档格式检查工作流 by @yqlbu in https://github.com/daeuniverse/dae/pull/93 +- refactor: 将 insert.sh 移动至 ./hack/test by @yqlbu in https://github.com/daeuniverse/dae/pull/95 +- ci(hack): 添加 config-doc-generator by @yqlbu in https://github.com/daeuniverse/dae/pull/101 +- fix(test): 修复 domain_matcher/benchmark_test.go in https://github.com/daeuniverse/dae/pull/107 +- ci: 添加文档自动同步至 dae-docs 项目 by @yqlbu in https://github.com/daeuniverse/dae/pull/103 +- docs(routing.md): 修订 fwmark 一节的文档 by @mzz2017 in https://github.com/daeuniverse/dae/pull/113 #### Changes -- patch(geodata): fix incorrect geodata search path `/etc/dae/dae` caused by #84 by @mzz2017 in https://github.com/daeuniverse/dae/pull/90 +- feat: support iptables tproxy by @mzz2017 in https://github.com/daeuniverse/dae/pull/80 +- feat: add uTLS support by @AkinoKaede in https://github.com/daeuniverse/dae/pull/94 +- feat: support geosite attr by @mzz2017 in https://github.com/daeuniverse/dae/pull/98 +- fix(dns): mismatched qname matching rules by @mzz2017 in https://github.com/daeuniverse/dae/pull/99 +- feat(dns): support fixed domain ttl by @mzz2017 in https://github.com/daeuniverse/dae/pull/100 +- fix: rand seed for network check by @mzz2017 in https://github.com/daeuniverse/dae/pull/106 +- fix(config_parser): potential panic due to out of index by @mzz2017 in https://github.com/daeuniverse/dae/pull/108 +- fix(trojan): potential panic due to incorrect n returned by ReadFrom by @mzz2017 in https://github.com/daeuniverse/dae/pull/109 -**Full Changelog**: https://github.com/daeuniverse/dae/compare/v0.1.10rc...v0.1.10rc1 +#### Other Changes -### 0.1.10rc (Pre-release) +- ci: add check-docs workflow by @yqlbu in https://github.com/daeuniverse/dae/pull/93 +- refactor: move insert.sh to ./hack/test by @yqlbu in https://github.com/daeuniverse/dae/pull/95 +- ci(hack): add config-doc-generator by @yqlbu in https://github.com/daeuniverse/dae/pull/101 +- fix(test): domain_matcher/benchmark_test.go in https://github.com/daeuniverse/dae/pull/107 +- ci: docs synchronization by @yqlbu in https://github.com/daeuniverse/dae/pull/103 +- docs(routing.md): revise fwmark section by @mzz2017 in https://github.com/daeuniverse/dae/pull/113 -> Release date: 2023/05/14 +#### New Contributors + +- @AkinoKaede made their first contribution in https://github.com/daeuniverse/dae/pull/94 + +**Full Changelog**: https://github.com/daeuniverse/dae/compare/v0.1.10...v0.2.0rc1 + +**Example Config**: https://github.com/daeuniverse/dae/blob/v0.2.0rc1/example.dae + +### 0.1.10 (Current) + +> Release date: 2023/06/04 #### 功能变更 - feat: 支持 `tcp_check_http_method` by @mzz2017 in https://github.com/daeuniverse/dae/pull/77 - patch: 现在会优先在配置文件同目录搜索 geodata by @mzz2017 in https://github.com/daeuniverse/dae/pull/84 - fix(dns): 修复 0.1.8 版本中 PR #63 导致的 DNS 缓存不会过期的问题 by @mzz2017 in https://github.com/daeuniverse/dae/pull/87 +- patch(geodata): 修复由 #84 导致的错误的 geodata 搜索路径 `/etc/dae/dae` by @mzz2017 in https://github.com/daeuniverse/dae/pull/90 #### 其他变更 @@ -61,6 +100,7 @@ curl --silent "https://api.github.com/repos/daeuniverse/dae/releases" | jq -r '. - feat: support `tcp_check_http_method` by @mzz2017 in https://github.com/daeuniverse/dae/pull/77 - patch: search geodata at same dir with config first by @mzz2017 in https://github.com/daeuniverse/dae/pull/84 - fix(dns): cache would never expire caused by #63 by accident by @mzz2017 in https://github.com/daeuniverse/dae/pull/87 +- patch(geodata): fix incorrect geodata search path `/etc/dae/dae` caused by #84 by @mzz2017 in https://github.com/daeuniverse/dae/pull/90 #### Other Changes @@ -68,9 +108,11 @@ curl --silent "https://api.github.com/repos/daeuniverse/dae/releases" | jq -r '. - chore: add editorconfig by @yqlbu in https://github.com/daeuniverse/dae/pull/85 - chore: add pull_request_template by @yqlbu in https://github.com/daeuniverse/dae/pull/86 -**Full Changelog**: https://github.com/daeuniverse/dae/compare/v0.1.9...v0.1.10rc +**Full Changelog**: https://github.com/daeuniverse/dae/compare/v0.1.9...v0.1.10 -### 0.1.9-patch.1 (Current) +**Example Config**: https://github.com/daeuniverse/dae/blob/v0.1.10/example.dae + +### 0.1.9-patch.1 > Release date: 2023/05/14 diff --git a/Makefile b/Makefile index 009f38b..8e4511c 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ else STRIP_FLAG := -strip=$(STRIP_PATH) endif +# Do NOT remove the line below. This line is for CI. #export GOMODCACHE=$(PWD)/go-mod # Get version from .git. @@ -41,7 +42,7 @@ dae: export GOOS=linux dae: ebpf go build -o $(OUTPUT) -trimpath -ldflags "-s -w -X github.com/daeuniverse/dae/cmd.Version=$(VERSION) -X github.com/daeuniverse/dae/common/consts.MaxMatchSetLen_=$(MAX_MATCH_SET_LEN)" . -clean-ebpf: +clean-ebpf: @rm -f control/bpf_bpf*.go && \ rm -f control/bpf_bpf*.o fmt: diff --git a/README.md b/README.md index 3d89d85..00e2659 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

License - version + version lastcommit

diff --git a/cmd/run.go b/cmd/run.go index b248be6..f50ea02 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,9 +1,12 @@ package cmd import ( + "context" "errors" "fmt" + "github.com/mzz2017/softwind/netproxy" "github.com/mzz2017/softwind/pkg/fastrand" + "github.com/mzz2017/softwind/protocol/direct" "net" "net/http" "os" @@ -247,6 +250,20 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c if !conf.Global.DisableWaitingNetwork && len(conf.Subscription) > 0 { epo := 5 * time.Second client := http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) { + cd := netproxy.ContextDialer{Dialer: direct.SymmetricDirect} + conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", conf.Global.SoMarkFromDae), addr) + if err != nil { + return nil, err + } + return &netproxy.FakeNetConn{ + Conn: conn, + LAddr: nil, + RAddr: nil, + }, nil + }, + }, Timeout: epo, } log.Infoln("Waiting for network...") @@ -274,8 +291,25 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c if len(conf.Subscription) > 0 { log.Infoln("Fetching subscriptions...") } + client := http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) { + cd := netproxy.ContextDialer{Dialer: direct.SymmetricDirect} + conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", conf.Global.SoMarkFromDae), addr) + if err != nil { + return nil, err + } + return &netproxy.FakeNetConn{ + Conn: conn, + LAddr: nil, + RAddr: nil, + }, nil + }, + }, + Timeout: 30 * time.Second, + } for _, sub := range conf.Subscription { - tag, nodes, err := subscription.ResolveSubscription(log, filepath.Dir(cfgFile), string(sub)) + tag, nodes, err := subscription.ResolveSubscription(log, &client, filepath.Dir(cfgFile), string(sub)) if err != nil { log.Warnf(`failed to resolve subscription "%v": %v`, sub, err) resolvingfailed = true diff --git a/common/consts/ebpf.go b/common/consts/ebpf.go index f532ccb..586cc64 100644 --- a/common/consts/ebpf.go +++ b/common/consts/ebpf.go @@ -146,6 +146,7 @@ var ( const ( TproxyMark uint32 = 0x8000000 + Recognize uint16 = 0x2017 LoopbackIfIndex = 1 ) diff --git a/common/netutils/dns.go b/common/netutils/dns.go index f863d5a..b38e450 100644 --- a/common/netutils/dns.go +++ b/common/netutils/dns.go @@ -19,7 +19,6 @@ import ( "github.com/mzz2017/softwind/netproxy" "github.com/mzz2017/softwind/pkg/fastrand" "github.com/mzz2017/softwind/pool" - "github.com/sirupsen/logrus" "golang.org/x/net/dns/dnsmessage" ) @@ -91,8 +90,8 @@ func SystemDns() (dns netip.AddrPort, err error) { return systemDns, nil } -func ResolveNetip(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host string, typ dnsmessage.Type, tcp bool) (addrs []netip.Addr, err error) { - resources, err := resolve(ctx, d, dns, host, typ, tcp) +func ResolveNetip(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host string, typ dnsmessage.Type, network string) (addrs []netip.Addr, err error) { + resources, err := resolve(ctx, d, dns, host, typ, network) if err != nil { return nil, err } @@ -118,16 +117,14 @@ func ResolveNetip(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, ho return addrs, nil } -func ResolveNS(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host string, tcp bool) (records []string, err error) { +func ResolveNS(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host string, network string) (records []string, err error) { typ := dnsmessage.TypeNS - resources, err := resolve(ctx, d, dns, host, typ, tcp) + resources, err := resolve(ctx, d, dns, host, typ, network) if err != nil { return nil, err } - logrus.Println(host, len(resources)) for _, ans := range resources { if ans.Header.Type != typ { - logrus.Println(host, ans.Header.Type) continue } ns, ok := ans.Body.(*dnsmessage.NSResource) @@ -139,7 +136,7 @@ func ResolveNS(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host return records, nil } -func resolve(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host string, typ dnsmessage.Type, tcp bool) (ans []dnsmessage.Resource, err error) { +func resolve(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host string, typ dnsmessage.Type, network string) (ans []dnsmessage.Resource, err error) { ctx, cancel := context.WithCancel(ctx) defer cancel() fqdn := host @@ -202,7 +199,11 @@ func resolve(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host st if err != nil { return nil, err } - if tcp { + magicNetwork, err := netproxy.ParseMagicNetwork(network) + if err != nil { + return nil, err + } + if magicNetwork.Network == "tcp" { // Put DNS request length buf := pool.Get(2 + len(b)) defer pool.Put(buf) @@ -213,12 +214,7 @@ func resolve(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host st // Dial and write. cd := &netproxy.ContextDialer{Dialer: d} - var c netproxy.Conn - if tcp { - c, err = cd.DialTcpContext(ctx, dns.String()) - } else { - c, err = cd.DialUdpContext(ctx, dns.String()) - } + c, err := cd.DialContext(ctx, network, dns.String()) if err != nil { return nil, err } @@ -228,7 +224,7 @@ func resolve(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host st return nil, err } ch := make(chan error, 2) - if !tcp { + if magicNetwork.Network == "udp" { go func() { // Resend every 3 seconds for UDP. for { @@ -249,7 +245,7 @@ func resolve(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host st go func() { buf := pool.Get(512) defer pool.Put(buf) - if tcp { + if magicNetwork.Network == "tcp" { // Read DNS response length _, err := io.ReadFull(c, buf[:2]) if err != nil { diff --git a/common/netutils/ip46.go b/common/netutils/ip46.go index f9a7504..6d2f007 100644 --- a/common/netutils/ip46.go +++ b/common/netutils/ip46.go @@ -22,7 +22,7 @@ type Ip46 struct { Ip6 netip.Addr } -func ResolveIp46(ctx context.Context, dialer netproxy.Dialer, dns netip.AddrPort, host string, tcp bool, race bool) (ipv46 *Ip46, err error) { +func ResolveIp46(ctx context.Context, dialer netproxy.Dialer, dns netip.AddrPort, host string, network string, race bool) (ipv46 *Ip46, err error) { var log *logrus.Logger if _log := ctx.Value("logger"); _log != nil { log = _log.(*logrus.Logger) @@ -49,7 +49,7 @@ func ResolveIp46(ctx context.Context, dialer netproxy.Dialer, dns netip.AddrPort } }() var e error - addrs4, e = ResolveNetip(ctx4, dialer, dns, host, dnsmessage.TypeA, tcp) + addrs4, e = ResolveNetip(ctx4, dialer, dns, host, dnsmessage.TypeA, network) if err != nil && !errors.Is(e, context.Canceled) { err4 = e return @@ -67,7 +67,7 @@ func ResolveIp46(ctx context.Context, dialer netproxy.Dialer, dns netip.AddrPort } }() var e error - addrs6, e = ResolveNetip(ctx6, dialer, dns, host, dnsmessage.TypeAAAA, tcp) + addrs6, e = ResolveNetip(ctx6, dialer, dns, host, dnsmessage.TypeAAAA, network) if err != nil && !errors.Is(e, context.Canceled) { err6 = e return diff --git a/common/subscription/subscription.go b/common/subscription/subscription.go index b187d99..d8b665c 100644 --- a/common/subscription/subscription.go +++ b/common/subscription/subscription.go @@ -137,7 +137,7 @@ func ResolveFile(u *url.URL, configDir string) (b []byte, err error) { return bytes.TrimSpace(b), err } -func ResolveSubscription(log *logrus.Logger, configDir string, subscription string) (tag string, nodes []string, err error) { +func ResolveSubscription(log *logrus.Logger, client *http.Client, configDir string, subscription string) (tag string, nodes []string, err error) { /// Get tag. tag, subscription = common.GetTagFromLinkLikePlaintext(subscription) @@ -160,7 +160,7 @@ func ResolveSubscription(log *logrus.Logger, configDir string, subscription stri goto resolve default: } - resp, err = http.Get(subscription) + resp, err = client.Get(subscription) if err != nil { return "", nil, err } diff --git a/common/utils.go b/common/utils.go index f3ee518..2067903 100644 --- a/common/utils.go +++ b/common/utils.go @@ -12,6 +12,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "github.com/mzz2017/softwind/netproxy" "net/netip" "net/url" "path/filepath" @@ -221,25 +222,25 @@ func FuzzyDecode(to interface{}, val string) bool { v := reflect.Indirect(reflect.ValueOf(to)) switch v.Kind() { case reflect.Int: - i, err := strconv.ParseInt(val, 10, strconv.IntSize) + i, err := strconv.ParseInt(val, 0, strconv.IntSize) if err != nil { return false } v.SetInt(i) case reflect.Int8: - i, err := strconv.ParseInt(val, 10, 8) + i, err := strconv.ParseInt(val, 0, 8) if err != nil { return false } v.SetInt(i) case reflect.Int16: - i, err := strconv.ParseInt(val, 10, 16) + i, err := strconv.ParseInt(val, 0, 16) if err != nil { return false } v.SetInt(i) case reflect.Int32: - i, err := strconv.ParseInt(val, 10, 32) + i, err := strconv.ParseInt(val, 0, 32) if err != nil { return false } @@ -253,38 +254,38 @@ func FuzzyDecode(to interface{}, val string) bool { } v.Set(reflect.ValueOf(duration)) default: - i, err := strconv.ParseInt(val, 10, 64) + i, err := strconv.ParseInt(val, 0, 64) if err != nil { return false } v.SetInt(i) } case reflect.Uint: - i, err := strconv.ParseUint(val, 10, strconv.IntSize) + i, err := strconv.ParseUint(val, 0, strconv.IntSize) if err != nil { return false } v.SetUint(i) case reflect.Uint8: - i, err := strconv.ParseUint(val, 10, 8) + i, err := strconv.ParseUint(val, 0, 8) if err != nil { return false } v.SetUint(i) case reflect.Uint16: - i, err := strconv.ParseUint(val, 10, 16) + i, err := strconv.ParseUint(val, 0, 16) if err != nil { return false } v.SetUint(i) case reflect.Uint32: - i, err := strconv.ParseUint(val, 10, 32) + i, err := strconv.ParseUint(val, 0, 32) if err != nil { return false } v.SetUint(i) case reflect.Uint64: - i, err := strconv.ParseUint(val, 10, 64) + i, err := strconv.ParseUint(val, 0, 64) if err != nil { return false } @@ -458,6 +459,17 @@ nextLink: return Deduplicate(defaultIfs), nil } +func MagicNetwork(network string, mark uint32) string { + if mark == 0 { + return network + } else { + return netproxy.MagicNetwork{ + Network: network, + Mark: mark, + }.Encode() + } +} + func IsValidHttpMethod(method string) bool { switch method { case "GET", "POST", "PUT", "PATCH", "DELETE", "COPY", "HEAD", "OPTIONS", "LINK", "UNLINK", "PURGE", "LOCK", "UNLOCK", "PROPFIND", "CONNECT", "TRACE": diff --git a/component/dns/dns.go b/component/dns/dns.go index 5ff6153..f997dd0 100644 --- a/component/dns/dns.go +++ b/component/dns/dns.go @@ -32,9 +32,10 @@ type Dns struct { } type NewOption struct { - Logger *logrus.Logger - LocationFinder *assets.LocationFinder - UpstreamReadyCallback func(dnsUpstream *Upstream) (err error) + Logger *logrus.Logger + LocationFinder *assets.LocationFinder + UpstreamReadyCallback func(dnsUpstream *Upstream) (err error) + UpstreamResolverNetwork string } func New(dns *config.Dns, opt *NewOption) (s *Dns, err error) { @@ -62,7 +63,8 @@ func New(dns *config.Dns, opt *NewOption) (s *Dns, err error) { return nil, fmt.Errorf("%w: %v", BadUpstreamFormatError, err) } r := &UpstreamResolver{ - Raw: u, + Raw: u, + Network: opt.UpstreamResolverNetwork, FinishInitCallback: func(i int) func(raw *url.URL, upstream *Upstream) (err error) { return func(raw *url.URL, upstream *Upstream) (err error) { if opt != nil && opt.UpstreamReadyCallback != nil { @@ -77,6 +79,9 @@ func New(dns *config.Dns, opt *NewOption) (s *Dns, err error) { return nil } }(i), + mu: sync.Mutex{}, + upstream: nil, + init: false, } upstreamName2Id[tag] = uint8(len(s.upstream)) s.upstream = append(s.upstream, r) diff --git a/component/dns/upstream.go b/component/dns/upstream.go index f8203fa..348db5a 100644 --- a/component/dns/upstream.go +++ b/component/dns/upstream.go @@ -72,7 +72,7 @@ type Upstream struct { *netutils.Ip46 } -func NewUpstream(ctx context.Context, upstream *url.URL) (up *Upstream, err error) { +func NewUpstream(ctx context.Context, upstream *url.URL, resolverNetwork string) (up *Upstream, err error) { scheme, hostname, port, err := ParseRawUpstream(upstream) if err != nil { return nil, fmt.Errorf("%w: %v", FormatError, err) @@ -88,7 +88,7 @@ func NewUpstream(ctx context.Context, upstream *url.URL) (up *Upstream, err erro } }() - ip46, err := netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, hostname, false, false) + ip46, err := netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, hostname, resolverNetwork, false) if err != nil { return nil, fmt.Errorf("failed to resolve dns_upstream: %w", err) } @@ -131,7 +131,8 @@ func (u *Upstream) String() string { } type UpstreamResolver struct { - Raw *url.URL + Raw *url.URL + Network string // FinishInitCallback may be invoked again if err is not nil FinishInitCallback func(raw *url.URL, upstream *Upstream) (err error) mu sync.Mutex @@ -154,7 +155,7 @@ func (u *UpstreamResolver) GetUpstream() (_ *Upstream, err error) { }() ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) defer cancel() - if u.upstream, err = NewUpstream(ctx, u.Raw); err != nil { + if u.upstream, err = NewUpstream(ctx, u.Raw, u.Network); err != nil { return nil, fmt.Errorf("failed to init dns upstream: %w", err) } } diff --git a/component/outbound/dialer/connectivity_check.go b/component/outbound/dialer/connectivity_check.go index 77f9443..2bfb474 100644 --- a/component/outbound/dialer/connectivity_check.go +++ b/component/outbound/dialer/connectivity_check.go @@ -9,6 +9,7 @@ import ( "context" "errors" "fmt" + "github.com/daeuniverse/dae/common" "net" "net/http" "net/netip" @@ -121,7 +122,7 @@ type TcpCheckOption struct { Method string } -func ParseTcpCheckOption(ctx context.Context, rawURL []string, method string) (opt *TcpCheckOption, err error) { +func ParseTcpCheckOption(ctx context.Context, rawURL []string, method string, resolverNetwork string) (opt *TcpCheckOption, err error) { if method == "" { method = http.MethodGet } @@ -146,7 +147,7 @@ func ParseTcpCheckOption(ctx context.Context, rawURL []string, method string) (o if len(rawURL) > 1 { ip46 = parseIp46FromList(rawURL[1:]) } else { - ip46, err = netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, u.Hostname(), false, false) + ip46, err = netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, u.Hostname(), resolverNetwork, false) if err != nil { return nil, err } @@ -164,7 +165,7 @@ type CheckDnsOption struct { *netutils.Ip46 } -func ParseCheckDnsOption(ctx context.Context, dnsHostPort []string) (opt *CheckDnsOption, err error) { +func ParseCheckDnsOption(ctx context.Context, dnsHostPort []string, resolverNetwork string) (opt *CheckDnsOption, err error) { systemDns, err := netutils.SystemDns() if err != nil { return nil, err @@ -191,7 +192,7 @@ func ParseCheckDnsOption(ctx context.Context, dnsHostPort []string) (opt *CheckD if len(dnsHostPort) > 1 { ip46 = parseIp46FromList(dnsHostPort[1:]) } else { - ip46, err = netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, host, false, false) + ip46, err = netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, host, resolverNetwork, false) if err != nil { return nil, err } @@ -204,11 +205,12 @@ func ParseCheckDnsOption(ctx context.Context, dnsHostPort []string) (opt *CheckD } type TcpCheckOptionRaw struct { - opt *TcpCheckOption - mu sync.Mutex - Log *logrus.Logger - Raw []string - Method string + opt *TcpCheckOption + mu sync.Mutex + Log *logrus.Logger + Raw []string + ResolverNetwork string + Method string } func (c *TcpCheckOptionRaw) Option() (opt *TcpCheckOption, err error) { @@ -218,7 +220,7 @@ func (c *TcpCheckOptionRaw) Option() (opt *TcpCheckOption, err error) { ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) defer cancel() ctx = context.WithValue(ctx, "logger", c.Log) - tcpCheckOption, err := ParseTcpCheckOption(ctx, c.Raw, c.Method) + tcpCheckOption, err := ParseTcpCheckOption(ctx, c.Raw, c.Method, c.ResolverNetwork) if err != nil { return nil, fmt.Errorf("failed to parse tcp_check_url: %w", err) } @@ -228,9 +230,10 @@ func (c *TcpCheckOptionRaw) Option() (opt *TcpCheckOption, err error) { } type CheckDnsOptionRaw struct { - opt *CheckDnsOption - mu sync.Mutex - Raw []string + opt *CheckDnsOption + mu sync.Mutex + Raw []string + ResolverNetwork string } func (c *CheckDnsOptionRaw) Option() (opt *CheckDnsOption, err error) { @@ -239,7 +242,7 @@ func (c *CheckDnsOptionRaw) Option() (opt *CheckDnsOption, err error) { if c.opt == nil { ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) defer cancel() - udpCheckOption, err := ParseCheckDnsOption(ctx, c.Raw) + udpCheckOption, err := ParseCheckDnsOption(ctx, c.Raw, c.ResolverNetwork) if err != nil { return nil, fmt.Errorf("failed to parse tcp_check_url: %w", err) } @@ -266,6 +269,10 @@ func (d *Dialer) ActivateCheck() { func (d *Dialer) aliveBackground() { timeout := 10 * time.Second cycle := d.CheckInterval + var tcpSomark uint32 + if network, err := netproxy.ParseMagicNetwork(d.TcpCheckOptionRaw.ResolverNetwork); err == nil { + tcpSomark = network.Mark + } tcp4CheckOpt := &CheckOption{ networkType: &NetworkType{ L4Proto: consts.L4ProtoStr_TCP, @@ -285,7 +292,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.HttpCheck(ctx, opt.Url, opt.Ip4, opt.Method) + return d.HttpCheck(ctx, opt.Url, opt.Ip4, opt.Method, tcpSomark) }, } tcp6CheckOpt := &CheckOption{ @@ -307,7 +314,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.HttpCheck(ctx, opt.Url, opt.Ip6, opt.Method) + return d.HttpCheck(ctx, opt.Url, opt.Ip6, opt.Method, tcpSomark) }, } tcp4CheckDnsOpt := &CheckOption{ @@ -329,7 +336,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip4, opt.DnsPort), true) + return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip4, opt.DnsPort), d.CheckDnsOptionRaw.ResolverNetwork) }, } tcp6CheckDnsOpt := &CheckOption{ @@ -351,7 +358,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip6, opt.DnsPort), true) + return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip6, opt.DnsPort), d.CheckDnsOptionRaw.ResolverNetwork) }, } udp4CheckDnsOpt := &CheckOption{ @@ -372,7 +379,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip4, opt.DnsPort), false) + return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip4, opt.DnsPort), d.CheckDnsOptionRaw.ResolverNetwork) }, } udp6CheckDnsOpt := &CheckOption{ @@ -393,7 +400,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip6, opt.DnsPort), false) + return d.DnsCheck(ctx, netip.AddrPortFrom(opt.Ip6, opt.DnsPort), d.CheckDnsOptionRaw.ResolverNetwork) }, } var CheckOpts = []*CheckOption{ @@ -535,7 +542,7 @@ func (d *Dialer) Check(timeout time.Duration, return ok, err } -func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, method string) (ok bool, err error) { +func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, method string, soMark uint32) (ok bool, err error) { // HTTP(S) check. if method == "" { method = http.MethodGet @@ -545,7 +552,7 @@ func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) { // Force to dial "ip". - conn, err := cd.DialTcpContext(ctx, net.JoinHostPort(ip.String(), u.Port())) + conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", soMark), net.JoinHostPort(ip.String(), u.Port())) if err != nil { return nil, err } @@ -584,8 +591,8 @@ func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, } } -func (d *Dialer) DnsCheck(ctx context.Context, dns netip.AddrPort, tcp bool) (ok bool, err error) { - addrs, err := netutils.ResolveNetip(ctx, d, dns, consts.UdpCheckLookupHost, dnsmessage.TypeA, tcp) +func (d *Dialer) DnsCheck(ctx context.Context, dns netip.AddrPort, network string) (ok bool, err error) { + addrs, err := netutils.ResolveNetip(ctx, d, dns, consts.UdpCheckLookupHost, dnsmessage.TypeA, network) if err != nil { return false, err } diff --git a/component/outbound/dialer/trojan/trojan.go b/component/outbound/dialer/trojan/trojan.go index f53c6d6..cfad0f3 100644 --- a/component/outbound/dialer/trojan/trojan.go +++ b/component/outbound/dialer/trojan/trojan.go @@ -2,6 +2,7 @@ package trojan import ( "fmt" + "github.com/daeuniverse/dae/component/outbound/transport/tls" "net" "net/url" "strconv" @@ -9,7 +10,6 @@ import ( "github.com/daeuniverse/dae/common" "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/dae/component/outbound/transport/tls" "github.com/daeuniverse/dae/component/outbound/transport/ws" "github.com/mzz2017/softwind/netproxy" "github.com/mzz2017/softwind/protocol" diff --git a/component/outbound/transport/simpleobfs/simpleobfs.go b/component/outbound/transport/simpleobfs/simpleobfs.go index cbe3317..aa267fd 100644 --- a/component/outbound/transport/simpleobfs/simpleobfs.go +++ b/component/outbound/transport/simpleobfs/simpleobfs.go @@ -63,38 +63,28 @@ func (s *SimpleObfs) Dial(network, addr string) (c netproxy.Conn, err error) { } switch magicNetwork.Network { case "tcp": - return s.DialTcp(addr) + rc, err := s.dialer.Dial(network, s.addr) + if err != nil { + return nil, fmt.Errorf("[simpleobfs]: dial to %s: %w", s.addr, err) + } + + host, port, err := net.SplitHostPort(s.addr) + if err != nil { + return nil, err + } + if s.host != "" { + host = s.host + } + switch s.obfstype { + case HTTP: + c = NewHTTPObfs(rc, host, port, s.path) + case TLS: + c = NewTLSObfs(rc, host) + } + return c, err case "udp": - return s.DialUdp(addr) + return nil, fmt.Errorf("%w: simpleobfs+udp", netproxy.UnsupportedTunnelTypeError) default: return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network) } } - -func (s *SimpleObfs) DialUdp(addr string) (conn netproxy.PacketConn, err error) { - return nil, fmt.Errorf("%w: simpleobfs+udp", netproxy.UnsupportedTunnelTypeError) -} - -// DialTcp connects to the address addr on the network net via the proxy. -func (s *SimpleObfs) DialTcp(addr string) (c netproxy.Conn, err error) { - - rc, err := s.dialer.DialTcp(s.addr) - if err != nil { - return nil, fmt.Errorf("[simpleobfs]: dial to %s: %w", s.addr, err) - } - - host, port, err := net.SplitHostPort(s.addr) - if err != nil { - return nil, err - } - if s.host != "" { - host = s.host - } - switch s.obfstype { - case HTTP: - c = NewHTTPObfs(rc, host, port, s.path) - case TLS: - c = NewTLSObfs(rc, host) - } - return c, err -} diff --git a/component/outbound/transport/tls/tls.go b/component/outbound/transport/tls/tls.go index 7546688..9ccf697 100644 --- a/component/outbound/transport/tls/tls.go +++ b/component/outbound/transport/tls/tls.go @@ -61,55 +61,47 @@ func (s *Tls) Dial(network, addr string) (c netproxy.Conn, err error) { } switch magicNetwork.Network { case "tcp": - return s.DialTcp(addr) + rc, err := s.dialer.Dial(network, addr) + if err != nil { + return nil, fmt.Errorf("[Tls]: dial to %s: %w", s.addr, err) + } + + var tlsConn interface { + netproxy.Conn + Handshake() error + } + + switch s.tlsImplentation { + case "tls": + tlsConn = tls.Client(&netproxy.FakeNetConn{ + Conn: rc, + LAddr: nil, + RAddr: nil, + }, s.tlsConfig) + + case "utls": + clientHelloID, err := nameToUtlsClientHelloID(s.utlsImitate) + if err != nil { + return nil, err + } + + tlsConn = utls.UClient(&netproxy.FakeNetConn{ + Conn: rc, + LAddr: nil, + RAddr: nil, + }, uTLSConfigFromTLSConfig(s.tlsConfig), *clientHelloID) + + default: + return nil, fmt.Errorf("unknown tls implementation: %v", s.tlsImplentation) + } + + if err := tlsConn.Handshake(); err != nil { + return nil, err + } + return tlsConn, err case "udp": - return s.DialUdp(addr) + return nil, fmt.Errorf("%w: tls+udp", netproxy.UnsupportedTunnelTypeError) default: return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network) } } - -func (s *Tls) DialUdp(addr string) (conn netproxy.PacketConn, err error) { - return nil, fmt.Errorf("%w: tls+udp", netproxy.UnsupportedTunnelTypeError) -} - -func (s *Tls) DialTcp(addr string) (conn netproxy.Conn, err error) { - rc, err := s.dialer.DialTcp(addr) - if err != nil { - return nil, fmt.Errorf("[Tls]: dial to %s: %w", s.addr, err) - } - - var tlsConn interface { - netproxy.Conn - Handshake() error - } - - switch s.tlsImplentation { - case "tls": - tlsConn = tls.Client(&netproxy.FakeNetConn{ - Conn: rc, - LAddr: nil, - RAddr: nil, - }, s.tlsConfig) - - case "utls": - clientHelloID, err := nameToUtlsClientHelloID(s.utlsImitate) - if err != nil { - return nil, err - } - - tlsConn = utls.UClient(&netproxy.FakeNetConn{ - Conn: rc, - LAddr: nil, - RAddr: nil, - }, uTLSConfigFromTLSConfig(s.tlsConfig), *clientHelloID) - - default: - return nil, fmt.Errorf("unknown tls implementation: %v", s.tlsImplentation) - } - - if err := tlsConn.Handshake(); err != nil { - return nil, err - } - return tlsConn, err -} diff --git a/component/outbound/transport/ws/ws.go b/component/outbound/transport/ws/ws.go index d29b057..4f90a31 100644 --- a/component/outbound/transport/ws/ws.go +++ b/component/outbound/transport/ws/ws.go @@ -13,10 +13,10 @@ import ( // Ws is a base Ws struct type Ws struct { - dialer netproxy.Dialer - wsAddr string - header http.Header - wsDialer *websocket.Dialer + dialer netproxy.Dialer + wsAddr string + header http.Header + tlsClientConfig *tls.Config } // NewWs returns a Ws infra. @@ -43,23 +43,9 @@ func NewWs(s string, d netproxy.Dialer) (*Ws, error) { Host: u.Host, } t.wsAddr = wsUrl.String() + u.Path - t.wsDialer = &websocket.Dialer{ - NetDial: func(network, addr string) (net.Conn, error) { - c, err := d.DialTcp(addr) - if err != nil { - return nil, err - } - return &netproxy.FakeNetConn{ - Conn: c, - LAddr: nil, - RAddr: nil, - }, nil - }, - //Subprotocols: []string{"binary"}, - } if u.Scheme == "wss" { skipVerify, _ := strconv.ParseBool(u.Query().Get("allowInsecure")) - t.wsDialer.TLSClientConfig = &tls.Config{ + t.tlsClientConfig = &tls.Config{ ServerName: u.Query().Get("sni"), InsecureSkipVerify: skipVerify, } @@ -74,23 +60,28 @@ func (s *Ws) Dial(network, addr string) (c netproxy.Conn, err error) { } switch magicNetwork.Network { case "tcp": - return s.DialTcp(addr) + wsDialer := &websocket.Dialer{ + NetDial: func(_, addr string) (net.Conn, error) { + c, err := s.dialer.Dial(network, addr) + if err != nil { + return nil, err + } + return &netproxy.FakeNetConn{ + Conn: c, + LAddr: nil, + RAddr: nil, + }, nil + }, + //Subprotocols: []string{"binary"}, + } + rc, _, err := wsDialer.Dial(s.wsAddr, s.header) + if err != nil { + return nil, fmt.Errorf("[Ws]: dial to %s: %w", s.wsAddr, err) + } + return newConn(rc), err case "udp": - return s.DialUdp(addr) + return nil, fmt.Errorf("%w: ws+udp", netproxy.UnsupportedTunnelTypeError) default: return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network) } } - -func (s *Ws) DialUdp(addr string) (netproxy.PacketConn, error) { - return nil, fmt.Errorf("%w: ws+udp", netproxy.UnsupportedTunnelTypeError) -} - -// DialTcp connects to the address addr on the network net via the infra. -func (s *Ws) DialTcp(addr string) (netproxy.Conn, error) { - rc, _, err := s.wsDialer.Dial(s.wsAddr, s.header) - if err != nil { - return nil, fmt.Errorf("[Ws]: dial to %s: %w", s.wsAddr, err) - } - return newConn(rc), err -} diff --git a/config/config.go b/config/config.go index df16e2a..7283cc6 100644 --- a/config/config.go +++ b/config/config.go @@ -14,8 +14,10 @@ import ( ) type Global struct { - TproxyPort uint16 `mapstructure:"tproxy_port" default:"12345"` - LogLevel string `mapstructure:"log_level" default:"info"` + TproxyPort uint16 `mapstructure:"tproxy_port" default:"12345"` + TproxyPortProtect bool `mapstructure:"tproxy_port_protect" default:"true"` + SoMarkFromDae uint32 `mapstructure:"so_mark_from_dae"` + LogLevel string `mapstructure:"log_level" default:"info"` // We use DirectTcpCheckUrl to check (tcp)*(ipv4/ipv6) connectivity for direct. //DirectTcpCheckUrl string `mapstructure:"direct_tcp_check_url" default:"http://www.qualcomm.cn/generate_204"` TcpCheckUrl []string `mapstructure:"tcp_check_url" default:"http://cp.cloudflare.com,1.1.1.1,2606:4700:4700::1111"` diff --git a/config/desc.go b/config/desc.go index b4d9fa6..afb9470 100644 --- a/config/desc.go +++ b/config/desc.go @@ -36,6 +36,8 @@ var SectionDescription = map[string]Desc{ var GlobalDesc = Desc{ "tproxy_port": "tproxy port to listen on. It is NOT a HTTP/SOCKS port, and is just used by eBPF program.\nIn normal case, you do not need to use it.", + "tproxy_port_protect": "Set it true to protect tproxy port from unsolicited traffic. Set it false to allow users to use self-managed iptables tproxy rules.", + "so_mark_from_dae": "If not zero, traffic sent from dae will be set SO_MARK. It is useful to avoid traffic loop with iptables tproxy rules.", "log_level": "Log level: error, warn, info, debug, trace.", "tcp_check_url": "Node connectivity check.\nHost of URL should have both IPv4 and IPv6 if you have double stack in local.\nConsidering traffic consumption, it is recommended to choose a site with anycast IP and less response.", "tcp_check_http_method": "The HTTP request method to `tcp_check_url`. Use 'CONNECT' by default because some server implementations bypass accounting for this kind of traffic.", diff --git a/control/control_plane.go b/control/control_plane.go index 1ac00a2..70449b5 100644 --- a/control/control_plane.go +++ b/control/control_plane.go @@ -67,7 +67,9 @@ type ControlPlane struct { wanInterface []string lanInterface []string - sniffingTimeout time.Duration + sniffingTimeout time.Duration + tproxyPortProtect bool + soMarkFromDae uint32 } func NewControlPlane( @@ -226,9 +228,17 @@ func NewControlPlane( log.Warnln("AllowInsecure is enabled, but it is not recommended. Please make sure you have to turn it on.") } option := &dialer.GlobalOption{ - Log: log, - TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl, Log: log, Method: global.TcpCheckHttpMethod}, - CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{Raw: global.UdpCheckDns}, + Log: log, + TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{ + Raw: global.TcpCheckUrl, + Log: log, + ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), + Method: global.TcpCheckHttpMethod, + }, + CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{ + Raw: global.UdpCheckDns, + ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), + }, CheckInterval: global.CheckInterval, CheckTolerance: global.CheckTolerance, CheckDnsTcp: true, @@ -337,23 +347,25 @@ func NewControlPlane( ctx, cancel := context.WithCancel(context.Background()) plane := &ControlPlane{ - log: log, - core: core, - deferFuncs: deferFuncs, - listenIp: "0.0.0.0", - outbounds: outbounds, - dnsController: nil, - onceNetworkReady: sync.Once{}, - dialMode: dialMode, - routingMatcher: routingMatcher, - ctx: ctx, - cancel: cancel, - ready: make(chan struct{}), - muRealDomainSet: sync.Mutex{}, - realDomainSet: bloom.NewWithEstimates(2048, 0.001), - lanInterface: global.LanInterface, - wanInterface: global.WanInterface, - sniffingTimeout: sniffingTimeout, + log: log, + core: core, + deferFuncs: deferFuncs, + listenIp: "0.0.0.0", + outbounds: outbounds, + dnsController: nil, + onceNetworkReady: sync.Once{}, + dialMode: dialMode, + routingMatcher: routingMatcher, + ctx: ctx, + cancel: cancel, + ready: make(chan struct{}), + muRealDomainSet: sync.Mutex{}, + realDomainSet: bloom.NewWithEstimates(2048, 0.001), + lanInterface: global.LanInterface, + wanInterface: global.WanInterface, + sniffingTimeout: sniffingTimeout, + tproxyPortProtect: global.TproxyPortProtect, + soMarkFromDae: global.SoMarkFromDae, } defer func() { if err != nil { @@ -363,9 +375,10 @@ func NewControlPlane( /// DNS upstream. dnsUpstream, err := dns.New(dnsConfig, &dns.NewOption{ - Logger: log, - LocationFinder: locationFinder, - UpstreamReadyCallback: plane.dnsUpstreamReadyCallback, + Logger: log, + LocationFinder: locationFinder, + UpstreamReadyCallback: plane.dnsUpstreamReadyCallback, + UpstreamResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), }) if err != nil { return nil, err @@ -559,7 +572,7 @@ func (c *ControlPlane) ChooseDialTarget(outbound consts.OutboundIndex, dst netip // TODO: use DNS controller and re-route by control plane. systemDns, err := netutils.SystemDns() if err == nil { - if ip46, err := netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, domain, false, true); err == nil && (ip46.Ip4.IsValid() || ip46.Ip6.IsValid()) { + if ip46, err := netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, domain, common.MagicNetwork("udp", c.soMarkFromDae), true); err == nil && (ip46.Ip4.IsValid() || ip46.Ip6.IsValid()) { // Has A/AAAA records. It is a real domain. dialMode = consts.DialMode_Domain // Add it to real-domain set. @@ -717,8 +730,21 @@ func (c *ControlPlane) Serve(readyChan chan<- bool, listener *Listener) (err err lastErr := err addrHdr, dataOffset, err := ParseAddrHdr(data) if err != nil { - c.log.Warnf("No AddrPort presented: %v, %v", lastErr, err) - return + if c.tproxyPortProtect { + c.log.Warnf("No AddrPort presented: %v, %v", lastErr, err) + return + } else { + routingResult = &bpfRoutingResult{ + Mark: 0, + Must: 0, + Mac: [6]uint8{}, + Outbound: uint8(consts.OutboundControlPlaneRouting), + Pname: [16]uint8{}, + Pid: 0, + } + realDst = pktDst + goto destRetrieved + } } n := copy(data, data[dataOffset:]) data = data[:n] @@ -731,6 +757,7 @@ func (c *ControlPlane) Serve(readyChan chan<- bool, listener *Listener) (err err } else { realDst = pktDst } + destRetrieved: if e := c.handlePkt(udpConn, data, common.ConvergeAddrPort(src), common.ConvergeAddrPort(pktDst), common.ConvergeAddrPort(realDst), routingResult); e != nil { c.log.Warnln("handlePkt:", e) } @@ -814,6 +841,9 @@ func (c *ControlPlane) chooseBestDnsDialer( if err != nil { return nil, err } + if mark == 0 { + mark = c.soMarkFromDae + } if int(outboundIndex) >= len(c.outbounds) { return nil, fmt.Errorf("bad outbound index: %v", outboundIndex) } diff --git a/control/dns_control.go b/control/dns_control.go index fb16f4d..e534d34 100644 --- a/control/dns_control.go +++ b/control/dns_control.go @@ -10,6 +10,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/daeuniverse/dae/common" "io" "math" "net" @@ -645,7 +646,7 @@ func (c *DnsController) dialSend(invokingDepth int, req *udpRequest, data []byte // TODO: connection pool. conn, err = dialArgument.bestDialer.Dial( - MagicNetwork("udp", dialArgument.mark), + common.MagicNetwork("udp", dialArgument.mark), dialArgument.bestTarget.String(), ) if err != nil { @@ -707,7 +708,7 @@ func (c *DnsController) dialSend(invokingDepth int, req *udpRequest, data []byte case consts.L4ProtoStr_TCP: // We can block here because we are in a coroutine. - conn, err = dialArgument.bestDialer.Dial(MagicNetwork("tcp", dialArgument.mark), dialArgument.bestTarget.String()) + conn, err = dialArgument.bestDialer.Dial(common.MagicNetwork("tcp", dialArgument.mark), dialArgument.bestTarget.String()) if err != nil { return fmt.Errorf("failed to dial proxy to tcp: %w", err) } diff --git a/control/kern/tproxy.c b/control/kern/tproxy.c index d2ce368..43a2eaf 100644 --- a/control/kern/tproxy.c +++ b/control/kern/tproxy.c @@ -64,6 +64,7 @@ #define IS_LAN 1 #define TPROXY_MARK 0x8000000 +#define RECOGNIZE 0x2017 #define ESOCKTNOSUPPORT 94 /* Socket type not supported */ @@ -139,6 +140,7 @@ struct routing_result { struct dst_routing_result { __be32 ip[4]; __be16 port; + __u16 recognize; struct routing_result routing_result; }; @@ -1751,6 +1753,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) { __builtin_memset(&new_hdr, 0, sizeof(new_hdr)); __builtin_memcpy(new_hdr.ip, &tuples.dip, IPV6_BYTE_LENGTH); new_hdr.port = udph.dest; + new_hdr.recognize = RECOGNIZE; new_hdr.routing_result.outbound = s64_ret; new_hdr.routing_result.mark = s64_ret >> 8; new_hdr.routing_result.must = (s64_ret >> 40) & 1; diff --git a/control/tcp.go b/control/tcp.go index 387b82f..7500828 100644 --- a/control/tcp.go +++ b/control/tcp.go @@ -50,7 +50,19 @@ func (c *ControlPlane) handleConn(lConn net.Conn) (err error) { Ip: struct{ U6Addr8 [16]uint8 }{U6Addr8: ip6}, Port: common.Htons(src.Port()), }, &value); e != nil { - return fmt.Errorf("failed to retrieve target info %v: %v, %v", src.String(), err, e) + if c.tproxyPortProtect { + return fmt.Errorf("failed to retrieve target info %v: %v, %v", src.String(), err, e) + } else { + routingResult = &bpfRoutingResult{ + Mark: 0, + Must: 0, + Mac: [6]uint8{}, + Outbound: uint8(consts.OutboundControlPlaneRouting), + Pname: [16]uint8{}, + Pid: 0, + } + goto destRetrieved + } } routingResult = &value.RoutingResult @@ -60,6 +72,7 @@ func (c *ControlPlane) handleConn(lConn net.Conn) (err error) { } dst = netip.AddrPortFrom(dstAddr, common.Htons(value.Port)) } +destRetrieved: src = common.ConvergeAddrPort(src) dst = common.ConvergeAddrPort(dst) @@ -92,6 +105,9 @@ func (c *ControlPlane) handleConn(lConn net.Conn) (err error) { dialTarget, _ = c.ChooseDialTarget(outboundIndex, dst, domain) default: } + if routingResult.Mark == 0 { + routingResult.Mark = c.soMarkFromDae + } // TODO: Set-up ip to domain mapping and show domain if possible. if outboundIndex < 0 || int(outboundIndex) >= len(c.outbounds) { return fmt.Errorf("outbound id from bpf is out of range: %v not in [0, %v]", outboundIndex, len(c.outbounds)-1) @@ -122,7 +138,7 @@ func (c *ControlPlane) handleConn(lConn net.Conn) (err error) { } // Dial and relay. - rConn, err := d.Dial(MagicNetwork("tcp", routingResult.Mark), dialTarget) + rConn, err := d.Dial(common.MagicNetwork("tcp", routingResult.Mark), dialTarget) if err != nil { return fmt.Errorf("failed to dial %v: %w", dst, err) } diff --git a/control/udp.go b/control/udp.go index edab9be..612cffd 100644 --- a/control/udp.go +++ b/control/udp.go @@ -48,6 +48,9 @@ func ParseAddrHdr(data []byte) (hdr *bpfDstRoutingResult, dataOffset int, err er return nil, 0, fmt.Errorf("data is too short to parse AddrHdr") } _hdr := *(*bpfDstRoutingResult)(unsafe.Pointer(&data[0])) + if _hdr.Recognize != consts.Recognize { + return nil, 0, fmt.Errorf("bad recognize") + } _hdr.Port = common.Ntohs(_hdr.Port) return &_hdr, dataOffset, nil } @@ -173,6 +176,9 @@ func (c *ControlPlane) handlePkt(lConn *net.UDPConn, data []byte, src, pktDst, r dialTarget, _ = c.ChooseDialTarget(outboundIndex, realDst, domain) default: } + if routingResult.Mark == 0 { + routingResult.Mark = c.soMarkFromDae + } if isDns { return c.dnsController.Handle_(dnsMessage, &udpRequest{ lanWanFlag: lanWanFlag, @@ -226,7 +232,7 @@ getNew: }, NatTimeout: natTimeout, Dialer: dialerForNew, - Network: MagicNetwork("udp", routingResult.Mark), + Network: common.MagicNetwork("udp", routingResult.Mark), Target: dialTarget, }) if err != nil { diff --git a/control/utils.go b/control/utils.go index 2a8d463..60972f3 100644 --- a/control/utils.go +++ b/control/utils.go @@ -16,7 +16,6 @@ import ( "github.com/daeuniverse/dae/common" "github.com/daeuniverse/dae/common/consts" - "github.com/mzz2017/softwind/netproxy" "golang.org/x/sys/unix" ) @@ -160,17 +159,6 @@ func SetSendRedirects(ifname string, val string) { _ = setSendRedirects(ifname, consts.IpVersionStr_4, val) } -func MagicNetwork(network string, mark uint32) string { - if mark == 0 { - return network - } else { - return netproxy.MagicNetwork{ - Network: network, - Mark: mark, - }.Encode() - } -} - func ProcessName2String(pname []uint8) string { return string(bytes.TrimRight(pname[:], string([]byte{0}))) } diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index affc0cc..3b2b61e 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -90,6 +90,18 @@ sudo systemctl start dae sudo systemctl enable dae ``` +### Gentoo Linux + +dae has been released on [gentoo-zh](https://github.com/microcai/gentoo-zh) + +use `app-eselect/eselect-repository` to enable this overlay: + +```shell +eselect repository enable gentoo-zh +emaint sync -r gentoo-zh +emerge -a net-proxy/dae +``` + ### macOS We provide a hacky way to run dae on your macOS. See [run on macOS](run-on-macos.md). diff --git a/docs/getting-started/README_zh.md b/docs/getting-started/README_zh.md index c755c27..d8fe91a 100644 --- a/docs/getting-started/README_zh.md +++ b/docs/getting-started/README_zh.md @@ -86,6 +86,16 @@ sudo systemctl start dae sudo systemctl enable dae ``` +### Gentoo Linux + +dae 已发布于 [gentoo-zh](https://github.com/microcai/gentoo-zh),可以使用 `app-eselect/eselect-repository` 启用此 overlay: + +```shell +eselect repository enable gentoo-zh +emaint sync -r gentoo-zh +emerge -a net-proxy/dae +``` + ### macOS 我们提供了一种比较 hacky 的方式在 macOS 上运行 dae,见 [run on macOS](run-on-macos.md)。 diff --git a/docs/routing.md b/docs/routing.md index 99fa9d9..272dc0a 100644 --- a/docs/routing.md +++ b/docs/routing.md @@ -84,10 +84,8 @@ domain(geosite:geolocation-!cn) && domain(ext:"yourdatfile.dat:yourtag")->direct dip(ext:"yourdatfile.dat:yourtag")->direct -### Mark for direct/must_direct outbound -# Mark is useful when you want to redirect traffic to specific interface (such as wireguard) or other advanced uses. -# Traffic from LAN will not be forwarded by dae to archive higher performance if lan_nat_direct is off (you can set it -# off only if you are sure dae is on a bridge device). +### Set fwmark +# Mark is useful when you want to redirect traffic to specific interface (such as wireguard) or for other advanced uses. # An example of redirecting Disney traffic to wg0 is given here. # You need set ip rule and ip table like this: diff --git a/example.dae b/example.dae index 0b07e0b..7a9403d 100644 --- a/example.dae +++ b/example.dae @@ -5,6 +5,14 @@ global { # In normal case, you do not need to use it. tproxy_port: 12345 + # Set it true to protect tproxy port from unsolicited traffic. Set it false to allow users to use self-managed + # iptables tproxy rules. + tproxy_port_protect: true + + # If not zero, traffic sent from dae will be set SO_MARK. It is useful to avoid traffic loop with iptables tproxy + # rules. + so_mark_from_dae: 0 + # Log level: error, warn, info, debug, trace. log_level: info diff --git a/go.mod b/go.mod index 551590f..2d24d59 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/json-iterator/go v1.1.12 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/mzz2017/softwind v0.0.0-20230501115403-98d9a7116d72 + github.com/mzz2017/softwind v0.0.0-20230530150701-b78490a65b9f github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd github.com/safchain/ethtool v0.0.0-20230116090318-67cc41908669 github.com/sirupsen/logrus v1.9.0 diff --git a/go.sum b/go.sum index 3b48cec..2581612 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B90M= github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI= -github.com/mzz2017/softwind v0.0.0-20230501115403-98d9a7116d72 h1:h6xMzLtz5pW24T8E+GSdNJ9lRYh5cDpgL85d5c3/om0= -github.com/mzz2017/softwind v0.0.0-20230501115403-98d9a7116d72/go.mod h1:V8GFOtdpTgzCJtCVXRqjmdDsY+PIhCCx4JpD0zq8Z7I= +github.com/mzz2017/softwind v0.0.0-20230530150701-b78490a65b9f h1:wrOAYscIeug7HZHS7LoOY+hBs4qk48twIxormhOD/Rc= +github.com/mzz2017/softwind v0.0.0-20230530150701-b78490a65b9f/go.mod h1:1hn+ZsP9fLm17yEx0jyqxHkIKm5u2gBXVGIrnKsE1fY= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/pkg/config_parser/walker.go b/pkg/config_parser/walker.go index f5c6e3f..869f0b3 100644 --- a/pkg/config_parser/walker.go +++ b/pkg/config_parser/walker.go @@ -86,10 +86,18 @@ func (w *Walker) parseFunctionPrototype(ctx *dae_config.FunctionPrototypeContext children := ctx.GetChildren() not := false offset := 0 + if len(children) == 0 { + w.ReportError(ctx, ErrorType_Unsupported, "bad function prototype expression") + return nil + } if children[0].(*antlr.TerminalNodeImpl).GetText() == "!" { offset++ not = true } + if len(children) <= offset+2 { + w.ReportError(ctx, ErrorType_Unsupported, "bad function prototype expression") + return nil + } funcName := children[offset+0].(*antlr.TerminalNodeImpl).GetText() paramList := children[offset+2].(*dae_config.OptParameterListContext) children = paramList.GetChildren() @@ -151,6 +159,10 @@ func (p *literalExpressionParser) Parse(ctx *dae_config.LiteralExpressionContext func (w *Walker) parseDeclaration(ctx dae_config.IDeclarationContext) *Param { children := ctx.GetChildren() + if len(children) < 3 { + w.ReportError(ctx, ErrorType_Unsupported, "bad declaration expression") + return nil + } key := children[0].(*antlr.TerminalNodeImpl).GetText() switch valueCtx := children[2].(type) { case *dae_config.LiteralExpressionContext: @@ -202,6 +214,10 @@ func (w *Walker) parseFunctionPrototypeExpression(ctx dae_config.IFunctionProtot func (w *Walker) parseRoutingRule(ctx dae_config.IRoutingRuleContext) *RoutingRule { children := ctx.GetChildren() + if len(children) < 3 { + w.ReportError(ctx, ErrorType_Unsupported, "bad routing rule expression") + return nil + } //logrus.Debugln(ctx.GetText(), children) functionList, ok := children[0].(*dae_config.FunctionPrototypeExpressionContext) if !ok {