├── 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 | 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 | 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 | --------------------------------------------------------------------------------