102 Commits

Author SHA1 Message Date
bjdgyc
ca4626879d Merge pull request #57 from bjdgyc/dev
修复前端bug
2021-08-26 15:43:02 +08:00
bjdgyc
a0065ade42 修改审计参数 2021-08-26 15:33:56 +08:00
bjdgyc
a47cdc9a00 修改说明文档 2021-08-26 14:52:55 +08:00
bjdgyc
c07914d568 移动files目录 2021-08-26 14:21:45 +08:00
bjdgyc
df94afdfd0 移动files目录 2021-08-26 14:16:46 +08:00
bjdgyc
b4f88e154c 修复前端不能删除的bug 2021-08-24 20:08:12 +08:00
bjdgyc
e4270f724b 修复Audit错误 2021-08-24 18:55:17 +08:00
bjdgyc
e9a27fd833 Merge pull request #56 from bjdgyc/dev
Dev
2021-08-23 11:17:45 +08:00
bjdgyc
9865d54f5c 修改打赏列表 2021-08-23 10:41:25 +08:00
bjdgyc
8453052d80 修改打赏列表 2021-08-23 10:35:01 +08:00
bjdgyc
e0d5638c17 Merge pull request #55 from bjdgyc/dev
Dev
2021-08-22 19:37:10 +08:00
bjdgyc
44adbe71ed Update user.go 2021-08-22 19:36:30 +08:00
bjdgyc
246efe430d 添加打赏列表 2021-08-20 19:20:04 +08:00
bjdgyc
80ca45c6ea 修复env问题 2021-08-20 19:16:54 +08:00
bjdgyc
dfe61667cb 修复env问题 2021-08-20 19:13:26 +08:00
bjdgyc
d9a3b0152b 添加审计日志界面 2021-08-17 13:14:13 +08:00
bjdgyc
6dcdc9766a 更新说明文档 2021-08-13 17:09:03 +08:00
bjdgyc
a65d3d1054 Merge pull request #54 from bjdgyc/dev
添加macvtap模式支持
2021-08-13 17:07:56 +08:00
bjdgyc
831b6786c5 更新说明文档 2021-08-13 16:37:16 +08:00
bjdgyc
b4e2550911 更新说明文档 2021-08-13 16:34:21 +08:00
bjdgyc
60019f3cf4 更新说明文档 2021-08-13 16:24:51 +08:00
bjdgyc
d4f1793675 添加打赏信息 2021-08-13 15:20:01 +08:00
bjdgyc
b2c171c39c 修改文档路径 2021-08-13 14:10:26 +08:00
bjdgyc
35dd1dc4e1 修改文档路径 2021-08-13 13:54:43 +08:00
bjdgyc
bc64a9f8f6 修复安全问题 2021-08-13 13:38:06 +08:00
bjdgyc
65463fee6c 增加、默认路由 2021-08-13 13:19:26 +08:00
bjdgyc
8187fb548f 增加默认路由 2021-08-13 11:33:53 +08:00
bjdgyc
5bd55d1fa8 增加 macvtap 说明文档 2021-08-13 10:22:49 +08:00
bjdgyc
42de4e6fd0 增加 macvtap 说明文档 2021-08-12 22:35:13 +08:00
bjdgyc
970b7d557a 增加 macvtap 说明文档 2021-08-12 22:33:50 +08:00
bjdgyc
58cdcbe192 增加 macvtap 模式支持 2021-08-12 18:34:09 +08:00
bjdgyc
0ffa9578cf Merge branch 'main' into dev 2021-08-12 18:30:56 +08:00
bjdgyc
903554533b 增加 macvtap 模式支持 2021-08-12 18:17:20 +08:00
bjdgyc
5010d2ecbd 修改bridge-init.sh 2021-08-10 18:39:40 +08:00
bjdgyc
cc99a936a6 修改bridge-init.sh 2021-08-09 17:21:33 +08:00
bjdgyc
fd54783d48 Merge pull request #53 from bjdgyc/dependabot/npm_and_yarn/web/axios-0.21.1
Bump axios from 0.20.0 to 0.21.1 in /web
2021-08-06 15:27:46 +08:00
bjdgyc
af9845a012 Create codeql-analysis.yml 2021-08-06 15:25:35 +08:00
dependabot[bot]
d663c43f6d Bump axios from 0.20.0 to 0.21.1 in /web
Bumps [axios](https://github.com/axios/axios) from 0.20.0 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.20.0...v0.21.1)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-06 07:24:15 +00:00
bjdgyc
b9b852123e 添加ip审计功能 2021-08-05 18:22:33 +08:00
bjdgyc
0d4d2bb3c4 添加ip审计功能 2021-08-05 18:20:13 +08:00
bjdgyc
1bb76e5d60 fix 2021-08-04 17:33:02 +08:00
bjdgyc
bf898ff34b Merge pull request #52 from bjdgyc/dev
Dev
2021-08-03 11:03:52 +08:00
bjdgyc
0a35ee18f4 fix 2021-08-03 10:59:58 +08:00
bjdgyc
2e764f7ee7 添加捐助信息 2021-08-03 10:48:39 +08:00
bjdgyc
d91d1a127e 添加捐助信息 2021-08-03 10:43:29 +08:00
bjdgyc
fef25da35c 添加捐助信息 2021-08-03 10:35:16 +08:00
bjdgyc
f3d6d23c3e 添加捐助信息 2021-08-03 10:18:37 +08:00
bjdgyc
88c1d09c8f Merge pull request #51 from bjdgyc/dev
Dev
2021-08-02 14:21:13 +08:00
bjdgyc
12febf3723 修复打包问题 2021-08-02 14:15:37 +08:00
bjdgyc
c77a765ae9 修复打包问题 2021-08-02 13:25:22 +08:00
bjdgyc
6daf4e1b03 Merge pull request #50 from bjdgyc/dev
更换为sql数据库
2021-08-02 11:42:49 +08:00
bjdgyc
2a66df55b0 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	server/handler/pool_test.go
2021-08-01 21:00:07 +08:00
bjdgyc
0f783cfaf6 添加测试文件 2021-08-01 20:58:57 +08:00
bjdgyc
afe447ada7 优化payload 2021-07-30 16:24:23 +08:00
bjdgyc
981f39799a 优化payload 2021-07-30 10:59:45 +08:00
bjdgyc
60084d499a 优化payload 2021-07-30 10:53:43 +08:00
bjdgyc
583ca4d635 优化payload 2021-07-29 19:02:55 +08:00
bjdgyc
3937d1eb65 修改组策略样式 2021-07-29 17:48:23 +08:00
bjdgyc
c943b9ee9b 添加env环境变量展示 2021-07-29 17:04:02 +08:00
bjdgyc
d78deafc0c 添加国内源 2021-07-27 12:56:26 +08:00
bjdgyc
2bdaf4a52e 修改说明文档 2021-07-23 19:27:12 +08:00
bjdgyc
d065a1f97f Merge branch 'main' into dev 2021-07-23 19:24:38 +08:00
bjdgyc
ba446b8a5c 修复pprof跳转问题 2021-07-23 18:33:12 +08:00
bjdgyc
73467a39d9 Merge pull request #46 from 0x0021/patch-1
Docker部分文档内容更新
2021-07-23 17:25:24 +08:00
bjdgyc
96276b8cac Update README.md 2021-07-23 17:24:49 +08:00
坤子
d17271da2e Docker部分文档内容更新
在 RadeME 增加了  Docker中配置文件变量映射的说明和示例
2021-07-23 17:03:40 +08:00
bjdgyc
dd16d52c95 Merge pull request #45 from bjdgyc/dev
修改为sql数据库
2021-07-22 19:34:08 +08:00
bjdgyc
eec3006b35 修复freeotp扫码问题 2021-07-22 19:25:05 +08:00
bjdgyc
35c6d80c8d 修复配置文件默认值 2021-07-21 16:46:34 +08:00
bjdgyc
88a3d35784 修改表结构 2021-07-21 13:42:32 +08:00
bjdgyc
712f57940c 优化pool 2021-07-20 18:59:09 +08:00
bjdgyc
2ad65039f3 修改数据表字段类型 2021-07-20 18:44:13 +08:00
bjdgyc
1940fcca87 修改数据库表定义 2021-07-19 16:29:26 +08:00
bjdgyc
e4f959cb69 修改数据库表 2021-07-19 15:15:29 +08:00
bjdgyc
8ff77626d0 优化byte内存池 2021-07-19 12:30:20 +08:00
bjdgyc
ea4dda0fca 修复没有ipv6报错的问题 2021-07-17 15:23:01 +08:00
bjdgyc
5ffea2339e 修复没有ipv6报错的问题 2021-07-16 18:12:08 +08:00
bjdgyc
a8038f8fe9 修复没有ipv6报错的问题 2021-07-16 18:10:13 +08:00
bjdgyc
e7ef29c4ad 修改为sql数据库 2021-07-16 11:25:06 +08:00
bjdgyc
884f41d2f8 修改为sql数据库 2021-07-15 18:17:37 +08:00
bjdgyc
7e95b1261a Merge pull request #43 from bjdgyc/dev
Dev
2021-07-06 09:35:13 +08:00
bjdgyc
6daf9cbfa3 修复ip限制时,ping不通的问题 2021-07-05 18:36:46 +08:00
bjdgyc
31a5337ddf 修改配置文件说明 2021-07-05 18:12:05 +08:00
bjdgyc
5bb44385b1 Merge pull request #41 from bjdgyc/dev
修改配置文件目录到 conf 下
2021-07-05 17:44:59 +08:00
bjdgyc
66ef639956 修改配置文件目录到 conf 下 2021-07-05 13:35:42 +08:00
bjdgyc
f6fd01d1e5 修改配置文件目录到 conf 下 2021-07-05 13:26:17 +08:00
bjdgyc
f50f00f464 Merge pull request #37 from bjdgyc/dev
修改版本号
2021-06-29 20:01:52 +08:00
bjdgyc
1b4fe6e450 修改版本号 2021-06-29 20:01:09 +08:00
bjdgyc
5289aa92eb Merge pull request #36 from bjdgyc/dev
Dev
2021-06-29 19:41:08 +08:00
bjdgyc
d9af1254a4 添加 question 问题 2021-06-27 07:40:00 +08:00
bjdgyc
94dfb8bc44 添加 question 问题 2021-06-27 07:23:09 +08:00
bjdgyc
3243cb98ad 添加 question 问题 2021-06-27 07:21:29 +08:00
bjdgyc
67d44805ce 修复ip重复分配问题 2021-06-26 23:33:15 +08:00
bjdgyc
22b42fa8b6 Merge pull request #34 from bjdgyc/dev
Dev
2021-06-22 15:44:50 +08:00
bjdgyc
b0ff8ba1be 更新build脚本 2021-06-22 15:42:43 +08:00
bjdgyc
1611f795d1 修改readme信息 2021-06-22 14:41:00 +08:00
bjdgyc
440b178232 修复action错误 2021-06-18 19:25:34 +08:00
bjdgyc
42c898cfea 更新go版本为1.16 2021-06-18 19:20:47 +08:00
bjdgyc
1564f6e56c 更新go版本为1.16 2021-06-18 19:20:15 +08:00
bjdgyc
993cd40c41 更新程序为单二进制文件
支持远程桌面连接
添加后台显示版本号
支持邮箱设置加密类型
2021-06-18 19:04:16 +08:00
bjdgyc
0ef18ee2f9 更新程序为单文件 2021-06-17 16:58:38 +08:00
bjdgyc
a616e42432 删除文件不存在的报错信息 2021-06-09 16:41:15 +08:00
87 changed files with 10574 additions and 7572 deletions

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ dev ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ dev ]
schedule:
- cron: '32 12 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go', 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# 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
# 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
# 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
# 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
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Go 1.x - name: Set up Go 1.x
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.15 go-version: 1.16
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
@@ -30,7 +30,9 @@ jobs:
- name: Build - name: Build
run: | run: |
cd server cd server
go build -v -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`" mkdir ui
touch ui/index.html
go build -v -o anylink -ldflags "-X main.CommitId=`git rev-parse HEAD`"
./anylink tool -v ./anylink tool -v
- name: Test coverage - name: Test coverage

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# Binaries for programs and plugins # Binaries for programs and plugins
.idea/ .idea/
anylink-deploy anylink-deploy
ui anylink-deploy.tar.gz

View File

@@ -2,24 +2,23 @@
FROM node:lts-alpine as builder_node FROM node:lts-alpine as builder_node
WORKDIR /web WORKDIR /web
COPY ./web /web COPY ./web /web
RUN npx browserslist@latest --update-db \ RUN npm install --registry=https://registry.npm.taobao.org \
&& npm install \
&& npm run build \ && npm run build \
&& ls /web/ui && ls /web/ui
# server # server
FROM golang:alpine as builder_golang FROM golang:1.16-alpine as builder_golang
#TODO 本地打包时使用镜像 #TODO 本地打包时使用镜像
#ENV GOPROXY=https://goproxy.io ENV GOPROXY=https://goproxy.io
ENV GOOS=linux ENV GOOS=linux
WORKDIR /anylink WORKDIR /anylink
COPY . /anylink COPY . /anylink
COPY --from=builder_node /web/ui /anylink/server/ui COPY --from=builder_node /web/ui /anylink/server/ui
#TODO 本地打包时使用镜像 #TODO 本地打包时使用镜像
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache git RUN apk add --no-cache git gcc musl-dev
RUN cd /anylink/server;go build -o anylink -ldflags "-X main.COMMIT_ID=$(git rev-parse HEAD)" \ RUN cd /anylink/server;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
&& /anylink/server/anylink tool -v && /anylink/server/anylink tool -v
# anylink # anylink
@@ -29,14 +28,16 @@ LABEL maintainer="github.com/bjdgyc"
ENV IPV4_CIDR="192.168.10.0/24" ENV IPV4_CIDR="192.168.10.0/24"
WORKDIR /app WORKDIR /app
COPY --from=builder_node /web/ui /app/ui
COPY --from=builder_golang /anylink/server/anylink /app/ COPY --from=builder_golang /anylink/server/anylink /app/
COPY ./server/conf /app/conf
COPY ./server/files /app/files
COPY docker_entrypoint.sh /app/ COPY docker_entrypoint.sh /app/
COPY ./server/bridge-init.sh /app/
COPY ./server/conf /app/conf
#COPY ./server/files /app/conf/files
#TODO 本地打包时使用镜像 #TODO 本地打包时使用镜像
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache bash iptables \ RUN apk add --no-cache bash iptables \
&& chmod +x /app/docker_entrypoint.sh \ && chmod +x /app/docker_entrypoint.sh \
&& ls /app && ls /app

127
README.md
View File

@@ -27,7 +27,7 @@ AnyLink 服务端仅在CentOS 7、Ubuntu 18.04测试通过,如需要安装在
## Screenshot ## Screenshot
![online](screenshot/online.jpg) ![online](doc/screenshot/online.jpg)
## Installation ## Installation
@@ -35,9 +35,11 @@ AnyLink 服务端仅在CentOS 7、Ubuntu 18.04测试通过,如需要安装在
> >
> https://github.com/bjdgyc/anylink/releases > https://github.com/bjdgyc/anylink/releases
> 升级 go version = 1.15 ### 自行编译安装
> 需要提前安装好 golang >= 1.16 和 nodejs >= 14.x
> >
> 需要提前安装好 golang 和 nodejs > 使用客户端前,必须申请安全的 https 证书,不支持私有证书连接
```shell ```shell
git clone https://github.com/bjdgyc/anylink.git git clone https://github.com/bjdgyc/anylink.git
@@ -47,7 +49,7 @@ sh build.sh
# 注意使用root权限运行 # 注意使用root权限运行
cd anylink-deploy cd anylink-deploy
sudo ./anylink --conf="conf/server.toml" sudo ./anylink
# 默认管理后台访问地址 # 默认管理后台访问地址
# http://host:8800 # http://host:8800
@@ -64,18 +66,21 @@ sudo ./anylink --conf="conf/server.toml"
- [x] 兼容 AnyConnect - [x] 兼容 AnyConnect
- [x] 基于 tun 设备的 nat 访问模式 - [x] 基于 tun 设备的 nat 访问模式
- [x] 基于 tap 设备的桥接访问模式 - [x] 基于 tap 设备的桥接访问模式
- [x] 基于 macvtap 设备的桥接访问模式
- [x] 支持 [proxy protocol v1](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议 - [x] 支持 [proxy protocol v1](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
- [x] 用户组支持 - [x] 用户组支持
- [x] 多用户支持 - [x] 多用户支持
- [x] TOTP 令牌支持 - [x] TOTP 令牌支持
- [x] TOTP 令牌开关 - [x] TOTP 令牌开关
- [x] 流量 - [x] 流量速率限
- [x] 后台管理界面 - [x] 后台管理界面
- [x] 访问权限管理 - [x] 访问权限管理
- [x] IP 访问审计功能
- [ ] 基于 ipvtap 设备的桥接访问模式
## Config ## Config
默认配置文件内有详细的注释,根据注释填写配置即可。 > 示例配置文件内有详细的注释,根据注释填写配置即可。
```shell ```shell
# 生成后台密码 # 生成后台密码
@@ -85,15 +90,25 @@ sudo ./anylink --conf="conf/server.toml"
./anylink tool -s ./anylink tool -s
``` ```
[conf/server.toml](server/conf/server.toml) > 数据库配置示例
| 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 |
> 示例配置文件
>
> [conf/server-sample.toml](server/conf/server-sample.toml)
## Setting ## Setting
> 以下参数必须设置其中之一 > 以下参数必须设置其中之一
网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="tap"` 两种参数。 不同的参数需要对服务器做相应的设置。 网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="macvtap"`,`link_mode="tap"` 参数。 不同的参数需要对服务器做相应的设置。
建议优先选择tun模式因客户端传输的是IP层数据无须进行数据转换。 tap模式是在用户态做的链路层到IP层的数据互相转换性能会有所下降。 如果需要在虚拟机内开启tap模式请确认虚拟机的网卡开启混杂模式。 建议优先选择 tun 模式,其次选择 macvtap 模式,因客户端传输的是 IP 层数据,无须进行数据转换。 tap 模式是在用户态做的链路层到 IP 层的数据互相转换,性能会有所下降。 如果需要在虚拟机内开启 tap 模式,请确认虚拟机的网卡开启混杂模式。
### tun 设置 ### tun 设置
@@ -110,6 +125,9 @@ sudo ./anylink --conf="conf/server.toml"
2. 设置 nat 转发规则 2. 设置 nat 转发规则
```shell ```shell
systemctl stop firewalld.service
systemctl disable firewalld.service
# 请根据服务器内网网卡替换 eth0 # 请根据服务器内网网卡替换 eth0
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
# 如果执行第一个命令不生效,可以继续执行下面的命令 # 如果执行第一个命令不生效,可以继续执行下面的命令
@@ -120,6 +138,23 @@ iptables -nL -t nat
3. 使用 AnyConnect 客户端连接即可 3. 使用 AnyConnect 客户端连接即可
### macvtap 设置
1. 设置配置文件
> macvtap 设置相对比较简单,只需要配置相应的参数即可。
> 以下参数可以通过执行 `ip a` 查看
```
#内网主网卡名称
ipv4_master = "eth0"
#以下网段需要跟ipv4_master网卡设置成一样
ipv4_cidr = "192.168.10.0/24"
ipv4_gateway = "192.168.10.1"
ipv4_start = "192.168.10.100"
ipv4_end = "192.168.10.200"
```
### tap 设置 ### tap 设置
1. 创建桥接网卡 1. 创建桥接网卡
@@ -130,12 +165,13 @@ iptables -nL -t nat
2. 修改 bridge-init.sh 内的参数 2. 修改 bridge-init.sh 内的参数
> 以下参数可以通过执行 `ip a` 查看
``` ```
eth="eth0" eth="eth0"
eth_ip="192.168.1.4" eth_ip="192.168.10.4/24"
eth_netmask="255.255.255.0" eth_broadcast="192.168.10.255"
eth_broadcast="192.168.1.255" eth_gateway="192.168.10.1"
eth_gateway="192.168.1.1"
``` ```
3. 执行 bridge-init.sh 文件 3. 执行 bridge-init.sh 文件
@@ -146,20 +182,20 @@ sh bridge-init.sh
## Systemd ## Systemd
添加 systemd脚本 1. 添加 anylink 程序
* anylink 程序目录放入 `/usr/local/anylink-deploy` - anylink 程序目录放入 `/usr/local/anylink-deploy`
systemd 脚本放入: 2. systemd/anylink.service 脚本放入:
* centos: `/usr/lib/systemd/system/` - centos: `/usr/lib/systemd/system/`
* ubuntu: `/lib/systemd/system/` - ubuntu: `/lib/systemd/system/`
操作命令: 3. 操作命令:
* 启动: `systemctl start anylink` - 启动: `systemctl start anylink`
* 停止: `systemctl stop anylink` - 停止: `systemctl stop anylink`
* 开机自启: `systemctl enable anylink` - 开机自启: `systemctl enable anylink`
## Docker ## Docker
@@ -169,21 +205,27 @@ systemd 脚本放入:
docker pull bjdgyc/anylink:latest docker pull bjdgyc/anylink:latest
``` ```
2. 生成密码 2. 查看命令信息
```bash
docker run -it --rm bjdgyc/anylink -h
```
3. 生成密码
```bash ```bash
docker run -it --rm bjdgyc/anylink tool -p 123456 docker run -it --rm bjdgyc/anylink tool -p 123456
#Passwd:$2a$10$lCWTCcGmQdE/4Kb1wabbLelu4vY/cUwBwN64xIzvXcihFgRzUvH2a #Passwd:$2a$10$lCWTCcGmQdE/4Kb1wabbLelu4vY/cUwBwN64xIzvXcihFgRzUvH2a
``` ```
3. 生成jwt secret 4. 生成 jwt secret
```bash ```bash
docker run -it --rm bjdgyc/anylink tool -s docker run -it --rm bjdgyc/anylink tool -s
#Secret:9qXoIhY01jqhWIeIluGliOS4O_rhcXGGGu422uRZ1JjZxIZmh17WwzW36woEbA #Secret:9qXoIhY01jqhWIeIluGliOS4O_rhcXGGGu422uRZ1JjZxIZmh17WwzW36woEbA
``` ```
4. 启动容器 5. 启动容器
```bash ```bash
docker run -itd --name anylink --privileged \ docker run -itd --name anylink --privileged \
@@ -192,18 +234,19 @@ systemd 脚本放入:
bjdgyc/anylink bjdgyc/anylink
``` ```
5. 使用自定义参数启动容器 6. 使用自定义参数启动容器
```bash ```bash
# 参数可以参考 -h 命令
docker run -itd --name anylink --privileged \ docker run -itd --name anylink --privileged \
-e IPV4_CIDR=192.168.10.0/24 \ -e IPV4_CIDR=192.168.10.0/24 \
-p 443:443 -p 8800:8800 \ -p 443:443 -p 8800:8800 \
--restart=always \ --restart=always \
bjdgyc/anylink \ bjdgyc/anylink \
-c=/etc/server.toml --admin_addr=:8080 -c=/etc/server.toml --ip_lease = 1209600 \ # IP地址租约时长
``` ```
6. 构建镜像 7. 构建镜像
```bash ```bash
#获取仓库源码 #获取仓库源码
@@ -216,9 +259,17 @@ systemd 脚本放入:
请前往 [问题地址](question.md) 查看具体信息 请前往 [问题地址](question.md) 查看具体信息
## Discussion ## Donate
![qq.png](screenshot/qq.png) > 如果您觉得 anylink 对你有帮助,欢迎给我们打赏,也是帮助 anylink 更好的发展。
>
> [查看打赏列表](doc/README.md)
<p>
<img src="doc/screenshot/wxpay2.png" width="400" />
</p>
## Discussion
添加 QQ 群: 567510628 添加 QQ 群: 567510628
@@ -235,11 +286,11 @@ QQ群共享文件有相关软件下载
<details> <details>
<summary>展开查看</summary> <summary>展开查看</summary>
![system.jpg](screenshot/system.jpg) ![system.jpg](doc/screenshot/system.jpg)
![setting.jpg](screenshot/setting.jpg) ![setting.jpg](doc/screenshot/setting.jpg)
![users.jpg](screenshot/users.jpg) ![users.jpg](doc/screenshot/users.jpg)
![ip_map.jpg](screenshot/ip_map.jpg) ![ip_map.jpg](doc/screenshot/ip_map.jpg)
![group.jpg](screenshot/group.jpg) ![group.jpg](doc/screenshot/group.jpg)
</details> </details>
@@ -250,9 +301,5 @@ QQ群共享文件有相关软件下载
## Thank ## Thank
<a href="https://www.jetbrains.com"> <a href="https://www.jetbrains.com">
<img src="screenshot/jetbrains.png" width="200" height="200" alt="jetbrains.png" /> <img src="doc/screenshot/jetbrains.png" width="200" alt="jetbrains.png" />
</a> </a>

View File

@@ -12,35 +12,38 @@ function RETVAL() {
#当前目录 #当前目录
cpath=$(pwd) cpath=$(pwd)
echo "编译二进制文件"
cd $cpath/server
go build -v -o anylink -ldflags "-X main.COMMIT_ID=$(git rev-parse HEAD)"
RETVAL $?
echo "编译前端项目" echo "编译前端项目"
cd $cpath/web cd $cpath/web
#国内可替换源加快速度 #国内可替换源加快速度
#npx browserslist@latest --update-db
npm install --registry=https://registry.npm.taobao.org npm install --registry=https://registry.npm.taobao.org
npm run build --registry=https://registry.npm.taobao.org
#npm install #npm install
#npm run build npm run build
RETVAL $?
echo "编译二进制文件"
cd $cpath/server
rm -rf ui
cp -rf $cpath/web/ui .
#国内可替换源加快速度
export GOPROXY=https://goproxy.io
go build -v -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)"
RETVAL $? RETVAL $?
cd $cpath cd $cpath
echo "整理部署文件" echo "整理部署文件"
deploy="anylink-deploy" deploy="anylink-deploy"
rm -rf $deploy rm -rf $deploy ${deploy}.tar.gz
mkdir $deploy mkdir $deploy
mkdir $deploy/log
cp -r server/anylink $deploy cp -r server/anylink $deploy
cp -r server/conf $deploy
cp -r server/files $deploy
cp -r server/bridge-init.sh $deploy cp -r server/bridge-init.sh $deploy
cp -r server/conf $deploy
cp -r systemd $deploy cp -r systemd $deploy
cp -r web/ui $deploy
tar zcvf ${deploy}.tar.gz $deploy
#注意使用root权限运行 #注意使用root权限运行
#cd anylink-deploy #cd anylink-deploy

17
doc/README.md Normal file
View File

@@ -0,0 +1,17 @@
## Donate
> 如果您觉得 AnyLink 对你有帮助,欢迎给我们打赏,也是帮助 AnyLink 更好的发展。
<p>
<img src="screenshot/wxpay2.png" width="500" />
</p>
## Donator
> 感谢以下同学的打赏AnyLink 有你更美好!
| 昵称 | 主页 |
| -------- | ---------------------------- |
| 代码oo8 | |
| 甘磊 | https://github.com/ganlei333 |
| Oo@ | https://github.com/chooop |

42
doc/question.md Normal file
View File

@@ -0,0 +1,42 @@
# 常见问题
### anyconnect 客户端问题
> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常
>
> 添加QQ群: 567510628
### OTP 动态码
> 请使用手机安装 freeotp 然后扫描otp二维码生成的数字即是动态码
### 远程桌面连接
> 本软件已经支持远程桌面里面连接anyconnect。
### 私有证书问题
> anylink 默认不支持私有证书
>
> 其他使用私有证书的问题,请自行解决
### dpd timeout 设置问题
```
#客户端失效检测时间(秒) dpd > keepalive
cstp_keepalive = 20
cstp_dpd = 30
mobile_keepalive = 40
mobile_dpd = 50
```
> 以上dpd参数为客户端的超时检测时间, 如一段时间内,没有数据传输,防火墙会主动关闭连接
>
> 如经常出现 timeout 的错误信息应根据当前防火墙的设置适当减小dpd数值
### 性能问题
```
内网环境测试数据
虚拟服务器: centos7 4C8G
anylink: tun模式 tcp传输
客户端文件下载速度240Mb/s
客户端网卡下载速度270Mb/s
服务端网卡上传速度280Mb/s
```
> 客户端tls加密协议、隧道header头都会占用一定带宽

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
doc/screenshot/wxpay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
doc/screenshot/wxpay2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

13
docker_build.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/env bash
ver="0.5.1"
#docker login -u bjdgyc
docker build -t bjdgyc/anylink .
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
docker push bjdgyc/anylink:$ver
docker push bjdgyc/anylink:latest

View File

@@ -16,7 +16,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 -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE
# iptables -nL -t nat iptables -nL -t nat
/app/anylink "$@" /app/anylink "$@"
;; ;;

View File

@@ -9,6 +9,7 @@ import (
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/hex" "encoding/hex"
"errors" "errors"
"math/big" "math/big"
@@ -70,6 +71,11 @@ func WithDNS(key crypto.PrivateKey, cn string, sans ...string) (tls.Certificate,
names = append(names, sans...) names = append(names, sans...)
template := x509.Certificate{ template := x509.Certificate{
Subject: pkix.Name{
// TODO anylink
Organization: []string{cn},
OrganizationalUnit: names,
},
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageServerAuth,

View File

@@ -1,15 +0,0 @@
# 常见问题
### anyconnect 客户端问题
> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常。
>
> 添加QQ群: 567510628
### 远程桌面连接
> 本软件不支持远程桌面连接,请注意。
### 私有证书问题
> anylink 默认不支持私有证书
>
> 仅测试的话,可以通过 https://github.com/square/certstrap 生成私有的证书, 然后把CA证书放在客户端机器上即可以连接。

View File

@@ -22,7 +22,7 @@ func GroupList(w http.ResponseWriter, r *http.Request) {
count := dbdata.CountAll(&dbdata.Group{}) count := dbdata.CountAll(&dbdata.Group{})
var datas []dbdata.Group var datas []dbdata.Group
err := dbdata.All(&datas, pageSize, page) err := dbdata.Find(&datas, pageSize, page)
if err != nil { if err != nil {
RespError(w, RespInternalErr, err) RespError(w, RespInternalErr, err)
return return

View File

@@ -5,7 +5,6 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"time"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
) )
@@ -23,7 +22,7 @@ func UserIpMapList(w http.ResponseWriter, r *http.Request) {
count := dbdata.CountAll(&dbdata.IpMap{}) count := dbdata.CountAll(&dbdata.IpMap{})
var datas []dbdata.IpMap var datas []dbdata.IpMap
err := dbdata.All(&datas, pageSize, page) err := dbdata.Find(&datas, pageSize, page)
if err != nil { if err != nil {
RespError(w, RespInternalErr, err) RespError(w, RespInternalErr, err)
return return
@@ -75,14 +74,7 @@ func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
// fmt.Println(v, len(v.Ip), len(v.MacAddr)) // fmt.Println(v, len(v.Ip), len(v.MacAddr))
if len(v.IpAddr) < 4 || len(v.MacAddr) < 6 { err = dbdata.SetIpMap(v)
RespError(w, RespParamErr, "IP或MAC错误")
return
}
v.UpdatedAt = time.Now()
err = dbdata.Save(v)
if err != nil { if err != nil {
RespError(w, RespInternalErr, err) RespError(w, RespInternalErr, err)
return return

View File

@@ -5,11 +5,10 @@ import (
"net/http" "net/http"
"runtime" "runtime"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils" "github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata"
"github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host" "github.com/shirou/gopsutil/host"
@@ -71,6 +70,7 @@ func SetSystem(w http.ResponseWriter, r *http.Request) {
"goArch": runtime.GOARCH, "goArch": runtime.GOARCH,
"goVersion": runtime.Version(), "goVersion": runtime.Version(),
"goroutine": runtime.NumGoroutine(), "goroutine": runtime.NumGoroutine(),
"appVersion": "v" + base.APP_VER,
"hostname": hi.Hostname, "hostname": hi.Hostname,
"platform": fmt.Sprintf("%v %v %v", hi.Platform, hi.PlatformFamily, hi.PlatformVersion), "platform": fmt.Sprintf("%v %v %v", hi.Platform, hi.PlatformFamily, hi.PlatformVersion),

View File

@@ -0,0 +1,33 @@
package admin
import (
"net/http"
"strconv"
"github.com/bjdgyc/anylink/dbdata"
)
func SetAuditList(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS)
if page < 1 {
page = 1
}
var datas []dbdata.AccessAudit
count := dbdata.CountAll(&dbdata.AccessAudit{})
err := dbdata.Find(&datas, dbdata.PageSize, page)
if err != nil && !dbdata.CheckErrNotFound(err) {
RespError(w, RespInternalErr, err)
return
}
data := map[string]interface{}{
"count": count,
"page_size": dbdata.PageSize,
"datas": datas,
}
RespSucess(w, data)
}

View File

@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@@ -36,11 +37,11 @@ func UserList(w http.ResponseWriter, r *http.Request) {
// 查询前缀匹配 // 查询前缀匹配
if len(prefix) > 0 { if len(prefix) > 0 {
count = pageSize count = dbdata.CountPrefix("username", prefix, &dbdata.User{})
err = dbdata.Prefix("Username", prefix, &datas, pageSize, 1) err = dbdata.Prefix("username", prefix, &datas, pageSize, 1)
} else { } else {
count = dbdata.CountAll(&dbdata.User{}) count = dbdata.CountAll(&dbdata.User{})
err = dbdata.All(&datas, pageSize, page) err = dbdata.Find(&datas, pageSize, page)
} }
if err != nil && !dbdata.CheckErrNotFound(err) { if err != nil && !dbdata.CheckErrNotFound(err) {
@@ -141,7 +142,7 @@ func UserOtpQr(w http.ResponseWriter, r *http.Request) {
return return
} }
issuer := base.Cfg.Issuer issuer := url.QueryEscape(base.Cfg.Issuer)
qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret) qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret)
qr, _ := qrcode.New(qrstr, qrcode.High) qr, _ := qrcode.New(qrstr, qrcode.High)

View File

@@ -7,7 +7,7 @@ import (
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
"github.com/dgrijalva/jwt-go" "github.com/golang-jwt/jwt/v4"
mail "github.com/xhit/go-simple-mail/v2" mail "github.com/xhit/go-simple-mail/v2"
// "github.com/mojocn/base64Captcha" // "github.com/mojocn/base64Captcha"
) )
@@ -59,8 +59,14 @@ func SendMail(subject, to, htmlBody string) error {
server.Port = dataSmtp.Port server.Port = dataSmtp.Port
server.Username = dataSmtp.Username server.Username = dataSmtp.Username
server.Password = dataSmtp.Password server.Password = dataSmtp.Password
if dataSmtp.UseSSl {
server.Encryption = mail.EncryptionSSL switch dataSmtp.Encryption {
case "SSLTLS":
server.Encryption = mail.EncryptionSSLTLS
case "STARTTLS":
server.Encryption = mail.EncryptionSTARTTLS
default:
server.Encryption = mail.EncryptionNone
} }
// Since v2.3.0 you can specified authentication type: // Since v2.3.0 you can specified authentication type:

View File

@@ -2,6 +2,7 @@
package admin package admin
import ( import (
"embed"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
@@ -9,7 +10,9 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
// 开启服务 var UiData embed.FS
// StartAdmin 开启服务
func StartAdmin() { func StartAdmin() {
r := mux.NewRouter() r := mux.NewRouter()
@@ -17,7 +20,8 @@ func StartAdmin() {
r.Handle("/", http.RedirectHandler("/ui/", http.StatusFound)).Name("index") r.Handle("/", http.RedirectHandler("/ui/", http.StatusFound)).Name("index")
r.PathPrefix("/ui/").Handler( r.PathPrefix("/ui/").Handler(
http.StripPrefix("/ui/", http.FileServer(http.Dir(base.Cfg.UiPath))), // http.StripPrefix("/ui/", http.FileServer(http.Dir(base.Cfg.UiPath))),
http.FileServer(http.FS(UiData)),
).Name("static") ).Name("static")
r.HandleFunc("/base/login", Login).Name("login") r.HandleFunc("/base/login", Login).Name("login")
@@ -28,6 +32,7 @@ func StartAdmin() {
r.HandleFunc("/set/other/edit", SetOtherEdit) r.HandleFunc("/set/other/edit", SetOtherEdit)
r.HandleFunc("/set/other/smtp", SetOtherSmtp) r.HandleFunc("/set/other/smtp", SetOtherSmtp)
r.HandleFunc("/set/other/smtp/edit", SetOtherSmtpEdit) r.HandleFunc("/set/other/smtp/edit", SetOtherSmtpEdit)
r.HandleFunc("/set/audit/list", SetAuditList)
r.HandleFunc("/user/list", UserList) r.HandleFunc("/user/list", UserList)
r.HandleFunc("/user/detail", UserDetail) r.HandleFunc("/user/detail", UserDetail)
@@ -54,7 +59,7 @@ func StartAdmin() {
r.HandleFunc("/debug/pprof/profile", pprof.Profile).Name("debug") r.HandleFunc("/debug/pprof/profile", pprof.Profile).Name("debug")
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol).Name("debug") r.HandleFunc("/debug/pprof/symbol", pprof.Symbol).Name("debug")
r.HandleFunc("/debug/pprof/trace", pprof.Trace).Name("debug") r.HandleFunc("/debug/pprof/trace", pprof.Trace).Name("debug")
r.HandleFunc("/debug/pprof", location("/debug/pprof/")) r.HandleFunc("/debug/pprof", location("/debug/pprof/")).Name("debug")
r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index).Name("debug") r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index).Name("debug")
} }

View File

@@ -2,6 +2,6 @@ package base
const ( const (
APP_NAME = "AnyLink" APP_NAME = "AnyLink"
// 修复严重bug // 修复前端bug
APP_VER = "0.3.3" APP_VER = "0.6.2"
) )

View File

@@ -5,13 +5,13 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"github.com/spf13/viper"
) )
const ( const (
LinkModeTUN = "tun" LinkModeTUN = "tun"
LinkModeTAP = "tap" LinkModeTAP = "tap"
LinkModeMacvtap = "macvtap"
LinkModeIpvtap = "ipvtap"
) )
var ( var (
@@ -31,15 +31,16 @@ var (
type ServerConfig struct { type ServerConfig struct {
// LinkAddr string `json:"link_addr"` // LinkAddr string `json:"link_addr"`
Conf string `json:"conf"`
ServerAddr string `json:"server_addr"` ServerAddr string `json:"server_addr"`
ServerDTLSAddr string `json:"server_dtls_addr"` ServerDTLSAddr string `json:"server_dtls_addr"`
ServerDTLS bool `json:"server_dtls"` ServerDTLS bool `json:"server_dtls"`
AdminAddr string `json:"admin_addr"` AdminAddr string `json:"admin_addr"`
ProxyProtocol bool `json:"proxy_protocol"` ProxyProtocol bool `json:"proxy_protocol"`
DbFile string `json:"db_file"` DbType string `json:"db_type"`
DbSource string `json:"db_source"`
CertFile string `json:"cert_file"` CertFile string `json:"cert_file"`
CertKey string `json:"cert_key"` CertKey string `json:"cert_key"`
UiPath string `json:"ui_path"`
FilesPath string `json:"files_path"` FilesPath string `json:"files_path"`
LogPath string `json:"log_path"` LogPath string `json:"log_path"`
LogLevel string `json:"log_level"` LogLevel string `json:"log_level"`
@@ -49,11 +50,12 @@ type ServerConfig struct {
AdminPass string `json:"admin_pass"` AdminPass string `json:"admin_pass"`
JwtSecret string `json:"jwt_secret"` JwtSecret string `json:"jwt_secret"`
LinkMode string `json:"link_mode"` // tun tap LinkMode string `json:"link_mode"` // tun tap macvtap ipvtap
Ipv4CIDR string `json:"ipv4_cidr"` // 192.168.1.0/24 Ipv4Master string `json:"ipv4_master"` // eth0
Ipv4Gateway string `json:"ipv4_gateway"` Ipv4CIDR string `json:"ipv4_cidr"` // 192.168.10.0/24
Ipv4Start string `json:"ipv4_start"` // 192.168.1.100 Ipv4Gateway string `json:"ipv4_gateway"` // 192.168.10.1
Ipv4End string `json:"ipv4_end"` // 192.168.1.200 Ipv4Start string `json:"ipv4_start"` // 192.168.10.100
Ipv4End string `json:"ipv4_end"` // 192.168.10.200
IpLease int `json:"ip_lease"` IpLease int `json:"ip_lease"`
MaxClient int `json:"max_client"` MaxClient int `json:"max_client"`
@@ -65,25 +67,30 @@ type ServerConfig struct {
MobileDpd int `json:"mobile_dpd"` MobileDpd int `json:"mobile_dpd"`
SessionTimeout int `json:"session_timeout"` // in seconds SessionTimeout int `json:"session_timeout"` // in seconds
AuthTimeout int `json:"auth_timeout"` // in seconds // AuthTimeout int `json:"auth_timeout"` // in seconds
AuditInterval int `json:"audit_interval"` // in seconds
} }
func initServerCfg() { func initServerCfg() {
sf, _ := filepath.Abs(cfgFile) // TODO 取消绝对地址转换
base := filepath.Dir(sf) // sf, _ := filepath.Abs(cfgFile)
// base := filepath.Dir(sf)
// 转换成绝对路径 // 转换成绝对路径
Cfg.DbFile = getAbsPath(base, Cfg.DbFile) // Cfg.DbFile = getAbsPath(base, Cfg.DbFile)
Cfg.CertFile = getAbsPath(base, Cfg.CertFile) // Cfg.CertFile = getAbsPath(base, Cfg.CertFile)
Cfg.CertKey = getAbsPath(base, Cfg.CertKey) // Cfg.CertKey = getAbsPath(base, Cfg.CertKey)
Cfg.UiPath = getAbsPath(base, Cfg.UiPath) // Cfg.UiPath = getAbsPath(base, Cfg.UiPath)
Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath) // Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
Cfg.LogPath = getAbsPath(base, Cfg.LogPath) // Cfg.LogPath = getAbsPath(base, Cfg.LogPath)
if len(Cfg.JwtSecret) < 20 { if Cfg.AdminPass == defaultPwd {
fmt.Println("请设置 jwt_secret 长度20位以上") fmt.Fprintln(os.Stderr, "=== 使用默认的admin_pass有安全风险请设置新的admin_pass ===")
os.Exit(0) }
if Cfg.JwtSecret == defaultJwt {
fmt.Fprintln(os.Stderr, "=== 使用默认的jwt_secret有安全风险请设置新的jwt_secret ===")
} }
fmt.Printf("ServerCfg: %+v \n", Cfg) fmt.Printf("ServerCfg: %+v \n", Cfg)
@@ -115,13 +122,13 @@ func initCfg() {
for _, v := range configs { for _, v := range configs {
if v.Name == tag { if v.Name == tag {
if v.Typ == cfgStr { if v.Typ == cfgStr {
value.SetString(viper.GetString(v.Name)) value.SetString(linkViper.GetString(v.Name))
} }
if v.Typ == cfgInt { if v.Typ == cfgInt {
value.SetInt(int64(viper.GetInt(v.Name))) value.SetInt(int64(linkViper.GetInt(v.Name)))
} }
if v.Typ == cfgBool { if v.Typ == cfgBool {
value.SetBool(viper.GetBool(v.Name)) value.SetBool(linkViper.GetBool(v.Name))
} }
} }
} }
@@ -150,9 +157,6 @@ func ServerCfg2Slice() []SCfg {
value := s.Field(i) value := s.Field(i)
tag := field.Tag.Get("json") tag := field.Tag.Get("json")
usage, env := getUsageEnv(tag) usage, env := getUsageEnv(tag)
if usage == "" {
continue
}
datas = append(datas, SCfg{Name: tag, Env: env, Info: usage, Data: value.Interface()}) datas = append(datas, SCfg{Name: tag, Env: env, Info: usage, Data: value.Interface()})
} }

View File

@@ -1,12 +1,12 @@
package base package base
import ( import (
"errors"
"fmt" "fmt"
"math/rand"
"os" "os"
"reflect"
"runtime" "runtime"
"strings" "strings"
"time"
"github.com/bjdgyc/anylink/pkg/utils" "github.com/bjdgyc/anylink/pkg/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -16,25 +16,26 @@ import (
var ( var (
// 提交id // 提交id
CommitId string CommitId string
// 配置文件
cfgFile string
// pass明文 // pass明文
passwd string passwd string
// 生成密钥 // 生成密钥
secret bool secret bool
// 显示版本信息 // 显示版本信息
rev bool rev bool
// 获取env名称 // 输出debug信息
env bool debug bool
// Used for flags. // Used for flags.
runSrv bool runSrv bool
linkViper *viper.Viper
rootCmd *cobra.Command rootCmd *cobra.Command
) )
// Execute executes the root command. // Execute executes the root command.
func execute() { func execute() {
initCmd()
err := rootCmd.Execute() err := rootCmd.Execute()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@@ -42,13 +43,25 @@ func execute() {
} }
// viper.Debug() // viper.Debug()
ref := reflect.ValueOf(linkViper)
s := ref.Elem()
ee := s.FieldByName("env")
if ee.Kind() != reflect.Map {
panic("Viper env is err")
}
rr := ee.MapRange()
for rr.Next() {
// fmt.Println(rr.Key(), rr.Value().Index(0))
envs[rr.Key().String()] = rr.Value().Index(0).String()
}
if !runSrv { if !runSrv {
os.Exit(0) os.Exit(0)
} }
} }
func init() { func initCmd() {
linkViper = viper.New()
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "anylink", Use: "anylink",
Short: "AnyLink VPN Server", Short: "AnyLink VPN Server",
@@ -59,38 +72,44 @@ func init() {
}, },
} }
cobra.OnInitialize(func() { linkViper.SetEnvPrefix("link")
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
fmt.Println("Using config file:", err)
}
})
viper.SetEnvPrefix("link")
// 基础配置 // 基础配置
rootCmd.Flags().StringVarP(&cfgFile, "conf", "c", "./conf/server.toml", "config file")
for _, v := range configs { for _, v := range configs {
if v.Typ == cfgStr { if v.Typ == cfgStr {
rootCmd.Flags().String(v.Name, v.ValStr, v.Usage) rootCmd.Flags().StringP(v.Name, v.Short, v.ValStr, v.Usage)
} }
if v.Typ == cfgInt { if v.Typ == cfgInt {
rootCmd.Flags().Int(v.Name, v.ValInt, v.Usage) rootCmd.Flags().IntP(v.Name, v.Short, v.ValInt, v.Usage)
} }
if v.Typ == cfgBool { if v.Typ == cfgBool {
rootCmd.Flags().Bool(v.Name, v.ValBool, v.Usage) rootCmd.Flags().BoolP(v.Name, v.Short, v.ValBool, v.Usage)
} }
_ = viper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name)) _ = linkViper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name))
_ = viper.BindEnv(v.Name) _ = linkViper.BindEnv(v.Name)
// viper.SetDefault(v.Name, v.Value) // viper.SetDefault(v.Name, v.Value)
} }
rootCmd.AddCommand(initToolCmd()) rootCmd.AddCommand(initToolCmd())
cobra.OnInitialize(func() {
linkViper.AutomaticEnv()
conf := linkViper.GetString("conf")
_, err := os.Stat(conf)
if errors.Is(err, os.ErrNotExist) {
// 没有配置文件,不做处理
return
}
linkViper.SetConfigFile(conf)
err = linkViper.ReadInConfig()
if err != nil {
fmt.Println("Using config file:", err)
}
})
} }
func initToolCmd() *cobra.Command { func initToolCmd() *cobra.Command {
@@ -103,7 +122,7 @@ func initToolCmd() *cobra.Command {
toolCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info") toolCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info")
toolCmd.Flags().BoolVarP(&secret, "secret", "s", false, "generate a random jwt secret") toolCmd.Flags().BoolVarP(&secret, "secret", "s", false, "generate a random jwt secret")
toolCmd.Flags().StringVarP(&passwd, "passwd", "p", "", "convert the password plaintext") toolCmd.Flags().StringVarP(&passwd, "passwd", "p", "", "convert the password plaintext")
toolCmd.Flags().BoolVarP(&env, "env", "e", false, "list the config name and env key") toolCmd.Flags().BoolVarP(&debug, "debug", "d", false, "list the config viper.Debug() info")
toolCmd.Run = func(cmd *cobra.Command, args []string) { toolCmd.Run = func(cmd *cobra.Command, args []string) {
switch { switch {
@@ -111,17 +130,14 @@ func initToolCmd() *cobra.Command {
fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n", fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n",
APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId) APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId)
case secret: case secret:
rand.Seed(time.Now().UnixNano())
s, _ := utils.RandSecret(40, 60) s, _ := utils.RandSecret(40, 60)
s = strings.Trim(s, "=") s = strings.Trim(s, "=")
fmt.Printf("Secret:%s\n", s) fmt.Printf("Secret:%s\n", s)
case passwd != "": case passwd != "":
pass, _ := utils.PasswordHash(passwd) pass, _ := utils.PasswordHash(passwd)
fmt.Printf("Passwd:%s\n", pass) fmt.Printf("Passwd:%s\n", pass)
case env: case debug:
for k, v := range envs { linkViper.Debug()
fmt.Printf("%s => %s\n", k, v)
}
default: default:
fmt.Println("Using [anylink tool -h] for help") fmt.Println("Using [anylink tool -h] for help")
} }

View File

@@ -4,11 +4,15 @@ const (
cfgStr = iota cfgStr = iota
cfgInt cfgInt
cfgBool cfgBool
defaultJwt = "abcdef.0123456789.abcdef"
defaultPwd = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"
) )
type config struct { type config struct {
Typ int Typ int
Name string Name string
Short string
Usage string Usage string
ValStr string ValStr string
ValInt int ValInt int
@@ -16,24 +20,26 @@ type config struct {
} }
var configs = []config{ var configs = []config{
{Typ: cfgStr, Name: "conf", Usage: "config file", ValStr: "./conf/server.toml", Short: "c"},
{Typ: cfgStr, Name: "server_addr", Usage: "服务监听地址", ValStr: ":443"}, {Typ: cfgStr, Name: "server_addr", Usage: "服务监听地址", ValStr: ":443"},
{Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false}, {Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false},
{Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":4433"}, {Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":4433"},
{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"}, {Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false}, {Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
{Typ: cfgStr, Name: "db_file", Usage: "数据库地址", ValStr: "./data.db"}, {Typ: cfgStr, Name: "db_type", Usage: "数据库类型 [sqlite3 mysql postgres]", ValStr: "sqlite3"},
{Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./vpn_cert.pem"}, {Typ: cfgStr, Name: "db_source", Usage: "数据库source", ValStr: "./conf/anylink.db"},
{Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./vpn_cert.key"}, {Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./conf/vpn_cert.pem"},
{Typ: cfgStr, Name: "ui_path", Usage: "ui文件路径", ValStr: "./ui"}, {Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./conf/vpn_cert.key"},
{Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./files"}, {Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./conf/files"},
{Typ: cfgStr, Name: "log_path", Usage: "日志文件路径", ValStr: ""}, {Typ: cfgStr, Name: "log_path", Usage: "日志文件路径,默认标准输出", ValStr: ""},
{Typ: cfgStr, Name: "log_level", Usage: "日志等级", ValStr: "info"}, {Typ: cfgStr, Name: "log_level", Usage: "日志等级 [debug info warn error]", ValStr: "info"},
{Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false}, {Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false},
{Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"}, {Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"},
{Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"}, {Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"},
{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"}, {Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: defaultPwd},
{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: "iLmspvOiz*%ovfcs*wersdf#heR8pNU4XxBm&mW$aPCjSRMbYH#&"}, {Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: defaultJwt},
{Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型", ValStr: "tun"}, {Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型[tun tap macvtap ipvtap]", ValStr: "tun"},
{Typ: cfgStr, Name: "ipv4_master", Usage: "ipv4主网卡名称", ValStr: "eth0"},
{Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"}, {Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"},
{Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.10.1"}, {Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.10.1"},
{Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.10.100"}, {Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.10.100"},
@@ -49,6 +55,7 @@ var configs = []config{
{Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 60}, {Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 60},
{Typ: cfgInt, Name: "session_timeout", Usage: "session过期时间(秒)", ValInt: 3600}, {Typ: cfgInt, Name: "session_timeout", Usage: "session过期时间(秒)", ValInt: 3600},
// {Typ: cfgInt, Name: "auth_timeout", Usage: "auth_timeout", ValInt: 0}, // {Typ: cfgInt, Name: "auth_timeout", Usage: "auth_timeout", ValInt: 0},
{Typ: cfgInt, Name: "audit_interval", Usage: "审计去重间隔(秒),-1关闭", ValInt: -1},
} }
var envs = map[string]string{} var envs = map[string]string{}

View File

@@ -1,19 +1,13 @@
#!/bin/bash #!/bin/bash
################################# #yum install bridge-utils
# Set up Ethernet bridge on Linux
# Requires: bridge-utils
#################################
# Define Bridge Interface # Define Bridge Interface
br="anylink0" br="anylink0"
# Define physical ethernet interface to be bridged # 请根据sever服务器信息更新下面的信息
# with TAP interface(s) above.
eth="eth0" eth="eth0"
eth_ip="192.168.10.4" eth_ip="192.168.10.4/24"
eth_netmask="255.255.255.0"
eth_broadcast="192.168.10.255" eth_broadcast="192.168.10.255"
eth_gateway="192.168.10.1" eth_gateway="192.168.10.1"
@@ -21,11 +15,14 @@ eth_gateway="192.168.10.1"
brctl addbr $br brctl addbr $br
brctl addif $br $eth brctl addif $br $eth
ifconfig $eth 0.0.0.0 up ip addr del $eth_ip dev $eth
ip addr add 0.0.0.0 dev $eth
ip link set dev $eth up promisc on
mac=`cat /sys/class/net/$eth/address` mac=`cat /sys/class/net/$eth/address`
ifconfig $br hw ether $mac ip link set dev $br up address $mac promisc on
ifconfig $br $eth_ip netmask $eth_netmask broadcast $eth_broadcast up ip addr add $eth_ip broadcast $eth_broadcast dev $br
route add default gateway $eth_gateway route add default gateway $eth_gateway

View File

@@ -1,17 +1,17 @@
#服务配置信息 #示例配置信息
#其他配置文件,可以使用绝对路径 #其他配置文件,可以使用绝对路径
#或者相对于server.toml的路径 #或者相对于 anylink 二进制文件的路径
#数据文件 #数据文件
db_file = "./data.db" db_type = "sqlite3"
db_source = "./conf/anylink.db"
#证书文件 #证书文件
cert_file = "./test_vpn_cert.pem" cert_file = "./conf/vpn_cert.pem"
cert_key = "./test_vpn_key.pem" cert_key = "./conf/vpn_cert.key"
ui_path = "../ui" files_path = "./conf/files"
files_path = "../files"
#日志目录,为空写入标准输出 #日志目录,为空写入标准输出
#log_path = "../log" #log_path = "./log"
log_path = "" log_path = ""
log_level = "debug" log_level = "debug"
pprof = false pprof = false
@@ -22,7 +22,7 @@ issuer = "XX公司VPN"
admin_user = "admin" admin_user = "admin"
#pass 123456 #pass 123456
admin_pass = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke" admin_pass = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"
jwt_secret = "iLmspvOiz*%ovfcs*wersdf#heR8pNU4XxBm&mW$aPCjSRMbYH#&" jwt_secret = "abcdef.0123456789.abcdef"
#服务监听地址 #服务监听地址
@@ -38,6 +38,7 @@ proxy_protocol = false
link_mode = "tun" link_mode = "tun"
#客户端分配的ip地址池 #客户端分配的ip地址池
ipv4_master = "eth0"
ipv4_cidr = "192.168.10.0/24" ipv4_cidr = "192.168.10.0/24"
ipv4_gateway = "192.168.10.1" ipv4_gateway = "192.168.10.1"
ipv4_start = "192.168.10.100" ipv4_start = "192.168.10.100"
@@ -61,7 +62,7 @@ mobile_dpd = 50
#session过期时间用于断线重连0永不过期 #session过期时间用于断线重连0永不过期
session_timeout = 3600 session_timeout = 3600
auth_timeout = 0 auth_timeout = 0
audit_interval = -1

View File

@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDGDCCAgACCQCecQDpy/8hRTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJD
TjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQswCQYDVQQKDAJCRDELMAkGA1UE
CwwCQkQxCzAJBgNVBAMMAkNTMB4XDTIxMDMyNjA5MTkwNloXDTMxMDMyNDA5MTkw
NlowTjELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAlNIMQswCQYDVQQHDAJTSDELMAkG
A1UECgwCVE0xCzAJBgNVBAsMAlRNMQswCQYDVQQDDAJDUzCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAJtDxHduS8gjI0P6txHS+cODxKjyjNiCBa7tFgSc
d9hRrzCvK4Q4M5StKJoSczmHl0C3HVoq92Gv1vENxq4irYdCrwLeOZGyt7urUlbs
PkvEoVXxfAkPpue+JewG/CvGArJeP7UGsP5IrD0Dt5X1DP677K6qf5igzyaJqYJu
RDJ5wR84BoDvY66Zc578N9tK9XusdJ63gQ5jGcG4Dneu1UX3g8lQkJ6P0xLXTh7W
u5Sjx8axbDcFxbDLxNGL1yPgAjhIRgMfaWLwuQQg4WKFsdMljv1Flz8/h91z2xo+
+E/B4YF0UFWTcWQ2TQ8w8noDqnnXVVQyOvuI3aajodml/f0CAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEAd89n0eWXgO1lqMciWmS9xY8Sj/U840bPo/4Kclsm1vFNvIXu
I50PeaNiU2E5+CMk8AwXaJ5gDO7vsRxvLLRAUWZeuxSror2a0RkViEFW+UKcBuuB
Izl9giXUhB/P85+We1ma5jizqj7OpzgMkzkcTZL2M6Gw6IWY4jopvLQjiCooSiYF
wtLZjuFKfpLrPw5RgpWI4L8Hftbkmh6Q8nqcoQvgwm7rLrD5VqiTu7Rk1SXTFuXn
uuazXasWIWRVGFuFcYP1rwyOfp9HhCFKngi0w8IRnbOcaPdXydtbKMcKt5z9zQX5
BqrZ3ZfPp5HeklG7L8eQrnp4ines6YDshPnaRQ==
-----END CERTIFICATE-----

View File

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAm0PEd25LyCMjQ/q3EdL5w4PEqPKM2IIFru0WBJx32FGvMK8r
hDgzlK0omhJzOYeXQLcdWir3Ya/W8Q3GriKth0KvAt45kbK3u6tSVuw+S8ShVfF8
CQ+m574l7Ab8K8YCsl4/tQaw/kisPQO3lfUM/rvsrqp/mKDPJompgm5EMnnBHzgG
gO9jrplznvw320r1e6x0nreBDmMZwbgOd67VRfeDyVCQno/TEtdOHta7lKPHxrFs
NwXFsMvE0YvXI+ACOEhGAx9pYvC5BCDhYoWx0yWO/UWXPz+H3XPbGj74T8HhgXRQ
VZNxZDZNDzDyegOqeddVVDI6+4jdpqOh2aX9/QIDAQABAoIBADWT2fz4g5AJiAbS
QlAVRHjSRI+kOzQPEhT93SY0NCribRjYqaSTnEEGy8b27OoCPxBm3+sYfosoGXzP
Kys17jmJqkjMFIORb1OEWAKEvS56KM42aX3a99ZqSD29X1Ffn9ibK1K1f2gP/deE
K9rEV/qjMJZJYYRyoWkEAglvMXtU/NMRoTuFYtrJPr9sFEfpBFq97WpWiyMdLKTG
MmlN+T1CXFQj/+mpv+DDSXcwLPBxAttDYE2GeqlhntId0I6cgaEGMO42D6fnqrKi
PDilA/D6zos4o/bpRGvVBdXHqOXvX2stNHK+PvEX46GRd+OZhLh0KEcrWAx8cXs9
ZhugTyECgYEAyffRPd98acL0OhXJR9mZTgDdotl7iYq+RTZbmEvAFst3mL3LA6Ba
BTrwRLh9x8lzxoTQHHFaJL63kIrN6QAR9e3+pR0e8IX3vYCVGIlRCYB5CrE/O3Pi
B9R17tCI5dFrFXYiST38sjwrWG9+geKarbUH5AZrZEO5uw0q7+4F3TkCgYEAxM1h
Xo+xRt8RXoWZ6Cl66HhZKIvDcxkBtoNh54YLzrVpv0D+RvAWNDzRVXbbIUUpBGPN
pHrwU8G0qWr4Q/Zx+vnckqotGMTNCB7vcmB/qwF9grNW9E0rCyIYLXtJcEiclJIF
Oe406YXl7mSG1I6QjAADz8PNb4++Ct1+hVS56uUCgYAx9g/Y0nQgZY2s4L7N+1Il
LammI06gE6ZF0NCPuA1oliSbsDeMShp6uL2/AjR7O6ZcMXaZ0qCN/m/CXdPaE55d
y+X2SmHg9gL26dv4Gd/mDdXjgz01I9GCRlh2Hzf+QfPPd027+I2OObwvQEV3M+s3
lVTCX6QpRWeokfVRLPxeYQKBgDIYPVK+rNdnbJps05JfDKQkDj3d5bBkiyUUKFWw
r0y8rOA8AP25m01MtdRVXs4HNruhU/UsPgRz6DK/wdY64ySJeXXzz2rgnXgVt8mb
eqPiyzn7wISLKAu7cAATw8vLD+BZku7+DYXryW13NULhzzVzw4SdSKu/IRbO7qet
u21pAoGAd2mBJ+PWKnUkARS8gQ3Y3cagA/qGGr094P9relglRDBv/Pm7kTUt6K8B
NnpqWydcVtcrXmNzGRx4ftm18SzmTJEohF14nF9424q4aiWoNZyG8adxaI0Yqv3G
LnH8n2fzC+pf31LijBRM8DRnepah64mLF+OM/SxgVg1nP9jVUG4=
-----END RSA PRIVATE KEY-----

View File

@@ -1,70 +1,112 @@
package dbdata package dbdata
import ( import (
"time"
"github.com/asdine/storm/v3"
"github.com/asdine/storm/v3/codec/json"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
bolt "go.etcd.io/bbolt" _ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
"xorm.io/xorm"
) )
var ( var (
sdb *storm.DB xdb *xorm.Engine
) )
func GetXdb() *xorm.Engine {
return xdb
}
func initDb() { func initDb() {
var err error var err error
sdb, err = storm.Open(base.Cfg.DbFile, storm.Codec(json.Codec), xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource)
storm.BoltOptions(0600, &bolt.Options{Timeout: 10 * time.Second})) // xdb.ShowSQL(true)
if err != nil { if err != nil {
base.Fatal(err) base.Fatal(err)
} }
// 初始化数据库 // 初始化数据库
err = sdb.Init(&User{}) err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{})
if err != nil { if err != nil {
base.Fatal(err) base.Fatal(err)
} }
// fmt.Println("s1") // fmt.Println("s1=============", err)
} }
func initData() { func initData() {
var ( var (
err error err error
install bool
) )
// 判断是否初次使用 // 判断是否初次使用
err = Get(SettingBucket, Installed, &install) install := &SettingInstall{}
if err == nil && install { err = SettingGet(install)
if err == nil && install.Installed {
// 已经安装过 // 已经安装过
return return
} }
defer func() { // 发生错误
_ = Set(SettingBucket, Installed, true) if err != ErrNotFound {
}() base.Fatal(err)
}
err = addInitData()
if err != nil {
base.Fatal(err)
}
}
func addInitData() error {
var (
err error
)
sess := xdb.NewSession()
defer sess.Close()
err = sess.Begin()
if err != nil {
return err
}
// SettingSmtp
smtp := &SettingSmtp{ smtp := &SettingSmtp{
Host: "127.0.0.1", Host: "127.0.0.1",
Port: 25, Port: 25,
From: "vpn@xx.com", From: "vpn@xx.com",
Encryption: "None",
}
err = SettingSessAdd(sess, smtp)
if err != nil {
return err
} }
_ = SettingSet(smtp)
// SettingOther
other := &SettingOther{ other := &SettingOther{
LinkAddr: "vpn.xx.com", LinkAddr: "vpn.xx.com",
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为", Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为",
AccountMail: accountMail, AccountMail: accountMail,
} }
_ = SettingSet(other) err = SettingSessAdd(sess, other)
if err != nil {
return err
}
// Install
install := &SettingInstall{Installed: true}
err = SettingSessAdd(sess, install)
if err != nil {
return err
}
return sess.Commit()
} }
func CheckErrNotFound(err error) bool { func CheckErrNotFound(err error) bool {
return err == storm.ErrNotFound return err == ErrNotFound
} }
const accountMail = `<p>您好:</p> const accountMail = `<p>您好:</p>

View File

@@ -1,66 +1,84 @@
package dbdata package dbdata
import "github.com/asdine/storm/v3/index" import (
"errors"
"reflect"
)
const PageSize = 10 const PageSize = 10
func Save(data interface{}) error { var ErrNotFound = errors.New("ErrNotFound")
return sdb.Save(data)
func Add(data interface{}) error {
_, err := xdb.InsertOne(data)
return err
} }
func Update(data interface{}) error { func Update(fieldName string, value interface{}, data interface{}) error {
return sdb.Update(data) _, err := xdb.Where(fieldName+"=?", value).Update(data)
} return err
func UpdateField(data interface{}, fieldName string, value interface{}) error {
return sdb.UpdateField(data, fieldName, value)
} }
func Del(data interface{}) error { func Del(data interface{}) error {
return sdb.DeleteStruct(data) _, err := xdb.Delete(data)
return err
} }
func Set(bucket, key string, data interface{}) error { func extract(data interface{}, fieldName string) interface{} {
return sdb.Set(bucket, key, data) ref := reflect.ValueOf(data)
r := &ref
if r.Kind() == reflect.Ptr {
e := r.Elem()
r = &e
}
field := r.FieldByName(fieldName).Interface()
return field
} }
func Get(bucket, key string, data interface{}) error { // 更新全部字段
return sdb.Get(bucket, key, data) func Set(data interface{}) error {
id := extract(data, "Id")
_, err := xdb.ID(id).AllCols().Update(data)
return err
}
func One(fieldName string, value interface{}, data interface{}) error {
has, err := xdb.Where(fieldName+"=?", value).Get(data)
if err != nil {
return err
}
if !has {
return ErrNotFound
}
return nil
} }
func CountAll(data interface{}) int { func CountAll(data interface{}) int {
n, _ := sdb.Count(data) n, _ := xdb.Count(data)
return n return int(n)
} }
func One(fieldName string, value interface{}, to interface{}) error { func Find(data interface{}, limit, page int) error {
return sdb.One(fieldName, value, to) if limit == 0 {
return xdb.Find(data)
} }
func Find(fieldName string, value interface{}, to interface{}, options ...func(q *index.Options)) error { start := (page - 1) * limit
return sdb.Find(fieldName, value, to, options...) return xdb.Limit(limit, start).Find(data)
} }
func All(to interface{}, limit, page int) error { func CountPrefix(fieldName string, prefix string, data interface{}) int {
opt := getOpt(limit, page) n, _ := xdb.Where(fieldName+" like ?", prefix+"%").Count(data)
return sdb.All(to, opt) return int(n)
} }
func Prefix(fieldName string, prefix string, to interface{}, limit, page int) error { func Prefix(fieldName string, prefix string, data interface{}, limit, page int) error {
opt := getOpt(limit, page) where := xdb.Where(fieldName+" like ?", prefix+"%")
return sdb.Prefix(fieldName, prefix, to, opt) if limit == 0 {
return where.Find(data)
} }
func getOpt(limit, page int) func(*index.Options) { start := (page - 1) * limit
skip := (page - 1) * limit return where.Limit(limit, start).Find(data)
opt := func(opt *index.Options) {
opt.Reverse = true
if limit > 0 {
opt.Limit = limit
}
if skip > 0 {
opt.Skip = skip
}
}
return opt
} }

View File

@@ -11,12 +11,13 @@ import (
func preIpData() { func preIpData() {
tmpDb := path.Join(os.TempDir(), "anylink_test.db") tmpDb := path.Join(os.TempDir(), "anylink_test.db")
base.Cfg.DbFile = tmpDb base.Cfg.DbType = "sqlite3"
base.Cfg.DbSource = tmpDb
initDb() initDb()
} }
func closeIpdata() { func closeIpdata() {
sdb.Close() xdb.Close()
tmpDb := path.Join(os.TempDir(), "anylink_test.db") tmpDb := path.Join(os.TempDir(), "anylink_test.db")
os.Remove(tmpDb) os.Remove(tmpDb)
} }
@@ -27,7 +28,7 @@ func TestDb(t *testing.T) {
defer closeIpdata() defer closeIpdata()
u := User{Username: "a"} u := User{Username: "a"}
err := Save(&u) err := Add(&u)
ast.Nil(err) ast.Nil(err)
ast.Equal(u.Id, 1) ast.Equal(u.Id, 1)

View File

@@ -29,24 +29,24 @@ type ValData struct {
Note string `json:"note"` Note string `json:"note"`
} }
type Group struct { // type Group struct {
Id int `json:"id" storm:"id,increment"` // Id int `json:"id" xorm:"pk autoincr not null"`
Name string `json:"name" storm:"unique"` // Name string `json:"name" xorm:"not null unique"`
Note string `json:"note"` // Note string `json:"note"`
AllowLan bool `json:"allow_lan"` // AllowLan bool `json:"allow_lan"`
ClientDns []ValData `json:"client_dns"` // ClientDns []ValData `json:"client_dns"`
RouteInclude []ValData `json:"route_include"` // RouteInclude []ValData `json:"route_include"`
RouteExclude []ValData `json:"route_exclude"` // RouteExclude []ValData `json:"route_exclude"`
LinkAcl []GroupLinkAcl `json:"link_acl"` // LinkAcl []GroupLinkAcl `json:"link_acl"`
Bandwidth int `json:"bandwidth"` // 带宽限制 // Bandwidth int `json:"bandwidth"` // 带宽限制
Status int8 `json:"status"` // 1正常 // Status int8 `json:"status"` // 1正常
CreatedAt time.Time `json:"created_at"` // CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt time.Time `json:"updated_at"`
} // }
func GetGroupNames() []string { func GetGroupNames() []string {
var datas []Group var datas []Group
err := All(&datas, 0, 0) err := Find(&datas, 0, 0)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
return nil return nil
@@ -68,17 +68,26 @@ func SetGroup(g *Group) error {
clientDns := []ValData{} clientDns := []ValData{}
for _, v := range g.ClientDns { for _, v := range g.ClientDns {
if v.Val != "" { if v.Val != "" {
ip := net.ParseIP(v.Val)
if ip.String() != v.Val {
return errors.New("DNS IP 错误")
}
clientDns = append(clientDns, v) clientDns = append(clientDns, v)
} }
} }
if len(clientDns) == 0 { if len(clientDns) == 0 {
return errors.New("DNS 错误") return errors.New("必须设置一个DNS")
} }
g.ClientDns = clientDns g.ClientDns = clientDns
routeInclude := []ValData{} routeInclude := []ValData{}
for _, v := range g.RouteInclude { for _, v := range g.RouteInclude {
if v.Val != "" { if v.Val != "" {
if v.Val == "all" {
routeInclude = append(routeInclude, v)
continue
}
ipMask, _, err := parseIpNet(v.Val) ipMask, _, err := parseIpNet(v.Val)
if err != nil { if err != nil {
return errors.New("RouteInclude 错误" + err.Error()) return errors.New("RouteInclude 错误" + err.Error())
@@ -116,7 +125,11 @@ func SetGroup(g *Group) error {
g.LinkAcl = linkAcl g.LinkAcl = linkAcl
g.UpdatedAt = time.Now() g.UpdatedAt = time.Now()
err = Save(g) if g.Id > 0 {
err = Set(g)
} else {
err = Add(g)
}
return err return err
} }

View File

@@ -1,18 +1,34 @@
package dbdata package dbdata
import ( import (
"net" "errors"
"time" "time"
) )
type IpMap struct { // type IpMap struct {
Id int `json:"id" storm:"id,increment"` // Id int `json:"id" xorm:"pk autoincr not null"`
IpAddr net.IP `json:"ip_addr" storm:"unique"` // IpAddr string `json:"ip_addr" xorm:"not null unique"`
MacAddr string `json:"mac_addr" storm:"unique"` // MacAddr string `json:"mac_addr" xorm:"not null unique"`
Username string `json:"username"` // Username string `json:"username"`
Keep bool `json:"keep"` // 保留 ip-mac 绑定 // Keep bool `json:"keep"` // 保留 ip-mac 绑定
KeepTime time.Time `json:"keep_time"` // KeepTime time.Time `json:"keep_time"`
Note string `json:"note"` // 备注 // Note string `json:"note"` // 备注
LastLogin time.Time `json:"last_login"` // LastLogin time.Time `json:"last_login"`
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt time.Time `json:"updated_at"`
// }
func SetIpMap(v *IpMap) error {
var err error
if len(v.IpAddr) < 4 || len(v.MacAddr) < 6 {
return errors.New("IP或MAC错误")
}
v.UpdatedAt = time.Now()
if v.Id > 0 {
err = Set(v)
} else {
err = Add(v)
}
return err
} }

View File

@@ -1,13 +1,29 @@
package dbdata package dbdata
import ( import (
"encoding/json"
"reflect" "reflect"
"xorm.io/xorm"
) )
const ( type SettingInstall struct {
SettingBucket = "SettingBucket" Installed bool `json:"installed"`
Installed = "Installed" }
)
type SettingSmtp struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
From string `json:"from"`
Encryption string `json:"encryption"`
}
type SettingOther struct {
LinkAddr string `json:"link_addr"`
Banner string `json:"banner"`
AccountMail string `json:"account_mail"`
}
func StructName(data interface{}) string { func StructName(data interface{}) string {
ref := reflect.ValueOf(data) ref := reflect.ValueOf(data)
@@ -20,29 +36,30 @@ func StructName(data interface{}) string {
return name return name
} }
func SettingSessAdd(sess *xorm.Session, data interface{}) error {
name := StructName(data)
v, _ := json.Marshal(data)
s := &Setting{Name: name, Data: v}
_, err := sess.InsertOne(s)
return err
}
func SettingSet(data interface{}) error { func SettingSet(data interface{}) error {
key := StructName(data) name := StructName(data)
err := Set(SettingBucket, key, data) v, _ := json.Marshal(data)
s := &Setting{Data: v}
err := Update("name", name, s)
return err return err
} }
func SettingGet(data interface{}) error { func SettingGet(data interface{}) error {
key := StructName(data) name := StructName(data)
err := Get(SettingBucket, key, data) s := &Setting{Name: name}
err := One("name", name, s)
if err != nil {
return err return err
} }
err = json.Unmarshal(s.Data, data)
type SettingSmtp struct { return err
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
From string `json:"from"`
UseSSl bool `json:"use_ssl"`
}
type SettingOther struct {
LinkAddr string `json:"link_addr"`
Banner string `json:"banner"`
AccountMail string `json:"account_mail"`
} }

View File

@@ -6,5 +6,5 @@ func Start() {
} }
func Stop() error { func Stop() error {
return sdb.Close() return xdb.Close()
} }

67
server/dbdata/tables.go Normal file
View File

@@ -0,0 +1,67 @@
package dbdata
import (
"encoding/json"
"time"
)
type Group struct {
Id int `json:"id" xorm:"pk autoincr not null"`
Name string `json:"name" xorm:"varchar(60) not null unique"`
Note string `json:"note" xorm:"varchar(255)"`
AllowLan bool `json:"allow_lan" xorm:"Bool"`
ClientDns []ValData `json:"client_dns" xorm:"Text"`
RouteInclude []ValData `json:"route_include" xorm:"Text"`
RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
Status int8 `json:"status" xorm:"Int"` // 1正常
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
}
type User struct {
Id int `json:"id" xorm:"pk autoincr not null"`
Username string `json:"username" xorm:"varchar(60) not null unique"`
Nickname string `json:"nickname" xorm:"varchar(255)"`
Email string `json:"email" xorm:"varchar(255)"`
// Password string `json:"password"`
PinCode string `json:"pin_code" xorm:"varchar(32)"`
OtpSecret string `json:"otp_secret" xorm:"varchar(255)"`
DisableOtp bool `json:"disable_otp" xorm:"Bool"` // 禁用otp
Groups []string `json:"groups" xorm:"Text"`
Status int8 `json:"status" xorm:"Int"` // 1正常
SendEmail bool `json:"send_email" xorm:"Bool"`
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
}
type IpMap struct {
Id int `json:"id" xorm:"pk autoincr not null"`
IpAddr string `json:"ip_addr" xorm:"varchar(32) not null unique"`
MacAddr string `json:"mac_addr" xorm:"varchar(32) not null unique"`
Username string `json:"username" xorm:"varchar(60)"`
Keep bool `json:"keep" xorm:"Bool"` // 保留 ip-mac 绑定
KeepTime time.Time `json:"keep_time" xorm:"DateTime"`
Note string `json:"note" xorm:"varchar(255)"` // 备注
LastLogin time.Time `json:"last_login" xorm:"DateTime updated"`
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
}
type Setting struct {
Id int `json:"id" xorm:"pk autoincr not null"`
Name string `json:"name" xorm:"varchar(60) not null unique"`
Data json.RawMessage `json:"data" xorm:"Text"`
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
}
type AccessAudit struct {
Id int `json:"id" xorm:"pk autoincr not null"`
Username string `json:"username" xorm:"varchar(60) not null"`
Protocol uint8 `json:"protocol" xorm:"not null"`
Src string `json:"src" xorm:"varchar(60) not null"`
SrcPort uint16 `json:"src_port" xorm:"not null"`
Dst string `json:"dst" xorm:"varchar(60) not null"`
DstPort uint16 `json:"dst_port" xorm:"not null"`
CreatedAt time.Time `json:"created_at" xorm:"DateTime"`
}

View File

@@ -10,21 +10,21 @@ import (
"github.com/xlzd/gotp" "github.com/xlzd/gotp"
) )
type User struct { // type User struct {
Id int `json:"id" storm:"id,increment"` // Id int `json:"id" xorm:"pk autoincr not null"`
Username string `json:"username" storm:"unique"` // Username string `json:"username" storm:"not null unique"`
Nickname string `json:"nickname"` // Nickname string `json:"nickname"`
Email string `json:"email"` // Email string `json:"email"`
// Password string `json:"password"` // // Password string `json:"password"`
PinCode string `json:"pin_code"` // PinCode string `json:"pin_code"`
OtpSecret string `json:"otp_secret"` // OtpSecret string `json:"otp_secret"`
DisableOtp bool `json:"disable_otp"` // 禁用otp // DisableOtp bool `json:"disable_otp"` // 禁用otp
Groups []string `json:"groups"` // Groups []string `json:"groups"`
Status int8 `json:"status"` // 1正常 // Status int8 `json:"status"` // 1正常
SendEmail bool `json:"send_email"` // SendEmail bool `json:"send_email"`
CreatedAt time.Time `json:"created_at"` // CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt time.Time `json:"updated_at"`
} // }
func SetUser(v *User) error { func SetUser(v *User) error {
var err error var err error
@@ -57,7 +57,11 @@ func SetUser(v *User) error {
v.Groups = ng v.Groups = ng
v.UpdatedAt = time.Now() v.UpdatedAt = time.Now()
err = Save(v) if v.Id > 0 {
err = Set(v)
} else {
err = Add(v)
}
return err return err
} }

View File

@@ -3,28 +3,29 @@ module github.com/bjdgyc/anylink
go 1.16 go 1.16
require ( require (
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect github.com/StackExchange/wmi v1.2.1 // indirect
github.com/asdine/storm/v3 v3.2.1 github.com/go-sql-driver/mysql v1.6.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/golang-jwt/jwt/v4 v4.0.0
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/pion/dtls/v2 v2.0.0-00010101000000-000000000000 github.com/lib/pq v1.10.2
github.com/mattn/go-sqlite3 v1.14.8
github.com/pion/dtls/v2 v2.0.9
github.com/pion/logging v0.2.2 github.com/pion/logging v0.2.2
github.com/shirou/gopsutil v3.21.4+incompatible github.com/shirou/gopsutil v3.21.7+incompatible
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/cobra v1.1.3 github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.7.1 github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tklauser/go-sysconf v0.3.6 // indirect github.com/tklauser/go-sysconf v0.3.7 // indirect
github.com/xhit/go-simple-mail/v2 v2.9.0 github.com/xhit/go-simple-mail/v2 v2.10.0
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
go.etcd.io/bbolt v1.3.5 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba xorm.io/xorm v1.2.2
) )
replace github.com/pion/dtls/v2 => ../dtls-2.0.9 replace github.com/pion/dtls/v2 => ../dtls-2.0.9

File diff suppressed because it is too large Load Diff

View File

@@ -173,6 +173,48 @@ var auth_complete = `<?xml version="1.0" encoding="UTF-8"?>
<server-cert-hash>240B97A685B2BFA66AD699B90AAC49EA66495D69</server-cert-hash> <server-cert-hash>240B97A685B2BFA66AD699B90AAC49EA66495D69</server-cert-hash>
</vpn-base-config> </vpn-base-config>
<opaque is-for="vpn-client"></opaque> <opaque is-for="vpn-client"></opaque>
<vpn-profile-manifest>
<vpn rev="1.0">
<file type="profile" service-type="user">
<uri>/profile.xml</uri>
<hash type="sha1">A8B0B07FBA93D06E8501E40AB807AEE2464E73B7</hash>
</file>
</vpn>
</vpn-profile-manifest>
</config> </config>
</config-auth> </config-auth>
` `
var auth_profile = `<?xml version="1.0" encoding="UTF-8"?>
<AnyConnectProfile xmlns="http://schemas.xmlsoap.org/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.xmlsoap.org/encoding/ AnyConnectProfile.xsd">
<ClientInitialization>
<UseStartBeforeLogon UserControllable="false">false</UseStartBeforeLogon>
<StrictCertificateTrust>false</StrictCertificateTrust>
<RestrictPreferenceCaching>false</RestrictPreferenceCaching>
<RestrictTunnelProtocols>IPSec</RestrictTunnelProtocols>
<BypassDownloader>true</BypassDownloader>
<WindowsVPNEstablishment>AllowRemoteUsers</WindowsVPNEstablishment>
<CertEnrollmentPin>pinAllowed</CertEnrollmentPin>
<CertificateMatch>
<KeyUsage>
<MatchKey>Digital_Signature</MatchKey>
</KeyUsage>
<ExtendedKeyUsage>
<ExtendedMatchKey>ClientAuth</ExtendedMatchKey>
</ExtendedKeyUsage>
</CertificateMatch>
<BackupServerList>
<HostAddress>localhost</HostAddress>
</BackupServerList>
</ClientInitialization>
<ServerList>
<HostEntry>
<HostName>VPN Server</HostName>
<HostAddress>localhost</HostAddress>
</HostEntry>
</ServerList>
</AnyConnectProfile>
`

View File

@@ -58,7 +58,7 @@ func execCmd(cmdStrs []string) error {
cmd := exec.Command("sh", "-c", cmdStr) cmd := exec.Command("sh", "-c", cmdStr)
b, err := cmd.CombinedOutput() b, err := cmd.CombinedOutput()
if err != nil { if err != nil {
log.Println(string(b), err) log.Println(string(b))
return err return err
} }
} }

View File

@@ -1,15 +1,17 @@
package handler package handler
import ( import (
"bufio"
"encoding/binary" "encoding/binary"
"net" "net"
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata" "github.com/bjdgyc/anylink/sessdata"
) )
func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) { func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) {
defer func() { defer func() {
base.Debug("LinkCstp return", cSess.IpAddr) base.Debug("LinkCstp return", cSess.IpAddr)
_ = conn.Close() _ = conn.Close()
@@ -23,19 +25,19 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
dead = time.Duration(cSess.CstpDpd+5) * time.Second dead = time.Duration(cSess.CstpDpd+5) * time.Second
) )
go cstpWrite(conn, cSess) go cstpWrite(conn, bufRW, cSess)
for { for {
// 设置超时限制 // 设置超时限制
err = conn.SetReadDeadline(time.Now().Add(dead)) err = conn.SetReadDeadline(utils.NowSec().Add(dead))
if err != nil { if err != nil {
base.Error("SetDeadline: ", err) base.Error("SetDeadline: ", err)
return return
} }
// hdata := make([]byte, BufferSize) // hdata := make([]byte, BufferSize)
hdata := getByteFull() pl := getPayload()
n, err = conn.Read(hdata) n, err = bufRW.Read(pl.Data)
if err != nil { if err != nil {
base.Error("read hdata: ", err) base.Error("read hdata: ", err)
return return
@@ -47,7 +49,7 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
base.Error(err) base.Error(err)
} }
switch hdata[6] { switch pl.Data[6] {
case 0x07: // KEEPALIVE case 0x07: // KEEPALIVE
// do nothing // do nothing
// base.Debug("recv keepalive", cSess.IpAddr) // base.Debug("recv keepalive", cSess.IpAddr)
@@ -56,23 +58,28 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
return return
case 0x03: // DPD-REQ case 0x03: // DPD-REQ
// base.Debug("recv DPD-REQ", cSess.IpAddr) // base.Debug("recv DPD-REQ", cSess.IpAddr)
if payloadOutCstp(cSess, sessdata.LTypeIPData, 0x04, nil) { pl.PType = 0x04
if payloadOutCstp(cSess, pl) {
return return
} }
case 0x04: case 0x04:
// log.Println("recv DPD-RESP") // log.Println("recv DPD-RESP")
case 0x00: // DATA case 0x00: // DATA
dataLen = binary.BigEndian.Uint16(hdata[4:6]) // 4,5 // 获取数据长度
if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[8:8+dataLen]) { dataLen = binary.BigEndian.Uint16(pl.Data[4:6]) // 4,5
// 去除数据头
copy(pl.Data, pl.Data[8:8+dataLen])
// 更新切片长度
pl.Data = pl.Data[:dataLen]
// pl.Data = append(pl.Data[:0], pl.Data[8:8+dataLen]...)
if payloadIn(cSess, pl) {
return return
} }
} }
putByte(hdata)
} }
} }
func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) { func cstpWrite(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) {
defer func() { defer func() {
base.Debug("cstpWrite return", cSess.IpAddr) base.Debug("cstpWrite return", cSess.IpAddr)
_ = conn.Close() _ = conn.Close()
@@ -82,36 +89,44 @@ func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
var ( var (
err error err error
n int n int
// header []byte pl *sessdata.Payload
payload *sessdata.Payload
) )
for { for {
select { select {
case payload = <-cSess.PayloadOutCstp: case pl = <-cSess.PayloadOutCstp:
case <-cSess.CloseChan: case <-cSess.CloseChan:
return return
} }
if payload.LType != sessdata.LTypeIPData { if pl.LType != sessdata.LTypeIPData {
continue continue
} }
h := []byte{'S', 'T', 'F', 0x01, 0x00, 0x00, payload.PType, 0x00} if pl.PType == 0x00 {
header := getByteZero() // 获取数据长度
header = append(header, h...) l := len(pl.Data)
if payload.PType == 0x00 { // data // 先扩容 +8
binary.BigEndian.PutUint16(header[4:6], uint16(len(payload.Data))) pl.Data = pl.Data[:l+8]
header = append(header, payload.Data...) // 数据后移
copy(pl.Data[8:], pl.Data)
// 添加头信息
copy(pl.Data[:8], plHeader)
// 更新头长度
binary.BigEndian.PutUint16(pl.Data[4:6], uint16(l))
} else {
pl.Data = append(pl.Data[:0], plHeader...)
// 设置头类型
pl.Data[6] = pl.PType
} }
n, err = conn.Write(header)
n, err = conn.Write(pl.Data)
if err != nil { if err != nil {
base.Error("write err", err) base.Error("write err", err)
return return
} }
putByte(header) putPayload(pl)
putPayload(payload)
// 限流设置 // 限流设置
err = cSess.RateLimit(n, false) err = cSess.RateLimit(n, false)

View File

@@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata" "github.com/bjdgyc/anylink/sessdata"
) )
@@ -24,21 +25,22 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
}() }()
var ( var (
err error
n int
dead = time.Duration(cSess.CstpDpd+5) * time.Second dead = time.Duration(cSess.CstpDpd+5) * time.Second
) )
go dtlsWrite(conn, dSess, cSess) go dtlsWrite(conn, dSess, cSess)
for { for {
err := conn.SetReadDeadline(time.Now().Add(dead)) err = conn.SetReadDeadline(utils.NowSec().Add(dead))
if err != nil { if err != nil {
base.Error("SetDeadline: ", err) base.Error("SetDeadline: ", err)
return return
} }
// hdata := make([]byte, BufferSize) pl := getPayload()
hdata := getByteFull() n, err = conn.Read(pl.Data)
n, err := conn.Read(hdata)
if err != nil { if err != nil {
base.Error("read hdata: ", err) base.Error("read hdata: ", err)
return return
@@ -50,7 +52,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
base.Error(err) base.Error(err)
} }
switch hdata[0] { switch pl.Data[0] {
case 0x07: // KEEPALIVE case 0x07: // KEEPALIVE
// do nothing // do nothing
// base.Debug("recv keepalive", cSess.IpAddr) // base.Debug("recv keepalive", cSess.IpAddr)
@@ -59,18 +61,23 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
return return
case 0x03: // DPD-REQ case 0x03: // DPD-REQ
// base.Debug("recv DPD-REQ", cSess.IpAddr) // base.Debug("recv DPD-REQ", cSess.IpAddr)
if payloadOutDtls(cSess, dSess, sessdata.LTypeIPData, 0x04, nil) { pl.PType = 0x04
if payloadOutDtls(cSess, dSess, pl) {
return return
} }
case 0x04: case 0x04:
// base.Debug("recv DPD-RESP", cSess.IpAddr) // base.Debug("recv DPD-RESP", cSess.IpAddr)
case 0x00: // DATA case 0x00: // DATA
if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[1:n]) { // 去除数据头
// copy(pl.Data, pl.Data[1:n])
// 更新切片长度
// pl.Data = pl.Data[:n-1]
pl.Data = append(pl.Data[:0], pl.Data[1:n]...)
if payloadIn(cSess, pl) {
return return
} }
} }
putByte(hdata)
} }
} }
@@ -82,36 +89,42 @@ func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnS
}() }()
var ( var (
// header []byte pl *sessdata.Payload
payload *sessdata.Payload
) )
for { for {
// dtls优先推送数据 // dtls优先推送数据
select { select {
case payload = <-cSess.PayloadOutDtls: case pl = <-cSess.PayloadOutDtls:
case <-dSess.CloseChan: case <-dSess.CloseChan:
return return
} }
if payload.LType != sessdata.LTypeIPData { if pl.LType != sessdata.LTypeIPData {
continue continue
} }
// header = []byte{payload.PType} // header = []byte{payload.PType}
header := getByteZero() if pl.PType == 0x00 { // data
header = append(header, payload.PType) // 获取数据长度
if payload.PType == 0x00 { // data l := len(pl.Data)
header = append(header, payload.Data...) // 先扩容 +1
pl.Data = pl.Data[:l+1]
// 数据后移
copy(pl.Data[1:], pl.Data)
// 添加头信息
pl.Data[0] = pl.PType
} else {
// 设置头类型
pl.Data = append(pl.Data[:0], pl.PType)
} }
n, err := conn.Write(header) n, err := conn.Write(pl.Data)
if err != nil { if err != nil {
base.Error("write err", err) base.Error("write err", err)
return return
} }
putByte(header) putPayload(pl)
putPayload(payload)
// 限流设置 // 限流设置
err = cSess.RateLimit(n, false) err = cSess.RateLimit(n, false)

View File

@@ -2,6 +2,7 @@ package handler
import ( import (
"fmt" "fmt"
"io"
"net" "net"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
@@ -17,18 +18,32 @@ import (
const bridgeName = "anylink0" const bridgeName = "anylink0"
var ( var (
bridgeIp net.IP // 网关mac地址
bridgeHw net.HardwareAddr gatewayHw net.HardwareAddr
) )
func checkTap() { type LinkDriver interface {
brFace, err := net.InterfaceByName(bridgeName) io.ReadWriteCloser
Name() string
}
func _setGateway() {
dstAddr := arpdis.Lookup(sessdata.IpPool.Ipv4Gateway, false)
gatewayHw = dstAddr.HardwareAddr
// 设置为静态地址映射
dstAddr.Type = arpdis.TypeStatic
arpdis.Add(dstAddr)
}
func _checkTapIp(ifName string) {
iFace, err := net.InterfaceByName(ifName)
if err != nil { if err != nil {
base.Fatal("testTap err: ", err) base.Fatal("testTap err: ", err)
} }
bridgeHw = brFace.HardwareAddr
addrs, err := brFace.Addrs() var ifIp net.IP
addrs, err := iFace.Addrs()
if err != nil { if err != nil {
base.Fatal("testTap err: ", err) base.Fatal("testTap err: ", err)
} }
@@ -37,17 +52,19 @@ func checkTap() {
if err != nil || ip.To4() == nil { if err != nil || ip.To4() == nil {
continue continue
} }
bridgeIp = ip ifIp = ip
}
if bridgeIp == nil && bridgeHw == nil {
base.Fatal("bridgeIp is err")
} }
if !sessdata.IpPool.Ipv4IPNet.Contains(bridgeIp) { if !sessdata.IpPool.Ipv4IPNet.Contains(ifIp) {
base.Fatal("bridgeIp or Ip network err") base.Fatal("tapIp or Ip network err")
} }
} }
func checkTap() {
_setGateway()
_checkTapIp(bridgeName)
}
// 创建tap网卡 // 创建tap网卡
func LinkTap(cSess *sessdata.ConnSession) error { func LinkTap(cSess *sessdata.ConnSession) error {
cfg := water.Config{ cfg := water.Config{
@@ -60,88 +77,94 @@ func LinkTap(cSess *sessdata.ConnSession) error {
return err return err
} }
cSess.TunName = ifce.Name() cSess.SetIfName(ifce.Name())
// arp on
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast on", ifce.Name(), cSess.Mtu) cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast on", ifce.Name(), cSess.Mtu)
cmdstr2 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name()) cmdstr2 := fmt.Sprintf("ip link set dev %s master %s", ifce.Name(), bridgeName)
cmdstr3 := fmt.Sprintf("ip link set dev %s master %s", ifce.Name(), bridgeName) err = execCmd([]string{cmdstr1, cmdstr2})
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
err = execCmd(cmdStrs)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
_ = ifce.Close() _ = ifce.Close()
return err return err
} }
go tapRead(ifce, cSess) cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
go tapWrite(ifce, cSess) execCmd([]string{cmdstr3})
go allTapRead(ifce, cSess)
go allTapWrite(ifce, cSess)
return nil return nil
} }
func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) { // ========================通用代码===========================
func allTapWrite(ifce LinkDriver, cSess *sessdata.ConnSession) {
defer func() { defer func() {
base.Debug("LinkTap return", cSess.IpAddr) base.Debug("LinkTap return", cSess.IpAddr)
cSess.Close() cSess.Close()
_ = ifce.Close() ifce.Close()
}() }()
var ( var (
err error err error
payload *sessdata.Payload dstHw net.HardwareAddr
frame ethernet.Frame pl *sessdata.Payload
frame = make(ethernet.Frame, BufferSize)
ipDst = net.IPv4(1, 2, 3, 4)
) )
for { for {
frame.Resize(BufferSize)
select { select {
case payload = <-cSess.PayloadIn: case pl = <-cSess.PayloadIn:
case <-cSess.CloseChan: case <-cSess.CloseChan:
return return
} }
// var frame ethernet.Frame // var frame ethernet.Frame
frame = getByteFull() switch pl.LType {
switch payload.LType {
default: default:
// log.Println(payload) // log.Println(payload)
case sessdata.LTypeEthernet: case sessdata.LTypeEthernet:
copy(frame, payload.Data) copy(frame, pl.Data)
frame = frame[:len(payload.Data)] frame = frame[:len(pl.Data)]
case sessdata.LTypeIPData: // 需要转换成 Ethernet 数据
data := payload.Data
ip_src := waterutil.IPv4Source(data) // packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
if waterutil.IsIPv6(data) || !ip_src.Equal(cSess.IpAddr) { // fmt.Println("wirteArp:", packet)
// 过滤掉IPv6的数据 case sessdata.LTypeIPData: // 需要转换成 Ethernet 数据
ipSrc := waterutil.IPv4Source(pl.Data)
if !ipSrc.Equal(cSess.IpAddr) {
// 非分配给客户端ip直接丢弃 // 非分配给客户端ip直接丢弃
continue continue
} }
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default) if waterutil.IsIPv6(pl.Data) {
// 过滤掉IPv6的数据
continue
}
// packet := gopacket.NewPacket(pl.Data, layers.LayerTypeIPv4, gopacket.Default)
// fmt.Println("get:", packet) // fmt.Println("get:", packet)
ip_dst := waterutil.IPv4Destination(data) // 手动设置ipv4地址
// fmt.Println("get:", ip_src, ip_dst) ipDst[12] = pl.Data[16]
ipDst[13] = pl.Data[17]
ipDst[14] = pl.Data[18]
ipDst[15] = pl.Data[19]
var dstHw net.HardwareAddr dstHw = gatewayHw
if !sessdata.IpPool.Ipv4IPNet.Contains(ip_dst) || ip_dst.Equal(sessdata.IpPool.Ipv4Gateway) { if sessdata.IpPool.Ipv4IPNet.Contains(ipDst) {
// 不是同一网段使用网关mac地址 dstAddr := arpdis.Lookup(ipDst, true)
dstAddr := arpdis.Lookup(sessdata.IpPool.Ipv4Gateway, false)
dstHw = dstAddr.HardwareAddr
} else {
dstAddr := arpdis.Lookup(ip_dst, true)
// fmt.Println("dstAddr", dstAddr) // fmt.Println("dstAddr", dstAddr)
if dstAddr != nil { if dstAddr != nil {
dstHw = dstAddr.HardwareAddr dstHw = dstAddr.HardwareAddr
} else { }
dstHw = bridgeHw
} }
} // fmt.Println("Gateway", ipSrc, ipDst, dstHw)
// fmt.Println("Gateway", ip_dst, dstAddr.HardwareAddr) frame.Prepare(dstHw, cSess.MacHw, ethernet.NotTagged, ethernet.IPv4, len(pl.Data))
copy(frame[12+2:], pl.Data)
frame.Prepare(dstHw, cSess.MacHw, ethernet.NotTagged, ethernet.IPv4, len(data))
copy(frame[12+2:], data)
} }
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default) // packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
@@ -152,28 +175,26 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
return return
} }
putByte(frame) putPayload(pl)
putPayload(payload)
} }
} }
func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) { func allTapRead(ifce LinkDriver, cSess *sessdata.ConnSession) {
defer func() { defer func() {
base.Debug("tapRead return", cSess.IpAddr) base.Debug("tapRead return", cSess.IpAddr)
_ = ifce.Close() ifce.Close()
}() }()
var ( var (
err error err error
n int n int
buf []byte data []byte
frame ethernet.Frame frame = make(ethernet.Frame, BufferSize)
) )
for { for {
// var frame ethernet.Frame frame.Resize(BufferSize)
// frame.Resize(BufferSize)
frame = getByteFull()
n, err = ifce.Read(frame) n, err = ifce.Read(frame)
if err != nil { if err != nil {
base.Error("tap Read err", n, err) base.Error("tap Read err", n, err)
@@ -183,14 +204,12 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
switch frame.Ethertype() { switch frame.Ethertype() {
default: default:
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
// fmt.Println(packet)
continue continue
case ethernet.IPv6: case ethernet.IPv6:
continue continue
case ethernet.IPv4: case ethernet.IPv4:
// 发送IP数据 // 发送IP数据
data := frame.Payload() data = frame.Payload()
ip_dst := waterutil.IPv4Destination(data) ip_dst := waterutil.IPv4Destination(data)
if !ip_dst.Equal(cSess.IpAddr) { if !ip_dst.Equal(cSess.IpAddr) {
@@ -202,13 +221,18 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default) // packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
// fmt.Println("put:", packet) // fmt.Println("put:", packet)
if payloadOut(cSess, sessdata.LTypeIPData, 0x00, data) { pl := getPayload()
// 拷贝数据到pl
copy(pl.Data, data)
// 更新切片长度
pl.Data = pl.Data[:len(data)]
if payloadOut(cSess, pl) {
return return
} }
case ethernet.ARP: case ethernet.ARP:
// 暂时仅实现了ARP协议 // 暂时仅实现了ARP协议
packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default) packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.NoCopy)
layer := packet.Layer(layers.LayerTypeARP) layer := packet.Layer(layers.LayerTypeARP)
arpReq := layer.(*layers.ARP) arpReq := layer.(*layers.ARP)
@@ -217,13 +241,13 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
continue continue
} }
// fmt.Println("arp", net.IP(arpReq.SourceProtAddress), sess.Ip) // fmt.Println("arp", time.Now(), net.IP(arpReq.SourceProtAddress), cSess.IpAddr)
// fmt.Println(packet) // fmt.Println(packet)
// 返回ARP数据 // 返回ARP数据
src := &arpdis.Addr{IP: cSess.IpAddr, HardwareAddr: cSess.MacHw} src := &arpdis.Addr{IP: cSess.IpAddr, HardwareAddr: cSess.MacHw}
dst := &arpdis.Addr{IP: arpReq.SourceProtAddress, HardwareAddr: frame.Source()} dst := &arpdis.Addr{IP: arpReq.SourceProtAddress, HardwareAddr: arpReq.SourceHwAddress}
buf, err = arpdis.NewARPReply(src, dst) data, err = arpdis.NewARPReply(src, dst)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
return return
@@ -231,21 +255,23 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
// 从接受的arp信息添加arp地址 // 从接受的arp信息添加arp地址
addr := &arpdis.Addr{ addr := &arpdis.Addr{
IP: make([]byte, len(arpReq.SourceProtAddress)), IP: append([]byte{}, dst.IP...),
HardwareAddr: make([]byte, len(frame.Source())), HardwareAddr: append([]byte{}, dst.HardwareAddr...),
} }
// addr.IP = arpReq.SourceProtAddress
// addr.HardwareAddr = frame.Source()
copy(addr.IP, arpReq.SourceProtAddress)
copy(addr.HardwareAddr, frame.Source())
arpdis.Add(addr) arpdis.Add(addr)
if payloadIn(cSess, sessdata.LTypeEthernet, 0x00, buf) { pl := getPayload()
// 设置为二层数据类型
pl.LType = sessdata.LTypeEthernet
// 拷贝数据到pl
copy(pl.Data, data)
// 更新切片长度
pl.Data = pl.Data[:len(data)]
if payloadIn(cSess, pl) {
return return
} }
} }
putByte(frame)
} }
} }

View File

@@ -40,21 +40,21 @@ func LinkTun(cSess *sessdata.ConnSession) error {
return err return err
} }
// log.Printf("Interface Name: %s\n", ifce.Name()) // log.Printf("Interface Name: %s\n", ifce.Name())
cSess.SetTunName(ifce.Name()) cSess.SetIfName(ifce.Name())
// cSess.TunName = ifce.Name()
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu) cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu)
cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32", cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr) ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name()) err = execCmd([]string{cmdstr1, cmdstr2})
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
err = execCmd(cmdStrs)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
_ = ifce.Close() _ = ifce.Close()
return err return err
} }
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
execCmd([]string{cmdstr3})
go tunRead(ifce, cSess) go tunRead(ifce, cSess)
go tunWrite(ifce, cSess) go tunWrite(ifce, cSess)
return nil return nil
@@ -69,23 +69,23 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
var ( var (
err error err error
payload *sessdata.Payload pl *sessdata.Payload
) )
for { for {
select { select {
case payload = <-cSess.PayloadIn: case pl = <-cSess.PayloadIn:
case <-cSess.CloseChan: case <-cSess.CloseChan:
return return
} }
_, err = ifce.Write(payload.Data) _, err = ifce.Write(pl.Data)
if err != nil { if err != nil {
base.Error("tun Write err", err) base.Error("tun Write err", err)
return return
} }
putPayload(payload) putPayload(pl)
} }
} }
@@ -101,13 +101,16 @@ func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
for { for {
// data := make([]byte, BufferSize) // data := make([]byte, BufferSize)
data := getByteFull() pl := getPayload()
n, err = ifce.Read(data) n, err = ifce.Read(pl.Data)
if err != nil { if err != nil {
base.Error("tun Read err", n, err) base.Error("tun Read err", n, err)
return return
} }
// 更新数据长度
pl.Data = (pl.Data)[:n]
// data = data[:n] // data = data[:n]
// ip_src := waterutil.IPv4Source(data) // ip_src := waterutil.IPv4Source(data)
// ip_dst := waterutil.IPv4Destination(data) // ip_dst := waterutil.IPv4Destination(data)
@@ -116,10 +119,8 @@ func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default) // packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
// fmt.Println("read:", packet) // fmt.Println("read:", packet)
if payloadOut(cSess, sessdata.LTypeIPData, 0x00, data[:n]) { if payloadOut(cSess, pl) {
return return
} }
putByte(data)
} }
} }

View File

@@ -96,6 +96,9 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
} }
// 允许的路由 // 允许的路由
for _, v := range cSess.Group.RouteInclude { for _, v := range cSess.Group.RouteInclude {
if v.Val == "all" {
continue
}
w.Header().Add("X-CSTP-Split-Include", v.IpMask) w.Header().Add("X-CSTP-Split-Include", v.IpMask)
} }
// 不允许的路由 // 不允许的路由
@@ -147,7 +150,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
base.Debug(buf.String()) base.Debug(buf.String())
hj := w.(http.Hijacker) hj := w.(http.Hijacker)
conn, _, err := hj.Hijack() conn, bufRW, err := hj.Hijack()
if err != nil { if err != nil {
base.Error(err) base.Error(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@@ -160,11 +163,14 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
err = LinkTun(cSess) err = LinkTun(cSess)
case base.LinkModeTAP: case base.LinkModeTAP:
err = LinkTap(cSess) err = LinkTap(cSess)
case base.LinkModeMacvtap:
err = LinkMacvtap(cSess)
} }
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) conn.Close()
base.Error(err)
return return
} }
go LinkCstp(conn, cSess) go LinkCstp(conn, bufRW, cSess)
} }

137
server/handler/link_vtap.go Normal file
View File

@@ -0,0 +1,137 @@
package handler
import (
"fmt"
"net"
"os"
"strings"
"syscall"
"unsafe"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata"
)
// link vtap
const vTapPrefix = "lvtap"
type Vtap struct {
*os.File
ifName string
}
func (v *Vtap) Close() error {
v.File.Close()
cmdstr := fmt.Sprintf("ip link del %s", v.ifName)
return execCmd([]string{cmdstr})
}
func checkMacvtap() {
_setGateway()
_checkTapIp(base.Cfg.Ipv4Master)
ifName := "anylinkMacvtap"
// 加载 macvtap
cmdstr0 := fmt.Sprintf("modprobe -i 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})
if err != nil {
base.Fatal(err)
}
}
// 创建 Macvtap 网卡
func LinkMacvtap(cSess *sessdata.ConnSession) error {
capL := sessdata.IpPool.IpLongMax - sessdata.IpPool.IpLongMin
ipN := utils.Ip2long(cSess.IpAddr) % capL
ifName := fmt.Sprintf("%s%d", vTapPrefix, ipN)
cSess.SetIfName(ifName)
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)
err := execCmd([]string{cmdstr1, cmdstr2})
if err != nil {
base.Error(err)
return err
}
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifName)
execCmd([]string{cmdstr3})
return createVtap(cSess, ifName)
}
func checkIpvtap() {
}
// 创建 Ipvtap 网卡
func LinkIpvtap(cSess *sessdata.ConnSession) error {
return nil
}
type ifReq struct {
Name [0x10]byte
Flags uint16
pad [0x28 - 0x10 - 2]byte
}
func createVtap(cSess *sessdata.ConnSession, ifName string) error {
// 初始化 ifName
inf, err := net.InterfaceByName(ifName)
if err != nil {
base.Error(err)
return err
}
tName := fmt.Sprintf("/dev/tap%d", inf.Index)
var fdInt int
fdInt, err = syscall.Open(tName, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
if err != nil {
return err
}
var flags uint16 = syscall.IFF_TAP | syscall.IFF_NO_PI
var req ifReq
req.Flags = flags
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(fdInt),
uintptr(syscall.TUNSETIFF),
uintptr(unsafe.Pointer(&req)),
)
if errno != 0 {
return os.NewSyscallError("ioctl", errno)
}
file := os.NewFile(uintptr(fdInt), tName)
ifce := &Vtap{file, ifName}
go allTapRead(ifce, cSess)
go allTapWrite(ifce, cSess)
return nil
}
// 销毁未关闭的vtap
func destroyVtap() {
its, err := net.Interfaces()
if err != nil {
base.Error(err)
return
}
for _, v := range its {
if strings.HasPrefix(v.Name, vTapPrefix) {
// 删除原来的网卡
cmdstr := fmt.Sprintf("ip link del %s", v.Name)
execCmd([]string{cmdstr})
}
}
}

View File

@@ -1,31 +1,30 @@
package handler package handler
import ( import (
"encoding/binary"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata" "github.com/bjdgyc/anylink/sessdata"
"github.com/songgao/water/waterutil" "github.com/songgao/water/waterutil"
) )
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool { func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
pl := getPayload() if pl.LType == sessdata.LTypeIPData && pl.PType == 0x00 {
pl.LType = lType
pl.PType = pType
pl.Data = append(pl.Data, data...)
return payloadInData(cSess, pl)
}
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
// 进行Acl规则判断 // 进行Acl规则判断
check := checkLinkAcl(cSess.Group, payload) check := checkLinkAcl(cSess.Group, pl)
if !check { if !check {
// 校验不通过直接丢弃 // 校验不通过直接丢弃
return false return false
} }
logAudit(cSess, pl)
}
closed := false closed := false
select { select {
case cSess.PayloadIn <- payload: case cSess.PayloadIn <- pl:
case <-cSess.CloseChan: case <-cSess.CloseChan:
closed = true closed = true
} }
@@ -33,21 +32,16 @@ func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool
return closed return closed
} }
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool { func payloadOut(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
dSess := cSess.GetDtlsSession() dSess := cSess.GetDtlsSession()
if dSess == nil { if dSess == nil {
return payloadOutCstp(cSess, lType, pType, data) return payloadOutCstp(cSess, pl)
} else { } else {
return payloadOutDtls(cSess, dSess, lType, pType, data) return payloadOutDtls(cSess, dSess, pl)
} }
} }
func payloadOutCstp(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool { func payloadOutCstp(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
pl := getPayload()
pl.LType = lType
pl.PType = pType
pl.Data = append(pl.Data, data...)
closed := false closed := false
select { select {
@@ -59,12 +53,7 @@ func payloadOutCstp(cSess *sessdata.ConnSession, lType sessdata.LType, pType byt
return closed return closed
} }
func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lType sessdata.LType, pType byte, data []byte) bool { func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, pl *sessdata.Payload) bool {
pl := getPayload()
pl.LType = lType
pl.PType = pType
pl.Data = append(pl.Data, data...)
select { select {
case cSess.PayloadOutDtls <- pl: case cSess.PayloadOutDtls <- pl:
case <-dSess.CloseChan: case <-dSess.CloseChan:
@@ -74,27 +63,29 @@ func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lT
} }
// Acl规则校验 // Acl规则校验
func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool { func checkLinkAcl(group *dbdata.Group, pl *sessdata.Payload) bool {
if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 { if pl.LType == sessdata.LTypeIPData && pl.PType == 0x00 && len(group.LinkAcl) > 0 {
} else { } else {
return true return true
} }
ip_dst := waterutil.IPv4Destination(payload.Data) ipDst := waterutil.IPv4Destination(pl.Data)
ip_port := waterutil.IPv4DestinationPort(payload.Data) ipPort := waterutil.IPv4DestinationPort(pl.Data)
ipProto := waterutil.IPv4Protocol(pl.Data)
// fmt.Println("sent:", ip_dst, ip_port) // fmt.Println("sent:", ip_dst, ip_port)
// 优先放行dns端口 // 优先放行dns端口
for _, v := range group.ClientDns { for _, v := range group.ClientDns {
if v.Val == ip_dst.String() && ip_port == 53 { if v.Val == ipDst.String() && ipPort == 53 {
return true return true
} }
} }
for _, v := range group.LinkAcl { for _, v := range group.LinkAcl {
// 循环判断ip和端口 // 循环判断ip和端口
if v.IpNet.Contains(ip_dst) { if v.IpNet.Contains(ipDst) {
if v.Port == ip_port || v.Port == 0 { // 放行允许ip的ping
if v.Port == ipPort || v.Port == 0 || ipProto == waterutil.ICMP {
if v.Action == dbdata.Allow { if v.Action == dbdata.Allow {
return true return true
} else { } else {
@@ -106,3 +97,53 @@ func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
return false return false
} }
// 访问日志审计
func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) {
if base.Cfg.AuditInterval < 0 {
return
}
ipProto := waterutil.IPv4Protocol(pl.Data)
// 只统计 tcp和udp 的访问
switch ipProto {
case waterutil.TCP:
case waterutil.UDP:
default:
return
}
ipSrc := waterutil.IPv4Source(pl.Data)
ipDst := waterutil.IPv4Destination(pl.Data)
ipPort := waterutil.IPv4DestinationPort(pl.Data)
b := getByte34()
key := *b
copy(key[:16], ipSrc)
copy(key[16:32], ipDst)
binary.BigEndian.PutUint16(key[32:34], ipPort)
s := utils.BytesToString(key)
nu := utils.NowSec().Unix()
// 判断已经存在,并且没有过期
v, ok := cSess.IpAuditMap[s]
if ok && nu-v < int64(base.Cfg.AuditInterval) {
// 回收byte对象
putByte34(b)
return
}
cSess.IpAuditMap[s] = nu
audit := dbdata.AccessAudit{
Username: cSess.Sess.Username,
Protocol: uint8(ipProto),
Src: ipSrc.String(),
Dst: ipDst.String(),
DstPort: ipPort,
CreatedAt: utils.NowSec(),
}
_ = dbdata.Add(audit)
}

View File

@@ -3,13 +3,26 @@ package handler
import ( import (
"sync" "sync"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/sessdata" "github.com/bjdgyc/anylink/sessdata"
) )
// 不允许直接修改
// [6] => PType
var plHeader = []byte{
'S', 'T', 'F', 1,
0x00, 0x00, /* Length */
0x00, /* Type */
0x00, /* Unknown */
}
var plPool = sync.Pool{ var plPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
b := make([]byte, BufferSize)
pl := sessdata.Payload{ pl := sessdata.Payload{
Data: make([]byte, 0, BufferSize), LType: sessdata.LTypeIPData,
PType: 0x00,
Data: b,
} }
// fmt.Println("plPool-init", len(pl.Data), cap(pl.Data)) // fmt.Println("plPool-init", len(pl.Data), cap(pl.Data))
return &pl return &pl
@@ -22,31 +35,55 @@ func getPayload() *sessdata.Payload {
} }
func putPayload(pl *sessdata.Payload) { func putPayload(pl *sessdata.Payload) {
pl.LType = 0 // 错误数据丢弃
pl.PType = 0 if cap(pl.Data) != BufferSize {
pl.Data = pl.Data[:0] base.Warn("payload cap is err", cap(pl.Data))
return
}
pl.LType = sessdata.LTypeIPData
pl.PType = 0x00
pl.Data = pl.Data[:BufferSize]
plPool.Put(pl) plPool.Put(pl)
} }
var bytePool = sync.Pool{ var bytePool = sync.Pool{
New: func() interface{} { New: func() interface{} {
b := make([]byte, 0, BufferSize) b := make([]byte, BufferSize)
// fmt.Println("bytePool-init") // fmt.Println("bytePool-init")
return b return &b
}, },
} }
func getByteZero() []byte { func getByteZero() *[]byte {
b := bytePool.Get().([]byte) b := bytePool.Get().(*[]byte)
*b = (*b)[:0]
return b return b
} }
func getByteFull() []byte { func getByteFull() *[]byte {
b := bytePool.Get().([]byte) b := bytePool.Get().(*[]byte)
b = b[:BufferSize]
return b return b
} }
func putByte(b []byte) { func putByte(b *[]byte) {
b = b[:0] *b = (*b)[:BufferSize]
bytePool.Put(b) bytePool.Put(b)
} }
// 长度 34 小对象
var byte34Pool = sync.Pool{
New: func() interface{} {
b := make([]byte, 34)
return &b
},
}
func getByte34() *[]byte {
b := byte34Pool.Get().(*[]byte)
return b
}
func putByte34(b *[]byte) {
*b = (*b)[:34]
byte34Pool.Put(b)
}

View File

@@ -0,0 +1,44 @@
package handler
import (
"testing"
)
// go test -bench=. -benchmem
// 去除数据头
func BenchmarkHeaderCopy(b *testing.B) {
l := 1500
for i := 0; i < b.N; i++ {
b.StopTimer()
pl := getPayload()
// 初始化数据
pl.Data = pl.Data[:l]
b.StartTimer()
dataLen := l - 8
copy(pl.Data, pl.Data[8:8+dataLen])
// 更新切片长度
pl.Data = pl.Data[:dataLen]
b.StopTimer()
putPayload(pl)
}
}
func BenchmarkHeaderAppend(b *testing.B) {
l := 1500
for i := 0; i < b.N; i++ {
b.StopTimer()
pl := getPayload()
// 初始化数据
pl.Data = pl.Data[:l]
b.StartTimer()
dataLen := l - 8
pl.Data = append(pl.Data[:0], pl.Data[:8+dataLen]...)
b.StopTimer()
putPayload(pl)
}
}

View File

@@ -2,27 +2,52 @@ package handler
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"log" "log"
"net" "net"
"net/http" "net/http"
"os"
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/proxyproto" "github.com/bjdgyc/anylink/pkg/proxyproto"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pion/dtls/v2/pkg/crypto/selfsign"
) )
func startTls() { func startTls() {
addr := base.Cfg.ServerAddr
certFile := base.Cfg.CertFile var (
keyFile := base.Cfg.CertKey err error
addr = base.Cfg.ServerAddr
certFile = base.Cfg.CertFile
keyFile = base.Cfg.CertKey
certs = make([]tls.Certificate, 1)
ln net.Listener
)
// 判断证书文件
_, err = os.Stat(certFile)
if errors.Is(err, os.ErrNotExist) {
// 自动生成证书
certs[0], err = selfsign.GenerateSelfSignedWithDNS("vpn.anylink")
} else {
// 使用自定义证书
certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
}
if err != nil {
panic(err)
}
// 设置tls信息 // 设置tls信息
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"}, NextProtos: []string{"http/1.1"},
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true, Certificates: certs,
// InsecureSkipVerify: true,
} }
srv := &http.Server{ srv := &http.Server{
Addr: addr, Addr: addr,
@@ -31,9 +56,7 @@ func startTls() {
ErrorLog: base.GetBaseLog(), ErrorLog: base.GetBaseLog(),
} }
var ln net.Listener ln, err = net.Listen("tcp", addr)
ln, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -44,7 +67,7 @@ func startTls() {
} }
base.Info("listen server", addr) base.Info("listen server", addr)
err = srv.ServeTLS(ln, certFile, keyFile) err = srv.ServeTLS(ln, "", "")
if err != nil { if err != nil {
base.Fatal(err) base.Fatal(err)
} }
@@ -56,6 +79,9 @@ func initRoute() http.Handler {
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost) r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect) r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet) r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet)
r.HandleFunc("/profile.xml", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(auth_profile))
}).Methods(http.MethodGet)
r.PathPrefix("/files/").Handler( r.PathPrefix("/files/").Handler(
http.StripPrefix("/files/", http.StripPrefix("/files/",
http.FileServer(http.Dir(base.Cfg.FilesPath)), http.FileServer(http.Dir(base.Cfg.FilesPath)),

View File

@@ -11,10 +11,17 @@ func Start() {
dbdata.Start() dbdata.Start()
sessdata.Start() sessdata.Start()
switch base.Cfg.LinkMode {
case base.LinkModeTUN:
checkTun() checkTun()
if base.Cfg.LinkMode == base.LinkModeTAP { case base.LinkModeTAP:
checkTap() checkTap()
case base.LinkModeMacvtap:
checkMacvtap()
default:
base.Fatal("LinkMode is err")
} }
go admin.StartAdmin() go admin.StartAdmin()
go startTls() go startTls()
go startDtls() go startDtls()
@@ -22,4 +29,5 @@ func Start() {
func Stop() { func Stop() {
_ = dbdata.Stop() _ = dbdata.Stop()
destroyVtap()
} }

View File

@@ -1,21 +1,29 @@
// AnyLink 是一个企业级远程办公vpn软件可以支持多人同时在线使用。 // AnyLink 是一个企业级远程办公vpn软件可以支持多人同时在线使用。
// +build !windows
package main package main
import ( import (
"embed"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/bjdgyc/anylink/admin"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/handler" "github.com/bjdgyc/anylink/handler"
) )
//go:embed ui
var uiData embed.FS
// 程序版本 // 程序版本
var COMMIT_ID string var CommitId string
func main() { func main() {
base.CommitId = COMMIT_ID base.CommitId = CommitId
admin.UiData = uiData
base.Start() base.Start()
handler.Start() handler.Start()

View File

@@ -4,6 +4,8 @@ import (
"net" "net"
"sync" "sync"
"time" "time"
"github.com/bjdgyc/anylink/pkg/utils"
) )
const ( const (
@@ -16,7 +18,7 @@ const (
) )
var ( var (
table = make(map[string]*Addr) table = make(map[string]*Addr, 128)
tableMu sync.RWMutex tableMu sync.RWMutex
) )
@@ -40,25 +42,25 @@ func Lookup(ip net.IP, onlyTable bool) *Addr {
// Add adds a IP-MAC map to a runtime ARP table. // Add adds a IP-MAC map to a runtime ARP table.
func tableLookup(ip net.IP) *Addr { func tableLookup(ip net.IP) *Addr {
tableMu.Lock() tableMu.RLock()
addr := table[ip.To4().String()] addr := table[ip.To4().String()]
tableMu.Unlock() tableMu.RUnlock()
if addr == nil { if addr == nil {
return nil return nil
} }
// 判断老化过期时间 // 判断老化过期时间
tsub := time.Since(addr.disTime) tSub := utils.NowSec().Sub(addr.disTime)
switch addr.Type { switch addr.Type {
case TypeStatic:
case TypeNormal: case TypeNormal:
if tsub > StaleTimeNormal { if tSub > StaleTimeNormal {
return nil return nil
} }
case TypeUnreachable: case TypeUnreachable:
if tsub > StaleTimeUnreachable { if tSub > StaleTimeUnreachable {
return nil return nil
} }
case TypeStatic:
} }
return addr return addr
@@ -70,7 +72,7 @@ func Add(addr *Addr) {
return return
} }
if addr.disTime.IsZero() { if addr.disTime.IsZero() {
addr.disTime = time.Now() addr.disTime = utils.NowSec()
} }
ip := addr.IP.To4().String() ip := addr.IP.To4().String()
tableMu.Lock() tableMu.Lock()

View File

@@ -19,7 +19,8 @@ func doLookup(ip net.IP) *Addr {
err := doPing(ip.String()) err := doPing(ip.String())
if err != nil { if err != nil {
// log.Println(err) // log.Println(err)
addr := &Addr{IP: ip, Type: TypeUnreachable} addr := &Addr{IP: net.IPv4(1, 2, 3, 4), Type: TypeUnreachable}
copy(addr.IP, ip)
return addr return addr
} }
@@ -50,7 +51,9 @@ func doArpShow(ip net.IP) *Addr {
return nil return nil
} }
return &Addr{IP: ip, HardwareAddr: mac} addr := &Addr{IP: net.IPv4(1, 2, 3, 4), HardwareAddr: mac}
copy(addr.IP, ip)
return addr
} }
// IP address HW type Flags HW address Mask Device // IP address HW type Flags HW address Mask Device

17
server/pkg/utils/ip.go Normal file
View File

@@ -0,0 +1,17 @@
package utils
import (
"encoding/binary"
"net"
)
func Long2ip(i uint32) net.IP {
ip := make([]byte, 4)
binary.BigEndian.PutUint32(ip, i)
return ip
}
func Ip2long(ip net.IP) uint32 {
ip = ip.To4()
return binary.BigEndian.Uint32(ip)
}

View File

@@ -0,0 +1,20 @@
package utils
import (
"unsafe"
)
// BytesToString converts byte slice to string.
func BytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// StringToBytes converts string to byte slice.
func StringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(
&struct {
string
Cap int
}{s, len(s)},
))
}

View File

@@ -3,11 +3,30 @@ package utils
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"sync/atomic"
"time" "time"
) )
var (
// 每秒时间缓存
timeNowSec = &atomic.Value{}
)
func init() { func init() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
timeNowSec.Store(time.Now())
go func() {
tick := time.NewTicker(time.Second * 1)
for c := range tick.C {
timeNowSec.Store(c)
}
}()
}
func NowSec() time.Time {
t := timeNowSec.Load()
return t.(time.Time)
} }
func InArrStr(arr []string, str string) bool { func InArrStr(arr []string, str string) bool {

View File

@@ -1,13 +1,13 @@
package sessdata package sessdata
import ( import (
"encoding/binary"
"net" "net"
"sync" "sync"
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
) )
var ( var (
@@ -43,22 +43,11 @@ func initIpPool() {
// max := min | uint32(math.Pow(2, float64(32-one))-1) // max := min | uint32(math.Pow(2, float64(32-one))-1)
// ip地址池 // ip地址池
IpPool.IpLongMin = ip2long(net.ParseIP(base.Cfg.Ipv4Start)) IpPool.IpLongMin = utils.Ip2long(net.ParseIP(base.Cfg.Ipv4Start))
IpPool.IpLongMax = ip2long(net.ParseIP(base.Cfg.Ipv4End)) IpPool.IpLongMax = utils.Ip2long(net.ParseIP(base.Cfg.Ipv4End))
} }
func long2ip(i uint32) net.IP { // AcquireIp 获取动态ip
ip := make([]byte, 4)
binary.BigEndian.PutUint32(ip, i)
return ip
}
func ip2long(ip net.IP) uint32 {
ip = ip.To4()
return binary.BigEndian.Uint32(ip)
}
// 获取动态ip
func AcquireIp(username, macAddr string) net.IP { func AcquireIp(username, macAddr string) net.IP {
IpPool.mux.Lock() IpPool.mux.Lock()
defer IpPool.mux.Unlock() defer IpPool.mux.Unlock()
@@ -67,43 +56,30 @@ func AcquireIp(username, macAddr string) net.IP {
// 判断已经分配过 // 判断已经分配过
mi := &dbdata.IpMap{} mi := &dbdata.IpMap{}
err := dbdata.One("MacAddr", macAddr, mi) err := dbdata.One("mac_addr", macAddr, mi)
if err == nil { if err == nil {
ip := mi.IpAddr ipStr := mi.IpAddr
ipStr := ip.String() ip := net.ParseIP(ipStr)
// 跳过活跃连接
_, ok := ipActive[ipStr]
// 检测原有ip是否在新的ip池内 // 检测原有ip是否在新的ip池内
if IpPool.Ipv4IPNet.Contains(ip) { if IpPool.Ipv4IPNet.Contains(ip) && !ok {
mi.Username = username mi.Username = username
mi.LastLogin = tNow mi.LastLogin = tNow
// 回写db数据 // 回写db数据
_ = dbdata.Save(mi) _ = dbdata.Add(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} else {
_ = dbdata.Del(mi)
}
} }
// 全局遍历未分配ip _ = dbdata.Del(mi)
// 优先获取没有使用的ip
for i := IpPool.IpLongMin; i <= IpPool.IpLongMax; i++ {
ip := long2ip(i)
ipStr := ip.String()
mi := &dbdata.IpMap{}
err := dbdata.One("IpAddr", ip, mi)
if err != nil && dbdata.CheckErrNotFound(err) {
// 该ip没有被使用
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
_ = dbdata.Save(mi)
ipActive[ipStr] = true
return ip
}
} }
farIp := &dbdata.IpMap{LastLogin: tNow} farIp := &dbdata.IpMap{LastLogin: tNow}
// 遍历超过租期ip // 全局遍历超过租期ip
for i := IpPool.IpLongMin; i <= IpPool.IpLongMax; i++ { for i := IpPool.IpLongMin; i <= IpPool.IpLongMax; i++ {
ip := long2ip(i) ip := utils.Long2ip(i)
ipStr := ip.String() ipStr := ip.String()
// 跳过活跃连接 // 跳过活跃连接
@@ -112,11 +88,20 @@ func AcquireIp(username, macAddr string) net.IP {
} }
v := &dbdata.IpMap{} v := &dbdata.IpMap{}
err := dbdata.One("IpAddr", ip, v) err = dbdata.One("ip_addr", ipStr, v)
if err != nil { if err != nil {
if dbdata.CheckErrNotFound(err) {
// 该ip没有被使用
mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
_ = dbdata.Add(mi)
ipActive[ipStr] = true
return ip
}
base.Error(err) base.Error(err)
return nil return nil
} }
// 跳过ip保留
if v.Keep { if v.Keep {
continue continue
} }
@@ -124,9 +109,9 @@ func AcquireIp(username, macAddr string) net.IP {
// 已经超过租期 // 已经超过租期
if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second { if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
_ = dbdata.Del(v) _ = dbdata.Del(v)
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
// 重写db数据 // 重写db数据
_ = dbdata.Save(mi) _ = dbdata.Add(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -143,11 +128,11 @@ func AcquireIp(username, macAddr string) net.IP {
} }
// 使用最早登陆的mac ip // 使用最早登陆的mac ip
ip := farIp.IpAddr ipStr := farIp.IpAddr
ipStr := ip.String() ip := net.ParseIP(ipStr)
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
// 回写db数据 // 回写db数据
_ = dbdata.Save(mi) _ = dbdata.Add(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -159,9 +144,9 @@ func ReleaseIp(ip net.IP, macAddr string) {
delete(ipActive, ip.String()) delete(ipActive, ip.String())
mi := &dbdata.IpMap{} mi := &dbdata.IpMap{}
err := dbdata.One("IpAddr", ip, mi) err := dbdata.One("ip_addr", ip.String(), mi)
if err == nil { if err == nil {
mi.LastLogin = time.Now() mi.LastLogin = time.Now()
_ = dbdata.Save(mi) _ = dbdata.Add(mi)
} }
} }

View File

@@ -15,7 +15,8 @@ import (
func preData(tmpDir string) { func preData(tmpDir string) {
base.Test() base.Test()
tmpDb := path.Join(tmpDir, "test.db") tmpDb := path.Join(tmpDir, "test.db")
base.Cfg.DbFile = tmpDb base.Cfg.DbType = "sqlite3"
base.Cfg.DbSource = tmpDb
base.Cfg.Ipv4CIDR = "192.168.3.0/24" base.Cfg.Ipv4CIDR = "192.168.3.0/24"
base.Cfg.Ipv4Start = "192.168.3.1" base.Cfg.Ipv4Start = "192.168.3.1"
base.Cfg.Ipv4End = "192.168.3.199" base.Cfg.Ipv4End = "192.168.3.199"
@@ -27,7 +28,7 @@ func preData(tmpDir string) {
Name: "group1", Name: "group1",
Bandwidth: 1000, Bandwidth: 1000,
} }
_ = dbdata.Save(&group) _ = dbdata.Add(&group)
initIpPool() initIpPool()
} }

View File

@@ -54,13 +54,13 @@ func OnlineSess() []Online {
Group: v.Group, Group: v.Group,
MacAddr: v.MacAddr, MacAddr: v.MacAddr,
RemoteAddr: v.CSess.RemoteAddr, RemoteAddr: v.CSess.RemoteAddr,
TunName: v.CSess.TunName, TunName: v.CSess.IfName,
Mtu: v.CSess.Mtu, Mtu: v.CSess.Mtu,
Client: v.CSess.Client, Client: v.CSess.Client,
BandwidthUp: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthUpPeriod)) + "/s", BandwidthUp: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthUpPeriod)) + "/s",
BandwidthDown: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthDownPeriod)) + "/s", BandwidthDown: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthDownPeriod)) + "/s",
BandwidthUpAll: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthUpAll)), BandwidthUpAll: utils.HumanByte(atomic.LoadUint64(&v.CSess.BandwidthUpAll)),
BandwidthDownAll: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthDownAll)), BandwidthDownAll: utils.HumanByte(atomic.LoadUint64(&v.CSess.BandwidthDownAll)),
LastLogin: v.LastLogin, LastLogin: v.LastLogin,
} }
datas = append(datas, val) datas = append(datas, val)

View File

@@ -8,8 +8,8 @@ const (
) )
type Payload struct { type Payload struct {
PType byte // payload types
LType LType // LinkType LType LType // LinkType
PType byte // payload types
Data []byte Data []byte
} }

View File

@@ -32,7 +32,7 @@ type ConnSession struct {
MacHw net.HardwareAddr // 客户端mac地址,从Session取出 MacHw net.HardwareAddr // 客户端mac地址,从Session取出
RemoteAddr string RemoteAddr string
Mtu int Mtu int
TunName string IfName string
Client string // 客户端 mobile pc Client string // 客户端 mobile pc
CstpDpd int CstpDpd int
Group *dbdata.Group Group *dbdata.Group
@@ -41,14 +41,14 @@ type ConnSession struct {
BandwidthDown uint32 // 使用下行带宽 Byte BandwidthDown uint32 // 使用下行带宽 Byte
BandwidthUpPeriod uint32 // 前一周期的总量 BandwidthUpPeriod uint32 // 前一周期的总量
BandwidthDownPeriod uint32 BandwidthDownPeriod uint32
BandwidthUpAll uint32 // 使用上行带宽总量 BandwidthUpAll uint64 // 使用上行带宽总量
BandwidthDownAll uint32 // 使用下行带宽总量 BandwidthDownAll uint64 // 使用下行带宽总量
closeOnce sync.Once closeOnce sync.Once
CloseChan chan struct{} CloseChan chan struct{}
PayloadIn chan *Payload PayloadIn chan *Payload
// PayloadOut chan *Payload // 公共ip数据
PayloadOutCstp chan *Payload // Cstp的数据 PayloadOutCstp chan *Payload // Cstp的数据
PayloadOutDtls chan *Payload // Dtls的数据 PayloadOutDtls chan *Payload // Dtls的数据
IpAuditMap map[string]int64 // 审计的ip数据
// dSess *DtlsSession // dSess *DtlsSession
dSess *atomic.Value dSess *atomic.Value
@@ -111,9 +111,9 @@ func checkSession() {
func GenToken() string { func GenToken() string {
// 生成32位的 token // 生成32位的 token
btoken := make([]byte, 32) bToken := make([]byte, 32)
rand.Read(btoken) rand.Read(bToken)
return fmt.Sprintf("%x", btoken) return fmt.Sprintf("%x", bToken)
} }
func NewSession(token string) *Session { func NewSession(token string) *Session {
@@ -183,12 +183,17 @@ func (s *Session) NewConn() *ConnSession {
IpAddr: ip, IpAddr: ip,
closeOnce: sync.Once{}, closeOnce: sync.Once{},
CloseChan: make(chan struct{}), CloseChan: make(chan struct{}),
PayloadIn: make(chan *Payload), PayloadIn: make(chan *Payload, 64),
PayloadOutCstp: make(chan *Payload), PayloadOutCstp: make(chan *Payload, 64),
PayloadOutDtls: make(chan *Payload), PayloadOutDtls: make(chan *Payload, 64),
dSess: &atomic.Value{}, dSess: &atomic.Value{},
} }
// ip 审计
if base.Cfg.AuditInterval >= 0 {
cSess.IpAuditMap = make(map[string]int64, 512)
}
dSess := &DtlsSession{ dSess := &DtlsSession{
isActive: -1, isActive: -1,
} }
@@ -284,8 +289,8 @@ func (cs *ConnSession) ratePeriod() {
atomic.SwapUint32(&cs.BandwidthUpPeriod, rtUp/BandwidthPeriodSec) atomic.SwapUint32(&cs.BandwidthUpPeriod, rtUp/BandwidthPeriodSec)
atomic.SwapUint32(&cs.BandwidthDownPeriod, rtDown/BandwidthPeriodSec) atomic.SwapUint32(&cs.BandwidthDownPeriod, rtDown/BandwidthPeriodSec)
// 累加所有流量 // 累加所有流量
atomic.AddUint32(&cs.BandwidthUpAll, rtUp) atomic.AddUint64(&cs.BandwidthUpAll, uint64(rtUp))
atomic.AddUint32(&cs.BandwidthDownAll, rtDown) atomic.AddUint64(&cs.BandwidthDownAll, uint64(rtDown))
} }
} }
@@ -304,10 +309,10 @@ func (cs *ConnSession) SetMtu(mtu string) {
} }
} }
func (cs *ConnSession) SetTunName(name string) { func (cs *ConnSession) SetIfName(name string) {
cs.Sess.mux.Lock() cs.Sess.mux.Lock()
defer cs.Sess.mux.Unlock() defer cs.Sess.mux.Unlock()
cs.TunName = name cs.IfName = name
} }
func (cs *ConnSession) RateLimit(byt int, isUp bool) error { func (cs *ConnSession) RateLimit(byt int, isUp bool) error {

View File

@@ -1,6 +1,7 @@
[Unit] [Unit]
Description=VPN Server Service Description=AnyLink Server Service
After=network.target Documentation=https://github.com/bjdgyc/anylink
After=network-online.target
[Service] [Service]
Type=simple Type=simple
@@ -8,6 +9,7 @@ User=root
WorkingDirectory=/usr/local/anylink-deploy WorkingDirectory=/usr/local/anylink-deploy
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
ExecStart=/usr/local/anylink-deploy/anylink --conf=conf/server.toml ExecStart=/usr/local/anylink-deploy/anylink --conf=./conf/server.toml
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

14807
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,13 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"axios": "^0.20.0", "axios": "^0.21.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"echarts": "^4.9.0", "echarts": "^4.9.0",
"element-ui": "^2.4.5", "element-ui": "^2.4.5",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-count-to": "^1.0.13", "vue-count-to": "^1.0.13",
"vue-router": "^3.4.6" "vue-router": "^3.5.2"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
@@ -25,5 +25,24 @@
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"vue-cli-plugin-element": "~1.0.1", "vue-cli-plugin-element": "~1.0.1",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.11"
} },
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
} }

View File

@@ -32,6 +32,7 @@
<el-menu-item index="/admin/set/system">系统信息</el-menu-item> <el-menu-item index="/admin/set/system">系统信息</el-menu-item>
<el-menu-item index="/admin/set/soft">软件配置</el-menu-item> <el-menu-item index="/admin/set/soft">软件配置</el-menu-item>
<el-menu-item index="/admin/set/other">其他设置</el-menu-item> <el-menu-item index="/admin/set/other">其他设置</el-menu-item>
<el-menu-item index="/admin/set/audit">审计日志</el-menu-item>
</el-submenu> </el-submenu>
<el-submenu index="2"> <el-submenu index="2">

View File

@@ -118,8 +118,8 @@
<el-popconfirm <el-popconfirm
style="margin-left: 10px" style="margin-left: 10px"
@onConfirm="handleDel(scope.row)" @confirm="handleDel(scope.row)"
title="确定要删除用户吗?"> title="确定要删除用户吗?">
<el-button <el-button
slot="reference" slot="reference"
size="mini" size="mini"
@@ -166,7 +166,7 @@
<el-form-item label="带宽限制" prop="bandwidth"> <el-form-item label="带宽限制" prop="bandwidth">
<el-input v-model.number="ruleForm.bandwidth"> <el-input v-model.number="ruleForm.bandwidth">
<template slot="append">BYTE</template> <template slot="append">BYTE/S</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="本地网络" prop="allow_lan"> <el-form-item label="本地网络" prop="allow_lan">
@@ -181,18 +181,20 @@
<el-col :span="4"> <el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.client_dns)"></el-button> @click.prevent="addDomain(ruleForm.client_dns)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.client_dns)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-for="(item,index) in ruleForm.client_dns" <el-row v-for="(item,index) in ruleForm.client_dns"
:key="index" style="margin-bottom: 5px" gutter="10"> :key="index" style="margin-bottom: 5px" :gutter="10">
<el-col :span="10"> <el-col :span="10">
<el-input v-model="item.val"></el-input> <el-input v-model="item.val"></el-input>
</el-col> </el-col>
<el-col :span="14"> <el-col :span="12">
<el-input v-model="item.note" placeholder="备注"></el-input> <el-input v-model="item.note" placeholder="备注"></el-input>
</el-col> </el-col>
<el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.client_dns,index)"></el-button>
</el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@@ -202,18 +204,20 @@
<el-col :span="4"> <el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.route_include)"></el-button> @click.prevent="addDomain(ruleForm.route_include)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.route_include)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-for="(item,index) in ruleForm.route_include" <el-row v-for="(item,index) in ruleForm.route_include"
:key="index" style="margin-bottom: 5px" gutter="10"> :key="index" style="margin-bottom: 5px" :gutter="10">
<el-col :span="10"> <el-col :span="10">
<el-input v-model="item.val"></el-input> <el-input v-model="item.val"></el-input>
</el-col> </el-col>
<el-col :span="14"> <el-col :span="12">
<el-input v-model="item.note" placeholder="备注"></el-input> <el-input v-model="item.note" placeholder="备注"></el-input>
</el-col> </el-col>
<el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.route_include,index)"></el-button>
</el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@@ -223,18 +227,20 @@
<el-col :span="4"> <el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button> @click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.route_exclude)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-for="(item,index) in ruleForm.route_exclude" <el-row v-for="(item,index) in ruleForm.route_exclude"
:key="index" style="margin-bottom: 5px" gutter="10"> :key="index" style="margin-bottom: 5px" :gutter="10">
<el-col :span="10"> <el-col :span="10">
<el-input v-model="item.val"></el-input> <el-input v-model="item.val"></el-input>
</el-col> </el-col>
<el-col :span="14"> <el-col :span="12">
<el-input v-model="item.note" placeholder="备注"></el-input> <el-input v-model="item.note" placeholder="备注"></el-input>
</el-col> </el-col>
<el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button>
</el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@@ -244,13 +250,11 @@
<el-col :span="4"> <el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.link_acl)"></el-button> @click.prevent="addDomain(ruleForm.link_acl)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.link_acl)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-for="(item,index) in ruleForm.link_acl" <el-row v-for="(item,index) in ruleForm.link_acl"
:key="index" style="margin-bottom: 5px" gutter="5"> :key="index" style="margin-bottom: 5px" :gutter="5">
<el-col :span="11"> <el-col :span="11">
<el-input placeholder="请输入CIDR地址" v-model="item.val"> <el-input placeholder="请输入CIDR地址" v-model="item.val">
<el-select v-model="item.action" slot="prepend"> <el-select v-model="item.action" slot="prepend">
@@ -262,9 +266,13 @@
<el-col :span="3"> <el-col :span="3">
<el-input v-model.number="item.port" placeholder="端口"></el-input> <el-input v-model.number="item.port" placeholder="端口"></el-input>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="8">
<el-input v-model="item.note" placeholder="备注"></el-input> <el-input v-model="item.note" placeholder="备注"></el-input>
</el-col> </el-col>
<el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.link_acl,index)"></el-button>
</el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
@@ -311,7 +319,7 @@ export default {
status: 1, status: 1,
allow_lan: true, allow_lan: true,
client_dns: [{val: '114.114.114.114'}], client_dns: [{val: '114.114.114.114'}],
route_include: [], route_include: [{val: 'all', note: '默认全局代理'}],
route_exclude: [], route_exclude: [],
link_acl: [], link_acl: [],
}, },
@@ -389,13 +397,16 @@ export default {
console.log(error); console.log(error);
}); });
}, },
removeDomain(arr, item) { removeDomain(arr, index) {
console.log(item) console.log(index)
if (index >= 0 && index < arr.length) {
arr.splice(index, 1)
}
// let index = arr.indexOf(item); // let index = arr.indexOf(item);
// if (index !== -1 && arr.length > 1) { // if (index !== -1 && arr.length > 1) {
// arr.splice(index, 1) // arr.splice(index, 1)
// } // }
arr.pop() // arr.pop()
}, },
addDomain(arr) { addDomain(arr) {
arr.push({val: "", action: "allow", port: 0}); arr.push({val: "", action: "allow", port: 0});

145
web/src/pages/set/Audit.vue Normal file
View File

@@ -0,0 +1,145 @@
<template>
<div>
<el-card>
<el-table
ref="multipleTable"
:data="tableData"
border>
<el-table-column
sortable="true"
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="username"
label="用户名">
</el-table-column>
<el-table-column
prop="protocol"
label="协议">
</el-table-column>
<el-table-column
prop="src"
label="源IP地址">
</el-table-column>
<el-table-column
prop="dst"
label="目的IP地址">
</el-table-column>
<el-table-column
prop="dst_port"
label="目的端口">
</el-table-column>
<el-table-column
prop="created_at"
label="创建时间"
:formatter="tableDateFormat">
</el-table-column>
<el-table-column
label="操作"
width="150">
<template slot-scope="scope">
<el-popconfirm
class="m-left-10"
@confirm="handleDel(scope.row)"
title="确定要删除审计日志吗?">
<el-button
slot="reference"
size="mini"
type="danger">删除
</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="sh-20"></div>
<el-pagination
background
layout="prev, pager, next"
:pager-count="11"
@current-change="pageChange"
:total="count">
</el-pagination>
</el-card>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "Audit",
components: {},
mixins: [],
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['基础信息', 'IP审计'])
},
mounted() {
this.getData(1)
},
data() {
return {
tableData: [],
count: 10,
nowIndex: 0,
}
},
methods: {
getData(p) {
axios.get('/set/audit/list', {
params: {
page: p,
}
}).then(resp => {
var data = resp.data.data
console.log(data);
this.tableData = data.datas;
this.count = data.count
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
pageChange(p) {
this.getData(p)
},
handleDel(row) {
axios.post('/set/audit/del?id=' + row.id).then(resp => {
var rdata = resp.data
if (rdata.code === 0) {
this.$message.success(rdata.msg);
this.getData(1);
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
},
}
</script>
<style scoped>
</style>

View File

@@ -15,8 +15,12 @@
<el-form-item label="密码" prop="password"> <el-form-item label="密码" prop="password">
<el-input v-model="dataSmtp.password"></el-input> <el-input v-model="dataSmtp.password"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="启用SSL" prop="use_ssl"> <el-form-item label="加密类型" prop="encryption">
<el-switch v-model="dataSmtp.use_ssl"></el-switch> <el-radio-group v-model="dataSmtp.encryption">
<el-radio label="None">None</el-radio>
<el-radio label="SSLTLS">SSLTLS</el-radio>
<el-radio label="STARTTLS">STARTTLS</el-radio>
</el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="邮件from" prop="from"> <el-form-item label="邮件from" prop="from">
<el-input v-model="dataSmtp.from"></el-input> <el-input v-model="dataSmtp.from"></el-input>

View File

@@ -46,8 +46,9 @@
<el-card v-if="system.sys" style="margin-top: 10px"> <el-card v-if="system.sys" style="margin-top: 10px">
<div slot="header"> <div slot="header">
<span>go运行环境</span> <span>运行环境</span>
</div> </div>
<Cell left="软件版本" :right="system.sys.appVersion" divider/>
<Cell left="GO版本" :right="system.sys.goOs" divider/> <Cell left="GO版本" :right="system.sys.goOs" divider/>
<Cell left="GoArch" :right="system.sys.goArch" divider/> <Cell left="GoArch" :right="system.sys.goArch" divider/>
<Cell left="GoVersion" :right="system.sys.goVersion" divider/> <Cell left="GoVersion" :right="system.sys.goVersion" divider/>

View File

@@ -76,13 +76,12 @@
<el-popconfirm <el-popconfirm
class="m-left-10" class="m-left-10"
@onConfirm="handleDel(scope.row)" @confirm="handleDel(scope.row)"
title="确定要删除IP映射吗"> title="确定要删除IP映射吗">
<el-button <el-button
slot="reference" slot="reference"
size="mini" size="mini"
type="danger" type="danger">删除
@click="handleDelete(scope.row)">删除
</el-button> </el-button>
</el-popconfirm> </el-popconfirm>

View File

@@ -120,7 +120,7 @@
<el-popconfirm <el-popconfirm
class="m-left-10" class="m-left-10"
@onConfirm="handleDel(scope.row)" @confirm="handleDel(scope.row)"
title="确定要删除用户吗?"> title="确定要删除用户吗?">
<el-button <el-button
slot="reference" slot="reference"

View File

@@ -95,7 +95,7 @@
<el-popconfirm <el-popconfirm
class="m-left-10" class="m-left-10"
@onConfirm="handleOffline(scope.row)" @confirm="handleOffline(scope.row)"
title="确定要下线用户吗?"> title="确定要下线用户吗?">
<el-button <el-button
slot="reference" slot="reference"

View File

@@ -17,6 +17,7 @@ const routes = [
{path: 'set/system', component: () => import('@/pages/set/System')}, {path: 'set/system', component: () => import('@/pages/set/System')},
{path: 'set/soft', component: () => import('@/pages/set/Soft')}, {path: 'set/soft', component: () => import('@/pages/set/Soft')},
{path: 'set/other', component: () => import('@/pages/set/Other')}, {path: 'set/other', component: () => import('@/pages/set/Other')},
{path: 'set/audit', component: () => import('@/pages/set/Audit')},
{path: 'user/list', component: () => import('@/pages/user/List')}, {path: 'user/list', component: () => import('@/pages/user/List')},
{path: 'user/online', component: () => import('@/pages/user/Online')}, {path: 'user/online', component: () => import('@/pages/user/Online')},