125 Commits

Author SHA1 Message Date
dependabot[bot]
0bea22e145 build(deps): bump golang.org/x/crypto from 0.3.0 to 0.4.0 (#145)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: thank243 <thank243@gmail.com>
2022-12-07 23:28:46 +08:00
dependabot[bot]
f25d5993f3 build(deps): bump golang.org/x/net from 0.3.0 to 0.4.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-07 23:27:46 +08:00
Senis John
676365b13b update: Add support NewV2board api block rules 2022-12-07 23:26:09 +08:00
thank243
e774d5c822 Merge branch 'master' into globalLimit 2022-12-07 20:19:43 +08:00
pocketW
be4f9cdac1 reduce clear interval 2022-12-07 21:58:12 +11:00
Senis John
9a06f78653 fix: Add deviceLimit to the prefix of email string
Solve different node and different device limit the less limit node cannot work normally.
2022-12-07 18:49:08 +08:00
Senis John
85d73408c3 update: refactor global limit cache. Use gocache lib for level 2 cache
fix: shadowsocks2022 init feat
2022-12-07 13:29:05 +08:00
dependabot[bot]
c93fdb4cf3 build(deps): bump golang.org/x/net from 0.2.0 to 0.3.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-06 23:25:19 +08:00
dependabot[bot]
0d565b034b build(deps): bump golang.org/x/time from 0.2.0 to 0.3.0
Bumps [golang.org/x/time](https://github.com/golang/time) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/golang/time/releases)
- [Commits](https://github.com/golang/time/compare/v0.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/time
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 23:31:39 +08:00
Senis John
212f0ff135 fix: log prefix display not completed 2022-12-04 08:43:10 +08:00
Senis John
608764a8a0 fix: shadowsocks2022 user manager not working 2022-12-04 00:50:54 +08:00
Senis John
fc16cb0972 fix: shadowsocks2022 user manager not working 2022-12-04 00:42:35 +08:00
pocketW
5397720430 fix: expire time error 2022-12-03 19:17:33 +11:00
pocketW
839b15c22c fix: fix condition of check cert service
fix: check devices on redis first
2022-12-03 14:28:32 +11:00
Senis John
5b45b8ffe8 fix: refactor the InboundInfo struct
Global limit can separate settings for each node
2022-12-03 09:11:43 +08:00
dependabot[bot]
a56cd91b34 build(deps): bump github.com/shirou/gopsutil/v3 from 3.22.10 to 3.22.11 (#132)
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.22.10 to 3.22.11.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/commits)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-01 23:18:31 +08:00
Senis John
b6600729b2 update: code clean 2022-11-30 09:13:35 +08:00
Senis John
8d0225bcbb fix: On enable global limit, the online device report feat will failure
update: add pushIP method for limiter
2022-11-29 20:42:09 +08:00
pocketW
ce5fe799f4 Merge pull request #126 from XrayR-project/dependabot/go_modules/github.com/xtls/xray-core-1.6.5
build(deps): bump github.com/xtls/xray-core from 1.6.4 to 1.6.5
2022-11-29 09:51:07 +11:00
pocketW
8763d5960f Merge pull request #125 from XrayR-project/dependabot/go_modules/github.com/go-acme/lego/v4-4.9.1
build(deps): bump github.com/go-acme/lego/v4 from 4.9.0 to 4.9.1
2022-11-29 09:50:55 +11:00
dependabot[bot]
7a452a4a53 build(deps): bump github.com/xtls/xray-core from 1.6.4 to 1.6.5
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.6.4 to 1.6.5.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.6.4...v1.6.5)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-28 15:10:20 +00:00
dependabot[bot]
fac8b62286 build(deps): bump github.com/go-acme/lego/v4 from 4.9.0 to 4.9.1
Bumps [github.com/go-acme/lego/v4](https://github.com/go-acme/lego) from 4.9.0 to 4.9.1.
- [Release notes](https://github.com/go-acme/lego/releases)
- [Changelog](https://github.com/go-acme/lego/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-acme/lego/compare/v4.9.0...v4.9.1)

---
updated-dependencies:
- dependency-name: github.com/go-acme/lego/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-28 15:10:08 +00:00
thank243
d320aadb54 fix: global limit will failure on some situation (#124)
fix: typo
2022-11-28 11:16:27 +08:00
thank243
40ae48f507 update: refactor the global limit (#121)
fix: some log prefix

It an enhance device limit. Redis just use to sync local ipMap.
If global limit is enabled. The online report will faliure. All enabled nodes will report the device from redis.
2022-11-27 10:12:30 +08:00
pocketW
d1bc36782b add timeout 2022-11-26 21:44:14 +11:00
AikoCute
c04330d0bf Merge branch 'master' of https://github.com/Github-Aiko/XrayR-Real 2022-11-26 16:14:06 +07:00
AikoCute
872eb12d35 Check Connect Redis Server 2022-11-26 16:13:45 +07:00
pocketW
e1d4428d98 Merge pull request #118 from amin550/master
fix: ssPanel Report Node Status
2022-11-26 17:41:00 +11:00
pocketW
0fef3cf278 update config example 2022-11-26 17:30:20 +11:00
Amin
a4ca37b1de Fix sspanel ReportNodeStatus 2022-11-26 00:52:30 +03:30
Senis Y
74f3a75682 fix: delete unused struct 2022-11-25 00:25:39 +08:00
Senis Y
183b1be519 feature: add support v2board new API
update: add eTag on fetch users for save resources
update: split cert monitor service
update: refactor log format
update: add support shadowsocks_2022

The cert monitor period = UpdatePeriodic * 60

The xray-core official is not implementation of proxy.UserManager feature. So it may change in the future.
2022-11-24 15:11:01 +08:00
pocketW
2f10c3f6b8 revert error format 2022-11-20 14:57:20 +11:00
AikoCute
86324ff1ae Logs for GlobalDeviceLimit 2022-11-20 10:10:03 +07:00
pocketW
1897404c9d Merge pull request #104 from XrayR-project/dependabot/go_modules/golang.org/x/crypto-0.3.0
build(deps): bump golang.org/x/crypto from 0.2.0 to 0.3.0
2022-11-18 17:25:38 +11:00
dependabot[bot]
ee53e746c5 build(deps): bump golang.org/x/crypto from 0.2.0 to 0.3.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-17 15:15:59 +00:00
pocketW
656df61c17 Merge pull request #101 from thank243/master
fix: http mode not working
fix: use "email|uid" for global limit user identify
update: tls alpn challenge for apply certificate
2022-11-17 12:42:53 +11:00
Senis
e0237f5c54 fix: http mode not working
fix: use "email|uid" for global limit user identify
update: tls alpn challenge for apply certificate
2022-11-16 12:59:44 +08:00
pocketW
e16d94fb4a Merge pull request #97 from XrayR-project/dependabot/go_modules/github.com/xtls/xray-core-1.6.4
build(deps): bump github.com/xtls/xray-core from 1.6.3 to 1.6.4
2022-11-15 11:25:59 +11:00
dependabot[bot]
e357fc438f build(deps): bump github.com/xtls/xray-core from 1.6.3 to 1.6.4
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.6.3 to 1.6.4.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.6.3...v1.6.4)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-14 15:12:52 +00:00
pocketW
cb1638ac21 update to v0.8.6 2022-11-13 13:08:12 +11:00
pocketW
de0da25c21 chore: update dependence 2022-11-12 21:22:13 +11:00
pocketW
faec840c23 Merge pull request #90 from XrayR-project/dependabot/go_modules/golang.org/x/crypto-0.2.0
build(deps): bump golang.org/x/crypto from 0.1.0 to 0.2.0
2022-11-12 02:26:08 +11:00
pocketW
a6a1baf70c Merge pull request #89 from thank243/master
fix: adding the issuers certificate to the new certificate
2022-11-12 02:25:54 +11:00
pocketW
4013f71e4c fix: update global device limit 2022-11-12 02:22:53 +11:00
dependabot[bot]
c8f0981b0e build(deps): bump golang.org/x/crypto from 0.1.0 to 0.2.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 15:16:31 +00:00
Senis
5274edf657 fix: adding the issuers certificate to the new certificate 2022-11-11 22:42:31 +08:00
pocketW
e6232c1852 Merge pull request #88 from XrayR-project/dependabot/go_modules/golang.org/x/net-0.2.0
build(deps): bump golang.org/x/net from 0.1.0 to 0.2.0
2022-11-11 23:59:47 +11:00
dependabot[bot]
e0688fc609 build(deps): bump golang.org/x/net from 0.1.0 to 0.2.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 12:58:43 +00:00
pocketW
ffa444f2ab Merge pull request #86 from XrayR-project/dependabot/go_modules/github.com/spf13/viper-1.14.0
build(deps): bump github.com/spf13/viper from 1.13.0 to 1.14.0
2022-11-11 23:57:06 +11:00
dependabot[bot]
81ba4ebb43 build(deps): bump github.com/spf13/viper from 1.13.0 to 1.14.0
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 12:56:11 +00:00
pocketW
f50c61c782 fix: fix cert storage path 2022-11-11 23:44:43 +11:00
Senis
9c356cd28c Merge remote-tracking branch 'origin/master' 2022-11-08 17:50:53 +08:00
Senis
310353f344 fix: Redundant character escape 2022-11-08 17:50:03 +08:00
thank243
808b5ecc3c Merge branch 'master' into master 2022-11-08 14:48:44 +08:00
Senis
c4ef5bb843 update: xray-core v1.6.3 2022-11-08 14:46:06 +08:00
Senis
ddce3fa86d fix: typo 2022-11-08 11:57:06 +08:00
Senis
209f5a17d6 update: rebuild the implementation of legocmd 2022-11-08 11:13:27 +08:00
pocketW
af3fae9cdb Merge pull request #82 from thank243/master
fix: on some circumstance, periodic will run twice
2022-11-07 23:30:32 +11:00
Senis
3b96b352cb fix: on some circumstance, periodic will run twice 2022-11-07 15:55:30 +08:00
pocketW
398c3133d3 Merge pull request #79 from thank243/master
feat: add global user ip limit
2022-11-06 14:31:22 +11:00
Senis
a397af5d73 fix: init global limit 2022-11-04 14:36:34 +08:00
Senis
b47954ea64 fix: minor bugs 2022-11-04 08:58:41 +08:00
Senis
9a2188cb0c update: Global User IP limit. 2022-11-04 08:26:56 +08:00
pocketW
c7af43fc49 Merge pull request #75 from XrayR-project/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.22.10
build(deps): bump github.com/shirou/gopsutil/v3 from 3.22.9 to 3.22.10
2022-11-03 22:49:11 +11:00
pocketW
87aa855154 Merge pull request #74 from XrayR-project/dependabot/go_modules/github.com/xtls/xray-core-1.6.2
build(deps): bump github.com/xtls/xray-core from 1.6.1 to 1.6.2
2022-11-03 22:48:58 +11:00
dependabot[bot]
37eff6755c build(deps): bump github.com/shirou/gopsutil/v3 from 3.22.9 to 3.22.10
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.22.9 to 3.22.10.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.22.9...v3.22.10)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-01 15:53:37 +00:00
dependabot[bot]
5e346ddfee build(deps): bump github.com/xtls/xray-core from 1.6.1 to 1.6.2
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.6.1 to 1.6.2.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.6.1...v1.6.2)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-31 16:00:54 +00:00
Senis
838c667a87 fix: typo 2022-10-27 08:26:23 +08:00
pocketW
057f4156bf Merge pull request #71 from XrayR-project/dependabot/go_modules/github.com/stretchr/testify-1.8.1
build(deps): bump github.com/stretchr/testify from 1.8.0 to 1.8.1
2022-10-26 13:40:42 +11:00
pocketW
1f59a7cd7a Merge pull request #70 from XrayR-project/dependabot/go_modules/github.com/xtls/xray-core-1.6.1
build(deps): bump github.com/xtls/xray-core from 1.6.0 to 1.6.1
2022-10-26 13:40:30 +11:00
dependabot[bot]
2b5fa4feee build(deps): bump github.com/stretchr/testify from 1.8.0 to 1.8.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 16:02:42 +00:00
dependabot[bot]
571191a190 build(deps): bump github.com/xtls/xray-core from 1.6.0 to 1.6.1
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 16:02:30 +00:00
pocketW
3d5891fef3 update to v0.8.5 2022-10-14 15:19:16 +11:00
pocketW
70a0099f2c Merge pull request #67 from XrayR-project/dependabot/go_modules/github.com/fsnotify/fsnotify-1.6.0
build(deps): bump github.com/fsnotify/fsnotify from 1.5.4 to 1.6.0
2022-10-14 08:44:50 +11:00
dependabot[bot]
af8f24b5b1 build(deps): bump github.com/fsnotify/fsnotify from 1.5.4 to 1.6.0
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.5.4 to 1.6.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.5.4...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-13 15:51:51 +00:00
pocketW
79528d3e17 feat: dynamic update speedlimit bucket 2022-10-13 08:37:59 +11:00
pocketW
2f0461ddda chore: update lego to v4.9.0 2022-10-05 10:25:07 +11:00
pocketW
023680fec7 Merge pull request #62 from XrayR-project/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.22.9
build(deps): bump github.com/shirou/gopsutil/v3 from 3.22.8 to 3.22.9
2022-10-04 11:50:38 +11:00
dependabot[bot]
708adf1e43 build(deps): bump github.com/shirou/gopsutil/v3 from 3.22.8 to 3.22.9
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.22.8 to 3.22.9.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.22.8...v3.22.9)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-03 15:40:50 +00:00
pocketW
0bbec7ebeb Merge pull request #61 from JoshuaCylinder/master
feat: add dynamic speed limit #39
添加动态限速功能
2022-10-03 13:03:58 +11:00
JoshuaCylinder
a9dfd5404f Add default value for AutoSpeedLimitConfig 2022-10-01 07:11:20 +00:00
JoshuaCylinder
39c1036c4a Add AutoSpeedLimitConfig for some special node (For example: IPLC, IEPL, GameNode) 2022-10-01 06:49:51 +00:00
pocketW
7604e33b03 chore: update go to 1.19 2022-09-22 16:12:37 +10:00
pocketW
a906006015 Merge pull request #54 from XrayR-project/dependabot/go_modules/github.com/xtls/xray-core-1.6.0
build(deps): bump github.com/xtls/xray-core from 1.5.10 to 1.6.0
2022-09-20 17:48:04 +10:00
dependabot[bot]
cb5cc17a82 build(deps): bump github.com/xtls/xray-core from 1.5.10 to 1.6.0
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.5.10 to 1.6.0.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.5.10...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 15:52:56 +00:00
pocketW
1cf8bca79e update v0.8.4 2022-09-18 14:26:48 +10:00
pocketW
cbffafbb4c fix: fix CF dns env error #51 2022-09-18 14:19:55 +10:00
pocketW
b37705b374 update v0.8.3 2022-09-11 00:05:22 +10:00
pocketW
e00d228c3d Merge branch 'master' of https://github.com/XrayR-project/XrayR 2022-09-09 22:43:06 +10:00
pocketW
21847fee7d remove dockerhub support 2022-09-09 22:43:00 +10:00
pocketW
f42e30cc61 Merge pull request #44 from XrayR-project/dependabot/go_modules/github.com/spf13/viper-1.13.0
build(deps): bump github.com/spf13/viper from 1.12.0 to 1.13.0
2022-09-07 11:03:44 +10:00
pocketW
e3c7cf2a5f Merge pull request #43 from XrayR-project/dependabot/go_modules/github.com/urfave/cli-1.22.10
build(deps): bump github.com/urfave/cli from 1.22.9 to 1.22.10
2022-09-07 11:03:31 +10:00
dependabot[bot]
82731d9d9c build(deps): bump github.com/spf13/viper from 1.12.0 to 1.13.0
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-06 15:39:39 +00:00
dependabot[bot]
45f4d80a49 build(deps): bump github.com/urfave/cli from 1.22.9 to 1.22.10
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.9 to 1.22.10.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.9...v1.22.10)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 16:49:36 +00:00
pocketW
10691e4cf3 panic #42 2022-09-03 20:34:22 +10:00
pocketW
9dbb231a12 Merge pull request #41 from XrayR-project/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.22.8
build(deps): bump github.com/shirou/gopsutil/v3 from 3.22.7 to 3.22.8
2022-09-03 20:32:45 +10:00
dependabot[bot]
8724ece88a build(deps): bump github.com/shirou/gopsutil/v3 from 3.22.7 to 3.22.8
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.22.7 to 3.22.8.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.22.7...v3.22.8)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 15:46:31 +00:00
pocketW
f0610d09fd feat: not reset traffic when report failed 2022-08-30 00:51:21 +10:00
pocketW
8991284e19 chore: update Xray-core to 1.5.10 2022-08-28 14:15:57 +10:00
pocketW
7a12a806a8 Update README.md 2022-08-27 21:05:18 +10:00
pocketW
fda14882a4 Create stale.yml 2022-08-26 22:29:38 +10:00
pocketW
710f081025 fix: #37 2022-08-26 13:00:07 +10:00
pocketW
c280cca7b3 add github docker package 2022-08-25 20:32:07 +10:00
pocketW
fa51cb5309 fix: fix bug when parsing transport protocol 2022-08-24 09:52:45 +10:00
pocketW
529eb46ea0 chore: add hints for config file
doc: add some badges
2022-08-23 11:20:33 +10:00
pocketW
2a528070bf doc: update doc 2022-08-22 22:41:28 +10:00
pocketW
d9089520db doc: update readme 2022-08-22 22:24:05 +10:00
pocketW
bdbf916a14 doc: add support for v2raysocks 2022-08-22 22:13:58 +10:00
pocketW
1ec1765517 fix: trim regexp: prefix for rule 2022-08-22 22:09:54 +10:00
pocketW
f29d2db235 fix: update url for v2raysocks 2022-08-21 21:55:30 +10:00
pocketW
337f441c32 feat: add support for v2raysocks 2022-08-21 00:36:16 +10:00
pocketW
c9895fa02f feat: add support for customize transport protocol 2022-08-17 11:08:56 +10:00
qiuzi
5d6eeb4f29 增加trojan自定义网络协议与加密方式 2022-08-16 16:03:21 +08:00
pocketW
fc9cb4ac10 fix: fix index out of range #27 2022-08-08 11:19:23 +10:00
pocketW
01ef6fb699 Update codeql-analysis.yml 2022-08-04 10:48:02 +10:00
pocketW
e46dc1d8d7 fix: fix incorrect conversion between integer type 2022-08-02 19:42:29 +10:00
pocketW
84db0453cb chore: remove support for ppc64 2022-08-02 19:25:01 +10:00
pocketW
b934a52875 fix: fix get cpu usage error in openbsd
fix: fix incorrect conversion between integer types
fix: fix test file error
chore: update dependencies
2022-08-02 19:03:53 +10:00
pocketW
246f9374a4 Merge pull request #20 from XrayR-project/dependabot/go_modules/google.golang.org/protobuf-1.28.1
build(deps): bump google.golang.org/protobuf from 1.28.0 to 1.28.1
2022-07-30 23:10:39 +10:00
萌新
4dd827d94d Update README.md 2022-07-29 09:40:09 +08:00
dependabot[bot]
c349d3d9a1 build(deps): bump google.golang.org/protobuf from 1.28.0 to 1.28.1
Bumps [google.golang.org/protobuf](https://github.com/protocolbuffers/protobuf-go) from 1.28.0 to 1.28.1.
- [Release notes](https://github.com/protocolbuffers/protobuf-go/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf-go/blob/master/release.bash)
- [Commits](https://github.com/protocolbuffers/protobuf-go/compare/v1.28.0...v1.28.1)

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-28 15:41:35 +00:00
pocketW
48ca6d9648 chore: update docker.yml 2022-07-28 21:55:23 +10:00
pocketW
f7d1114fb2 doc: update doc url 2022-07-28 21:50:37 +10:00
萌新
dbf7727bcd Update docker.yml 2022-07-28 17:31:10 +08:00
85 changed files with 3123 additions and 4615 deletions

View File

@@ -1,33 +1,95 @@
{
"android-arm64": { "friendlyName": "android-arm64-v8a" },
"darwin-amd64": { "friendlyName": "macos-64" },
"darwin-arm64": { "friendlyName": "macos-arm64-v8a" },
"dragonfly-amd64": { "friendlyName": "dragonfly-64" },
"freebsd-386": { "friendlyName": "freebsd-32" },
"freebsd-amd64": { "friendlyName": "freebsd-64" },
"freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" },
"freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" },
"linux-386": { "friendlyName": "linux-32" },
"linux-amd64": { "friendlyName": "linux-64" },
"linux-arm5": { "friendlyName": "linux-arm32-v5" },
"linux-arm64": { "friendlyName": "linux-arm64-v8a" },
"linux-arm6": { "friendlyName": "linux-arm32-v6" },
"linux-arm7": { "friendlyName": "linux-arm32-v7a" },
"linux-mips64le": { "friendlyName": "linux-mips64le" },
"linux-mips64": { "friendlyName": "linux-mips64" },
"linux-mipslesoftfloat": { "friendlyName": "linux-mips32le-softfloat" },
"linux-mipsle": { "friendlyName": "linux-mips32le" },
"linux-mipssoftfloat": { "friendlyName": "linux-mips32-softfloat" },
"linux-mips": { "friendlyName": "linux-mips32" },
"linux-ppc64le": { "friendlyName": "linux-ppc64le" },
"linux-ppc64": { "friendlyName": "linux-ppc64" },
"linux-riscv64": { "friendlyName": "linux-riscv64" },
"linux-s390x": { "friendlyName": "linux-s390x" },
"openbsd-386": { "friendlyName": "openbsd-32" },
"openbsd-amd64": { "friendlyName": "openbsd-64" },
"openbsd-arm64": { "friendlyName": "openbsd-arm64-v8a" },
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
"windows-386": { "friendlyName": "windows-32" },
"windows-amd64": { "friendlyName": "windows-64" },
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
}
"android-arm64": {
"friendlyName": "android-arm64-v8a"
},
"darwin-amd64": {
"friendlyName": "macos-64"
},
"darwin-arm64": {
"friendlyName": "macos-arm64-v8a"
},
"dragonfly-amd64": {
"friendlyName": "dragonfly-64"
},
"freebsd-386": {
"friendlyName": "freebsd-32"
},
"freebsd-amd64": {
"friendlyName": "freebsd-64"
},
"freebsd-arm64": {
"friendlyName": "freebsd-arm64-v8a"
},
"freebsd-arm7": {
"friendlyName": "freebsd-arm32-v7a"
},
"linux-386": {
"friendlyName": "linux-32"
},
"linux-amd64": {
"friendlyName": "linux-64"
},
"linux-arm5": {
"friendlyName": "linux-arm32-v5"
},
"linux-arm64": {
"friendlyName": "linux-arm64-v8a"
},
"linux-arm6": {
"friendlyName": "linux-arm32-v6"
},
"linux-arm7": {
"friendlyName": "linux-arm32-v7a"
},
"linux-mips64le": {
"friendlyName": "linux-mips64le"
},
"linux-mips64": {
"friendlyName": "linux-mips64"
},
"linux-mipslesoftfloat": {
"friendlyName": "linux-mips32le-softfloat"
},
"linux-mipsle": {
"friendlyName": "linux-mips32le"
},
"linux-mipssoftfloat": {
"friendlyName": "linux-mips32-softfloat"
},
"linux-mips": {
"friendlyName": "linux-mips32"
},
"linux-ppc64le": {
"friendlyName": "linux-ppc64le"
},
"linux-ppc64": {
"friendlyName": "linux-ppc64"
},
"linux-riscv64": {
"friendlyName": "linux-riscv64"
},
"linux-s390x": {
"friendlyName": "linux-s390x"
},
"openbsd-386": {
"friendlyName": "openbsd-32"
},
"openbsd-amd64": {
"friendlyName": "openbsd-64"
},
"openbsd-arm64": {
"friendlyName": "openbsd-arm64-v8a"
},
"openbsd-arm7": {
"friendlyName": "openbsd-arm32-v7a"
},
"windows-386": {
"friendlyName": "windows-32"
},
"windows-amd64": {
"friendlyName": "windows-64"
},
"windows-arm7": {
"friendlyName": "windows-arm32-v7a"
}
}

View File

@@ -34,34 +34,34 @@ jobs:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -4,6 +4,11 @@ on:
push:
branches:
- master
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
tags:
- 'v*'
pull_request:
@@ -15,28 +20,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
-
name: Docker meta
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: crackair/xrayr
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Build and push
uses: docker/build-push-action@v2
images: ghcr.io/${{ github.repository }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/arm/v7,linux/arm64,linux/amd64,linux/s390x
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -11,14 +11,14 @@ on:
- "go.sum"
- ".github/workflows/*.yml"
pull_request:
types: [opened, synchronize, reopened]
types: [ opened, synchronize, reopened ]
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
release:
types: [published]
types: [ published ]
jobs:
@@ -26,8 +26,8 @@ jobs:
strategy:
matrix:
# Include amd64 on all platforms.
goos: [windows, freebsd, openbsd, linux, dragonfly, darwin]
goarch: [amd64, 386]
goos: [ windows, freebsd, openbsd, linux, dragonfly, darwin ]
goarch: [ amd64, 386 ]
exclude:
# Exclude i386 on darwin and dragonfly.
- goarch: 386
@@ -74,8 +74,8 @@ jobs:
goarch: mips
# END MIPS
# BEGIN PPC
- goos: linux
goarch: ppc64
# - goos: linux # Removed due to the unsupport of shirou/gopsutil
# goarch: ppc64
- goos: linux
goarch: ppc64le
# END PPC
@@ -92,7 +92,7 @@ jobs:
# END S390X
# END Other architectures
fail-fast: false
runs-on: ubuntu-latest
env:
GOOS: ${{ matrix.goos }}
@@ -101,19 +101,19 @@ jobs:
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v2
- name: Show workflow information
uses: actions/checkout@v3
- name: Show workflow information
id: get_filename
run: |
export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
echo "::set-output name=ASSET_NAME::$_NAME"
echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ^1.18
go-version: ^1.19
- name: Get project dependencies
run: go mod download
@@ -123,7 +123,7 @@ jobs:
run: |
mkdir -p build_assets
go build -v -o build_assets/XrayR -trimpath -ldflags "-s -w -buildid=" ./main
- name: Build Mips softfloat XrayR
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
run: |
@@ -173,7 +173,7 @@ jobs:
run: |
mv build_assets XrayR-$ASSET_NAME
- name: Upload files to Artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: XrayR-${{ steps.get_filename.outputs.ASSET_NAME }}
path: |

22
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,20 +0,0 @@
name: Sync to Gitlab
on:
push:
delete:
workflow_dispatch:
jobs:
to_gitlab:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url:
git@gitlab.com:xrayr-project/XrayR.git
ssh_private_key:
${{ secrets.SSH_PRIVATEKEY }}

5
.gitignore vendored
View File

@@ -12,4 +12,7 @@ main/.lego
main/cert
main/config.yml
./vscode
.idea/*
.idea/*
.DS_Store
*.bak
go.work*

View File

@@ -1,5 +1,5 @@
# Build go
FROM golang:1.18-alpine AS builder
FROM golang:1.19-alpine AS builder
WORKDIR /app
COPY . .
ENV CGO_ENABLED=0

View File

@@ -1,6 +1,12 @@
# XrayR
[![](https://img.shields.io/badge/TgChat-@XrayR讨论-blue.svg)](https://t.me/XrayR_project)
[![](https://img.shields.io/badge/Channel-@XrayR通知-blue.svg)](https://t.me/XrayR_channel)
![](https://img.shields.io/github/stars/XrayR-project/XrayR)
![](https://img.shields.io/github/forks/XrayR-project/XrayR)
![](https://github.com/XrayR-project/XrayR/actions/workflows/release.yml/badge.svg)
![](https://github.com/XrayR-project/XrayR/actions/workflows/docker.yml/badge.svg)
[![Github All Releases](https://img.shields.io/github/downloads/XrayR-project/XrayR/total.svg)]()
A Xray backend framework that can easily support many panels.
@@ -8,12 +14,14 @@ A Xray backend framework that can easily support many panels.
如果您喜欢本项目可以右上角点个star+watch持续关注本项目的进展。
使用教程:[详细使用教程](https://crackair.gitbook.io/xrayr-project/)
使用教程:[详细使用教程](https://xrayr-project.github.io/XrayR-doc/)
## 免责声明
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
## 特点
* 永久开源且免费。
* 支持V2rayTrojan Shadowsocks多种协议。
* 支持Vless和XTLS等新特性。
@@ -26,43 +34,50 @@ A Xray backend framework that can easily support many panels.
## 功能介绍
| 功能 | v2ray | trojan | shadowsocks |
| --------------- | ----- | ------ | ----------- |
| 功能 | v2ray | trojan | shadowsocks |
|-----------|-------|--------|-------------|
| 获取节点信息 | √ | √ | √ |
| 获取用户信息 | √ | √ | √ |
| 用户流量统计 | √ | √ | √ |
| 服务器信息上报 | √ | √ | √ |
| 服务器信息上报 | √ | √ | √ |
| 自动申请tls证书 | √ | √ | √ |
| 自动续签tls证书 | √ | √ | √ |
| 在线人数统计 | √ | √ | √ |
| 在线用户限制 | √ | √ | √ |
| 审计规则 | √ | √ | √ |
| 审计规则 | √ | √ | √ |
| 节点端口限速 | √ | √ | √ |
| 按照用户限速 | √ | √ | √ |
| 自定义DNS | √ | √ | √ |
| 自定义DNS | √ | √ | √ |
## 支持前端
| 前端 | v2ray | trojan | shadowsocks |
| ------------------------------------------------------ | ----- | ------ | ------------------------------ |
| 前端 | v2ray | trojan | shadowsocks |
|--------------------------------------------------------|-------|--------|-------------------------|
| sspanel-uim | √ | √ | √ (单端口多用户和V2ray-Plugin) |
| v2board | √ | √ | √ |
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
| v2board | √ | √ | √ |
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
## 软件安装
### 一键安装
```
wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh && bash install.sh
```
### 使用Docker部署软件
[Docker部署教程](https://crackair.gitbook.io/xrayr-project/xrayr-xia-zai-he-an-zhuang/install/docker)
[Docker部署教程](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/docker)
### 手动安装
[手动安装教程](https://crackair.gitbook.io/xrayr-project/xrayr-xia-zai-he-an-zhuang/install/manual)
[手动安装教程](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
## 配置文件及详细使用教程
[详细使用教程](https://crackair.gitbook.io/xrayr-project/)
[详细使用教程](https://xrayr-project.github.io/XrayR-doc/)
## Thanks
@@ -80,7 +95,9 @@ wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/ins
[XrayR后端讨论](https://t.me/XrayR_project)
[XrayR通知](https://t.me/XrayR_channel)
## Stargazers over time
[![Stargazers over time](https://starchart.cc/XrayR-project/XrayR.svg)](https://starchart.cc/XrayR-project/XrayR)

View File

@@ -5,7 +5,7 @@ import (
"regexp"
)
// API config
// Config API config
type Config struct {
APIHost string `mapstructure:"ApiHost"`
NodeID int `mapstructure:"NodeID"`
@@ -20,20 +20,20 @@ type Config struct {
DisableCustomConfig bool `mapstructure:"DisableCustomConfig"`
}
// Node status
// NodeStatus Node status
type NodeStatus struct {
CPU float64
Mem float64
Disk float64
Uptime int
Uptime uint64
}
type NodeInfo struct {
NodeType string // Must be V2ray, Trojan, and Shadowsocks
NodeID int
Port int
Port uint32
SpeedLimit uint64 // Bps
AlterID int
AlterID uint16
TransportProtocol string
FakeType string
Host string
@@ -42,6 +42,7 @@ type NodeInfo struct {
TLSType string
EnableVless bool
CypherMethod string
ServerKey string
ServiceName string
Header json.RawMessage
}
@@ -50,7 +51,7 @@ type UserInfo struct {
UID int
Email string
Passwd string
Port int
Port uint32
Method string
SpeedLimit uint64 // Bps
DeviceLimit int
@@ -59,7 +60,7 @@ type UserInfo struct {
Obfs string
ObfsParam string
UUID string
AlterID int
AlterID uint16
}
type OnlineUser struct {

1
api/newV2board/model.go Normal file
View File

@@ -0,0 +1 @@
package newV2board

369
api/newV2board/v2board.go Normal file
View File

@@ -0,0 +1,369 @@
package newV2board
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/bitly/go-simplejson"
"github.com/go-resty/resty/v2"
"github.com/XrayR-project/XrayR/api"
)
// APIClient create an api client to the panel.
type APIClient struct {
client *resty.Client
APIHost string
NodeID int
Key string
NodeType string
EnableVless bool
EnableXTLS bool
SpeedLimit float64
DeviceLimit int
LocalRuleList []api.DetectRule
resp atomic.Value
eTag string
}
// New create an api instance
func New(apiConfig *api.Config) *APIClient {
client := resty.New()
client.SetRetryCount(3)
if apiConfig.Timeout > 0 {
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
} else {
client.SetTimeout(5 * time.Second)
}
client.OnError(func(req *resty.Request, err error) {
if v, ok := err.(*resty.ResponseError); ok {
// v.Response contains the last response from the server
// v.Err contains the original error
log.Print(v.Err)
}
})
client.SetBaseURL(apiConfig.APIHost)
// Create Key for each requests
client.SetQueryParams(map[string]string{
"node_id": strconv.Itoa(apiConfig.NodeID),
"node_type": strings.ToLower(apiConfig.NodeType),
"token": apiConfig.Key,
})
// Read local rule list
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
apiClient := &APIClient{
client: client,
NodeID: apiConfig.NodeID,
Key: apiConfig.Key,
APIHost: apiConfig.APIHost,
NodeType: apiConfig.NodeType,
EnableVless: apiConfig.EnableVless,
EnableXTLS: apiConfig.EnableXTLS,
SpeedLimit: apiConfig.SpeedLimit,
DeviceLimit: apiConfig.DeviceLimit,
LocalRuleList: localRuleList,
}
return apiClient
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
LocalRuleList = make([]api.DetectRule, 0)
if path != "" {
// open the file
file, err := os.Open(path)
// handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return LocalRuleList
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, api.DetectRule{
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
file.Close()
}
return LocalRuleList
}
// Describe return a description of the client
func (c *APIClient) Describe() api.ClientInfo {
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
}
// Debug set the client debug for client
func (c *APIClient) Debug() {
c.client.SetDebug(true)
}
func (c *APIClient) assembleURL(path string) string {
return c.APIHost + path
}
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*simplejson.Json, error) {
if err != nil {
return nil, fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
}
if res.StatusCode() > 399 {
body := res.Body()
return nil, fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
}
rtn, err := simplejson.NewJson(res.Body())
if err != nil {
return nil, fmt.Errorf("ret %s invalid", res.String())
}
return rtn, nil
}
// GetNodeInfo will pull NodeInfo Config from panel
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
path := "/api/v1/server/UniProxy/config"
res, err := c.client.R().
ForceContentType("application/json").
Get(path)
response, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
c.resp.Store(response)
switch c.NodeType {
case "V2ray":
nodeInfo, err = c.parseV2rayNodeResponse(response)
case "Trojan":
nodeInfo, err = c.parseTrojanNodeResponse(response)
case "Shadowsocks":
nodeInfo, err = c.parseSSNodeResponse(response)
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
if err != nil {
res, _ := response.MarshalJSON()
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
}
return nodeInfo, nil
}
// GetUserList will pull user form panel
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
path := "/api/v1/server/UniProxy/user"
switch c.NodeType {
case "V2ray", "Trojan", "Shadowsocks":
break
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err := c.client.R().
SetHeader("If-None-Match", c.eTag).
ForceContentType("application/json").
Get(path)
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
if res.StatusCode() == 304 {
return nil, errors.New("users no change")
}
// update etag
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTag {
c.eTag = res.Header().Get("Etag")
}
response, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
numOfUsers := len(response.Get("users").MustArray())
userList := make([]api.UserInfo, numOfUsers)
for i := 0; i < numOfUsers; i++ {
user := response.Get("users").GetIndex(i)
u := api.UserInfo{}
u.UID = user.Get("id").MustInt()
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8) // todo waiting v2board send configuration
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
u.UUID = user.Get("uuid").MustString()
u.Email = u.UUID + "@v2board.user"
if c.NodeType == "Shadowsocks" {
u.Passwd = u.UUID
}
userList[i] = u
}
return &userList, nil
}
// ReportUserTraffic reports the user traffic
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
path := "/api/v1/server/UniProxy/push"
// json structure: {uid1: [u, d], uid2: [u, d], uid1: [u, d], uid3: [u, d]}
data := make(map[int][]int64, len(*userTraffic))
for _, traffic := range *userTraffic {
data[traffic.UID] = []int64{traffic.Upload, traffic.Download}
}
res, err := c.client.R().
SetBody(data).
ForceContentType("application/json").
Post(path)
_, err = c.parseResponse(res, path, err)
if err != nil {
return err
}
return nil
}
// GetNodeRule implements the API interface
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
ruleList := c.LocalRuleList
nodeInfoResponse := c.resp.Load().(*simplejson.Json)
for i, rule := range nodeInfoResponse.Get("routes").MustArray() {
r := rule.(map[string]any)
if r["action"] == "block" {
ruleListItem := api.DetectRule{
ID: i,
Pattern: regexp.MustCompile(r["match"].(string)),
}
ruleList = append(ruleList, ruleListItem)
}
}
return &ruleList, nil
}
// ReportNodeStatus implements the API interface
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
return nil
}
// ReportNodeOnlineUsers implements the API interface
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
return nil
}
// ReportIllegal implements the API interface
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
return nil
}
// parseTrojanNodeResponse parse the response for the given nodeInfo format
func (c *APIClient) parseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
var TLSType = "tls"
if c.EnableXTLS {
TLSType = "xtls"
}
// Create GeneralNodeInfo
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
TransportProtocol: "tcp",
EnableTLS: true,
TLSType: TLSType,
Host: nodeInfoResponse.Get("host").MustString(),
ServiceName: nodeInfoResponse.Get("server_name").MustString(),
}
return nodeInfo, nil
}
// parseSSNodeResponse parse the response for the given nodeInfo format
func (c *APIClient) parseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
// Create GeneralNodeInfo
return &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
TransportProtocol: "tcp",
CypherMethod: nodeInfoResponse.Get("cipher").MustString(),
ServerKey: nodeInfoResponse.Get("server_key").MustString(), // shadowsocks2022 share key
}, nil
}
// parseV2rayNodeResponse parse the response for the given nodeInfo format
func (c *APIClient) parseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
var (
TLSType = "tls"
path, host, serviceName string
header json.RawMessage
enableTLS bool
alterID uint16 = 0
)
if c.EnableXTLS {
TLSType = "xtls"
}
transportProtocol := nodeInfoResponse.Get("network").MustString()
switch transportProtocol {
case "ws":
path = nodeInfoResponse.Get("networkSettings").Get("path").MustString()
host = nodeInfoResponse.Get("networkSettings").Get("headers").Get("Host").MustString()
case "grpc":
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("serviceName"); ok {
serviceName = data.MustString()
}
case "tcp":
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("headers"); ok {
if httpHeader, err := data.MarshalJSON(); err != nil {
return nil, err
} else {
header = httpHeader
}
}
}
if nodeInfoResponse.Get("tls").MustInt() == 1 {
enableTLS = true
}
// Create GeneralNodeInfo
return &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
AlterID: alterID,
TransportProtocol: transportProtocol,
EnableTLS: enableTLS,
TLSType: TLSType,
Path: path,
Host: host,
EnableVless: c.EnableVless,
ServiceName: serviceName,
Header: header,
}, nil
}

View File

@@ -0,0 +1,101 @@
package newV2board_test
import (
"testing"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/newV2board"
)
func CreateClient() api.API {
apiConfig := &api.Config{
APIHost: "http://localhost:9897",
Key: "qwertyuiopasdfghjkl",
NodeID: 1,
NodeType: "V2ray",
}
client := newV2board.New(apiConfig)
return client
}
func TestGetV2rayNodeInfo(t *testing.T) {
client := CreateClient()
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetSSNodeInfo(t *testing.T) {
apiConfig := &api.Config{
APIHost: "http://127.0.0.1:668",
Key: "qwertyuiopasdfghjkl",
NodeID: 1,
NodeType: "Shadowsocks",
}
client := newV2board.New(apiConfig)
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetTrojanNodeInfo(t *testing.T) {
apiConfig := &api.Config{
APIHost: "http://127.0.0.1:668",
Key: "qwertyuiopasdfghjkl",
NodeID: 1,
NodeType: "Trojan",
}
client := newV2board.New(apiConfig)
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetUserList(t *testing.T) {
client := CreateClient()
userList, err := client.GetUserList()
if err != nil {
t.Error(err)
}
t.Log(userList)
}
func TestReportReportUserTraffic(t *testing.T) {
client := CreateClient()
userList, err := client.GetUserList()
if err != nil {
t.Error(err)
}
generalUserTraffic := make([]api.UserTraffic, len(*userList))
for i, userInfo := range *userList {
generalUserTraffic[i] = api.UserTraffic{
UID: userInfo.UID,
Upload: 114514,
Download: 114514,
}
}
// client.Debug()
err = client.ReportUserTraffic(&generalUserTraffic)
if err != nil {
t.Error(err)
}
}
func TestGetNodeRule(t *testing.T) {
client := CreateClient()
client.Debug()
ruleList, err := client.GetNodeRule()
if err != nil {
t.Error(err)
}
t.Log(ruleList)
}

View File

@@ -9,14 +9,14 @@ type NodeInfoResponse struct {
Method string `json:"method"`
TrafficRate float64 `json:"trafficRate"`
RawServerString string `json:"outServer"`
Port int `json:"outPort"`
AlterId int `json:"alterId"`
Port uint32 `json:"outPort"`
AlterId uint16 `json:"alterId"`
Network string `json:"network"`
Security string `json:"security"`
Host string `json:"host"`
Path string `json:"path"`
Grpc bool `json:"grpc"`
Sni string `json:sni`
Sni string `json:"sni"`
}
// UserResponse is the response of user

View File

@@ -11,8 +11,9 @@ import (
"strconv"
"time"
"github.com/XrayR-project/XrayR/api"
"github.com/go-resty/resty/v2"
"github.com/XrayR-project/XrayR/api"
)
// APIClient create a api client to the panel.
@@ -76,7 +77,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// open the file
file, err := os.Open(path)
//handle errors while opening
// handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return LocalRuleList
@@ -94,7 +95,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return make([]api.DetectRule, 0)
return
}
file.Close()
@@ -130,7 +131,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
if response.Ret != 200 {
res, _ := json.Marshal(&response)
return nil, fmt.Errorf("Ret %s invalid", string(res))
return nil, fmt.Errorf("ret %s invalid", string(res))
}
return response, nil
}
@@ -167,7 +168,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
nodeInfoResponse := new(NodeInfoResponse)
if err := json.Unmarshal(response.Data, nodeInfoResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
}
switch c.NodeType {
case "V2ray":
@@ -177,7 +178,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
case "Shadowsocks":
nodeInfo, err = c.ParseSSNodeResponse(nodeInfoResponse)
default:
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
if err != nil {
@@ -219,12 +220,12 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
var userListResponse *[]UserResponse
if err := json.Unmarshal(response.Data, &userListResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
}
userList, err := c.ParseUserListResponse(userListResponse)
if err != nil {
res, _ := json.Marshal(userListResponse)
return nil, fmt.Errorf("Parse user list failed: %s", string(res))
return nil, fmt.Errorf("parse user list failed: %s", string(res))
}
return userList, nil
}
@@ -234,7 +235,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
return nil
}
//ReportNodeOnlineUsers reports online user ip
// ReportNodeOnlineUsers reports online user ip
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
var nodeType = ""
switch c.NodeType {
@@ -338,7 +339,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
ruleListResponse := new([]RuleItem)
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
}
for _, r := range *ruleListResponse {
@@ -411,11 +412,8 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
// ParseSSNodeResponse parse the response for the given nodeinfor format
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
var port int = 0
var speedlimit uint64 = 0
port = nodeInfoResponse.Port
if c.SpeedLimit > 0 {
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
@@ -425,7 +423,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
nodeinfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
Port: nodeInfoResponse.Port,
SpeedLimit: speedlimit,
TransportProtocol: "tcp",
CypherMethod: nodeInfoResponse.Method,

View File

@@ -85,7 +85,7 @@ func TestGetUserList(t *testing.T) {
func TestReportNodeStatus(t *testing.T) {
client := CreateClient()
nodeStatus := &api.NodeStatus{
1, 1, 1, 256,
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
}
err := client.ReportNodeStatus(nodeStatus)
if err != nil {
@@ -107,7 +107,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
IP: fmt.Sprintf("1.1.1.%d", i),
}
}
//client.Debug()
// client.Debug()
err = client.ReportNodeOnlineUsers(&onlineUserList)
if err != nil {
t.Error(err)
@@ -128,7 +128,7 @@ func TestReportReportUserTraffic(t *testing.T) {
Download: 114514,
}
}
//client.Debug()
// client.Debug()
err = client.ReportUserTraffic(&generalUserTraffic)
if err != nil {
t.Error(err)
@@ -150,8 +150,8 @@ func TestReportIllegal(t *testing.T) {
client := CreateClient()
detectResult := []api.DetectResult{
api.DetectResult{1, 2},
api.DetectResult{1, 3},
{1, 2},
{1, 3},
}
client.Debug()
err := client.ReportIllegal(&detectResult)

View File

@@ -19,8 +19,8 @@ type V2rayNodeInfo struct {
Key string `json:"key"`
Cert string `json:"pem"`
V2License string `json:"v2_license"`
V2AlterID int `json:"v2_alter_id"`
V2Port int `json:"v2_port"`
V2AlterID uint16 `json:"v2_alter_id"`
V2Port uint32 `json:"v2_port"`
V2Method string `json:"v2_method"`
V2Net string `json:"v2_net"`
V2Type string `json:"v2_type"`
@@ -37,7 +37,7 @@ type ShadowsocksNodeInfo struct {
SpeedLimit uint64 `json:"speed_limit"`
ClientLimit int `json:"client_limit"`
Method string `json:"method"`
Port int `json:"port"`
Port uint32 `json:"port"`
}
type TrojanNodeInfo struct {
@@ -46,10 +46,10 @@ type TrojanNodeInfo struct {
SpeedLimit uint64 `json:"speed_limit"`
ClientLimit int `json:"client_limit"`
PushPort int `json:"push_port"`
TrojanPort int `json:"trojan_port"`
TrojanPort uint32 `json:"trojan_port"`
}
// Node status report
// NodeStatus Node status report
type NodeStatus struct {
CPU string `json:"cpu"`
Mem string `json:"mem"`
@@ -98,7 +98,6 @@ type NodeRuleItem struct {
Pattern string `json:"pattern"`
}
// IllegalReport
type IllegalReport struct {
UID int `json:"uid"`
RuleID int `json:"rule_id"`

View File

@@ -11,8 +11,9 @@ import (
"strconv"
"time"
"github.com/XrayR-project/XrayR/api"
"github.com/go-resty/resty/v2"
"github.com/XrayR-project/XrayR/api"
)
// APIClient create a api client to the panel.
@@ -72,7 +73,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// open the file
file, err := os.Open(path)
//handle errors while opening
// handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return LocalRuleList
@@ -90,7 +91,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return make([]api.DetectRule, 0)
return
}
file.Close()
@@ -134,7 +135,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
if response.Status != "success" {
res, _ := json.Marshal(&response)
return nil, fmt.Errorf("Ret %s invalid", string(res))
return nil, fmt.Errorf("ret %s invalid", string(res))
}
return response, nil
}
@@ -150,7 +151,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
case "Shadowsocks":
path = fmt.Sprintf("/api/ss/v1/node/%d", c.NodeID)
default:
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err := c.createCommonRequest().
@@ -171,7 +172,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
case "Shadowsocks":
nodeInfo, err = c.ParseSSNodeResponse(&response.Data)
default:
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
if err != nil {
@@ -193,7 +194,7 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
case "Shadowsocks":
path = fmt.Sprintf("/api/ss/v1/userList/%d", c.NodeID)
default:
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err := c.createCommonRequest().
@@ -214,11 +215,11 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
case "Shadowsocks":
userList, err = c.ParseSSUserListResponse(&response.Data)
default:
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
if err != nil {
res, _ := json.Marshal(response.Data)
return nil, fmt.Errorf("Parse user list failed: %s", string(res))
return nil, fmt.Errorf("parse user list failed: %s", string(res))
}
return userList, nil
}
@@ -234,11 +235,11 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
case "Shadowsocks":
path = fmt.Sprintf("/api/ss/v1/nodeStatus/%d", c.NodeID)
default:
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
systemload := NodeStatus{
Uptime: nodeStatus.Uptime,
Uptime: int(nodeStatus.Uptime),
CPU: fmt.Sprintf("%d%%", int(nodeStatus.CPU)),
Mem: fmt.Sprintf("%d%%", int(nodeStatus.Mem)),
Disk: fmt.Sprintf("%d%%", int(nodeStatus.Disk)),
@@ -258,7 +259,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
return nil
}
//ReportNodeOnlineUsers reports online user ip
// ReportNodeOnlineUsers reports online user ip
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
var path string
@@ -270,7 +271,7 @@ func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) erro
case "Shadowsocks":
path = fmt.Sprintf("/api/ss/v1/nodeOnline/%d", c.NodeID)
default:
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
data := make([]NodeOnline, len(*onlineUserList))
@@ -303,7 +304,7 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
case "Shadowsocks":
path = fmt.Sprintf("/api/ss/v1/userTraffic/%d", c.NodeID)
default:
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
data := make([]UserTraffic, len(*userTraffic))
@@ -338,7 +339,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
case "Shadowsocks":
path = fmt.Sprintf("/api/ss/v1/nodeRule/%d", c.NodeID)
default:
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err := c.createCommonRequest().
@@ -354,7 +355,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
ruleListResponse := new(NodeRule)
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
}
ruleList := c.LocalRuleList
// Only support reject rule type
@@ -386,7 +387,7 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
case "Shadowsocks":
path = fmt.Sprintf("/api/ss/v1/trigger/%d", c.NodeID)
default:
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
for _, r := range *detectResultList {
@@ -421,7 +422,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*
v2rayNodeInfo := new(V2rayNodeInfo)
if err := json.Unmarshal(*nodeInfoResponse, v2rayNodeInfo); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
}
if c.SpeedLimit > 0 {
@@ -458,7 +459,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *json.RawMessage) (*api
var speedlimit uint64 = 0
shadowsocksNodeInfo := new(ShadowsocksNodeInfo)
if err := json.Unmarshal(*nodeInfoResponse, shadowsocksNodeInfo); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
}
if c.SpeedLimit > 0 {
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
@@ -495,7 +496,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (
trojanNodeInfo := new(TrojanNodeInfo)
if err := json.Unmarshal(*nodeInfoResponse, trojanNodeInfo); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
}
if c.SpeedLimit > 0 {
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
@@ -527,7 +528,7 @@ func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage
vmessUserList := new([]*VMessUser)
if err := json.Unmarshal(*userInfoResponse, vmessUserList); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
}
userList := make([]api.UserInfo, len(*vmessUserList))
@@ -555,7 +556,7 @@ func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessag
trojanUserList := new([]*TrojanUser)
if err := json.Unmarshal(*userInfoResponse, trojanUserList); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
}
userList := make([]api.UserInfo, len(*trojanUserList))
@@ -563,7 +564,7 @@ func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessag
if c.SpeedLimit > 0 {
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
} else {
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
speedlimit = (user.SpeedLimit * 1000000) / 8
}
userList[i] = api.UserInfo{
UID: user.UID,
@@ -583,7 +584,7 @@ func (c *APIClient) ParseSSUserListResponse(userInfoResponse *json.RawMessage) (
ssUserList := new([]*SSUser)
if err := json.Unmarshal(*userInfoResponse, ssUserList); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
}
userList := make([]api.UserInfo, len(*ssUserList))

View File

@@ -89,7 +89,7 @@ func TestGetUserList(t *testing.T) {
func TestReportNodeStatus(t *testing.T) {
client := CreateClient()
nodeStatus := &api.NodeStatus{
1, 1, 1, 256,
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
}
err := client.ReportNodeStatus(nodeStatus)
if err != nil {
@@ -111,7 +111,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
IP: fmt.Sprintf("1.1.1.%d", i),
}
}
//client.Debug()
// client.Debug()
err = client.ReportNodeOnlineUsers(&onlineUserList)
if err != nil {
t.Error(err)
@@ -154,8 +154,8 @@ func TestReportIllegal(t *testing.T) {
client := CreateClient()
detectResult := []api.DetectResult{
api.DetectResult{1, 1},
api.DetectResult{1, 2},
{1, 1},
{1, 2},
}
client.Debug()
err := client.ReportIllegal(&detectResult)

View File

@@ -49,7 +49,7 @@ type UserResponse struct {
ID int `json:"id"`
Email string `json:"email"`
Passwd string `json:"passwd"`
Port int `json:"port"`
Port uint32 `json:"port"`
Method string `json:"method"`
SpeedLimit float64 `json:"node_speedlimit"`
DeviceLimit int `json:"node_connector"`

View File

@@ -13,14 +13,15 @@ import (
"sync"
"time"
"github.com/XrayR-project/XrayR/api"
"github.com/go-resty/resty/v2"
"github.com/XrayR-project/XrayR/api"
)
var (
firstPortRe = regexp.MustCompile(`(?m)port=(?P<outport>\d+)#?`) // First Port
secondPortRe = regexp.MustCompile(`(?m)port=\d+#(\d+)`) // Second Port
hostRe = regexp.MustCompile(`(?m)host=([\w\.]+)\|?`) // Host
hostRe = regexp.MustCompile(`(?m)host=([\w.]+)\|?`) // Host
)
// APIClient create a api client to the panel.
@@ -89,7 +90,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// open the file
file, err := os.Open(path)
//handle errors while opening
// handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return LocalRuleList
@@ -107,7 +108,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return make([]api.DetectRule, 0)
return
}
file.Close()
@@ -143,7 +144,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
if response.Ret != 1 {
res, _ := json.Marshal(&response)
return nil, fmt.Errorf("Ret %s invalid", string(res))
return nil, fmt.Errorf("ret %s invalid", string(res))
}
return response, nil
}
@@ -164,12 +165,12 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
nodeInfoResponse := new(NodeInfoResponse)
if err := json.Unmarshal(response.Data, nodeInfoResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
}
// New sspanel API
disableCustomConfig := c.DisableCustomConfig
if nodeInfoResponse.Version == "2021.11" && !disableCustomConfig {
if nodeInfoResponse.Version != "" && !disableCustomConfig {
// Check if custom_config is empty
if configString, err := json.Marshal(nodeInfoResponse.CustomConfig); err != nil || string(configString) == "[]" {
log.Printf("custom_config is empty! take config from address now.")
@@ -183,7 +184,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
if err != nil {
res, _ := json.Marshal(nodeInfoResponse)
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://crackair.gitbook.io/xrayr-project/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://xrayr-project.github.io/XrayR-doc/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
}
} else {
switch c.NodeType {
@@ -196,7 +197,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
case "Shadowsocks-Plugin":
nodeInfo, err = c.ParseSSPluginNodeResponse(nodeInfoResponse)
default:
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
}
@@ -225,12 +226,12 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
userListResponse := new([]UserResponse)
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
}
userList, err := c.ParseUserListResponse(userListResponse)
if err != nil {
res, _ := json.Marshal(userListResponse)
return nil, fmt.Errorf("Parse user list failed: %s", string(res))
return nil, fmt.Errorf("parse user list failed: %s", string(res))
}
return userList, nil
}
@@ -239,8 +240,8 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
systemload := SystemLoad{
Uptime: strconv.Itoa(nodeStatus.Uptime),
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.CPU/100, nodeStatus.CPU/100),
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
}
res, err := c.client.R().
@@ -257,7 +258,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
return nil
}
//ReportNodeOnlineUsers reports online user ip
// ReportNodeOnlineUsers reports online user ip
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
c.access.Lock()
defer c.access.Unlock()
@@ -334,7 +335,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
ruleListResponse := new([]RuleItem)
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
}
for _, r := range *ruleListResponse {
@@ -378,18 +379,23 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
var header json.RawMessage
var speedlimit uint64 = 0
if nodeInfoResponse.RawServerString == "" {
return nil, fmt.Errorf("No server info in response")
return nil, fmt.Errorf("no server info in response")
}
//nodeInfo.RawServerString = strings.ToLower(nodeInfo.RawServerString)
// nodeInfo.RawServerString = strings.ToLower(nodeInfo.RawServerString)
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
port, err := strconv.Atoi(serverConf[1])
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
if err != nil {
return nil, err
}
alterID, err := strconv.Atoi(serverConf[2])
port := uint32(parsedPort)
parsedAlterID, err := strconv.ParseInt(serverConf[2], 10, 16)
if err != nil {
return nil, err
}
alterID := uint16(parsedAlterID)
// Compatible with more node types config
for _, value := range serverConf[3:5] {
switch value {
@@ -439,7 +445,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
}
if err != nil {
return nil, fmt.Errorf("Marshal Header Type %s into config fialed: %s", header, err)
return nil, fmt.Errorf("marshal Header Type %s into config fialed: %s", header, err)
}
// Create GeneralNodeInfo
@@ -464,7 +470,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
// ParseSSNodeResponse parse the response for the given nodeinfor format
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
var port int = 0
var port uint32 = 0
var speedlimit uint64 = 0
var method string
path := "/mod_mu/users"
@@ -482,7 +488,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
userListResponse := new([]UserResponse)
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
}
// Find the multi-user
for _, u := range *userListResponse {
@@ -493,7 +499,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
}
}
if port == 0 || method == "" {
return nil, fmt.Errorf("Cant find the single port multi user")
return nil, fmt.Errorf("cant find the single port multi user")
}
if c.SpeedLimit > 0 {
@@ -521,10 +527,11 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
var speedlimit uint64 = 0
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
port, err := strconv.Atoi(serverConf[1])
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
if err != nil {
return nil, err
}
port := uint32(parsedPort)
port = port - 1 // Shadowsocks-Plugin requires two ports, one for ss the other for other stream protocol
if port <= 0 {
return nil, fmt.Errorf("Shadowsocks-Plugin listen port must bigger than 1")
@@ -597,7 +604,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
}
if nodeInfoResponse.RawServerString == "" {
return nil, fmt.Errorf("No server info in response")
return nil, fmt.Errorf("no server info in response")
}
if result := firstPortRe.FindStringSubmatch(nodeInfoResponse.RawServerString); len(result) > 1 {
outsidePort = result[1]
@@ -615,10 +622,11 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
p = outsidePort
}
port, err := strconv.Atoi(p)
parsedPort, err := strconv.ParseInt(p, 10, 32)
if err != nil {
return nil, err
}
port := uint32(parsedPort)
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
extraServerConf := strings.Split(serverConf[1], "|")
@@ -671,7 +679,7 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
var deviceLimit, localDeviceLimit int = 0, 0
var speedlimit uint64 = 0
userList := []api.UserInfo{}
var userList []api.UserInfo
for _, user := range *userInfoResponse {
if c.DeviceLimit > 0 {
deviceLimit = c.DeviceLimit
@@ -727,7 +735,7 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
var speedlimit uint64 = 0
var EnableTLS, EnableVless bool
var AlterID int = 0
var AlterID uint16 = 0
var TLSType, transportProtocol string
nodeConfig := new(CustomConfig)
@@ -739,10 +747,11 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
}
port, err := strconv.Atoi(nodeConfig.OffsetPortNode)
parsedPort, err := strconv.ParseInt(nodeConfig.OffsetPortNode, 10, 32)
if err != nil {
return nil, err
}
port := uint32(parsedPort)
if c.NodeType == "Shadowsocks" {
transportProtocol = "tcp"
@@ -751,9 +760,12 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
if c.NodeType == "V2ray" {
transportProtocol = nodeConfig.Network
TLSType = nodeConfig.Security
if AlterID, err = strconv.Atoi(nodeConfig.AlterID); err != nil {
if parsedAlterID, err := strconv.ParseInt(nodeConfig.AlterID, 10, 16); err != nil {
return nil, err
} else {
AlterID = uint16(parsedAlterID)
}
if TLSType == "tls" || TLSType == "xtls" {
EnableTLS = true
}
@@ -765,14 +777,20 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
if c.NodeType == "Trojan" {
EnableTLS = true
TLSType = "tls"
if nodeConfig.Grpc == "1" {
transportProtocol = "grpc"
} else {
transportProtocol = "tcp"
}
transportProtocol = "tcp"
// Select security type
if nodeConfig.EnableXtls == "1" {
TLSType = "xtls"
} else if nodeConfig.Security != "" {
TLSType = nodeConfig.Security // try to read security from config
}
// Select transport protocol
if nodeConfig.Grpc == "1" {
transportProtocol = "grpc"
} else if nodeConfig.Network != "" {
transportProtocol = nodeConfig.Network // try to read transport protocol from config
}
}

View File

@@ -83,7 +83,7 @@ func TestGetUserList(t *testing.T) {
func TestReportNodeStatus(t *testing.T) {
client := CreateClient()
nodeStatus := &api.NodeStatus{
1, 1, 1, 256,
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
}
err := client.ReportNodeStatus(nodeStatus)
if err != nil {
@@ -105,7 +105,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
IP: fmt.Sprintf("1.1.1.%d", i),
}
}
//client.Debug()
// client.Debug()
err = client.ReportNodeOnlineUsers(&onlineUserList)
if err != nil {
t.Error(err)
@@ -126,7 +126,7 @@ func TestReportReportUserTraffic(t *testing.T) {
Download: 114514,
}
}
//client.Debug()
// client.Debug()
err = client.ReportUserTraffic(&generalUserTraffic)
if err != nil {
t.Error(err)
@@ -148,8 +148,8 @@ func TestReportIllegal(t *testing.T) {
client := CreateClient()
detectResult := []api.DetectResult{
api.DetectResult{1, 2},
api.DetectResult{1, 3},
{1, 2},
{1, 3},
}
client.Debug()
err := client.ReportIllegal(&detectResult)

View File

@@ -1,7 +1,8 @@
// Deprecated: after 2023.6.1
package v2board
type UserTraffic struct {
UID int `json:"user_id"`
Upload int64 `json:"u"`
Download int64 `json:"d"`
UID int `json:"user_id"`
Upload int64 `json:"u"`
Download int64 `json:"d"`
}

View File

@@ -8,12 +8,14 @@ import (
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/XrayR-project/XrayR/api"
"github.com/bitly/go-simplejson"
"github.com/go-resty/resty/v2"
"github.com/XrayR-project/XrayR/api"
)
// APIClient create an api client to the panel.
@@ -80,7 +82,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// open the file
file, err := os.Open(path)
//handle errors while opening
// handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return LocalRuleList
@@ -98,7 +100,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return make([]api.DetectRule, 0)
return
}
file.Close()
@@ -132,7 +134,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
}
rtn, err := simplejson.NewJson(res.Body())
if err != nil {
return nil, fmt.Errorf("Ret %s invalid", res.String())
return nil, fmt.Errorf("ret %s invalid", res.String())
}
return rtn, nil
}
@@ -219,14 +221,14 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
user.Email = response.Get("data").GetIndex(i).Get("secret").MustString()
user.Passwd = response.Get("data").GetIndex(i).Get("secret").MustString()
user.Method = response.Get("data").GetIndex(i).Get("cipher").MustString()
user.Port = response.Get("data").GetIndex(i).Get("port").MustInt()
user.Port = uint32(response.Get("data").GetIndex(i).Get("port").MustUint64())
case "Trojan":
user.UUID = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
user.Email = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
case "V2ray":
user.UUID = response.Get("data").GetIndex(i).Get("v2ray_user").Get("uuid").MustString()
user.Email = response.Get("data").GetIndex(i).Get("v2ray_user").Get("email").MustString()
user.AlterID = response.Get("data").GetIndex(i).Get("v2ray_user").Get("alter_id").MustInt()
user.AlterID = uint16(response.Get("data").GetIndex(i).Get("v2ray_user").Get("alter_id").MustUint64())
}
userList[i] = user
}
@@ -278,6 +280,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
defer c.access.Unlock()
ruleListResponse := c.ConfigResp.Get("routing").Get("rules").GetIndex(1).Get("domain").MustStringArray()
for i, rule := range ruleListResponse {
rule = strings.TrimPrefix(rule, "regexp:")
ruleListItem := api.DetectRule{
ID: i,
Pattern: regexp.MustCompile(rule),
@@ -292,7 +295,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
return nil
}
//ReportNodeOnlineUsers implements the API interface
// ReportNodeOnlineUsers implements the API interface
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
return nil
}
@@ -308,7 +311,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (
if c.EnableXTLS {
TLSType = "xtls"
}
port := nodeInfoResponse.Get("local_port").MustInt()
port := uint32(nodeInfoResponse.Get("local_port").MustUint64())
host := nodeInfoResponse.Get("ssl").Get("sni").MustString()
// Create GeneralNodeInfo
@@ -326,7 +329,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (
// ParseSSNodeResponse parse the response for the given nodeinfor format
func (c *APIClient) ParseSSNodeResponse() (*api.NodeInfo, error) {
var port int
var port uint32
var method string
userInfo, err := c.GetUserList()
if err != nil {
@@ -355,7 +358,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
var path, host, serviceName string
var header json.RawMessage
var enableTLS bool
var alterID int = 0
var alterID uint16 = 0
if c.EnableXTLS {
TLSType = "xtls"
}
@@ -369,10 +372,10 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
inboundInfo, _ = simplejson.NewJson(marshalByte)
} else {
return nil, fmt.Errorf("Unable to find inbound(s) in the nodeInfo.")
return nil, fmt.Errorf("unable to find inbound(s) in the nodeInfo")
}
port := inboundInfo.Get("port").MustInt()
port := uint32(inboundInfo.Get("port").MustUint64())
transportProtocol := inboundInfo.Get("streamSettings").Get("network").MustString()
switch transportProtocol {

View File

@@ -82,7 +82,7 @@ func TestReportReportUserTraffic(t *testing.T) {
Download: 114514,
}
}
//client.Debug()
// client.Debug()
err = client.ReportUserTraffic(&generalUserTraffic)
if err != nil {
t.Error(err)

7
api/v2raysocks/model.go Normal file
View File

@@ -0,0 +1,7 @@
package v2raysocks
type UserTraffic struct {
UID int `json:"user_id"`
Upload int64 `json:"u"`
Download int64 `json:"d"`
}

View File

@@ -0,0 +1,414 @@
package v2raysocks
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/bitly/go-simplejson"
"github.com/go-resty/resty/v2"
"github.com/XrayR-project/XrayR/api"
)
// APIClient create an api client to the panel.
type APIClient struct {
client *resty.Client
APIHost string
NodeID int
Key string
NodeType string
EnableVless bool
EnableXTLS bool
SpeedLimit float64
DeviceLimit int
LocalRuleList []api.DetectRule
ConfigResp *simplejson.Json
access sync.Mutex
}
// New create an api instance
func New(apiConfig *api.Config) *APIClient {
client := resty.New()
client.SetRetryCount(3)
if apiConfig.Timeout > 0 {
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
} else {
client.SetTimeout(5 * time.Second)
}
client.OnError(func(req *resty.Request, err error) {
if v, ok := err.(*resty.ResponseError); ok {
// v.Response contains the last response from the server
// v.Err contains the original error
log.Print(v.Err)
}
})
// Create Key for each requests
client.SetQueryParams(map[string]string{
"node_id": strconv.Itoa(apiConfig.NodeID),
"token": apiConfig.Key,
})
// Read local rule list
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
apiClient := &APIClient{
client: client,
NodeID: apiConfig.NodeID,
Key: apiConfig.Key,
APIHost: apiConfig.APIHost,
NodeType: apiConfig.NodeType,
EnableVless: apiConfig.EnableVless,
EnableXTLS: apiConfig.EnableXTLS,
SpeedLimit: apiConfig.SpeedLimit,
DeviceLimit: apiConfig.DeviceLimit,
LocalRuleList: localRuleList,
}
return apiClient
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
LocalRuleList = make([]api.DetectRule, 0)
if path != "" {
// open the file
file, err := os.Open(path)
// handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return LocalRuleList
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, api.DetectRule{
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
file.Close()
}
return LocalRuleList
}
// Describe return a description of the client
func (c *APIClient) Describe() api.ClientInfo {
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
}
// Debug set the client debug for client
func (c *APIClient) Debug() {
c.client.SetDebug(true)
}
func (c *APIClient) assembleURL(path string) string {
return c.APIHost + path
}
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*simplejson.Json, error) {
if err != nil {
return nil, fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
}
if res.StatusCode() > 400 {
body := res.Body()
return nil, fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
}
rtn, err := simplejson.NewJson(res.Body())
if err != nil {
return nil, fmt.Errorf("ret %s invalid", res.String())
}
return rtn, nil
}
// GetNodeInfo will pull NodeInfo Config from sspanel
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
var nodeType string
switch c.NodeType {
case "V2ray", "Trojan", "Shadowsocks":
nodeType = strings.ToLower(c.NodeType)
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err := c.client.R().
SetQueryParams(map[string]string{
"act": "config",
"nodetype": nodeType,
}).
ForceContentType("application/json").
Get(c.APIHost)
response, err := c.parseResponse(res, "", err)
c.access.Lock()
defer c.access.Unlock()
c.ConfigResp = response
if err != nil {
return nil, err
}
switch c.NodeType {
case "V2ray":
nodeInfo, err = c.ParseV2rayNodeResponse(response)
case "Trojan":
nodeInfo, err = c.ParseTrojanNodeResponse(response)
case "Shadowsocks":
nodeInfo, err = c.ParseSSNodeResponse(response)
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
if err != nil {
res, _ := response.MarshalJSON()
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
}
return nodeInfo, nil
}
// GetUserList will pull user form sspanel
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
var nodeType string
switch c.NodeType {
case "V2ray", "Trojan", "Shadowsocks":
nodeType = strings.ToLower(c.NodeType)
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
res, err := c.client.R().
SetQueryParams(map[string]string{
"act": "user",
"nodetype": nodeType,
}).
ForceContentType("application/json").
Get(c.APIHost)
response, err := c.parseResponse(res, "", err)
if err != nil {
return nil, err
}
numOfUsers := len(response.Get("data").MustArray())
userList := make([]api.UserInfo, numOfUsers)
for i := 0; i < numOfUsers; i++ {
user := api.UserInfo{}
user.UID = response.Get("data").GetIndex(i).Get("id").MustInt()
switch c.NodeType {
case "Shadowsocks":
user.Email = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("secret").MustString()
user.Passwd = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("secret").MustString()
user.Method = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("cipher").MustString()
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("speed_limit").MustUint64() * 1000000 / 8)
case "Trojan":
user.UUID = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
user.Email = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("trojan_user").Get("speed_limit").MustUint64() * 1000000 / 8)
case "V2ray":
user.UUID = response.Get("data").GetIndex(i).Get("v2ray_user").Get("uuid").MustString()
user.Email = response.Get("data").GetIndex(i).Get("v2ray_user").Get("email").MustString()
user.AlterID = uint16(response.Get("data").GetIndex(i).Get("v2ray_user").Get("alter_id").MustUint64())
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("v2ray_user").Get("speed_limit").MustUint64() * 1000000 / 8)
}
if c.SpeedLimit > 0 {
user.SpeedLimit = uint64((c.SpeedLimit * 1000000) / 8)
}
user.DeviceLimit = c.DeviceLimit
userList[i] = user
}
return &userList, nil
}
// ReportUserTraffic reports the user traffic
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
data := make([]UserTraffic, len(*userTraffic))
for i, traffic := range *userTraffic {
data[i] = UserTraffic{
UID: traffic.UID,
Upload: traffic.Upload,
Download: traffic.Download}
}
res, err := c.client.R().
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
SetQueryParams(map[string]string{
"act": "submit",
"nodetype": strings.ToLower(c.NodeType),
}).
SetBody(data).
ForceContentType("application/json").
Post(c.APIHost)
_, err = c.parseResponse(res, "", err)
if err != nil {
return err
}
return nil
}
// GetNodeRule implements the API interface
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
ruleList := c.LocalRuleList
if c.NodeType != "V2ray" {
return &ruleList, nil
}
// V2board only support the rule for v2ray
// fix: reuse config response
c.access.Lock()
defer c.access.Unlock()
ruleListResponse := c.ConfigResp.Get("routing").Get("rules").GetIndex(1).Get("domain").MustStringArray()
for i, rule := range ruleListResponse {
rule = strings.TrimPrefix(rule, "regexp:")
ruleListItem := api.DetectRule{
ID: i,
Pattern: regexp.MustCompile(rule),
}
ruleList = append(ruleList, ruleListItem)
}
return &ruleList, nil
}
// ReportNodeStatus implements the API interface
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
return nil
}
// ReportNodeOnlineUsers implements the API interface
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
return nil
}
// ReportIllegal implements the API interface
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
return nil
}
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
var TLSType = "tls"
if c.EnableXTLS {
TLSType = "xtls"
}
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
inboundInfo, _ := simplejson.NewJson(marshalByte)
port := uint32(inboundInfo.Get("port").MustUint64())
host := inboundInfo.Get("streamSettings").Get("tlsSettings").Get("serverName").MustString()
// Create GeneralNodeInfo
nodeinfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
TransportProtocol: "tcp",
EnableTLS: true,
TLSType: TLSType,
Host: host,
}
return nodeinfo, nil
}
// ParseSSNodeResponse parse the response for the given nodeinfor format
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
var method string
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
inboundInfo, _ := simplejson.NewJson(marshalByte)
port := uint32(inboundInfo.Get("port").MustUint64())
userInfo, err := c.GetUserList()
if err != nil {
return nil, err
}
if len(*userInfo) > 0 {
method = (*userInfo)[0].Method
}
// Create GeneralNodeInfo
nodeinfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
TransportProtocol: "tcp",
CypherMethod: method,
}
return nodeinfo, nil
}
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
var TLSType string = "tls"
var path, host, serviceName string
var header json.RawMessage
var enableTLS bool
var alterID uint16 = 0
if c.EnableXTLS {
TLSType = "xtls"
}
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
inboundInfo, _ := simplejson.NewJson(marshalByte)
port := uint32(inboundInfo.Get("port").MustUint64())
transportProtocol := inboundInfo.Get("streamSettings").Get("network").MustString()
switch transportProtocol {
case "ws":
path = inboundInfo.Get("streamSettings").Get("wsSettings").Get("path").MustString()
host = inboundInfo.Get("streamSettings").Get("wsSettings").Get("headers").Get("Host").MustString()
case "grpc":
if data, ok := inboundInfo.Get("streamSettings").Get("grpcSettings").CheckGet("serviceName"); ok {
serviceName = data.MustString()
}
case "tcp":
if data, ok := inboundInfo.Get("streamSettings").Get("tcpSettings").CheckGet("header"); ok {
if httpHeader, err := data.MarshalJSON(); err != nil {
return nil, err
} else {
header = httpHeader
}
}
}
if inboundInfo.Get("streamSettings").Get("security").MustString() == "tls" {
enableTLS = true
} else {
enableTLS = false
}
// Create GeneralNodeInfo
// AlterID will be updated after next sync
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: port,
AlterID: alterID,
TransportProtocol: transportProtocol,
EnableTLS: enableTLS,
TLSType: TLSType,
Path: path,
Host: host,
EnableVless: c.EnableVless,
ServiceName: serviceName,
Header: header,
}
return nodeInfo, nil
}

View File

@@ -0,0 +1,102 @@
package v2raysocks_test
import (
"testing"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/v2raysocks"
)
func CreateClient() api.API {
apiConfig := &api.Config{
APIHost: "https://127.0.0.1/",
Key: "123456789",
NodeID: 280002,
NodeType: "V2ray",
}
client := v2raysocks.New(apiConfig)
return client
}
func TestGetV2rayNodeinfo(t *testing.T) {
client := CreateClient()
client.Debug()
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetSSNodeinfo(t *testing.T) {
apiConfig := &api.Config{
APIHost: "https://127.0.0.1/",
Key: "123456789",
NodeID: 280009,
NodeType: "Shadowsocks",
}
client := v2raysocks.New(apiConfig)
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetTrojanNodeinfo(t *testing.T) {
apiConfig := &api.Config{
APIHost: "https://127.0.0.1/",
Key: "123456789",
NodeID: 280008,
NodeType: "Trojan",
}
client := v2raysocks.New(apiConfig)
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetUserList(t *testing.T) {
client := CreateClient()
userList, err := client.GetUserList()
if err != nil {
t.Error(err)
}
t.Log(userList)
}
func TestReportReportUserTraffic(t *testing.T) {
client := CreateClient()
userList, err := client.GetUserList()
if err != nil {
t.Error(err)
}
generalUserTraffic := make([]api.UserTraffic, len(*userList))
for i, userInfo := range *userList {
generalUserTraffic[i] = api.UserTraffic{
UID: userInfo.UID,
Upload: 114514,
Download: 114514,
}
}
// client.Debug()
err = client.ReportUserTraffic(&generalUserTraffic)
if err != nil {
t.Error(err)
}
}
func TestGetNodeRule(t *testing.T) {
client := CreateClient()
client.Debug()
ruleList, err := client.GetNodeRule()
if err != nil {
t.Error(err)
}
t.Log(ruleList)
}

View File

@@ -9,8 +9,6 @@ import (
"sync"
"time"
"github.com/XrayR-project/XrayR/common/limiter"
"github.com/XrayR-project/XrayR/common/rule"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/log"
@@ -22,10 +20,13 @@ import (
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing"
routing_session "github.com/xtls/xray-core/features/routing/session"
routingSession "github.com/xtls/xray-core/features/routing/session"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/pipe"
"github.com/XrayR-project/XrayR/common/limiter"
"github.com/XrayR-project/XrayR/common/rule"
)
var errSniffingTimeout = newError("timeout on sniffing")
@@ -98,7 +99,7 @@ type DefaultDispatcher struct {
dns dns.Client
fdns dns.FakeDNSEngine
Limiter *limiter.Limiter
RuleManager *rule.RuleManager
RuleManager *rule.Manager
}
func init() {
@@ -139,7 +140,9 @@ func (*DefaultDispatcher) Start() error {
}
// Close implements common.Closable.
func (*DefaultDispatcher) Close() error { return nil }
func (*DefaultDispatcher) Close() error {
return nil
}
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, error) {
downOpt := pipe.OptionsFromContext(ctx)
@@ -172,7 +175,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx))
}
domain := addr.Domain()
ips, err := d.dns.LookupIP(domain, dns.IPOption{true, true, false})
ips, err := d.dns.LookupIP(domain, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
if err == nil {
for _, ip := range ips {
ip2domain.Store(ip.String(), domain)
@@ -233,7 +236,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
// Speed Limit and Device Limit
bucket, ok, reject := d.Limiter.GetUserBucket(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
if reject {
newError("Devices reach the limit: ", user.Email).AtError().WriteToLog()
newError("Devices reach the limit: ", user.Email).AtWarning().WriteToLog()
common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader)
@@ -314,13 +317,13 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
}
sniffingRequest := content.SniffingRequest
inbound, outbound, err := d.getLink(ctx, destination.Network, sniffingRequest)
in, out, err := d.getLink(ctx, destination.Network, sniffingRequest)
if err != nil {
return nil, err
}
switch {
case !sniffingRequest.Enabled:
go d.routedDispatch(ctx, outbound, destination)
go d.routedDispatch(ctx, out, destination)
case destination.Network != net.Network_TCP:
// Only metadata sniff will be used for non tcp connection
result, err := sniffer(ctx, nil, true)
@@ -337,13 +340,13 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
}
}
}
go d.routedDispatch(ctx, outbound, destination)
go d.routedDispatch(ctx, out, destination)
default:
go func() {
cReader := &cachedReader{
reader: outbound.Reader.(*pipe.Reader),
reader: out.Reader.(*pipe.Reader),
}
outbound.Reader = cReader
out.Reader = cReader
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
if err == nil {
content.Protocol = result.Protocol()
@@ -358,10 +361,10 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
ob.Target = destination
}
}
d.routedDispatch(ctx, outbound, destination)
d.routedDispatch(ctx, out, destination)
}()
}
return inbound, nil
return in, nil
}
// DispatchLink implements routing.Dispatcher.
@@ -501,7 +504,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
}
}
routingLink := routing_session.AsRoutingContext(ctx)
routingLink := routingSession.AsRoutingContext(ctx)
inTag := routingLink.GetInboundTag()
isPickRoute := 0
if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" {

View File

@@ -1,4 +1,4 @@
// Package dispather implement the rate limiter and the onlie device counter
// Package mydispatcher Package dispatcher implement the rate limiter and the online device counter
package mydispatcher
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen

View File

@@ -2,8 +2,6 @@ package mydispatcher
import "github.com/xtls/xray-core/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
return errors.New(values...)
}

View File

@@ -1,14 +0,0 @@
package cmd
import "github.com/urfave/cli"
// CreateCommands Creates all CLI commands.
func CreateCommands() []cli.Command {
return []cli.Command{
createRun(),
createRevoke(),
createRenew(),
createDNSHelp(),
createList(),
}
}

View File

@@ -1,23 +0,0 @@
package cmd
import (
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/urfave/cli"
)
func Before(ctx *cli.Context) error {
if ctx.GlobalString("path") == "" {
log.Panic("Could not determine current working directory. Please pass --path.")
}
err := createNonExistingFolder(ctx.GlobalString("path"))
if err != nil {
log.Panicf("Could not check/create path: %v", err)
}
if ctx.GlobalString("server") == "" {
log.Panic("Could not determine current working server. Please pass --server.")
}
return nil
}

View File

@@ -1,73 +0,0 @@
package cmd
import (
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"github.com/urfave/cli"
)
func createDNSHelp() cli.Command {
return cli.Command{
Name: "dnshelp",
Usage: "Shows additional help for the '--dns' global option",
Action: dnsHelp,
Flags: []cli.Flag{
cli.StringFlag{
Name: "code, c",
Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()),
},
},
}
}
func dnsHelp(ctx *cli.Context) error {
code := ctx.String("code")
if code == "" {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
ew := &errWriter{w: w}
ew.writeln(`Credentials for DNS providers must be passed through environment variables.`)
ew.writeln()
ew.writeln(`To display the documentation for a DNS providers:`)
ew.writeln()
ew.writeln("\t$ lego dnshelp -c code")
ew.writeln()
ew.writeln("All DNS codes:")
ew.writef("\t%s\n", allDNSCodes())
ew.writeln()
ew.writeln("More information: https://go-acme.github.io/lego/dns")
if ew.err != nil {
return ew.err
}
return w.Flush()
}
return displayDNSHelp(strings.ToLower(code))
}
type errWriter struct {
w io.Writer
err error
}
func (ew *errWriter) writeln(a ...interface{}) {
if ew.err != nil {
return
}
_, ew.err = fmt.Fprintln(ew.w, a...)
}
func (ew *errWriter) writef(format string, a ...interface{}) {
if ew.err != nil {
return
}
_, ew.err = fmt.Fprintf(ew.w, format, a...)
}

View File

@@ -1,136 +0,0 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"path/filepath"
"strings"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/urfave/cli"
)
func createList() cli.Command {
return cli.Command{
Name: "list",
Usage: "Display certificates and accounts information.",
Action: list,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "accounts, a",
Usage: "Display accounts.",
},
cli.BoolFlag{
Name: "names, n",
Usage: "Display certificate common names only.",
},
},
}
}
func list(ctx *cli.Context) error {
if ctx.Bool("accounts") && !ctx.Bool("names") {
if err := listAccount(ctx); err != nil {
return err
}
}
return listCertificates(ctx)
}
func listCertificates(ctx *cli.Context) error {
certsStorage := NewCertificatesStorage(ctx)
matches, err := filepath.Glob(filepath.Join(certsStorage.GetRootPath(), "*.crt"))
if err != nil {
return err
}
names := ctx.Bool("names")
if len(matches) == 0 {
if !names {
fmt.Println("No certificates found.")
}
return nil
}
if !names {
fmt.Println("Found the following certs:")
}
for _, filename := range matches {
if strings.HasSuffix(filename, ".issuer.crt") {
continue
}
data, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
pCert, err := certcrypto.ParsePEMCertificate(data)
if err != nil {
return err
}
if names {
fmt.Println(pCert.Subject.CommonName)
} else {
fmt.Println(" Certificate Name:", pCert.Subject.CommonName)
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
fmt.Println(" Expiry Date:", pCert.NotAfter)
fmt.Println(" Certificate Path:", filename)
fmt.Println()
}
}
return nil
}
func listAccount(ctx *cli.Context) error {
// fake email, needed by NewAccountsStorage
if err := ctx.GlobalSet("email", "unknown"); err != nil {
return err
}
accountsStorage := NewAccountsStorage(ctx)
matches, err := filepath.Glob(filepath.Join(accountsStorage.GetRootPath(), "*", "*", "*.json"))
if err != nil {
return err
}
if len(matches) == 0 {
fmt.Println("No accounts found.")
return nil
}
fmt.Println("Found the following accounts:")
for _, filename := range matches {
data, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
var account Account
err = json.Unmarshal(data, &account)
if err != nil {
return err
}
uri, err := url.Parse(account.Registration.URI)
if err != nil {
return err
}
fmt.Println(" Email:", account.Email)
fmt.Println(" Server:", uri.Host)
fmt.Println(" Path:", filepath.Dir(filename))
fmt.Println()
}
return nil
}

View File

@@ -1,225 +0,0 @@
package cmd
import (
"crypto"
"crypto/x509"
"time"
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/lego"
"github.com/urfave/cli"
)
const (
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
renewEnvCertPath = "LEGO_CERT_PATH"
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
)
func createRenew() cli.Command {
return cli.Command{
Name: "renew",
Usage: "Renew a certificate",
Action: renew,
Before: func(ctx *cli.Context) error {
// we require either domains or csr, but not both
hasDomains := len(ctx.GlobalStringSlice("domains")) > 0
hasCsr := len(ctx.GlobalString("csr")) > 0
if hasDomains && hasCsr {
log.Panic("Please specify either --domains/-d or --csr/-c, but not both")
}
if !hasDomains && !hasCsr {
log.Panic("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
}
return nil
},
Flags: []cli.Flag{
cli.IntFlag{
Name: "days",
Value: 30,
Usage: "The number of days left on a certificate to renew it.",
},
cli.BoolFlag{
Name: "reuse-key",
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
},
cli.BoolFlag{
Name: "no-bundle",
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
cli.BoolFlag{
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
},
cli.StringFlag{
Name: "renew-hook",
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
},
cli.StringFlag{
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
},
},
}
}
func renew(ctx *cli.Context) error {
account, client := setup(ctx, NewAccountsStorage(ctx))
setupChallenges(ctx, client)
if account.Registration == nil {
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email)
}
certsStorage := NewCertificatesStorage(ctx)
bundle := !ctx.Bool("no-bundle")
meta := map[string]string{renewEnvAccountEmail: account.Email}
// CSR
if ctx.GlobalIsSet("csr") {
return renewForCSR(ctx, client, certsStorage, bundle, meta)
}
// Domains
return renewForDomains(ctx, client, certsStorage, bundle, meta)
}
func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
domains := ctx.GlobalStringSlice("domains")
domain := domains[0]
// load the cert resource from files.
// We store the certificate, private key and metadata in different files
// as web servers would not be able to work with a combined file.
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
if err != nil {
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
}
cert := certificates[0]
if !needRenewal(cert, domain, ctx.Int("days")) {
return nil
}
// This is just meant to be informal for the user.
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
certDomains := certcrypto.ExtractDomains(cert)
var privateKey crypto.PrivateKey
if ctx.Bool("reuse-key") {
keyBytes, errR := certsStorage.ReadFile(domain, ".key")
if errR != nil {
log.Panicf("Error while loading the private key for domain %s\n\t%v", domain, errR)
}
privateKey, errR = certcrypto.ParsePEMPrivateKey(keyBytes)
if errR != nil {
return errR
}
}
request := certificate.ObtainRequest{
Domains: merge(certDomains, domains),
Bundle: bundle,
PrivateKey: privateKey,
MustStaple: ctx.Bool("must-staple"),
PreferredChain: ctx.String("preferred-chain"),
}
certRes, err := client.Certificate.Obtain(request)
if err != nil {
log.Panic(err)
}
certsStorage.SaveResource(certRes)
meta[renewEnvCertDomain] = domain
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
return launchHook(ctx.String("renew-hook"), meta)
}
func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
csr, err := readCSRFile(ctx.GlobalString("csr"))
if err != nil {
log.Panic(err)
}
domain := csr.Subject.CommonName
// load the cert resource from files.
// We store the certificate, private key and metadata in different files
// as web servers would not be able to work with a combined file.
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
if err != nil {
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
}
cert := certificates[0]
if !needRenewal(cert, domain, ctx.Int("days")) {
return nil
}
// This is just meant to be informal for the user.
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
certRes, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
CSR: csr,
Bundle: bundle,
PreferredChain: ctx.String("preferred-chain"),
})
if err != nil {
log.Panic(err)
}
certsStorage.SaveResource(certRes)
meta[renewEnvCertDomain] = domain
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
return launchHook(ctx.String("renew-hook"), meta)
}
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
if x509Cert.IsCA {
log.Panicf("[%s] Certificate bundle starts with a CA certificate", domain)
}
if days >= 0 {
notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0)
if notAfter > days {
log.Printf("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.",
domain, notAfter, days)
return false
}
}
return true
}
func merge(prevDomains, nextDomains []string) []string {
for _, next := range nextDomains {
var found bool
for _, prev := range prevDomains {
if prev == next {
found = true
break
}
}
if !found {
prevDomains = append(prevDomains, next)
}
}
return prevDomains
}

View File

@@ -1,62 +0,0 @@
package cmd
import (
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/urfave/cli"
)
func createRevoke() cli.Command {
return cli.Command{
Name: "revoke",
Usage: "Revoke a certificate",
Action: revoke,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "keep, k",
Usage: "Keep the certificates after the revocation instead of archiving them.",
},
},
}
}
func revoke(ctx *cli.Context) error {
acc, client := setup(ctx, NewAccountsStorage(ctx))
if acc.Registration == nil {
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email)
}
certsStorage := NewCertificatesStorage(ctx)
certsStorage.CreateRootFolder()
for _, domain := range ctx.GlobalStringSlice("domains") {
log.Printf("Trying to revoke certificate for domain %s", domain)
certBytes, err := certsStorage.ReadFile(domain, ".crt")
if err != nil {
log.Panicf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
}
err = client.Certificate.Revoke(certBytes)
if err != nil {
log.Panicf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
}
log.Println("Certificate was revoked.")
if ctx.Bool("keep") {
return nil
}
certsStorage.CreateArchiveFolder()
err = certsStorage.MoveToArchive(domain)
if err != nil {
return err
}
log.Println("Certificate was archived for domain:", domain)
}
return nil
}

View File

@@ -1,186 +0,0 @@
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/urfave/cli"
)
func createRun() cli.Command {
return cli.Command{
Name: "run",
Usage: "Register an account, then create and install a certificate",
Before: func(ctx *cli.Context) error {
// we require either domains or csr, but not both
hasDomains := len(ctx.GlobalStringSlice("domains")) > 0
hasCsr := len(ctx.GlobalString("csr")) > 0
if hasDomains && hasCsr {
log.Panic("Please specify either --domains/-d or --csr/-c, but not both")
}
if !hasDomains && !hasCsr {
log.Panic("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
}
return nil
},
Action: run,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "no-bundle",
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
cli.BoolFlag{
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
},
cli.StringFlag{
Name: "run-hook",
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
},
cli.StringFlag{
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
},
},
}
}
const rootPathWarningMessage = `!!!! HEADS UP !!!!
Your account credentials have been saved in your Let's Encrypt
configuration directory at "%s".
You should make a secure backup of this folder now. This
configuration directory will also contain certificates and
private keys obtained from Let's Encrypt so making regular
backups of this folder is ideal.
`
func run(ctx *cli.Context) error {
accountsStorage := NewAccountsStorage(ctx)
account, client := setup(ctx, accountsStorage)
setupChallenges(ctx, client)
if account.Registration == nil {
reg, err := register(ctx, client)
if err != nil {
log.Panicf("Could not complete registration\n\t%v", err)
}
account.Registration = reg
if err = accountsStorage.Save(account); err != nil {
log.Panic(err)
}
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
}
certsStorage := NewCertificatesStorage(ctx)
certsStorage.CreateRootFolder()
cert, err := obtainCertificate(ctx, client)
if err != nil {
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
// Due to us not returning partial certificate we can just exit here instead of at the end.
log.Panicf("Could not obtain certificates:\n\t%v", err)
}
certsStorage.SaveResource(cert)
meta := map[string]string{
renewEnvAccountEmail: account.Email,
renewEnvCertDomain: cert.Domain,
renewEnvCertPath: certsStorage.GetFileName(cert.Domain, ".crt"),
renewEnvCertKeyPath: certsStorage.GetFileName(cert.Domain, ".key"),
}
return launchHook(ctx.String("run-hook"), meta)
}
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
// Check for a global accept override
if ctx.GlobalBool("accept-tos") {
return true
}
reader := bufio.NewReader(os.Stdin)
log.Printf("Please review the TOS at %s", client.GetToSURL())
for {
fmt.Println("Do you accept the TOS? Y/n")
text, err := reader.ReadString('\n')
if err != nil {
log.Panicf("Could not read from console: %v", err)
}
text = strings.Trim(text, "\r\n")
switch text {
case "", "y", "Y":
return true
case "n", "N":
return false
default:
fmt.Println("Your input was invalid. Please answer with one of Y/y, n/N or by pressing enter.")
}
}
}
func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, error) {
accepted := handleTOS(ctx, client)
if !accepted {
log.Panic("You did not accept the TOS. Unable to proceed.")
}
if ctx.GlobalBool("eab") {
kid := ctx.GlobalString("kid")
hmacEncoded := ctx.GlobalString("hmac")
if kid == "" || hmacEncoded == "" {
log.Panicf("Requires arguments --kid and --hmac.")
}
return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: accepted,
Kid: kid,
HmacEncoded: hmacEncoded,
})
}
return client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
}
func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Resource, error) {
bundle := !ctx.Bool("no-bundle")
domains := ctx.GlobalStringSlice("domains")
if len(domains) > 0 {
// obtain a certificate, generating a new private key
request := certificate.ObtainRequest{
Domains: domains,
Bundle: bundle,
MustStaple: ctx.Bool("must-staple"),
PreferredChain: ctx.String("preferred-chain"),
}
return client.Certificate.Obtain(request)
}
// read the CSR
csr, err := readCSRFile(ctx.GlobalString("csr"))
if err != nil {
return nil, err
}
// obtain a certificate for this CSR
return client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
CSR: csr,
Bundle: bundle,
PreferredChain: ctx.String("preferred-chain"),
})
}

View File

@@ -1,120 +0,0 @@
package cmd
import (
"github.com/go-acme/lego/v4/lego"
"github.com/urfave/cli"
)
func CreateFlags(defaultPath string) []cli.Flag {
return []cli.Flag{
cli.StringSliceFlag{
Name: "domains, d",
Usage: "Add a domain to the process. Can be specified multiple times.",
},
cli.StringFlag{
Name: "server, s",
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
Value: lego.LEDirectoryProduction,
},
cli.BoolFlag{
Name: "accept-tos, a",
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
},
cli.StringFlag{
Name: "email, m",
Usage: "Email used for registration and recovery contact.",
},
cli.StringFlag{
Name: "csr, c",
Usage: "Certificate signing request filename, if an external CSR is to be used.",
},
cli.BoolFlag{
Name: "eab",
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
},
cli.StringFlag{
Name: "kid",
Usage: "Key identifier from External CA. Used for External Account Binding.",
},
cli.StringFlag{
Name: "hmac",
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
},
cli.StringFlag{
Name: "key-type, k",
Value: "ec256",
Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384.",
},
cli.StringFlag{
Name: "filename",
Usage: "(deprecated) Filename of the generated certificate.",
},
cli.StringFlag{
Name: "path",
EnvVar: "LEGO_PATH",
Usage: "Directory to use for storing the data.",
Value: defaultPath,
},
cli.BoolFlag{
Name: "http",
Usage: "Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges.",
},
cli.StringFlag{
Name: "http.port",
Usage: "Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port.",
Value: ":80",
},
cli.StringFlag{
Name: "http.proxy-header",
Usage: "Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy.",
Value: "Host",
},
cli.StringFlag{
Name: "http.webroot",
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge",
},
cli.StringSliceFlag{
Name: "http.memcached-host",
Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.",
},
cli.BoolFlag{
Name: "tls",
Usage: "Use the TLS challenge to solve challenges. Can be mixed with other types of challenges.",
},
cli.StringFlag{
Name: "tls.port",
Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port.",
Value: ":443",
},
cli.StringFlag{
Name: "dns",
Usage: "Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.",
},
cli.BoolFlag{
Name: "dns.disable-cp",
Usage: "By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.",
},
cli.StringSliceFlag{
Name: "dns.resolvers",
Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
},
cli.IntFlag{
Name: "http-timeout",
Usage: "Set the HTTP timeout value to a specific value in seconds.",
},
cli.IntFlag{
Name: "dns-timeout",
Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries.",
Value: 10,
},
cli.BoolFlag{
Name: "pem",
Usage: "Generate a .pem file by concatenating the .key and .crt files together.",
},
cli.IntFlag{
Name: "cert.timeout",
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
Value: 30,
},
}
}

View File

@@ -1,47 +0,0 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"time"
)
func launchHook(hook string, meta map[string]string) error {
if hook == "" {
return nil
}
ctxCmd, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
parts := strings.Fields(hook)
cmdCtx := exec.CommandContext(ctxCmd, parts[0], parts[1:]...)
cmdCtx.Env = append(os.Environ(), metaToEnv(meta)...)
output, err := cmdCtx.CombinedOutput()
if len(output) > 0 {
fmt.Println(string(output))
}
if errors.Is(ctxCmd.Err(), context.DeadlineExceeded) {
return errors.New("hook timed out")
}
return err
}
func metaToEnv(meta map[string]string) []string {
var envs []string
for k, v := range meta {
envs = append(envs, k+"="+v)
}
return envs
}

View File

@@ -1,129 +0,0 @@
package cmd
import (
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/urfave/cli"
)
const filePerm os.FileMode = 0o600
func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego.Client) {
keyType := getKeyType(ctx)
privateKey := accountsStorage.GetPrivateKey(keyType)
var account *Account
if accountsStorage.ExistsAccountFilePath() {
account = accountsStorage.LoadAccount(privateKey)
} else {
account = &Account{Email: accountsStorage.GetUserID(), key: privateKey}
}
client := newClient(ctx, account, keyType)
return account, client
}
func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
config := lego.NewConfig(acc)
config.CADirURL = ctx.GlobalString("server")
config.Certificate = lego.CertificateConfig{
KeyType: keyType,
Timeout: time.Duration(ctx.GlobalInt("cert.timeout")) * time.Second,
}
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
if ctx.GlobalIsSet("http-timeout") {
config.HTTPClient.Timeout = time.Duration(ctx.GlobalInt("http-timeout")) * time.Second
}
client, err := lego.NewClient(config)
if err != nil {
log.Panicf("Could not create client: %v", err)
}
if client.GetExternalAccountRequired() && !ctx.GlobalIsSet("eab") {
log.Panic("Server requires External Account Binding. Use --eab with --kid and --hmac.")
}
return client
}
// getKeyType the type from which private keys should be generated.
func getKeyType(ctx *cli.Context) certcrypto.KeyType {
keyType := ctx.GlobalString("key-type")
switch strings.ToUpper(keyType) {
case "RSA2048":
return certcrypto.RSA2048
case "RSA4096":
return certcrypto.RSA4096
case "RSA8192":
return certcrypto.RSA8192
case "EC256":
return certcrypto.EC256
case "EC384":
return certcrypto.EC384
}
log.Panicf("Unsupported KeyType: %s", keyType)
return ""
}
func getEmail(ctx *cli.Context) string {
email := ctx.GlobalString("email")
if email == "" {
log.Panic("You have to pass an account (email address) to the program using --email or -m")
}
return email
}
func createNonExistingFolder(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, 0o700)
} else if err != nil {
return err
}
return nil
}
func readCSRFile(filename string) (*x509.CertificateRequest, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
raw := bytes
// see if we can find a PEM-encoded CSR
var p *pem.Block
rest := bytes
for {
// decode a PEM block
p, rest = pem.Decode(rest)
// did we fail?
if p == nil {
break
}
// did we get a CSR?
if p.Type == "CERTIFICATE REQUEST" {
raw = p.Bytes
}
}
// no PEM-encoded CSR
// assume we were given a DER-encoded ASN.1 CSR
// (if this assumption is wrong, parsing these bytes will fail)
return x509.ParseCertificateRequest(raw)
}

View File

@@ -1,126 +0,0 @@
package cmd
import (
"net"
"strings"
"time"
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/providers/http/memcached"
"github.com/go-acme/lego/v4/providers/http/webroot"
"github.com/urfave/cli"
)
func setupChallenges(ctx *cli.Context, client *lego.Client) {
if !ctx.GlobalBool("http") && !ctx.GlobalBool("tls") && !ctx.GlobalIsSet("dns") {
log.Panic("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`.")
}
if ctx.GlobalBool("http") {
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx))
if err != nil {
log.Panic(err)
}
}
if ctx.GlobalBool("tls") {
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
if err != nil {
log.Panic(err)
}
}
if ctx.GlobalIsSet("dns") {
setupDNS(ctx, client)
}
}
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
switch {
case ctx.GlobalIsSet("http.webroot"):
ps, err := webroot.NewHTTPProvider(ctx.GlobalString("http.webroot"))
if err != nil {
log.Panic(err)
}
return ps
case ctx.GlobalIsSet("http.memcached-host"):
ps, err := memcached.NewMemcachedProvider(ctx.GlobalStringSlice("http.memcached-host"))
if err != nil {
log.Panic(err)
}
return ps
case ctx.GlobalIsSet("http.port"):
iface := ctx.GlobalString("http.port")
if !strings.Contains(iface, ":") {
log.Panicf("The --http switch only accepts interface:port or :port for its argument.")
}
host, port, err := net.SplitHostPort(iface)
if err != nil {
log.Panic(err)
}
srv := http01.NewProviderServer(host, port)
if header := ctx.GlobalString("http.proxy-header"); header != "" {
srv.SetProxyHeader(header)
}
return srv
case ctx.GlobalBool("http"):
srv := http01.NewProviderServer("", "")
if header := ctx.GlobalString("http.proxy-header"); header != "" {
srv.SetProxyHeader(header)
}
return srv
default:
log.Panic("Invalid HTTP challenge options.")
return nil
}
}
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
switch {
case ctx.GlobalIsSet("tls.port"):
iface := ctx.GlobalString("tls.port")
if !strings.Contains(iface, ":") {
log.Panicf("The --tls switch only accepts interface:port or :port for its argument.")
}
host, port, err := net.SplitHostPort(iface)
if err != nil {
log.Panic(err)
}
return tlsalpn01.NewProviderServer(host, port)
case ctx.GlobalBool("tls"):
return tlsalpn01.NewProviderServer("", "")
default:
log.Panic("Invalid HTTP challenge options.")
return nil
}
}
func setupDNS(ctx *cli.Context, client *lego.Client) {
provider, err := dns.NewDNSChallengeProviderByName(ctx.GlobalString("dns"))
if err != nil {
log.Panic(err)
}
servers := ctx.GlobalStringSlice("dns.resolvers")
err = client.Challenge.SetDNS01Provider(provider,
dns01.CondOption(len(servers) > 0,
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.GlobalStringSlice("dns.resolvers")))),
dns01.CondOption(ctx.GlobalBool("dns.disable-cp"),
dns01.DisableCompletePropagationRequirement()),
dns01.CondOption(ctx.GlobalIsSet("dns-timeout"),
dns01.AddDNSTimeout(time.Duration(ctx.GlobalInt("dns-timeout"))*time.Second)),
)
if err != nil {
log.Panic(err)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,189 +0,0 @@
// Let's Encrypt client to go!
// CLI application for generating Let's Encrypt certificates using the ACME package.
package legocmd
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/XrayR-project/XrayR/common/legocmd/cmd"
"github.com/urfave/cli"
)
var version = "dev"
var defaultPath string
type LegoCMD struct {
cmdClient *cli.App
}
func New() (*LegoCMD, error) {
app := cli.NewApp()
app.Name = "lego"
app.HelpName = "lego"
app.Usage = "Let's Encrypt client written in Go"
app.EnableBashCompletion = true
app.Version = version
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("lego version %s %s/%s\n", c.App.Version, runtime.GOOS, runtime.GOARCH)
}
// Set default path to configPath/cert
var path string = ""
configPath := os.Getenv("XRAY_LOCATION_CONFIG")
if configPath != "" {
path = configPath
} else if cwd, err := os.Getwd(); err==nil{
path = cwd
} else {
path = "."
}
defaultPath = filepath.Join(path, "cert")
app.Flags = cmd.CreateFlags(defaultPath)
app.Before = cmd.Before
app.Commands = cmd.CreateCommands()
lego := &LegoCMD{
cmdClient: app,
}
return lego, nil
}
// DNSCert cert a domain using DNS API
func (l *LegoCMD) DNSCert(domain, email, provider string, DNSEnv map[string]string) (CertPath string, KeyPath string, err error) {
defer func() (string, string, error) {
// Handle any error
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknow panic")
}
return "", "", err
}
return CertPath, KeyPath, nil
}()
// Set Env for DNS configuration
for key, value := range DNSEnv {
os.Setenv(key, value)
}
// First check if the certificate exists
CertPath, KeyPath, err = checkCertfile(domain)
if err == nil {
return CertPath, KeyPath, err
}
argstring := fmt.Sprintf("lego -a -d %s -m %s --dns %s run", domain, email, provider)
err = l.cmdClient.Run(strings.Split(argstring, " "))
if err != nil {
return "", "", err
}
CertPath, KeyPath, err = checkCertfile(domain)
if err != nil {
return "", "", err
}
return CertPath, KeyPath, nil
}
// HTTPCert cert a domain using http methods
func (l *LegoCMD) HTTPCert(domain, email string) (CertPath string, KeyPath string, err error) {
defer func() (string, string, error) {
// Handle any error
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknow panic")
}
return "", "", err
}
return CertPath, KeyPath, nil
}()
// First check if the certificate exists
CertPath, KeyPath, err = checkCertfile(domain)
if err == nil {
return CertPath, KeyPath, err
}
argstring := fmt.Sprintf("lego -a -d %s -m %s --http run", domain, email)
err = l.cmdClient.Run(strings.Split(argstring, " "))
if err != nil {
return "", "", err
}
CertPath, KeyPath, err = checkCertfile(domain)
if err != nil {
return "", "", err
}
return CertPath, KeyPath, nil
}
//RenewCert renew a domain cert
func (l *LegoCMD) RenewCert(domain, email, certMode, provider string, DNSEnv map[string]string) (CertPath string, KeyPath string, err error) {
var argstring string
defer func() (string, string, error) {
// Handle any error
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknow panic")
}
return "", "", err
}
return CertPath, KeyPath, nil
}()
if certMode == "http" {
argstring = fmt.Sprintf("lego -a -d %s -m %s --http renew --days 30", domain, email)
} else if certMode == "dns" {
// Set Env for DNS configuration
for key, value := range DNSEnv {
os.Setenv(key, value)
}
argstring = fmt.Sprintf("lego -a -d %s -m %s --dns %s renew --days 30", domain, email, provider)
} else {
return "", "", fmt.Errorf("Unsupport cert mode: %s", certMode)
}
err = l.cmdClient.Run(strings.Split(argstring, " "))
if err != nil {
return "", "", err
}
CertPath, KeyPath, err = checkCertfile(domain)
if err != nil {
return "", "", err
}
return CertPath, KeyPath, nil
}
func checkCertfile(domain string) (string, string, error) {
keyPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.key", domain))
certPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.crt", domain))
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
return "", "", fmt.Errorf("Cert key failed: %s", domain)
}
if _, err := os.Stat(certPath); os.IsNotExist(err) {
return "", "", fmt.Errorf("Cert cert failed: %s", domain)
}
absKeyPath, _ := filepath.Abs(keyPath)
absCertPath, _ := filepath.Abs(certPath)
return absCertPath, absKeyPath, nil
}

View File

@@ -1,82 +0,0 @@
package legocmd_test
import (
"testing"
"github.com/XrayR-project/XrayR/common/legocmd"
)
func TestLegoClient(t *testing.T) {
_, err := legocmd.New()
if err != nil {
t.Error(err)
}
}
func TestLegoDNSCert(t *testing.T) {
lego, err := legocmd.New()
if err != nil {
t.Error(err)
}
var (
domain string = "node1.test.com"
email string = "test@gmail.com"
provider string = "alidns"
DNSEnv map[string]string
)
DNSEnv = make(map[string]string)
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
certPath, keyPath, err := lego.DNSCert(domain, email, provider, DNSEnv)
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
}
func TestLegoHTTPCert(t *testing.T) {
lego, err := legocmd.New()
if err != nil {
t.Error(err)
}
var (
domain string = "node1.test.com"
email string = "test@gmail.com"
)
certPath, keyPath, err := lego.HTTPCert(domain, email)
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
}
func TestLegoRenewCert(t *testing.T) {
lego, err := legocmd.New()
if err != nil {
t.Error(err)
}
var (
domain string = "node1.test.com"
email string = "test@gmail.com"
provider string = "alidns"
DNSEnv map[string]string
)
DNSEnv = make(map[string]string)
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
certPath, keyPath, err := lego.RenewCert(domain, email, "dns", provider, DNSEnv)
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
certPath, keyPath, err = lego.RenewCert(domain, email, "http", provider, DNSEnv)
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
}

View File

@@ -1,60 +0,0 @@
package log
import (
"log"
"os"
)
// Logger is an optional custom logger.
var Logger StdLogger = log.New(os.Stdout, "", log.LstdFlags)
// StdLogger interface for Standard Logger.
type StdLogger interface {
Panic(args ...interface{})
Fatalln(args ...interface{})
Panicf(format string, args ...interface{})
Print(args ...interface{})
Println(args ...interface{})
Printf(format string, args ...interface{})
}
// Panic writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Panic(args ...interface{}) {
Logger.Panic(args...)
}
// Panicf writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Panicf(format string, args ...interface{}) {
Logger.Panicf(format, args...)
}
// Print writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Print(args ...interface{}) {
Logger.Print(args...)
}
// Println writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Println(args ...interface{}) {
Logger.Println(args...)
}
// Printf writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Printf(format string, args ...interface{}) {
Logger.Printf(format, args...)
}
// Warnf writes a log entry.
func Warnf(format string, args ...interface{}) {
Printf("[WARN] "+format, args...)
}
// Infof writes a log entry.
func Infof(format string, args ...interface{}) {
Printf("[INFO] "+format, args...)
}

View File

@@ -2,8 +2,6 @@ package limiter
import "github.com/xtls/xray-core/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
return errors.New(values...)
}

View File

@@ -1,13 +1,24 @@
// Package limiter is to control the links that go into the dispather
// Package limiter is to control the links that go into the dispatcher
package limiter
import (
"context"
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/eko/gocache/lib/v4/cache"
"github.com/eko/gocache/lib/v4/marshaler"
"github.com/eko/gocache/lib/v4/store"
goCacheStore "github.com/eko/gocache/store/go_cache/v4"
redisStore "github.com/eko/gocache/store/redis/v4"
"github.com/go-redis/redis/v8"
goCache "github.com/patrickmn/go-cache"
"golang.org/x/time/rate"
"github.com/XrayR-project/XrayR/api"
"github.com/juju/ratelimit"
)
type UserInfo struct {
@@ -20,8 +31,12 @@ type InboundInfo struct {
Tag string
NodeSpeedLimit uint64
UserInfo *sync.Map // Key: Email value: UserInfo
BucketHub *sync.Map // key: Email, value: *ratelimit.Bucket
UserOnlineIP *sync.Map // Key: Email Value: *sync.Map: Key: IP, Value: UID
BucketHub *sync.Map // key: Email, value: *rate.Limiter
UserOnlineIP *sync.Map // Key: Email, value: {Key: IP, value: UID}
GlobalLimit struct {
config *GlobalDeviceLimitConfig
globalOnlineIP *marshaler.Marshaler
}
}
type Limiter struct {
@@ -34,13 +49,37 @@ func New() *Limiter {
}
}
func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo) error {
func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo, globalLimit *GlobalDeviceLimitConfig) error {
inboundInfo := &InboundInfo{
Tag: tag,
NodeSpeedLimit: nodeSpeedLimit,
BucketHub: new(sync.Map),
UserOnlineIP: new(sync.Map),
}
if globalLimit != nil && globalLimit.Enable {
inboundInfo.GlobalLimit.config = globalLimit
// init local store
gs := goCacheStore.NewGoCache(goCache.New(time.Duration(globalLimit.Expiry)*time.Second, 1*time.Minute))
// init redis store
rs := redisStore.NewRedis(redis.NewClient(
&redis.Options{
Addr: globalLimit.RedisAddr,
Password: globalLimit.RedisPassword,
DB: globalLimit.RedisDB,
}),
store.WithExpiration(time.Duration(globalLimit.Expiry)*time.Second))
// init chained cache. First use local go-cache, if go-cache is nil, then use redis cache
cacheManager := cache.NewChain[any](
cache.New[any](gs), // go-cache is priority
cache.New[any](rs),
)
inboundInfo.GlobalLimit.globalOnlineIP = marshaler.New(cacheManager)
}
userMap := new(sync.Map)
for _, u := range *userList {
userMap.Store(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID), UserInfo{
@@ -55,7 +94,6 @@ func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList
}
func (l *Limiter) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserInfo) error {
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
// Update User info
@@ -65,7 +103,17 @@ func (l *Limiter) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserIn
SpeedLimit: u.SpeedLimit,
DeviceLimit: u.DeviceLimit,
})
inboundInfo.BucketHub.Delete(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID)) // Delete old limiter bucket
// Update old limiter bucket
limit := determineRate(inboundInfo.NodeSpeedLimit, u.SpeedLimit)
if limit > 0 {
if bucket, ok := inboundInfo.BucketHub.Load(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID)); ok {
limiter := bucket.(*rate.Limiter)
limiter.SetLimit(rate.Limit(limit))
limiter.SetBurst(int(limit))
}
} else {
inboundInfo.BucketHub.Delete(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID))
}
}
} else {
return fmt.Errorf("no such inbound in limiter: %s", tag)
@@ -79,7 +127,8 @@ func (l *Limiter) DeleteInboundLimiter(tag string) error {
}
func (l *Limiter) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
onlineUser := make([]api.OnlineUser, 0)
var onlineUser []api.OnlineUser
if value, ok := l.InboundInfo.Load(tag); ok {
inboundInfo := value.(*InboundInfo)
// Clear Speed Limiter bucket for users who are not online
@@ -91,43 +140,48 @@ func (l *Limiter) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
return true
})
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
email := key.(string)
ipMap := value.(*sync.Map)
ipMap.Range(func(key, value interface{}) bool {
ip := key.(string)
uid := value.(int)
ip := key.(string)
onlineUser = append(onlineUser, api.OnlineUser{UID: uid, IP: ip})
return true
})
email := key.(string)
inboundInfo.UserOnlineIP.Delete(email) // Reset online device
return true
})
} else {
return nil, fmt.Errorf("no such inbound in limiter: %s", tag)
}
return &onlineUser, nil
}
func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *ratelimit.Bucket, SpeedLimit bool, Reject bool) {
func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *rate.Limiter, SpeedLimit bool, Reject bool) {
if value, ok := l.InboundInfo.Load(tag); ok {
var (
userLimit uint64 = 0
deviceLimit, uid int
)
inboundInfo := value.(*InboundInfo)
nodeLimit := inboundInfo.NodeSpeedLimit
var userLimit uint64 = 0
var deviceLimit int = 0
var uid int = 0
if v, ok := inboundInfo.UserInfo.Load(email); ok {
u := v.(UserInfo)
uid = u.UID
userLimit = u.SpeedLimit
deviceLimit = u.DeviceLimit
}
// Report online device
// Local device limit
ipMap := new(sync.Map)
ipMap.Store(ip, uid)
// If any device is online
if v, ok := inboundInfo.UserOnlineIP.LoadOrStore(email, ipMap); ok {
ipMap := v.(*sync.Map)
// If this ip is a new device
// If this is a new ip
if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
counter := 0
ipMap.Range(func(key, value interface{}) bool {
@@ -140,11 +194,20 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r
}
}
}
limit := determineRate(nodeLimit, userLimit) // If need the Speed limit
// GlobalLimit
if inboundInfo.GlobalLimit.config != nil && inboundInfo.GlobalLimit.config.Enable {
if reject := globalLimit(inboundInfo, email, uid, ip, deviceLimit); reject {
return nil, false, true
}
}
// Speed limit
limit := determineRate(nodeLimit, userLimit) // Determine the speed limit rate
if limit > 0 {
limiter := ratelimit.NewBucketWithQuantum(time.Duration(int64(time.Second)), int64(limit), int64(limit)) // Byte/s
limiter := rate.NewLimiter(rate.Limit(limit), int(limit)) // Byte/s
if v, ok := inboundInfo.BucketHub.LoadOrStore(email, limiter); ok {
bucket := v.(*ratelimit.Bucket)
bucket := v.(*rate.Limiter)
return bucket, true, false
} else {
return limiter, true, false
@@ -158,6 +221,51 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r
}
}
// Global device limit
func globalLimit(inboundInfo *InboundInfo, email string, uid int, ip string, deviceLimit int) bool {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(inboundInfo.GlobalLimit.config.Timeout)*time.Second)
defer cancel()
// reformat email for unique key
uniqueKey := strings.Replace(email, inboundInfo.Tag, strconv.Itoa(deviceLimit), 1)
v, err := inboundInfo.GlobalLimit.globalOnlineIP.Get(ctx, uniqueKey, new(map[string]int))
if err != nil {
if _, ok := err.(*store.NotFound); ok {
// If the email is a new device
go pushIP(inboundInfo, uniqueKey, &map[string]int{ip: uid})
} else {
newError("cache service").Base(err).AtError().WriteToLog()
}
return false
}
ipMap := v.(*map[string]int)
// Reject device reach limit directly
if deviceLimit > 0 && len(*ipMap) > deviceLimit {
return true
}
// If the ip is not in cache
if _, ok := (*ipMap)[ip]; !ok {
(*ipMap)[ip] = uid
go pushIP(inboundInfo, email, ipMap)
}
return false
}
// push the ip to cache
func pushIP(inboundInfo *InboundInfo, email string, ipMap *map[string]int) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(inboundInfo.GlobalLimit.config.Timeout)*time.Second)
defer cancel()
if err := inboundInfo.GlobalLimit.globalOnlineIP.Set(ctx, email, ipMap); err != nil {
newError("cache service").Base(err).AtError().WriteToLog()
}
}
// determineRate returns the minimum non-zero rate
func determineRate(nodeLimit, userLimit uint64) (limit uint64) {
if nodeLimit == 0 || userLimit == 0 {

10
common/limiter/model.go Normal file
View File

@@ -0,0 +1,10 @@
package limiter
type GlobalDeviceLimitConfig struct {
Enable bool `mapstructure:"Enable"`
RedisAddr string `mapstructure:"RedisAddr"` // host:port
RedisPassword string `mapstructure:"RedisPassword"`
RedisDB int `mapstructure:"RedisDB"`
Timeout int `mapstructure:"Timeout"`
Expiry int `mapstructure:"Expiry"` // second
}

View File

@@ -1,20 +1,21 @@
package limiter
import (
"context"
"io"
"github.com/juju/ratelimit"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"golang.org/x/time/rate"
)
type Writer struct {
writer buf.Writer
limiter *ratelimit.Bucket
limiter *rate.Limiter
w io.Writer
}
func (l *Limiter) RateWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer {
func (l *Limiter) RateWriter(writer buf.Writer, limiter *rate.Limiter) buf.Writer {
return &Writer{
writer: writer,
limiter: limiter,
@@ -26,6 +27,7 @@ func (w *Writer) Close() error {
}
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.limiter.Wait(int64(mb.Len()))
ctx := context.Background()
w.limiter.WaitN(ctx, int(mb.Len()))
return w.writer.WriteMultiBuffer(mb)
}

View File

@@ -1,4 +1,4 @@
package cmd
package mylego
import (
"crypto"

View File

@@ -1,4 +1,4 @@
package cmd
package mylego
import (
"crypto"
@@ -6,18 +6,16 @@ import (
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/urfave/cli"
"golang.org/x/crypto/acme"
)
const (
@@ -30,56 +28,53 @@ const (
//
// rootPath:
//
// ./.lego/accounts/
// │ └── root accounts directory
// └── "path" option
// ./.lego/accounts/
// │ └── root accounts directory
// └── "path" option
//
// rootUserPath:
//
// ./.lego/accounts/localhost_14000/hubert@hubert.com/
// │ │ │ └── userID ("email" option)
// │ │ └── CA server ("server" option)
// │ └── root accounts directory
// └── "path" option
// ./.lego/accounts/localhost_14000/hubert@hubert.com/
// │ │ │ └── userID ("email" option)
// │ │ └── CA server ("server" option)
// │ └── root accounts directory
// └── "path" option
//
// keysPath:
//
// ./.lego/accounts/localhost_14000/hubert@hubert.com/keys/
// │ │ │ │ └── root keys directory
// │ │ │ └── userID ("email" option)
// │ │ └── CA server ("server" option)
// │ └── root accounts directory
// └── "path" option
// ./.lego/accounts/localhost_14000/hubert@hubert.com/keys/
// │ │ │ │ └── root keys directory
// │ │ │ └── userID ("email" option)
// │ │ └── CA server ("server" option)
// │ └── root accounts directory
// └── "path" option
//
// accountFilePath:
//
// ./.lego/accounts/localhost_14000/hubert@hubert.com/account.json
// │ │ │ │ └── account file
// │ │ │ └── userID ("email" option)
// │ │ └── CA server ("server" option)
// │ └── root accounts directory
// └── "path" option
//
// ./.lego/accounts/localhost_14000/hubert@hubert.com/account.json
// │ │ │ │ └── account file
// │ │ │ └── userID ("email" option)
// │ │ └── CA server ("server" option)
// │ └── root accounts directory
// └── "path" option
type AccountsStorage struct {
userID string
rootPath string
rootUserPath string
keysPath string
accountFilePath string
ctx *cli.Context
}
// NewAccountsStorage Creates a new AccountsStorage.
func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
// TODO: move to account struct? Currently MUST pass email.
email := getEmail(ctx)
func NewAccountsStorage(l *LegoCMD) *AccountsStorage {
email := l.C.Email
serverURL, err := url.Parse(ctx.GlobalString("server"))
serverURL, err := url.Parse(acme.LetsEncryptURL)
if err != nil {
log.Panic(err)
}
rootPath := filepath.Join(ctx.GlobalString("path"), baseAccountsRootFolderName)
rootPath := filepath.Join(l.path, baseAccountsRootFolderName)
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
accountsPath := filepath.Join(rootPath, serverPath)
rootUserPath := filepath.Join(accountsPath, email)
@@ -90,7 +85,6 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
rootUserPath: rootUserPath,
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
accountFilePath: filepath.Join(rootUserPath, accountFileName),
ctx: ctx,
}
}
@@ -122,11 +116,11 @@ func (s *AccountsStorage) Save(account *Account) error {
return err
}
return ioutil.WriteFile(s.accountFilePath, jsonBytes, filePerm)
return os.WriteFile(s.accountFilePath, jsonBytes, filePerm)
}
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
fileBytes, err := ioutil.ReadFile(s.accountFilePath)
fileBytes, err := os.ReadFile(s.accountFilePath)
if err != nil {
log.Panicf("Could not load file for account %s: %v", s.userID, err)
}
@@ -140,7 +134,7 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
account.key = privateKey
if account.Registration == nil || account.Registration.Body.Status == "" {
reg, err := tryRecoverRegistration(s.ctx, privateKey)
reg, err := tryRecoverRegistration(privateKey)
if err != nil {
log.Panicf("Could not load account for %s. Registration is nil: %#v", s.userID, err)
}
@@ -207,7 +201,7 @@ func generatePrivateKey(file string, keyType certcrypto.KeyType) (crypto.Private
}
func loadPrivateKey(file string) (crypto.PrivateKey, error) {
keyBytes, err := ioutil.ReadFile(file)
keyBytes, err := os.ReadFile(file)
if err != nil {
return nil, err
}
@@ -224,11 +218,11 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
return nil, errors.New("unknown private key type")
}
func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) {
func tryRecoverRegistration(privateKey crypto.PrivateKey) (*registration.Resource, error) {
// couldn't load account but got a key. Try to look the account up.
config := lego.NewConfig(&Account{key: privateKey})
config.CADirURL = ctx.GlobalString("server")
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
config.CADirURL = acme.LetsEncryptURL
config.UserAgent = "lego-cli/dev"
client, err := lego.NewClient(config)
if err != nil {

View File

@@ -1,56 +1,45 @@
package cmd
package mylego
import (
"bytes"
"crypto/x509"
"encoding/json"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/XrayR-project/XrayR/common/legocmd/log"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/urfave/cli"
"golang.org/x/net/idna"
)
const (
baseCertificatesFolderName = "certificates"
baseArchivesFolderName = "archives"
)
// CertificatesStorage a certificates storage.
// CertificatesStorage a certificates' storage.
//
// rootPath:
//
// ./.lego/certificates/
// │ └── root certificates directory
// └── "path" option
// ./.lego/certificates/
// │ └── root certificates directory
// └── "path" option
//
// archivePath:
//
// ./.lego/archives/
// │ └── archived certificates directory
// └── "path" option
//
// ./.lego/archives/
// │ └── archived certificates directory
// └── "path" option
type CertificatesStorage struct {
rootPath string
archivePath string
pem bool
filename string // Deprecated
rootPath string
pem bool
}
// NewCertificatesStorage create a new certificates storage.
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
func NewCertificatesStorage(path string) *CertificatesStorage {
return &CertificatesStorage{
rootPath: filepath.Join(ctx.GlobalString("path"), baseCertificatesFolderName),
archivePath: filepath.Join(ctx.GlobalString("path"), baseArchivesFolderName),
pem: ctx.GlobalBool("pem"),
filename: ctx.GlobalString("filename"),
rootPath: filepath.Join(path, baseCertificatesFolderName),
}
}
@@ -61,13 +50,6 @@ func (s *CertificatesStorage) CreateRootFolder() {
}
}
func (s *CertificatesStorage) CreateArchiveFolder() {
err := createNonExistingFolder(s.archivePath)
if err != nil {
log.Panicf("Could not check/create path: %v", err)
}
}
func (s *CertificatesStorage) GetRootPath() string {
return s.rootPath
}
@@ -144,7 +126,7 @@ func (s *CertificatesStorage) ExistsFile(domain, extension string) bool {
}
func (s *CertificatesStorage) ReadFile(domain, extension string) ([]byte, error) {
return ioutil.ReadFile(s.GetFileName(domain, extension))
return os.ReadFile(s.GetFileName(domain, extension))
}
func (s *CertificatesStorage) GetFileName(domain, extension string) string {
@@ -163,36 +145,11 @@ func (s *CertificatesStorage) ReadCertificate(domain, extension string) ([]*x509
}
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
var baseFileName string
if s.filename != "" {
baseFileName = s.filename
} else {
baseFileName = sanitizedDomain(domain)
}
var baseFileName = sanitizedDomain(domain)
filePath := filepath.Join(s.rootPath, baseFileName+extension)
return ioutil.WriteFile(filePath, data, filePerm)
}
func (s *CertificatesStorage) MoveToArchive(domain string) error {
matches, err := filepath.Glob(filepath.Join(s.rootPath, sanitizedDomain(domain)+".*"))
if err != nil {
return err
}
for _, oldFile := range matches {
date := strconv.FormatInt(time.Now().Unix(), 10)
filename := date + "." + filepath.Base(oldFile)
newFile := filepath.Join(s.archivePath, filename)
err = os.Rename(oldFile, newFile)
if err != nil {
return err
}
}
return nil
return os.WriteFile(filePath, data, filePerm)
}
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).

View File

@@ -0,0 +1,87 @@
package mylego_test
import (
"testing"
"github.com/XrayR-project/XrayR/common/mylego"
)
func TestLegoClient(t *testing.T) {
_, err := mylego.New(&mylego.CertConfig{})
if err != nil {
t.Error(err)
}
}
func TestLegoDNSCert(t *testing.T) {
lego, err := mylego.New(&mylego.CertConfig{
CertDomain: "node1.test.com",
Provider: "alidns",
Email: "test@gmail.com",
DNSEnv: map[string]string{
"ALICLOUD_ACCESS_KEY": "aaa",
"ALICLOUD_SECRET_KEY": "bbb",
},
},
)
if err != nil {
t.Error(err)
}
certPath, keyPath, err := lego.DNSCert()
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
}
func TestLegoHTTPCert(t *testing.T) {
lego, err := mylego.New(&mylego.CertConfig{
CertMode: "http",
CertDomain: "node1.test.com",
Email: "test@gmail.com",
})
if err != nil {
t.Error(err)
}
certPath, keyPath, err := lego.HTTPCert()
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
}
func TestLegoRenewCert(t *testing.T) {
lego, err := mylego.New(&mylego.CertConfig{
CertDomain: "node1.test.com",
Email: "test@gmail.com",
Provider: "alidns",
DNSEnv: map[string]string{
"ALICLOUD_ACCESS_KEY": "aaa",
"ALICLOUD_SECRET_KEY": "bbb",
},
})
if err != nil {
t.Error(err)
}
lego.C.CertMode = "http"
certPath, keyPath, ok, err := lego.RenewCert()
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
t.Log(ok)
lego.C.CertMode = "dns"
certPath, keyPath, ok, err = lego.RenewCert()
if err != nil {
t.Error(err)
}
t.Log(certPath)
t.Log(keyPath)
t.Log(ok)
}

17
common/mylego/model.go Normal file
View File

@@ -0,0 +1,17 @@
package mylego
type CertConfig struct {
CertMode string `mapstructure:"CertMode"` // none, file, http, dns
CertDomain string `mapstructure:"CertDomain"`
CertFile string `mapstructure:"CertFile"`
KeyFile string `mapstructure:"KeyFile"`
Provider string `mapstructure:"Provider"` // alidns, cloudflare, gandi, godaddy....
Email string `mapstructure:"Email"`
DNSEnv map[string]string `mapstructure:"DNSEnv"`
RejectUnknownSni bool `mapstructure:"RejectUnknownSni"`
}
type LegoCMD struct {
C *CertConfig
path string
}

163
common/mylego/mylego.go Normal file
View File

@@ -0,0 +1,163 @@
package mylego
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
var defaultPath string
func New(certConf *CertConfig) (*LegoCMD, error) {
// Set default path to configPath/cert
var p = ""
configPath := os.Getenv("XRAY_LOCATION_CONFIG")
if configPath != "" {
p = configPath
} else if cwd, err := os.Getwd(); err == nil {
p = cwd
} else {
p = "."
}
defaultPath = filepath.Join(p, "cert")
lego := &LegoCMD{
C: certConf,
path: defaultPath,
}
return lego, nil
}
func (l *LegoCMD) getPath() string {
return l.path
}
func (l *LegoCMD) getCertConfig() *CertConfig {
return l.C
}
// DNSCert cert a domain using DNS API
func (l *LegoCMD) DNSCert() (CertPath string, KeyPath string, err error) {
defer func() (string, string, error) {
// Handle any error
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknown panic")
}
return "", "", err
}
return CertPath, KeyPath, nil
}()
// Set Env for DNS configuration
for key, value := range l.C.DNSEnv {
os.Setenv(strings.ToUpper(key), value)
}
// First check if the certificate exists
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
if err == nil {
return CertPath, KeyPath, err
}
err = l.Run()
if err != nil {
return "", "", err
}
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
if err != nil {
return "", "", err
}
return CertPath, KeyPath, nil
}
// HTTPCert cert a domain using http methods
func (l *LegoCMD) HTTPCert() (CertPath string, KeyPath string, err error) {
defer func() (string, string, error) {
// Handle any error
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknown panic")
}
return "", "", err
}
return CertPath, KeyPath, nil
}()
// First check if the certificate exists
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
if err == nil {
return CertPath, KeyPath, err
}
err = l.Run()
if err != nil {
return "", "", err
}
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
if err != nil {
return "", "", err
}
return CertPath, KeyPath, nil
}
// RenewCert renew a domain cert
func (l *LegoCMD) RenewCert() (CertPath string, KeyPath string, ok bool, err error) {
defer func() (string, string, bool, error) {
// Handle any error
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknown panic")
}
return "", "", false, err
}
return CertPath, KeyPath, ok, nil
}()
ok, err = l.Renew()
if err != nil {
return
}
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
if err != nil {
return
}
return
}
func checkCertFile(domain string) (string, string, error) {
keyPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.key", domain))
certPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.crt", domain))
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
return "", "", fmt.Errorf("cert key failed: %s", domain)
}
if _, err := os.Stat(certPath); os.IsNotExist(err) {
return "", "", fmt.Errorf("cert cert failed: %s", domain)
}
absKeyPath, _ := filepath.Abs(keyPath)
absCertPath, _ := filepath.Abs(certPath)
return absCertPath, absKeyPath, nil
}

77
common/mylego/renew.go Normal file
View File

@@ -0,0 +1,77 @@
package mylego
import (
"crypto"
"crypto/x509"
"log"
"time"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/lego"
)
func (l *LegoCMD) Renew() (bool, error) {
account, client := setup(NewAccountsStorage(l))
setupChallenges(l, client)
if account.Registration == nil {
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email)
}
return renewForDomains(l.C.CertDomain, client, NewCertificatesStorage(l.path))
}
func renewForDomains(domain string, client *lego.Client, certsStorage *CertificatesStorage) (bool, error) {
// load the cert resource from files.
// We store the certificate, private key and metadata in different files
// as web servers would not be able to work with a combined file.
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
if err != nil {
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
}
cert := certificates[0]
if !needRenewal(cert, domain, 30) {
return false, nil
}
// This is just meant to be informal for the user.
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
log.Printf("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
certDomains := certcrypto.ExtractDomains(cert)
var privateKey crypto.PrivateKey
request := certificate.ObtainRequest{
Domains: certDomains,
Bundle: true,
PrivateKey: privateKey,
}
certRes, err := client.Certificate.Obtain(request)
if err != nil {
log.Panic(err)
}
certsStorage.SaveResource(certRes)
return true, nil
}
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
if x509Cert.IsCA {
log.Panicf("[%s] Certificate bundle starts with a CA certificate", domain)
}
if days >= 0 {
notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0)
if notAfter > days {
log.Printf("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.",
domain, notAfter, days)
return false
}
}
return true
}

View File

@@ -1,4 +1,4 @@
package cmd
package mylego
import (
"crypto/x509"
@@ -116,3 +116,19 @@ func Test_needRenewal(t *testing.T) {
})
}
}
func merge(prevDomains, nextDomains []string) []string {
for _, next := range nextDomains {
var found bool
for _, prev := range prevDomains {
if prev == next {
found = true
break
}
}
if !found {
prevDomains = append(prevDomains, next)
}
}
return prevDomains
}

68
common/mylego/run.go Normal file
View File

@@ -0,0 +1,68 @@
package mylego
import (
"fmt"
"log"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
)
const rootPathWarningMessage = `!!!! HEADS UP !!!!
Your account credentials have been saved in your Let's Encrypt
configuration directory at "%s".
You should make a secure backup of this folder now. This
configuration directory will also contain certificates and
private keys obtained from Let's Encrypt so making regular
backups of this folder is ideal.
`
func (l *LegoCMD) Run() error {
accountsStorage := NewAccountsStorage(l)
account, client := setup(accountsStorage)
setupChallenges(l, client)
if account.Registration == nil {
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Panicf("Could not complete registration\n\t%v", err)
}
account.Registration = reg
if err = accountsStorage.Save(account); err != nil {
log.Panic(err)
}
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
}
certsStorage := NewCertificatesStorage(l.path)
certsStorage.CreateRootFolder()
cert, err := obtainCertificate([]string{l.C.CertDomain}, client)
if err != nil {
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
// Due to us not returning partial certificate we can just exit here instead of at the end.
log.Panicf("Could not obtain certificates:\n\t%v", err)
}
certsStorage.SaveResource(cert)
return nil
}
func obtainCertificate(domains []string, client *lego.Client) (*certificate.Resource, error) {
if len(domains) > 0 {
// obtain a certificate, generating a new private key
request := certificate.ObtainRequest{
Domains: domains,
Bundle: true,
}
return client.Certificate.Obtain(request)
}
return nil, fmt.Errorf("not a valid domain")
}

95
common/mylego/setup.go Normal file
View File

@@ -0,0 +1,95 @@
package mylego
import (
"log"
"os"
"time"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/registration"
"golang.org/x/crypto/acme"
)
const filePerm os.FileMode = 0o600
func setup(accountsStorage *AccountsStorage) (*Account, *lego.Client) {
keyType := certcrypto.EC256
privateKey := accountsStorage.GetPrivateKey(keyType)
var account *Account
if accountsStorage.ExistsAccountFilePath() {
account = accountsStorage.LoadAccount(privateKey)
} else {
account = &Account{Email: accountsStorage.GetUserID(), key: privateKey}
}
client := newClient(account, keyType)
return account, client
}
func newClient(acc registration.User, keyType certcrypto.KeyType) *lego.Client {
config := lego.NewConfig(acc)
config.CADirURL = acme.LetsEncryptURL
config.Certificate = lego.CertificateConfig{
KeyType: keyType,
Timeout: 30 * time.Second,
}
config.UserAgent = "lego-cli/dev"
client, err := lego.NewClient(config)
if err != nil {
log.Panicf("Could not create client: %v", err)
}
return client
}
func createNonExistingFolder(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, 0o700)
} else if err != nil {
return err
}
return nil
}
func setupChallenges(l *LegoCMD, client *lego.Client) {
switch l.C.CertMode {
case "http":
err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", ""))
if err != nil {
log.Panic(err)
}
case "tls":
err := client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", ""))
if err != nil {
log.Panic(err)
}
case "dns":
setupDNS(l.C.Provider, client)
default:
log.Panic("No challenge selected. You must specify at least one challenge: `http`, `tls`, `dns`.")
}
}
func setupDNS(p string, client *lego.Client) {
provider, err := dns.NewDNSChallengeProviderByName(p)
if err != nil {
log.Panic(err)
}
err = client.Challenge.SetDNS01Provider(
provider,
dns01.CondOption(true, dns01.AddDNSTimeout(10*time.Second)),
)
if err != nil {
log.Panic(err)
}
}

View File

@@ -2,8 +2,6 @@ package rule
import "github.com/xtls/xray-core/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
return errors.New(values...)
}

View File

@@ -8,23 +8,24 @@ import (
"strings"
"sync"
"github.com/XrayR-project/XrayR/api"
mapset "github.com/deckarep/golang-set"
"github.com/XrayR-project/XrayR/api"
)
type RuleManager struct {
type Manager struct {
InboundRule *sync.Map // Key: Tag, Value: []api.DetectRule
InboundDetectResult *sync.Map // key: Tag, Value: mapset.NewSet []api.DetectResult
}
func New() *RuleManager {
return &RuleManager{
func New() *Manager {
return &Manager{
InboundRule: new(sync.Map),
InboundDetectResult: new(sync.Map),
}
}
func (r *RuleManager) UpdateRule(tag string, newRuleList []api.DetectRule) error {
func (r *Manager) UpdateRule(tag string, newRuleList []api.DetectRule) error {
if value, ok := r.InboundRule.LoadOrStore(tag, newRuleList); ok {
oldRuleList := value.([]api.DetectRule)
if !reflect.DeepEqual(oldRuleList, newRuleList) {
@@ -34,7 +35,7 @@ func (r *RuleManager) UpdateRule(tag string, newRuleList []api.DetectRule) error
return nil
}
func (r *RuleManager) GetDetectResult(tag string) (*[]api.DetectResult, error) {
func (r *Manager) GetDetectResult(tag string) (*[]api.DetectResult, error) {
detectResult := make([]api.DetectResult, 0)
if value, ok := r.InboundDetectResult.LoadAndDelete(tag); ok {
resultSet := value.(mapset.Set)
@@ -46,9 +47,9 @@ func (r *RuleManager) GetDetectResult(tag string) (*[]api.DetectResult, error) {
return &detectResult, nil
}
func (r *RuleManager) Detect(tag string, destination string, email string) (reject bool) {
func (r *Manager) Detect(tag string, destination string, email string) (reject bool) {
reject = false
var hitRuleID int = -1
var hitRuleID = -1
// If we have some rule for this inbound
if value, ok := r.InboundRule.Load(tag); ok {
ruleList := value.([]api.DetectRule)

View File

@@ -3,39 +3,51 @@ package serverstatus
import (
"fmt"
"time"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/mem"
)
// GetSystemInfo get the system info of a given periodic
func GetSystemInfo() (Cpu float64, Mem float64, Disk float64, Uptime int, err error) {
func GetSystemInfo() (Cpu float64, Mem float64, Disk float64, Uptime uint64, err error) {
errorString := ""
upTime := time.Now()
cpuPercent, err := cpu.Percent(0, false)
// Check if cpuPercent is empty
if len(cpuPercent) > 0 {
if len(cpuPercent) > 0 && err == nil {
Cpu = cpuPercent[0]
} else {
Cpu = 0
}
if err != nil {
return 0, 0, 0, 0, fmt.Errorf("get cpu usage failed: %s", err)
errorString += fmt.Sprintf("get cpu usage failed: %s ", err)
}
memUsage, err := mem.VirtualMemory()
if err != nil {
return 0, 0, 0, 0, fmt.Errorf("get mem usage failed: %s", err)
errorString += fmt.Sprintf("get mem usage failed: %s ", err)
} else {
Mem = memUsage.UsedPercent
}
diskUsage, err := disk.Usage("/")
if err != nil {
return 0, 0, 0, 0, fmt.Errorf("et disk usage failed: %s", err)
errorString += fmt.Sprintf("get disk usage failed: %s ", err)
} else {
Disk = diskUsage.UsedPercent
}
Uptime = int(time.Since(upTime).Seconds())
return Cpu, memUsage.UsedPercent, diskUsage.UsedPercent, Uptime, nil
uptime, err := host.Uptime()
if err != nil {
errorString += fmt.Sprintf("get uptime failed: %s ", err)
} else {
Uptime = uptime
}
if errorString != "" {
err = fmt.Errorf(errorString)
}
return Cpu, Mem, Disk, Uptime, err
}

184
go.mod
View File

@@ -1,36 +1,39 @@
module github.com/XrayR-project/XrayR
go 1.18
go 1.19
require (
github.com/bitly/go-simplejson v0.5.0
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/deckarep/golang-set v1.8.0
github.com/fsnotify/fsnotify v1.5.4
github.com/go-acme/lego/v4 v4.7.0
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/eko/gocache/lib/v4 v4.1.2
github.com/eko/gocache/store/go_cache/v4 v4.1.2
github.com/eko/gocache/store/redis/v4 v4.1.2
github.com/fsnotify/fsnotify v1.6.0
github.com/go-acme/lego/v4 v4.9.1
github.com/go-redis/redis/v8 v8.11.5
github.com/go-resty/resty/v2 v2.7.0
github.com/golang/protobuf v1.5.2 // indirect
github.com/imdario/mergo v0.3.13
github.com/juju/ratelimit v1.0.1
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/r3labs/diff/v2 v2.15.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.8.0
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/urfave/cli v1.22.9
github.com/xtls/xray-core v1.5.9
golang.org/x/net v0.0.0-20220708220712-1185a9018129
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.0
github.com/sagernet/sing v0.1.0
github.com/sagernet/sing-shadowsocks v0.1.0
github.com/shirou/gopsutil/v3 v3.22.11
github.com/spf13/viper v1.14.0
github.com/stretchr/testify v1.8.1
github.com/xtls/xray-core v1.6.5
golang.org/x/crypto v0.4.0
golang.org/x/net v0.4.0
golang.org/x/time v0.3.0
google.golang.org/protobuf v1.28.1
)
require (
cloud.google.com/go/compute v1.6.1 // indirect
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
cloud.google.com/go/compute v1.12.1 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
@@ -39,60 +42,71 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1621 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/aws/aws-sdk-go v1.44.26 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aws/aws-sdk-go v1.39.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/cloudflare/cloudflare-go v0.40.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.49.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.11.0 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dnsimple/dnsimple-go v0.71.1 // indirect
github.com/exoscale/egoscale v1.19.0 // indirect
github.com/exoscale/egoscale v0.90.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/gophercloud/gophercloud v0.25.0 // indirect
github.com/gophercloud/utils v0.0.0-20220307143606-8e7800759d16 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
github.com/gophercloud/gophercloud v1.0.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/jarcoal/httpmock v1.2.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.1 // indirect
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
github.com/labbsr0x/goh v1.0.1 // indirect
github.com/linode/linodego v1.6.0 // indirect
github.com/linode/linodego v1.9.1 // indirect
github.com/liquidweb/go-lwApi v0.0.5 // indirect
github.com/liquidweb/liquidweb-cli v0.6.10 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.3 // indirect
github.com/lucas-clemente/quic-go v0.28.0 // indirect
github.com/lucas-clemente/quic-go v0.31.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mimuret/golang-iij-dpf v0.7.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
@@ -100,76 +114,78 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.0.1 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/desec v0.6.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.2.0 // indirect
github.com/nrdcg/goinwx v0.8.1 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/nrdcg/porkbun v0.1.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/ginkgo/v2 v2.5.1 // indirect
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
github.com/ovh/go-ovh v1.1.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pires/go-proxyproto v0.6.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/otp v1.3.0 // indirect
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 // indirect
github.com/refraction-networking/utls v1.1.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/refraction-networking/utls v1.2.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sacloud/libsacloud v1.36.2 // indirect
github.com/sagernet/sing v0.0.0-20220714145306-09b55ce4b6d0 // indirect
github.com/sagernet/sing-shadowsocks v0.0.0-20220716012931-952ae62e05d7 // indirect
github.com/sacloud/api-client-go v0.2.1 // indirect
github.com/sacloud/go-http v0.1.2 // indirect
github.com/sacloud/iaas-api-go v1.3.2 // indirect
github.com/sacloud/packages-go v0.0.5 // indirect
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.0.5 // indirect
github.com/softlayer/softlayer-go v1.0.6 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.412 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.412 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/transip/gotransip/v6 v6.17.0 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vultr/govultr/v2 v2.17.0 // indirect
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
github.com/vultr/govultr/v2 v2.17.2 // indirect
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.opencensus.io v0.23.0 // indirect
go.starlark.net v0.0.0-20220714194419-4cadf0a12139 // indirect
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/tools v0.1.11 // indirect
google.golang.org/api v0.82.0 // indirect
google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9 // indirect
google.golang.org/grpc v1.48.0 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
golang.org/x/tools v0.3.0 // indirect
google.golang.org/api v0.102.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
google.golang.org/grpc v1.51.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.6.5 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)
replace github.com/linode/linodego => github.com/linode/linodego v0.31.1
replace github.com/exoscale/egoscale => github.com/exoscale/egoscale v0.67.0

603
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -6,15 +6,15 @@ DnsConfigPath: # /etc/XrayR/dns.json # Path to dns config, check https://xtls.gi
RouteConfigPath: # /etc/XrayR/route.json # Path to route config, check https://xtls.github.io/config/routing.html for help
InboundConfigPath: # /etc/XrayR/custom_inbound.json # Path to custom inbound config, check https://xtls.github.io/config/inbound.html for help
OutboundConfigPath: # /etc/XrayR/custom_outbound.json # Path to custom outbound config, check https://xtls.github.io/config/outbound.html for help
ConnetionConfig:
ConnectionConfig:
Handshake: 4 # Handshake time limit, Second
ConnIdle: 30 # Connection idle time limit, Second
UplinkOnly: 2 # Time limit when the connection downstream is closed, Second
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
BufferSize: 64 # The internal cache size of each connection, kB
BufferSize: 64 # The internal cache size of each connection, kB
Nodes:
-
PanelType: "SSpanel" # Panel type: SSpanel, V2board, PMpanel, , Proxypanel
PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
ApiConfig:
ApiHost: "http://127.0.0.1:667"
ApiKey: "123"
@@ -33,6 +33,18 @@ Nodes:
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
EnableProxyProtocol: false # Only works for WebSocket and TCP
AutoSpeedLimitConfig:
Limit: 0 # Warned speed. Set to 0 to disable AutoSpeedLimit (mbps)
WarnTimes: 0 # After (WarnTimes) consecutive warnings, the user will be limited. Set to 0 to punish overspeed user immediately.
LimitSpeed: 0 # The speedlimit of a limited user (unit: mbps)
LimitDuration: 0 # How many minutes will the limiting last (unit: minute)
GlobalDeviceLimitConfig:
Enable: false # Enable the global device limit of a user
RedisAddr: 127.0.0.1:6379 # The redis server address
RedisPassword: YOUR PASSWORD # Redis password
RedisDB: 0 # Redis DB
Timeout: 5 # Timeout for redis request
Expiry: 60 # Expiry time (second)
EnableFallback: false # Only support for Trojan and Vless
FallBackConfigs: # Support multiple fallbacks
-
@@ -42,7 +54,7 @@ Nodes:
Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable
CertConfig:
CertMode: dns # Option about how to get certificate: none, file, http, dns. Choose "none" will forcedly disable the tls config.
CertMode: dns # Option about how to get certificate: none, file, http, tls, dns. Choose "none" will forcedly disable the tls config.
CertDomain: "node1.test.com" # Domain to cert
CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
KeyFile: /etc/XrayR/cert/node1.test.com.key
@@ -52,7 +64,7 @@ Nodes:
ALICLOUD_ACCESS_KEY: aaa
ALICLOUD_SECRET_KEY: bbb
# -
# PanelType: "V2board" # Panel type: SSpanel, V2board
# PanelType: "NewV2board" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
# ApiConfig:
# ApiHost: "http://127.0.0.1:668"
# ApiKey: "123"
@@ -77,4 +89,3 @@ Nodes:
# DNSEnv: # DNS ENV option used by DNS provider
# ALICLOUD_ACCESS_KEY: aaa
# ALICLOUD_SECRET_KEY: bbb

View File

@@ -1,19 +1,19 @@
[
{
"listen": "0.0.0.0",
"port": 1234,
"protocol": "socks",
"settings": {
"auth": "noauth",
"accounts": [
{
"user": "my-username",
"pass": "my-password"
}
],
"udp": false,
"ip": "127.0.0.1",
"userLevel": 0
{
"listen": "0.0.0.0",
"port": 1234,
"protocol": "socks",
"settings": {
"auth": "noauth",
"accounts": [
{
"user": "my-username",
"pass": "my-password"
}
],
"udp": false,
"ip": "127.0.0.1",
"userLevel": 0
}
}
]

View File

@@ -1,28 +1,30 @@
[
{
"tag": "IPv4_out",
"protocol": "freedom",
"settings": {}
},
{
"tag": "IPv6_out",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv6"
}
},
{
"tag": "socks5-warp",
"protocol": "socks",
"settings": {
"servers": [{
"address": "127.0.0.1",
"port": 1080
}]
}
},
{
"protocol": "blackhole",
"tag": "block"
{
"tag": "IPv4_out",
"protocol": "freedom",
"settings": {}
},
{
"tag": "IPv6_out",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv6"
}
},
{
"tag": "socks5-warp",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": 1080
}
]
}
},
{
"protocol": "blackhole",
"tag": "block"
}
]

View File

@@ -3,11 +3,12 @@ package all
import (
// The following are necessary as they register handlers in their init functions.
_ "github.com/xtls/xray-core/app/proxyman/inbound"
_ "github.com/xtls/xray-core/app/proxyman/outbound"
// Required features. Can't remove unless there is replacements.
// _ "github.com/xtls/xray-core/app/dispatcher"
_ "github.com/XrayR-project/XrayR/app/mydispatcher"
_ "github.com/xtls/xray-core/app/proxyman/inbound"
_ "github.com/xtls/xray-core/app/proxyman/outbound"
// Default commander and all its services. This is an optional feature.
_ "github.com/xtls/xray-core/app/commander"

View File

@@ -1,8 +1,8 @@
{
"servers": [
"1.1.1.1",
"8.8.8.8",
"localhost"
],
"tag": "dns_inbound"
"servers": [
"1.1.1.1",
"8.8.8.8",
"localhost"
],
"tag": "dns_inbound"
}

View File

@@ -12,9 +12,10 @@ import (
"syscall"
"time"
"github.com/XrayR-project/XrayR/panel"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"github.com/XrayR-project/XrayR/panel"
)
var (
@@ -23,7 +24,7 @@ var (
)
var (
version = "0.8.1"
version = "0.8.7"
codename = "XrayR"
intro = "A Xray backend that supports many panels"
)
@@ -56,7 +57,7 @@ func getConfig() *viper.Viper {
}
if err := config.ReadInConfig(); err != nil {
log.Panicf("Fatal error config file: %s \n", err)
log.Panicf("Config file error: %s \n", err)
}
config.WatchConfig() // Watch the config
@@ -73,7 +74,9 @@ func main() {
config := getConfig()
panelConfig := &panel.Config{}
config.Unmarshal(panelConfig)
if err := config.Unmarshal(panelConfig); err != nil {
log.Panicf("Parse config file %v failed: %s \n", configFile, err)
}
p := panel.New(panelConfig)
lastTime := time.Now()
config.OnConfigChange(func(e fsnotify.Event) {
@@ -84,7 +87,9 @@ func main() {
p.Close()
// Delete old instance and trigger GC
runtime.GC()
config.Unmarshal(panelConfig)
if err := config.Unmarshal(panelConfig); err != nil {
log.Panicf("Parse config file %v failed: %s \n", configFile, err)
}
p.Start()
lastTime = time.Now()
}
@@ -92,7 +97,7 @@ func main() {
p.Start()
defer p.Close()
//Explicitly triggering GC to remove garbage from config loading.
// Explicitly triggering GC to remove garbage from config loading.
runtime.GC()
// Running backend
{

View File

@@ -1,36 +1,36 @@
{
"domainStrategy": "IPOnDemand",
"rules": [
{
"type": "field",
"outboundTag": "block",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "block",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "socks5-warp",
"domain": []
},
{
"type": "field",
"outboundTag": "IPv6_out",
"domain": [
"geosite:netflix"
]
},
{
"type": "field",
"outboundTag": "IPv4_out",
"network": "udp,tcp"
}
]
"domainStrategy": "IPOnDemand",
"rules": [
{
"type": "field",
"outboundTag": "block",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "block",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "socks5-warp",
"domain": []
},
{
"type": "field",
"outboundTag": "IPv6_out",
"domain": [
"geosite:netflix"
]
},
{
"type": "field",
"outboundTag": "IPv4_out",
"network": "udp,tcp"
}
]
}

View File

@@ -6,13 +6,13 @@ import (
)
type Config struct {
LogConfig *LogConfig `mapstructure:"Log"`
DnsConfigPath string `mapstructure:"DnsConfigPath"`
InboundConfigPath string `mapstructure:"InboundConfigPath"`
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
RouteConfigPath string `mapstructure:"RouteConfigPath"`
ConnetionConfig *ConnetionConfig `mapstructure:"ConnetionConfig"`
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
LogConfig *LogConfig `mapstructure:"Log"`
DnsConfigPath string `mapstructure:"DnsConfigPath"`
InboundConfigPath string `mapstructure:"InboundConfigPath"`
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
RouteConfigPath string `mapstructure:"RouteConfigPath"`
ConnectionConfig *ConnectionConfig `mapstructure:"ConnectionConfig"`
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
}
type NodesConfig struct {
@@ -27,7 +27,7 @@ type LogConfig struct {
ErrorPath string `mapstructure:"ErrorPath"`
}
type ConnetionConfig struct {
type ConnectionConfig struct {
Handshake uint32 `mapstructure:"handshake"`
ConnIdle uint32 `mapstructure:"connIdle"`
UplinkOnly uint32 `mapstructure:"uplinkOnly"`

View File

@@ -10,8 +10,8 @@ func getDefaultLogConfig() *LogConfig {
}
}
func getDefaultConnetionConfig() *ConnetionConfig {
return &ConnetionConfig{
func getDefaultConnectionConfig() *ConnectionConfig {
return &ConnectionConfig{
Handshake: 4,
ConnIdle: 30,
UplinkOnly: 2,

View File

@@ -2,19 +2,13 @@ package panel
import (
"encoding/json"
"github.com/XrayR-project/XrayR/app/mydispatcher"
io "io/ioutil"
"log"
"os"
"sync"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/pmpanel"
"github.com/XrayR-project/XrayR/api/proxypanel"
"github.com/XrayR-project/XrayR/api/sspanel"
"github.com/XrayR-project/XrayR/api/v2board"
_ "github.com/XrayR-project/XrayR/main/distro/all"
"github.com/XrayR-project/XrayR/service"
"github.com/XrayR-project/XrayR/service/controller"
"github.com/XrayR-project/XrayR/api/newV2board"
"github.com/XrayR-project/XrayR/app/mydispatcher"
"github.com/imdario/mergo"
"github.com/r3labs/diff/v2"
"github.com/xtls/xray-core/app/proxyman"
@@ -22,6 +16,16 @@ import (
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/pmpanel"
"github.com/XrayR-project/XrayR/api/proxypanel"
"github.com/XrayR-project/XrayR/api/sspanel"
"github.com/XrayR-project/XrayR/api/v2board"
"github.com/XrayR-project/XrayR/api/v2raysocks"
_ "github.com/XrayR-project/XrayR/main/distro/all"
"github.com/XrayR-project/XrayR/service"
"github.com/XrayR-project/XrayR/service/controller"
)
// Panel Structure
@@ -54,7 +58,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
// DNS config
coreDnsConfig := &conf.DNSConfig{}
if panelConfig.DnsConfigPath != "" {
if data, err := io.ReadFile(panelConfig.DnsConfigPath); err != nil {
if data, err := os.ReadFile(panelConfig.DnsConfigPath); err != nil {
log.Panicf("Failed to read DNS config file at: %s", panelConfig.DnsConfigPath)
} else {
if err = json.Unmarshal(data, coreDnsConfig); err != nil {
@@ -69,7 +73,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
// Routing config
coreRouterConfig := &conf.RouterConfig{}
if panelConfig.RouteConfigPath != "" {
if data, err := io.ReadFile(panelConfig.RouteConfigPath); err != nil {
if data, err := os.ReadFile(panelConfig.RouteConfigPath); err != nil {
log.Panicf("Failed to read Routing config file at: %s", panelConfig.RouteConfigPath)
} else {
if err = json.Unmarshal(data, coreRouterConfig); err != nil {
@@ -82,9 +86,9 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
log.Panicf("Failed to understand Routing config Please check: https://xtls.github.io/config/routing.html for help: %s", err)
}
// Custom Inbound config
coreCustomInboundConfig := []conf.InboundDetourConfig{}
var coreCustomInboundConfig []conf.InboundDetourConfig
if panelConfig.InboundConfigPath != "" {
if data, err := io.ReadFile(panelConfig.InboundConfigPath); err != nil {
if data, err := os.ReadFile(panelConfig.InboundConfigPath); err != nil {
log.Panicf("Failed to read Custom Inbound config file at: %s", panelConfig.OutboundConfigPath)
} else {
if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil {
@@ -92,7 +96,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
}
}
}
inBoundConfig := []*core.InboundHandlerConfig{}
var inBoundConfig []*core.InboundHandlerConfig
for _, config := range coreCustomInboundConfig {
oc, err := config.Build()
if err != nil {
@@ -101,9 +105,9 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
inBoundConfig = append(inBoundConfig, oc)
}
// Custom Outbound config
coreCustomOutboundConfig := []conf.OutboundDetourConfig{}
var coreCustomOutboundConfig []conf.OutboundDetourConfig
if panelConfig.OutboundConfigPath != "" {
if data, err := io.ReadFile(panelConfig.OutboundConfigPath); err != nil {
if data, err := os.ReadFile(panelConfig.OutboundConfigPath); err != nil {
log.Panicf("Failed to read Custom Outbound config file at: %s", panelConfig.OutboundConfigPath)
} else {
if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil {
@@ -111,7 +115,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
}
}
}
outBoundConfig := []*core.OutboundHandlerConfig{}
var outBoundConfig []*core.OutboundHandlerConfig
for _, config := range coreCustomOutboundConfig {
oc, err := config.Build()
if err != nil {
@@ -120,7 +124,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
outBoundConfig = append(outBoundConfig, oc)
}
// Policy config
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnetionConfig)
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnectionConfig)
corePolicyConfig := &conf.PolicyConfig{}
corePolicyConfig.Levels = map[uint32]*conf.Policy{0: levelPolicyConfig}
policyConfig, _ := corePolicyConfig.Build()
@@ -148,7 +152,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
return server
}
// Start Start the panel
// Start the panel
func (p *Panel) Start() {
p.access.Lock()
defer p.access.Unlock()
@@ -159,18 +163,24 @@ func (p *Panel) Start() {
log.Panicf("Failed to start instance: %s", err)
}
p.Server = server
// Load Nodes config
for _, nodeConfig := range p.panelConfig.NodesConfig {
var apiClient api.API
switch nodeConfig.PanelType {
case "SSpanel":
apiClient = sspanel.New(nodeConfig.ApiConfig)
// todo Deprecated after 2023.6.1
case "V2board":
apiClient = v2board.New(nodeConfig.ApiConfig)
case "NewV2board":
apiClient = newV2board.New(nodeConfig.ApiConfig)
case "PMpanel":
apiClient = pmpanel.New(nodeConfig.ApiConfig)
case "Proxypanel":
apiClient = proxypanel.New(nodeConfig.ApiConfig)
case "V2RaySocks":
apiClient = v2raysocks.New(nodeConfig.ApiConfig)
default:
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
}
@@ -198,7 +208,7 @@ func (p *Panel) Start() {
return
}
// Close Close the panel
// Close the panel
func (p *Panel) Close() {
p.access.Lock()
defer p.access.Unlock()
@@ -214,21 +224,21 @@ func (p *Panel) Close() {
return
}
func parseConnectionConfig(c *ConnetionConfig) (policy *conf.Policy) {
connetionConfig := getDefaultConnetionConfig()
func parseConnectionConfig(c *ConnectionConfig) (policy *conf.Policy) {
connectionConfig := getDefaultConnectionConfig()
if c != nil {
if _, err := diff.Merge(connetionConfig, c, connetionConfig); err != nil {
log.Panicf("Read ConnetionConfig failed: %s", err)
if _, err := diff.Merge(connectionConfig, c, connectionConfig); err != nil {
log.Panicf("Read ConnectionConfig failed: %s", err)
}
}
policy = &conf.Policy{
StatsUserUplink: true,
StatsUserDownlink: true,
Handshake: &connetionConfig.Handshake,
ConnectionIdle: &connetionConfig.ConnIdle,
UplinkOnly: &connetionConfig.UplinkOnly,
DownlinkOnly: &connetionConfig.DownlinkOnly,
BufferSize: &connetionConfig.BufferSize,
Handshake: &connectionConfig.Handshake,
ConnectionIdle: &connectionConfig.ConnIdle,
UplinkOnly: &connectionConfig.UplinkOnly,
DownlinkOnly: &connectionConfig.DownlinkOnly,
BufferSize: &connectionConfig.BufferSize,
}
return

View File

@@ -1,30 +1,33 @@
package controller
import (
"github.com/XrayR-project/XrayR/common/limiter"
"github.com/XrayR-project/XrayR/common/mylego"
)
type Config struct {
ListenIP string `mapstructure:"ListenIP"`
SendIP string `mapstructure:"SendIP"`
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
CertConfig *CertConfig `mapstructure:"CertConfig"`
EnableDNS bool `mapstructure:"EnableDNS"`
DNSType string `mapstructure:"DNSType"`
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
DisableGetRule bool `mapstructure:"DisableGetRule"`
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
EnableFallback bool `mapstructure:"EnableFallback"`
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
DisableSniffing bool `mapstructure:"DisableSniffing"`
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
ListenIP string `mapstructure:"ListenIP"`
SendIP string `mapstructure:"SendIP"`
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
EnableDNS bool `mapstructure:"EnableDNS"`
DNSType string `mapstructure:"DNSType"`
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
DisableGetRule bool `mapstructure:"DisableGetRule"`
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
EnableFallback bool `mapstructure:"EnableFallback"`
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
DisableSniffing bool `mapstructure:"DisableSniffing"`
AutoSpeedLimitConfig *AutoSpeedLimitConfig `mapstructure:"AutoSpeedLimitConfig"`
GlobalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig `mapstructure:"GlobalDeviceLimitConfig"`
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
}
type CertConfig struct {
CertMode string `mapstructure:"CertMode"` // none, file, http, dns
RejectUnknownSni bool `mapstructure:"RejectUnknownSni"`
CertDomain string `mapstructure:"CertDomain"`
CertFile string `mapstructure:"CertFile"`
KeyFile string `mapstructure:"KeyFile"`
Provider string `mapstructure:"Provider"` // alidns, cloudflare, gandi, godaddy....
Email string `mapstructure:"Email"`
DNSEnv map[string]string `mapstructure:"DNSEnv"`
type AutoSpeedLimitConfig struct {
Limit int `mapstructure:"Limit"` // mbps
WarnTimes int `mapstructure:"WarnTimes"`
LimitSpeed int `mapstructure:"LimitSpeed"` // mbps
LimitDuration int `mapstructure:"LimitDuration"` // minute
}
type FallBackConfig struct {

View File

@@ -4,21 +4,24 @@ import (
"context"
"fmt"
"github.com/XrayR-project/XrayR/api"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/inbound"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/common/limiter"
)
func (c *Controller) removeInbound(tag string) error {
err := c.ihm.RemoveHandler(context.Background(), tag)
err := c.ibm.RemoveHandler(context.Background(), tag)
return err
}
func (c *Controller) removeOutbound(tag string) error {
err := c.ohm.RemoveHandler(context.Background(), tag)
err := c.obm.RemoveHandler(context.Background(), tag)
return err
}
@@ -31,7 +34,7 @@ func (c *Controller) addInbound(config *core.InboundHandlerConfig) error {
if !ok {
return fmt.Errorf("not an InboundHandler: %s", err)
}
if err := c.ihm.AddHandler(context.Background(), handler); err != nil {
if err := c.ibm.AddHandler(context.Background(), handler); err != nil {
return err
}
return nil
@@ -46,16 +49,16 @@ func (c *Controller) addOutbound(config *core.OutboundHandlerConfig) error {
if !ok {
return fmt.Errorf("not an InboundHandler: %s", err)
}
if err := c.ohm.AddHandler(context.Background(), handler); err != nil {
if err := c.obm.AddHandler(context.Background(), handler); err != nil {
return err
}
return nil
}
func (c *Controller) addUsers(users []*protocol.User, tag string) error {
handler, err := c.ihm.GetHandler(context.Background(), tag)
handler, err := c.ibm.GetHandler(context.Background(), tag)
if err != nil {
return fmt.Errorf("No such inbound tag: %s", err)
return fmt.Errorf("no such inbound tag: %s", err)
}
inboundInstance, ok := handler.(proxy.GetInbound)
if !ok {
@@ -80,9 +83,9 @@ func (c *Controller) addUsers(users []*protocol.User, tag string) error {
}
func (c *Controller) removeUsers(users []string, tag string) error {
handler, err := c.ihm.GetHandler(context.Background(), tag)
handler, err := c.ibm.GetHandler(context.Background(), tag)
if err != nil {
return fmt.Errorf("No such inbound tag: %s", err)
return fmt.Errorf("no such inbound tag: %s", err)
}
inboundInstance, ok := handler.(proxy.GetInbound)
if !ok {
@@ -102,25 +105,35 @@ func (c *Controller) removeUsers(users []string, tag string) error {
return nil
}
func (c *Controller) getTraffic(email string) (up int64, down int64) {
func (c *Controller) getTraffic(email string) (up int64, down int64, upCounter stats.Counter, downCounter stats.Counter) {
upName := "user>>>" + email + ">>>traffic>>>uplink"
downName := "user>>>" + email + ">>>traffic>>>downlink"
upCounter := c.stm.GetCounter(upName)
downCounter := c.stm.GetCounter(downName)
if upCounter != nil {
upCounter = c.stm.GetCounter(upName)
downCounter = c.stm.GetCounter(downName)
if upCounter != nil && upCounter.Value() != 0 {
up = upCounter.Value()
upCounter.Set(0)
} else {
upCounter = nil
}
if downCounter != nil {
if downCounter != nil && downCounter.Value() != 0 {
down = downCounter.Value()
downCounter.Set(0)
} else {
downCounter = nil
}
return up, down
return up, down, upCounter, downCounter
}
func (c *Controller) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo) error {
err := c.dispatcher.Limiter.AddInboundLimiter(tag, nodeSpeedLimit, userList)
func (c *Controller) resetTraffic(upCounterList *[]stats.Counter, downCounterList *[]stats.Counter) {
for _, upCounter := range *upCounterList {
upCounter.Set(0)
}
for _, downCounter := range *downCounterList {
downCounter.Set(0)
}
}
func (c *Controller) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo, globalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig) error {
err := c.dispatcher.Limiter.AddInboundLimiter(tag, nodeSpeedLimit, userList, globalDeviceLimitConfig)
return err
}

View File

@@ -3,14 +3,9 @@ package controller
import (
"fmt"
"log"
"math"
"reflect"
"time"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/app/mydispatcher"
"github.com/XrayR-project/XrayR/common/legocmd"
"github.com/XrayR-project/XrayR/common/serverstatus"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core"
@@ -18,23 +13,41 @@ import (
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/app/mydispatcher"
"github.com/XrayR-project/XrayR/common/mylego"
"github.com/XrayR-project/XrayR/common/serverstatus"
)
type LimitInfo struct {
end int64
currentSpeedLimit int
originSpeedLimit uint64
}
type Controller struct {
server *core.Instance
config *Config
clientInfo api.ClientInfo
apiClient api.API
nodeInfo *api.NodeInfo
Tag string
userList *[]api.UserInfo
nodeInfoMonitorPeriodic *task.Periodic
userReportPeriodic *task.Periodic
panelType string
ihm inbound.Manager
ohm outbound.Manager
stm stats.Manager
dispatcher *mydispatcher.DefaultDispatcher
server *core.Instance
config *Config
clientInfo api.ClientInfo
apiClient api.API
nodeInfo *api.NodeInfo
Tag string
userList *[]api.UserInfo
tasks []periodicTask
limitedUsers map[api.UserInfo]LimitInfo
warnedUsers map[api.UserInfo]int
panelType string
ibm inbound.Manager
obm outbound.Manager
stm stats.Manager
dispatcher *mydispatcher.DefaultDispatcher
startAt time.Time
}
type periodicTask struct {
tag string
*task.Periodic
}
// New return a Controller service with default parameters.
@@ -44,11 +57,13 @@ func New(server *core.Instance, api api.API, config *Config, panelType string) *
config: config,
apiClient: api,
panelType: panelType,
ihm: server.GetFeature(inbound.ManagerType()).(inbound.Manager),
ohm: server.GetFeature(outbound.ManagerType()).(outbound.Manager),
ibm: server.GetFeature(inbound.ManagerType()).(inbound.Manager),
obm: server.GetFeature(outbound.ManagerType()).(outbound.Manager),
stm: server.GetFeature(stats.ManagerType()).(stats.Manager),
dispatcher: server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher),
startAt: time.Now(),
}
return controller
}
@@ -74,17 +89,19 @@ func (c *Controller) Start() error {
return err
}
// sync controller userList
c.userList = userInfo
err = c.addNewUser(userInfo, newNodeInfo)
if err != nil {
return err
}
//sync controller userList
c.userList = userInfo
// Add Limiter
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, userInfo); err != nil {
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, userInfo, c.config.GlobalDeviceLimitConfig); err != nil {
log.Print(err)
}
// Add Rule Manager
if !c.config.DisableGetRule {
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
@@ -95,49 +112,70 @@ func (c *Controller) Start() error {
}
}
}
c.nodeInfoMonitorPeriodic = &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
Execute: c.nodeInfoMonitor,
}
c.userReportPeriodic = &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
Execute: c.userInfoMonitor,
}
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeID)
// delay to start nodeInfoMonitor
go func() {
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second)
_ = c.nodeInfoMonitorPeriodic.Start()
}()
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeID)
// delay to start userReport
go func() {
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second)
_ = c.userReportPeriodic.Start()
}()
// Init AutoSpeedLimitConfig
if c.config.AutoSpeedLimitConfig == nil {
c.config.AutoSpeedLimitConfig = &AutoSpeedLimitConfig{0, 0, 0, 0}
}
if c.config.AutoSpeedLimitConfig.Limit > 0 {
c.limitedUsers = make(map[api.UserInfo]LimitInfo)
c.warnedUsers = make(map[api.UserInfo]int)
}
// Add periodic tasks
c.tasks = append(c.tasks,
periodicTask{
tag: "node monitor",
Periodic: &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
Execute: c.nodeInfoMonitor,
}},
periodicTask{
tag: "user monitor",
Periodic: &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
Execute: c.userInfoMonitor,
}},
)
// Check cert service in need
if c.nodeInfo.EnableTLS {
c.tasks = append(c.tasks, periodicTask{
tag: "cert monitor",
Periodic: &task.Periodic{
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second * 60,
Execute: c.certMonitor,
}})
}
// Start periodic tasks
for i := range c.tasks {
log.Printf("%s Start %s periodic task", c.logPrefix(), c.tasks[i].tag)
go c.tasks[i].Start()
}
return nil
}
// Close implement the Close() function of the service interface
func (c *Controller) Close() error {
if c.nodeInfoMonitorPeriodic != nil {
err := c.nodeInfoMonitorPeriodic.Close()
if err != nil {
log.Panicf("node info periodic close failed: %s", err)
for i := range c.tasks {
if c.tasks[i].Periodic != nil {
if err := c.tasks[i].Periodic.Close(); err != nil {
log.Panicf("%s %s periodic task close failed: %s", c.logPrefix(), c.tasks[i].tag, err)
}
}
}
if c.nodeInfoMonitorPeriodic != nil {
err := c.userReportPeriodic.Close()
if err != nil {
log.Panicf("user report periodic close failed: %s", err)
}
}
return nil
}
func (c *Controller) nodeInfoMonitor() (err error) {
// delay to start
if time.Since(c.startAt) < time.Duration(c.config.UpdatePeriodic)*time.Second {
return nil
}
// First fetch Node Info
newNodeInfo, err := c.apiClient.GetNodeInfo()
if err != nil {
@@ -146,18 +184,24 @@ func (c *Controller) nodeInfoMonitor() (err error) {
}
// Update User
var usersChanged = true
newUserInfo, err := c.apiClient.GetUserList()
if err != nil {
log.Print(err)
return nil
if err.Error() == "users no change" {
usersChanged = false
newUserInfo = c.userList
} else {
log.Print(err)
return nil
}
}
var nodeInfoChanged = false
// If nodeInfo changed
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
// Remove old tag
oldtag := c.Tag
err := c.removeOldTag(oldtag)
oldTag := c.Tag
err := c.removeOldTag(oldTag)
if err != nil {
log.Print(err)
return nil
@@ -179,7 +223,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
}
nodeInfoChanged = true
// Remove Old limiter
if err = c.DeleteInboundLimiter(oldtag); err != nil {
if err = c.DeleteInboundLimiter(oldTag); err != nil {
log.Print(err)
return nil
}
@@ -196,64 +240,55 @@ func (c *Controller) nodeInfoMonitor() (err error) {
}
}
// Check Cert
if c.nodeInfo.EnableTLS && (c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") {
lego, err := legocmd.New()
if err != nil {
log.Print(err)
}
// Xray-core supports the OcspStapling certification hot renew
_, _, err = lego.RenewCert(c.config.CertConfig.CertDomain, c.config.CertConfig.Email, c.config.CertConfig.CertMode, c.config.CertConfig.Provider, c.config.CertConfig.DNSEnv)
if err != nil {
log.Print(err)
}
}
if nodeInfoChanged {
err = c.addNewUser(newUserInfo, newNodeInfo)
if err != nil {
log.Print(err)
return nil
}
// Add Limiter
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, newUserInfo); err != nil {
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, newUserInfo, c.config.GlobalDeviceLimitConfig); err != nil {
log.Print(err)
return nil
}
} else {
deleted, added := compareUserList(c.userList, newUserInfo)
if len(deleted) > 0 {
deletedEmail := make([]string, len(deleted))
for i, u := range deleted {
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, u.Email, u.UID)
var deleted, added []api.UserInfo
if usersChanged {
deleted, added = compareUserList(c.userList, newUserInfo)
if len(deleted) > 0 {
deletedEmail := make([]string, len(deleted))
for i, u := range deleted {
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, u.Email, u.UID)
}
err := c.removeUsers(deletedEmail, c.Tag)
if err != nil {
log.Print(err)
}
}
err := c.removeUsers(deletedEmail, c.Tag)
if err != nil {
log.Print(err)
if len(added) > 0 {
err = c.addNewUser(&added, c.nodeInfo)
if err != nil {
log.Print(err)
}
// Update Limiter
if err := c.UpdateInboundLimiter(c.Tag, &added); err != nil {
log.Print(err)
}
}
}
if len(added) > 0 {
err = c.addNewUser(&added, c.nodeInfo)
if err != nil {
log.Print(err)
}
// Update Limiter
if err := c.UpdateInboundLimiter(c.Tag, &added); err != nil {
log.Print(err)
}
}
log.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(deleted), len(added))
log.Printf("%s %d user deleted, %d user added", c.logPrefix(), len(deleted), len(added))
}
c.userList = newUserInfo
return nil
}
func (c *Controller) removeOldTag(oldtag string) (err error) {
err = c.removeInbound(oldtag)
func (c *Controller) removeOldTag(oldTag string) (err error) {
err = c.removeInbound(oldTag)
if err != nil {
return err
}
err = c.removeOutbound(oldtag)
err = c.removeOutbound(oldTag)
if err != nil {
return err
}
@@ -289,7 +324,7 @@ func (c *Controller) addNewTag(newNodeInfo *api.NodeInfo) (err error) {
}
func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error) {
// Shadowsocks-Plugin require a seaperate inbound for other TransportProtocol likes: ws, grpc
// Shadowsocks-Plugin require a separate inbound for other TransportProtocol likes: ws, grpc
fakeNodeInfo := newNodeInfo
fakeNodeInfo.TransportProtocol = "tcp"
fakeNodeInfo.EnableTLS = false
@@ -342,69 +377,66 @@ func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error)
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
users := make([]*protocol.User, 0)
if nodeInfo.NodeType == "V2ray" {
switch nodeInfo.NodeType {
case "V2ray":
if nodeInfo.EnableVless {
users = c.buildVlessUser(userInfo)
} else {
alterID := 0
if c.panelType == "V2board" {
var alterID uint16 = 0
if (c.panelType == "V2board" || c.panelType == "V2RaySocks") && len(*userInfo) > 0 {
// use latest userInfo
alterID = (*userInfo)[0].AlterID
} else {
alterID = nodeInfo.AlterID
}
if alterID >= 0 && alterID < math.MaxUint16 {
users = c.buildVmessUser(userInfo, uint16(alterID))
} else {
users = c.buildVmessUser(userInfo, 0)
return fmt.Errorf("AlterID should between 0 to 1<<16 - 1, set it to 0 for now")
}
users = c.buildVmessUser(userInfo, alterID)
}
} else if nodeInfo.NodeType == "Trojan" {
case "Trojan":
users = c.buildTrojanUser(userInfo)
} else if nodeInfo.NodeType == "Shadowsocks" {
case "Shadowsocks":
users = c.buildSSUser(userInfo, nodeInfo.CypherMethod)
} else if nodeInfo.NodeType == "Shadowsocks-Plugin" {
case "Shadowsocks-Plugin":
users = c.buildSSPluginUser(userInfo)
} else {
default:
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
}
err = c.addUsers(users, c.Tag)
if err != nil {
return err
}
log.Printf("[%s: %d] Added %d new users", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*userInfo))
log.Printf("%s Added %d new users", c.logPrefix(), len(*userInfo))
return nil
}
func compareUserList(old, new *[]api.UserInfo) (deleted, added []api.UserInfo) {
msrc := make(map[api.UserInfo]byte) //按源数组建索引
mall := make(map[api.UserInfo]byte) //源+目所有元素建索引
mSrc := make(map[api.UserInfo]byte) // 按源数组建索引
mAll := make(map[api.UserInfo]byte) // 源+目所有元素建索引
var set []api.UserInfo //交集
var set []api.UserInfo // 交集
//1.源数组建立map
// 1.源数组建立map
for _, v := range *old {
msrc[v] = 0
mall[v] = 0
mSrc[v] = 0
mAll[v] = 0
}
//2.目数组中,存不进去,即重复元素,所有存不进去的集合就是并集
// 2.目数组中,存不进去,即重复元素,所有存不进去的集合就是并集
for _, v := range *new {
l := len(mall)
mall[v] = 1
if l != len(mall) { //长度变化,即可以存
l = len(mall)
} else { //存不了,进并集
l := len(mAll)
mAll[v] = 1
if l != len(mAll) { // 长度变化,即可以存
l = len(mAll)
} else { // 存不了,进并集
set = append(set, v)
}
}
//3.遍历交集,在并集中找,找到就从并集中删,删完后就是补集(即并-交=所有变化的元素)
// 3.遍历交集,在并集中找,找到就从并集中删,删完后就是补集(即并-交=所有变化的元素)
for _, v := range set {
delete(mall, v)
delete(mAll, v)
}
//4.此时mall是补集所有元素去源中找找到就是删除的找不到的必定能在目数组中找到即新加的
for v := range mall {
_, exist := msrc[v]
// 4.此时mall是补集所有元素去源中找找到就是删除的找不到的必定能在目数组中找到即新加的
for v := range mAll {
_, exist := mSrc[v]
if exist {
deleted = append(deleted, v)
} else {
@@ -415,7 +447,23 @@ func compareUserList(old, new *[]api.UserInfo) (deleted, added []api.UserInfo) {
return deleted, added
}
func limitUser(c *Controller, user api.UserInfo, silentUsers *[]api.UserInfo) {
c.limitedUsers[user] = LimitInfo{
end: time.Now().Unix() + int64(c.config.AutoSpeedLimitConfig.LimitDuration*60),
currentSpeedLimit: c.config.AutoSpeedLimitConfig.LimitSpeed,
originSpeedLimit: user.SpeedLimit,
}
log.Printf("Limit User: %s Speed: %d End: %s", c.buildUserTag(&user), c.config.AutoSpeedLimitConfig.LimitSpeed, time.Unix(c.limitedUsers[user].end, 0).Format("01-02 15:04:05"))
user.SpeedLimit = uint64((c.config.AutoSpeedLimitConfig.LimitSpeed * 1000000) / 8)
*silentUsers = append(*silentUsers, user)
}
func (c *Controller) userInfoMonitor() (err error) {
// delay to start
if time.Since(c.startAt) < time.Duration(c.config.UpdatePeriodic)*time.Second {
return nil
}
// Get server status
CPU, Mem, Disk, Uptime, err := serverstatus.GetSystemInfo()
if err != nil {
@@ -431,23 +479,86 @@ func (c *Controller) userInfoMonitor() (err error) {
if err != nil {
log.Print(err)
}
// Unlock users
if c.config.AutoSpeedLimitConfig.Limit > 0 && len(c.limitedUsers) > 0 {
log.Printf("%s Limited users:", c.logPrefix())
toReleaseUsers := make([]api.UserInfo, 0)
for user, limitInfo := range c.limitedUsers {
if time.Now().Unix() > limitInfo.end {
user.SpeedLimit = limitInfo.originSpeedLimit
toReleaseUsers = append(toReleaseUsers, user)
log.Printf("User: %s Speed: %d End: nil (Unlimit)", c.buildUserTag(&user), user.SpeedLimit)
delete(c.limitedUsers, user)
} else {
log.Printf("User: %s Speed: %d End: %s", c.buildUserTag(&user), limitInfo.currentSpeedLimit, time.Unix(c.limitedUsers[user].end, 0).Format("01-02 15:04:05"))
}
}
if len(toReleaseUsers) > 0 {
if err := c.UpdateInboundLimiter(c.Tag, &toReleaseUsers); err != nil {
log.Print(err)
}
}
}
// Get User traffic
userTraffic := make([]api.UserTraffic, 0)
var userTraffic []api.UserTraffic
var upCounterList []stats.Counter
var downCounterList []stats.Counter
AutoSpeedLimit := int64(c.config.AutoSpeedLimitConfig.Limit)
UpdatePeriodic := int64(c.config.UpdatePeriodic)
limitedUsers := make([]api.UserInfo, 0)
for _, user := range *c.userList {
up, down := c.getTraffic(c.buildUserTag(&user))
up, down, upCounter, downCounter := c.getTraffic(c.buildUserTag(&user))
if up > 0 || down > 0 {
// Over speed users
if AutoSpeedLimit > 0 {
if down > AutoSpeedLimit*1000000*UpdatePeriodic/8 || up > AutoSpeedLimit*1000000*UpdatePeriodic/8 {
if _, ok := c.limitedUsers[user]; !ok {
if c.config.AutoSpeedLimitConfig.WarnTimes == 0 {
limitUser(c, user, &limitedUsers)
} else {
c.warnedUsers[user] += 1
if c.warnedUsers[user] > c.config.AutoSpeedLimitConfig.WarnTimes {
limitUser(c, user, &limitedUsers)
delete(c.warnedUsers, user)
}
}
}
} else {
delete(c.warnedUsers, user)
}
}
userTraffic = append(userTraffic, api.UserTraffic{
UID: user.UID,
Email: user.Email,
Upload: up,
Download: down})
if upCounter != nil {
upCounterList = append(upCounterList, upCounter)
}
if downCounter != nil {
downCounterList = append(downCounterList, downCounter)
}
} else {
delete(c.warnedUsers, user)
}
}
if len(userTraffic) > 0 && !c.config.DisableUploadTraffic {
err = c.apiClient.ReportUserTraffic(&userTraffic)
if len(limitedUsers) > 0 {
if err := c.UpdateInboundLimiter(c.Tag, &limitedUsers); err != nil {
log.Print(err)
}
}
if len(userTraffic) > 0 {
var err error // Define an empty error
if !c.config.DisableUploadTraffic {
err = c.apiClient.ReportUserTraffic(&userTraffic)
}
// If report traffic error, not clear the traffic
if err != nil {
log.Print(err)
} else {
c.resetTraffic(&upCounterList, &downCounterList)
}
}
@@ -458,9 +569,10 @@ func (c *Controller) userInfoMonitor() (err error) {
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
log.Print(err)
} else {
log.Printf("[%s: %d] Report %d online users", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*onlineDevice))
log.Printf("%s Report %d online users", c.logPrefix(), len(*onlineDevice))
}
}
// Report Illegal user
if detectResult, err := c.GetDetectResult(c.Tag); err != nil {
log.Print(err)
@@ -468,7 +580,7 @@ func (c *Controller) userInfoMonitor() (err error) {
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
log.Print(err)
} else {
log.Printf("[%s: %d] Report %d illegal behaviors", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*detectResult))
log.Printf("%s Report %d illegal behaviors", c.logPrefix(), len(*detectResult))
}
}
@@ -478,3 +590,26 @@ func (c *Controller) userInfoMonitor() (err error) {
func (c *Controller) buildNodeTag() string {
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
}
func (c *Controller) logPrefix() string {
return fmt.Sprintf("[%s] %s(ID=%d)", c.clientInfo.APIHost, c.nodeInfo.NodeType, c.nodeInfo.NodeID)
}
// Check Cert
func (c *Controller) certMonitor() error {
if c.nodeInfo.EnableTLS {
switch c.config.CertConfig.CertMode {
case "dns", "http", "tls":
lego, err := mylego.New(c.config.CertConfig)
if err != nil {
log.Print(err)
}
// Xray-core supports the OcspStapling certification hot renew
_, _, _, err = lego.RenewCert()
if err != nil {
log.Print(err)
}
}
}
return nil
}

View File

@@ -8,12 +8,14 @@ import (
"syscall"
"testing"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/sspanel"
_ "github.com/XrayR-project/XrayR/main/distro/all"
. "github.com/XrayR-project/XrayR/service/controller"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/sspanel"
"github.com/XrayR-project/XrayR/common/mylego"
_ "github.com/XrayR-project/XrayR/main/distro/all"
. "github.com/XrayR-project/XrayR/service/controller"
)
func TestController(t *testing.T) {
@@ -22,7 +24,7 @@ func TestController(t *testing.T) {
LogConfig: &conf.LogConfig{LogLevel: "debug"},
}
policyConfig := &conf.PolicyConfig{}
policyConfig.Levels = map[uint32]*conf.Policy{0: &conf.Policy{
policyConfig.Levels = map[uint32]*conf.Policy{0: {
StatsUserUplink: true,
StatsUserDownlink: true,
}}
@@ -45,13 +47,13 @@ func TestController(t *testing.T) {
if err = server.Start(); err != nil {
t.Errorf("Failed to start instance: %s", err)
}
certConfig := &CertConfig{
certConfig := &mylego.CertConfig{
CertMode: "http",
CertDomain: "test.ss.tk",
Provider: "alidns",
Email: "ss@ss.com",
}
controlerconfig := &Config{
controlerConfig := &Config{
UpdatePeriodic: 5,
CertConfig: certConfig,
}
@@ -61,14 +63,14 @@ func TestController(t *testing.T) {
NodeID: 41,
NodeType: "V2ray",
}
apiclient := sspanel.New(apiConfig)
c := New(server, apiclient, controlerconfig)
apiClient := sspanel.New(apiConfig)
c := New(server, apiClient, controlerConfig, "SSpanel")
fmt.Println("Sleep 1s")
err = c.Start()
if err != nil {
t.Error(err)
}
//Explicitly triggering GC to remove garbage from config loading.
// Explicitly triggering GC to remove garbage from config loading.
runtime.GC()
{

View File

@@ -0,0 +1,7 @@
package controller
import "github.com/xtls/xray-core/common/errors"
func newError(values ...interface{}) *errors.Error {
return errors.New(values...)
}

View File

@@ -1,33 +1,39 @@
//Package generate the InbounderConfig used by add inbound
// Package controller Package generate the InboundConfig used by add inbound
package controller
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/common/legocmd"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
C "github.com/sagernet/sing/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/common/mylego"
)
//InboundBuilder build Inbound config for different protocol
// InboundBuilder build Inbound config for different protocol
func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
inboundDetourConfig := &conf.InboundDetourConfig{}
// Build Listen IP address
if nodeInfo.NodeType == "Shadowsocks-Plugin" {
// Shdowsocks listen in 127.0.0.1 for safety
inboundDetourConfig.ListenOn = &conf.Address{net.ParseAddress("127.0.0.1")}
inboundDetourConfig.ListenOn = &conf.Address{Address: net.ParseAddress("127.0.0.1")}
} else if config.ListenIP != "" {
ipAddress := net.ParseAddress(config.ListenIP)
inboundDetourConfig.ListenOn = &conf.Address{ipAddress}
inboundDetourConfig.ListenOn = &conf.Address{Address: ipAddress}
}
// Build Port
portList := &conf.PortList{
Range: []conf.PortRange{{From: uint32(nodeInfo.Port), To: uint32(nodeInfo.Port)}},
Range: []conf.PortRange{{From: nodeInfo.Port, To: nodeInfo.Port}},
}
inboundDetourConfig.PortList = portList
// Build Tag
@@ -48,9 +54,10 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
setting json.RawMessage
)
var proxySetting interface{}
var proxySetting any
// Build Protocol and Protocol setting
if nodeInfo.NodeType == "V2ray" {
switch nodeInfo.NodeType {
case "V2ray":
if nodeInfo.EnableVless {
protocol = "vless"
// Enable fallback
@@ -73,7 +80,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
protocol = "vmess"
proxySetting = &conf.VMessInboundConfig{}
}
} else if nodeInfo.NodeType == "Trojan" {
case "Trojan":
protocol = "trojan"
// Enable fallback
if config.EnableFallback {
@@ -88,23 +95,36 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
} else {
proxySetting = &conf.TrojanServerConfig{}
}
} else if nodeInfo.NodeType == "Shadowsocks" || nodeInfo.NodeType == "Shadowsocks-Plugin" {
case "Shadowsocks", "Shadowsocks-Plugin":
protocol = "shadowsocks"
proxySetting = &conf.ShadowsocksServerConfig{}
randomPasswd := uuid.New()
defaultSSuser := &conf.ShadowsocksUserConfig{
Cipher: "aes-128-gcm",
Password: randomPasswd.String(),
cipher := strings.ToLower(nodeInfo.CypherMethod)
proxySetting = &conf.ShadowsocksServerConfig{
Cipher: cipher,
Password: nodeInfo.ServerKey, // shadowsocks2022 shareKey
}
proxySetting, _ := proxySetting.(*conf.ShadowsocksServerConfig)
proxySetting.Users = append(proxySetting.Users, defaultSSuser)
// shadowsocks must have a random password
// shadowsocks2022's password == user PSK, thus should a length of string >= 32 and base64 encoder
b := make([]byte, 32)
rand.Read(b)
randPasswd := hex.EncodeToString(b)
if C.Contains(shadowaead_2022.List, cipher) {
proxySetting.Users = append(proxySetting.Users, &conf.ShadowsocksUserConfig{
Password: base64.StdEncoding.EncodeToString(b),
})
} else {
proxySetting.Password = randPasswd
}
proxySetting.NetworkList = &conf.NetworkList{"tcp", "udp"}
proxySetting.IVCheck = true
if config.DisableIVCheck {
proxySetting.IVCheck = false
}
} else if nodeInfo.NodeType == "dokodemo-door" {
case "dokodemo-door":
protocol = "dokodemo-door"
proxySetting = struct {
Host string `json:"address"`
@@ -113,14 +133,16 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
Host: "v1.mux.cool",
NetworkList: []string{"tcp", "udp"},
}
} else {
return nil, fmt.Errorf("Unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
default:
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
}
setting, err := json.Marshal(proxySetting)
if err != nil {
return nil, fmt.Errorf("Marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
}
inboundDetourConfig.Protocol = protocol
inboundDetourConfig.Settings = &setting
// Build streamSettings
streamSetting = new(conf.StreamConfig)
@@ -129,13 +151,15 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
if err != nil {
return nil, fmt.Errorf("convert TransportProtocol failed: %s", err)
}
if networkType == "tcp" {
switch networkType {
case "tcp":
tcpSetting := &conf.TCPConfig{
AcceptProxyProtocol: config.EnableProxyProtocol,
HeaderConfig: nodeInfo.Header,
}
streamSetting.TCPSettings = tcpSetting
} else if networkType == "websocket" {
case "websocket":
headers := make(map[string]string)
headers["Host"] = nodeInfo.Host
wsSettings := &conf.WebSocketConfig{
@@ -144,14 +168,14 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
Headers: headers,
}
streamSetting.WSSettings = wsSettings
} else if networkType == "http" {
case "http":
hosts := conf.StringList{nodeInfo.Host}
httpSettings := &conf.HTTPConfig{
Host: &hosts,
Path: nodeInfo.Path,
}
streamSetting.HTTPSettings = httpSettings
} else if networkType == "grpc" {
case "grpc":
grpcSettings := &conf.GRPCConfig{
ServiceName: nodeInfo.ServiceName,
}
@@ -159,6 +183,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
}
streamSetting.Network = &transportProtocol
// Build TLS and XTLS settings
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
streamSetting.Security = nodeInfo.TLSType
@@ -181,6 +206,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
streamSetting.XTLSSettings = xtlsSettings
}
}
// Support ProxyProtocol for any transport protocol
if networkType != "tcp" && networkType != "ws" && config.EnableProxyProtocol {
sockoptConfig := &conf.SocketConfig{
@@ -188,60 +214,59 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
}
streamSetting.SocketSettings = sockoptConfig
}
inboundDetourConfig.Protocol = protocol
inboundDetourConfig.StreamSetting = streamSetting
inboundDetourConfig.Settings = &setting
return inboundDetourConfig.Build()
}
func getCertFile(certConfig *CertConfig) (certFile string, keyFile string, err error) {
if certConfig.CertMode == "file" {
func getCertFile(certConfig *mylego.CertConfig) (certFile string, keyFile string, err error) {
switch certConfig.CertMode {
case "file":
if certConfig.CertFile == "" || certConfig.KeyFile == "" {
return "", "", fmt.Errorf("Cert file path or key file path not exist")
return "", "", fmt.Errorf("cert file path or key file path not exist")
}
return certConfig.CertFile, certConfig.KeyFile, nil
} else if certConfig.CertMode == "dns" {
lego, err := legocmd.New()
case "dns":
lego, err := mylego.New(certConfig)
if err != nil {
return "", "", err
}
certPath, keyPath, err := lego.DNSCert(certConfig.CertDomain, certConfig.Email, certConfig.Provider, certConfig.DNSEnv)
certPath, keyPath, err := lego.DNSCert()
if err != nil {
return "", "", err
}
return certPath, keyPath, err
} else if certConfig.CertMode == "http" {
lego, err := legocmd.New()
case "http", "tls":
lego, err := mylego.New(certConfig)
if err != nil {
return "", "", err
}
certPath, keyPath, err := lego.HTTPCert(certConfig.CertDomain, certConfig.Email)
certPath, keyPath, err := lego.HTTPCert()
if err != nil {
return "", "", err
}
return certPath, keyPath, err
default:
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
}
return "", "", fmt.Errorf("Unsupported certmode: %s", certConfig.CertMode)
}
func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboundFallback, error) {
if fallbackConfigs == nil {
return nil, fmt.Errorf("You must provide FallBackConfigs")
return nil, fmt.Errorf("you must provide FallBackConfigs")
}
vlessFallBacks := make([]*conf.VLessInboundFallback, len(fallbackConfigs))
for i, c := range fallbackConfigs {
if c.Dest == "" {
return nil, fmt.Errorf("Dest is required for fallback fialed")
return nil, fmt.Errorf("dest is required for fallback fialed")
}
var dest json.RawMessage
dest, err := json.Marshal(c.Dest)
if err != nil {
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
}
vlessFallBacks[i] = &conf.VLessInboundFallback{
Name: c.SNI,
@@ -256,20 +281,20 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun
func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInboundFallback, error) {
if fallbackConfigs == nil {
return nil, fmt.Errorf("You must provide FallBackConfigs")
return nil, fmt.Errorf("you must provide FallBackConfigs")
}
trojanFallBacks := make([]*conf.TrojanInboundFallback, len(fallbackConfigs))
for i, c := range fallbackConfigs {
if c.Dest == "" {
return nil, fmt.Errorf("Dest is required for fallback fialed")
return nil, fmt.Errorf("dest is required for fallback fialed")
}
var dest json.RawMessage
dest, err := json.Marshal(c.Dest)
if err != nil {
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
}
trojanFallBacks[i] = &conf.TrojanInboundFallback{
Name: c.SNI,

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/common/mylego"
. "github.com/XrayR-project/XrayR/service/controller"
)
@@ -20,7 +21,7 @@ func TestBuildV2ray(t *testing.T) {
EnableTLS: false,
TLSType: "tls",
}
certConfig := &CertConfig{
certConfig := &mylego.CertConfig{
CertMode: "http",
CertDomain: "test.test.tk",
Provider: "alidns",
@@ -29,7 +30,7 @@ func TestBuildV2ray(t *testing.T) {
config := &Config{
CertConfig: certConfig,
}
_, err := InboundBuilder(config, nodeInfo)
_, err := InboundBuilder(config, nodeInfo, "test_tag")
if err != nil {
t.Error(err)
}
@@ -51,7 +52,7 @@ func TestBuildTrojan(t *testing.T) {
DNSEnv := make(map[string]string)
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
certConfig := &CertConfig{
certConfig := &mylego.CertConfig{
CertMode: "dns",
CertDomain: "trojan.test.tk",
Provider: "alidns",
@@ -61,7 +62,7 @@ func TestBuildTrojan(t *testing.T) {
config := &Config{
CertConfig: certConfig,
}
_, err := InboundBuilder(config, nodeInfo)
_, err := InboundBuilder(config, nodeInfo, "test_tag")
if err != nil {
t.Error(err)
}
@@ -83,7 +84,7 @@ func TestBuildSS(t *testing.T) {
DNSEnv := make(map[string]string)
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
certConfig := &CertConfig{
certConfig := &mylego.CertConfig{
CertMode: "dns",
CertDomain: "trojan.test.tk",
Provider: "alidns",
@@ -93,7 +94,7 @@ func TestBuildSS(t *testing.T) {
config := &Config{
CertConfig: certConfig,
}
_, err := InboundBuilder(config, nodeInfo)
_, err := InboundBuilder(config, nodeInfo, "test_tag")
if err != nil {
t.Error(err)
}

View File

@@ -4,13 +4,14 @@ import (
"encoding/json"
"fmt"
"github.com/XrayR-project/XrayR/api"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/XrayR-project/XrayR/api"
)
//OutboundBuilder build freedom outbund config for addoutbound
// OutboundBuilder build freedom outbound config for addOutbound
func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.OutboundHandlerConfig, error) {
outboundDetourConfig := &conf.OutboundDetourConfig{}
outboundDetourConfig.Protocol = "freedom"
@@ -19,11 +20,11 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
// Build Send IP address
if config.SendIP != "" {
ipAddress := net.ParseAddress(config.SendIP)
outboundDetourConfig.SendThrough = &conf.Address{ipAddress}
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
}
// Freedom Protocol setting
var domainStrategy string = "Asis"
var domainStrategy = "Asis"
if config.EnableDNS {
if config.DNSType != "" {
domainStrategy = config.DNSType
@@ -41,7 +42,7 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
var setting json.RawMessage
setting, err := json.Marshal(proxySetting)
if err != nil {
return nil, fmt.Errorf("Marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
}
outboundDetourConfig.Settings = &setting
return outboundDetourConfig.Build()

View File

@@ -1,19 +1,29 @@
package controller
import (
"encoding/base64"
"fmt"
"strings"
"github.com/XrayR-project/XrayR/api"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
C "github.com/sagernet/sing/common"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/proxy/shadowsocks"
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
"github.com/xtls/xray-core/proxy/trojan"
"github.com/xtls/xray-core/proxy/vless"
"github.com/XrayR-project/XrayR/api"
)
var AEADMethod = []shadowsocks.CipherType{shadowsocks.CipherType_AES_128_GCM, shadowsocks.CipherType_AES_256_GCM, shadowsocks.CipherType_CHACHA20_POLY1305, shadowsocks.CipherType_XCHACHA20_POLY1305}
var AEADMethod = map[shadowsocks.CipherType]uint8{
shadowsocks.CipherType_AES_128_GCM: 0,
shadowsocks.CipherType_AES_256_GCM: 0,
shadowsocks.CipherType_CHACHA20_POLY1305: 0,
shadowsocks.CipherType_XCHACHA20_POLY1305: 0,
}
func (c *Controller) buildVmessUser(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) {
users = make([]*protocol.User, len(*userInfo))
@@ -65,43 +75,65 @@ func (c *Controller) buildTrojanUser(userInfo *[]api.UserInfo) (users []*protoco
}
func (c *Controller) buildSSUser(userInfo *[]api.UserInfo, method string) (users []*protocol.User) {
users = make([]*protocol.User, 0)
users = make([]*protocol.User, len(*userInfo))
cypherMethod := cipherFromString(method)
for _, user := range *userInfo {
ssAccount := &shadowsocks.Account{
Password: user.Passwd,
CipherType: cypherMethod,
for i, user := range *userInfo {
// // shadowsocks2022 Key = openssl rand -base64 32 and multi users needn't cipher method
if C.Contains(shadowaead_2022.List, strings.ToLower(method)) {
e := c.buildUserTag(&user)
users[i] = &protocol.User{
Level: 0,
Email: e,
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
Key: base64.StdEncoding.EncodeToString([]byte(user.Passwd)),
Email: e,
Level: 0,
}),
}
} else {
users[i] = &protocol.User{
Level: 0,
Email: c.buildUserTag(&user),
Account: serial.ToTypedMessage(&shadowsocks.Account{
Password: user.Passwd,
CipherType: cipherFromString(method),
}),
}
}
users = append(users, &protocol.User{
Level: 0,
Email: c.buildUserTag(&user),
Account: serial.ToTypedMessage(ssAccount),
})
}
return users
}
func (c *Controller) buildSSPluginUser(userInfo *[]api.UserInfo) (users []*protocol.User) {
users = make([]*protocol.User, 0)
users = make([]*protocol.User, len(*userInfo))
for _, user := range *userInfo {
// Check if the cypher method is AEAD
cypherMethod := cipherFromString(user.Method)
for _, aeadMethod := range AEADMethod {
if aeadMethod == cypherMethod {
ssAccount := &shadowsocks.Account{
Password: user.Passwd,
CipherType: cypherMethod,
for i, user := range *userInfo {
// shadowsocks2022 Key = openssl rand -base64 32 and multi users needn't cipher method
if C.Contains(shadowaead_2022.List, strings.ToLower(user.Method)) {
e := c.buildUserTag(&user)
users[i] = &protocol.User{
Level: 0,
Email: e,
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
Key: base64.StdEncoding.EncodeToString([]byte(user.Passwd)),
Email: e,
Level: 0,
}),
}
} else {
// Check if the cypher method is AEAD
cypherMethod := cipherFromString(user.Method)
if _, ok := AEADMethod[cypherMethod]; ok {
users[i] = &protocol.User{
Level: 0,
Email: c.buildUserTag(&user),
Account: serial.ToTypedMessage(&shadowsocks.Account{
Password: user.Passwd,
CipherType: cypherMethod,
}),
}
users = append(users, &protocol.User{
Level: 0,
Email: c.buildUserTag(&user),
Account: serial.ToTypedMessage(ssAccount),
})
}
}
}
return users
}

View File

@@ -1,5 +1,5 @@
// Package service contains all the services used by XrayR
// To implement an service, one needs to implement the interface below.
// To implement a service, one needs to implement the interface below.
package service
// Service is the interface of all the services running in the panel