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
uses: actions/setup-go@v2
with:
go-version: 1.15
go-version: 1.16
id: go
- name: Check out code into the Go module directory
@@ -30,7 +30,9 @@ jobs:
- name: Build
run: |
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
- name: Test coverage

2
.gitignore vendored
View File

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

View File

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

181
README.md
View File

@@ -8,7 +8,7 @@
![GitHub downloads)](https://img.shields.io/github/downloads/bjdgyc/anylink/total)
![LICENSE](https://img.shields.io/github/license/bjdgyc/anylink)
AnyLink 是一个企业级远程办公sslvpn的软件可以支持多人同时在线使用。
AnyLink 是一个企业级远程办公 sslvpn 的软件,可以支持多人同时在线使用。
## Repo
@@ -21,23 +21,25 @@ AnyLink 是一个企业级远程办公sslvpn的软件可以支持多人同时
AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannopoulos-openconnect-02)
协议开发,并且借鉴了 [ocserv](http://ocserv.gitlab.io/www/index.html) 的开发思路,使其可以同时兼容 AnyConnect 客户端。
AnyLink 使用TLS/DTLS进行数据加密因此需要RSAECC证书可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSAECC 证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的 SSL 证书。
AnyLink 服务端仅在CentOS 7、Ubuntu 18.04测试通过如需要安装在其他系统需要服务端支持tun/tap功能、ip设置命令。
AnyLink 服务端仅在 CentOS 7、Ubuntu 18.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap 功能、ip 设置命令。
## Screenshot
![online](screenshot/online.jpg)
![online](doc/screenshot/online.jpg)
## Installation
> 没有编程基础的同学建议直接下载release包从下面的地址下载 anylink-deploy.tar.gz
> 没有编程基础的同学建议直接下载 release 包,从下面的地址下载 anylink-deploy.tar.gz
>
> https://github.com/bjdgyc/anylink/releases
> 升级 go version = 1.15
### 自行编译安装
> 需要提前安装好 golang >= 1.16 和 nodejs >= 14.x
>
> 需要提前安装好 golang 和 nodejs
> 使用客户端前,必须申请安全的 https 证书,不支持私有证书连接
```shell
git clone https://github.com/bjdgyc/anylink.git
@@ -47,35 +49,38 @@ sh build.sh
# 注意使用root权限运行
cd anylink-deploy
sudo ./anylink --conf="conf/server.toml"
sudo ./anylink
# 默认管理后台访问地址
# http://host:8800
# 默认账号密码
# 默认账号 密码
# admin 123456
```
## Feature
- [x] IP分配(实现IP、MAC映射信息的持久化)
- [x] TLS-TCP通道
- [x] DTLS-UDP通道
- [x] 兼容AnyConnect
- [x] 基于tun设备的nat访问模式
- [x] 基于tap设备的桥接访问模式
- [x] IP 分配(实现 IP、MAC 映射信息的持久化)
- [x] TLS-TCP 通道
- [x] DTLS-UDP 通道
- [x] 兼容 AnyConnect
- [x] 基于 tun 设备的 nat 访问模式
- [x] 基于 tap 设备的桥接访问模式
- [x] 基于 macvtap 设备的桥接访问模式
- [x] 支持 [proxy protocol v1](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
- [x] 用户组支持
- [x] 多用户支持
- [x] TOTP令牌支持
- [x] TOTP令牌开关
- [x] 流量
- [x] TOTP 令牌支持
- [x] TOTP 令牌开关
- [x] 流量速率限
- [x] 后台管理界面
- [x] 访问权限管理
- [x] IP 访问审计功能
- [ ] 基于 ipvtap 设备的桥接访问模式
## Config
默认配置文件内有详细的注释,根据注释填写配置即可。
> 示例配置文件内有详细的注释,根据注释填写配置即可。
```shell
# 生成后台密码
@@ -85,31 +90,44 @@ sudo ./anylink --conf="conf/server.toml"
./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
> 以下参数必须设置其中之一
网络模式选择,需要配置 `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 设置
1. 开启服务器转发
```shell
# flie: /etc/sysctl.conf
net.ipv4.ip_forward = 1
```shell
# flie: /etc/sysctl.conf
net.ipv4.ip_forward = 1
#执行如下命令
sysctl -w net.ipv4.ip_forward=1
```
#执行如下命令
sysctl -w net.ipv4.ip_forward=1
```
2. 设置nat转发规则
2. 设置 nat 转发规则
```shell
systemctl stop firewalld.service
systemctl disable firewalld.service
# 请根据服务器内网网卡替换 eth0
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
# 如果执行第一个命令不生效,可以继续执行下面的命令
@@ -118,9 +136,26 @@ iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
iptables -nL -t nat
```
3. 使用AnyConnect客户端连接即可
3. 使用 AnyConnect 客户端连接即可
### tap设置
### 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 设置
1. 创建桥接网卡
@@ -130,12 +165,13 @@ iptables -nL -t nat
2. 修改 bridge-init.sh 内的参数
> 以下参数可以通过执行 `ip a` 查看
```
eth="eth0"
eth_ip="192.168.1.4"
eth_netmask="255.255.255.0"
eth_broadcast="192.168.1.255"
eth_gateway="192.168.1.1"
eth_ip="192.168.10.4/24"
eth_broadcast="192.168.10.255"
eth_gateway="192.168.10.1"
```
3. 执行 bridge-init.sh 文件
@@ -146,20 +182,20 @@ sh bridge-init.sh
## Systemd
添加 systemd脚本
1. 添加 anylink 程序
* anylink 程序目录放入 `/usr/local/anylink-deploy`
- anylink 程序目录放入 `/usr/local/anylink-deploy`
systemd 脚本放入:
2. systemd/anylink.service 脚本放入:
* centos: `/usr/lib/systemd/system/`
* ubuntu: `/lib/systemd/system/`
- centos: `/usr/lib/systemd/system/`
- ubuntu: `/lib/systemd/system/`
操作命令:
3. 操作命令:
* 启动: `systemctl start anylink`
* 停止: `systemctl stop anylink`
* 开机自启: `systemctl enable anylink`
- 启动: `systemctl start anylink`
- 停止: `systemctl stop anylink`
- 开机自启: `systemctl enable anylink`
## Docker
@@ -169,21 +205,27 @@ systemd 脚本放入:
docker pull bjdgyc/anylink:latest
```
2. 生成密码
2. 查看命令信息
```bash
docker run -it --rm bjdgyc/anylink -h
```
3. 生成密码
```bash
docker run -it --rm bjdgyc/anylink tool -p 123456
#Passwd:$2a$10$lCWTCcGmQdE/4Kb1wabbLelu4vY/cUwBwN64xIzvXcihFgRzUvH2a
```
3. 生成jwt secret
4. 生成 jwt secret
```bash
docker run -it --rm bjdgyc/anylink tool -s
#Secret:9qXoIhY01jqhWIeIluGliOS4O_rhcXGGGu422uRZ1JjZxIZmh17WwzW36woEbA
```
4. 启动容器
5. 启动容器
```bash
docker run -itd --name anylink --privileged \
@@ -192,18 +234,19 @@ systemd 脚本放入:
bjdgyc/anylink
```
5. 使用自定义参数启动容器
6. 使用自定义参数启动容器
```bash
# 参数可以参考 -h 命令
docker run -itd --name anylink --privileged \
-e IPV4_CIDR=192.168.10.0/24 \
-p 443:443 -p 8800:8800 \
--restart=always \
bjdgyc/anylink \
-c=/etc/server.toml --admin_addr=:8080
-c=/etc/server.toml --ip_lease = 1209600 \ # IP地址租约时长
```
6. 构建镜像
7. 构建镜像
```bash
#获取仓库源码
@@ -216,30 +259,38 @@ systemd 脚本放入:
请前往 [问题地址](question.md) 查看具体信息
## Donate
> 如果您觉得 anylink 对你有帮助,欢迎给我们打赏,也是帮助 anylink 更好的发展。
>
> [查看打赏列表](doc/README.md)
<p>
<img src="doc/screenshot/wxpay2.png" width="400" />
</p>
## Discussion
![qq.png](screenshot/qq.png)
添加 QQ 群: 567510628
添加QQ群: 567510628
QQ群共享文件有相关软件下载
QQ 群共享文件有相关软件下载
## Contribution
欢迎提交 PR、Issues感谢为AnyLink做出贡献。
欢迎提交 PR、Issues感谢为 AnyLink 做出贡献。
注意新建PR需要提交到dev分支其他分支暂不会合并。
注意新建 PR需要提交到 dev 分支,其他分支暂不会合并。
## Other Screenshot
<details>
<summary>展开查看</summary>
![system.jpg](screenshot/system.jpg)
![setting.jpg](screenshot/setting.jpg)
![users.jpg](screenshot/users.jpg)
![ip_map.jpg](screenshot/ip_map.jpg)
![group.jpg](screenshot/group.jpg)
![system.jpg](doc/screenshot/system.jpg)
![setting.jpg](doc/screenshot/setting.jpg)
![users.jpg](doc/screenshot/users.jpg)
![ip_map.jpg](doc/screenshot/ip_map.jpg)
![group.jpg](doc/screenshot/group.jpg)
</details>
@@ -250,9 +301,5 @@ QQ群共享文件有相关软件下载
## Thank
<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>

View File

@@ -12,35 +12,38 @@ function RETVAL() {
#当前目录
cpath=$(pwd)
echo "编译二进制文件"
cd $cpath/server
go build -v -o anylink -ldflags "-X main.COMMIT_ID=$(git rev-parse HEAD)"
RETVAL $?
echo "编译前端项目"
cd $cpath/web
#国内可替换源加快速度
#npx browserslist@latest --update-db
npm install --registry=https://registry.npm.taobao.org
npm run build --registry=https://registry.npm.taobao.org
#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 $?
cd $cpath
echo "整理部署文件"
deploy="anylink-deploy"
rm -rf $deploy
rm -rf $deploy ${deploy}.tar.gz
mkdir $deploy
mkdir $deploy/log
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/conf $deploy
cp -r systemd $deploy
cp -r web/ui $deploy
tar zcvf ${deploy}.tar.gz $deploy
#注意使用root权限运行
#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
iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE
# iptables -nL -t nat
iptables -nL -t nat
/app/anylink "$@"
;;

View File

@@ -9,6 +9,7 @@ import (
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"errors"
"math/big"
@@ -70,6 +71,11 @@ func WithDNS(key crypto.PrivateKey, cn string, sans ...string) (tls.Certificate,
names = append(names, sans...)
template := x509.Certificate{
Subject: pkix.Name{
// TODO anylink
Organization: []string{cn},
OrganizationalUnit: names,
},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
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{})
var datas []dbdata.Group
err := dbdata.All(&datas, pageSize, page)
err := dbdata.Find(&datas, pageSize, page)
if err != nil {
RespError(w, RespInternalErr, err)
return

View File

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

View File

@@ -5,11 +5,10 @@ import (
"net/http"
"runtime"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
@@ -71,6 +70,7 @@ func SetSystem(w http.ResponseWriter, r *http.Request) {
"goArch": runtime.GOARCH,
"goVersion": runtime.Version(),
"goroutine": runtime.NumGoroutine(),
"appVersion": "v" + base.APP_VER,
"hostname": hi.Hostname,
"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"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"text/template"
@@ -36,11 +37,11 @@ func UserList(w http.ResponseWriter, r *http.Request) {
// 查询前缀匹配
if len(prefix) > 0 {
count = pageSize
err = dbdata.Prefix("Username", prefix, &datas, pageSize, 1)
count = dbdata.CountPrefix("username", prefix, &dbdata.User{})
err = dbdata.Prefix("username", prefix, &datas, pageSize, 1)
} else {
count = dbdata.CountAll(&dbdata.User{})
err = dbdata.All(&datas, pageSize, page)
err = dbdata.Find(&datas, pageSize, page)
}
if err != nil && !dbdata.CheckErrNotFound(err) {
@@ -141,7 +142,7 @@ func UserOtpQr(w http.ResponseWriter, r *http.Request) {
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)
qr, _ := qrcode.New(qrstr, qrcode.High)

View File

@@ -7,7 +7,7 @@ import (
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/dgrijalva/jwt-go"
"github.com/golang-jwt/jwt/v4"
mail "github.com/xhit/go-simple-mail/v2"
// "github.com/mojocn/base64Captcha"
)
@@ -59,8 +59,14 @@ func SendMail(subject, to, htmlBody string) error {
server.Port = dataSmtp.Port
server.Username = dataSmtp.Username
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:

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
package base
import (
"errors"
"fmt"
"math/rand"
"os"
"reflect"
"runtime"
"strings"
"time"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/spf13/cobra"
@@ -16,25 +16,26 @@ import (
var (
// 提交id
CommitId string
// 配置文件
cfgFile string
// pass明文
passwd string
// 生成密钥
secret bool
// 显示版本信息
rev bool
// 获取env名称
env bool
// 输出debug信息
debug bool
// Used for flags.
runSrv bool
linkViper *viper.Viper
rootCmd *cobra.Command
)
// Execute executes the root command.
func execute() {
initCmd()
err := rootCmd.Execute()
if err != nil {
fmt.Println(err)
@@ -42,13 +43,25 @@ func execute() {
}
// 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 {
os.Exit(0)
}
}
func init() {
func initCmd() {
linkViper = viper.New()
rootCmd = &cobra.Command{
Use: "anylink",
Short: "AnyLink VPN Server",
@@ -59,38 +72,44 @@ func init() {
},
}
cobra.OnInitialize(func() {
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
fmt.Println("Using config file:", err)
}
})
viper.SetEnvPrefix("link")
linkViper.SetEnvPrefix("link")
// 基础配置
rootCmd.Flags().StringVarP(&cfgFile, "conf", "c", "./conf/server.toml", "config file")
for _, v := range configs {
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 {
rootCmd.Flags().Int(v.Name, v.ValInt, v.Usage)
rootCmd.Flags().IntP(v.Name, v.Short, v.ValInt, v.Usage)
}
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))
_ = viper.BindEnv(v.Name)
_ = linkViper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name))
_ = linkViper.BindEnv(v.Name)
// viper.SetDefault(v.Name, v.Value)
}
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 {
@@ -103,7 +122,7 @@ func initToolCmd() *cobra.Command {
toolCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info")
toolCmd.Flags().BoolVarP(&secret, "secret", "s", false, "generate a random jwt secret")
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) {
switch {
@@ -111,17 +130,14 @@ func initToolCmd() *cobra.Command {
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)
case secret:
rand.Seed(time.Now().UnixNano())
s, _ := utils.RandSecret(40, 60)
s = strings.Trim(s, "=")
fmt.Printf("Secret:%s\n", s)
case passwd != "":
pass, _ := utils.PasswordHash(passwd)
fmt.Printf("Passwd:%s\n", pass)
case env:
for k, v := range envs {
fmt.Printf("%s => %s\n", k, v)
}
case debug:
linkViper.Debug()
default:
fmt.Println("Using [anylink tool -h] for help")
}

View File

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

View File

@@ -1,19 +1,13 @@
#!/bin/bash
#################################
# Set up Ethernet bridge on Linux
# Requires: bridge-utils
#################################
#yum install bridge-utils
# Define Bridge Interface
br="anylink0"
# Define physical ethernet interface to be bridged
# with TAP interface(s) above.
# 请根据sever服务器信息更新下面的信息
eth="eth0"
eth_ip="192.168.10.4"
eth_netmask="255.255.255.0"
eth_ip="192.168.10.4/24"
eth_broadcast="192.168.10.255"
eth_gateway="192.168.10.1"
@@ -21,11 +15,14 @@ eth_gateway="192.168.10.1"
brctl addbr $br
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`
ifconfig $br hw ether $mac
ifconfig $br $eth_ip netmask $eth_netmask broadcast $eth_broadcast up
ip link set dev $br up address $mac promisc on
ip addr add $eth_ip broadcast $eth_broadcast dev $br
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_key = "./test_vpn_key.pem"
ui_path = "../ui"
files_path = "../files"
cert_file = "./conf/vpn_cert.pem"
cert_key = "./conf/vpn_cert.key"
files_path = "./conf/files"
#日志目录,为空写入标准输出
#log_path = "../log"
#log_path = "./log"
log_path = ""
log_level = "debug"
pprof = false
@@ -22,7 +22,7 @@ issuer = "XX公司VPN"
admin_user = "admin"
#pass 123456
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"
#客户端分配的ip地址池
ipv4_master = "eth0"
ipv4_cidr = "192.168.10.0/24"
ipv4_gateway = "192.168.10.1"
ipv4_start = "192.168.10.100"
@@ -61,7 +62,7 @@ mobile_dpd = 50
#session过期时间用于断线重连0永不过期
session_timeout = 3600
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
import (
"time"
"github.com/asdine/storm/v3"
"github.com/asdine/storm/v3/codec/json"
"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 (
sdb *storm.DB
xdb *xorm.Engine
)
func GetXdb() *xorm.Engine {
return xdb
}
func initDb() {
var err error
sdb, err = storm.Open(base.Cfg.DbFile, storm.Codec(json.Codec),
storm.BoltOptions(0600, &bolt.Options{Timeout: 10 * time.Second}))
xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource)
// xdb.ShowSQL(true)
if err != nil {
base.Fatal(err)
}
// 初始化数据库
err = sdb.Init(&User{})
err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{})
if err != nil {
base.Fatal(err)
}
// fmt.Println("s1")
// fmt.Println("s1=============", err)
}
func initData() {
var (
err error
install bool
)
// 判断是否初次使用
err = Get(SettingBucket, Installed, &install)
if err == nil && install {
install := &SettingInstall{}
err = SettingGet(install)
if err == nil && install.Installed {
// 已经安装过
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{
Host: "127.0.0.1",
Port: 25,
From: "vpn@xx.com",
Encryption: "None",
}
err = SettingSessAdd(sess, smtp)
if err != nil {
return err
}
_ = SettingSet(smtp)
// SettingOther
other := &SettingOther{
LinkAddr: "vpn.xx.com",
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为",
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 {
return err == storm.ErrNotFound
return err == ErrNotFound
}
const accountMail = `<p>您好:</p>

View File

@@ -1,66 +1,84 @@
package dbdata
import "github.com/asdine/storm/v3/index"
import (
"errors"
"reflect"
)
const PageSize = 10
func Save(data interface{}) error {
return sdb.Save(data)
var ErrNotFound = errors.New("ErrNotFound")
func Add(data interface{}) error {
_, err := xdb.InsertOne(data)
return err
}
func Update(data interface{}) error {
return sdb.Update(data)
}
func UpdateField(data interface{}, fieldName string, value interface{}) error {
return sdb.UpdateField(data, fieldName, value)
func Update(fieldName string, value interface{}, data interface{}) error {
_, err := xdb.Where(fieldName+"=?", value).Update(data)
return err
}
func Del(data interface{}) error {
return sdb.DeleteStruct(data)
_, err := xdb.Delete(data)
return err
}
func Set(bucket, key string, data interface{}) error {
return sdb.Set(bucket, key, data)
func extract(data interface{}, fieldName string) interface{} {
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 {
n, _ := sdb.Count(data)
return n
n, _ := xdb.Count(data)
return int(n)
}
func One(fieldName string, value interface{}, to interface{}) error {
return sdb.One(fieldName, value, to)
}
func Find(fieldName string, value interface{}, to interface{}, options ...func(q *index.Options)) error {
return sdb.Find(fieldName, value, to, options...)
}
func All(to interface{}, limit, page int) error {
opt := getOpt(limit, page)
return sdb.All(to, opt)
}
func Prefix(fieldName string, prefix string, to interface{}, limit, page int) error {
opt := getOpt(limit, page)
return sdb.Prefix(fieldName, prefix, to, opt)
}
func getOpt(limit, page int) func(*index.Options) {
skip := (page - 1) * limit
opt := func(opt *index.Options) {
opt.Reverse = true
if limit > 0 {
opt.Limit = limit
func Find(data interface{}, limit, page int) error {
if limit == 0 {
return xdb.Find(data)
}
if skip > 0 {
opt.Skip = skip
}
}
return opt
start := (page - 1) * limit
return xdb.Limit(limit, start).Find(data)
}
func CountPrefix(fieldName string, prefix string, data interface{}) int {
n, _ := xdb.Where(fieldName+" like ?", prefix+"%").Count(data)
return int(n)
}
func Prefix(fieldName string, prefix string, data interface{}, limit, page int) error {
where := xdb.Where(fieldName+" like ?", prefix+"%")
if limit == 0 {
return where.Find(data)
}
start := (page - 1) * limit
return where.Limit(limit, start).Find(data)
}

View File

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

View File

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

View File

@@ -1,18 +1,34 @@
package dbdata
import (
"net"
"errors"
"time"
)
type IpMap struct {
Id int `json:"id" storm:"id,increment"`
IpAddr net.IP `json:"ip_addr" storm:"unique"`
MacAddr string `json:"mac_addr" storm:"unique"`
Username string `json:"username"`
Keep bool `json:"keep"` // 保留 ip-mac 绑定
KeepTime time.Time `json:"keep_time"`
Note string `json:"note"` // 备注
LastLogin time.Time `json:"last_login"`
UpdatedAt time.Time `json:"updated_at"`
// type IpMap struct {
// Id int `json:"id" xorm:"pk autoincr not null"`
// IpAddr string `json:"ip_addr" xorm:"not null unique"`
// MacAddr string `json:"mac_addr" xorm:"not null unique"`
// Username string `json:"username"`
// Keep bool `json:"keep"` // 保留 ip-mac 绑定
// KeepTime time.Time `json:"keep_time"`
// Note string `json:"note"` // 备注
// LastLogin time.Time `json:"last_login"`
// 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
import (
"encoding/json"
"reflect"
"xorm.io/xorm"
)
const (
SettingBucket = "SettingBucket"
Installed = "Installed"
)
type SettingInstall struct {
Installed bool `json:"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 {
ref := reflect.ValueOf(data)
@@ -20,29 +36,30 @@ func StructName(data interface{}) string {
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 {
key := StructName(data)
err := Set(SettingBucket, key, data)
name := StructName(data)
v, _ := json.Marshal(data)
s := &Setting{Data: v}
err := Update("name", name, s)
return err
}
func SettingGet(data interface{}) error {
key := StructName(data)
err := Get(SettingBucket, key, data)
name := StructName(data)
s := &Setting{Name: name}
err := One("name", name, s)
if err != nil {
return err
}
err = json.Unmarshal(s.Data, data)
return err
}
type SettingSmtp struct {
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 {
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"
)
type User struct {
Id int `json:"id" storm:"id,increment"`
Username string `json:"username" storm:"unique"`
Nickname string `json:"nickname"`
Email string `json:"email"`
// Password string `json:"password"`
PinCode string `json:"pin_code"`
OtpSecret string `json:"otp_secret"`
DisableOtp bool `json:"disable_otp"` // 禁用otp
Groups []string `json:"groups"`
Status int8 `json:"status"` // 1正常
SendEmail bool `json:"send_email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// type User struct {
// Id int `json:"id" xorm:"pk autoincr not null"`
// Username string `json:"username" storm:"not null unique"`
// Nickname string `json:"nickname"`
// Email string `json:"email"`
// // Password string `json:"password"`
// PinCode string `json:"pin_code"`
// OtpSecret string `json:"otp_secret"`
// DisableOtp bool `json:"disable_otp"` // 禁用otp
// Groups []string `json:"groups"`
// Status int8 `json:"status"` // 1正常
// SendEmail bool `json:"send_email"`
// CreatedAt time.Time `json:"created_at"`
// UpdatedAt time.Time `json:"updated_at"`
// }
func SetUser(v *User) error {
var err error
@@ -57,7 +57,11 @@ func SetUser(v *User) error {
v.Groups = ng
v.UpdatedAt = time.Now()
err = Save(v)
if v.Id > 0 {
err = Set(v)
} else {
err = Add(v)
}
return err
}

View File

@@ -3,28 +3,29 @@ module github.com/bjdgyc/anylink
go 1.16
require (
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
github.com/asdine/storm/v3 v3.2.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.0.0
github.com/google/gopacket v1.1.19
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/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/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/cobra v1.1.3
github.com/spf13/viper v1.7.1
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.7.0
github.com/tklauser/go-sysconf v0.3.6 // indirect
github.com/xhit/go-simple-mail/v2 v2.9.0
github.com/tklauser/go-sysconf v0.3.7 // indirect
github.com/xhit/go-simple-mail/v2 v2.10.0
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
xorm.io/xorm v1.2.2
)
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>
</vpn-base-config>
<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-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)
b, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(b), err)
log.Println(string(b))
return err
}
}

View File

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

View File

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

View File

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

View File

@@ -40,21 +40,21 @@ func LinkTun(cSess *sessdata.ConnSession) error {
return err
}
// log.Printf("Interface Name: %s\n", ifce.Name())
cSess.SetTunName(ifce.Name())
// cSess.TunName = ifce.Name()
cSess.SetIfName(ifce.Name())
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",
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
err = execCmd(cmdStrs)
err = execCmd([]string{cmdstr1, cmdstr2})
if err != nil {
base.Error(err)
_ = ifce.Close()
return err
}
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
execCmd([]string{cmdstr3})
go tunRead(ifce, cSess)
go tunWrite(ifce, cSess)
return nil
@@ -69,23 +69,23 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
var (
err error
payload *sessdata.Payload
pl *sessdata.Payload
)
for {
select {
case payload = <-cSess.PayloadIn:
case pl = <-cSess.PayloadIn:
case <-cSess.CloseChan:
return
}
_, err = ifce.Write(payload.Data)
_, err = ifce.Write(pl.Data)
if err != nil {
base.Error("tun Write err", err)
return
}
putPayload(payload)
putPayload(pl)
}
}
@@ -101,13 +101,16 @@ func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
for {
// data := make([]byte, BufferSize)
data := getByteFull()
n, err = ifce.Read(data)
pl := getPayload()
n, err = ifce.Read(pl.Data)
if err != nil {
base.Error("tun Read err", n, err)
return
}
// 更新数据长度
pl.Data = (pl.Data)[:n]
// data = data[:n]
// ip_src := waterutil.IPv4Source(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)
// fmt.Println("read:", packet)
if payloadOut(cSess, sessdata.LTypeIPData, 0x00, data[:n]) {
if payloadOut(cSess, pl) {
return
}
putByte(data)
}
}

View File

@@ -96,6 +96,9 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
}
// 允许的路由
for _, v := range cSess.Group.RouteInclude {
if v.Val == "all" {
continue
}
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())
hj := w.(http.Hijacker)
conn, _, err := hj.Hijack()
conn, bufRW, err := hj.Hijack()
if err != nil {
base.Error(err)
w.WriteHeader(http.StatusInternalServerError)
@@ -160,11 +163,14 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
err = LinkTun(cSess)
case base.LinkModeTAP:
err = LinkTap(cSess)
case base.LinkModeMacvtap:
err = LinkMacvtap(cSess)
}
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
conn.Close()
base.Error(err)
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
import (
"encoding/binary"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/bjdgyc/anylink/sessdata"
"github.com/songgao/water/waterutil"
)
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
pl := getPayload()
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 {
func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
if pl.LType == sessdata.LTypeIPData && pl.PType == 0x00 {
// 进行Acl规则判断
check := checkLinkAcl(cSess.Group, payload)
check := checkLinkAcl(cSess.Group, pl)
if !check {
// 校验不通过直接丢弃
return false
}
logAudit(cSess, pl)
}
closed := false
select {
case cSess.PayloadIn <- payload:
case cSess.PayloadIn <- pl:
case <-cSess.CloseChan:
closed = true
}
@@ -33,21 +32,16 @@ func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool
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()
if dSess == nil {
return payloadOutCstp(cSess, lType, pType, data)
return payloadOutCstp(cSess, pl)
} 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 {
pl := getPayload()
pl.LType = lType
pl.PType = pType
pl.Data = append(pl.Data, data...)
func payloadOutCstp(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
closed := false
select {
@@ -59,12 +53,7 @@ func payloadOutCstp(cSess *sessdata.ConnSession, lType sessdata.LType, pType byt
return closed
}
func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lType sessdata.LType, pType byte, data []byte) bool {
pl := getPayload()
pl.LType = lType
pl.PType = pType
pl.Data = append(pl.Data, data...)
func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, pl *sessdata.Payload) bool {
select {
case cSess.PayloadOutDtls <- pl:
case <-dSess.CloseChan:
@@ -74,27 +63,29 @@ func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lT
}
// Acl规则校验
func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 {
func checkLinkAcl(group *dbdata.Group, pl *sessdata.Payload) bool {
if pl.LType == sessdata.LTypeIPData && pl.PType == 0x00 && len(group.LinkAcl) > 0 {
} else {
return true
}
ip_dst := waterutil.IPv4Destination(payload.Data)
ip_port := waterutil.IPv4DestinationPort(payload.Data)
ipDst := waterutil.IPv4Destination(pl.Data)
ipPort := waterutil.IPv4DestinationPort(pl.Data)
ipProto := waterutil.IPv4Protocol(pl.Data)
// fmt.Println("sent:", ip_dst, ip_port)
// 优先放行dns端口
for _, v := range group.ClientDns {
if v.Val == ip_dst.String() && ip_port == 53 {
if v.Val == ipDst.String() && ipPort == 53 {
return true
}
}
for _, v := range group.LinkAcl {
// 循环判断ip和端口
if v.IpNet.Contains(ip_dst) {
if v.Port == ip_port || v.Port == 0 {
if v.IpNet.Contains(ipDst) {
// 放行允许ip的ping
if v.Port == ipPort || v.Port == 0 || ipProto == waterutil.ICMP {
if v.Action == dbdata.Allow {
return true
} else {
@@ -106,3 +97,53 @@ func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
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 (
"sync"
"github.com/bjdgyc/anylink/base"
"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{
New: func() interface{} {
b := make([]byte, BufferSize)
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))
return &pl
@@ -22,31 +35,55 @@ func getPayload() *sessdata.Payload {
}
func putPayload(pl *sessdata.Payload) {
pl.LType = 0
pl.PType = 0
pl.Data = pl.Data[:0]
// 错误数据丢弃
if cap(pl.Data) != BufferSize {
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)
}
var bytePool = sync.Pool{
New: func() interface{} {
b := make([]byte, 0, BufferSize)
b := make([]byte, BufferSize)
// fmt.Println("bytePool-init")
return b
return &b
},
}
func getByteZero() []byte {
b := bytePool.Get().([]byte)
func getByteZero() *[]byte {
b := bytePool.Get().(*[]byte)
*b = (*b)[:0]
return b
}
func getByteFull() []byte {
b := bytePool.Get().([]byte)
b = b[:BufferSize]
func getByteFull() *[]byte {
b := bytePool.Get().(*[]byte)
return b
}
func putByte(b []byte) {
b = b[:0]
func putByte(b *[]byte) {
*b = (*b)[:BufferSize]
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 (
"crypto/tls"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/pkg/proxyproto"
"github.com/gorilla/mux"
"github.com/pion/dtls/v2/pkg/crypto/selfsign"
)
func startTls() {
addr := base.Cfg.ServerAddr
certFile := base.Cfg.CertFile
keyFile := base.Cfg.CertKey
var (
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信息
tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"},
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
Certificates: certs,
// InsecureSkipVerify: true,
}
srv := &http.Server{
Addr: addr,
@@ -31,9 +56,7 @@ func startTls() {
ErrorLog: base.GetBaseLog(),
}
var ln net.Listener
ln, err := net.Listen("tcp", addr)
ln, err = net.Listen("tcp", addr)
if err != nil {
log.Fatal(err)
}
@@ -44,7 +67,7 @@ func startTls() {
}
base.Info("listen server", addr)
err = srv.ServeTLS(ln, certFile, keyFile)
err = srv.ServeTLS(ln, "", "")
if err != nil {
base.Fatal(err)
}
@@ -56,6 +79,9 @@ func initRoute() http.Handler {
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
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(
http.StripPrefix("/files/",
http.FileServer(http.Dir(base.Cfg.FilesPath)),

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,8 @@ func doLookup(ip net.IP) *Addr {
err := doPing(ip.String())
if err != nil {
// 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
}
@@ -50,7 +51,9 @@ func doArpShow(ip net.IP) *Addr {
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

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 (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
var (
// 每秒时间缓存
timeNowSec = &atomic.Value{}
)
func init() {
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 {

View File

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

View File

@@ -15,7 +15,8 @@ import (
func preData(tmpDir string) {
base.Test()
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.Ipv4Start = "192.168.3.1"
base.Cfg.Ipv4End = "192.168.3.199"
@@ -27,7 +28,7 @@ func preData(tmpDir string) {
Name: "group1",
Bandwidth: 1000,
}
_ = dbdata.Save(&group)
_ = dbdata.Add(&group)
initIpPool()
}

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,15 @@
[Unit]
Description=VPN Server Service
After=network.target
Description=AnyLink Server Service
Documentation=https://github.com/bjdgyc/anylink
After=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory= /usr/local/anylink-deploy
WorkingDirectory=/usr/local/anylink-deploy
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/anylink-deploy/anylink --conf=conf/server.toml
ExecStart=/usr/local/anylink-deploy/anylink --conf=./conf/server.toml
[Install]
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"
},
"dependencies": {
"axios": "^0.20.0",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"echarts": "^4.9.0",
"element-ui": "^2.4.5",
"vue": "^2.6.11",
"vue-count-to": "^1.0.13",
"vue-router": "^3.4.6"
"vue-router": "^3.5.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
@@ -25,5 +25,24 @@
"eslint-plugin-vue": "^6.2.2",
"vue-cli-plugin-element": "~1.0.1",
"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/soft">软件配置</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 index="2">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ const routes = [
{path: 'set/system', component: () => import('@/pages/set/System')},
{path: 'set/soft', component: () => import('@/pages/set/Soft')},
{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/online', component: () => import('@/pages/user/Online')},