51 Commits

Author SHA1 Message Date
bjdgyc
edc7a4a4a3 Merge pull request #283 from bjdgyc/dev
修复容器频繁重启的问题
2023-12-26 12:13:03 +08:00
bjdgy
17492d8172 修复容器频繁重启的问题 2023-12-26 11:24:01 +08:00
bjdgyc
65de1a58cf Merge pull request #282 from bjdgyc/dev
添加问题信息
2023-12-25 15:39:43 +08:00
bjdgyc
dfb25718f7 添加问题信息 2023-12-25 15:38:59 +08:00
bjdgyc
3deda4d77f Merge pull request #281 from bjdgyc/dev
合并新版
2023-12-25 14:56:58 +08:00
bjdgyc
2af524f87b Merge pull request #274 from aiminickwong/main
解决电信DNS Let's Encrypt证书刷新缓慢问题
2023-12-25 14:55:14 +08:00
bjdgyc
1b6abeb849 修复 modprobe 报错 2023-12-25 14:47:30 +08:00
bjdgyc
e3b303744b 修复 modprobe 报错 2023-12-25 14:34:21 +08:00
bjdgyc
d3f16eb2ad 修复 modprobe 报错 2023-12-25 14:31:35 +08:00
bjdgyc
64404ea94b 修改readme 2023-12-13 16:50:35 +08:00
bjdgyc
ededfddff4 Merge pull request #278 from lanrenwo/group_ip_list
新增路由设置的编辑模式
2023-12-04 18:38:17 +08:00
lanrenwo
8a3d34b737 优化isValidCIDR函数,解决部分格式检测有误,并给予建议提示。 2023-12-04 18:32:09 +08:00
lanrenwo
7c040e2a0f 过滤文本框内的空行 2023-12-04 13:44:06 +08:00
lanrenwo
3d03f6adb8 解决大量路由导致弹窗卡顿的问题(当点击“路由设置”时,才加载路由) 2023-12-04 13:08:37 +08:00
lanrenwo
5d24eda7fc 不限制路由的数量,并检测CIDR格式的正确性 2023-12-03 22:09:46 +08:00
lanrenwo
ea92857524 新增路由设置的编辑模式 2023-12-02 15:38:18 +08:00
bjdgyc
b521dddb98 Merge pull request #276 from lanrenwo/banner_special_chars
优化处理Banner特殊字符的代码
2023-11-27 10:18:31 +08:00
lanrenwo
2bd94aef2b 优化处理Banner特殊字符的代码 2023-11-20 12:24:44 +08:00
bjdgyc
3879c3a4bc 修复Banner特殊字符 2023-11-15 11:57:12 +08:00
bjdgyc
aa2b89855f 添加 DTLS12-CipherSuite 筛选 2023-11-09 10:52:10 +08:00
bjdgyc
9e1969e3d0 添加 DTLS12-CipherSuite 筛选 2023-11-08 18:07:32 +08:00
bjdgyc
5b1d86282a fix 2023-11-06 16:51:32 +08:00
bjdgyc
bfc39fe4ea 默认显示错误信息 2023-11-02 15:54:59 +08:00
bjdgyc
9019a5f03a 添加网卡 alias 信息 2023-10-30 13:04:07 +08:00
bjdgyc
38d268e999 添加网卡 alias 信息 2023-10-30 11:46:53 +08:00
bjdgyc
57990d3d2a 添加网卡 alias 2023-10-30 11:38:31 +08:00
bjdgyc
6788a875a2 优化 2023-10-24 17:49:13 +08:00
bjdgyc
a9ad21b3b5 修改dtls加密套件 2023-10-17 16:30:45 +08:00
bjdgyc
43ca09e985 修改dtls加密套件 2023-10-17 16:01:31 +08:00
aiminick
b7da567cee 解决电信DNS Let's Encrypt证书刷新缓慢问题
解决电信DNS Let's Encrypt证书刷新缓慢问题,改为阿里DNS后问题改善
2023-10-17 00:54:49 +08:00
bjdgyc
6eea265b15 添加自定义首页 2023-10-11 17:21:26 +08:00
bjdgyc
06c8ee1197 添加自定义首页 2023-10-11 17:20:57 +08:00
bjdgyc
ebc7cc85c0 添加nginx stream示例 2023-10-11 16:00:53 +08:00
bjdgyc
012f636cf7 修改 profile.xml 2023-10-11 10:19:23 +08:00
bjdgyc
4f9cc2074a Merge remote-tracking branch 'origin/dev' into dev 2023-09-22 16:19:10 +08:00
bjdgyc
bbc5877eb9 修复header 2023-09-22 16:18:38 +08:00
bjdgyc
c6b85c7d66 Merge pull request #270 from lanrenwo/dev
修复logAudit的panic
2023-09-12 08:40:50 +08:00
lanrenwo
8e843d5eae Update payload_access_audit.go 2023-09-08 21:01:03 +08:00
lanrenwo
7b9be9377f 修复logAudit的panic 2023-09-08 20:33:30 +08:00
bjdgyc
f03264faf3 Merge pull request #268 from shikaiguo/main
修改邮件内容的参数
2023-09-06 15:57:08 +08:00
bjdgyc
b19ff321ad Merge pull request #267 from lanrenwo/dev
修复sniNewParser的panic
2023-09-06 15:54:22 +08:00
lanrenwo
f6980261d4 logAudit引入recover, 防止主程序崩溃. 2023-09-03 11:18:52 +08:00
lanrenwo
7651b69ed6 删除sniNewParser多余的空格 2023-09-02 10:46:01 +08:00
lanrenwo
2af2d273e4 简化sniNewParser代码 2023-09-02 10:44:47 +08:00
K
ff54abc5d5 增加邮件内容的昵称参数 2023-09-01 22:49:07 +08:00
lanrenwo
a168c96a93 修复sniNewParser的panic 2023-09-01 18:10:20 +08:00
bjdgyc
6127c41aea 修复 panic 2023-09-01 17:55:15 +08:00
bjdgyc
da1d6c6c6d 添加安全的header头 2023-08-25 13:56:04 +08:00
bjdgyc
08de4fe086 添加安全的header头 2023-08-24 16:59:35 +08:00
bjdgyc
7714c2a3e8 debug信息 需要鉴权后显示 2023-08-24 14:27:12 +08:00
bjdgyc
78a8b06467 变更qq群 2023-08-17 16:27:12 +08:00
33 changed files with 644 additions and 165 deletions

View File

@@ -24,7 +24,8 @@ AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannop
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的 SSL 证书。
AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap 功能、ip 设置命令。
AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap
功能、ip 设置命令。
## Screenshot
@@ -52,6 +53,8 @@ AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试
>
> 对于线上环境,必须申请安全的 https 证书,不支持私有证书连接
>
> 服务端安装 yum install iproute 或者 apt-get install iproute2
>
> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常
>
> 其他问题 [前往查看](doc/question.md)
@@ -60,11 +63,17 @@ AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试
### 自行编译安装
> 需要提前安装好 golang >= 1.19 和 nodejs >= 14.x 和 yarn >= v1.22.x
> 需要提前安装好 golang >= 1.19 和 nodejs >= 16.x 和 yarn >= v1.22.x
```shell
git clone https://github.com/bjdgyc/anylink.git
# 编译参考软件版本
# go 1.20.12
# node v16.20.2
# yarn 1.22.19
cd anylink
sh build.sh
@@ -121,7 +130,7 @@ sudo ./anylink
> 数据库配置示例
| db_type | db_source |
| -------- | ------------------------------------------------------ |
|----------|--------------------------------------------------------|
| sqlite3 | ./conf/anylink.db |
| mysql | user:password@tcp(127.0.0.1:3306)/anylink?charset=utf8 |
| postgres | user:password@localhost/anylink?sslmode=verify-full |
@@ -134,9 +143,11 @@ sudo ./anylink
> 以下参数必须设置其中之一
网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="macvtap"`,`link_mode="tap"(不推荐)` 等参数。 不同的参数需要对服务器做相应的设置。
网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="macvtap"`,`link_mode="tap"(不推荐)` 等参数。
不同的参数需要对服务器做相应的设置。
建议优先选择 tun 模式,其次选择 macvtap 模式,因客户端传输的是 IP 层数据,无须进行数据转换。 tap 模式是在用户态做的链路层到 IP 层的数据互相转换,性能会有所下降。 如果需要在虚拟机内开启 tap
建议优先选择 tun 模式,其次选择 macvtap 模式,因客户端传输的是 IP 层数据,无须进行数据转换。 tap 模式是在用户态做的链路层到
IP 层的数据互相转换,性能会有所下降。 如果需要在虚拟机内开启 tap
模式,请确认虚拟机的网卡开启混杂模式。
### tun 设置
@@ -190,7 +201,6 @@ https://cloud.tencent.com/document/product/216/62007
```
3. 使用 AnyConnect 客户端连接即可
### macvtap 设置
@@ -216,7 +226,6 @@ ipv4_start = "10.1.2.100"
ipv4_end = "10.1.2.200"
```
## Systemd
1. 添加 anylink 程序
@@ -284,7 +293,7 @@ ipv4_end = "10.1.2.200"
-c=/etc/server.toml --ip_lease=1209600 # IP地址租约时长
```
7. 构建镜像
7. 构建镜像 (非必需)
```bash
#获取仓库源码
@@ -293,7 +302,6 @@ ipv4_end = "10.1.2.200"
docker build -t anylink -f docker/Dockerfile .
```
## 常见问题
请前往 [问题地址](doc/question.md) 查看具体信息
@@ -301,6 +309,7 @@ ipv4_end = "10.1.2.200"
## Discussion
添加QQ群(1): 567510628
添加QQ群(2): 739072205
群共享文件有相关软件下载
@@ -311,7 +320,6 @@ ipv4_end = "10.1.2.200"
![contact_me_qr](doc/screenshot/contact_me_qr.png)
-->
## Contribution
欢迎提交 PR、Issues感谢为 AnyLink 做出贡献。

3
build.sh Executable file → Normal file
View File

@@ -36,7 +36,7 @@ cp -rf $cpath/web/ui .
#国内可替换源加快速度
export GOPROXY=https://goproxy.io
go mod tidy
go build -v -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)"
go build -v -o anylink -ldflags "-s -w -X main.CommitId=$(git rev-parse HEAD)"
RETVAL $?
cd $cpath
@@ -52,6 +52,7 @@ cp -r server/conf $deploy
cp -r systemd $deploy
cp -r LICENSE $deploy
cp -r home $deploy
tar zcvf ${deploy}.tar.gz $deploy

View File

@@ -6,10 +6,13 @@ echo $ver
#docker login -u bjdgyc
#docker build -t bjdgyc/anylink .
docker build -t bjdgyc/anylink -f docker/Dockerfile .
docker build -t bjdgyc/anylink --build-arg GitCommitId=$(git rev-parse HEAD) -f docker/Dockerfile .
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
exit 0
docker push bjdgyc/anylink:$ver
docker push bjdgyc/anylink:latest

View File

@@ -12,8 +12,8 @@
>
> 需要展示主页的同学可以在QQ群 直接联系我添加。
| 昵称 | 主页 |
|---------| ---------------------------- |
| 昵称 | 主页 / 留言 |
|-----------|------------------------------|
| 代码 oo8 | |
| 甘磊 | https://github.com/ganlei333 |
| Oo@ | https://github.com/chooop |
@@ -40,6 +40,9 @@
| 悲鸣 | |
| 谢谢 | |
| 云思科技 | |
| 哆啦A伟(张佳伟) | 嘿嘿 |
| 人类的悲欢并不相通 | 开源不易,感谢分享 |
| 做人要低调 | |

View File

@@ -1,34 +1,53 @@
## 常见问题
### anyconnect 客户端问题
> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常
>
> 添加QQ群: 567510628
### OTP 动态码
> 请使用手机安装 freeotp 然后扫描otp二维码生成的数字即是动态码
### 远程桌面连接
> 本软件已经支持远程桌面里面连接anyconnect。
### 私有证书问题
> anylink 默认不支持私有证书
>
> 其他使用私有证书的问题,请自行解决
### 客户端连接名称
> 客户端连接名称需要修改 [profile.xml](../server/conf/profile.xml) 文件
```xml
<HostEntry>
<HostName>VPN</HostName>
<HostAddress>localhost</HostAddress>
</HostEntry>
```
### dpd timeout 设置问题
```
```yaml
#客户端失效检测时间(秒) dpd > keepalive
cstp_keepalive = 6
cstp_dpd = 10
mobile_keepalive = 15
mobile_dpd = 20
cstp_keepalive = 4
cstp_dpd = 9
mobile_keepalive = 7
mobile_dpd = 15
```
> 以上dpd参数为客户端的超时检测时间, 如一段时间内,没有数据传输,防火墙会主动关闭连接
>
> 如经常出现 timeout 的错误信息应根据当前防火墙的设置适当减小dpd数值
### 反向代理问题
> anylink 仅支持四层反向代理,不支持七层反向代理
>
> 如Nginx请使用 stream模块
@@ -46,7 +65,36 @@ stream {
}
```
> nginx实现 共用443端口 示例
```conf
stream {
map $ssl_preread_server_name $name {
vpn.xx.com myvpn;
default defaultpage;
}
# upstream pool
upstream myvpn {
server 127.0.0.1:8443;
}
upstream defaultpage {
server 127.0.0.1:8080;
}
server {
listen 443 so_keepalive=on;
ssl_preread on;
#接收端也需要设置 proxy_protocol
#proxy_protocol on;
proxy_pass $name;
}
}
```
### 性能问题
```
内网环境测试数据
虚拟服务器: centos7 4C8G
@@ -55,6 +103,7 @@ anylink: tun模式 tcp传输
客户端网卡下载速度270Mb/s
服务端网卡上传速度280Mb/s
```
> 客户端tls加密协议、隧道header头都会占用一定带宽

View File

@@ -1,40 +1,49 @@
#node:16-bullseye
#golang:1.20-bullseye
#debian:bullseye-slim
#sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
# web
FROM node:16.17.1-alpine3.15 as builder_node
FROM node:16-alpine3.18 as builder_node
WORKDIR /web
COPY ./web /web
RUN yarn install \
&& yarn run build \
&& ls /web/ui
# server
FROM golang:1.19-alpine as builder_golang
FROM golang:1.20-alpine3.18 as builder_golang
#TODO 本地打包时使用镜像
ENV GOPROXY=https://goproxy.io
ENV GOPROXY=https://goproxy.cn
ENV GOOS=linux
ARG GitCommitId="gitCommitId"
WORKDIR /anylink
COPY . /anylink
COPY --from=builder_node /web/ui /anylink/server/ui
COPY server /anylink
COPY --from=builder_node /web/ui /anylink/ui
#TODO 本地打包时使用镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache git gcc musl-dev
RUN cd /anylink/server;go mod tidy;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
&& /anylink/server/anylink tool -v
RUN apk add gcc musl-dev
RUN cd /anylink;go mod tidy;go build -o anylink -ldflags "-s -w -X main.CommitId=${GitCommitId}" \
&& /anylink/anylink tool -v
# anylink
FROM alpine
FROM alpine:3.18
LABEL maintainer="github.com/bjdgyc"
#ENV IPV4_CIDR="192.168.10.0/24"
ENV ANYLINK_IN_CONTAINER=true
WORKDIR /app
COPY --from=builder_golang /anylink/server/anylink /app/
COPY --from=builder_golang /anylink/anylink /app/
COPY docker/docker_entrypoint.sh /app/
#COPY ./server/bridge-init.sh /app/
COPY ./server/bridge-init.sh /app/
COPY ./server/conf /app/conf
COPY ./LICENSE /app/LICENSE
COPY ./home /app/home
#TODO 本地打包时使用镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories

View File

@@ -14,7 +14,7 @@ case $var1 in
;;
*)
sysctl -w net.ipv4.ip_forward=1
#sysctl -w net.ipv4.ip_forward=1
#iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE
#iptables -nL -t nat

101
home/自定义首页1.html Normal file
View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AnyLink - 企业级远程办公 SSL VPN</title>
<style>
/* CSS样式表 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
header {
background-color: #333;
color: #fff;
padding: 20px;
text-align: center;
}
h1 {
margin: 0;
font-size: 32px;
}
main {
max-width: 960px;
margin: 20px auto;
padding: 0 20px;
margin-bottom: 100px;
}
p {
line-height: 1.5;
}
/* 设置页脚固定在底部,并且占满横向宽度 */
footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
}
footer {
background-color: #f2f2f2;
padding: 20px;
text-align: center;
}
.cta-button {
display: inline-block;
background-color: #007bff;
color: #fff;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
margin-right: 10px;
}
</style>
</head>
<body>
<header>
<h1>欢迎使用 AnyLink</h1>
</header>
<main>
<h2>什么是 AnyLink</h2>
<p>AnyLink 是一款面向企业级的远程办公 SSL VPN 软件,支持多人同时在线使用。它提供安全、便捷的访问内部网络资源的方式,使远程工作者能够有效协作。</p>
<h2>核心功能</h2>
<ul>
<li>安全远程访问AnyLink 使用 SSL/TLS 加密技术,确保远程用户与企业网络之间的连接安全可靠。</li>
<li>多用户支持:多个用户可以同时连接 VPN实现不同地点团队的无缝协作。</li>
<li>灵活网络访问AnyLink 能够安全地让远程工作者访问内部资源,如文件、应用程序和数据库。</li>
<li>集中化管理:该 VPN 解决方案提供集中化管理控制台,便于用户管理、访问控制和监控。</li>
</ul>
<h2>开始使用 AnyLink</h2>
<p>体验 AnyLink 为您的企业远程办公需求所带来的便利和安全。</p>
<h2>下载客户端</h2>
<a href="/files/anyconnect-win-4.10.05111.msi" class="cta-button">Windows 客户端</a>
<a href="/files/anyconnect-macos-4.10.05111.dmg" class="cta-button">Mac 客户端</a>
<a href="https://apps.apple.com/cn/app/cisco-secure-client/id1135064690" class="cta-button">iOS 客户端</a>
<a href="/files/CiscoSecureClientAnyConnect_v5.0.00247.apk" class="cta-button">Android 客户端</a>
<a href="/files/freeotp.apk" class="cta-button">Android FreeOTP客户端</a>
<a href="https://apps.apple.com/cn/app/freeotp-authenticator/id872559395" class="cta-button">iOS FreeOTP客户端</a>
<h2>使用手册</h2>
<a href="/files/anylink_doc.pdf" class="cta-button">使用手册(Windows)</a>
</main>
<footer>
&copy; 2023 AnyLink. 保留所有权利。
</footer>
</body>
</html>

View File

@@ -67,6 +67,14 @@ func Login(w http.ResponseWriter, r *http.Request) {
data["admin_user"] = adminUser
data["expires_at"] = expiresAt
ck := &http.Cookie{
Name: "jwt",
Value: tokenString,
Path: "/",
HttpOnly: true,
}
http.SetCookie(w, ck)
RespSucess(w, data)
}
@@ -76,13 +84,15 @@ func authMiddleware(next http.Handler) http.Handler {
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "*")
if r.Method == http.MethodOptions {
// 正式环境不支持 OPTIONS
w.WriteHeader(http.StatusForbidden)
return
}
route := mux.CurrentRoute(r)
name := route.GetName()
// fmt.Println("bb", r.URL.Path, name)
if utils.InArrStr([]string{"login", "index", "static", "debug"}, name) {
if utils.InArrStr([]string{"login", "index", "static"}, name) {
// 不进行鉴权
next.ServeHTTP(w, r)
return
@@ -93,6 +103,12 @@ func authMiddleware(next http.Handler) http.Handler {
if jwtToken == "" {
jwtToken = r.FormValue("jwt")
}
if jwtToken == "" {
cc, err := r.Cookie("jwt")
if err == nil {
jwtToken = cc.Value
}
}
data, err := GetJwtData(jwtToken)
if err != nil || base.Cfg.AdminUser != fmt.Sprint(data["admin_user"]) {
w.WriteHeader(http.StatusUnauthorized)

View File

@@ -205,6 +205,7 @@ type userAccountMailData struct {
LinkAddr string
Group string
Username string
Nickname string
PinCode string
OtpImg string
OtpImgBase64 string
@@ -251,9 +252,11 @@ func userAccountMail(user *dbdata.User) error {
otpData, _ := userOtpQr(user.Id, true)
data := userAccountMailData{
Issuer: base.Cfg.Issuer,
LinkAddr: setting.LinkAddr,
Group: strings.Join(user.Groups, ","),
Username: user.Username,
Nickname: user.Nickname,
PinCode: user.PinCode,
OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString),
OtpImgBase64: "data:image/png;base64," + otpData,

View File

@@ -10,6 +10,7 @@ import (
"github.com/arl/statsviz"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
@@ -20,6 +21,13 @@ var UiData embed.FS
func StartAdmin() {
r := mux.NewRouter()
// 所有路由添加安全头
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
utils.SetSecureHeader(w)
next.ServeHTTP(w, req)
})
})
r.Use(authMiddleware)
r.Use(handlers.CompressHandler)

View File

@@ -3,5 +3,5 @@ package base
const (
APP_NAME = "AnyLink"
// app版本号
APP_VER = "0.9.4"
APP_VER = "0.10.1"
)

View File

@@ -53,7 +53,7 @@ var configs = []config{
{Typ: cfgInt, Name: "max_client", Usage: "最大用户连接", ValInt: 200},
{Typ: cfgInt, Name: "max_user_client", Usage: "最大单用户连接", ValInt: 3},
{Typ: cfgInt, Name: "cstp_keepalive", Usage: "keepalive时间(秒)", ValInt: 4},
{Typ: cfgInt, Name: "cstp_dpd", Usage: "死链接检测时间(秒)", ValInt: 10},
{Typ: cfgInt, Name: "cstp_dpd", Usage: "死链接检测时间(秒)", ValInt: 9},
{Typ: cfgInt, Name: "mobile_keepalive", Usage: "移动端keepalive接检测时间(秒)", ValInt: 7},
{Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 15},
{Typ: cfgInt, Name: "mtu", Usage: "最大传输单元MTU", ValInt: 1460},

77
server/base/mod.go Normal file
View File

@@ -0,0 +1,77 @@
package base
import (
"bufio"
"fmt"
"log"
"os"
"os/exec"
"strings"
)
const (
procModulesPath = "/proc/modules"
inContainerKey = "ANYLINK_IN_CONTAINER"
tunPath = "/dev/net/tun"
)
var (
InContainer = false
modMap = map[string]struct{}{}
)
func initMod() {
container := os.Getenv(inContainerKey)
if container == "true" {
InContainer = true
}
log.Println("InContainer", InContainer)
file, err := os.Open(procModulesPath)
if err != nil {
err = fmt.Errorf("[ERROR] Problem with open file: %s", err)
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
splited := strings.Split(scanner.Text(), " ")
if len(splited[0]) > 0 {
modMap[splited[0]] = struct{}{}
}
}
}
func CheckModOrLoad(mod string) {
log.Println("CheckModOrLoad", mod)
if _, ok := modMap[mod]; ok {
return
}
if mod == "tun" || mod == "tap" {
_, err := os.Stat(tunPath)
if err == nil {
// 文件存在
return
}
panic("Linux tunFile is null " + tunPath)
}
if InContainer {
err := fmt.Errorf("Linux module %s is not loaded, please run `modprobe %s`", mod, mod)
// log.Println(err)
// return
panic(err)
}
cmdstr := fmt.Sprintln("modprobe", mod)
cmd := exec.Command("sh", "-c", cmdstr)
b, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(b))
panic(err)
}
}

View File

@@ -4,6 +4,7 @@ func Start() {
execute()
initCfg()
initLog()
initMod()
}
func Test() {

View File

@@ -1,2 +1,2 @@
客户端软件需放置在files目录内
如需要帮助请加QQ群567510628
如需要帮助请加QQ群567510628 、739072205

View File

@@ -8,6 +8,7 @@
<RestrictPreferenceCaching>false</RestrictPreferenceCaching>
<RestrictTunnelProtocols>IPSec</RestrictTunnelProtocols>
<BypassDownloader>true</BypassDownloader>
<AutoUpdate UserControllable="false">false</AutoUpdate>
<WindowsVPNEstablishment>AllowRemoteUsers</WindowsVPNEstablishment>
<LinuxVPNEstablishment>AllowRemoteUsers</LinuxVPNEstablishment>
<CertEnrollmentPin>pinAllowed</CertEnrollmentPin>
@@ -20,15 +21,19 @@
</ExtendedKeyUsage>
</CertificateMatch>
<BackupServerList>
<HostAddress>localhost</HostAddress>
</BackupServerList>
</ClientInitialization>
<ServerList>
<HostEntry>
<HostName>VPN Server</HostName>
<HostName>VPN</HostName>
<HostAddress>localhost</HostAddress>
</HostEntry>
<HostEntry>
<HostName>VPN2</HostName>
<HostAddress>localhost2</HostAddress>
</HostEntry>
</ServerList>
</AnyConnectProfile>

View File

@@ -41,6 +41,6 @@ iptables_nat = true
#客户端显示详细错误信息(线上环境慎开启)
display_error = false
display_error = true

View File

@@ -198,7 +198,7 @@ func (c *LeGoClient) NewClient(l *SettingLetsEncrypt) error {
if err != nil {
return err
}
if err := client.Challenge.SetDNS01Provider(Provider, dns01.AddRecursiveNameservers([]string{"114.114.114.114", "114.114.115.115"})); err != nil {
if err := client.Challenge.SetDNS01Provider(Provider, dns01.AddRecursiveNameservers([]string{"223.6.6.6", "223.5.5.5"})); err != nil {
return err
}
if legouser.Registration == nil {

View File

@@ -2,10 +2,13 @@ package handler
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"encoding/hex"
"errors"
"net"
"strings"
"time"
"github.com/bjdgyc/anylink/base"
@@ -20,10 +23,13 @@ func startDtls() {
return
}
certificate, err := selfsign.GenerateSelfSigned()
// rsa 兼容 open connect
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
certificate, err := selfsign.SelfSign(priv)
if err != nil {
panic(err)
}
logf := logging.NewDefaultLoggerFactory()
logf.Writer = base.GetBaseLw()
// logf.DefaultLogLevel = logging.LogLevelTrace
@@ -34,9 +40,14 @@ func startDtls() {
config := &dtls.Config{
Certificates: []tls.Certificate{certificate},
InsecureSkipVerify: true,
ExtendedMasterSecret: dtls.DisableExtendedMasterSecret,
CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
CipherSuites: func() []dtls.CipherSuiteID {
var cs = []dtls.CipherSuiteID{}
for _, vv := range dtlsCipherSuites {
cs = append(cs, vv)
}
return cs
}(),
LoggerFactory: logf,
MTU: BufferSize,
SessionStore: sessStore,
@@ -98,3 +109,23 @@ func (ms *sessionStore) Get(key []byte) (dtls.Session, error) {
func (ms *sessionStore) Del(key []byte) error {
return nil
}
// 客户端和服务端映射 X-DTLS12-CipherSuite
var dtlsCipherSuites = map[string]dtls.CipherSuiteID{
// "ECDHE-ECDSA-AES256-GCM-SHA384": dtls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
// "ECDHE-ECDSA-AES128-GCM-SHA256": dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"ECDHE-RSA-AES256-GCM-SHA384": dtls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"ECDHE-RSA-AES128-GCM-SHA256": dtls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
func checkDtls12Ciphersuite(ciphersuite string) string {
csArr := strings.Split(ciphersuite, ":")
for _, v := range csArr {
if _, ok := dtlsCipherSuites[v]; ok {
return v
}
}
// 返回默认值
return "ECDHE-RSA-AES128-GCM-SHA256"
}

View File

@@ -1,6 +1,7 @@
package handler
import (
"bytes"
"crypto/md5"
"encoding/xml"
"fmt"
@@ -49,7 +50,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
return
}
// fmt.Printf("%+v \n", cr)
setCommonHeader(w)
// setCommonHeader(w)
if cr.Type == "logout" {
// 退出删除session信息
if cr.SessionToken != "" {
@@ -154,10 +155,12 @@ func tplRequest(typ int, w io.Writer, data RequestData) {
return
}
if strings.Contains(data.Banner, "\n") {
// 替换xml文件的换行符
data.Banner = strings.ReplaceAll(data.Banner, "\n", "&#x0A;")
if data.Banner != "" {
buf := new(bytes.Buffer)
xml.EscapeText(buf, []byte(data.Banner))
data.Banner = buf.String()
}
t, _ := template.New("auth_complete").Parse(auth_complete)
_ = t.Execute(w, data)
}

View File

@@ -3,7 +3,6 @@ package handler
import (
"encoding/xml"
"log"
"net/http"
"os/exec"
)
@@ -42,28 +41,6 @@ type macAddressList struct {
MacAddress string `xml:"mac-address"`
}
func setCommonHeader(w http.ResponseWriter) {
// Content-Length Date 默认已经存在
w.Header().Set("Server", "AnyLinkOpenSource")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "no-store,no-cache")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Frame-Options", "deny")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Security-Policy", "default-src 'none'")
w.Header().Set("X-Permitted-Cross-Domain-Policies", "none")
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("Clear-Site-Data", "cache,cookies,storage")
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
w.Header().Set("X-XSS-Protection", "0")
w.Header().Set("X-Aggregate-Auth", "1")
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
}
func execCmd(cmdStrs []string) error {
for _, cmdStr := range cmdStrs {
cmd := exec.Command("sh", "-c", cmdStr)

View File

@@ -13,7 +13,7 @@ func LinkHome(w http.ResponseWriter, r *http.Request) {
// fmt.Println(r.RemoteAddr)
// hu, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpHome: ", string(hu))
w.Header().Set("Server", "AnyLinkOpenSource")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
connection := strings.ToLower(r.Header.Get("Connection"))
userAgent := strings.ToLower(r.UserAgent())
if connection == "close" && (strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect")) {

View File

@@ -22,22 +22,29 @@ func checkTun() {
defer ifce.Close()
// 测试ip命令
cmdstr := fmt.Sprintf("ip link set dev %s up mtu %s multicast off", ifce.Name(), "1399")
err = execCmd([]string{cmdstr})
base.CheckModOrLoad("tun")
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %s multicast off", ifce.Name(), "1399")
err = execCmd([]string{cmdstr1})
if err != nil {
base.Fatal("testTun err: ", err)
}
// 开启服务器转发
if err := execCmd([]string{"sysctl -w net.ipv4.ip_forward=1"}); err != nil {
base.Error(err)
base.Fatal(err)
}
if base.Cfg.IptablesNat {
// 添加NAT转发规则
ipt, err := iptables.New()
if err != nil {
base.Error(err)
base.Fatal(err)
return
}
// 修复 rockyos nat 不生效
base.CheckModOrLoad("iptable_filter")
base.CheckModOrLoad("iptable_nat")
natRule := []string{"-s", base.Cfg.Ipv4CIDR, "-o", base.Cfg.Ipv4Master, "-j", "MASQUERADE"}
forwardRule := []string{"-j", "ACCEPT"}
if natExists, _ := ipt.Exists("nat", "POSTROUTING", natRule...); !natExists {
@@ -65,7 +72,13 @@ func LinkTun(cSess *sessdata.ConnSession) error {
// log.Printf("Interface Name: %s\n", ifce.Name())
cSess.SetIfName(ifce.Name())
// 通过 ip link show 查看 alias 信息
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu)
if !base.InContainer {
// 容器默认 iproute 不支持 alias
cmdstr1 += fmt.Sprintf(" alias %s.%s", cSess.Group.Name, cSess.Username)
}
cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
err = execCmd([]string{cmdstr1, cmdstr2})

View File

@@ -92,6 +92,10 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
base.Debug(cSess.IpAddr, cSess.MacHw, sess.Username, mobile)
// 检测密码套件
dtlsCiphersuite := checkDtls12Ciphersuite(r.Header.Get("X-Dtls12-Ciphersuite"))
base.Trace("dtlsCiphersuite", dtlsCiphersuite)
// 压缩
if cmpName, ok := cSess.SetPickCmp("cstp", r.Header.Get("X-Cstp-Accept-Encoding")); ok {
HttpSetHeader(w, "X-CSTP-Content-Encoding", cmpName)
@@ -164,7 +168,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
HttpSetHeader(w, "X-DTLS-Port", dtlsPort)
HttpSetHeader(w, "X-DTLS-DPD", fmt.Sprintf("%d", cstpDpd))
HttpSetHeader(w, "X-DTLS-Keepalive", fmt.Sprintf("%d", cstpKeepalive))
HttpSetHeader(w, "X-DTLS12-CipherSuite", "ECDHE-ECDSA-AES128-GCM-SHA256")
HttpSetHeader(w, "X-DTLS12-CipherSuite", dtlsCiphersuite)
HttpSetHeader(w, "X-CSTP-License", "accept")
HttpSetHeader(w, "X-CSTP-Routing-Filtering-Ignore", "false")
@@ -234,7 +238,11 @@ func SetPostAuthXml(g *dbdata.Group, w http.ResponseWriter) error {
if err != nil {
return err
}
HttpSetHeader(w, "X-CSTP-Post-Auth-XML", result.String())
xmlAuth := ""
for _, v := range strings.Split(result.String(), "\n") {
xmlAuth += strings.TrimSpace(v)
}
HttpSetHeader(w, "X-CSTP-Post-Auth-XML", xmlAuth)
return nil
}

View File

@@ -33,13 +33,14 @@ func checkMacvtap() {
ifName := "anylinkMacvtap"
// 加载 macvtap
cmdstr0 := fmt.Sprintln("modprobe -i macvtap")
base.CheckModOrLoad("macvtap")
// 开启主网卡混杂模式
cmdstr1 := fmt.Sprintf("ip link set dev %s promisc on", base.Cfg.Ipv4Master)
// 测试 macvtap 功能
cmdstr2 := fmt.Sprintf("ip link add link %s name %s type macvtap mode bridge", base.Cfg.Ipv4Master, ifName)
cmdstr3 := fmt.Sprintf("ip link del %s", ifName)
err := execCmd([]string{cmdstr0, cmdstr1, cmdstr2, cmdstr3})
err := execCmd([]string{cmdstr1, cmdstr2, cmdstr3})
if err != nil {
base.Fatal(err)
}
@@ -55,6 +56,10 @@ func LinkMacvtap(cSess *sessdata.ConnSession) error {
cmdstr1 := fmt.Sprintf("ip link add link %s name %s type macvtap mode bridge", base.Cfg.Ipv4Master, ifName)
cmdstr2 := fmt.Sprintf("ip link set dev %s up mtu %d address %s", ifName, cSess.Mtu, cSess.MacHw)
if !base.InContainer {
// 容器默认 iproute 不支持 alias
cmdstr2 += fmt.Sprintf(" alias %s.%s", cSess.Group.Name, cSess.Username)
}
err := execCmd([]string{cmdstr1, cmdstr2})
if err != nil {
base.Error(err)

View File

@@ -3,6 +3,7 @@ package handler
import (
"crypto/md5"
"encoding/binary"
"runtime/debug"
"time"
"github.com/bjdgyc/anylink/base"
@@ -101,11 +102,17 @@ func logAuditBatch() {
// 解析IP包的数据
func logAudit(userName string, pl *sessdata.Payload) {
defer putPayload(pl)
defer func() {
if err := recover(); err != nil {
base.Error("logAudit is panic: ", err, "\n", string(debug.Stack()), "\n", pl.Data)
}
putPayload(pl)
}()
if !(pl.LType == sessdata.LTypeIPData && pl.PType == 0x00) {
return
}
ipProto := waterutil.IPv4Protocol(pl.Data)
// 访问协议
var accessProto uint8
@@ -118,11 +125,15 @@ func logAudit(userName string, pl *sessdata.Payload) {
default:
return
}
// IP报文只包含头部信息时, 则打印LOG并退出
ipPl := waterutil.IPv4Payload(pl.Data)
if len(ipPl) < 4 {
base.Error("ipPl len < 4", ipPl, pl.Data)
return
}
ipPort := (uint16(ipPl[2]) << 8) | uint16(ipPl[3])
ipSrc := waterutil.IPv4Source(pl.Data)
ipDst := waterutil.IPv4Destination(pl.Data)
ipPort := waterutil.IPv4DestinationPort(pl.Data)
b := getByte51()
key := *b
copy(key[:16], ipSrc)
@@ -178,7 +189,6 @@ func logAudit(userName string, pl *sessdata.Payload) {
AccessProto: accessProto,
Info: info,
}
select {
case logBatch.LogChan <- audit:
default:

View File

@@ -29,7 +29,7 @@ func onTCP(payload []byte) (uint8, string) {
}
func sniNewParser(b []byte) (uint8, string) {
if len(b) < 2 || b[0] != 0x16 || b[1] != 0x03 {
if len(b) < 6 || b[0] != 0x16 || b[1] != 0x03 {
return acc_proto_tcp, ""
}
rest := b[5:]

View File

@@ -12,6 +12,7 @@ import (
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/gorilla/mux"
"github.com/pires/go-proxyproto"
)
@@ -53,7 +54,6 @@ func startTls() {
base.Trace("GetCertificate", chi.ServerName)
return dbdata.GetCertificateBySNI(chi.ServerName)
},
// InsecureSkipVerify: true,
}
srv := &http.Server{
Addr: addr,
@@ -86,6 +86,14 @@ func startTls() {
func initRoute() http.Handler {
r := mux.NewRouter()
// 所有路由添加安全头
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
utils.SetSecureHeader(w)
next.ServeHTTP(w, req)
})
})
r.HandleFunc("/", LinkHome).Methods(http.MethodGet)
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)

View File

@@ -0,0 +1,32 @@
package utils
import "net/http"
// SetSecureHeader 设置安全的header头
// https://blog.csdn.net/liwan09/article/details/130248003
// https://zhuanlan.zhihu.com/p/335165168
func SetSecureHeader(w http.ResponseWriter) {
// Content-Length Date 默认已经存在
w.Header().Set("Server", "AnyLinkOpenSource")
// w.Header().Set("Content-Type", "text/html; charset=utf-8")
// w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("X-Aggregate-Auth", "1")
w.Header().Set("Cache-Control", "no-store,no-cache")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Download-Options", "noopen")
w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; frame-ancestors 'self'; base-uri 'self'; block-all-mixed-content")
w.Header().Set("X-Permitted-Cross-Domain-Policies", "none")
w.Header().Set("Referrer-Policy", "same-origin")
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
w.Header().Set("X-XSS-Protection", "1;mode=block")
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
// w.Header().Set("Clear-Site-Data", "cache,cookies,storage")
}

View File

@@ -11,12 +11,14 @@ Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml
# systemctl --version
# systemd older than v236
# ExecStart=/bin/bash -c 'exec /usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml >> /usr/local/anylink-deploy/log/anylink.log 2>&1'
StandardOutput=file:/usr/local/anylink-deploy/log/anylink.log
StandardError=file:/usr/local/anylink-deploy/log/anylink.log
# systemd new than v236
# StandardOutput=file:/usr/local/anylink-deploy/log/anylink.log
# StandardError=file:/usr/local/anylink-deploy/log/anylink.log
[Install]
WantedBy=multi-user.target

View File

@@ -281,12 +281,17 @@
<el-tab-pane label="路由设置" name="route">
<el-form-item label="包含路由" prop="route_include">
<el-row class="msg-info">
<el-col :span="20">输入CIDR格式如: 192.168.1.0/24</el-col>
<el-col :span="4">
<el-col :span="18">输入CIDR格式如: 192.168.1.0/24</el-col>
<el-col :span="2">
<el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.route_include)"></el-button>
</el-col>
<el-col :span="4">
<el-button size="mini" type="info" icon="el-icon-edit" circle
@click.prevent="openIpListDialog('route_include')"></el-button>
</el-col>
</el-row>
<templete v-if="activeTab == 'route'">
<el-row v-for="(item,index) in ruleForm.route_include"
:key="index" style="margin-bottom: 5px" :gutter="10">
<el-col :span="10">
@@ -300,16 +305,22 @@
@click.prevent="removeDomain(ruleForm.route_include,index)"></el-button>
</el-col>
</el-row>
</templete>
</el-form-item>
<el-form-item label="排除路由" prop="route_exclude">
<el-row class="msg-info">
<el-col :span="20">输入CIDR格式如: 192.168.2.0/24</el-col>
<el-col :span="4">
<el-col :span="18">输入CIDR格式如: 192.168.2.0/24</el-col>
<el-col :span="2">
<el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
</el-col>
<el-col :span="4">
<el-button size="mini" type="info" icon="el-icon-edit" circle
@click.prevent="openIpListDialog('route_exclude')"></el-button>
</el-col>
</el-row>
<templete v-if="activeTab == 'route'">
<el-row v-for="(item,index) in ruleForm.route_exclude"
:key="index" style="margin-bottom: 5px" :gutter="10">
<el-col :span="10">
@@ -323,6 +334,7 @@
@click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button>
</el-col>
</el-row>
</templete>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="权限控制" name="link_acl">
@@ -365,6 +377,7 @@
</el-form-item>
<el-form-item label="排除域名" prop="ds_exclude_domains">
<el-input type="textarea" :rows="5" v-model="ruleForm.ds_exclude_domains" placeholder="输入域名用,号分隔,默认匹配所有子域名, 如baidu.com,163.com"></el-input>
<div class="msg-info">域名拆分隧道仅支持AnyConnect的桌面客户端不支持移动端.</div>
</el-form-item>
</el-tab-pane>
<el-form-item>
@@ -398,6 +411,25 @@
</el-form-item>
</el-form>
</el-dialog>
<!--编辑模式弹窗-->
<el-dialog
:close-on-click-modal="false"
title="编辑模式"
:visible.sync="ipListDialog"
width="650px"
custom-class="valgin-dialog"
center>
<el-form ref="ipEditForm" label-width="80px">
<el-form-item label="路由表" prop="ip_list">
<el-input type="textarea" :rows="10" v-model="ipEditForm.ip_list" placeholder="每行一条路由192.168.1.0/24,备注 或 192.168.1.0/24"></el-input>
<div class="msg-info">当前共 {{ ipEditForm.ip_list.trim() === '' ? 0 : ipEditForm.ip_list.trim().split("\n").length }} AnyConnect客户端最多支持{{ this.maxRouteRows }}条路由</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="ipEdit()" :loading="ipEditLoading">更新</el-button>
<el-button @click="ipListDialog = false"> </el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
@@ -424,6 +456,7 @@ export default {
activeTab : "general",
readMore: {},
readMinRows : 5,
maxRouteRows : 2500,
defAuth : {
type:'local',
radius:{addr:"", secret:""},
@@ -450,11 +483,17 @@ export default {
auth : {},
},
authLoginDialog : false,
ipListDialog : false,
authLoginLoading : false,
authLoginForm : {
name : "",
pwd : "",
},
ipEditForm: {
ip_list: "",
type : "",
},
ipEditLoading : false,
authLoginRules: {
name: [
{required: true, message: '请输入账号', trigger: 'blur'},
@@ -644,6 +683,70 @@ export default {
});
});
},
openIpListDialog(type) {
this.ipListDialog = true;
this.ipEditForm.type = type;
this.ipEditForm.ip_list = this.ruleForm[type].map(item => item.val + (item.note ? "," + item.note : "")).join("\n");
},
ipEdit() {
this.ipEditLoading = true;
let ipList = [];
if (this.ipEditForm.ip_list.trim() !== "") {
ipList = this.ipEditForm.ip_list.trim().split("\n");
}
let arr = [];
for (let i = 0; i < ipList.length; i++) {
let item = ipList[i];
if (item.trim() === "") {
continue;
}
let ip = item.split(",");
if (ip.length > 2) {
ip[1] = ip.slice(1).join(",");
}
let note = ip[1] ? ip[1] : "";
const pushToArr = () => {
arr.push({val: ip[0], note: note});
};
if (this.ipEditForm.type == "route_include" && ip[0] == "all") {
pushToArr();
continue;
}
let valid = this.isValidCIDR(ip[0]);
if (!valid.valid) {
this.$message.error("错误CIDR格式错误建议 " + ip[0] + " 改为 " + valid.suggestion);
this.ipEditLoading = false;
return;
}
pushToArr();
}
this.ruleForm[this.ipEditForm.type] = arr;
this.ipEditLoading = false;
this.ipListDialog = false;
},
isValidCIDR(input) {
const cidrRegex = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)\/([12]?\d|3[0-2])$/;
if (!cidrRegex.test(input)) {
return { valid: false, suggestion: null };
}
const [ip, mask] = input.split('/');
const maskNum = parseInt(mask);
const ipParts = ip.split('.').map(part => parseInt(part));
const binaryIP = ipParts.map(part => part.toString(2).padStart(8, '0')).join('');
for (let i = maskNum; i < 32; i++) {
if (binaryIP[i] === '1') {
const binaryNetworkPart = binaryIP.substring(0, maskNum).padEnd(32, '0');
const networkIPParts = [];
for (let j = 0; j < 4; j++) {
const octet = binaryNetworkPart.substring(j * 8, (j + 1) * 8);
networkIPParts.push(parseInt(octet, 2));
}
const suggestedIP = networkIPParts.join('.');
return { valid: false, suggestion: `${suggestedIP}/${mask}` };
}
}
return { valid: true, suggestion: null };
},
resetForm(formName) {
this.$refs[formName].resetFields();
},

View File

@@ -248,11 +248,14 @@
<el-form-item label="自定义首页" prop="homeindex">
<el-input
type="textarea"
:rows="5"
:rows="10"
placeholder="请输入内容"
v-model="dataOther.homeindex"
>
</el-input>
<el-tooltip content="自定义内容可以参考 home 目录下的文件" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</el-form-item>
<el-form-item label="账户开通邮件" prop="account_mail">