├── example
├── net-top-pics
│ ├── README.md
│ └── 一个比较极端的例子-单网卡win的vmware下的ikuai+op
│ │ ├── 爱快内外网口.png
│ │ ├── 爱快端口分流.png
│ │ ├── op的两个网口.png
│ │ ├── 爱快内dhcp配置.png
│ │ ├── vmware 和 爱快的网卡配置.png
│ │ ├── 爱快内dhcp配置-给op的分配.png
│ │ ├── 爱快内vm跑的op的两个虚拟网卡.png
│ │ └── README.md
└── script
│ └── AddOpenwrtService.sh
├── .gitignore
├── assets
└── img.png
├── private
├── bypass-domain-list.txt
└── direct-domain-list.txt
├── .idea
├── dictionaries
│ ├── yanhui.xml
│ ├── default_user.xml
│ └── yh.xml
├── .gitignore
├── misc.xml
├── vcs.xml
├── modules.xml
├── ikuai-bypass.iml
└── inspectionProfiles
│ └── Project_Default.xml
├── go.mod
├── Dockerfile_example
├── router
├── router_other.go
├── router.go
└── router_linux.go
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── Release_goreleaser.yml
├── go.sum
├── api
├── utils.go
├── ikuai.go
├── custom_isp.go
├── stream_domain.go
├── stream_ipport.go
├── ip_group.go
└── ipv6_group.go
├── flake.nix
├── runModeClean.go
├── dev_shell.sh
├── confYaml.go
├── runModeExportDomainSteamToTxt.go
├── main.go
├── runModeUpdateIspRule.go
├── release.py
├── config.yml
├── runModeUpdateIpgroup.go
├── helperFunc.go
├── README.md
└── LICENSE
/example/net-top-pics/README.md:
--------------------------------------------------------------------------------
1 | 储存一些实践中的网络拓扑图
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /ikuai-bypass
2 | /ikuai-bypass.exe
3 | /ikuai-bypass_exe
4 | *.psd
--------------------------------------------------------------------------------
/assets/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/assets/img.png
--------------------------------------------------------------------------------
/private/bypass-domain-list.txt:
--------------------------------------------------------------------------------
1 | dl.google.com-del
2 | cache.nixos.org
3 | copilot.microsoft.com
--------------------------------------------------------------------------------
/.idea/dictionaries/yanhui.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/dscao/ikuai-bypass
2 |
3 | go 1.23.1
4 |
5 | require (
6 | github.com/robfig/cron/v3 v3.0.1
7 | gopkg.in/yaml.v3 v3.0.1
8 | )
9 |
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内外网口.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内外网口.png
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快端口分流.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快端口分流.png
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/op的两个网口.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/op的两个网口.png
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内dhcp配置.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内dhcp配置.png
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/vmware 和 爱快的网卡配置.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/vmware 和 爱快的网卡配置.png
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内dhcp配置-给op的分配.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内dhcp配置-给op的分配.png
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内vm跑的op的两个虚拟网卡.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joyanhui/ikuai-bypass/HEAD/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/爱快内vm跑的op的两个虚拟网卡.png
--------------------------------------------------------------------------------
/.idea/dictionaries/default_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | softprops
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Dockerfile_example:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | WORKDIR /build
4 | COPY ikuai-bypass .
5 |
6 | RUN apk add --no-cache tzdata
7 | ENV TZ=Asia/Shanghai
8 |
9 | CMD ["./ikuai-bypass", "-c", "/etc/ikuai-bypass/config.yml"]
--------------------------------------------------------------------------------
/router/router_other.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 | // +build !linux
3 |
4 | package router
5 |
6 | import "errors"
7 |
8 | func GetRouteInfo() (*router, error) {
9 | return nil, errors.New("GetRouteInfo is only available on Linux")
10 | }
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request 功能建议
3 | about: Suggest an idea for this project
4 | title: '建议:'
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | 爱快本身的使用问题,还有网络top结构问题,在移步 爱快官方论坛或恩山或者discussions https://github.com/joyanhui/ikuai-bypass/discussions
10 |
11 | **期望到效果和场景**
12 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 | ## Things done
13 |
14 |
15 |
16 | - 测试过的平台
17 | - [ ] linux linux distributions and versions: ( )
18 | - [ ] windows10/11
19 | - [ ] macos 版本( )
20 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
2 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
3 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
6 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
7 |
--------------------------------------------------------------------------------
/.idea/ikuai-bypass.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/api/utils.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | )
8 |
9 | func postJson(httpClient *http.Client, url string, body interface{}, result interface{}) (err error) {
10 | bodyByte, err := json.Marshal(body)
11 | if err != nil {
12 | return
13 | }
14 |
15 | req, err := http.NewRequest("POST", url, bytes.NewReader(bodyByte))
16 | if err != nil {
17 | return
18 | }
19 |
20 | req.Header.Set("Content-Type", "application/json")
21 |
22 | resp, err := httpClient.Do(req)
23 | if err != nil {
24 | return
25 | }
26 |
27 | err = json.NewDecoder(resp.Body).Decode(result)
28 | _ = resp.Body.Close()
29 | return
30 | }
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report bug反馈或者未知问题
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **简单描述**
11 | 请描述清楚问题,并点亮star支持本项目。
12 | 文末最好提供完整运行命令和日志,以及配置文件的核心内容
13 | 请勿在旧的issue中提交新issue.
14 |
15 | **运行环境**
16 | 爱快版本:免费版本3.XX.XX x64 BuildXXXX
17 | ikuai-bypass版本:v2.X.X
18 | ikuai-bypass运行环境: linux发行版名称和版本/windows11/macos14
19 | ikuai-bypass运行环境:amd64/386/arm7/mips-softfloat/ppc64le
20 | **日志代码或截图支持md格式**
21 |
22 | 务必提供 完整命令 配置文件 以及运行日志,否则很有可能无法判断问题
23 |
24 | 爱快本身的使用问题,还有网络top结构问题,在移步 爱快官方论坛或恩山或者discussions https://github.com/joyanhui/ikuai-bypass/discussions
25 |
26 | >
27 |
--------------------------------------------------------------------------------
/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "errors"
5 | "net"
6 | )
7 |
8 | type rtInfo struct {
9 | Dst net.IPNet
10 | Gateway, PrefSrc net.IP
11 | OutputIface uint32
12 | Priority uint32
13 | }
14 |
15 | type routeSlice []*rtInfo
16 |
17 | type router struct {
18 | ifaces []net.Interface
19 | addrs []net.IP
20 | v4 routeSlice
21 | }
22 |
23 | func GetGateway() (gateway string, err error) {
24 | newRoute, err := GetRouteInfo()
25 | if err != nil {
26 | err = errors.New("找不到默认网关")
27 | return
28 | }
29 |
30 | for _, rt := range newRoute.v4 {
31 | if rt.Gateway != nil && len(rt.Gateway) != 0 {
32 | gateway = rt.Gateway.String()
33 | return
34 | }
35 | }
36 | err = errors.New("找不到默认网关")
37 | return
38 | }
39 |
--------------------------------------------------------------------------------
/.github/workflows/Release_goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: Release ikuai-bypass
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 |
10 | build-release:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Set up Go
16 | uses: actions/setup-go@v3
17 | with:
18 | go-version: '1.22.1'
19 | check-latest: true
20 | cache: true
21 |
22 | #- name: Test
23 | # run: go test -race -v ./...
24 |
25 | - name: Set up Python
26 | uses: actions/setup-python@v2
27 | with:
28 | python-version: '3.8'
29 |
30 | - name: Build
31 | run: python ./release.py
32 | env:
33 | CGO_ENABLED: '0'
34 |
35 | - name: Publish
36 | uses: softprops/action-gh-release@v1
37 | with:
38 | files: './release/*.zip'
39 | prerelease: false
40 | env:
41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42 |
--------------------------------------------------------------------------------
/.idea/dictionaries/yh.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | adguard
5 | compresslevel
6 | devnull
7 | f'build
8 | f'building
9 | f'get
10 | f'go
11 | f'unknown
12 | f'upx
13 | ghproxy
14 | goarm
15 | gomips
16 | hardfloat
17 | iface
18 | ikuai
19 | ipcf
20 | ipcn
21 | ipfb
22 | ipgoogle
23 | ipgroup
24 | ipnf
25 | ipport
26 | iptg
27 | iptw
28 | ispdomain
29 | joyanhui
30 | kuai
31 | mipsle
32 | mosdns
33 | nexthop
34 | nocron
35 | segmens
36 | softfloat
37 | trimpath
38 | zipfile
39 |
40 |
41 |
--------------------------------------------------------------------------------
/example/net-top-pics/一个比较极端的例子-单网卡win的vmware下的ikuai+op/README.md:
--------------------------------------------------------------------------------
1 | ## 简述
2 | 这是只是一个思路和演示,单网卡这样搞性能并不好。很多因素会导致跑不满理论带宽。
3 | - windows 宿主机 + VMware 单物理网卡
4 | - 交换机
5 | - 光猫
6 | - AP若干
7 | - 其他设备
8 |
9 | openwrt 部署在爱快的vm内
10 | ## 物理连接
11 | 所有设备一股脑都插到交换机
12 |
13 | ## ikuai网卡说明
14 | - lan1 使用VMware NAT网卡 vmnet8 仅供 windows宿主机管理爱快
15 | - wan1 桥接物理网卡,也就是vmnet0 作为pppoe 通过交换机再到光猫拨号
16 | - lan2 桥接物理网卡,也就是vmnet0 通过交换机给局域网内其他设备提供上网服务和dhcp
17 | - wan2 使用VMware NAT网卡 vmnet10 ,VMware默认没启用自己启用一下, 作为openwrt下游
18 |
19 | ## ikuai内-vm-openwrt网卡说明
20 | - lan 桥接 爱快的wan2 ,为局域网提供代理功能,作为爱快wan2的上游
21 | - wan 桥接 爱快的lan1 ,作为ikuai的下的一个设备,openwrt自己连接互联网用
22 |
23 | ## 关于vmnet10
24 | 这里的vmnet10 单独给爱快的openwrt用的。VMware默认也没有启用这个网卡,实际上也非必须,可以用vmnet8替代。不过建议还是启用一下,方便隔离和管理。
25 |
26 | ## 物理机器上的网卡
27 | ### 物理网卡
28 | 关闭ipv4 和ipv6 功能,可以通过vmnet0上网。不过会导致win的商店打不开,或许也会有其他问题。
29 | 所以也建议打开ipv4/ipv6 然后dhcp获取,或手动指定到 和ikuai的lan2下的网段里面。
30 | ### VMware Network Adapter VMnet1
31 | 这里没用到,不用管
32 | ### VMware Network Adapter VMnet8
33 | 由爱快lan1分配ip,或者手动配置到和爱快lan1同一个网段
34 | ### VMware Network Adapter VMnet10
35 | 理论上最好和爱快的wan2 以及 openwrt lan 在同一个网段,也可以在openwrt的lan上启用dhcp由op来分发ip。实际上不用管也不用配ip。
36 |
--------------------------------------------------------------------------------
/private/direct-domain-list.txt:
--------------------------------------------------------------------------------
1 | cn.bing.com
2 | s.cn.bing.net
3 | dl.google.com
4 | pub.dev
5 | leiyanhui.com
6 | cf-cdn-ns.work
7 | mirror.ghproxy.com
8 | ghproxy.net
9 | kkgithub.com
10 | nixos.wiki
11 | search.nixos.org
12 | tarballs.nixos.org-del
13 | plugins.jetbrains.com
14 | download.jetbrains.com
15 | ly729.out.airtime.pro
16 | c20.radioboss.fm
17 | listen.729ly.net
18 | plugins.jetbrains.com
19 | downloads.marketplace.jetbrains.com
20 | download.fedoraproject.org
21 | vmware.com
22 | mozilla.org
23 | #允许cursor.com直连
24 | cursor.com
25 | cursor.sh
26 | #by.Missing_Love.start.ooooooooooooooooooooooooooooooo
27 | kp.m-team.cc
28 | kp.m-team.io
29 | tracker.m-team.io
30 | tracker.m-team.cc
31 | v6tracker.m-team.io
32 | v6tracker.m-team.cc
33 | hdatmos.club
34 | pt.btschool.club
35 | pt.hdupt.com
36 | pt.upxin.net
37 | t.hdhome.org
38 | t.pthome.net
39 | tracker.hdarea.club
40 | tracker.hdsky.me
41 | tracker.pterclub.com
42 | tracker.wintersakura.net
43 | www.pttime.org
44 | kamept.com
45 | kame.gay
46 | test-ipv6.com
47 | stun.syncthing.net
48 | stun.hot-chilli.net
49 | stun.fitauto.ru
50 | stun.miwifi.com
51 | #by.Missing_Love.end.ooooooooooooooooooooooooooooooo
52 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | # start : bash : nix run .#dev
2 | # 或者 nix shell .#dev --command 'dev-shell'
3 | {
4 | description = "A Nix-flake-based golang development environment .";
5 |
6 | inputs = {
7 | nixpkgs.url = "github:NixOS/nixpkgs/24.11";
8 | };
9 |
10 | outputs =
11 | { nixpkgs, ... }:
12 | let
13 | system = "x86_64-linux";
14 | in
15 | {
16 | packages."${system}".dev =
17 | let
18 | pkgs = import nixpkgs {
19 | inherit system;
20 | config.allowUnfree = true;
21 | };
22 | packages = with pkgs; [
23 | go_1_23
24 | gopls
25 | gcc
26 | upx
27 | nushell
28 | ];
29 | in
30 | pkgs.runCommand "dev-shell"
31 | {
32 | buildInputs = packages;
33 | nativeBuildInputs = [ pkgs.makeWrapper ];
34 | }
35 | ''
36 | mkdir -p $out/bin/
37 | ln -s ${pkgs.nushell}/bin/nu $out/bin/dev-shell
38 | wrapProgram $out/bin/dev-shell --set GOPROXY https://goproxy.cn,direct
39 | wrapProgram $out/bin/dev-shell --prefix PATH : ${pkgs.lib.makeBinPath packages}
40 | '';
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/runModeClean.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | // 清理旧分流规则
8 | func clean() {
9 | iKuai, err := loginToIkuai()
10 | if err != nil {
11 | log.Println("登录爱快失败:", err)
12 | return
13 | }
14 |
15 | //删除旧的自定义运营商
16 | err = iKuai.DelCustomIspAll(*cleanTag)
17 | if err != nil {
18 | log.Println("移除旧的自定义运营商失败 tag:"+*cleanTag+":", err)
19 | } else {
20 | log.Println("移除旧的自定义运营商成功 tag:" + *cleanTag)
21 | }
22 | //删除旧的域名分流
23 | err = iKuai.DelStreamDomainAll(*cleanTag)
24 | if err != nil {
25 | log.Println("移除旧的域名分流失败 tag:"+*cleanTag+":", err)
26 | } else {
27 | log.Println("移除旧的域名分流成功 tag:" + *cleanTag)
28 | }
29 | //删除旧的ip组
30 | err = iKuai.DelIKuaiBypassIpGroup(*cleanTag)
31 | if err != nil {
32 | log.Println("移除旧的IP分组失败 tag:"+*cleanTag+":", err)
33 | } else {
34 | log.Println("移除旧的IP分组成功 tag:" + *cleanTag)
35 | }
36 | //删除旧的ipv6组
37 | err = iKuai.DelIKuaiBypassIpv6Group(*cleanTag)
38 | if err != nil {
39 | log.Println("移除旧的IPV6分组失败 tag:"+*cleanTag+":", err)
40 | } else {
41 | log.Println("移除旧的IPV6分组成功 tag:" + *cleanTag)
42 | }
43 | //删除端口分流规则
44 | err = iKuai.DelIKuaiBypassStreamIpPort(*cleanTag)
45 | if err != nil {
46 | log.Println("移除旧的端口分流失败 tag:"+*cleanTag+":", err)
47 | } else {
48 | log.Println("移除旧的端口分流成功 tag:" + *cleanTag)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/api/ikuai.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/base64"
6 | "encoding/hex"
7 | "errors"
8 | "log"
9 | "net/http"
10 | "net/http/cookiejar"
11 | "time"
12 | )
13 |
14 | const COMMENT_IKUAI_BYPASS = "IKUAI_BYPASS"
15 |
16 | type IKuai struct {
17 | baseurl string
18 | client *http.Client
19 | }
20 |
21 | type CallReq struct {
22 | FuncName string `json:"func_name"`
23 | Action string `json:"action"`
24 | Param interface{} `json:"param"`
25 | }
26 |
27 | type CallResp struct {
28 | ErrMsg string `json:"ErrMsg"`
29 | Result int `json:"Result"`
30 | RowID int `json:"RowId"`
31 | Data *CallRespData `json:"Data"`
32 | }
33 |
34 | type CallRespData struct {
35 | Total int `json:"total"`
36 | Data interface{} `json:"data"`
37 | }
38 |
39 | func md5String(v string) string {
40 | d := []byte(v)
41 | m := md5.New()
42 | m.Write(d)
43 | return hex.EncodeToString(m.Sum(nil))
44 | }
45 |
46 | func NewIKuai(baseurl string) *IKuai {
47 | cookieJar, err := cookiejar.New(nil)
48 | if err != nil {
49 | log.Fatalln(err)
50 | }
51 | return &IKuai{baseurl, &http.Client{Jar: cookieJar, Timeout: time.Second * 10}}
52 | }
53 |
54 | // https://github.com/joyanhui/ikuai-bypass/issues/55 疑似有bug但是无法复现
55 | func (i *IKuai) Login(username, password string) error {
56 | passwd := md5String(password)
57 | pass := base64.StdEncoding.EncodeToString([]byte("salt_11" + password))
58 | req := map[string]string{
59 | "passwd": passwd,
60 | "pass": pass,
61 | "remember_password": "",
62 | "username": username,
63 | }
64 | resp := CallResp{}
65 | err := postJson(i.client, i.baseurl+"/Action/login", req, &resp)
66 | if err != nil {
67 | return err
68 | }
69 | if resp.Result != 10000 {
70 | return errors.New(resp.ErrMsg)
71 | }
72 | return nil
73 | }
74 |
--------------------------------------------------------------------------------
/dev_shell.sh:
--------------------------------------------------------------------------------
1 | go run *.go -r clean -c config.yml
2 |
3 | go run *.go -r 1 -c config.yml -m ii
4 |
5 |
6 | go run *.go -r 1 -c config.yml -m ipgroup -delOldRule before
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | go run *.go -r 1 -c /home/yh/workspace/ikuai-bypass/config_example.yml -login http://10.1.1.1,admin,123
24 |
25 |
26 |
27 | go run *.go -r 1 -m ipgroup -c /home/yh/workspace/ikuai-bypass/config_example.yml -login http://10.1.1.1,admin,123
28 |
29 |
30 | go run *.go -r 1 -m ispdomain -c /home/yh/workspace/ikuai-bypass/config_example.yml -login http://10.1.1.1,admin,123
31 |
32 |
33 | go run *.go -r clean -c /home/yh/workspace/ikuai-bypass/config_example.yml -login http://10.1.1.1,admin,123
34 |
35 | git tag -a v2.1.2-alpha1 -m "- 提供完整的最新的config.yml 文件,供参考
36 | - 修复端口分流规则自动添加未能关联ip分组的bug,本次修改更新了一下config.yml的默认内容,请注意更新您的配置文件。[[#30]](https://github.com/joyanhui/ikuai-bypass/issues/30)
37 | - 修复清理模式的删除规则问题 [[#27#issuecomment-2388114699]](https://github.com/joyanhui/ikuai-bypass/issues/27#issuecomment-2388114699)
38 | - ip分组第一行的备注问题 [[#22]](https://github.com/joyanhui/ikuai-bypass/issues/22)
39 | - 修复 卡`ip分组== 正在查询 备注为: IKUAI_BYPASS_ 的ip分组规则` 的bug [[#24]](https://github.com/joyanhui/ikuai-bypass/issues/24) [[#27]](https://github.com/joyanhui/ikuai-bypass/issues/27)
40 | - 修复运营商分流的ip列表会添加一个空行的bug [[#24]](https://github.com/joyanhui/ikuai-bypass/issues/24)"
41 |
42 |
43 | git tag -a v2.0.1-alpha1 -m "增加删除旧规则的顺序的开关控制参数,此版本未经过测试,请谨慎使用"
44 | git tag -a v2.0.0-beta2 -m "修复不添加-m参数无法执行的bug"
45 | git tag -a v2.0.0-beta1 -m "增加ip分组和端口分流模式,增加命令行覆盖ikuai登陆参数模式。其他更新请参考readme或commit"
46 |
47 |
48 |
49 |
50 |
51 | go run *.go -r 1 -m ispdomain -delOldRule before -c /home/yh/workspace/ikuai-bypass/config_example.yml -login http://10.1.1.1,admin,123
52 |
53 |
54 | git tag -d v2.0.0-beta2
55 | git tag -d v2.0.0-beta1
56 |
57 |
58 |
59 | git push origin --tags
60 |
61 |
62 | git push origin :refs/tags/v1.0-test
--------------------------------------------------------------------------------
/example/script/AddOpenwrtService.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Test passed under openwrt5.15.137 x64 在openwrt 5.15.137 x64 测试通过
3 | # 下面的脚本只支持ash sh bash zsh 不兼容fishshell
4 | # 更新或者下载最新版到 /opt/注意修改版本号CPU架构以及路径 =================================== start
5 | # 最好逐行运行
6 | # opkg update
7 | # opkg install wget unzip
8 | export GhProxy=https://ghp.ci/ # 配置github代理 如果不可用请自行更换如果已经有直连github环境也可以去掉这行
9 | mkdir -p /opt/ && cd /opt/
10 | wget ${GhProxy}https://github.com/joyanhui/ikuai-bypass/releases/download/v1.0.0-beta2/ikuai-bypass-linux-amd64.zip
11 | unzip ikuai-bypass-linux-amd64.zip && rm -rf ikuai-bypass-linux-amd64.zip && rm -rf README.md
12 | # 使用版本内的配置文件
13 | mv config.yml ikuai-bypass.yml
14 | # 或者用最新的演示配置
15 | rm -rf ikuai-bypass.yml && rm -rf config.yml
16 | wget ${GhProxy}https://raw.githubusercontent.com/joyanhui/ikuai-bypass/main/config.yml -O ikuai-bypass.yml
17 | # 更新或者下载最新版到 /opt/注意修改版本号CPU架构以及路径 =================================== end
18 | # 手动执行一次 检查执行结果
19 | # /opt/ikuai-bypass -r 1 -c /opt/ikuai-bypass.yml
20 | # 创建服务脚本,这段代码请整体复制后粘贴,或者使用vim nano编辑 ================================= start
21 | cat > /etc/init.d/ikuai-bypass << \EOF
22 | #!/bin/sh /etc/rc.common
23 | START=99
24 | start(){
25 | /opt/ikuai-bypass -r cronAft -c /opt/ikuai-bypass.yml > /dev/null 2>&1 &
26 | echo "ikuai-bypass is start"
27 | }
28 |
29 | stop(){
30 | killall -q -9 ikuai-bypass
31 | echo "ikuai-bypass is stop"
32 | }
33 | EOF
34 | # 创建服务脚本,这段代码请整体复制后粘贴,或者使用vim nano编辑 ================================= end
35 |
36 | chmod +x /etc/init.d/ikuai-bypass # 添加执行权限
37 |
38 | # 服务设定为开机启动
39 | service ikuai-bypass enable
40 | # 手动启动 并查看进程是否存在
41 | service ikuai-bypass start && ps |grep ikuai-bypass
42 | # 手动停止
43 | # service ikuai-bypass stop && ps |grep ikuai-bypass
44 |
45 | # 卸载 清理
46 | # service ikuai-bypass stop
47 | # service ikuai-bypass disable
48 | # rm -rf /etc/init.d/ikuai-bypass
49 | # rm -rf /opt/ikuai-bypass
50 | # rm -rf /opt/ikuai-bypass.yml
51 |
52 |
53 |
--------------------------------------------------------------------------------
/confYaml.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "gopkg.in/yaml.v3"
6 | "log"
7 | "os"
8 | "time"
9 | )
10 |
11 | var conf struct {
12 | IkuaiURL string `yaml:"ikuai-url"`
13 | Username string `yaml:"username"`
14 | Password string `yaml:"password"`
15 | Cron string `yaml:"cron"`
16 | AddErrRetryWait time.Duration `yaml:"AddErrRetryWait"`
17 | AddWait time.Duration `yaml:"AddWait"`
18 | CustomIsp []struct {
19 | Name string `yaml:"name"`
20 | URL string `yaml:"url"`
21 | Tag string `yaml:"tag"`
22 | } `yaml:"custom-isp"`
23 | StreamDomain []struct {
24 | Interface string `yaml:"interface"`
25 | SrcAddr string `yaml:"src-addr"`
26 | URL string `yaml:"url"`
27 | Tag string `yaml:"tag"`
28 | } `yaml:"stream-domain"`
29 | IpGroup []struct {
30 | Name string `yaml:"name"`
31 | URL string `yaml:"url"`
32 | } `yaml:"ip-group"`
33 | Ipv6Group []struct {
34 | Name string `yaml:"name"`
35 | URL string `yaml:"url"`
36 | } `yaml:"ipv6-group"`
37 | StreamIpPort []struct {
38 | Type string `yaml:"type"`
39 | Interface string `yaml:"interface"`
40 | Nexthop string `yaml:"nexthop"`
41 | SrcAddr string `yaml:"src-addr"`
42 | IpGroup string `yaml:"ip-group"`
43 | Mode int `yaml:"mode"`
44 | IfaceBand int `yaml:"ifaceband"`
45 | } `yaml:"stream-ipport"`
46 | }
47 |
48 | func readConf(filename string) error {
49 | buf, err := os.ReadFile(filename)
50 | if err != nil {
51 | return err
52 | }
53 | err = yaml.Unmarshal(buf, &conf)
54 | if err != nil {
55 | return fmt.Errorf("in file %q: %v", filename, err)
56 | }
57 |
58 | // 检查每个 CustomIsp 的 Tag,如果不存在,则使用 Name
59 | for i := range conf.CustomIsp {
60 | if conf.CustomIsp[i].Tag == "" {
61 | log.Println("运营商分流规则中配置中Tag为空,采用:", conf.CustomIsp[i].Name)
62 | conf.CustomIsp[i].Tag = conf.CustomIsp[i].Name
63 | }
64 | }
65 |
66 | // 检查每个 StreamDomain 的 Tag,如果不存在,则使用 Interface
67 | for i := range conf.StreamDomain {
68 | if conf.StreamDomain[i].Tag == "" {
69 | log.Println("域名分流规则中中Tag为空,采用:", conf.StreamDomain[i].Interface)
70 | conf.StreamDomain[i].Tag = conf.StreamDomain[i].Interface
71 | }
72 | }
73 |
74 | return nil
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/router/router_linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 | // +build linux
3 |
4 | package router
5 |
6 | import (
7 | "net"
8 | "sort"
9 | "syscall"
10 | "unsafe"
11 | )
12 |
13 | func GetRouteInfo() (*router, error) {
14 | rtr := &router{}
15 |
16 | tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_INET)
17 | if err != nil {
18 | return nil, err
19 | }
20 |
21 | msgs, err := syscall.ParseNetlinkMessage(tab)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | for _, m := range msgs {
27 | switch m.Header.Type {
28 | case syscall.NLMSG_DONE:
29 | break
30 | case syscall.RTM_NEWROUTE:
31 | rtmsg := (*syscall.RtMsg)(unsafe.Pointer(&m.Data[0]))
32 | attrs, err := syscall.ParseNetlinkRouteAttr(&m)
33 | if err != nil {
34 | return nil, err
35 | }
36 | routeInfo := rtInfo{}
37 | rtr.v4 = append(rtr.v4, &routeInfo)
38 | for _, attr := range attrs {
39 | switch attr.Attr.Type {
40 | case syscall.RTA_DST:
41 | routeInfo.Dst.IP = net.IP(attr.Value)
42 | routeInfo.Dst.Mask = net.CIDRMask(int(rtmsg.Dst_len), len(attr.Value)*8)
43 | case syscall.RTA_GATEWAY:
44 | routeInfo.Gateway = net.IPv4(attr.Value[0], attr.Value[1], attr.Value[2], attr.Value[3])
45 | case syscall.RTA_OIF:
46 | routeInfo.OutputIface = *(*uint32)(unsafe.Pointer(&attr.Value[0]))
47 | case syscall.RTA_PRIORITY:
48 | routeInfo.Priority = *(*uint32)(unsafe.Pointer(&attr.Value[0]))
49 | case syscall.RTA_PREFSRC:
50 | routeInfo.PrefSrc = net.IPv4(attr.Value[0], attr.Value[1], attr.Value[2], attr.Value[3])
51 | }
52 | }
53 | }
54 | }
55 |
56 | sort.Slice(rtr.v4, func(i, j int) bool {
57 | return rtr.v4[i].Priority < rtr.v4[j].Priority
58 | })
59 |
60 | ifaces, err := net.Interfaces()
61 | if err != nil {
62 | return nil, err
63 | }
64 |
65 | for i, iface := range ifaces {
66 |
67 | if i != iface.Index-1 {
68 | break
69 | }
70 |
71 | if iface.Flags&net.FlagUp == 0 {
72 | continue
73 | }
74 | rtr.ifaces = append(rtr.ifaces, iface)
75 | ifaceAddrs, err := iface.Addrs()
76 | if err != nil {
77 | return nil, err
78 | }
79 | var addrs net.IP
80 | for _, addr := range ifaceAddrs {
81 | if inet, ok := addr.(*net.IPNet); ok {
82 | if v4 := inet.IP.To4(); v4 != nil {
83 | if addrs == nil {
84 | addrs = v4
85 | }
86 | }
87 | }
88 | }
89 | rtr.addrs = append(rtr.addrs, addrs)
90 | }
91 | return rtr, nil
92 | }
93 |
--------------------------------------------------------------------------------
/runModeExportDomainSteamToTxt.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "io"
6 | "log"
7 | "net/http"
8 | "os"
9 | "strconv"
10 | "strings"
11 | "time"
12 | )
13 |
14 | func exportDomainSteamToTxt() {
15 | line := ""
16 |
17 | for _, streamDomain := range conf.StreamDomain {
18 | log.Println("域名分流== ", streamDomain.Interface, streamDomain.Tag, "正在获取....")
19 | log.Println("域名分流== http.get ...", streamDomain.URL)
20 | resp, err := http.Get(streamDomain.URL)
21 | if err != nil {
22 | log.Println("域名分流== ", streamDomain.Interface, streamDomain.Tag, "获取失败 Get", err)
23 | return
24 | }
25 | if resp.StatusCode != 200 {
26 | err = errors.New(resp.Status)
27 | log.Println("域名分流== ", streamDomain.Interface, streamDomain.Tag, "获取失败 Status", err)
28 | return
29 | }
30 | body, err := io.ReadAll(resp.Body)
31 | if err != nil {
32 | log.Println("域名分流== ", streamDomain.Interface, streamDomain.Tag, "获取失败 ReadAll", err)
33 | return
34 | }
35 | domains := strings.Split(string(body), "\n")
36 | log.Println("域名分流== ", streamDomain.Interface, streamDomain.Tag, "获取到", len(domains), "个域名")
37 | domainGroup := group(domains, 1000) //1000条
38 | var countFor int = 0
39 | for _, d := range domainGroup {
40 | countFor++
41 | log.Println("域名分流== ", countFor, "/", len(domainGroup), streamDomain.Interface, streamDomain.Tag, " 正在导出整理 .... ")
42 | domain := strings.Join(d, ",")
43 | line = line + "id=" + strconv.Itoa(countFor) + " enabled=yes comment=IKUAI_BYPASS_" + streamDomain.Tag + " domain=" + domain + " interface=" + streamDomain.Interface + " src_addr=" + streamDomain.SrcAddr + " week=1234567 time=00:00-23:59"
44 | line = line + "\n"
45 | }
46 |
47 | }
48 | //write line to file /tmp/domain.txt
49 | fileName := "/stream_domain_" + time.Now().Format("20060102150405") + ".text"
50 | err := writeFile(*exportPath+fileName, line)
51 | if err != nil {
52 | log.Println("writeFile== ", err)
53 | return
54 | }
55 | log.Println("===============================================================")
56 | log.Println("域名分流== 导出成功", *exportPath+fileName)
57 | log.Println("可能会有id冲突覆盖你的原记录,请注意备份")
58 | log.Println("最好删除你的旧记录后再导入")
59 | log.Println("爱快内:留空分流>域名分流>导入 ")
60 | log.Println("===============================================================")
61 |
62 | }
63 |
64 | func writeFile(fileName string, content string) (err error) {
65 | f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
66 | if err != nil {
67 | log.Println("writeFile== ", err)
68 | return
69 | }
70 | defer func(f *os.File) {
71 | _ = f.Close()
72 | }(f)
73 | _, err = f.WriteString(content)
74 | if err != nil {
75 | log.Println("writeFile== ", err)
76 | return
77 | }
78 | return nil
79 | }
80 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "log"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 |
10 | "github.com/robfig/cron/v3"
11 | )
12 |
13 | var confPath = flag.String("c", "./config.yml", "配置文件路径")
14 | var runMode = flag.String("r", "cron", "运行模式,马上执行 或者定时执行 或者执行一次")
15 | var isAcIpgroup = flag.String("m", "ispdomain", "ipgroup(启用ip分组和下一条网关模式) 或者 ispdomain(isp和域名分流模式)")
16 | var cleanTag = flag.String("tag", "cleanAll", "要清理的分流规则备注名或关键词") //COMMENT_IKUAI_BYPASS
17 | var exportPath = flag.String("exportPath", "/tmp", "域名分流规则导出文件路径")
18 | var ikuaiLoginInfo = flag.String("login", "", "爱快登陆地址,用户名,密码。优先级比配置文件内的高")
19 | var delOldRule = flag.String("delOldRule", "after", "删除旧规则顺序 after before ")
20 | var isIpGroupNameAddRandomSuff = flag.String("isIpGroupNameAddRandomSuff", "1", "ip分组名称是否增加随机数后缀(仅ip分组模式有效) 1为添加 0不添加") //#issues/76
21 |
22 | func main() {
23 | flag.Parse()
24 |
25 | if *cleanTag != "cleanAll" {
26 | //检查规则名称中是否包含前缀 COMMENT_IKUAI_BYPASS,如果没有添加上
27 | if len(*cleanTag) < len("IKUAI_BYPASS") || (*cleanTag)[:len("IKUAI_BYPASS")] != "IKUAI_BYPASS" {
28 | *cleanTag = "IKUAI_BYPASS_" + *cleanTag
29 | }
30 | }
31 |
32 | log.Println("运行模式", *runMode, "配置文件", *confPath)
33 | err := readConf(*confPath)
34 | //log.Println(conf)
35 | if err != nil {
36 | log.Println("读取配置文件失败:", err)
37 | return
38 | }
39 | switch *runMode { //运行模式选择
40 | case "exportDomainSteamToTxt":
41 | log.Println("导出域名分流规则到txt,可以从爱快内导入 ")
42 | log.Println("导出路径:", *exportPath)
43 | exportDomainSteamToTxt()
44 | return
45 | case "cron":
46 | log.Println("cron 模式,执行一次,然后进入定时执行模式")
47 | updateEntrance()
48 | case "cronAft":
49 | log.Println("cronAft 模式,暂时不执行,稍后定时执行")
50 | case "nocron", "once", "1":
51 | updateEntrance()
52 | log.Println("once 模式 执行完毕自动退出")
53 | return
54 | case "clean":
55 | log.Println("清理模式")
56 | if *cleanTag == "cleanAll" {
57 | log.Println("清理所有备注中包含", "IKUAI_BYPASS", "的规则")
58 | } else {
59 | log.Println("清理规则备注为:", *cleanTag, "的规则")
60 | }
61 | clean()
62 | return
63 | default:
64 | log.Println("-r 参数错误")
65 | return
66 | }
67 | // 定时任务启动和检查 ================= start
68 | if conf.Cron == "" {
69 | log.Println("Cron配为空 自动推出")
70 | return
71 | }
72 |
73 | c := cron.New()
74 | _, err = c.AddFunc(conf.Cron, updateEntrance)
75 | if err != nil {
76 | log.Println("启动计划任务失败:", err)
77 | return
78 | } else {
79 | log.Println("已启动计划任务", conf.Cron)
80 | }
81 | c.Start()
82 |
83 | {
84 | osSignals := make(chan os.Signal, 1)
85 | signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
86 | <-osSignals
87 | }
88 | // 定时任务启动和检查 ================= end
89 |
90 | }
91 |
92 | func updateEntrance() {
93 | switch *isAcIpgroup {
94 | case "ispdomain":
95 | log.Println("启动 ... 自定义isp和域名分流模式 模式")
96 | updateIspRule()
97 | case "ipgroup":
98 | log.Println("启动 ... ip分组和下一条网关模式")
99 | updateIpgroup()
100 | case "ipv6group":
101 | log.Println("启动 ... ipv6分组")
102 | updateIpv6group()
103 | case "ii":
104 | log.Println("先 启动 ... 自定义isp和域名分流模式 模式")
105 | log.Println("再 启动 ... ip分组和下一条网关模式")
106 | updateIspRule()
107 | updateIpgroup()
108 | case "ip":
109 | log.Println("先 启动 ... ip分组和下一条网关模式")
110 | log.Println("再 启动 ... ipv6分组")
111 | updateIpgroup()
112 | updateIpv6group()
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/runModeUpdateIspRule.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "time"
7 | )
8 |
9 | func updateIspRule() {
10 | iKuai, err := loginToIkuai()
11 | if err != nil {
12 | log.Println("登录爱快失败:", err)
13 | return
14 | }
15 | var GoroutineEnd1 bool = false
16 | var GoroutineEnd2 bool = false
17 | go func() {
18 | for _, customIsp := range conf.CustomIsp {
19 | //记录旧的自定义运营商
20 | preIds, err := iKuai.GetCustomIspAll(customIsp.Tag)
21 | if err != nil {
22 | log.Println("运营商/IP分流== 获取准备更新的自定义运营商列表失败:", customIsp.Name, customIsp.Tag, err)
23 | //return
24 | break
25 | } else {
26 | log.Println("运营商/IP分流== 获取准备更新的自定义运营商列表成功", customIsp.Name, customIsp.Tag)
27 | }
28 | //是否要先删除旧规则
29 | if *delOldRule == "before" {
30 | //删除旧的自定义运营商
31 | err = iKuai.DelCustomIspFromPreIds(preIds)
32 | if err == nil {
33 | log.Println("运营商/IP分流== 删除旧的运营商列表成功", customIsp.Name, customIsp.Tag)
34 | log.Println("运营商/IP分流== 更新完成", customIsp.Name, customIsp.Tag)
35 | } else {
36 | log.Println("运营商/IP分流== 删除旧的运营商列表有错误", customIsp.Name, customIsp.Tag, err)
37 | }
38 | }
39 | //更新自定义运营商
40 | log.Println("运营商/IP分流== 正在更新", customIsp.Name, customIsp.Tag)
41 | err = updateCustomIsp(iKuai, customIsp.Name, customIsp.Tag, customIsp.URL)
42 | if err != nil {
43 | log.Printf("运营商/IP分流== 添加自定义运营商'%s'失败:%s\n", customIsp.Name, err)
44 | } else {
45 | log.Printf("运营商/IP分流== 添加自定义运营商'%s'成功\n", customIsp.Name)
46 | if *delOldRule == "after" {
47 | //删除旧的自定义运营商
48 | err = iKuai.DelCustomIspFromPreIds(preIds)
49 | if err == nil {
50 | log.Println("运营商/IP分流== 删除旧的运营商列表成功", customIsp.Name, customIsp.Tag)
51 | log.Println("运营商/IP分流== 更新完成", customIsp.Name, customIsp.Tag)
52 | } else {
53 | log.Println("运营商/IP分流== 删除旧的运营商列表有错误", customIsp.Name, customIsp.Tag, err)
54 | }
55 | }
56 |
57 | }
58 |
59 | }
60 |
61 | GoroutineEnd1 = true
62 | }()
63 |
64 | go func() {
65 |
66 | for _, streamDomain := range conf.StreamDomain {
67 | //记录旧的域名分流
68 | preIds, err := iKuai.GetStreamDomainAll(streamDomain.Tag)
69 | if err != nil {
70 | log.Println("域名分流== 获取准备更新的域名列表失败:", streamDomain.Tag, err)
71 | break
72 | } else {
73 | log.Println("域名分流== 获取准备更新的域名列表成功", streamDomain.Tag)
74 | }
75 | if *delOldRule == "before" {
76 | //删除旧的域名分流
77 | err = iKuai.DelStreamDomainFromPreIds(preIds)
78 | if err == nil {
79 | log.Println("域名分流== 删除旧的运营商列表成功")
80 | } else {
81 | log.Println("域名分流== 删除旧的运营商列表有错误", err)
82 | }
83 | }
84 | //更新域名分流
85 | log.Println("域名分流== 正在更新", streamDomain.Interface, streamDomain.Tag, streamDomain.SrcAddr)
86 | err = updateStreamDomain(iKuai, streamDomain.Interface, streamDomain.Tag, streamDomain.SrcAddr, streamDomain.URL)
87 | if err != nil {
88 | log.Printf("域名分流== 添加域名分流 '%s' 失败:%s\n", streamDomain.Interface, err)
89 | } else {
90 | log.Printf("域名分流== 添加域名分流 '%s' 成功\n", streamDomain.Interface)
91 | if *delOldRule == "after" {
92 | //删除旧的域名分流
93 | err = iKuai.DelStreamDomainFromPreIds(preIds)
94 | if err == nil {
95 | log.Println("域名分流== 删除旧的运营商列表成功")
96 | } else {
97 | log.Println("域名分流== 删除旧的运营商列表有错误", err)
98 | }
99 | }
100 |
101 | }
102 | }
103 |
104 | GoroutineEnd2 = true
105 | }()
106 |
107 | for { //等待两个协程结束
108 | if GoroutineEnd1 && GoroutineEnd2 {
109 | break
110 | }
111 | time.Sleep(1 * time.Second)
112 | fmt.Printf(".")
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/api/custom_isp.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "errors"
5 | "log"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | const FUNC_NAME_CUSTOM_ISP = "custom_isp"
11 |
12 | type CustomIspData struct {
13 | Ipgroup string `json:"ipgroup"`
14 | Time string `json:"time"`
15 | ID int `json:"id"`
16 | Comment string `json:"comment"`
17 | Name string `json:"name"`
18 | }
19 |
20 | func (i *IKuai) ShowCustomIspByComment() (result []CustomIspData, err error) {
21 | param := struct {
22 | Type string `json:"TYPE"`
23 | Limit string `json:"limit"`
24 | OrderBy string `json:"ORDER_BY"`
25 | Order string `json:"ORDER"`
26 | }{
27 | Type: "data",
28 | }
29 | req := CallReq{
30 | FuncName: FUNC_NAME_CUSTOM_ISP,
31 | Action: "show",
32 | Param: ¶m,
33 | }
34 | resp := CallResp{Data: &CallRespData{Data: &result}}
35 | err = postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
36 | if err != nil {
37 | return
38 | }
39 | if resp.Result != 30000 {
40 | err = errors.New(resp.ErrMsg)
41 | return
42 | }
43 | return
44 | }
45 |
46 | func (i *IKuai) AddCustomIsp(name, tag, ipgroup string) error {
47 | // https://github.com/joyanhui/ikuai-bypass/issues/24
48 | // 去掉末尾空行
49 | ipgroup = strings.Trim(strings.Trim(ipgroup, "\n"), "\r")
50 | param := struct {
51 | Name string `json:"name"`
52 | Ipgroup string `json:"ipgroup"`
53 | Comment string `json:"comment"`
54 | }{
55 | Name: name,
56 | Ipgroup: ipgroup,
57 | Comment: COMMENT_IKUAI_BYPASS + "_" + tag,
58 | }
59 | req := CallReq{
60 | FuncName: FUNC_NAME_CUSTOM_ISP,
61 | Action: "add",
62 | Param: ¶m,
63 | }
64 | resp := CallResp{}
65 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
66 | if err != nil {
67 | return err
68 | }
69 | if resp.Result != 30000 {
70 | return errors.New(resp.ErrMsg)
71 | }
72 | return nil
73 | }
74 |
75 | func (i *IKuai) DelCustomIsp(id string) error {
76 | param := struct {
77 | Id string `json:"id"`
78 | }{
79 | Id: id,
80 | }
81 | req := CallReq{
82 | FuncName: FUNC_NAME_CUSTOM_ISP,
83 | Action: "del",
84 | Param: ¶m,
85 | }
86 | resp := CallResp{}
87 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
88 | if err != nil {
89 | return err
90 | }
91 | if resp.Result != 30000 {
92 | return errors.New(resp.ErrMsg)
93 | }
94 | return nil
95 | }
96 |
97 | // GetCustomIspAll 预备删除
98 | func (i *IKuai) GetCustomIspAll(tag string) (preIds string, err error) {
99 | log.Println("运营商/IP分流== 正在查询 备注为:", COMMENT_IKUAI_BYPASS+"_"+tag, "的运营商配置规则")
100 | preIds = ""
101 | err = nil
102 | //for loop := 0; loop < 3; loop++ {
103 | var data []CustomIspData
104 | data, err = i.ShowCustomIspByComment()
105 | var ids []string
106 | for _, d := range data {
107 | if d.Comment == COMMENT_IKUAI_BYPASS+"_"+tag {
108 | ids = append(ids, strconv.Itoa(d.ID))
109 | }
110 | }
111 | if len(ids) <= 0 {
112 | return
113 | }
114 |
115 | id := strings.Join(ids, ",")
116 | preIds = preIds + "||" + id
117 | //fmt.Println("preIds", preIds)
118 | //err = i.DelCustomIsp(id)
119 | //if err != nil {
120 | // return
121 | //}
122 | //}
123 | return
124 | }
125 |
126 | // DelCustomIspFromPreIds 删除
127 | func (i *IKuai) DelCustomIspFromPreIds(preIds string) (err error) {
128 | arr := strings.Split(preIds, "||")
129 | for _, id := range arr {
130 | if len(id) < 1 {
131 | continue
132 | }
133 | err = i.DelCustomIsp(id)
134 | if err != nil {
135 | return
136 | }
137 | }
138 | return
139 | }
140 |
141 | func (i *IKuai) DelCustomIspAll(cleanTag string) (err error) {
142 | for {
143 | var data []CustomIspData
144 | data, err = i.ShowCustomIspByComment()
145 | var ids []string
146 | for _, d := range data {
147 | if cleanTag == "cleanAll" {
148 | if d.Comment == COMMENT_IKUAI_BYPASS || strings.Contains(d.Comment, COMMENT_IKUAI_BYPASS) {
149 | ids = append(ids, strconv.Itoa(d.ID))
150 | }
151 | } else {
152 | if d.Comment == cleanTag {
153 | ids = append(ids, strconv.Itoa(d.ID))
154 | }
155 | }
156 |
157 | }
158 | if len(ids) <= 0 {
159 | return
160 | }
161 | id := strings.Join(ids, ",")
162 | err = i.DelCustomIsp(id)
163 | if err != nil {
164 | return
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/release.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python3
2 | import argparse
3 | import logging
4 | import os
5 | import subprocess
6 | import zipfile
7 |
8 | parser = argparse.ArgumentParser()
9 | parser.add_argument("-upx", action="store_true")
10 | parser.add_argument("-i", type=int)
11 | args = parser.parse_args()
12 |
13 | PROJECT_NAME = 'ikuai-bypass'
14 | RELEASE_DIR = './release'
15 |
16 | logger = logging.getLogger(__name__)
17 |
18 | # more info: https://golang.org/doc/install/source
19 | # [(env : value),(env : value)]
20 | envs = [
21 | [['GOOS', 'darwin'], ['GOARCH', 'amd64']],
22 | [['GOOS', 'darwin'], ['GOARCH', 'arm64']],
23 |
24 | [['GOOS', 'linux'], ['GOARCH', '386']],
25 | [['GOOS', 'linux'], ['GOARCH', 'amd64']],
26 | [['GOOS', 'linux'], ['GOARCH', 'arm'], ['GOARM', '5']],
27 | [['GOOS', 'linux'], ['GOARCH', 'arm'], ['GOARM', '6']],
28 | [['GOOS', 'linux'], ['GOARCH', 'arm'], ['GOARM', '7']],
29 | [['GOOS', 'linux'], ['GOARCH', 'arm64']],
30 |
31 | # [['GOOS', 'linux'], ['GOARCH', 'mips'], ['GOMIPS', 'hardfloat']],
32 | [['GOOS', 'linux'], ['GOARCH', 'mips'], ['GOMIPS', 'softfloat']],
33 | # [['GOOS', 'linux'], ['GOARCH', 'mipsle'], ['GOMIPS', 'hardfloat']],
34 | [['GOOS', 'linux'], ['GOARCH', 'mipsle'], ['GOMIPS', 'softfloat']],
35 |
36 | # [['GOOS', 'linux'], ['GOARCH', 'mips64'], ['GOMIPS64', 'hardfloat']],
37 | [['GOOS', 'linux'], ['GOARCH', 'mips64'], ['GOMIPS64', 'softfloat']],
38 | # [['GOOS', 'linux'], ['GOARCH', 'mips64le'], ['GOMIPS64', 'hardfloat']],
39 | [['GOOS', 'linux'], ['GOARCH', 'mips64le'], ['GOMIPS64', 'softfloat']],
40 |
41 | [['GOOS', 'linux'], ['GOARCH', 'ppc64le']],
42 |
43 | [['GOOS', 'freebsd'], ['GOARCH', '386']],
44 | [['GOOS', 'freebsd'], ['GOARCH', 'amd64']],
45 |
46 | [['GOOS', 'windows'], ['GOARCH', '386']],
47 | [['GOOS', 'windows'], ['GOARCH', 'amd64']],
48 | ]
49 |
50 |
51 | def go_build():
52 | logger.info(f'building {PROJECT_NAME}')
53 |
54 | global envs
55 | if args.i:
56 | envs = [envs[args.i]]
57 |
58 | version = 'dev/unknown'
59 | try:
60 | version = subprocess.check_output('git describe --tags --long --always', shell=True).decode().rstrip()
61 | except subprocess.CalledProcessError as e:
62 | logger.error(f'get git tag failed: {e.args}')
63 |
64 | # try:
65 | # subprocess.check_call('go run ../ config gen config.yaml', shell=True, env=os.environ)
66 | # except Exception:
67 | # logger.exception('failed to generate config template')
68 | # raise
69 |
70 | for env in envs:
71 | os_env = os.environ.copy() # new env
72 |
73 | s = PROJECT_NAME
74 | for pairs in env:
75 | os_env[pairs[0]] = pairs[1] # add env
76 | s = s + '-' + pairs[1]
77 | zip_filename = s + '.zip'
78 |
79 | suffix = '.exe' if os_env['GOOS'] == 'windows' else ''
80 | bin_filename = PROJECT_NAME + suffix
81 |
82 | logger.info(f'building {zip_filename}')
83 | try:
84 | subprocess.check_call(
85 | f'go build -ldflags "-s -w -X main.version={version}" -trimpath -o {bin_filename} ../', shell=True,
86 | env=os_env)
87 |
88 | if args.upx:
89 | try:
90 | subprocess.check_call(f'upx -9 -q {bin_filename}', shell=True, stderr=subprocess.DEVNULL,
91 | stdout=subprocess.DEVNULL)
92 | except subprocess.CalledProcessError as e:
93 | logger.error(f'upx failed: {e.args}')
94 |
95 | with zipfile.ZipFile(zip_filename, mode='w', compression=zipfile.ZIP_DEFLATED,
96 | compresslevel=5) as zf:
97 | zf.write(bin_filename)
98 | zf.write('../README.md', 'README.md')
99 | zf.write('../config.yml', 'config.yml')
100 | # zf.write('../LICENSE', 'LICENSE')
101 |
102 | except subprocess.CalledProcessError as e:
103 | logger.error(f'build {zip_filename} failed: {e.args}')
104 | except Exception as e:
105 | logger.exception(f'unknown err: {e}')
106 |
107 |
108 | if __name__ == '__main__':
109 | logging.basicConfig(level=logging.INFO)
110 |
111 | if len(RELEASE_DIR) != 0:
112 | if not os.path.exists(RELEASE_DIR):
113 | os.mkdir(RELEASE_DIR)
114 | os.chdir(RELEASE_DIR)
115 | go_build()
116 |
--------------------------------------------------------------------------------
/config.yml:
--------------------------------------------------------------------------------
1 | ## 这时一个实例代码请根据你情况配置功能.如果不能直接访问github 请替换下面的url改为镜像地址
2 | ikuai-url: http://192.168.99.2 # 爱快网页控制台登陆地址 结尾不要加 "/",如在爱快docker内运行,网关就是爱快地址,可以不写,如不填写,则使用第一个接口的网关地址,
3 | username: admin # ikuai username 爱快登陆用户名
4 | password: admin888 # ikuai user password爱快登陆密码
5 | cron: 0 7 * * * # crontab 执行更新的周期 格式为linux crontab 格式 注意时区问题,也可以用 @every 24h00m00s 表示每间隔24小时执行一次 openwrt可能需要处理时区 https://github.com/joyanhui/ikuai-bypass/issues/33
6 | AddErrRetryWait: 10s # 自动重试时间间隔 时间格式为 10s 120s
7 | AddWait: 1s # 添加规则后等待时间 等待爱快反应 适合性能低的设备
8 |
9 | ip-group: # IP分组 和端口分流配合使用
10 | - name: 国内 # IP分组名称,分为多个时在名称后面拼接“序号”,如“国内_1” 因为爱快的ip分组没有备注功能,这里使用 IKUAI_BYPASS_<国内>_<01>来区分,
11 | # 为了实现先增加后删除,因爱快的分组增加时分组名称不能有同名的存在,所以在最后增加了4位数字作后缀,如“国内_12_1234”,分组名最大20个字条,减去占用的字符,这里的名称最多12个字符。
12 | ## IP分组 cidr 列表网址,每行一个,超过1000行会自动分为多个,ipv6 地址会被删除
13 | url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/cn.txt
14 | - name: Telegram
15 | url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/telegram.txt
16 | ipv6-group: # IP分组 和端口分流配合使用
17 | - name: 国内v6 # IP分组名称,分为多个时在名称后面拼接“序号”,如“国内v6_1” 因为爱快的ip分组没有备注功能,这里使用 IKUAI_BYPASS_<国内v6>_<01>来区分
18 | # 为了实现先增加后删除,因爱快的分组增加时分组名称不能有同名的存在,所以在最后增加了4位数字作后缀,如“国内_12_1234”,分组名最大20个字条,减去占用的字符,这里的名称最多12个字符。
19 | ## IP分组 cidr 列表网址,每行一个,超过1000行会自动分为多个,ipv4 地址会被删除
20 | url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/cn.txt
21 | stream-ipport: # 端口分流,与IP分组组合使用
22 | - type: 0 # 分流方式:0-外网线路,1-下一跳网关
23 | interface: wan1 # 分流线路 这里默认到wan1 运营商
24 | src-addr: 192.168.1.10-192.168.1.254 # 分流的源地址 对应 ip-group: 国内 的目标地址
25 | ip-group: 国内
26 | mode: 0 # 负载模式,0-新建连接数, 1-源IP, 2-源IP+源端口,3-源IP+目的IP,4-源IP+目的IP+目的端口,5-主备模式
27 | ifaceband: 0 # 线路绑定 (线路断开后禁止切换到其他线路) ,0-不勾选, 1-勾选
28 | - type: 1 # 分流方式:0-外网线路,1-下一跳网关
29 | nexthop: 192.168.1.2 # 下一跳网关 一般是openwrt地址
30 | src-addr: 192.168.1.10-192.168.1.254
31 | ip-group: Telegram
32 |
33 | custom-isp: # 自定义运营商 IP分流
34 | - name: 国内IP列表 # 自定义运营商名称
35 | ## 自定义运营商 cidr 列表网址,每行一个,超过5000行会自动分为多个,ipv6 地址会被删除
36 | ## 下面演示规则使用了ghproxy.com的代理,如果失效请自行更换或另外想办法,建议使用https://github.com/hunshcn/gh-proxy自建
37 | url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/cn.txt
38 | tag: ipcn # 规则的备注标签后缀 如果留空默认为自定义运营商名称
39 | - name: telegram
40 | url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/telegram.txt
41 | tag: iptg
42 | - name: google
43 | url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/google.txt
44 | tag: ipgoogle
45 | #- name: facebook
46 | # url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/facebook.txt
47 | # tag: ipfb
48 | #- name: twitter
49 | # url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/twitter.txt
50 | # tag: iptw
51 | #- name: cloudflare
52 | # url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/cloudflare.txt
53 | # tag: ipcf
54 | #- name: netflix
55 | # url: https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/netflix.txt
56 | # tag: ipnf
57 |
58 | stream-domain: # 域名分流 可选功能,优先级比ip分流高
59 | #- interface: wan2 # 分流线路
60 | # src-addr: 192.168.1.10-192.168.1.254 # 分流的源地址 多ip段用“,”分开 参考 https://github.com/joyanhui/ikuai-bypass/issues/1#issuecomment-1892763993
61 | # ## 域名列表网址,每行一个,超过1000行会自动分为多个
62 | # url: https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/greatfire.txt
63 | # 下面是强制走wan2的
64 | - interface: wan2
65 | src-addr: 192.168.1.10-192.168.1.254
66 | url: https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/gfw.txt
67 | tag: gfw
68 | - interface: wan2
69 | src-addr: 192.168.1.10-192.168.1.254
70 | # 作者自己维护的自定义的域名列表(只是演示,请自行维护) 强制走代理的域名
71 | url: https://raw.githubusercontent.com/joyanhui/ikuai-bypass/main/private/bypass-domain-list.txt
72 | tag: private_bypass
73 | # 下面是自动走wan1直连的,
74 | - interface: wan1
75 | src-addr: 192.168.1.10-192.168.1.254
76 | # 作者自己维护的自定义的域名列表(只是演示,请自行维护) 主要存放github的镜像网站等国内可以直连的镜像或其他类型域名
77 | url: https://raw.githubusercontent.com/joyanhui/ikuai-bypass/main/private/direct-domain-list.txt
78 | tag: private_direct
79 | #- interface: wan1
80 | # src-addr: 192.168.1.10-192.168.1.254
81 | # china-list 网址列表较大,不建议启用,在部分ikuai设备上可能会导致维护的时候超时出错。
82 | # url: https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/china-list.txt
83 | # tag: cn
84 | #- interface: wan1
85 | # src-addr: 192.168.1.10-192.168.1.254
86 | # url: https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/apple-cn.txt
87 | # tag: apple
88 | # 下面是去广告的
89 | # todo 以后会使用爱快的域名黑名单模式来实现广告过滤,暂时用线路分流功能屏蔽
90 | # wan99是一个不可用或者不通的线路。因为reject-list网址列表较大,如爱快性能不佳不建议启用。
91 | #- interface: wan99
92 | # src-addr: 192.168.1.10-192.168.1.254
93 | # url: https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/reject-list.txt
94 | # tag: adFilter
95 |
--------------------------------------------------------------------------------
/api/stream_domain.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "errors"
5 | "log"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | const FuncNameStreamDomain = "stream_domain"
11 |
12 | type StreamDomainData struct {
13 | Week string `json:"week"`
14 | Comment string `json:"comment"`
15 | Domain string `json:"domain"`
16 | SrcAddr string `json:"src_addr"`
17 | Interface string `json:"interface"`
18 | Time string `json:"time"`
19 | ID int `json:"id"`
20 | Enabled string `json:"enabled"`
21 | }
22 |
23 | func (i *IKuai) AddStreamDomain(iface, tag, srcAddr, domains string) error {
24 | // https://github.com/joyanhui/ikuai-bypass/issues/24
25 | // 去掉末尾空行
26 | domains = strings.Trim(strings.Trim(domains, "\n"), "\r")
27 | param := struct {
28 | Interface string `json:"interface"`
29 | SrcAddr string `json:"src_addr"`
30 | Domain string `json:"domain"`
31 | Comment string `json:"comment"`
32 | Week string `json:"week"`
33 | Time string `json:"time"`
34 | Enabled string `json:"enabled"`
35 | }{
36 | Interface: iface,
37 | SrcAddr: srcAddr,
38 | Domain: domains,
39 | Comment: COMMENT_IKUAI_BYPASS + "_" + tag,
40 | Week: "1234567",
41 | Time: "00:00-23:59",
42 | Enabled: "yes",
43 | }
44 | req := CallReq{
45 | FuncName: FuncNameStreamDomain,
46 | Action: "add",
47 | Param: ¶m,
48 | }
49 | resp := CallResp{}
50 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
51 | if err != nil {
52 | return err
53 | }
54 | if resp.Result != 30000 {
55 | return errors.New(resp.ErrMsg)
56 | }
57 | return nil
58 | }
59 |
60 | func (i *IKuai) ShowStreamDomainByComment(comment string) (result []StreamDomainData, err error) {
61 | param := struct {
62 | Type string `json:"TYPE"`
63 | Limit string `json:"limit"`
64 | OrderBy string `json:"ORDER_BY"`
65 | Order string `json:"ORDER"`
66 | Finds string `json:"FINDS"`
67 | Keywords string `json:"KEYWORDS"`
68 | }{
69 | Finds: "comment",
70 | Keywords: comment,
71 | Type: "data",
72 | }
73 | req := CallReq{
74 | FuncName: FuncNameStreamDomain,
75 | Action: "show",
76 | Param: ¶m,
77 | }
78 | resp := CallResp{Data: &CallRespData{Data: &result}}
79 | err = postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
80 | if err != nil {
81 | return
82 | }
83 | if resp.Result != 30000 {
84 | err = errors.New(resp.ErrMsg)
85 | return
86 | }
87 | return
88 | }
89 |
90 | func (i *IKuai) DelStreamDomain(id string) error {
91 | param := struct {
92 | Id string `json:"id"`
93 | }{
94 | Id: id,
95 | }
96 | req := CallReq{
97 | FuncName: FuncNameStreamDomain,
98 | Action: "del",
99 | Param: ¶m,
100 | }
101 | resp := CallResp{}
102 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
103 | if err != nil {
104 | return err
105 | }
106 | if resp.Result != 30000 {
107 | return errors.New(resp.ErrMsg)
108 | }
109 | return nil
110 | }
111 |
112 | // GetStreamDomainAll 为了防止误删,先查询,然后再删除
113 | func (i *IKuai) GetStreamDomainAll(tag string) (preIds string, err error) {
114 | log.Println("域名分流== 正在查询 备注为:", COMMENT_IKUAI_BYPASS+"_"+tag, "的域名分流规则")
115 | preIds = ""
116 | err = nil
117 | //for loop := 0; loop < 2; loop++ {
118 | var data []StreamDomainData
119 | data, err = i.ShowStreamDomainByComment(COMMENT_IKUAI_BYPASS + "_" + tag)
120 | if err != nil {
121 | return
122 | }
123 | var ids []string
124 | for _, d := range data {
125 | if d.Comment == COMMENT_IKUAI_BYPASS+"_"+tag {
126 | ids = append(ids, strconv.Itoa(d.ID))
127 | }
128 | }
129 | if len(ids) <= 0 {
130 | return
131 | }
132 | id := strings.Join(ids, ",")
133 | preIds = preIds + "||" + id
134 | //}
135 | return
136 | }
137 |
138 | // DelStreamDomainFromPreIds 从预备删除的id中删除
139 | func (i *IKuai) DelStreamDomainFromPreIds(preIds string) (err error) {
140 | arr := strings.Split(preIds, "||")
141 | for _, id := range arr {
142 | if len(id) < 1 {
143 | continue
144 | }
145 | err = i.DelStreamDomain(id)
146 | if err != nil {
147 | return
148 | }
149 | }
150 | return
151 |
152 | }
153 |
154 | // DelStreamDomainAll 删除所有的域名分流规则
155 | func (i *IKuai) DelStreamDomainAll(cleanTag string) (err error) {
156 | for {
157 | var data []StreamDomainData
158 | data, err = i.ShowStreamDomainByComment(COMMENT_IKUAI_BYPASS)
159 | if err != nil {
160 | return
161 | }
162 | var ids []string
163 | for _, d := range data {
164 | if cleanTag == "cleanAll" {
165 | if d.Comment == COMMENT_IKUAI_BYPASS || strings.Contains(d.Comment, COMMENT_IKUAI_BYPASS) {
166 | ids = append(ids, strconv.Itoa(d.ID))
167 | }
168 | } else {
169 | if d.Comment == cleanTag {
170 | ids = append(ids, strconv.Itoa(d.ID))
171 | }
172 | }
173 | }
174 | if len(ids) <= 0 {
175 | return
176 | }
177 | id := strings.Join(ids, ",")
178 | err = i.DelStreamDomain(id)
179 | if err != nil {
180 | return
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/api/stream_ipport.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "errors"
5 | "log"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | const FUNC_NAME_STREAM_IPPORT = "stream_ipport"
11 |
12 | type StreamIpPortData struct {
13 | Protocol string `json:"protocol"`
14 | SrcPort string `json:"src_port"`
15 | ID int `json:"id"`
16 | Enabled string `json:"enabled"`
17 | Week string `json:"week"`
18 | Comment string `json:"comment"`
19 | Time string `json:"time"`
20 | Nexthop string `json:"nexthop"`
21 | IfaceBand int `json:"iface_band"`
22 | Interface string `json:"interface"`
23 | Mode int `json:"mode"`
24 | SrcAddr string `json:"src_addr"`
25 | DstPort string `json:"dst_port"`
26 | DstAddr string `json:"dst_addr"`
27 | Type int `json:"type"`
28 | }
29 |
30 | func (i *IKuai) AddStreamIpPort(forwardType string, iface string, dstAddr string, srcAddr string, nexthop string, tag string, mode int, ifaceband int) error {
31 |
32 | param := struct {
33 | Interface string `json:"interface"`
34 | Protocol string `json:"protocol"`
35 | Mode int `json:"mode"`
36 | DstAddr string `json:"dst_addr"`
37 | SrcAddr string `json:"src_addr"`
38 | Week string `json:"week"`
39 | Time string `json:"time"`
40 | Enabled string `json:"enabled"`
41 | Type string `json:"type"`
42 | Nexthop string `json:"nexthop"`
43 | IfaceBand int `json:"iface_band"`
44 | Comment string `json:"comment"`
45 | }{
46 | Interface: iface,
47 | Protocol: "any",
48 | Mode: mode,
49 | DstAddr: dstAddr,
50 | SrcAddr: srcAddr,
51 | Week: "1234567",
52 | Time: "00:00-23:59",
53 | Enabled: "yes",
54 | Type: forwardType,
55 | Nexthop: nexthop,
56 | IfaceBand: ifaceband,
57 | Comment: COMMENT_IKUAI_BYPASS + "_" + tag,
58 | }
59 | req := CallReq{
60 | FuncName: FUNC_NAME_STREAM_IPPORT,
61 | Action: "add",
62 | Param: ¶m,
63 | }
64 | resp := CallResp{}
65 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
66 | if err != nil {
67 | return err
68 | }
69 | if resp.Result != 30000 {
70 | return errors.New(resp.ErrMsg)
71 | }
72 | return nil
73 | }
74 |
75 | func (i *IKuai) ShowStreamIpPortByComment(comment string) (result []StreamIpPortData, err error) {
76 | param := struct {
77 | Type string `json:"TYPE"`
78 | Limit string `json:"limit"`
79 | OrderBy string `json:"ORDER_BY"`
80 | Order string `json:"ORDER"`
81 | Finds string `json:"FINDS"`
82 | Keywords string `json:"KEYWORDS"`
83 | }{
84 | Finds: "comment",
85 | Keywords: comment,
86 | Type: "data",
87 | }
88 | req := CallReq{
89 | FuncName: FUNC_NAME_STREAM_IPPORT,
90 | Action: "show",
91 | Param: ¶m,
92 | }
93 | resp := CallResp{Data: &CallRespData{Data: &result}}
94 | err = postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
95 | if err != nil {
96 | return
97 | }
98 | if resp.Result != 30000 {
99 | err = errors.New(resp.ErrMsg)
100 | return
101 | }
102 | return
103 | }
104 |
105 | func (i *IKuai) DelStreamIpPort(id string) error {
106 | param := struct {
107 | Id string `json:"id"`
108 | }{
109 | Id: id,
110 | }
111 | req := CallReq{
112 | FuncName: FUNC_NAME_STREAM_IPPORT,
113 | Action: "del",
114 | Param: ¶m,
115 | }
116 | resp := CallResp{}
117 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
118 | if err != nil {
119 | return err
120 | }
121 | if resp.Result != 30000 {
122 | return errors.New(resp.ErrMsg)
123 | }
124 | return nil
125 | }
126 |
127 | func (i *IKuai) DelIKuaiBypassStreamIpPort(cleanTag string) (err error) {
128 | for {
129 | var data []StreamIpPortData
130 | data, err = i.ShowStreamIpPortByComment(COMMENT_IKUAI_BYPASS)
131 | if err != nil {
132 | return
133 | }
134 | var ids []string
135 | for _, d := range data {
136 |
137 | if cleanTag == "cleanAll" {
138 | if d.Comment == COMMENT_IKUAI_BYPASS || strings.Contains(d.Comment, COMMENT_IKUAI_BYPASS) {
139 | ids = append(ids, strconv.Itoa(d.ID))
140 | }
141 | } else {
142 | if cleanTag == "" {
143 | cleanTag = COMMENT_IKUAI_BYPASS
144 | }
145 |
146 | if d.Comment == cleanTag || d.Comment == COMMENT_IKUAI_BYPASS+"_"+cleanTag {
147 | ids = append(ids, strconv.Itoa(d.ID))
148 | }
149 | }
150 |
151 | }
152 | if len(ids) <= 0 {
153 | return
154 | }
155 | id := strings.Join(ids, ",")
156 | err = i.DelStreamIpPort(id)
157 | if err != nil {
158 | return
159 | }
160 | }
161 | }
162 |
163 | func (i *IKuai) GetStreamIpPortIds(tag string) (preDelIds string, err error) {
164 | log.Println("端口分流== 正在查询 备注为:", COMMENT_IKUAI_BYPASS, "的端口分流规则")
165 | var data []StreamIpPortData
166 | data, err = i.ShowStreamIpPortByComment(COMMENT_IKUAI_BYPASS)
167 | if err != nil {
168 | return
169 | }
170 | var ids []string
171 |
172 | for _, d := range data {
173 | ids = append(ids, strconv.Itoa(d.ID))
174 | }
175 |
176 | if len(ids) <= 0 {
177 | return "", nil // 返回空字符串和 nil 错误
178 | }
179 |
180 | preDelIds = strings.Join(ids, ",") // 将 IDs 连接成逗号分隔的字符串
181 |
182 | return preDelIds, nil // 返回 IDs 和 nil 错误
183 | }
184 |
185 |
--------------------------------------------------------------------------------
/runModeUpdateIpgroup.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | func updateIpgroup() {
8 | iKuai, err := loginToIkuai()
9 | if err != nil {
10 | log.Println("登录爱快失败:", err)
11 | return
12 | }
13 |
14 | if *delOldRule == "before" {
15 | log.Println("Tips: 在添加之前会强制删除所有备注包含 IKUAI_BYPASS 字符的ip分组和端口分流")
16 | err = iKuai.DelIKuaiBypassIpGroup("cleanAll")
17 | if err != nil {
18 | log.Println("ip分组== 删除旧的IP分组失败,退出:", err)
19 | return
20 | } else {
21 | log.Println("ip分组== 删除旧的IP分组成功")
22 | }
23 | }
24 |
25 | for _, ipGroup := range conf.IpGroup {
26 | if *delOldRule == "after" {
27 | preIds, err := iKuai.GetIpGroup(ipGroup.Name)
28 | if err != nil {
29 | log.Println("ip分组== 获取准备更新的IP分组列表失败:", ipGroup.Name, err)
30 | continue
31 | } else {
32 | log.Println("ip分组== 获取准备更新的IP分组列表成功", ipGroup.Name, preIds)
33 | }
34 |
35 | err = updateIpGroup(iKuai, ipGroup.Name, ipGroup.URL)
36 | if err != nil {
37 | log.Printf("ip分组== 添加IP分组'%s@%s'失败:%s\n", ipGroup.Name, ipGroup.URL, err)
38 | } else {
39 | log.Printf("ip分组== 添加IP分组'%s@%s'成功\n", ipGroup.Name, ipGroup.URL)
40 | err = iKuai.DelIpGroup(preIds)
41 | if err == nil {
42 | log.Println("ip分组== 删除旧的IP分组列表成功", ipGroup.Name)
43 | log.Println("ip分组== 更新完成", ipGroup.Name)
44 | } else {
45 | log.Println("ip分组== 删除旧的IP分组列表有错误", ipGroup.Name, err)
46 | }
47 | }
48 | } else {
49 | err := updateIpGroup(iKuai, ipGroup.Name, ipGroup.URL)
50 | if err != nil {
51 | log.Printf("ip分组== 添加IP分组'%s@%s'失败:%s\n", ipGroup.Name, ipGroup.URL, err)
52 | } else {
53 | log.Printf("ip分组== 添加IP分组'%s@%s'成功\n", ipGroup.Name, ipGroup.URL)
54 | }
55 | }
56 | }
57 |
58 | if *delOldRule == "before" {
59 | err = iKuai.DelIKuaiBypassStreamIpPort("cleanAll")
60 | if err != nil {
61 | log.Println("端口分流== 删除旧的端口分流失败,退出:", err)
62 | return
63 | } else {
64 | log.Println("端口分流== 删除旧的端口分流成功")
65 | }
66 | }
67 | for _, streamIpPort := range conf.StreamIpPort {
68 | if *delOldRule == "after" {
69 | preIds, err := iKuai.GetStreamIpPortIds(streamIpPort.IpGroup)
70 | if err != nil {
71 | log.Println("端口分流== 获取准备更新的端口分流列表失败:", streamIpPort.IpGroup, err)
72 | continue
73 | } else {
74 | log.Println("端口分流== 获取准备更新的端口分流列表成功", streamIpPort.IpGroup, preIds)
75 | }
76 |
77 | err = updateStreamIpPort(
78 | iKuai,
79 | streamIpPort.Type,
80 | streamIpPort.Interface,
81 | streamIpPort.Nexthop,
82 | streamIpPort.SrcAddr,
83 | streamIpPort.IpGroup,
84 | streamIpPort.Mode,
85 | streamIpPort.IfaceBand,
86 | )
87 | if err != nil {
88 | log.Printf("端口分流== 添加端口分流 '%s@%s' 失败:%s\n",
89 | streamIpPort.Interface+streamIpPort.Nexthop,
90 | streamIpPort.IpGroup,
91 | err,
92 | )
93 | } else {
94 | log.Printf("端口分流== 添加端口分流 '%s@%s' 成功\n",
95 | streamIpPort.Interface+streamIpPort.Nexthop,
96 | streamIpPort.IpGroup,
97 | )
98 | err = iKuai.DelStreamIpPort(preIds)
99 | if err == nil {
100 | log.Println("端口分流== 删除旧的端口分流列表成功", streamIpPort.IpGroup, preIds)
101 | log.Println("端口分流== 更新完成", streamIpPort.IpGroup)
102 | } else {
103 | log.Println("端口分流== 删除旧的端口分流列表有错误", streamIpPort.IpGroup, err)
104 | }
105 | }
106 | } else {
107 | err := updateStreamIpPort(
108 | iKuai,
109 | streamIpPort.Type,
110 | streamIpPort.Interface,
111 | streamIpPort.Nexthop,
112 | streamIpPort.SrcAddr,
113 | streamIpPort.IpGroup,
114 | streamIpPort.Mode,
115 | streamIpPort.IfaceBand,
116 | )
117 | if err != nil {
118 | log.Printf("端口分流== 添加端口分流 '%s@%s' 失败:%s\n",
119 | streamIpPort.Interface+streamIpPort.Nexthop,
120 | streamIpPort.IpGroup,
121 | err,
122 | )
123 | } else {
124 | log.Printf("端口分流== 添加端口分流 '%s@%s' 成功\n",
125 | streamIpPort.Interface+streamIpPort.Nexthop,
126 | streamIpPort.IpGroup,
127 | )
128 | }
129 | }
130 | }
131 | }
132 |
133 | func updateIpv6group() {
134 | iKuai, err := loginToIkuai()
135 | if err != nil {
136 | log.Println("登录爱快失败:", err)
137 | return
138 | }
139 | if *delOldRule == "before" {
140 | log.Println("Tips: 在添加之前会强制删除所有备注包含 IKUAI_BYPASS 字符的ipv6分组")
141 | err = iKuai.DelIKuaiBypassIpv6Group("cleanAll")
142 | if err != nil {
143 | log.Println("ipv6分组== 删除旧的IPV6分组失败,退出:", err)
144 | return
145 | } else {
146 | log.Println("ipv6分组== 删除旧的IPV6分组成功")
147 | }
148 | }
149 | for _, ipv6Group := range conf.Ipv6Group {
150 | if *delOldRule == "after" {
151 | preIds, err := iKuai.GetIpv6Group(ipv6Group.Name)
152 | if err != nil {
153 | log.Println("ipv6分组== 获取准备更新的IPv6分组列表失败:", ipv6Group.Name, err)
154 | continue
155 | } else {
156 | log.Println("ipv6分组== 获取准备更新的IPv6分组列表成功", ipv6Group.Name, preIds)
157 | }
158 |
159 | err = updateIpv6Group(iKuai, ipv6Group.Name, ipv6Group.URL)
160 | if err != nil {
161 | log.Printf("ipv6分组== 添加IPV6分组'%s@%s'失败:%s\n", ipv6Group.Name, ipv6Group.URL, err)
162 | } else {
163 | log.Printf("ipv6分组== 添加IPV6分组'%s@%s'成功\n", ipv6Group.Name, ipv6Group.URL)
164 | err = iKuai.DelIpv6Group(preIds)
165 | if err == nil {
166 | log.Println("ipv6分组== 删除旧的IPv6分组列表成功", ipv6Group.Name, preIds)
167 | log.Println("ipv6分组== 更新完成", ipv6Group.Name)
168 | } else {
169 | log.Println("ipv6分组== 删除旧的IPv6分组列表有错误", ipv6Group.Name, err)
170 | }
171 | }
172 | } else {
173 | err := updateIpv6Group(iKuai, ipv6Group.Name, ipv6Group.URL)
174 | if err != nil {
175 | log.Printf("ipv6分组== 添加IPV6分组'%s@%s'失败:%s\n", ipv6Group.Name, ipv6Group.URL, err)
176 | } else {
177 | log.Printf("ipv6分组== 添加IPV6分组'%s@%s'成功\n", ipv6Group.Name, ipv6Group.URL)
178 | }
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/api/ip_group.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "errors"
5 | "log"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | const FUNC_NAME_IP_GROUP = "ipgroup"
11 |
12 | type IpGroupData struct {
13 | AddrPool string `json:"addr_pool"`
14 | Comment string `json:"comment"`
15 | GroupName string `json:"group_name"`
16 | ID int `json:"id"`
17 | Type int `json:"type"`
18 | }
19 |
20 | func (i *IKuai) ShowIpGroupByComment(comment string) (result []IpGroupData, err error) {
21 | param := struct {
22 | Finds string `json:"FINDS"`
23 | Keywords string `json:"KEYWORDS"`
24 | Type string `json:"TYPE"`
25 | Limit string `json:"limit"`
26 | OrderBy string `json:"ORDER_BY"`
27 | Order string `json:"ORDER"`
28 | }{
29 | Finds: "comment",
30 | Keywords: comment,
31 | Type: "data",
32 | }
33 | req := CallReq{
34 | FuncName: FUNC_NAME_IP_GROUP,
35 | Action: "show",
36 | Param: ¶m,
37 | }
38 | resp := CallResp{Data: &CallRespData{Data: &result}}
39 | err = postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
40 | if err != nil {
41 | return
42 | }
43 | if resp.Result != 30000 {
44 | err = errors.New(resp.ErrMsg)
45 | return
46 | }
47 | return
48 | }
49 |
50 | func (i *IKuai) ShowIpGroupByName(name string) (result []IpGroupData, err error) {
51 | param := struct {
52 | Finds string `json:"FINDS"`
53 | Keywords string `json:"KEYWORDS"`
54 | Type string `json:"TYPE"`
55 | Limit string `json:"limit"`
56 | OrderBy string `json:"ORDER_BY"`
57 | Order string `json:"ORDER"`
58 | }{
59 | Finds: "group_name",
60 | Keywords: name,
61 | Type: "data",
62 | }
63 | req := CallReq{
64 | FuncName: FUNC_NAME_IP_GROUP,
65 | Action: "show",
66 | Param: ¶m,
67 | }
68 | resp := CallResp{Data: &CallRespData{Data: &result}}
69 | err = postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
70 | if err != nil {
71 | return
72 | }
73 | if resp.Result != 30000 {
74 | err = errors.New(resp.ErrMsg)
75 | return
76 | }
77 | return
78 | }
79 |
80 | func (i *IKuai) AddIpGroup(groupName, addrPool string) error {
81 | param := struct {
82 | AddrPool string `json:"addr_pool"`
83 | Comment string `json:"comment"`
84 | GroupName string `json:"group_name"`
85 | NewRow bool `json:"newRow"`
86 | Type int `json:"type"`
87 | }{
88 | GroupName: groupName,
89 | AddrPool: addrPool,
90 | Comment: COMMENT_IKUAI_BYPASS + "_" + groupName, //自定义的备注无效的问题
91 | NewRow: true,
92 | Type: 0,
93 | }
94 | req := CallReq{
95 | FuncName: FUNC_NAME_IP_GROUP,
96 | Action: "add",
97 | Param: ¶m,
98 | }
99 | resp := CallResp{}
100 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
101 | if err != nil {
102 | return err
103 | }
104 | if resp.Result != 30000 {
105 | return errors.New(resp.ErrMsg)
106 | }
107 | return nil
108 | }
109 |
110 | func (i *IKuai) DelIpGroup(id string) error {
111 | param := struct {
112 | Id string `json:"id"`
113 | }{
114 | Id: id,
115 | }
116 | req := CallReq{
117 | FuncName: FUNC_NAME_IP_GROUP,
118 | Action: "del",
119 | Param: ¶m,
120 | }
121 | resp := CallResp{}
122 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
123 | if err != nil {
124 | return err
125 | }
126 | if resp.Result != 30000 {
127 | return errors.New(resp.ErrMsg)
128 | }
129 | return nil
130 | }
131 |
132 | func (i *IKuai) GetIpGroup(tag string) (preIds string, err error) {
133 | log.Println("ip分组== 正在查询 备注为:", COMMENT_IKUAI_BYPASS+"_"+tag, "的ip分组规则")
134 | var tagComment = ""
135 | if tag == "" {
136 | tagComment = COMMENT_IKUAI_BYPASS
137 | } else {
138 | tagComment = COMMENT_IKUAI_BYPASS + "_" + tag
139 | }
140 |
141 | var ids []string // 初始化 ids 切片
142 |
143 | var data []IpGroupData
144 | data, err = i.ShowIpGroupByComment(tagComment) // 获取数据并处理错误
145 | if err != nil {
146 | return "", err // 返回错误
147 | }
148 |
149 | for _, d := range data {
150 | ids = append(ids, strconv.Itoa(d.ID))
151 | }
152 |
153 | // 如果没有找到匹配的IP分组,则返回空字符串和nil error
154 | if len(ids) <= 0 {
155 | return "", nil // 返回空字符串和 nil 错误
156 | }
157 |
158 | preIds = strings.Join(ids, ",") // 将 IDs 连接成逗号分隔的字符串
159 |
160 | return preIds, nil // 返回 IDs 和 nil 错误
161 | }
162 |
163 | func (i *IKuai) DelIKuaiBypassIpGroup(cleanTag string) (err error) {
164 |
165 | for {
166 | var data []IpGroupData
167 | data, err = i.ShowIpGroupByComment(COMMENT_IKUAI_BYPASS)
168 | var ids []string
169 | for _, d := range data {
170 | //log.Println("在判断:", d.GroupName, d.Comment)
171 | if cleanTag == "cleanAll" {
172 | if d.Comment == COMMENT_IKUAI_BYPASS || strings.Contains(d.Comment, COMMENT_IKUAI_BYPASS) {
173 | ids = append(ids, strconv.Itoa(d.ID))
174 | }
175 | } else {
176 | if cleanTag == "" {
177 | cleanTag = COMMENT_IKUAI_BYPASS
178 | }
179 | if d.Comment == cleanTag || d.Comment == COMMENT_IKUAI_BYPASS+"_"+cleanTag {
180 | ids = append(ids, strconv.Itoa(d.ID))
181 | }
182 | }
183 | }
184 | if len(ids) <= 0 {
185 | return
186 | }
187 | id := strings.Join(ids, ",")
188 | err = i.DelIpGroup(id)
189 | if err != nil {
190 | return
191 | }
192 | }
193 | }
194 |
195 | func (i *IKuai) GetAllIKuaiBypassIpGroupNamesByName(name string) (names []string, err error) {
196 | var data []IpGroupData
197 | data, err = i.ShowIpGroupByName(name)
198 |
199 | for _, d := range data {
200 | // for https://github.com/joyanhui/ikuai-bypass/issues/30
201 | // fix 前面修改ip分组的备注导致的 无法甄别ip分组的问题
202 | //match, _ := regexp.MatchString(name+`_\d+`, d.GroupName)
203 | //log.Println(d.GroupName)
204 | match := strings.Contains(d.GroupName, name)
205 | if (d.Comment == COMMENT_IKUAI_BYPASS || strings.Contains(d.Comment, COMMENT_IKUAI_BYPASS)) && match {
206 | names = append(names, d.GroupName)
207 | }
208 | }
209 | return
210 | }
211 |
--------------------------------------------------------------------------------
/api/ipv6_group.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "errors"
5 | "log"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | const FUNC_NAME_IPV6_GROUP = "ipv6group"
11 |
12 | type Ipv6GroupData struct {
13 | AddrPool string `json:"addr_pool"`
14 | Comment string `json:"comment"`
15 | GroupName string `json:"group_name"`
16 | ID int `json:"id"`
17 | Type int `json:"type"`
18 | }
19 |
20 | func (i *IKuai) ShowIpv6GroupByComment(comment string) (result []Ipv6GroupData, err error) {
21 | param := struct {
22 | Finds string `json:"FINDS"`
23 | Keywords string `json:"KEYWORDS"`
24 | Type string `json:"TYPE"`
25 | Limit string `json:"limit"`
26 | OrderBy string `json:"ORDER_BY"`
27 | Order string `json:"ORDER"`
28 | }{
29 | Finds: "comment",
30 | Keywords: comment,
31 | Type: "data",
32 | }
33 | req := CallReq{
34 | FuncName: FUNC_NAME_IPV6_GROUP,
35 | Action: "show",
36 | Param: ¶m,
37 | }
38 | resp := CallResp{Data: &CallRespData{Data: &result}}
39 | err = postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
40 | if err != nil {
41 | return
42 | }
43 | if resp.Result != 30000 {
44 | err = errors.New(resp.ErrMsg)
45 | return
46 | }
47 | return
48 | }
49 |
50 | func (i *IKuai) ShowIpv6GroupByName(name string) (result []Ipv6GroupData, err error) {
51 | param := struct {
52 | Finds string `json:"FINDS"`
53 | Keywords string `json:"KEYWORDS"`
54 | Type string `json:"TYPE"`
55 | Limit string `json:"limit"`
56 | OrderBy string `json:"ORDER_BY"`
57 | Order string `json:"ORDER"`
58 | }{
59 | Finds: "group_name",
60 | Keywords: name,
61 | Type: "data",
62 | }
63 | req := CallReq{
64 | FuncName: FUNC_NAME_IPV6_GROUP,
65 | Action: "show",
66 | Param: ¶m,
67 | }
68 | resp := CallResp{Data: &CallRespData{Data: &result}}
69 | err = postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
70 | if err != nil {
71 | return
72 | }
73 | if resp.Result != 30000 {
74 | err = errors.New(resp.ErrMsg)
75 | return
76 | }
77 | return
78 | }
79 |
80 | func (i *IKuai) AddIpv6Group(groupName, addrPool string) error {
81 | param := struct {
82 | AddrPool string `json:"addr_pool"`
83 | Comment string `json:"comment"`
84 | GroupName string `json:"group_name"`
85 | NewRow bool `json:"newRow"`
86 | Type int `json:"type"`
87 | }{
88 | GroupName: groupName,
89 | AddrPool: addrPool,
90 | Comment: COMMENT_IKUAI_BYPASS + "_" + groupName, //自定义的备注无效的问题
91 | NewRow: true,
92 | Type: 0,
93 | }
94 | req := CallReq{
95 | FuncName: FUNC_NAME_IPV6_GROUP,
96 | Action: "add",
97 | Param: ¶m,
98 | }
99 | resp := CallResp{}
100 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
101 | if err != nil {
102 | return err
103 | }
104 | if resp.Result != 30000 {
105 | return errors.New(resp.ErrMsg)
106 | }
107 | return nil
108 | }
109 |
110 | func (i *IKuai) DelIpv6Group(id string) error {
111 | param := struct {
112 | Id string `json:"id"`
113 | }{
114 | Id: id,
115 | }
116 | req := CallReq{
117 | FuncName: FUNC_NAME_IPV6_GROUP,
118 | Action: "del",
119 | Param: ¶m,
120 | }
121 | resp := CallResp{}
122 | err := postJson(i.client, i.baseurl+"/Action/call", &req, &resp)
123 | if err != nil {
124 | return err
125 | }
126 | if resp.Result != 30000 {
127 | return errors.New(resp.ErrMsg)
128 | }
129 | return nil
130 | }
131 |
132 | func (i *IKuai) GetIpv6Group(tag string) (preIds string, err error) {
133 | log.Println("ipv6分组== 正在查询 备注为:", COMMENT_IKUAI_BYPASS+"_"+tag, "的IPv6分组规则")
134 | var tagComment = ""
135 | if tag == "" {
136 | tagComment = COMMENT_IKUAI_BYPASS
137 | } else {
138 | tagComment = COMMENT_IKUAI_BYPASS + "_" + tag
139 | }
140 |
141 | var ids []string // 初始化 ids 切片
142 |
143 | var data []Ipv6GroupData
144 | data, err = i.ShowIpv6GroupByComment(tagComment) // 获取数据并处理错误
145 | if err != nil {
146 | return "", err // 返回错误
147 | }
148 |
149 | for _, d := range data {
150 | ids = append(ids, strconv.Itoa(d.ID))
151 | }
152 |
153 | // 如果没有找到匹配的IP分组,则返回空字符串和nil error
154 | if len(ids) <= 0 {
155 | return "", nil // 返回空字符串和 nil 错误
156 | }
157 |
158 | preIds = strings.Join(ids, ",") // 将 IDs 连接成逗号分隔的字符串
159 |
160 | return preIds, nil // 返回 IDs 和 nil 错误
161 | }
162 |
163 | func (i *IKuai) DelIKuaiBypassIpv6Group(cleanTag string) (err error) {
164 |
165 | for {
166 | var data []Ipv6GroupData
167 | data, err = i.ShowIpv6GroupByComment(COMMENT_IKUAI_BYPASS)
168 | var ids []string
169 | for _, d := range data {
170 | //log.Println("在判断:", d.GroupName, d.Comment)
171 | if cleanTag == "cleanAll" {
172 | if d.Comment == COMMENT_IKUAI_BYPASS || strings.Contains(d.Comment, COMMENT_IKUAI_BYPASS) {
173 | ids = append(ids, strconv.Itoa(d.ID))
174 | }
175 | } else {
176 | if cleanTag == "" {
177 | cleanTag = COMMENT_IKUAI_BYPASS
178 | }
179 | if d.Comment == cleanTag || d.Comment == COMMENT_IKUAI_BYPASS+"_"+cleanTag {
180 | ids = append(ids, strconv.Itoa(d.ID))
181 | }
182 | }
183 | }
184 | if len(ids) <= 0 {
185 | return
186 | }
187 | id := strings.Join(ids, ",")
188 | err = i.DelIpv6Group(id)
189 | if err != nil {
190 | return
191 | }
192 | }
193 | }
194 |
195 | func (i *IKuai) GetAllIKuaiBypassIpv6GroupNamesByName(name string) (names []string, err error) {
196 | var data []Ipv6GroupData
197 | data, err = i.ShowIpv6GroupByName(name)
198 |
199 | for _, d := range data {
200 | // for https://github.com/joyanhui/ikuai-bypass/issues/30
201 | // fix 前面修改ip分组的备注导致的 无法甄别ip分组的问题
202 | //match, _ := regexp.MatchString(name+`_\d+`, d.GroupName)
203 | //log.Println(d.GroupName)
204 | match := strings.Contains(d.GroupName, name)
205 | if (d.Comment == COMMENT_IKUAI_BYPASS || strings.Contains(d.Comment, COMMENT_IKUAI_BYPASS)) && match {
206 | names = append(names, d.GroupName)
207 | }
208 | }
209 | return
210 | }
211 |
--------------------------------------------------------------------------------
/helperFunc.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "io"
6 | "log"
7 | "net/http"
8 | "strconv"
9 | "strings"
10 | "time"
11 |
12 | "github.com/dscao/ikuai-bypass/api"
13 | "github.com/dscao/ikuai-bypass/router"
14 | )
15 |
16 | // 读取配置文件 到 conf
17 |
18 | // updateCustomIsp 更新运营商分流规则
19 | func updateCustomIsp(iKuai *api.IKuai, name string, tag string, url string) (err error) {
20 | log.Println("运营商/IP分流== http.get ...", url)
21 | resp, err := http.Get(url)
22 | if err != nil {
23 | return
24 | }
25 | if resp.StatusCode != 200 {
26 | err = errors.New(resp.Status)
27 | return
28 | }
29 | defer func(Body io.ReadCloser) {
30 | _ = Body.Close()
31 | }(resp.Body)
32 | body, err := io.ReadAll(resp.Body)
33 | if err != nil {
34 | return
35 | }
36 | ips := strings.Split(string(body), "\n")
37 | ips = removeIpv6AndRemoveEmptyLine(ips)
38 | log.Println("运营商/IP分流== ", name, tag, " 获取到", len(ips), "个ip")
39 | ipGroups := group(ips, 5000) //5000条
40 |
41 | for _, ig := range ipGroups {
42 | ipGroup := strings.Join(ig, ",")
43 | err = iKuai.AddCustomIsp(name, tag, ipGroup)
44 | if err != nil {
45 | log.Println("运营商/IP分流== ", name, tag, "添加失败,可能是列表太多了,添加太快,爱快没响应。", conf.AddErrRetryWait, "秒后重试", err)
46 | time.Sleep(conf.AddErrRetryWait)
47 | err = iKuai.AddCustomIsp(name, tag, ipGroup)
48 | if err != nil {
49 | log.Println("运营商/IP分流== ", name, tag, "重试失败,可能是列表太多了,添加太快,爱快没响应。已经重试过一次,所以跳过此次操作")
50 | break
51 | }
52 | }
53 | log.Println("运营商/IP分流== 添加ip:", len(ig), " 个,等待", conf.AddWait, "秒继续处理")
54 | time.Sleep(conf.AddWait)
55 | }
56 | return
57 | }
58 |
59 | // updateStreamDomain 更新域名分流规则
60 | func updateStreamDomain(iKuai *api.IKuai, iface, tag, srcAddr, url string) (err error) {
61 | log.Println("域名分流== http.get ...", url)
62 | resp, err := http.Get(url)
63 | if err != nil {
64 | return
65 | }
66 | if resp.StatusCode != 200 {
67 | err = errors.New(resp.Status)
68 | return
69 | }
70 | defer func(Body io.ReadCloser) {
71 | _ = Body.Close()
72 | }(resp.Body)
73 | body, err := io.ReadAll(resp.Body)
74 | if err != nil {
75 | return
76 | }
77 | domains := strings.Split(string(body), "\n")
78 | log.Println("域名分流== ", iface, tag, "获取到", len(domains), "个域名")
79 | domainGroup := group(domains, 1000) //1000条
80 | var countFor int = 0
81 | for _, d := range domainGroup {
82 | countFor = countFor + 1
83 | log.Println("域名分流== ", countFor, "/", len(domainGroup), iface, tag, " 正在添加 .... ")
84 | domain := strings.Join(d, ",")
85 | err = iKuai.AddStreamDomain(iface, tag, srcAddr, domain)
86 | if err != nil {
87 | log.Println("域名分流== ", countFor, "/", len(domainGroup), iface, tag, "添加失败,可能是列表太多了,添加太快,爱快没响应。", conf.AddErrRetryWait, "秒后重试", err)
88 | time.Sleep(conf.AddErrRetryWait)
89 | err = iKuai.AddStreamDomain(iface, tag, srcAddr, domain)
90 | if err != nil {
91 | log.Println("域名分流= ", countFor, "/", len(domainGroup), iface, tag, "重试失败,可能是列表太多了,添加太快,爱快没响应。已经重试过一次,所以跳过此次操作")
92 | break
93 | }
94 | } else {
95 | log.Println("域名分流== ", iface, tag, " 添加域名:", len(d), " 个成功,等待", conf.AddWait, "秒继续处理")
96 | time.Sleep(conf.AddWait)
97 | }
98 | }
99 | return
100 | }
101 |
102 | func removeIpv6AndRemoveEmptyLine(ips []string) []string {
103 | log.Println("移除ipv6地址 和删除空行....")
104 | i := 0
105 | for _, ip := range ips {
106 | if !strings.Contains(ip, ":") {
107 | //删除空行
108 | ip = strings.Trim(strings.Trim(ip, "\n"), "\r")
109 | if ip != "" {
110 | ips[i] = ip
111 | i++
112 | }
113 |
114 | }
115 | }
116 | return ips[:i]
117 | }
118 |
119 | func removeIpv4AndRemoveEmptyLine(ips []string) []string {
120 | log.Println("移除ipv4地址 和删除空行....")
121 | i := 0
122 | for _, ip := range ips {
123 | if strings.Contains(ip, ":") { // 检查IPv6地址特征
124 | // 清理首尾的空白字符和换行符
125 | ip = strings.TrimSpace(ip)
126 | if ip != "" {
127 | ips[i] = ip
128 | i++
129 | }
130 | }
131 | }
132 | return ips[:i]
133 | }
134 |
135 | func group(arr []string, subGroupLength int64) [][]string {
136 | groupMax := int64(len(arr))
137 | var segmens = make([][]string, 0)
138 | quantity := groupMax / subGroupLength
139 | remainder := groupMax % subGroupLength
140 | i := int64(0)
141 | for i = int64(0); i < quantity; i++ {
142 | segmens = append(segmens, arr[i*subGroupLength:(i+1)*subGroupLength])
143 | }
144 | if quantity == 0 || remainder != 0 {
145 | segmens = append(segmens, arr[i*subGroupLength:i*subGroupLength+remainder])
146 | }
147 | return segmens
148 | }
149 |
150 | // 更新ip分组
151 | func updateIpGroup(iKuai *api.IKuai, name, url string) (err error) {
152 | log.Println("ip分组== http.get ...", url)
153 | resp, err := http.Get(url)
154 | if err != nil {
155 | return
156 | }
157 | if resp.StatusCode != 200 {
158 | err = errors.New(resp.Status)
159 | }
160 | defer resp.Body.Close()
161 | body, err := io.ReadAll(resp.Body)
162 | if err != nil {
163 | return
164 | }
165 | ips := strings.Split(string(body), "\n")
166 | ips = removeIpv6AndRemoveEmptyLine(ips)
167 | ipGroups := group(ips, 1000)
168 | last4 := ""
169 | if *isIpGroupNameAddRandomSuff == "1" { //https://github.com/joyanhui/ikuai-bypass/issues/76
170 | timestamp := time.Now().Unix() // 秒级时间戳(10位,如 1620000000)
171 | str := strconv.FormatInt(timestamp, 10)
172 | last4 = "_" + str[len(str)-4:] // 截取字符串最后4位,防止分组名重复导致无法先增加后删除,分组名称最多20个字符,除去 _0_1234 分组名设置中最多13个。
173 | }
174 |
175 | for index, ig := range ipGroups {
176 | log.Println("ip分组== ", index, " 正在添加 .... ")
177 | ipGroup := strings.Join(ig, ",")
178 | err := iKuai.AddIpGroup(name+"_"+strconv.Itoa(index)+last4, ipGroup)
179 | if err != nil {
180 | log.Println("ip分组== ", index, "添加失败,可能是列表太多了,添加太快,爱快没响应。", conf.AddErrRetryWait, "秒后重试", err)
181 | time.Sleep(conf.AddWait)
182 | }
183 |
184 | }
185 | return
186 | }
187 |
188 | // 更新ipv6分组
189 | func updateIpv6Group(iKuai *api.IKuai, name, url string) (err error) {
190 | log.Println("ipv6分组== http.get ...", url)
191 | resp, err := http.Get(url)
192 | if err != nil {
193 | return
194 | }
195 | if resp.StatusCode != 200 {
196 | err = errors.New(resp.Status)
197 | }
198 | defer resp.Body.Close()
199 | body, err := io.ReadAll(resp.Body)
200 | if err != nil {
201 | return
202 | }
203 | ips := strings.Split(string(body), "\n")
204 | ips = removeIpv4AndRemoveEmptyLine(ips)
205 | ipGroups := group(ips, 1000)
206 | last4 := ""
207 | if *isIpGroupNameAddRandomSuff == "1" { //https://github.com/joyanhui/ikuai-bypass/issues/76
208 | timestamp := time.Now().Unix() // 秒级时间戳(10位,如 1620000000)
209 | str := strconv.FormatInt(timestamp, 10)
210 | last4 = "_" + str[len(str)-4:] // 截取字符串最后4位,防止分组名重复导致无法先增加后删除,分组名称最多20个字符,除去 _0_1234 分组名设置中最多13个。
211 | }
212 | for index, ig := range ipGroups {
213 | log.Println("ipv6分组== ", index, " 正在添加 .... ")
214 | ipGroup := strings.Join(ig, ",")
215 | err := iKuai.AddIpv6Group(name+"_"+strconv.Itoa(index)+last4, ipGroup)
216 | if err != nil {
217 | log.Println("ipv6分组== ", index, "添加失败,可能是列表太多了,添加太快,爱快没响应。", conf.AddErrRetryWait, "秒后重试", err)
218 | time.Sleep(conf.AddWait)
219 | }
220 | }
221 | return
222 | }
223 |
224 | // 更新ip端口分流
225 | func updateStreamIpPort(iKuai *api.IKuai, forwardType string, iface string, nexthop string, srcAddr string, ipGroup string, mode int, ifaceband int) (err error) {
226 |
227 | var ipGroupList []string
228 | for _, ipGroupItem := range strings.Split(ipGroup, ",") {
229 | var data []string
230 | data, err = iKuai.GetAllIKuaiBypassIpGroupNamesByName(ipGroupItem)
231 | if err != nil {
232 | return
233 | }
234 | ipGroupList = append(ipGroupList, data...)
235 | }
236 | err = iKuai.AddStreamIpPort(forwardType, iface, strings.Join(ipGroupList, ","), srcAddr, nexthop, ipGroup, mode, ifaceband)
237 | if err != nil {
238 | log.Println("ip端口分流== 添加失败,可能是列表太多了,添加太快,爱快没响应。", conf.AddErrRetryWait, "秒后重试", err)
239 | time.Sleep(conf.AddErrRetryWait)
240 | }
241 | return
242 | }
243 |
244 | // 登陆爱快
245 | func loginToIkuai() (*api.IKuai, error) {
246 | err := readConf(*confPath)
247 | if err != nil {
248 | log.Println("读取配置文件失败:", err)
249 | return nil, err
250 | }
251 | if *ikuaiLoginInfo != "" {
252 | log.Println("使用命令行参数登陆爱快")
253 | ikuaiLoginInfoArr := strings.Split(*ikuaiLoginInfo, ",")
254 | if len(ikuaiLoginInfoArr) != 3 {
255 | log.Println(*ikuaiLoginInfo)
256 | log.Println("命令行参数格式错误,请使用 -login http://ip|username|password ")
257 | return nil, errors.New("命令行参数格式错误,请使用 -login=\"ip|username|password\"")
258 | }
259 | iKuai := api.NewIKuai(ikuaiLoginInfoArr[0])
260 | err = iKuai.Login(ikuaiLoginInfoArr[1], ikuaiLoginInfoArr[2])
261 | if err != nil {
262 | log.Println("ikuai 登陆失败:", *ikuaiLoginInfo, err)
263 | return nil, err
264 | } else {
265 | log.Println("ikuai 登录成功", ikuaiLoginInfoArr[0])
266 | return iKuai, nil
267 | }
268 | } else {
269 | baseurl := conf.IkuaiURL
270 | if baseurl == "" {
271 | gateway, err := router.GetGateway()
272 | if err != nil {
273 | log.Println("获取默认网关失败:", err)
274 | return nil, err
275 | }
276 | baseurl = "http://" + gateway
277 | log.Println("使用默认网关地址:", baseurl)
278 | }
279 | iKuai := api.NewIKuai(baseurl)
280 | err = iKuai.Login(conf.Username, conf.Password)
281 | if err != nil {
282 | log.Println("ikuai 登陆失败:", baseurl, err)
283 | return iKuai, err
284 | } else {
285 | log.Println("ikuai 登录成功", baseurl)
286 | return iKuai, nil
287 | }
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iKuai Bypass
2 |
3 | 通过自定义运营商分流或者端口分流的方式实现非旁路由方式的自动分流,并定时更新规则.实现国内ip或者指定的ip或者指定的域名列表走直连或者经过openwrt等另外处理.爱快有简单易用的分流功能可以实现诸多功能,但是因为大家喜闻乐见的分流规则数据可能几万条,在ikuai上维护更新比较麻烦,这个工具就是为了自动从订阅地址更新爱快的分流规则的域名分流和运营商分流.
4 |
5 | > 使用问题交流请去恩山或[discussions](https://github.com/joyanhui/ikuai-bypass/discussions)
6 |
7 | > bug反馈和建议都可以提[issues](https://github.com/joyanhui/ikuai-bypass/issues),我会尽快处理.给个star我会很开心.
8 |
9 | > PR请尽量通过一次commit提交过来.
10 |
11 | > 关于dns分流解析,建议用ADGuard home自建,这里有一个本人维护的支持自动更新相关规则文件的mosdns的docker.[[joyanhui/adguardhome-rules]](https://github.com/joyanhui/adguardhome-rules).可以简单自动更新dns分流解析规则,广告屏蔽,以及ipv4优先等功能
12 |
13 | ## 版本选择
14 |
15 | - v2.1.2-alpha1 虽然是alpha版,但是功能已经稳定 存在少量不影响使用的bug
16 | - v3.0.0 版本 增加了ipv6分组 由 [[dscao]](https://github.com/dscao) 提供
17 |
18 | ### ip分组和端口分流方式 说明:
19 |
20 | ikuai 可以通过分流规则 让国内ip直连(或其他ip范围),非国内ip指定下一跳网关通常是openwrt.再把出口流量给上游运营商.实现方式参考[[#7]](https://github.com/joyanhui/ikuai-bypass/issues/7) 或者 [[恩山y2kji]](https://www.right.com.cn/forum/thread-8288009-1-1.html) 或B站网友的教程
21 |
22 | ### 自定义运营商分流方式 说明:
23 |
24 | ikuai 可以通过分流规则 把openwrt或者其他路由作为爱快的上级虚拟运营商,同时作为ikuai的下级路由,再把openwrt的出口流量绑回到爱快实际的运营商,实现无感分流:国内网站访问速度更好、不用单独配置网关、openwrt炸了不影响正常上网、openwrt恢复后网络自愈.[查看具体实现方式](https://dev.leiyanhui.com/route/ikuai-bypass-joyanhui/) 或者查看 [恩山eezz](https://www.right.com.cn/forum/thread-8252571-1-1.html) 或者下文 [分流模式的简单说明](https://github.com/joyanhui/ikuai-bypass?tab=readme-ov-file#%E5%88%86%E6%B5%81%E6%A8%A1%E5%BC%8F%E7%AE%80%E5%8D%95%E8%AF%B4%E6%98%8E).这种方式比传统用openwrt的作为旁路由的指定网关的方案,或者only openwrt的方案更加稳定,速度更好. `缺点是 网络top看起来有一些复杂,非开箱即用`
25 |
26 | #### 运营商和域名分流模式简单说明
27 |
28 | 通常是爱快+openwrt的双路由方式,ikuai可以是物理机也可以是虚拟机.openwrt同样可以是物理机也可以是虚拟机,也可以是lxc/docker也可以部署到爱快内.ikuai需要分配3个网口(分别绑定到wan1 wan2 lan1),openwrt需要2个(wan和lan).可以是物理网卡也可以是虚拟网卡.
29 |
30 |
31 | 点击这里展开查看详细图文说明
32 |
33 |
34 |
35 | ## 主要修改点
36 |
37 | - 使用协程并发处理运营商/IP分流和域名分流/ip分组,更新速度更快.
38 | - 支持多配置文件同时运行.
39 | - 更新成功后再删除旧规则,原版会先删除,如果更新失败就全部丢了,这也是自己下手修改的主要原因. v2.0.1后版本增加参数调整更新后删除还是更新之前删除. [[#15]](https://github.com/joyanhui/ikuai-bypass/issues/15)
40 | - 支持清理模式,单次更新模式,先更新一次再等计划任务触发模式,等待计划任务触发模式.
41 | - 支持域名分流规则直接导出为爱快可导入的txt格式 [[#5]](https://github.com/joyanhui/ikuai-bypass/issues/5)
42 | - 支持无docker环境运行,当然也支持docker运行.
43 | - 编译了 linux macos windows freebsd 系统下arm5-7 arm64 mipsle mips64le ppc64le amd64 386 架构二进制,当然也支持openwrt、老毛子和有shell权限其他系统.
44 |
45 | ## 参数说明
46 |
47 | - `-c` : 配置文件路径 默认为当前目录下`config.yml` 可用相对路径或者绝对路径
48 | - `-m` : 是否启用ip分组和下一跳网关模式(端口分流)v2.0以后版本有效
49 | - `ispdomain` : 使用isp和域名分流功能(默认,为了兼容v1.x版本)
50 | - `ipgroup` : 使用ip分组和下一跳网关模式(端口分流)
51 | - `ipv6group` : 使用ipv6分组(配合acl控制使用优先允许国内ipv6,次要不允许所有ipv6)
52 | - `ii` : 同时使用 ispdomain 和 ipgroup 两种模式
53 | - `ip` : 同时使用 ipgroup 和ipv6group 两种模式
54 | - `-r` : 运行模式 默认为`cron`
55 | - `cron` : 先运行一次 而后等待计划任务触发
56 | - `nocron` 或 `once`或 `1`: 忽略配置文件的cron定时配置配置 运行一次然后就直接退出结束,适合调试使用或者使用系统自带的计划任务或serverless/函数计算等方式触发.
57 | - `cronAft` : 先不运行等计划任务触发
58 | - `clean` : 清理模式 默认可选附加参数为 `-tag cleanAll`
59 | - `exportDomainSteamToTxt` : 导出域名分流规则到txt文件模式 方便手动从爱快导入 默认可选附加参数为 `-exportPath /tmp` 暂时只支持域名分流功能导出,
60 | - `-tag` : 备注信息 `clean`清理模式下的附加参数
61 | - 默认为cleanAll(即清理所有备注中包含`IKUAI_BYPASS`字符的规则)
62 | - 单独指定备注的关键词 可以不添写`IKUAI_BYPASS_`前缀 例如`-r clean -tag ipcn` 或 `-r clean -tag IKUAI_BYPASS_ipcn`
63 | - `-exportPath` : 导出域名分流规则的路径
64 | - `-login` : ikuai登陆地址和账户密码,优先级比配置文件的优先级更高.格式: `http://10.1.1.1,admin,password` 为空则使用配置文件内登陆信息
65 | - `-delOldRule` : 删除旧规则顺序,默认为 `after`
66 | - `after` : 先更新规则成功后再删除旧规则
67 | - `before` : 先删除旧规则再更新新规则,如果更新失败会丢失规则
68 | - `- isIpGroupNameAddRandomSuff` ip分组名称是否增加随机数后缀(仅ip分组模式有效) 1为添加 0不添加 [[#76]](https://github.com/joyanhui/ikuai-bypass/issues/76)
69 |
70 | ## 更新日志
71 |
72 | - 2025-04-23 部分代码规范性处理以及nilness的逻辑修复
73 | - 2025-04-23 增加开关isIpGroupNameAddRandomSuff [[#76]](https://github.com/joyanhui/ikuai-bypass/issues/76)
74 | - 2025-04-23 修复域名分流规则末行空行的bug [[#24]](https://github.com/joyanhui/ikuai-bypass/issues/24)
75 | - 2025-03-25 增加端口分流时能够选择更多参数:负载模式、线路绑定,修复完善delOldRule参数,对于ip分组、ipv6分组及端口分流都默认为先增加后删除,防止增加失败导致原来的规则丢失.
76 | - 2025-03-23 增加ipv6分组
77 | - 2024-10-04 提供完整的最新的config.yml 文件,供参考
78 | - 2024-10-04 修复端口分流规则自动添加未能关联ip分组的bug,本次修改更新了一下config.yml的默认内容,请注意更新您的配置文件.[[#30]](https://github.com/joyanhui/ikuai-bypass/issues/30)
79 | - 2024-10-04 修复清理模式的删除规则问题 [[#27#issuecomment-2388114699]](https://github.com/joyanhui/ikuai-bypass/issues/27#issuecomment-2388114699)
80 | - 2024-10-04 ip分组第一行的备注问题 [[#22]](https://github.com/joyanhui/ikuai-bypass/issues/22)
81 | - 2024-10-04 修复 卡`ip分组== 正在查询 备注为: IKUAI_BYPASS_ 的ip分组规则` 的bug [[#24]](https://github.com/joyanhui/ikuai-bypass/issues/24) [[#27]](https://github.com/joyanhui/ikuai-bypass/issues/27)
82 | - 2024-10-04 修复运营商分流的ip列表会添加一个空行的bug [[#24]](https://github.com/joyanhui/ikuai-bypass/issues/24)
83 | - 2024-06-29 修复清理模式无法清理ip分组和端口分流规则的问题 v2.0.1以后版本有效
84 | - 2024-06-29 增加运营商和域名分流规则旧规则删除模式参数 `-delOldRule` [[#15]](https://github.com/joyanhui/ikuai-bypass/issues/15) v2.0.1以后版本有效
85 | - 2024-06-29 修改-m参数默认值错误导致的不配置-m参数无法执行的问题 构建 v2.0.0-beta2 版本 这是一个未经过详细测试的版本,请谨慎使用.
86 | - 2024-05-26 修复OLOrz996分支里端口分流规则模式无法删除的bug
87 | - 2024-05-26 合并ztc1997的ip分组和下一跳网关功能[[#7]](https://github.com/joyanhui/ikuai-bypass/issues/7) 增加了 `-m`参数
88 | - 2024-05-26 命令行参数增加-login参数,可以覆盖配置文件内的爱快地址和用户名密码
89 | - 2024-03-23 增加域名分流规则导出为爱快兼容的可导入的txt文件 [[5#2016320900]](https://github.com/joyanhui/ikuai-bypass/issues/5#issuecomment-2016320900)
90 | - 2024-03-23 尝试修复列表太多导致爱快处理超时的问题 [[#5]](https://github.com/joyanhui/ikuai-bypass/issues/5)
91 | - 2024-03-07 openwrt服务安装脚本增加无代理环境安装
92 | - 2024-02-25 增加去广告功能演示规则 [[参考]](https://github.com/joyanhui/ikuai-bypass/blob/main/config.yml)
93 | - 2024-02-7 添加一个openwrt下开机自动运行 [[参考脚本]](https://github.com/joyanhui/ikuai-bypass/blob/main/example/script/AddOpenwrtService.sh)
94 | - 2024-02-1 优化清理模式的提示信息,增加`once`或 `1`模式等同于nocron模式
95 | - 2024-02-1 某一分组规则更新失败导致相关的旧规则被删除的bug [[#3]](https://github.com/joyanhui/ikuai-bypass/issues/3)
96 | - 2024-02-1 清理模式增加附加参数`-tag` 可以清理全部备注名包含`IKUAI_BYPASS`的分流规则,或者指定备注名全程或者后缀名的分流规则
97 | - 旧的更新记录没啥价值也未单独记,小工具代码简单,请参考commit记录
98 |
99 | ## todo list
100 |
101 | - 在性能受限制的爱快上域名分流过多导致爱快接口超时,进一步导致ikuai-bypass出错的bug [[#5]](https://github.com/joyanhui/ikuai-bypass/issues/5)
102 | - [[done]]ip分组和端口分流规则 修改为成功后再删除旧规则
103 | - [[done]]优化ip分组和下一跳网关功能 可能存在的bug问题
104 | - [[done]](https://github.com/joyanhui/ikuai-bypass/issues/33)时区问题需要检查
105 |
106 | ## 简要使用说明
107 |
108 | 从 Releases [[下载]](https://github.com/joyanhui/ikuai-bypass/releases) 解压后得到一个可执行文件`ikuai-bypass`和一个通用配置文件 `config.yml` [[参考]](https://github.com/joyanhui/ikuai-bypass/blob/main/config.yml) 编辑一下`config.yml`里面ikuai的地址用户名和密码,然后在可以访问到ikuai的设备上执行命令格式如下: ` ./ikuai-bypass -c /配置文件路径/config.yml -r 运行模式`即可.example: `./ikuai-bypass` 等同 `./ikuai-bypass -c config.yml -r cron`: 将根据配置文件的内容更新分流规则更新成功后删除旧的分流规则 并在配置文件的cron的时间按照计划任务 重新更新. `./ikuai-bypas -r clean` 等同 `./ikuai-bypass -c config.yml -r clean -tag cleanAll` : 删除所有备注包含 `IKUAI_BYPASS`的规则 `./ikuai-bypas -r clean -tag IKUAI_BYPASS_ipcn` 等同 `./ikuai-bypas -r clean -tag ipcn`: 删除备注为 `IKUAI_BYPASS_ipcn` 的分流规则
109 |
110 | ## 不同平台下
111 |
112 | ### linux(推荐openwrt内直接运行)
113 |
114 | 下载 linux-xxx.zip,unzip 后在shell运行. 建议把ikuai-bypass作为服务安装到openwrt [[参考安装脚本]](https://github.com/joyanhui/ikuai-bypass/blob/main/example/script/AddOpenwrtService.sh)
115 |
116 | ### docker
117 |
118 | 下载linux版本,参考命令如下
119 |
120 | ```sh
121 | mkdir ~/ikuai-bypass/ && cd ~/ikuai-bypass
122 | # 下载amd64版本,如arm版本自行修改
123 | wget -c https://github.com/joyanhui/ikuai-bypass/releases/download/v0.2.2/ikuai-bypass-linux-amd64.zip
124 | unzip ikuai-bypass-linux-amd64.zip
125 | # 编辑默认的 config.yml 略
126 | # 创建容器 docker/podman
127 | docker run -itd --name ikuai-bypass --privileged=true --restart=always \
128 | -v ~/ikuai-bypass/:/opt/ikuai-bypass/ \
129 | alpine:3.18.4 /opt/ikuai-bypass/ikuai-bypass -c /opt/ikuai-bypass/config.yml -r cron
130 | ```
131 |
132 | ### ikuai docker下
133 |
134 | `ikuai`无法直接执行`shell`命令,基于`golang`的`ikuai-bypass`没有外部依赖只是一个可执行文件.
135 |
136 | 如果您要在`ikuai`的`docker`内运行,请自行下载`linux`版本的Release,解压后,上传可执行文件和修改后的配置文件到`ikuai`.
137 |
138 | 例如创建对应的docker目录并上传:
139 |
140 | ```
141 | /data0/Docker/ikuai-bypass/ikuai-bypass
142 | /data0/Docker/ikuai-bypass/config.yml
143 | ```
144 |
145 | 而后在`ikuai`的`docker`中下载通用`linux`镜像,推荐`alpine:lastest`(实测Ubuntu,busybox等镜像不安装证书扩展会报github加速代理的证书问题).
146 |
147 | 创建`docker`目录挂载`/data0/Docker/ikuai-bypass/`到容器内`/opt/ikuai-bypass/`
148 |
149 | 启动命令设置为:
150 |
151 | ```sh
152 | /bin/sh -c "chmod +x /opt/ikuai-bypass/ikuai-bypass && /opt/ikuai-bypass/ikuai-bypass -r cron -c /opt/ikuai-bypass/config.yml"
153 | ```
154 |
155 | 再启动即可.
156 |
157 | ### 群晖或docker-compose:
158 |
159 | 请自行下载`linux`版本的Release,解压后,上传可执行文件和修改后的配置文件到`/volume1/docker/ikuai-bypass/data/`.群晖项目或compose同时运行多个配置文件示例:
160 |
161 | ```yaml
162 | version: '3.8'
163 |
164 | services:
165 | ikuai-bypass:
166 | image: alpine:3.18.4
167 | container_name: ikuai-bypass
168 | privileged: true
169 | volumes:
170 | - /volume1/docker/ikuai-bypass/data/:/opt/ikuai-bypass
171 | command: sh -c "/opt/ikuai-bypass/ikuai-bypass -c /opt/ikuai-bypass/config.yml -r cron -m ip & sleep 30 ; /opt/ikuai-bypass/ikuai-bypass -c /opt/ikuai-bypass/config2.yml -r cron -m ip ; wait"
172 | tty: true
173 | ```
174 |
175 | ### windows
176 |
177 | 请在 releases 里面点击 `show all xx assets` 可以看到windows的包 下载解压cmd下cd到解压后的目录运行里面的exe程序.或许因ikuai-bypass需要获取在线数据,并使用了upx压缩,也没有另外加壳,部分杀软可能会报毒或者安全风险,[[#6]](https://github.com/joyanhui/ikuai-bypass/issues/6) 请自行决定是否信任,或者安装go环境后git clone后自行编译.我没有WIN环境,也不打算解决此类问题.
178 |
179 | ### macos下
180 |
181 | 下载 darwin-arm64.zip 或者darwin-amd64.zip,unzip 后在shell运行.其他参考上文linux
182 |
183 | ## v0.1.15 升级0.2版
184 |
185 | v0.2.x 以后规则的备注不再只有字符`IKUAI_BYPASS`,会根据tag添加指定的后缀,所以升级到0.2.x后最好清理掉旧的分流规则重新添加.另外新版配置文件中每条规则都多了一个 `tag: 备注后缀` 用于区分不同的规则 [[参考]](https://github.com/joyanhui/ikuai-bypass/blob/main/config.yml)
186 |
187 | ```sh
188 | ./ikuai-bypass -c /路径/config.yml -r clean -tag cleanAll # 清理所有备注名包含`IKUAI_BYPASS`的分流规则
189 | ./ikuai-bypass -c /路径/config.yml -r cron #先运行一次 而后等待计划任务触发
190 | ```
191 |
192 | ## 我自用过的环境
193 |
194 | 单网口/双网口
195 |
196 | - pve宿主 kvm 运行ikuai,然后opewrt在kvm/lxc下,ikuai-bypass 部署在openwrt.
197 | - windows宿主 + vmware 桥接网卡爱快 ,openwrt在vmware的ikuai内的vm内,ikuai-bypass 部署在openwrt.
198 | - windows宿主 + hyperv 桥接网卡爱快,openwrt在vbox的ikuai内的vm内,ikuai-bypass 使用instsrv+srvany部署到windows.
199 | - nixos宿主 + virtualbox 桥接网卡爱快,openwrt在vbox的ikuai内的vm内,ikuai-bypass 部署在openwrt.
200 |
201 | ## 其他参考文档
202 |
203 | [https://dev.leiyanhui.com/route/ikuai-bypass-joyanhui/](https://dev.leiyanhui.com/route/ikuai-bypass-joyanhui/)
204 |
205 | ## 致谢
206 |
207 | - [恩山 ztc1997](https://github.com/ztc1997/ikuai-bypass/): 本项目fork自ztc1997, 感谢原作者实现的核心功能,我只是简单缝补了几个小地方.
208 | - [JetBrains](https://jb.gg/OpenSourceSupport): 为本项目提供免费的开源许可证
209 | - [neovim](https://neovim.io/),[NvChad](https://github.com/NvChad/NvChad),[elulcao](https://github.com/elulcao/NvChad-custom)
210 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------