Compare commits
86 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ca4626879d | ||
|
a0065ade42 | ||
|
a47cdc9a00 | ||
|
c07914d568 | ||
|
df94afdfd0 | ||
|
b4f88e154c | ||
|
e4270f724b | ||
|
e9a27fd833 | ||
|
9865d54f5c | ||
|
8453052d80 | ||
|
e0d5638c17 | ||
|
44adbe71ed | ||
|
246efe430d | ||
|
80ca45c6ea | ||
|
dfe61667cb | ||
|
d9a3b0152b | ||
|
6dcdc9766a | ||
|
a65d3d1054 | ||
|
831b6786c5 | ||
|
b4e2550911 | ||
|
60019f3cf4 | ||
|
d4f1793675 | ||
|
b2c171c39c | ||
|
35dd1dc4e1 | ||
|
bc64a9f8f6 | ||
|
65463fee6c | ||
|
8187fb548f | ||
|
5bd55d1fa8 | ||
|
42de4e6fd0 | ||
|
970b7d557a | ||
|
58cdcbe192 | ||
|
0ffa9578cf | ||
|
903554533b | ||
|
5010d2ecbd | ||
|
cc99a936a6 | ||
|
fd54783d48 | ||
|
af9845a012 | ||
|
d663c43f6d | ||
|
b9b852123e | ||
|
0d4d2bb3c4 | ||
|
1bb76e5d60 | ||
|
bf898ff34b | ||
|
0a35ee18f4 | ||
|
2e764f7ee7 | ||
|
d91d1a127e | ||
|
fef25da35c | ||
|
f3d6d23c3e | ||
|
88c1d09c8f | ||
|
12febf3723 | ||
|
c77a765ae9 | ||
|
6daf4e1b03 | ||
|
2a66df55b0 | ||
|
0f783cfaf6 | ||
|
afe447ada7 | ||
|
981f39799a | ||
|
60084d499a | ||
|
583ca4d635 | ||
|
3937d1eb65 | ||
|
c943b9ee9b | ||
|
d78deafc0c | ||
|
2bdaf4a52e | ||
|
d065a1f97f | ||
|
ba446b8a5c | ||
|
73467a39d9 | ||
|
96276b8cac | ||
|
d17271da2e | ||
|
dd16d52c95 | ||
|
eec3006b35 | ||
|
35c6d80c8d | ||
|
88a3d35784 | ||
|
712f57940c | ||
|
2ad65039f3 | ||
|
1940fcca87 | ||
|
e4f959cb69 | ||
|
8ff77626d0 | ||
|
ea4dda0fca | ||
|
5ffea2339e | ||
|
a8038f8fe9 | ||
|
e7ef29c4ad | ||
|
884f41d2f8 | ||
|
7e95b1261a | ||
|
6daf9cbfa3 | ||
|
31a5337ddf | ||
|
5bb44385b1 | ||
|
66ef639956 | ||
|
f6fd01d1e5 |
71
.github/workflows/codeql-analysis.yml
vendored
Normal 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
|
21
Dockerfile
@@ -2,23 +2,22 @@
|
|||||||
FROM node:lts-alpine as builder_node
|
FROM node:lts-alpine as builder_node
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./web /web
|
COPY ./web /web
|
||||||
RUN npx browserslist@latest --update-db \
|
RUN npm install --registry=https://registry.npm.taobao.org \
|
||||||
&& npm install \
|
|
||||||
&& npm run build \
|
&& npm run build \
|
||||||
&& ls /web/ui
|
&& ls /web/ui
|
||||||
|
|
||||||
# server
|
# server
|
||||||
FROM golang:alpine as builder_golang
|
FROM golang:1.16-alpine as builder_golang
|
||||||
#TODO 本地打包时使用镜像
|
#TODO 本地打包时使用镜像
|
||||||
#ENV GOPROXY=https://goproxy.io
|
ENV GOPROXY=https://goproxy.io
|
||||||
ENV GOOS=linux
|
ENV GOOS=linux
|
||||||
WORKDIR /anylink
|
WORKDIR /anylink
|
||||||
COPY . /anylink
|
COPY . /anylink
|
||||||
COPY --from=builder_node /web/ui /anylink/server/ui
|
COPY --from=builder_node /web/ui /anylink/server/ui
|
||||||
|
|
||||||
#TODO 本地打包时使用镜像
|
#TODO 本地打包时使用镜像
|
||||||
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git gcc musl-dev
|
||||||
RUN cd /anylink/server;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
|
RUN cd /anylink/server;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
|
||||||
&& /anylink/server/anylink tool -v
|
&& /anylink/server/anylink tool -v
|
||||||
|
|
||||||
@@ -29,14 +28,16 @@ LABEL maintainer="github.com/bjdgyc"
|
|||||||
ENV IPV4_CIDR="192.168.10.0/24"
|
ENV IPV4_CIDR="192.168.10.0/24"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder_node /web/ui /app/ui
|
|
||||||
COPY --from=builder_golang /anylink/server/anylink /app/
|
COPY --from=builder_golang /anylink/server/anylink /app/
|
||||||
COPY ./server/conf /app/conf
|
|
||||||
COPY ./server/files /app/files
|
|
||||||
COPY docker_entrypoint.sh /app/
|
COPY docker_entrypoint.sh /app/
|
||||||
|
|
||||||
|
COPY ./server/bridge-init.sh /app/
|
||||||
|
COPY ./server/conf /app/conf
|
||||||
|
#COPY ./server/files /app/conf/files
|
||||||
|
|
||||||
|
|
||||||
#TODO 本地打包时使用镜像
|
#TODO 本地打包时使用镜像
|
||||||
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||||
RUN apk add --no-cache bash iptables \
|
RUN apk add --no-cache bash iptables \
|
||||||
&& chmod +x /app/docker_entrypoint.sh \
|
&& chmod +x /app/docker_entrypoint.sh \
|
||||||
&& ls /app
|
&& ls /app
|
||||||
|
179
README.md
@@ -8,7 +8,7 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
AnyLink 是一个企业级远程办公sslvpn的软件,可以支持多人同时在线使用。
|
AnyLink 是一个企业级远程办公 sslvpn 的软件,可以支持多人同时在线使用。
|
||||||
|
|
||||||
## Repo
|
## Repo
|
||||||
|
|
||||||
@@ -21,25 +21,25 @@ AnyLink 是一个企业级远程办公sslvpn的软件,可以支持多人同时
|
|||||||
AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannopoulos-openconnect-02)
|
AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannopoulos-openconnect-02)
|
||||||
协议开发,并且借鉴了 [ocserv](http://ocserv.gitlab.io/www/index.html) 的开发思路,使其可以同时兼容 AnyConnect 客户端。
|
协议开发,并且借鉴了 [ocserv](http://ocserv.gitlab.io/www/index.html) 的开发思路,使其可以同时兼容 AnyConnect 客户端。
|
||||||
|
|
||||||
AnyLink 使用TLS/DTLS进行数据加密,因此需要RSA或ECC证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。
|
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的 SSL 证书。
|
||||||
|
|
||||||
AnyLink 服务端仅在CentOS 7、Ubuntu 18.04测试通过,如需要安装在其他系统,需要服务端支持tun/tap功能、ip设置命令。
|
AnyLink 服务端仅在 CentOS 7、Ubuntu 18.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap 功能、ip 设置命令。
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
> 没有编程基础的同学建议直接下载release包,从下面的地址下载 anylink-deploy.tar.gz
|
> 没有编程基础的同学建议直接下载 release 包,从下面的地址下载 anylink-deploy.tar.gz
|
||||||
>
|
>
|
||||||
> https://github.com/bjdgyc/anylink/releases
|
> https://github.com/bjdgyc/anylink/releases
|
||||||
|
|
||||||
> 升级 go version = 1.16
|
### 自行编译安装
|
||||||
|
|
||||||
|
> 需要提前安装好 golang >= 1.16 和 nodejs >= 14.x
|
||||||
>
|
>
|
||||||
> 需要提前安装好 golang 和 nodejs
|
> 使用客户端前,必须申请安全的 https 证书,不支持私有证书连接
|
||||||
>
|
|
||||||
> 使用客户端前,必须申请安全的https证书,不支持私有证书连接
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/bjdgyc/anylink.git
|
git clone https://github.com/bjdgyc/anylink.git
|
||||||
@@ -60,24 +60,27 @@ sudo ./anylink
|
|||||||
|
|
||||||
## Feature
|
## Feature
|
||||||
|
|
||||||
- [x] IP分配(实现IP、MAC映射信息的持久化)
|
- [x] IP 分配(实现 IP、MAC 映射信息的持久化)
|
||||||
- [x] TLS-TCP通道
|
- [x] TLS-TCP 通道
|
||||||
- [x] DTLS-UDP通道
|
- [x] DTLS-UDP 通道
|
||||||
- [x] 兼容AnyConnect
|
- [x] 兼容 AnyConnect
|
||||||
- [x] 基于tun设备的nat访问模式
|
- [x] 基于 tun 设备的 nat 访问模式
|
||||||
- [x] 基于tap设备的桥接访问模式
|
- [x] 基于 tap 设备的桥接访问模式
|
||||||
|
- [x] 基于 macvtap 设备的桥接访问模式
|
||||||
- [x] 支持 [proxy protocol v1](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
|
- [x] 支持 [proxy protocol v1](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
|
||||||
- [x] 用户组支持
|
- [x] 用户组支持
|
||||||
- [x] 多用户支持
|
- [x] 多用户支持
|
||||||
- [x] TOTP令牌支持
|
- [x] TOTP 令牌支持
|
||||||
- [x] TOTP令牌开关
|
- [x] TOTP 令牌开关
|
||||||
- [x] 流量控制
|
- [x] 流量速率限制
|
||||||
- [x] 后台管理界面
|
- [x] 后台管理界面
|
||||||
- [x] 访问权限管理
|
- [x] 访问权限管理
|
||||||
|
- [x] IP 访问审计功能
|
||||||
|
- [ ] 基于 ipvtap 设备的桥接访问模式
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
默认配置文件内有详细的注释,根据注释填写配置即可。
|
> 示例配置文件内有详细的注释,根据注释填写配置即可。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# 生成后台密码
|
# 生成后台密码
|
||||||
@@ -87,31 +90,44 @@ sudo ./anylink
|
|||||||
./anylink tool -s
|
./anylink tool -s
|
||||||
```
|
```
|
||||||
|
|
||||||
[conf/server.toml](server/conf/server.toml)
|
> 数据库配置示例
|
||||||
|
|
||||||
|
| db_type | db_source |
|
||||||
|
| -------- | ------------------------------------------------------ |
|
||||||
|
| sqlite3 | ./conf/anylink.db |
|
||||||
|
| mysql | user:password@tcp(127.0.0.1:3306)/anylink?charset=utf8 |
|
||||||
|
| postgres | user:password@localhost/anylink?sslmode=verify-full |
|
||||||
|
|
||||||
|
> 示例配置文件
|
||||||
|
>
|
||||||
|
> [conf/server-sample.toml](server/conf/server-sample.toml)
|
||||||
|
|
||||||
## Setting
|
## Setting
|
||||||
|
|
||||||
> 以下参数必须设置其中之一
|
> 以下参数必须设置其中之一
|
||||||
|
|
||||||
网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="tap"` 两种参数。 不同的参数需要对服务器做相应的设置。
|
网络模式选择,需要配置 `link_mode` 参数,如 `link_mode="tun"`,`link_mode="macvtap"`,`link_mode="tap"` 等参数。 不同的参数需要对服务器做相应的设置。
|
||||||
|
|
||||||
建议优先选择tun模式,因客户端传输的是IP层数据,无须进行数据转换。 tap模式是在用户态做的链路层到IP层的数据互相转换,性能会有所下降。 如果需要在虚拟机内开启tap模式,请确认虚拟机的网卡开启混杂模式。
|
建议优先选择 tun 模式,其次选择 macvtap 模式,因客户端传输的是 IP 层数据,无须进行数据转换。 tap 模式是在用户态做的链路层到 IP 层的数据互相转换,性能会有所下降。 如果需要在虚拟机内开启 tap 模式,请确认虚拟机的网卡开启混杂模式。
|
||||||
|
|
||||||
### tun设置
|
### tun 设置
|
||||||
|
|
||||||
1. 开启服务器转发
|
1. 开启服务器转发
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# flie: /etc/sysctl.conf
|
# flie: /etc/sysctl.conf
|
||||||
net.ipv4.ip_forward = 1
|
net.ipv4.ip_forward = 1
|
||||||
|
|
||||||
#执行如下命令
|
#执行如下命令
|
||||||
sysctl -w net.ipv4.ip_forward=1
|
sysctl -w net.ipv4.ip_forward=1
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 设置nat转发规则
|
2. 设置 nat 转发规则
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
systemctl stop firewalld.service
|
||||||
|
systemctl disable firewalld.service
|
||||||
|
|
||||||
# 请根据服务器内网网卡替换 eth0
|
# 请根据服务器内网网卡替换 eth0
|
||||||
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
|
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
|
||||||
# 如果执行第一个命令不生效,可以继续执行下面的命令
|
# 如果执行第一个命令不生效,可以继续执行下面的命令
|
||||||
@@ -120,9 +136,26 @@ iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
|
|||||||
iptables -nL -t nat
|
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. 创建桥接网卡
|
1. 创建桥接网卡
|
||||||
|
|
||||||
@@ -132,12 +165,13 @@ iptables -nL -t nat
|
|||||||
|
|
||||||
2. 修改 bridge-init.sh 内的参数
|
2. 修改 bridge-init.sh 内的参数
|
||||||
|
|
||||||
|
> 以下参数可以通过执行 `ip a` 查看
|
||||||
|
|
||||||
```
|
```
|
||||||
eth="eth0"
|
eth="eth0"
|
||||||
eth_ip="192.168.1.4"
|
eth_ip="192.168.10.4/24"
|
||||||
eth_netmask="255.255.255.0"
|
eth_broadcast="192.168.10.255"
|
||||||
eth_broadcast="192.168.1.255"
|
eth_gateway="192.168.10.1"
|
||||||
eth_gateway="192.168.1.1"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 执行 bridge-init.sh 文件
|
3. 执行 bridge-init.sh 文件
|
||||||
@@ -148,20 +182,20 @@ sh bridge-init.sh
|
|||||||
|
|
||||||
## Systemd
|
## Systemd
|
||||||
|
|
||||||
添加 systemd脚本
|
1. 添加 anylink 程序
|
||||||
|
|
||||||
* anylink 程序目录放入 `/usr/local/anylink-deploy`
|
- anylink 程序目录放入 `/usr/local/anylink-deploy`
|
||||||
|
|
||||||
systemd 脚本放入:
|
2. systemd/anylink.service 脚本放入:
|
||||||
|
|
||||||
* centos: `/usr/lib/systemd/system/`
|
- centos: `/usr/lib/systemd/system/`
|
||||||
* ubuntu: `/lib/systemd/system/`
|
- ubuntu: `/lib/systemd/system/`
|
||||||
|
|
||||||
操作命令:
|
3. 操作命令:
|
||||||
|
|
||||||
* 启动: `systemctl start anylink`
|
- 启动: `systemctl start anylink`
|
||||||
* 停止: `systemctl stop anylink`
|
- 停止: `systemctl stop anylink`
|
||||||
* 开机自启: `systemctl enable anylink`
|
- 开机自启: `systemctl enable anylink`
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
@@ -171,21 +205,27 @@ systemd 脚本放入:
|
|||||||
docker pull bjdgyc/anylink:latest
|
docker pull bjdgyc/anylink:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 生成密码
|
2. 查看命令信息
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -it --rm bjdgyc/anylink -h
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 生成密码
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm bjdgyc/anylink tool -p 123456
|
docker run -it --rm bjdgyc/anylink tool -p 123456
|
||||||
#Passwd:$2a$10$lCWTCcGmQdE/4Kb1wabbLelu4vY/cUwBwN64xIzvXcihFgRzUvH2a
|
#Passwd:$2a$10$lCWTCcGmQdE/4Kb1wabbLelu4vY/cUwBwN64xIzvXcihFgRzUvH2a
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 生成jwt secret
|
4. 生成 jwt secret
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm bjdgyc/anylink tool -s
|
docker run -it --rm bjdgyc/anylink tool -s
|
||||||
#Secret:9qXoIhY01jqhWIeIluGliOS4O_rhcXGGGu422uRZ1JjZxIZmh17WwzW36woEbA
|
#Secret:9qXoIhY01jqhWIeIluGliOS4O_rhcXGGGu422uRZ1JjZxIZmh17WwzW36woEbA
|
||||||
```
|
```
|
||||||
|
|
||||||
4. 启动容器
|
5. 启动容器
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -itd --name anylink --privileged \
|
docker run -itd --name anylink --privileged \
|
||||||
@@ -194,18 +234,19 @@ systemd 脚本放入:
|
|||||||
bjdgyc/anylink
|
bjdgyc/anylink
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 使用自定义参数启动容器
|
6. 使用自定义参数启动容器
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 参数可以参考 -h 命令
|
||||||
docker run -itd --name anylink --privileged \
|
docker run -itd --name anylink --privileged \
|
||||||
-e IPV4_CIDR=192.168.10.0/24 \
|
-e IPV4_CIDR=192.168.10.0/24 \
|
||||||
-p 443:443 -p 8800:8800 \
|
-p 443:443 -p 8800:8800 \
|
||||||
--restart=always \
|
--restart=always \
|
||||||
bjdgyc/anylink \
|
bjdgyc/anylink \
|
||||||
-c=/etc/server.toml --admin_addr=:8080
|
-c=/etc/server.toml --ip_lease = 1209600 \ # IP地址租约时长
|
||||||
```
|
```
|
||||||
|
|
||||||
6. 构建镜像
|
7. 构建镜像
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#获取仓库源码
|
#获取仓库源码
|
||||||
@@ -218,30 +259,38 @@ systemd 脚本放入:
|
|||||||
|
|
||||||
请前往 [问题地址](question.md) 查看具体信息
|
请前往 [问题地址](question.md) 查看具体信息
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
> 如果您觉得 anylink 对你有帮助,欢迎给我们打赏,也是帮助 anylink 更好的发展。
|
||||||
|
>
|
||||||
|
> [查看打赏列表](doc/README.md)
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="doc/screenshot/wxpay2.png" width="400" />
|
||||||
|
</p>
|
||||||
|
|
||||||
## Discussion
|
## Discussion
|
||||||
|
|
||||||

|
添加 QQ 群: 567510628
|
||||||
|
|
||||||
添加QQ群: 567510628
|
QQ 群共享文件有相关软件下载
|
||||||
|
|
||||||
QQ群共享文件有相关软件下载
|
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
欢迎提交 PR、Issues,感谢为AnyLink做出贡献。
|
欢迎提交 PR、Issues,感谢为 AnyLink 做出贡献。
|
||||||
|
|
||||||
注意新建PR,需要提交到dev分支,其他分支暂不会合并。
|
注意新建 PR,需要提交到 dev 分支,其他分支暂不会合并。
|
||||||
|
|
||||||
## Other Screenshot
|
## Other Screenshot
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>展开查看</summary>
|
<summary>展开查看</summary>
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -252,9 +301,5 @@ QQ群共享文件有相关软件下载
|
|||||||
## Thank
|
## Thank
|
||||||
|
|
||||||
<a href="https://www.jetbrains.com">
|
<a href="https://www.jetbrains.com">
|
||||||
<img src="screenshot/jetbrains.png" width="200" height="200" alt="jetbrains.png" />
|
<img src="doc/screenshot/jetbrains.png" width="200" alt="jetbrains.png" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5
build.sh
@@ -15,7 +15,7 @@ cpath=$(pwd)
|
|||||||
echo "编译前端项目"
|
echo "编译前端项目"
|
||||||
cd $cpath/web
|
cd $cpath/web
|
||||||
#国内可替换源加快速度
|
#国内可替换源加快速度
|
||||||
npx browserslist@latest --update-db
|
#npx browserslist@latest --update-db
|
||||||
npm install --registry=https://registry.npm.taobao.org
|
npm install --registry=https://registry.npm.taobao.org
|
||||||
#npm install
|
#npm install
|
||||||
npm run build
|
npm run build
|
||||||
@@ -25,6 +25,8 @@ echo "编译二进制文件"
|
|||||||
cd $cpath/server
|
cd $cpath/server
|
||||||
rm -rf ui
|
rm -rf ui
|
||||||
cp -rf $cpath/web/ui .
|
cp -rf $cpath/web/ui .
|
||||||
|
#国内可替换源加快速度
|
||||||
|
export GOPROXY=https://goproxy.io
|
||||||
go build -v -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)"
|
go build -v -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)"
|
||||||
RETVAL $?
|
RETVAL $?
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ mkdir $deploy
|
|||||||
|
|
||||||
cp -r server/anylink $deploy
|
cp -r server/anylink $deploy
|
||||||
cp -r server/bridge-init.sh $deploy
|
cp -r server/bridge-init.sh $deploy
|
||||||
|
cp -r server/conf $deploy
|
||||||
|
|
||||||
cp -r systemd $deploy
|
cp -r systemd $deploy
|
||||||
|
|
||||||
|
17
doc/README.md
Normal 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 |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
BIN
doc/screenshot/wxpay.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
doc/screenshot/wxpay2.png
Normal file
After Width: | Height: | Size: 71 KiB |
13
docker_build.sh
Normal 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
|
||||||
|
|
@@ -22,7 +22,7 @@ func GroupList(w http.ResponseWriter, r *http.Request) {
|
|||||||
count := dbdata.CountAll(&dbdata.Group{})
|
count := dbdata.CountAll(&dbdata.Group{})
|
||||||
|
|
||||||
var datas []dbdata.Group
|
var datas []dbdata.Group
|
||||||
err := dbdata.All(&datas, pageSize, page)
|
err := dbdata.Find(&datas, pageSize, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespError(w, RespInternalErr, err)
|
RespError(w, RespInternalErr, err)
|
||||||
return
|
return
|
||||||
|
@@ -5,7 +5,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
)
|
)
|
||||||
@@ -23,7 +22,7 @@ func UserIpMapList(w http.ResponseWriter, r *http.Request) {
|
|||||||
count := dbdata.CountAll(&dbdata.IpMap{})
|
count := dbdata.CountAll(&dbdata.IpMap{})
|
||||||
|
|
||||||
var datas []dbdata.IpMap
|
var datas []dbdata.IpMap
|
||||||
err := dbdata.All(&datas, pageSize, page)
|
err := dbdata.Find(&datas, pageSize, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespError(w, RespInternalErr, err)
|
RespError(w, RespInternalErr, err)
|
||||||
return
|
return
|
||||||
@@ -75,14 +74,7 @@ func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// fmt.Println(v, len(v.Ip), len(v.MacAddr))
|
// fmt.Println(v, len(v.Ip), len(v.MacAddr))
|
||||||
|
|
||||||
if len(v.IpAddr) < 4 || len(v.MacAddr) < 6 {
|
err = dbdata.SetIpMap(v)
|
||||||
RespError(w, RespParamErr, "IP或MAC错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
v.UpdatedAt = time.Now()
|
|
||||||
err = dbdata.Save(v)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespError(w, RespInternalErr, err)
|
RespError(w, RespInternalErr, err)
|
||||||
return
|
return
|
||||||
|
@@ -5,11 +5,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
"github.com/bjdgyc/anylink/pkg/utils"
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
"github.com/shirou/gopsutil/cpu"
|
"github.com/shirou/gopsutil/cpu"
|
||||||
"github.com/shirou/gopsutil/disk"
|
"github.com/shirou/gopsutil/disk"
|
||||||
"github.com/shirou/gopsutil/host"
|
"github.com/shirou/gopsutil/host"
|
||||||
|
33
server/admin/api_set_audit.go
Normal 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)
|
||||||
|
}
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
@@ -36,11 +37,11 @@ func UserList(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// 查询前缀匹配
|
// 查询前缀匹配
|
||||||
if len(prefix) > 0 {
|
if len(prefix) > 0 {
|
||||||
count = pageSize
|
count = dbdata.CountPrefix("username", prefix, &dbdata.User{})
|
||||||
err = dbdata.Prefix("Username", prefix, &datas, pageSize, 1)
|
err = dbdata.Prefix("username", prefix, &datas, pageSize, 1)
|
||||||
} else {
|
} else {
|
||||||
count = dbdata.CountAll(&dbdata.User{})
|
count = dbdata.CountAll(&dbdata.User{})
|
||||||
err = dbdata.All(&datas, pageSize, page)
|
err = dbdata.Find(&datas, pageSize, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && !dbdata.CheckErrNotFound(err) {
|
if err != nil && !dbdata.CheckErrNotFound(err) {
|
||||||
@@ -141,7 +142,7 @@ func UserOtpQr(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer := base.Cfg.Issuer
|
issuer := url.QueryEscape(base.Cfg.Issuer)
|
||||||
qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret)
|
qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret)
|
||||||
qr, _ := qrcode.New(qrstr, qrcode.High)
|
qr, _ := qrcode.New(qrstr, qrcode.High)
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
mail "github.com/xhit/go-simple-mail/v2"
|
mail "github.com/xhit/go-simple-mail/v2"
|
||||||
// "github.com/mojocn/base64Captcha"
|
// "github.com/mojocn/base64Captcha"
|
||||||
)
|
)
|
||||||
|
@@ -32,6 +32,7 @@ func StartAdmin() {
|
|||||||
r.HandleFunc("/set/other/edit", SetOtherEdit)
|
r.HandleFunc("/set/other/edit", SetOtherEdit)
|
||||||
r.HandleFunc("/set/other/smtp", SetOtherSmtp)
|
r.HandleFunc("/set/other/smtp", SetOtherSmtp)
|
||||||
r.HandleFunc("/set/other/smtp/edit", SetOtherSmtpEdit)
|
r.HandleFunc("/set/other/smtp/edit", SetOtherSmtpEdit)
|
||||||
|
r.HandleFunc("/set/audit/list", SetAuditList)
|
||||||
|
|
||||||
r.HandleFunc("/user/list", UserList)
|
r.HandleFunc("/user/list", UserList)
|
||||||
r.HandleFunc("/user/detail", UserDetail)
|
r.HandleFunc("/user/detail", UserDetail)
|
||||||
@@ -58,7 +59,7 @@ func StartAdmin() {
|
|||||||
r.HandleFunc("/debug/pprof/profile", pprof.Profile).Name("debug")
|
r.HandleFunc("/debug/pprof/profile", pprof.Profile).Name("debug")
|
||||||
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol).Name("debug")
|
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol).Name("debug")
|
||||||
r.HandleFunc("/debug/pprof/trace", pprof.Trace).Name("debug")
|
r.HandleFunc("/debug/pprof/trace", pprof.Trace).Name("debug")
|
||||||
r.HandleFunc("/debug/pprof", location("/debug/pprof/"))
|
r.HandleFunc("/debug/pprof", location("/debug/pprof/")).Name("debug")
|
||||||
r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index).Name("debug")
|
r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index).Name("debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,6 @@ package base
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
APP_NAME = "AnyLink"
|
APP_NAME = "AnyLink"
|
||||||
// 修复严重bug
|
// 修复前端bug
|
||||||
APP_VER = "0.4.1"
|
APP_VER = "0.6.2"
|
||||||
)
|
)
|
||||||
|
@@ -5,13 +5,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LinkModeTUN = "tun"
|
LinkModeTUN = "tun"
|
||||||
LinkModeTAP = "tap"
|
LinkModeTAP = "tap"
|
||||||
|
LinkModeMacvtap = "macvtap"
|
||||||
|
LinkModeIpvtap = "ipvtap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -31,12 +31,14 @@ var (
|
|||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
// LinkAddr string `json:"link_addr"`
|
// LinkAddr string `json:"link_addr"`
|
||||||
|
Conf string `json:"conf"`
|
||||||
ServerAddr string `json:"server_addr"`
|
ServerAddr string `json:"server_addr"`
|
||||||
ServerDTLSAddr string `json:"server_dtls_addr"`
|
ServerDTLSAddr string `json:"server_dtls_addr"`
|
||||||
ServerDTLS bool `json:"server_dtls"`
|
ServerDTLS bool `json:"server_dtls"`
|
||||||
AdminAddr string `json:"admin_addr"`
|
AdminAddr string `json:"admin_addr"`
|
||||||
ProxyProtocol bool `json:"proxy_protocol"`
|
ProxyProtocol bool `json:"proxy_protocol"`
|
||||||
DbFile string `json:"db_file"`
|
DbType string `json:"db_type"`
|
||||||
|
DbSource string `json:"db_source"`
|
||||||
CertFile string `json:"cert_file"`
|
CertFile string `json:"cert_file"`
|
||||||
CertKey string `json:"cert_key"`
|
CertKey string `json:"cert_key"`
|
||||||
FilesPath string `json:"files_path"`
|
FilesPath string `json:"files_path"`
|
||||||
@@ -48,11 +50,12 @@ type ServerConfig struct {
|
|||||||
AdminPass string `json:"admin_pass"`
|
AdminPass string `json:"admin_pass"`
|
||||||
JwtSecret string `json:"jwt_secret"`
|
JwtSecret string `json:"jwt_secret"`
|
||||||
|
|
||||||
LinkMode string `json:"link_mode"` // tun tap
|
LinkMode string `json:"link_mode"` // tun tap macvtap ipvtap
|
||||||
Ipv4CIDR string `json:"ipv4_cidr"` // 192.168.1.0/24
|
Ipv4Master string `json:"ipv4_master"` // eth0
|
||||||
Ipv4Gateway string `json:"ipv4_gateway"`
|
Ipv4CIDR string `json:"ipv4_cidr"` // 192.168.10.0/24
|
||||||
Ipv4Start string `json:"ipv4_start"` // 192.168.1.100
|
Ipv4Gateway string `json:"ipv4_gateway"` // 192.168.10.1
|
||||||
Ipv4End string `json:"ipv4_end"` // 192.168.1.200
|
Ipv4Start string `json:"ipv4_start"` // 192.168.10.100
|
||||||
|
Ipv4End string `json:"ipv4_end"` // 192.168.10.200
|
||||||
IpLease int `json:"ip_lease"`
|
IpLease int `json:"ip_lease"`
|
||||||
|
|
||||||
MaxClient int `json:"max_client"`
|
MaxClient int `json:"max_client"`
|
||||||
@@ -64,7 +67,8 @@ type ServerConfig struct {
|
|||||||
MobileDpd int `json:"mobile_dpd"`
|
MobileDpd int `json:"mobile_dpd"`
|
||||||
|
|
||||||
SessionTimeout int `json:"session_timeout"` // in seconds
|
SessionTimeout int `json:"session_timeout"` // in seconds
|
||||||
AuthTimeout int `json:"auth_timeout"` // in seconds
|
// AuthTimeout int `json:"auth_timeout"` // in seconds
|
||||||
|
AuditInterval int `json:"audit_interval"` // in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
func initServerCfg() {
|
func initServerCfg() {
|
||||||
@@ -81,6 +85,10 @@ func initServerCfg() {
|
|||||||
// Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
|
// Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
|
||||||
// Cfg.LogPath = getAbsPath(base, Cfg.LogPath)
|
// Cfg.LogPath = getAbsPath(base, Cfg.LogPath)
|
||||||
|
|
||||||
|
if Cfg.AdminPass == defaultPwd {
|
||||||
|
fmt.Fprintln(os.Stderr, "=== 使用默认的admin_pass有安全风险,请设置新的admin_pass ===")
|
||||||
|
}
|
||||||
|
|
||||||
if Cfg.JwtSecret == defaultJwt {
|
if Cfg.JwtSecret == defaultJwt {
|
||||||
fmt.Fprintln(os.Stderr, "=== 使用默认的jwt_secret有安全风险,请设置新的jwt_secret ===")
|
fmt.Fprintln(os.Stderr, "=== 使用默认的jwt_secret有安全风险,请设置新的jwt_secret ===")
|
||||||
}
|
}
|
||||||
@@ -114,13 +122,13 @@ func initCfg() {
|
|||||||
for _, v := range configs {
|
for _, v := range configs {
|
||||||
if v.Name == tag {
|
if v.Name == tag {
|
||||||
if v.Typ == cfgStr {
|
if v.Typ == cfgStr {
|
||||||
value.SetString(viper.GetString(v.Name))
|
value.SetString(linkViper.GetString(v.Name))
|
||||||
}
|
}
|
||||||
if v.Typ == cfgInt {
|
if v.Typ == cfgInt {
|
||||||
value.SetInt(int64(viper.GetInt(v.Name)))
|
value.SetInt(int64(linkViper.GetInt(v.Name)))
|
||||||
}
|
}
|
||||||
if v.Typ == cfgBool {
|
if v.Typ == cfgBool {
|
||||||
value.SetBool(viper.GetBool(v.Name))
|
value.SetBool(linkViper.GetBool(v.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,9 +157,6 @@ func ServerCfg2Slice() []SCfg {
|
|||||||
value := s.Field(i)
|
value := s.Field(i)
|
||||||
tag := field.Tag.Get("json")
|
tag := field.Tag.Get("json")
|
||||||
usage, env := getUsageEnv(tag)
|
usage, env := getUsageEnv(tag)
|
||||||
if usage == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
datas = append(datas, SCfg{Name: tag, Env: env, Info: usage, Data: value.Interface()})
|
datas = append(datas, SCfg{Name: tag, Env: env, Info: usage, Data: value.Interface()})
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -14,25 +16,26 @@ import (
|
|||||||
var (
|
var (
|
||||||
// 提交id
|
// 提交id
|
||||||
CommitId string
|
CommitId string
|
||||||
// 配置文件
|
|
||||||
cfgFile string
|
|
||||||
// pass明文
|
// pass明文
|
||||||
passwd string
|
passwd string
|
||||||
// 生成密钥
|
// 生成密钥
|
||||||
secret bool
|
secret bool
|
||||||
// 显示版本信息
|
// 显示版本信息
|
||||||
rev bool
|
rev bool
|
||||||
// 获取env名称
|
// 输出debug信息
|
||||||
env bool
|
debug bool
|
||||||
|
|
||||||
// Used for flags.
|
// Used for flags.
|
||||||
runSrv bool
|
runSrv bool
|
||||||
|
|
||||||
|
linkViper *viper.Viper
|
||||||
rootCmd *cobra.Command
|
rootCmd *cobra.Command
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute executes the root command.
|
// Execute executes the root command.
|
||||||
func execute() {
|
func execute() {
|
||||||
|
initCmd()
|
||||||
|
|
||||||
err := rootCmd.Execute()
|
err := rootCmd.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -40,13 +43,25 @@ func execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// viper.Debug()
|
// viper.Debug()
|
||||||
|
ref := reflect.ValueOf(linkViper)
|
||||||
|
s := ref.Elem()
|
||||||
|
ee := s.FieldByName("env")
|
||||||
|
if ee.Kind() != reflect.Map {
|
||||||
|
panic("Viper env is err")
|
||||||
|
}
|
||||||
|
rr := ee.MapRange()
|
||||||
|
for rr.Next() {
|
||||||
|
// fmt.Println(rr.Key(), rr.Value().Index(0))
|
||||||
|
envs[rr.Key().String()] = rr.Value().Index(0).String()
|
||||||
|
}
|
||||||
|
|
||||||
if !runSrv {
|
if !runSrv {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func initCmd() {
|
||||||
|
linkViper = viper.New()
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "anylink",
|
Use: "anylink",
|
||||||
Short: "AnyLink VPN Server",
|
Short: "AnyLink VPN Server",
|
||||||
@@ -57,43 +72,44 @@ func init() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cobra.OnInitialize(func() {
|
linkViper.SetEnvPrefix("link")
|
||||||
viper.SetConfigFile(cfgFile)
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
|
|
||||||
if cfgFile == "" {
|
|
||||||
// 没有配置文件,不做处理
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := viper.ReadInConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Using config file:", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
viper.SetEnvPrefix("link")
|
|
||||||
|
|
||||||
// 基础配置
|
// 基础配置
|
||||||
rootCmd.Flags().StringVarP(&cfgFile, "conf", "c", "", "config file")
|
|
||||||
|
|
||||||
for _, v := range configs {
|
for _, v := range configs {
|
||||||
if v.Typ == cfgStr {
|
if v.Typ == cfgStr {
|
||||||
rootCmd.Flags().String(v.Name, v.ValStr, v.Usage)
|
rootCmd.Flags().StringP(v.Name, v.Short, v.ValStr, v.Usage)
|
||||||
}
|
}
|
||||||
if v.Typ == cfgInt {
|
if v.Typ == cfgInt {
|
||||||
rootCmd.Flags().Int(v.Name, v.ValInt, v.Usage)
|
rootCmd.Flags().IntP(v.Name, v.Short, v.ValInt, v.Usage)
|
||||||
}
|
}
|
||||||
if v.Typ == cfgBool {
|
if v.Typ == cfgBool {
|
||||||
rootCmd.Flags().Bool(v.Name, v.ValBool, v.Usage)
|
rootCmd.Flags().BoolP(v.Name, v.Short, v.ValBool, v.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = viper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name))
|
_ = linkViper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name))
|
||||||
_ = viper.BindEnv(v.Name)
|
_ = linkViper.BindEnv(v.Name)
|
||||||
// viper.SetDefault(v.Name, v.Value)
|
// viper.SetDefault(v.Name, v.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(initToolCmd())
|
rootCmd.AddCommand(initToolCmd())
|
||||||
|
|
||||||
|
cobra.OnInitialize(func() {
|
||||||
|
linkViper.AutomaticEnv()
|
||||||
|
conf := linkViper.GetString("conf")
|
||||||
|
|
||||||
|
_, err := os.Stat(conf)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
// 没有配置文件,不做处理
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
linkViper.SetConfigFile(conf)
|
||||||
|
err = linkViper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Using config file:", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initToolCmd() *cobra.Command {
|
func initToolCmd() *cobra.Command {
|
||||||
@@ -106,7 +122,7 @@ func initToolCmd() *cobra.Command {
|
|||||||
toolCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info")
|
toolCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info")
|
||||||
toolCmd.Flags().BoolVarP(&secret, "secret", "s", false, "generate a random jwt secret")
|
toolCmd.Flags().BoolVarP(&secret, "secret", "s", false, "generate a random jwt secret")
|
||||||
toolCmd.Flags().StringVarP(&passwd, "passwd", "p", "", "convert the password plaintext")
|
toolCmd.Flags().StringVarP(&passwd, "passwd", "p", "", "convert the password plaintext")
|
||||||
toolCmd.Flags().BoolVarP(&env, "env", "e", false, "list the config name and env key")
|
toolCmd.Flags().BoolVarP(&debug, "debug", "d", false, "list the config viper.Debug() info")
|
||||||
|
|
||||||
toolCmd.Run = func(cmd *cobra.Command, args []string) {
|
toolCmd.Run = func(cmd *cobra.Command, args []string) {
|
||||||
switch {
|
switch {
|
||||||
@@ -120,10 +136,8 @@ func initToolCmd() *cobra.Command {
|
|||||||
case passwd != "":
|
case passwd != "":
|
||||||
pass, _ := utils.PasswordHash(passwd)
|
pass, _ := utils.PasswordHash(passwd)
|
||||||
fmt.Printf("Passwd:%s\n", pass)
|
fmt.Printf("Passwd:%s\n", pass)
|
||||||
case env:
|
case debug:
|
||||||
for k, v := range envs {
|
linkViper.Debug()
|
||||||
fmt.Printf("%s => %s\n", k, v)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("Using [anylink tool -h] for help")
|
fmt.Println("Using [anylink tool -h] for help")
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,13 @@ const (
|
|||||||
cfgBool
|
cfgBool
|
||||||
|
|
||||||
defaultJwt = "abcdef.0123456789.abcdef"
|
defaultJwt = "abcdef.0123456789.abcdef"
|
||||||
|
defaultPwd = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"
|
||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
Typ int
|
Typ int
|
||||||
Name string
|
Name string
|
||||||
|
Short string
|
||||||
Usage string
|
Usage string
|
||||||
ValStr string
|
ValStr string
|
||||||
ValInt int
|
ValInt int
|
||||||
@@ -18,23 +20,26 @@ type config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var configs = []config{
|
var configs = []config{
|
||||||
|
{Typ: cfgStr, Name: "conf", Usage: "config file", ValStr: "./conf/server.toml", Short: "c"},
|
||||||
{Typ: cfgStr, Name: "server_addr", Usage: "服务监听地址", ValStr: ":443"},
|
{Typ: cfgStr, Name: "server_addr", Usage: "服务监听地址", ValStr: ":443"},
|
||||||
{Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false},
|
{Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false},
|
||||||
{Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":4433"},
|
{Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":4433"},
|
||||||
{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
|
{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
|
||||||
{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
|
{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
|
||||||
{Typ: cfgStr, Name: "db_file", Usage: "数据库地址", ValStr: "./data.db"},
|
{Typ: cfgStr, Name: "db_type", Usage: "数据库类型 [sqlite3 mysql postgres]", ValStr: "sqlite3"},
|
||||||
{Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./vpn_cert.pem"},
|
{Typ: cfgStr, Name: "db_source", Usage: "数据库source", ValStr: "./conf/anylink.db"},
|
||||||
{Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./vpn_cert.key"},
|
{Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./conf/vpn_cert.pem"},
|
||||||
{Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./files"},
|
{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_path", Usage: "日志文件路径,默认标准输出", ValStr: ""},
|
||||||
{Typ: cfgStr, Name: "log_level", Usage: "日志等级 debug、info、warn、error", ValStr: "info"},
|
{Typ: cfgStr, Name: "log_level", Usage: "日志等级 [debug info warn error]", ValStr: "info"},
|
||||||
{Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false},
|
{Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false},
|
||||||
{Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"},
|
{Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"},
|
||||||
{Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"},
|
{Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"},
|
||||||
{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"},
|
{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: defaultPwd},
|
||||||
{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: defaultJwt},
|
{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: defaultJwt},
|
||||||
{Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型", ValStr: "tun"},
|
{Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型[tun tap macvtap ipvtap]", ValStr: "tun"},
|
||||||
|
{Typ: cfgStr, Name: "ipv4_master", Usage: "ipv4主网卡名称", ValStr: "eth0"},
|
||||||
{Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"},
|
{Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"},
|
||||||
{Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.10.1"},
|
{Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.10.1"},
|
||||||
{Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.10.100"},
|
{Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.10.100"},
|
||||||
@@ -50,6 +55,7 @@ var configs = []config{
|
|||||||
{Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 60},
|
{Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 60},
|
||||||
{Typ: cfgInt, Name: "session_timeout", Usage: "session过期时间(秒)", ValInt: 3600},
|
{Typ: cfgInt, Name: "session_timeout", Usage: "session过期时间(秒)", ValInt: 3600},
|
||||||
// {Typ: cfgInt, Name: "auth_timeout", Usage: "auth_timeout", ValInt: 0},
|
// {Typ: cfgInt, Name: "auth_timeout", Usage: "auth_timeout", ValInt: 0},
|
||||||
|
{Typ: cfgInt, Name: "audit_interval", Usage: "审计去重间隔(秒),-1关闭", ValInt: -1},
|
||||||
}
|
}
|
||||||
|
|
||||||
var envs = map[string]string{}
|
var envs = map[string]string{}
|
||||||
|
@@ -1,19 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
#################################
|
#yum install bridge-utils
|
||||||
# Set up Ethernet bridge on Linux
|
|
||||||
# Requires: bridge-utils
|
|
||||||
#################################
|
|
||||||
|
|
||||||
# Define Bridge Interface
|
# Define Bridge Interface
|
||||||
br="anylink0"
|
br="anylink0"
|
||||||
|
|
||||||
# Define physical ethernet interface to be bridged
|
# 请根据sever服务器信息,更新下面的信息
|
||||||
# with TAP interface(s) above.
|
|
||||||
|
|
||||||
eth="eth0"
|
eth="eth0"
|
||||||
eth_ip="192.168.10.4"
|
eth_ip="192.168.10.4/24"
|
||||||
eth_netmask="255.255.255.0"
|
|
||||||
eth_broadcast="192.168.10.255"
|
eth_broadcast="192.168.10.255"
|
||||||
eth_gateway="192.168.10.1"
|
eth_gateway="192.168.10.1"
|
||||||
|
|
||||||
@@ -21,11 +15,14 @@ eth_gateway="192.168.10.1"
|
|||||||
brctl addbr $br
|
brctl addbr $br
|
||||||
brctl addif $br $eth
|
brctl addif $br $eth
|
||||||
|
|
||||||
ifconfig $eth 0.0.0.0 up
|
ip addr del $eth_ip dev $eth
|
||||||
|
ip addr add 0.0.0.0 dev $eth
|
||||||
|
ip link set dev $eth up promisc on
|
||||||
|
|
||||||
mac=`cat /sys/class/net/$eth/address`
|
mac=`cat /sys/class/net/$eth/address`
|
||||||
ifconfig $br hw ether $mac
|
ip link set dev $br up address $mac promisc on
|
||||||
ifconfig $br $eth_ip netmask $eth_netmask broadcast $eth_broadcast up
|
ip addr add $eth_ip broadcast $eth_broadcast dev $br
|
||||||
|
|
||||||
|
|
||||||
route add default gateway $eth_gateway
|
route add default gateway $eth_gateway
|
||||||
|
|
||||||
|
@@ -1,16 +1,17 @@
|
|||||||
#服务配置信息
|
#示例配置信息
|
||||||
|
|
||||||
#其他配置文件,可以使用绝对路径
|
#其他配置文件,可以使用绝对路径
|
||||||
#或者相对于server.toml的路径
|
#或者相对于 anylink 二进制文件的路径
|
||||||
|
|
||||||
#数据文件
|
#数据文件
|
||||||
db_file = "./data.db"
|
db_type = "sqlite3"
|
||||||
|
db_source = "./conf/anylink.db"
|
||||||
#证书文件
|
#证书文件
|
||||||
cert_file = "./vpn_cert.pem"
|
cert_file = "./conf/vpn_cert.pem"
|
||||||
cert_key = "./vpn_cert.key"
|
cert_key = "./conf/vpn_cert.key"
|
||||||
files_path = "./files"
|
files_path = "./conf/files"
|
||||||
#日志目录,为空写入标准输出
|
#日志目录,为空写入标准输出
|
||||||
#log_path = "../log"
|
#log_path = "./log"
|
||||||
log_path = ""
|
log_path = ""
|
||||||
log_level = "debug"
|
log_level = "debug"
|
||||||
pprof = false
|
pprof = false
|
||||||
@@ -37,6 +38,7 @@ proxy_protocol = false
|
|||||||
link_mode = "tun"
|
link_mode = "tun"
|
||||||
|
|
||||||
#客户端分配的ip地址池
|
#客户端分配的ip地址池
|
||||||
|
ipv4_master = "eth0"
|
||||||
ipv4_cidr = "192.168.10.0/24"
|
ipv4_cidr = "192.168.10.0/24"
|
||||||
ipv4_gateway = "192.168.10.1"
|
ipv4_gateway = "192.168.10.1"
|
||||||
ipv4_start = "192.168.10.100"
|
ipv4_start = "192.168.10.100"
|
||||||
@@ -60,7 +62,7 @@ mobile_dpd = 50
|
|||||||
#session过期时间,用于断线重连,0永不过期
|
#session过期时间,用于断线重连,0永不过期
|
||||||
session_timeout = 3600
|
session_timeout = 3600
|
||||||
auth_timeout = 0
|
auth_timeout = 0
|
||||||
|
audit_interval = -1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,70 +1,112 @@
|
|||||||
package dbdata
|
package dbdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
|
||||||
"github.com/asdine/storm/v3/codec/json"
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
bolt "go.etcd.io/bbolt"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sdb *storm.DB
|
xdb *xorm.Engine
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetXdb() *xorm.Engine {
|
||||||
|
return xdb
|
||||||
|
}
|
||||||
|
|
||||||
func initDb() {
|
func initDb() {
|
||||||
var err error
|
var err error
|
||||||
sdb, err = storm.Open(base.Cfg.DbFile, storm.Codec(json.Codec),
|
xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource)
|
||||||
storm.BoltOptions(0600, &bolt.Options{Timeout: 10 * time.Second}))
|
// xdb.ShowSQL(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatal(err)
|
base.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化数据库
|
// 初始化数据库
|
||||||
err = sdb.Init(&User{})
|
err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatal(err)
|
base.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmt.Println("s1")
|
// fmt.Println("s1=============", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initData() {
|
func initData() {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
install bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 判断是否初次使用
|
// 判断是否初次使用
|
||||||
err = Get(SettingBucket, Installed, &install)
|
install := &SettingInstall{}
|
||||||
if err == nil && install {
|
err = SettingGet(install)
|
||||||
|
|
||||||
|
if err == nil && install.Installed {
|
||||||
// 已经安装过
|
// 已经安装过
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
// 发生错误
|
||||||
_ = Set(SettingBucket, Installed, true)
|
if err != ErrNotFound {
|
||||||
}()
|
base.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addInitData()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInitData() error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
sess := xdb.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
err = sess.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingSmtp
|
||||||
smtp := &SettingSmtp{
|
smtp := &SettingSmtp{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 25,
|
Port: 25,
|
||||||
From: "vpn@xx.com",
|
From: "vpn@xx.com",
|
||||||
|
Encryption: "None",
|
||||||
|
}
|
||||||
|
err = SettingSessAdd(sess, smtp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
_ = SettingSet(smtp)
|
|
||||||
|
|
||||||
|
// SettingOther
|
||||||
other := &SettingOther{
|
other := &SettingOther{
|
||||||
LinkAddr: "vpn.xx.com",
|
LinkAddr: "vpn.xx.com",
|
||||||
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为!",
|
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为!",
|
||||||
AccountMail: accountMail,
|
AccountMail: accountMail,
|
||||||
}
|
}
|
||||||
_ = SettingSet(other)
|
err = SettingSessAdd(sess, other)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install
|
||||||
|
install := &SettingInstall{Installed: true}
|
||||||
|
err = SettingSessAdd(sess, install)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckErrNotFound(err error) bool {
|
func CheckErrNotFound(err error) bool {
|
||||||
return err == storm.ErrNotFound
|
return err == ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountMail = `<p>您好:</p>
|
const accountMail = `<p>您好:</p>
|
||||||
|
@@ -1,66 +1,84 @@
|
|||||||
package dbdata
|
package dbdata
|
||||||
|
|
||||||
import "github.com/asdine/storm/v3/index"
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
const PageSize = 10
|
const PageSize = 10
|
||||||
|
|
||||||
func Save(data interface{}) error {
|
var ErrNotFound = errors.New("ErrNotFound")
|
||||||
return sdb.Save(data)
|
|
||||||
|
func Add(data interface{}) error {
|
||||||
|
_, err := xdb.InsertOne(data)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Update(data interface{}) error {
|
func Update(fieldName string, value interface{}, data interface{}) error {
|
||||||
return sdb.Update(data)
|
_, err := xdb.Where(fieldName+"=?", value).Update(data)
|
||||||
}
|
return err
|
||||||
|
|
||||||
func UpdateField(data interface{}, fieldName string, value interface{}) error {
|
|
||||||
return sdb.UpdateField(data, fieldName, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Del(data interface{}) error {
|
func Del(data interface{}) error {
|
||||||
return sdb.DeleteStruct(data)
|
_, err := xdb.Delete(data)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Set(bucket, key string, data interface{}) error {
|
func extract(data interface{}, fieldName string) interface{} {
|
||||||
return sdb.Set(bucket, key, data)
|
ref := reflect.ValueOf(data)
|
||||||
|
r := &ref
|
||||||
|
if r.Kind() == reflect.Ptr {
|
||||||
|
e := r.Elem()
|
||||||
|
r = &e
|
||||||
|
}
|
||||||
|
field := r.FieldByName(fieldName).Interface()
|
||||||
|
return field
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(bucket, key string, data interface{}) error {
|
// 更新全部字段
|
||||||
return sdb.Get(bucket, key, data)
|
func Set(data interface{}) error {
|
||||||
|
id := extract(data, "Id")
|
||||||
|
_, err := xdb.ID(id).AllCols().Update(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func One(fieldName string, value interface{}, data interface{}) error {
|
||||||
|
has, err := xdb.Where(fieldName+"=?", value).Get(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CountAll(data interface{}) int {
|
func CountAll(data interface{}) int {
|
||||||
n, _ := sdb.Count(data)
|
n, _ := xdb.Count(data)
|
||||||
return n
|
return int(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func One(fieldName string, value interface{}, to interface{}) error {
|
func Find(data interface{}, limit, page int) error {
|
||||||
return sdb.One(fieldName, value, to)
|
if limit == 0 {
|
||||||
}
|
return xdb.Find(data)
|
||||||
|
|
||||||
func Find(fieldName string, value interface{}, to interface{}, options ...func(q *index.Options)) error {
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
if skip > 0 {
|
|
||||||
opt.Skip = skip
|
start := (page - 1) * limit
|
||||||
}
|
return xdb.Limit(limit, start).Find(data)
|
||||||
}
|
}
|
||||||
return opt
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
@@ -11,12 +11,13 @@ import (
|
|||||||
|
|
||||||
func preIpData() {
|
func preIpData() {
|
||||||
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
|
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
|
||||||
base.Cfg.DbFile = tmpDb
|
base.Cfg.DbType = "sqlite3"
|
||||||
|
base.Cfg.DbSource = tmpDb
|
||||||
initDb()
|
initDb()
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeIpdata() {
|
func closeIpdata() {
|
||||||
sdb.Close()
|
xdb.Close()
|
||||||
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
|
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
|
||||||
os.Remove(tmpDb)
|
os.Remove(tmpDb)
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@ func TestDb(t *testing.T) {
|
|||||||
defer closeIpdata()
|
defer closeIpdata()
|
||||||
|
|
||||||
u := User{Username: "a"}
|
u := User{Username: "a"}
|
||||||
err := Save(&u)
|
err := Add(&u)
|
||||||
ast.Nil(err)
|
ast.Nil(err)
|
||||||
|
|
||||||
ast.Equal(u.Id, 1)
|
ast.Equal(u.Id, 1)
|
||||||
|
@@ -29,24 +29,24 @@ type ValData struct {
|
|||||||
Note string `json:"note"`
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
// type Group struct {
|
||||||
Id int `json:"id" storm:"id,increment"`
|
// Id int `json:"id" xorm:"pk autoincr not null"`
|
||||||
Name string `json:"name" storm:"unique"`
|
// Name string `json:"name" xorm:"not null unique"`
|
||||||
Note string `json:"note"`
|
// Note string `json:"note"`
|
||||||
AllowLan bool `json:"allow_lan"`
|
// AllowLan bool `json:"allow_lan"`
|
||||||
ClientDns []ValData `json:"client_dns"`
|
// ClientDns []ValData `json:"client_dns"`
|
||||||
RouteInclude []ValData `json:"route_include"`
|
// RouteInclude []ValData `json:"route_include"`
|
||||||
RouteExclude []ValData `json:"route_exclude"`
|
// RouteExclude []ValData `json:"route_exclude"`
|
||||||
LinkAcl []GroupLinkAcl `json:"link_acl"`
|
// LinkAcl []GroupLinkAcl `json:"link_acl"`
|
||||||
Bandwidth int `json:"bandwidth"` // 带宽限制
|
// Bandwidth int `json:"bandwidth"` // 带宽限制
|
||||||
Status int8 `json:"status"` // 1正常
|
// Status int8 `json:"status"` // 1正常
|
||||||
CreatedAt time.Time `json:"created_at"`
|
// CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
// UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
// }
|
||||||
|
|
||||||
func GetGroupNames() []string {
|
func GetGroupNames() []string {
|
||||||
var datas []Group
|
var datas []Group
|
||||||
err := All(&datas, 0, 0)
|
err := Find(&datas, 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
return nil
|
return nil
|
||||||
@@ -68,17 +68,26 @@ func SetGroup(g *Group) error {
|
|||||||
clientDns := []ValData{}
|
clientDns := []ValData{}
|
||||||
for _, v := range g.ClientDns {
|
for _, v := range g.ClientDns {
|
||||||
if v.Val != "" {
|
if v.Val != "" {
|
||||||
|
ip := net.ParseIP(v.Val)
|
||||||
|
if ip.String() != v.Val {
|
||||||
|
return errors.New("DNS IP 错误")
|
||||||
|
}
|
||||||
clientDns = append(clientDns, v)
|
clientDns = append(clientDns, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(clientDns) == 0 {
|
if len(clientDns) == 0 {
|
||||||
return errors.New("DNS 错误")
|
return errors.New("必须设置一个DNS")
|
||||||
}
|
}
|
||||||
g.ClientDns = clientDns
|
g.ClientDns = clientDns
|
||||||
|
|
||||||
routeInclude := []ValData{}
|
routeInclude := []ValData{}
|
||||||
for _, v := range g.RouteInclude {
|
for _, v := range g.RouteInclude {
|
||||||
if v.Val != "" {
|
if v.Val != "" {
|
||||||
|
if v.Val == "all" {
|
||||||
|
routeInclude = append(routeInclude, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
ipMask, _, err := parseIpNet(v.Val)
|
ipMask, _, err := parseIpNet(v.Val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("RouteInclude 错误" + err.Error())
|
return errors.New("RouteInclude 错误" + err.Error())
|
||||||
@@ -116,7 +125,11 @@ func SetGroup(g *Group) error {
|
|||||||
g.LinkAcl = linkAcl
|
g.LinkAcl = linkAcl
|
||||||
|
|
||||||
g.UpdatedAt = time.Now()
|
g.UpdatedAt = time.Now()
|
||||||
err = Save(g)
|
if g.Id > 0 {
|
||||||
|
err = Set(g)
|
||||||
|
} else {
|
||||||
|
err = Add(g)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,34 @@
|
|||||||
package dbdata
|
package dbdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IpMap struct {
|
// type IpMap struct {
|
||||||
Id int `json:"id" storm:"id,increment"`
|
// Id int `json:"id" xorm:"pk autoincr not null"`
|
||||||
IpAddr net.IP `json:"ip_addr" storm:"unique"`
|
// IpAddr string `json:"ip_addr" xorm:"not null unique"`
|
||||||
MacAddr string `json:"mac_addr" storm:"unique"`
|
// MacAddr string `json:"mac_addr" xorm:"not null unique"`
|
||||||
Username string `json:"username"`
|
// Username string `json:"username"`
|
||||||
Keep bool `json:"keep"` // 保留 ip-mac 绑定
|
// Keep bool `json:"keep"` // 保留 ip-mac 绑定
|
||||||
KeepTime time.Time `json:"keep_time"`
|
// KeepTime time.Time `json:"keep_time"`
|
||||||
Note string `json:"note"` // 备注
|
// Note string `json:"note"` // 备注
|
||||||
LastLogin time.Time `json:"last_login"`
|
// LastLogin time.Time `json:"last_login"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
// UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
func SetIpMap(v *IpMap) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(v.IpAddr) < 4 || len(v.MacAddr) < 6 {
|
||||||
|
return errors.New("IP或MAC错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
v.UpdatedAt = time.Now()
|
||||||
|
if v.Id > 0 {
|
||||||
|
err = Set(v)
|
||||||
|
} else {
|
||||||
|
err = Add(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -1,35 +1,13 @@
|
|||||||
package dbdata
|
package dbdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
type SettingInstall struct {
|
||||||
SettingBucket = "SettingBucket"
|
Installed bool `json:"installed"`
|
||||||
Installed = "Installed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StructName(data interface{}) string {
|
|
||||||
ref := reflect.ValueOf(data)
|
|
||||||
s := &ref
|
|
||||||
if s.Kind() == reflect.Ptr {
|
|
||||||
e := s.Elem()
|
|
||||||
s = &e
|
|
||||||
}
|
|
||||||
name := s.Type().Name()
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
func SettingSet(data interface{}) error {
|
|
||||||
key := StructName(data)
|
|
||||||
err := Set(SettingBucket, key, data)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func SettingGet(data interface{}) error {
|
|
||||||
key := StructName(data)
|
|
||||||
err := Get(SettingBucket, key, data)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingSmtp struct {
|
type SettingSmtp struct {
|
||||||
@@ -46,3 +24,42 @@ type SettingOther struct {
|
|||||||
Banner string `json:"banner"`
|
Banner string `json:"banner"`
|
||||||
AccountMail string `json:"account_mail"`
|
AccountMail string `json:"account_mail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StructName(data interface{}) string {
|
||||||
|
ref := reflect.ValueOf(data)
|
||||||
|
s := &ref
|
||||||
|
if s.Kind() == reflect.Ptr {
|
||||||
|
e := s.Elem()
|
||||||
|
s = &e
|
||||||
|
}
|
||||||
|
name := s.Type().Name()
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func SettingSessAdd(sess *xorm.Session, data interface{}) error {
|
||||||
|
name := StructName(data)
|
||||||
|
v, _ := json.Marshal(data)
|
||||||
|
s := &Setting{Name: name, Data: v}
|
||||||
|
_, err := sess.InsertOne(s)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SettingSet(data interface{}) error {
|
||||||
|
name := StructName(data)
|
||||||
|
v, _ := json.Marshal(data)
|
||||||
|
s := &Setting{Data: v}
|
||||||
|
err := Update("name", name, s)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SettingGet(data interface{}) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@@ -6,5 +6,5 @@ func Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Stop() error {
|
func Stop() error {
|
||||||
return sdb.Close()
|
return xdb.Close()
|
||||||
}
|
}
|
||||||
|
67
server/dbdata/tables.go
Normal file
@@ -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"`
|
||||||
|
}
|
@@ -10,21 +10,21 @@ import (
|
|||||||
"github.com/xlzd/gotp"
|
"github.com/xlzd/gotp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
// type User struct {
|
||||||
Id int `json:"id" storm:"id,increment"`
|
// Id int `json:"id" xorm:"pk autoincr not null"`
|
||||||
Username string `json:"username" storm:"unique"`
|
// Username string `json:"username" storm:"not null unique"`
|
||||||
Nickname string `json:"nickname"`
|
// Nickname string `json:"nickname"`
|
||||||
Email string `json:"email"`
|
// Email string `json:"email"`
|
||||||
// Password string `json:"password"`
|
// // Password string `json:"password"`
|
||||||
PinCode string `json:"pin_code"`
|
// PinCode string `json:"pin_code"`
|
||||||
OtpSecret string `json:"otp_secret"`
|
// OtpSecret string `json:"otp_secret"`
|
||||||
DisableOtp bool `json:"disable_otp"` // 禁用otp
|
// DisableOtp bool `json:"disable_otp"` // 禁用otp
|
||||||
Groups []string `json:"groups"`
|
// Groups []string `json:"groups"`
|
||||||
Status int8 `json:"status"` // 1正常
|
// Status int8 `json:"status"` // 1正常
|
||||||
SendEmail bool `json:"send_email"`
|
// SendEmail bool `json:"send_email"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
// CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
// UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
// }
|
||||||
|
|
||||||
func SetUser(v *User) error {
|
func SetUser(v *User) error {
|
||||||
var err error
|
var err error
|
||||||
@@ -57,7 +57,11 @@ func SetUser(v *User) error {
|
|||||||
v.Groups = ng
|
v.Groups = ng
|
||||||
|
|
||||||
v.UpdatedAt = time.Now()
|
v.UpdatedAt = time.Now()
|
||||||
err = Save(v)
|
if v.Id > 0 {
|
||||||
|
err = Set(v)
|
||||||
|
} else {
|
||||||
|
err = Add(v)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -3,28 +3,29 @@ module github.com/bjdgyc/anylink
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||||
github.com/asdine/storm/v3 v3.2.1
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/golang-jwt/jwt/v4 v4.0.0
|
||||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/pion/dtls/v2 v2.0.0-00010101000000-000000000000
|
github.com/lib/pq v1.10.2
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.8
|
||||||
|
github.com/pion/dtls/v2 v2.0.9
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
github.com/shirou/gopsutil v3.21.4+incompatible
|
github.com/shirou/gopsutil v3.21.7+incompatible
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
|
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.2.1
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.8.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tklauser/go-sysconf v0.3.6 // indirect
|
github.com/tklauser/go-sysconf v0.3.7 // indirect
|
||||||
github.com/xhit/go-simple-mail/v2 v2.9.0
|
github.com/xhit/go-simple-mail/v2 v2.10.0
|
||||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||||
go.etcd.io/bbolt v1.3.5
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
|
||||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
xorm.io/xorm v1.2.2
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/pion/dtls/v2 => ../dtls-2.0.9
|
replace github.com/pion/dtls/v2 => ../dtls-2.0.9
|
||||||
|
769
server/go.sum
@@ -58,7 +58,7 @@ func execCmd(cmdStrs []string) error {
|
|||||||
cmd := exec.Command("sh", "-c", cmdStr)
|
cmd := exec.Command("sh", "-c", cmdStr)
|
||||||
b, err := cmd.CombinedOutput()
|
b, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(string(b), err)
|
log.Println(string(b))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,17 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
base.Debug("LinkCstp return", cSess.IpAddr)
|
base.Debug("LinkCstp return", cSess.IpAddr)
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
@@ -23,19 +25,19 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
dead = time.Duration(cSess.CstpDpd+5) * time.Second
|
dead = time.Duration(cSess.CstpDpd+5) * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
go cstpWrite(conn, cSess)
|
go cstpWrite(conn, bufRW, cSess)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
// 设置超时限制
|
// 设置超时限制
|
||||||
err = conn.SetReadDeadline(time.Now().Add(dead))
|
err = conn.SetReadDeadline(utils.NowSec().Add(dead))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("SetDeadline: ", err)
|
base.Error("SetDeadline: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// hdata := make([]byte, BufferSize)
|
// hdata := make([]byte, BufferSize)
|
||||||
hdata := getByteFull()
|
pl := getPayload()
|
||||||
n, err = conn.Read(hdata)
|
n, err = bufRW.Read(pl.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("read hdata: ", err)
|
base.Error("read hdata: ", err)
|
||||||
return
|
return
|
||||||
@@ -47,7 +49,7 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
base.Error(err)
|
base.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch hdata[6] {
|
switch pl.Data[6] {
|
||||||
case 0x07: // KEEPALIVE
|
case 0x07: // KEEPALIVE
|
||||||
// do nothing
|
// do nothing
|
||||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||||
@@ -56,23 +58,28 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
return
|
return
|
||||||
case 0x03: // DPD-REQ
|
case 0x03: // DPD-REQ
|
||||||
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
||||||
if payloadOutCstp(cSess, sessdata.LTypeIPData, 0x04, nil) {
|
pl.PType = 0x04
|
||||||
|
if payloadOutCstp(cSess, pl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 0x04:
|
case 0x04:
|
||||||
// log.Println("recv DPD-RESP")
|
// log.Println("recv DPD-RESP")
|
||||||
case 0x00: // DATA
|
case 0x00: // DATA
|
||||||
dataLen = binary.BigEndian.Uint16(hdata[4:6]) // 4,5
|
// 获取数据长度
|
||||||
if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[8:8+dataLen]) {
|
dataLen = binary.BigEndian.Uint16(pl.Data[4:6]) // 4,5
|
||||||
|
// 去除数据头
|
||||||
|
copy(pl.Data, pl.Data[8:8+dataLen])
|
||||||
|
// 更新切片长度
|
||||||
|
pl.Data = pl.Data[:dataLen]
|
||||||
|
// pl.Data = append(pl.Data[:0], pl.Data[8:8+dataLen]...)
|
||||||
|
if payloadIn(cSess, pl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
putByte(hdata)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
|
func cstpWrite(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
base.Debug("cstpWrite return", cSess.IpAddr)
|
base.Debug("cstpWrite return", cSess.IpAddr)
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
@@ -82,36 +89,44 @@ func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
n int
|
n int
|
||||||
// header []byte
|
pl *sessdata.Payload
|
||||||
payload *sessdata.Payload
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case payload = <-cSess.PayloadOutCstp:
|
case pl = <-cSess.PayloadOutCstp:
|
||||||
case <-cSess.CloseChan:
|
case <-cSess.CloseChan:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.LType != sessdata.LTypeIPData {
|
if pl.LType != sessdata.LTypeIPData {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
h := []byte{'S', 'T', 'F', 0x01, 0x00, 0x00, payload.PType, 0x00}
|
if pl.PType == 0x00 {
|
||||||
header := getByteZero()
|
// 获取数据长度
|
||||||
header = append(header, h...)
|
l := len(pl.Data)
|
||||||
if payload.PType == 0x00 { // data
|
// 先扩容 +8
|
||||||
binary.BigEndian.PutUint16(header[4:6], uint16(len(payload.Data)))
|
pl.Data = pl.Data[:l+8]
|
||||||
header = append(header, payload.Data...)
|
// 数据后移
|
||||||
|
copy(pl.Data[8:], pl.Data)
|
||||||
|
// 添加头信息
|
||||||
|
copy(pl.Data[:8], plHeader)
|
||||||
|
// 更新头长度
|
||||||
|
binary.BigEndian.PutUint16(pl.Data[4:6], uint16(l))
|
||||||
|
} else {
|
||||||
|
pl.Data = append(pl.Data[:0], plHeader...)
|
||||||
|
// 设置头类型
|
||||||
|
pl.Data[6] = pl.PType
|
||||||
}
|
}
|
||||||
n, err = conn.Write(header)
|
|
||||||
|
n, err = conn.Write(pl.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("write err", err)
|
base.Error("write err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putByte(header)
|
putPayload(pl)
|
||||||
putPayload(payload)
|
|
||||||
|
|
||||||
// 限流设置
|
// 限流设置
|
||||||
err = cSess.RateLimit(n, false)
|
err = cSess.RateLimit(n, false)
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,21 +25,22 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
err error
|
||||||
|
n int
|
||||||
dead = time.Duration(cSess.CstpDpd+5) * time.Second
|
dead = time.Duration(cSess.CstpDpd+5) * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
go dtlsWrite(conn, dSess, cSess)
|
go dtlsWrite(conn, dSess, cSess)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
err := conn.SetReadDeadline(time.Now().Add(dead))
|
err = conn.SetReadDeadline(utils.NowSec().Add(dead))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("SetDeadline: ", err)
|
base.Error("SetDeadline: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// hdata := make([]byte, BufferSize)
|
pl := getPayload()
|
||||||
hdata := getByteFull()
|
n, err = conn.Read(pl.Data)
|
||||||
n, err := conn.Read(hdata)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("read hdata: ", err)
|
base.Error("read hdata: ", err)
|
||||||
return
|
return
|
||||||
@@ -50,7 +52,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
base.Error(err)
|
base.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch hdata[0] {
|
switch pl.Data[0] {
|
||||||
case 0x07: // KEEPALIVE
|
case 0x07: // KEEPALIVE
|
||||||
// do nothing
|
// do nothing
|
||||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||||
@@ -59,18 +61,23 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
return
|
return
|
||||||
case 0x03: // DPD-REQ
|
case 0x03: // DPD-REQ
|
||||||
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
||||||
if payloadOutDtls(cSess, dSess, sessdata.LTypeIPData, 0x04, nil) {
|
pl.PType = 0x04
|
||||||
|
if payloadOutDtls(cSess, dSess, pl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 0x04:
|
case 0x04:
|
||||||
// base.Debug("recv DPD-RESP", cSess.IpAddr)
|
// base.Debug("recv DPD-RESP", cSess.IpAddr)
|
||||||
case 0x00: // DATA
|
case 0x00: // DATA
|
||||||
if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[1:n]) {
|
// 去除数据头
|
||||||
|
// copy(pl.Data, pl.Data[1:n])
|
||||||
|
// 更新切片长度
|
||||||
|
// pl.Data = pl.Data[:n-1]
|
||||||
|
pl.Data = append(pl.Data[:0], pl.Data[1:n]...)
|
||||||
|
if payloadIn(cSess, pl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
putByte(hdata)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,36 +89,42 @@ func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnS
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// header []byte
|
pl *sessdata.Payload
|
||||||
payload *sessdata.Payload
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// dtls优先推送数据
|
// dtls优先推送数据
|
||||||
select {
|
select {
|
||||||
case payload = <-cSess.PayloadOutDtls:
|
case pl = <-cSess.PayloadOutDtls:
|
||||||
case <-dSess.CloseChan:
|
case <-dSess.CloseChan:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.LType != sessdata.LTypeIPData {
|
if pl.LType != sessdata.LTypeIPData {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// header = []byte{payload.PType}
|
// header = []byte{payload.PType}
|
||||||
header := getByteZero()
|
if pl.PType == 0x00 { // data
|
||||||
header = append(header, payload.PType)
|
// 获取数据长度
|
||||||
if payload.PType == 0x00 { // data
|
l := len(pl.Data)
|
||||||
header = append(header, payload.Data...)
|
// 先扩容 +1
|
||||||
|
pl.Data = pl.Data[:l+1]
|
||||||
|
// 数据后移
|
||||||
|
copy(pl.Data[1:], pl.Data)
|
||||||
|
// 添加头信息
|
||||||
|
pl.Data[0] = pl.PType
|
||||||
|
} else {
|
||||||
|
// 设置头类型
|
||||||
|
pl.Data = append(pl.Data[:0], pl.PType)
|
||||||
}
|
}
|
||||||
n, err := conn.Write(header)
|
n, err := conn.Write(pl.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("write err", err)
|
base.Error("write err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putByte(header)
|
putPayload(pl)
|
||||||
putPayload(payload)
|
|
||||||
|
|
||||||
// 限流设置
|
// 限流设置
|
||||||
err = cSess.RateLimit(n, false)
|
err = cSess.RateLimit(n, false)
|
||||||
|
@@ -2,6 +2,7 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
@@ -17,18 +18,32 @@ import (
|
|||||||
const bridgeName = "anylink0"
|
const bridgeName = "anylink0"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bridgeIp net.IP
|
// 网关mac地址
|
||||||
bridgeHw net.HardwareAddr
|
gatewayHw net.HardwareAddr
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkTap() {
|
type LinkDriver interface {
|
||||||
brFace, err := net.InterfaceByName(bridgeName)
|
io.ReadWriteCloser
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func _setGateway() {
|
||||||
|
dstAddr := arpdis.Lookup(sessdata.IpPool.Ipv4Gateway, false)
|
||||||
|
gatewayHw = dstAddr.HardwareAddr
|
||||||
|
// 设置为静态地址映射
|
||||||
|
dstAddr.Type = arpdis.TypeStatic
|
||||||
|
arpdis.Add(dstAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _checkTapIp(ifName string) {
|
||||||
|
iFace, err := net.InterfaceByName(ifName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatal("testTap err: ", err)
|
base.Fatal("testTap err: ", err)
|
||||||
}
|
}
|
||||||
bridgeHw = brFace.HardwareAddr
|
|
||||||
|
|
||||||
addrs, err := brFace.Addrs()
|
var ifIp net.IP
|
||||||
|
|
||||||
|
addrs, err := iFace.Addrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatal("testTap err: ", err)
|
base.Fatal("testTap err: ", err)
|
||||||
}
|
}
|
||||||
@@ -37,17 +52,19 @@ func checkTap() {
|
|||||||
if err != nil || ip.To4() == nil {
|
if err != nil || ip.To4() == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bridgeIp = ip
|
ifIp = ip
|
||||||
}
|
|
||||||
if bridgeIp == nil && bridgeHw == nil {
|
|
||||||
base.Fatal("bridgeIp is err")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sessdata.IpPool.Ipv4IPNet.Contains(bridgeIp) {
|
if !sessdata.IpPool.Ipv4IPNet.Contains(ifIp) {
|
||||||
base.Fatal("bridgeIp or Ip network err")
|
base.Fatal("tapIp or Ip network err")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkTap() {
|
||||||
|
_setGateway()
|
||||||
|
_checkTapIp(bridgeName)
|
||||||
|
}
|
||||||
|
|
||||||
// 创建tap网卡
|
// 创建tap网卡
|
||||||
func LinkTap(cSess *sessdata.ConnSession) error {
|
func LinkTap(cSess *sessdata.ConnSession) error {
|
||||||
cfg := water.Config{
|
cfg := water.Config{
|
||||||
@@ -60,88 +77,94 @@ func LinkTap(cSess *sessdata.ConnSession) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cSess.TunName = ifce.Name()
|
cSess.SetIfName(ifce.Name())
|
||||||
|
|
||||||
// arp on
|
|
||||||
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast on", ifce.Name(), cSess.Mtu)
|
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast on", ifce.Name(), cSess.Mtu)
|
||||||
cmdstr2 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
|
cmdstr2 := fmt.Sprintf("ip link set dev %s master %s", ifce.Name(), bridgeName)
|
||||||
cmdstr3 := fmt.Sprintf("ip link set dev %s master %s", ifce.Name(), bridgeName)
|
err = execCmd([]string{cmdstr1, cmdstr2})
|
||||||
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
|
|
||||||
err = execCmd(cmdStrs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
_ = ifce.Close()
|
_ = ifce.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
go tapRead(ifce, cSess)
|
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
|
||||||
go tapWrite(ifce, cSess)
|
execCmd([]string{cmdstr3})
|
||||||
|
|
||||||
|
go allTapRead(ifce, cSess)
|
||||||
|
go allTapWrite(ifce, cSess)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
// ========================通用代码===========================
|
||||||
|
|
||||||
|
func allTapWrite(ifce LinkDriver, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
base.Debug("LinkTap return", cSess.IpAddr)
|
base.Debug("LinkTap return", cSess.IpAddr)
|
||||||
cSess.Close()
|
cSess.Close()
|
||||||
_ = ifce.Close()
|
ifce.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
payload *sessdata.Payload
|
dstHw net.HardwareAddr
|
||||||
frame ethernet.Frame
|
pl *sessdata.Payload
|
||||||
|
frame = make(ethernet.Frame, BufferSize)
|
||||||
|
ipDst = net.IPv4(1, 2, 3, 4)
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
frame.Resize(BufferSize)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case payload = <-cSess.PayloadIn:
|
case pl = <-cSess.PayloadIn:
|
||||||
case <-cSess.CloseChan:
|
case <-cSess.CloseChan:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// var frame ethernet.Frame
|
// var frame ethernet.Frame
|
||||||
frame = getByteFull()
|
switch pl.LType {
|
||||||
switch payload.LType {
|
|
||||||
default:
|
default:
|
||||||
// log.Println(payload)
|
// log.Println(payload)
|
||||||
case sessdata.LTypeEthernet:
|
case sessdata.LTypeEthernet:
|
||||||
copy(frame, payload.Data)
|
copy(frame, pl.Data)
|
||||||
frame = frame[:len(payload.Data)]
|
frame = frame[:len(pl.Data)]
|
||||||
case sessdata.LTypeIPData: // 需要转换成 Ethernet 数据
|
|
||||||
data := payload.Data
|
|
||||||
|
|
||||||
ip_src := waterutil.IPv4Source(data)
|
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
|
||||||
if waterutil.IsIPv6(data) || !ip_src.Equal(cSess.IpAddr) {
|
// fmt.Println("wirteArp:", packet)
|
||||||
// 过滤掉IPv6的数据
|
case sessdata.LTypeIPData: // 需要转换成 Ethernet 数据
|
||||||
|
ipSrc := waterutil.IPv4Source(pl.Data)
|
||||||
|
if !ipSrc.Equal(cSess.IpAddr) {
|
||||||
// 非分配给客户端ip,直接丢弃
|
// 非分配给客户端ip,直接丢弃
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
if waterutil.IsIPv6(pl.Data) {
|
||||||
|
// 过滤掉IPv6的数据
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// packet := gopacket.NewPacket(pl.Data, layers.LayerTypeIPv4, gopacket.Default)
|
||||||
// fmt.Println("get:", packet)
|
// fmt.Println("get:", packet)
|
||||||
|
|
||||||
ip_dst := waterutil.IPv4Destination(data)
|
// 手动设置ipv4地址
|
||||||
// fmt.Println("get:", ip_src, ip_dst)
|
ipDst[12] = pl.Data[16]
|
||||||
|
ipDst[13] = pl.Data[17]
|
||||||
|
ipDst[14] = pl.Data[18]
|
||||||
|
ipDst[15] = pl.Data[19]
|
||||||
|
|
||||||
var dstHw net.HardwareAddr
|
dstHw = gatewayHw
|
||||||
if !sessdata.IpPool.Ipv4IPNet.Contains(ip_dst) || ip_dst.Equal(sessdata.IpPool.Ipv4Gateway) {
|
if sessdata.IpPool.Ipv4IPNet.Contains(ipDst) {
|
||||||
// 不是同一网段,使用网关mac地址
|
dstAddr := arpdis.Lookup(ipDst, true)
|
||||||
dstAddr := arpdis.Lookup(sessdata.IpPool.Ipv4Gateway, false)
|
|
||||||
dstHw = dstAddr.HardwareAddr
|
|
||||||
} else {
|
|
||||||
dstAddr := arpdis.Lookup(ip_dst, true)
|
|
||||||
// fmt.Println("dstAddr", dstAddr)
|
// fmt.Println("dstAddr", dstAddr)
|
||||||
if dstAddr != nil {
|
if dstAddr != nil {
|
||||||
dstHw = dstAddr.HardwareAddr
|
dstHw = dstAddr.HardwareAddr
|
||||||
} else {
|
}
|
||||||
dstHw = bridgeHw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// fmt.Println("Gateway", ipSrc, ipDst, dstHw)
|
||||||
// fmt.Println("Gateway", ip_dst, dstAddr.HardwareAddr)
|
frame.Prepare(dstHw, cSess.MacHw, ethernet.NotTagged, ethernet.IPv4, len(pl.Data))
|
||||||
|
copy(frame[12+2:], pl.Data)
|
||||||
frame.Prepare(dstHw, cSess.MacHw, ethernet.NotTagged, ethernet.IPv4, len(data))
|
|
||||||
copy(frame[12+2:], data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
|
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
|
||||||
@@ -152,28 +175,26 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putByte(frame)
|
putPayload(pl)
|
||||||
putPayload(payload)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
func allTapRead(ifce LinkDriver, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
base.Debug("tapRead return", cSess.IpAddr)
|
base.Debug("tapRead return", cSess.IpAddr)
|
||||||
_ = ifce.Close()
|
ifce.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
n int
|
n int
|
||||||
buf []byte
|
data []byte
|
||||||
frame ethernet.Frame
|
frame = make(ethernet.Frame, BufferSize)
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// var frame ethernet.Frame
|
frame.Resize(BufferSize)
|
||||||
// frame.Resize(BufferSize)
|
|
||||||
frame = getByteFull()
|
|
||||||
n, err = ifce.Read(frame)
|
n, err = ifce.Read(frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("tap Read err", n, err)
|
base.Error("tap Read err", n, err)
|
||||||
@@ -183,14 +204,12 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
|
|
||||||
switch frame.Ethertype() {
|
switch frame.Ethertype() {
|
||||||
default:
|
default:
|
||||||
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
|
|
||||||
// fmt.Println(packet)
|
|
||||||
continue
|
continue
|
||||||
case ethernet.IPv6:
|
case ethernet.IPv6:
|
||||||
continue
|
continue
|
||||||
case ethernet.IPv4:
|
case ethernet.IPv4:
|
||||||
// 发送IP数据
|
// 发送IP数据
|
||||||
data := frame.Payload()
|
data = frame.Payload()
|
||||||
|
|
||||||
ip_dst := waterutil.IPv4Destination(data)
|
ip_dst := waterutil.IPv4Destination(data)
|
||||||
if !ip_dst.Equal(cSess.IpAddr) {
|
if !ip_dst.Equal(cSess.IpAddr) {
|
||||||
@@ -202,13 +221,18 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
||||||
// fmt.Println("put:", packet)
|
// fmt.Println("put:", packet)
|
||||||
|
|
||||||
if payloadOut(cSess, sessdata.LTypeIPData, 0x00, data) {
|
pl := getPayload()
|
||||||
|
// 拷贝数据到pl
|
||||||
|
copy(pl.Data, data)
|
||||||
|
// 更新切片长度
|
||||||
|
pl.Data = pl.Data[:len(data)]
|
||||||
|
if payloadOut(cSess, pl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case ethernet.ARP:
|
case ethernet.ARP:
|
||||||
// 暂时仅实现了ARP协议
|
// 暂时仅实现了ARP协议
|
||||||
packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
|
packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.NoCopy)
|
||||||
layer := packet.Layer(layers.LayerTypeARP)
|
layer := packet.Layer(layers.LayerTypeARP)
|
||||||
arpReq := layer.(*layers.ARP)
|
arpReq := layer.(*layers.ARP)
|
||||||
|
|
||||||
@@ -217,13 +241,13 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmt.Println("arp", net.IP(arpReq.SourceProtAddress), sess.Ip)
|
// fmt.Println("arp", time.Now(), net.IP(arpReq.SourceProtAddress), cSess.IpAddr)
|
||||||
// fmt.Println(packet)
|
// fmt.Println(packet)
|
||||||
|
|
||||||
// 返回ARP数据
|
// 返回ARP数据
|
||||||
src := &arpdis.Addr{IP: cSess.IpAddr, HardwareAddr: cSess.MacHw}
|
src := &arpdis.Addr{IP: cSess.IpAddr, HardwareAddr: cSess.MacHw}
|
||||||
dst := &arpdis.Addr{IP: arpReq.SourceProtAddress, HardwareAddr: frame.Source()}
|
dst := &arpdis.Addr{IP: arpReq.SourceProtAddress, HardwareAddr: arpReq.SourceHwAddress}
|
||||||
buf, err = arpdis.NewARPReply(src, dst)
|
data, err = arpdis.NewARPReply(src, dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
return
|
return
|
||||||
@@ -231,21 +255,23 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
|
|
||||||
// 从接受的arp信息添加arp地址
|
// 从接受的arp信息添加arp地址
|
||||||
addr := &arpdis.Addr{
|
addr := &arpdis.Addr{
|
||||||
IP: make([]byte, len(arpReq.SourceProtAddress)),
|
IP: append([]byte{}, dst.IP...),
|
||||||
HardwareAddr: make([]byte, len(frame.Source())),
|
HardwareAddr: append([]byte{}, dst.HardwareAddr...),
|
||||||
}
|
}
|
||||||
// addr.IP = arpReq.SourceProtAddress
|
|
||||||
// addr.HardwareAddr = frame.Source()
|
|
||||||
copy(addr.IP, arpReq.SourceProtAddress)
|
|
||||||
copy(addr.HardwareAddr, frame.Source())
|
|
||||||
arpdis.Add(addr)
|
arpdis.Add(addr)
|
||||||
|
|
||||||
if payloadIn(cSess, sessdata.LTypeEthernet, 0x00, buf) {
|
pl := getPayload()
|
||||||
|
// 设置为二层数据类型
|
||||||
|
pl.LType = sessdata.LTypeEthernet
|
||||||
|
// 拷贝数据到pl
|
||||||
|
copy(pl.Data, data)
|
||||||
|
// 更新切片长度
|
||||||
|
pl.Data = pl.Data[:len(data)]
|
||||||
|
|
||||||
|
if payloadIn(cSess, pl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
putByte(frame)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -40,21 +40,21 @@ func LinkTun(cSess *sessdata.ConnSession) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// log.Printf("Interface Name: %s\n", ifce.Name())
|
// log.Printf("Interface Name: %s\n", ifce.Name())
|
||||||
cSess.SetTunName(ifce.Name())
|
cSess.SetIfName(ifce.Name())
|
||||||
// cSess.TunName = ifce.Name()
|
|
||||||
|
|
||||||
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu)
|
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu)
|
||||||
cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
|
cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
|
||||||
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
|
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
|
||||||
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
|
err = execCmd([]string{cmdstr1, cmdstr2})
|
||||||
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
|
|
||||||
err = execCmd(cmdStrs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
_ = ifce.Close()
|
_ = ifce.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
|
||||||
|
execCmd([]string{cmdstr3})
|
||||||
|
|
||||||
go tunRead(ifce, cSess)
|
go tunRead(ifce, cSess)
|
||||||
go tunWrite(ifce, cSess)
|
go tunWrite(ifce, cSess)
|
||||||
return nil
|
return nil
|
||||||
@@ -69,23 +69,23 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
payload *sessdata.Payload
|
pl *sessdata.Payload
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case payload = <-cSess.PayloadIn:
|
case pl = <-cSess.PayloadIn:
|
||||||
case <-cSess.CloseChan:
|
case <-cSess.CloseChan:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ifce.Write(payload.Data)
|
_, err = ifce.Write(pl.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("tun Write err", err)
|
base.Error("tun Write err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putPayload(payload)
|
putPayload(pl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +101,16 @@ func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
// data := make([]byte, BufferSize)
|
// data := make([]byte, BufferSize)
|
||||||
data := getByteFull()
|
pl := getPayload()
|
||||||
n, err = ifce.Read(data)
|
n, err = ifce.Read(pl.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error("tun Read err", n, err)
|
base.Error("tun Read err", n, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新数据长度
|
||||||
|
pl.Data = (pl.Data)[:n]
|
||||||
|
|
||||||
// data = data[:n]
|
// data = data[:n]
|
||||||
// ip_src := waterutil.IPv4Source(data)
|
// ip_src := waterutil.IPv4Source(data)
|
||||||
// ip_dst := waterutil.IPv4Destination(data)
|
// ip_dst := waterutil.IPv4Destination(data)
|
||||||
@@ -116,10 +119,8 @@ func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
||||||
// fmt.Println("read:", packet)
|
// fmt.Println("read:", packet)
|
||||||
|
|
||||||
if payloadOut(cSess, sessdata.LTypeIPData, 0x00, data[:n]) {
|
if payloadOut(cSess, pl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putByte(data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -96,6 +96,9 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
// 允许的路由
|
// 允许的路由
|
||||||
for _, v := range cSess.Group.RouteInclude {
|
for _, v := range cSess.Group.RouteInclude {
|
||||||
|
if v.Val == "all" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
w.Header().Add("X-CSTP-Split-Include", v.IpMask)
|
w.Header().Add("X-CSTP-Split-Include", v.IpMask)
|
||||||
}
|
}
|
||||||
// 不允许的路由
|
// 不允许的路由
|
||||||
@@ -147,7 +150,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||||||
base.Debug(buf.String())
|
base.Debug(buf.String())
|
||||||
|
|
||||||
hj := w.(http.Hijacker)
|
hj := w.(http.Hijacker)
|
||||||
conn, _, err := hj.Hijack()
|
conn, bufRW, err := hj.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
@@ -160,11 +163,14 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = LinkTun(cSess)
|
err = LinkTun(cSess)
|
||||||
case base.LinkModeTAP:
|
case base.LinkModeTAP:
|
||||||
err = LinkTap(cSess)
|
err = LinkTap(cSess)
|
||||||
|
case base.LinkModeMacvtap:
|
||||||
|
err = LinkMacvtap(cSess)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
conn.Close()
|
||||||
|
base.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go LinkCstp(conn, cSess)
|
go LinkCstp(conn, bufRW, cSess)
|
||||||
}
|
}
|
||||||
|
137
server/handler/link_vtap.go
Normal file
@@ -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})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,31 +1,30 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
"github.com/songgao/water/waterutil"
|
"github.com/songgao/water/waterutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
|
||||||
pl := getPayload()
|
if pl.LType == sessdata.LTypeIPData && pl.PType == 0x00 {
|
||||||
pl.LType = lType
|
|
||||||
pl.PType = pType
|
|
||||||
pl.Data = append(pl.Data, data...)
|
|
||||||
|
|
||||||
return payloadInData(cSess, pl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
|
|
||||||
// 进行Acl规则判断
|
// 进行Acl规则判断
|
||||||
check := checkLinkAcl(cSess.Group, payload)
|
check := checkLinkAcl(cSess.Group, pl)
|
||||||
if !check {
|
if !check {
|
||||||
// 校验不通过直接丢弃
|
// 校验不通过直接丢弃
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logAudit(cSess, pl)
|
||||||
|
}
|
||||||
|
|
||||||
closed := false
|
closed := false
|
||||||
select {
|
select {
|
||||||
case cSess.PayloadIn <- payload:
|
case cSess.PayloadIn <- pl:
|
||||||
case <-cSess.CloseChan:
|
case <-cSess.CloseChan:
|
||||||
closed = true
|
closed = true
|
||||||
}
|
}
|
||||||
@@ -33,21 +32,16 @@ func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool
|
|||||||
return closed
|
return closed
|
||||||
}
|
}
|
||||||
|
|
||||||
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
func payloadOut(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
|
||||||
dSess := cSess.GetDtlsSession()
|
dSess := cSess.GetDtlsSession()
|
||||||
if dSess == nil {
|
if dSess == nil {
|
||||||
return payloadOutCstp(cSess, lType, pType, data)
|
return payloadOutCstp(cSess, pl)
|
||||||
} else {
|
} else {
|
||||||
return payloadOutDtls(cSess, dSess, lType, pType, data)
|
return payloadOutDtls(cSess, dSess, pl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func payloadOutCstp(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
func payloadOutCstp(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
|
||||||
pl := getPayload()
|
|
||||||
pl.LType = lType
|
|
||||||
pl.PType = pType
|
|
||||||
pl.Data = append(pl.Data, data...)
|
|
||||||
|
|
||||||
closed := false
|
closed := false
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -59,12 +53,7 @@ func payloadOutCstp(cSess *sessdata.ConnSession, lType sessdata.LType, pType byt
|
|||||||
return closed
|
return closed
|
||||||
}
|
}
|
||||||
|
|
||||||
func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lType sessdata.LType, pType byte, data []byte) bool {
|
func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, pl *sessdata.Payload) bool {
|
||||||
pl := getPayload()
|
|
||||||
pl.LType = lType
|
|
||||||
pl.PType = pType
|
|
||||||
pl.Data = append(pl.Data, data...)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case cSess.PayloadOutDtls <- pl:
|
case cSess.PayloadOutDtls <- pl:
|
||||||
case <-dSess.CloseChan:
|
case <-dSess.CloseChan:
|
||||||
@@ -74,27 +63,29 @@ func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lT
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Acl规则校验
|
// Acl规则校验
|
||||||
func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
|
func checkLinkAcl(group *dbdata.Group, pl *sessdata.Payload) bool {
|
||||||
if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 {
|
if pl.LType == sessdata.LTypeIPData && pl.PType == 0x00 && len(group.LinkAcl) > 0 {
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ip_dst := waterutil.IPv4Destination(payload.Data)
|
ipDst := waterutil.IPv4Destination(pl.Data)
|
||||||
ip_port := waterutil.IPv4DestinationPort(payload.Data)
|
ipPort := waterutil.IPv4DestinationPort(pl.Data)
|
||||||
|
ipProto := waterutil.IPv4Protocol(pl.Data)
|
||||||
// fmt.Println("sent:", ip_dst, ip_port)
|
// fmt.Println("sent:", ip_dst, ip_port)
|
||||||
|
|
||||||
// 优先放行dns端口
|
// 优先放行dns端口
|
||||||
for _, v := range group.ClientDns {
|
for _, v := range group.ClientDns {
|
||||||
if v.Val == ip_dst.String() && ip_port == 53 {
|
if v.Val == ipDst.String() && ipPort == 53 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range group.LinkAcl {
|
for _, v := range group.LinkAcl {
|
||||||
// 循环判断ip和端口
|
// 循环判断ip和端口
|
||||||
if v.IpNet.Contains(ip_dst) {
|
if v.IpNet.Contains(ipDst) {
|
||||||
if v.Port == ip_port || v.Port == 0 {
|
// 放行允许ip的ping
|
||||||
|
if v.Port == ipPort || v.Port == 0 || ipProto == waterutil.ICMP {
|
||||||
if v.Action == dbdata.Allow {
|
if v.Action == dbdata.Allow {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@@ -106,3 +97,53 @@ func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 访问日志审计
|
||||||
|
func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) {
|
||||||
|
if base.Cfg.AuditInterval < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipProto := waterutil.IPv4Protocol(pl.Data)
|
||||||
|
// 只统计 tcp和udp 的访问
|
||||||
|
switch ipProto {
|
||||||
|
case waterutil.TCP:
|
||||||
|
case waterutil.UDP:
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipSrc := waterutil.IPv4Source(pl.Data)
|
||||||
|
ipDst := waterutil.IPv4Destination(pl.Data)
|
||||||
|
ipPort := waterutil.IPv4DestinationPort(pl.Data)
|
||||||
|
|
||||||
|
b := getByte34()
|
||||||
|
key := *b
|
||||||
|
copy(key[:16], ipSrc)
|
||||||
|
copy(key[16:32], ipDst)
|
||||||
|
binary.BigEndian.PutUint16(key[32:34], ipPort)
|
||||||
|
|
||||||
|
s := utils.BytesToString(key)
|
||||||
|
nu := utils.NowSec().Unix()
|
||||||
|
|
||||||
|
// 判断已经存在,并且没有过期
|
||||||
|
v, ok := cSess.IpAuditMap[s]
|
||||||
|
if ok && nu-v < int64(base.Cfg.AuditInterval) {
|
||||||
|
// 回收byte对象
|
||||||
|
putByte34(b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cSess.IpAuditMap[s] = nu
|
||||||
|
|
||||||
|
audit := dbdata.AccessAudit{
|
||||||
|
Username: cSess.Sess.Username,
|
||||||
|
Protocol: uint8(ipProto),
|
||||||
|
Src: ipSrc.String(),
|
||||||
|
Dst: ipDst.String(),
|
||||||
|
DstPort: ipPort,
|
||||||
|
CreatedAt: utils.NowSec(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = dbdata.Add(audit)
|
||||||
|
}
|
||||||
|
@@ -3,13 +3,26 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 不允许直接修改
|
||||||
|
// [6] => PType
|
||||||
|
var plHeader = []byte{
|
||||||
|
'S', 'T', 'F', 1,
|
||||||
|
0x00, 0x00, /* Length */
|
||||||
|
0x00, /* Type */
|
||||||
|
0x00, /* Unknown */
|
||||||
|
}
|
||||||
|
|
||||||
var plPool = sync.Pool{
|
var plPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
|
b := make([]byte, BufferSize)
|
||||||
pl := sessdata.Payload{
|
pl := sessdata.Payload{
|
||||||
Data: make([]byte, 0, BufferSize),
|
LType: sessdata.LTypeIPData,
|
||||||
|
PType: 0x00,
|
||||||
|
Data: b,
|
||||||
}
|
}
|
||||||
// fmt.Println("plPool-init", len(pl.Data), cap(pl.Data))
|
// fmt.Println("plPool-init", len(pl.Data), cap(pl.Data))
|
||||||
return &pl
|
return &pl
|
||||||
@@ -22,31 +35,55 @@ func getPayload() *sessdata.Payload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func putPayload(pl *sessdata.Payload) {
|
func putPayload(pl *sessdata.Payload) {
|
||||||
pl.LType = 0
|
// 错误数据丢弃
|
||||||
pl.PType = 0
|
if cap(pl.Data) != BufferSize {
|
||||||
pl.Data = pl.Data[:0]
|
base.Warn("payload cap is err", cap(pl.Data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pl.LType = sessdata.LTypeIPData
|
||||||
|
pl.PType = 0x00
|
||||||
|
pl.Data = pl.Data[:BufferSize]
|
||||||
plPool.Put(pl)
|
plPool.Put(pl)
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytePool = sync.Pool{
|
var bytePool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
b := make([]byte, 0, BufferSize)
|
b := make([]byte, BufferSize)
|
||||||
// fmt.Println("bytePool-init")
|
// fmt.Println("bytePool-init")
|
||||||
return b
|
return &b
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getByteZero() []byte {
|
func getByteZero() *[]byte {
|
||||||
b := bytePool.Get().([]byte)
|
b := bytePool.Get().(*[]byte)
|
||||||
|
*b = (*b)[:0]
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func getByteFull() []byte {
|
func getByteFull() *[]byte {
|
||||||
b := bytePool.Get().([]byte)
|
b := bytePool.Get().(*[]byte)
|
||||||
b = b[:BufferSize]
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
func putByte(b []byte) {
|
func putByte(b *[]byte) {
|
||||||
b = b[:0]
|
*b = (*b)[:BufferSize]
|
||||||
bytePool.Put(b)
|
bytePool.Put(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 长度 34 小对象
|
||||||
|
var byte34Pool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
b := make([]byte, 34)
|
||||||
|
return &b
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getByte34() *[]byte {
|
||||||
|
b := byte34Pool.Get().(*[]byte)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func putByte34(b *[]byte) {
|
||||||
|
*b = (*b)[:34]
|
||||||
|
byte34Pool.Put(b)
|
||||||
|
}
|
||||||
|
44
server/handler/pool_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@@ -10,11 +10,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/dtls/v2/pkg/crypto/selfsign"
|
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/pkg/proxyproto"
|
"github.com/bjdgyc/anylink/pkg/proxyproto"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/pion/dtls/v2/pkg/crypto/selfsign"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startTls() {
|
func startTls() {
|
||||||
@@ -47,8 +46,8 @@ func startTls() {
|
|||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
InsecureSkipVerify: true,
|
|
||||||
Certificates: certs,
|
Certificates: certs,
|
||||||
|
// InsecureSkipVerify: true,
|
||||||
}
|
}
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
@@ -11,10 +11,17 @@ func Start() {
|
|||||||
dbdata.Start()
|
dbdata.Start()
|
||||||
sessdata.Start()
|
sessdata.Start()
|
||||||
|
|
||||||
|
switch base.Cfg.LinkMode {
|
||||||
|
case base.LinkModeTUN:
|
||||||
checkTun()
|
checkTun()
|
||||||
if base.Cfg.LinkMode == base.LinkModeTAP {
|
case base.LinkModeTAP:
|
||||||
checkTap()
|
checkTap()
|
||||||
|
case base.LinkModeMacvtap:
|
||||||
|
checkMacvtap()
|
||||||
|
default:
|
||||||
|
base.Fatal("LinkMode is err")
|
||||||
}
|
}
|
||||||
|
|
||||||
go admin.StartAdmin()
|
go admin.StartAdmin()
|
||||||
go startTls()
|
go startTls()
|
||||||
go startDtls()
|
go startDtls()
|
||||||
@@ -22,4 +29,5 @@ func Start() {
|
|||||||
|
|
||||||
func Stop() {
|
func Stop() {
|
||||||
_ = dbdata.Stop()
|
_ = dbdata.Stop()
|
||||||
|
destroyVtap()
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/admin"
|
"github.com/bjdgyc/anylink/admin"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/handler"
|
"github.com/bjdgyc/anylink/handler"
|
||||||
)
|
)
|
||||||
|
@@ -4,6 +4,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -16,7 +18,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
table = make(map[string]*Addr)
|
table = make(map[string]*Addr, 128)
|
||||||
tableMu sync.RWMutex
|
tableMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,25 +42,25 @@ func Lookup(ip net.IP, onlyTable bool) *Addr {
|
|||||||
|
|
||||||
// Add adds a IP-MAC map to a runtime ARP table.
|
// Add adds a IP-MAC map to a runtime ARP table.
|
||||||
func tableLookup(ip net.IP) *Addr {
|
func tableLookup(ip net.IP) *Addr {
|
||||||
tableMu.Lock()
|
tableMu.RLock()
|
||||||
addr := table[ip.To4().String()]
|
addr := table[ip.To4().String()]
|
||||||
tableMu.Unlock()
|
tableMu.RUnlock()
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断老化过期时间
|
// 判断老化过期时间
|
||||||
tsub := time.Since(addr.disTime)
|
tSub := utils.NowSec().Sub(addr.disTime)
|
||||||
switch addr.Type {
|
switch addr.Type {
|
||||||
|
case TypeStatic:
|
||||||
case TypeNormal:
|
case TypeNormal:
|
||||||
if tsub > StaleTimeNormal {
|
if tSub > StaleTimeNormal {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case TypeUnreachable:
|
case TypeUnreachable:
|
||||||
if tsub > StaleTimeUnreachable {
|
if tSub > StaleTimeUnreachable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case TypeStatic:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return addr
|
return addr
|
||||||
@@ -70,7 +72,7 @@ func Add(addr *Addr) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if addr.disTime.IsZero() {
|
if addr.disTime.IsZero() {
|
||||||
addr.disTime = time.Now()
|
addr.disTime = utils.NowSec()
|
||||||
}
|
}
|
||||||
ip := addr.IP.To4().String()
|
ip := addr.IP.To4().String()
|
||||||
tableMu.Lock()
|
tableMu.Lock()
|
||||||
|
@@ -19,7 +19,8 @@ func doLookup(ip net.IP) *Addr {
|
|||||||
err := doPing(ip.String())
|
err := doPing(ip.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// log.Println(err)
|
// log.Println(err)
|
||||||
addr := &Addr{IP: ip, Type: TypeUnreachable}
|
addr := &Addr{IP: net.IPv4(1, 2, 3, 4), Type: TypeUnreachable}
|
||||||
|
copy(addr.IP, ip)
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +51,9 @@ func doArpShow(ip net.IP) *Addr {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Addr{IP: ip, HardwareAddr: mac}
|
addr := &Addr{IP: net.IPv4(1, 2, 3, 4), HardwareAddr: mac}
|
||||||
|
copy(addr.IP, ip)
|
||||||
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP address HW type Flags HW address Mask Device
|
// IP address HW type Flags HW address Mask Device
|
||||||
|
17
server/pkg/utils/ip.go
Normal file
@@ -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)
|
||||||
|
}
|
20
server/pkg/utils/unsafe.go
Normal 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)},
|
||||||
|
))
|
||||||
|
}
|
@@ -3,11 +3,30 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 每秒时间缓存
|
||||||
|
timeNowSec = &atomic.Value{}
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
timeNowSec.Store(time.Now())
|
||||||
|
go func() {
|
||||||
|
tick := time.NewTicker(time.Second * 1)
|
||||||
|
for c := range tick.C {
|
||||||
|
timeNowSec.Store(c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NowSec() time.Time {
|
||||||
|
t := timeNowSec.Load()
|
||||||
|
return t.(time.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InArrStr(arr []string, str string) bool {
|
func InArrStr(arr []string, str string) bool {
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
package sessdata
|
package sessdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -43,19 +43,8 @@ func initIpPool() {
|
|||||||
// max := min | uint32(math.Pow(2, float64(32-one))-1)
|
// max := min | uint32(math.Pow(2, float64(32-one))-1)
|
||||||
|
|
||||||
// ip地址池
|
// ip地址池
|
||||||
IpPool.IpLongMin = ip2long(net.ParseIP(base.Cfg.Ipv4Start))
|
IpPool.IpLongMin = utils.Ip2long(net.ParseIP(base.Cfg.Ipv4Start))
|
||||||
IpPool.IpLongMax = ip2long(net.ParseIP(base.Cfg.Ipv4End))
|
IpPool.IpLongMax = utils.Ip2long(net.ParseIP(base.Cfg.Ipv4End))
|
||||||
}
|
|
||||||
|
|
||||||
func long2ip(i uint32) net.IP {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcquireIp 获取动态ip
|
// AcquireIp 获取动态ip
|
||||||
@@ -67,10 +56,10 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
|
|
||||||
// 判断已经分配过
|
// 判断已经分配过
|
||||||
mi := &dbdata.IpMap{}
|
mi := &dbdata.IpMap{}
|
||||||
err := dbdata.One("MacAddr", macAddr, mi)
|
err := dbdata.One("mac_addr", macAddr, mi)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip := mi.IpAddr
|
ipStr := mi.IpAddr
|
||||||
ipStr := ip.String()
|
ip := net.ParseIP(ipStr)
|
||||||
// 跳过活跃连接
|
// 跳过活跃连接
|
||||||
_, ok := ipActive[ipStr]
|
_, ok := ipActive[ipStr]
|
||||||
// 检测原有ip是否在新的ip池内
|
// 检测原有ip是否在新的ip池内
|
||||||
@@ -78,7 +67,7 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
mi.Username = username
|
mi.Username = username
|
||||||
mi.LastLogin = tNow
|
mi.LastLogin = tNow
|
||||||
// 回写db数据
|
// 回写db数据
|
||||||
_ = dbdata.Save(mi)
|
_ = dbdata.Add(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -90,7 +79,7 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
farIp := &dbdata.IpMap{LastLogin: tNow}
|
farIp := &dbdata.IpMap{LastLogin: tNow}
|
||||||
// 全局遍历超过租期ip
|
// 全局遍历超过租期ip
|
||||||
for i := IpPool.IpLongMin; i <= IpPool.IpLongMax; i++ {
|
for i := IpPool.IpLongMin; i <= IpPool.IpLongMax; i++ {
|
||||||
ip := long2ip(i)
|
ip := utils.Long2ip(i)
|
||||||
ipStr := ip.String()
|
ipStr := ip.String()
|
||||||
|
|
||||||
// 跳过活跃连接
|
// 跳过活跃连接
|
||||||
@@ -99,12 +88,12 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v := &dbdata.IpMap{}
|
v := &dbdata.IpMap{}
|
||||||
err = dbdata.One("IpAddr", ip, v)
|
err = dbdata.One("ip_addr", ipStr, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if dbdata.CheckErrNotFound(err) {
|
if dbdata.CheckErrNotFound(err) {
|
||||||
// 该ip没有被使用
|
// 该ip没有被使用
|
||||||
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
||||||
_ = dbdata.Save(mi)
|
_ = dbdata.Add(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -120,9 +109,9 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
// 已经超过租期
|
// 已经超过租期
|
||||||
if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
|
if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
|
||||||
_ = dbdata.Del(v)
|
_ = dbdata.Del(v)
|
||||||
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
||||||
// 重写db数据
|
// 重写db数据
|
||||||
_ = dbdata.Save(mi)
|
_ = dbdata.Add(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -139,11 +128,11 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 使用最早登陆的mac ip
|
// 使用最早登陆的mac ip
|
||||||
ip := farIp.IpAddr
|
ipStr := farIp.IpAddr
|
||||||
ipStr := ip.String()
|
ip := net.ParseIP(ipStr)
|
||||||
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
||||||
// 回写db数据
|
// 回写db数据
|
||||||
_ = dbdata.Save(mi)
|
_ = dbdata.Add(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -155,9 +144,9 @@ func ReleaseIp(ip net.IP, macAddr string) {
|
|||||||
|
|
||||||
delete(ipActive, ip.String())
|
delete(ipActive, ip.String())
|
||||||
mi := &dbdata.IpMap{}
|
mi := &dbdata.IpMap{}
|
||||||
err := dbdata.One("IpAddr", ip, mi)
|
err := dbdata.One("ip_addr", ip.String(), mi)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mi.LastLogin = time.Now()
|
mi.LastLogin = time.Now()
|
||||||
_ = dbdata.Save(mi)
|
_ = dbdata.Add(mi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,8 @@ import (
|
|||||||
func preData(tmpDir string) {
|
func preData(tmpDir string) {
|
||||||
base.Test()
|
base.Test()
|
||||||
tmpDb := path.Join(tmpDir, "test.db")
|
tmpDb := path.Join(tmpDir, "test.db")
|
||||||
base.Cfg.DbFile = tmpDb
|
base.Cfg.DbType = "sqlite3"
|
||||||
|
base.Cfg.DbSource = tmpDb
|
||||||
base.Cfg.Ipv4CIDR = "192.168.3.0/24"
|
base.Cfg.Ipv4CIDR = "192.168.3.0/24"
|
||||||
base.Cfg.Ipv4Start = "192.168.3.1"
|
base.Cfg.Ipv4Start = "192.168.3.1"
|
||||||
base.Cfg.Ipv4End = "192.168.3.199"
|
base.Cfg.Ipv4End = "192.168.3.199"
|
||||||
@@ -27,7 +28,7 @@ func preData(tmpDir string) {
|
|||||||
Name: "group1",
|
Name: "group1",
|
||||||
Bandwidth: 1000,
|
Bandwidth: 1000,
|
||||||
}
|
}
|
||||||
_ = dbdata.Save(&group)
|
_ = dbdata.Add(&group)
|
||||||
initIpPool()
|
initIpPool()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -54,13 +54,13 @@ func OnlineSess() []Online {
|
|||||||
Group: v.Group,
|
Group: v.Group,
|
||||||
MacAddr: v.MacAddr,
|
MacAddr: v.MacAddr,
|
||||||
RemoteAddr: v.CSess.RemoteAddr,
|
RemoteAddr: v.CSess.RemoteAddr,
|
||||||
TunName: v.CSess.TunName,
|
TunName: v.CSess.IfName,
|
||||||
Mtu: v.CSess.Mtu,
|
Mtu: v.CSess.Mtu,
|
||||||
Client: v.CSess.Client,
|
Client: v.CSess.Client,
|
||||||
BandwidthUp: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthUpPeriod)) + "/s",
|
BandwidthUp: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthUpPeriod)) + "/s",
|
||||||
BandwidthDown: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthDownPeriod)) + "/s",
|
BandwidthDown: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthDownPeriod)) + "/s",
|
||||||
BandwidthUpAll: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthUpAll)),
|
BandwidthUpAll: utils.HumanByte(atomic.LoadUint64(&v.CSess.BandwidthUpAll)),
|
||||||
BandwidthDownAll: utils.HumanByte(atomic.LoadUint32(&v.CSess.BandwidthDownAll)),
|
BandwidthDownAll: utils.HumanByte(atomic.LoadUint64(&v.CSess.BandwidthDownAll)),
|
||||||
LastLogin: v.LastLogin,
|
LastLogin: v.LastLogin,
|
||||||
}
|
}
|
||||||
datas = append(datas, val)
|
datas = append(datas, val)
|
||||||
|
@@ -8,8 +8,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Payload struct {
|
type Payload struct {
|
||||||
PType byte // payload types
|
|
||||||
LType LType // LinkType
|
LType LType // LinkType
|
||||||
|
PType byte // payload types
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -32,7 +32,7 @@ type ConnSession struct {
|
|||||||
MacHw net.HardwareAddr // 客户端mac地址,从Session取出
|
MacHw net.HardwareAddr // 客户端mac地址,从Session取出
|
||||||
RemoteAddr string
|
RemoteAddr string
|
||||||
Mtu int
|
Mtu int
|
||||||
TunName string
|
IfName string
|
||||||
Client string // 客户端 mobile pc
|
Client string // 客户端 mobile pc
|
||||||
CstpDpd int
|
CstpDpd int
|
||||||
Group *dbdata.Group
|
Group *dbdata.Group
|
||||||
@@ -41,14 +41,14 @@ type ConnSession struct {
|
|||||||
BandwidthDown uint32 // 使用下行带宽 Byte
|
BandwidthDown uint32 // 使用下行带宽 Byte
|
||||||
BandwidthUpPeriod uint32 // 前一周期的总量
|
BandwidthUpPeriod uint32 // 前一周期的总量
|
||||||
BandwidthDownPeriod uint32
|
BandwidthDownPeriod uint32
|
||||||
BandwidthUpAll uint32 // 使用上行带宽总量
|
BandwidthUpAll uint64 // 使用上行带宽总量
|
||||||
BandwidthDownAll uint32 // 使用下行带宽总量
|
BandwidthDownAll uint64 // 使用下行带宽总量
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
CloseChan chan struct{}
|
CloseChan chan struct{}
|
||||||
PayloadIn chan *Payload
|
PayloadIn chan *Payload
|
||||||
// PayloadOut chan *Payload // 公共ip数据
|
|
||||||
PayloadOutCstp chan *Payload // Cstp的数据
|
PayloadOutCstp chan *Payload // Cstp的数据
|
||||||
PayloadOutDtls chan *Payload // Dtls的数据
|
PayloadOutDtls chan *Payload // Dtls的数据
|
||||||
|
IpAuditMap map[string]int64 // 审计的ip数据
|
||||||
|
|
||||||
// dSess *DtlsSession
|
// dSess *DtlsSession
|
||||||
dSess *atomic.Value
|
dSess *atomic.Value
|
||||||
@@ -111,9 +111,9 @@ func checkSession() {
|
|||||||
|
|
||||||
func GenToken() string {
|
func GenToken() string {
|
||||||
// 生成32位的 token
|
// 生成32位的 token
|
||||||
btoken := make([]byte, 32)
|
bToken := make([]byte, 32)
|
||||||
rand.Read(btoken)
|
rand.Read(bToken)
|
||||||
return fmt.Sprintf("%x", btoken)
|
return fmt.Sprintf("%x", bToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(token string) *Session {
|
func NewSession(token string) *Session {
|
||||||
@@ -183,12 +183,17 @@ func (s *Session) NewConn() *ConnSession {
|
|||||||
IpAddr: ip,
|
IpAddr: ip,
|
||||||
closeOnce: sync.Once{},
|
closeOnce: sync.Once{},
|
||||||
CloseChan: make(chan struct{}),
|
CloseChan: make(chan struct{}),
|
||||||
PayloadIn: make(chan *Payload),
|
PayloadIn: make(chan *Payload, 64),
|
||||||
PayloadOutCstp: make(chan *Payload),
|
PayloadOutCstp: make(chan *Payload, 64),
|
||||||
PayloadOutDtls: make(chan *Payload),
|
PayloadOutDtls: make(chan *Payload, 64),
|
||||||
dSess: &atomic.Value{},
|
dSess: &atomic.Value{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ip 审计
|
||||||
|
if base.Cfg.AuditInterval >= 0 {
|
||||||
|
cSess.IpAuditMap = make(map[string]int64, 512)
|
||||||
|
}
|
||||||
|
|
||||||
dSess := &DtlsSession{
|
dSess := &DtlsSession{
|
||||||
isActive: -1,
|
isActive: -1,
|
||||||
}
|
}
|
||||||
@@ -284,8 +289,8 @@ func (cs *ConnSession) ratePeriod() {
|
|||||||
atomic.SwapUint32(&cs.BandwidthUpPeriod, rtUp/BandwidthPeriodSec)
|
atomic.SwapUint32(&cs.BandwidthUpPeriod, rtUp/BandwidthPeriodSec)
|
||||||
atomic.SwapUint32(&cs.BandwidthDownPeriod, rtDown/BandwidthPeriodSec)
|
atomic.SwapUint32(&cs.BandwidthDownPeriod, rtDown/BandwidthPeriodSec)
|
||||||
// 累加所有流量
|
// 累加所有流量
|
||||||
atomic.AddUint32(&cs.BandwidthUpAll, rtUp)
|
atomic.AddUint64(&cs.BandwidthUpAll, uint64(rtUp))
|
||||||
atomic.AddUint32(&cs.BandwidthDownAll, rtDown)
|
atomic.AddUint64(&cs.BandwidthDownAll, uint64(rtDown))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,10 +309,10 @@ func (cs *ConnSession) SetMtu(mtu string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *ConnSession) SetTunName(name string) {
|
func (cs *ConnSession) SetIfName(name string) {
|
||||||
cs.Sess.mux.Lock()
|
cs.Sess.mux.Lock()
|
||||||
defer cs.Sess.mux.Unlock()
|
defer cs.Sess.mux.Unlock()
|
||||||
cs.TunName = name
|
cs.IfName = name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *ConnSession) RateLimit(byt int, isUp bool) error {
|
func (cs *ConnSession) RateLimit(byt int, isUp bool) error {
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=VPN Server Service
|
Description=AnyLink Server Service
|
||||||
After=network.target
|
Documentation=https://github.com/bjdgyc/anylink
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory= /usr/local/anylink-deploy
|
WorkingDirectory=/usr/local/anylink-deploy
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5s
|
RestartSec=5s
|
||||||
ExecStart=/usr/local/anylink-deploy/anylink --conf=conf/server.toml
|
ExecStart=/usr/local/anylink-deploy/anylink --conf=./conf/server.toml
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
> 1%
|
|
||||||
last 2 versions
|
|
||||||
not dead
|
|
14781
web/package-lock.json
generated
@@ -8,14 +8,13 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.20.0",
|
"axios": "^0.21.1",
|
||||||
"chokidar": "^3.5.2",
|
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"echarts": "^4.9.0",
|
"echarts": "^4.9.0",
|
||||||
"element-ui": "^2.4.5",
|
"element-ui": "^2.4.5",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-count-to": "^1.0.13",
|
"vue-count-to": "^1.0.13",
|
||||||
"vue-router": "^3.4.6"
|
"vue-router": "^3.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
@@ -26,5 +25,24 @@
|
|||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
"vue-cli-plugin-element": "~1.0.1",
|
"vue-cli-plugin-element": "~1.0.1",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.11"
|
||||||
}
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
<el-menu-item index="/admin/set/system">系统信息</el-menu-item>
|
<el-menu-item index="/admin/set/system">系统信息</el-menu-item>
|
||||||
<el-menu-item index="/admin/set/soft">软件配置</el-menu-item>
|
<el-menu-item index="/admin/set/soft">软件配置</el-menu-item>
|
||||||
<el-menu-item index="/admin/set/other">其他设置</el-menu-item>
|
<el-menu-item index="/admin/set/other">其他设置</el-menu-item>
|
||||||
|
<el-menu-item index="/admin/set/audit">审计日志</el-menu-item>
|
||||||
</el-submenu>
|
</el-submenu>
|
||||||
|
|
||||||
<el-submenu index="2">
|
<el-submenu index="2">
|
||||||
|
@@ -118,8 +118,8 @@
|
|||||||
|
|
||||||
<el-popconfirm
|
<el-popconfirm
|
||||||
style="margin-left: 10px"
|
style="margin-left: 10px"
|
||||||
@onConfirm="handleDel(scope.row)"
|
@confirm="handleDel(scope.row)"
|
||||||
title="确定要删除用户吗?">
|
title="确定要删除用户组吗?">
|
||||||
<el-button
|
<el-button
|
||||||
slot="reference"
|
slot="reference"
|
||||||
size="mini"
|
size="mini"
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
|
|
||||||
<el-form-item label="带宽限制" prop="bandwidth">
|
<el-form-item label="带宽限制" prop="bandwidth">
|
||||||
<el-input v-model.number="ruleForm.bandwidth">
|
<el-input v-model.number="ruleForm.bandwidth">
|
||||||
<template slot="append">BYTE</template>
|
<template slot="append">BYTE/S</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="本地网络" prop="allow_lan">
|
<el-form-item label="本地网络" prop="allow_lan">
|
||||||
@@ -181,18 +181,20 @@
|
|||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
||||||
@click.prevent="addDomain(ruleForm.client_dns)"></el-button>
|
@click.prevent="addDomain(ruleForm.client_dns)"></el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
|
||||||
@click.prevent="removeDomain(ruleForm.client_dns)"></el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row v-for="(item,index) in ruleForm.client_dns"
|
<el-row v-for="(item,index) in ruleForm.client_dns"
|
||||||
:key="index" style="margin-bottom: 5px" gutter="10">
|
:key="index" style="margin-bottom: 5px" :gutter="10">
|
||||||
<el-col :span="10">
|
<el-col :span="10">
|
||||||
<el-input v-model="item.val"></el-input>
|
<el-input v-model="item.val"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14">
|
<el-col :span="12">
|
||||||
<el-input v-model="item.note" placeholder="备注"></el-input>
|
<el-input v-model="item.note" placeholder="备注"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
||||||
|
@click.prevent="removeDomain(ruleForm.client_dns,index)"></el-button>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -202,18 +204,20 @@
|
|||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
||||||
@click.prevent="addDomain(ruleForm.route_include)"></el-button>
|
@click.prevent="addDomain(ruleForm.route_include)"></el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
|
||||||
@click.prevent="removeDomain(ruleForm.route_include)"></el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row v-for="(item,index) in ruleForm.route_include"
|
<el-row v-for="(item,index) in ruleForm.route_include"
|
||||||
:key="index" style="margin-bottom: 5px" gutter="10">
|
:key="index" style="margin-bottom: 5px" :gutter="10">
|
||||||
<el-col :span="10">
|
<el-col :span="10">
|
||||||
<el-input v-model="item.val"></el-input>
|
<el-input v-model="item.val"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14">
|
<el-col :span="12">
|
||||||
<el-input v-model="item.note" placeholder="备注"></el-input>
|
<el-input v-model="item.note" placeholder="备注"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
||||||
|
@click.prevent="removeDomain(ruleForm.route_include,index)"></el-button>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -223,18 +227,20 @@
|
|||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
||||||
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
|
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
|
||||||
@click.prevent="removeDomain(ruleForm.route_exclude)"></el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row v-for="(item,index) in ruleForm.route_exclude"
|
<el-row v-for="(item,index) in ruleForm.route_exclude"
|
||||||
:key="index" style="margin-bottom: 5px" gutter="10">
|
:key="index" style="margin-bottom: 5px" :gutter="10">
|
||||||
<el-col :span="10">
|
<el-col :span="10">
|
||||||
<el-input v-model="item.val"></el-input>
|
<el-input v-model="item.val"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14">
|
<el-col :span="12">
|
||||||
<el-input v-model="item.note" placeholder="备注"></el-input>
|
<el-input v-model="item.note" placeholder="备注"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
||||||
|
@click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -244,13 +250,11 @@
|
|||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
<el-button size="mini" type="success" icon="el-icon-plus" circle
|
||||||
@click.prevent="addDomain(ruleForm.link_acl)"></el-button>
|
@click.prevent="addDomain(ruleForm.link_acl)"></el-button>
|
||||||
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
|
||||||
@click.prevent="removeDomain(ruleForm.link_acl)"></el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row v-for="(item,index) in ruleForm.link_acl"
|
<el-row v-for="(item,index) in ruleForm.link_acl"
|
||||||
:key="index" style="margin-bottom: 5px" gutter="5">
|
:key="index" style="margin-bottom: 5px" :gutter="5">
|
||||||
<el-col :span="11">
|
<el-col :span="11">
|
||||||
<el-input placeholder="请输入CIDR地址" v-model="item.val">
|
<el-input placeholder="请输入CIDR地址" v-model="item.val">
|
||||||
<el-select v-model="item.action" slot="prepend">
|
<el-select v-model="item.action" slot="prepend">
|
||||||
@@ -262,9 +266,13 @@
|
|||||||
<el-col :span="3">
|
<el-col :span="3">
|
||||||
<el-input v-model.number="item.port" placeholder="端口"></el-input>
|
<el-input v-model.number="item.port" placeholder="端口"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="10">
|
<el-col :span="8">
|
||||||
<el-input v-model="item.note" placeholder="备注"></el-input>
|
<el-input v-model="item.note" placeholder="备注"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="2">
|
||||||
|
<el-button size="mini" type="danger" icon="el-icon-minus" circle
|
||||||
|
@click.prevent="removeDomain(ruleForm.link_acl,index)"></el-button>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -311,7 +319,7 @@ export default {
|
|||||||
status: 1,
|
status: 1,
|
||||||
allow_lan: true,
|
allow_lan: true,
|
||||||
client_dns: [{val: '114.114.114.114'}],
|
client_dns: [{val: '114.114.114.114'}],
|
||||||
route_include: [],
|
route_include: [{val: 'all', note: '默认全局代理'}],
|
||||||
route_exclude: [],
|
route_exclude: [],
|
||||||
link_acl: [],
|
link_acl: [],
|
||||||
},
|
},
|
||||||
@@ -389,13 +397,16 @@ export default {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
removeDomain(arr, item) {
|
removeDomain(arr, index) {
|
||||||
console.log(item)
|
console.log(index)
|
||||||
|
if (index >= 0 && index < arr.length) {
|
||||||
|
arr.splice(index, 1)
|
||||||
|
}
|
||||||
// let index = arr.indexOf(item);
|
// let index = arr.indexOf(item);
|
||||||
// if (index !== -1 && arr.length > 1) {
|
// if (index !== -1 && arr.length > 1) {
|
||||||
// arr.splice(index, 1)
|
// arr.splice(index, 1)
|
||||||
// }
|
// }
|
||||||
arr.pop()
|
// arr.pop()
|
||||||
},
|
},
|
||||||
addDomain(arr) {
|
addDomain(arr) {
|
||||||
arr.push({val: "", action: "allow", port: 0});
|
arr.push({val: "", action: "allow", port: 0});
|
||||||
|
145
web/src/pages/set/Audit.vue
Normal file
@@ -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>
|
@@ -76,13 +76,12 @@
|
|||||||
|
|
||||||
<el-popconfirm
|
<el-popconfirm
|
||||||
class="m-left-10"
|
class="m-left-10"
|
||||||
@onConfirm="handleDel(scope.row)"
|
@confirm="handleDel(scope.row)"
|
||||||
title="确定要删除IP映射吗?">
|
title="确定要删除IP映射吗?">
|
||||||
<el-button
|
<el-button
|
||||||
slot="reference"
|
slot="reference"
|
||||||
size="mini"
|
size="mini"
|
||||||
type="danger"
|
type="danger">删除
|
||||||
@click="handleDelete(scope.row)">删除
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-popconfirm>
|
</el-popconfirm>
|
||||||
|
|
||||||
|
@@ -120,7 +120,7 @@
|
|||||||
|
|
||||||
<el-popconfirm
|
<el-popconfirm
|
||||||
class="m-left-10"
|
class="m-left-10"
|
||||||
@onConfirm="handleDel(scope.row)"
|
@confirm="handleDel(scope.row)"
|
||||||
title="确定要删除用户吗?">
|
title="确定要删除用户吗?">
|
||||||
<el-button
|
<el-button
|
||||||
slot="reference"
|
slot="reference"
|
||||||
|
@@ -95,7 +95,7 @@
|
|||||||
|
|
||||||
<el-popconfirm
|
<el-popconfirm
|
||||||
class="m-left-10"
|
class="m-left-10"
|
||||||
@onConfirm="handleOffline(scope.row)"
|
@confirm="handleOffline(scope.row)"
|
||||||
title="确定要下线用户吗?">
|
title="确定要下线用户吗?">
|
||||||
<el-button
|
<el-button
|
||||||
slot="reference"
|
slot="reference"
|
||||||
|
@@ -17,6 +17,7 @@ const routes = [
|
|||||||
{path: 'set/system', component: () => import('@/pages/set/System')},
|
{path: 'set/system', component: () => import('@/pages/set/System')},
|
||||||
{path: 'set/soft', component: () => import('@/pages/set/Soft')},
|
{path: 'set/soft', component: () => import('@/pages/set/Soft')},
|
||||||
{path: 'set/other', component: () => import('@/pages/set/Other')},
|
{path: 'set/other', component: () => import('@/pages/set/Other')},
|
||||||
|
{path: 'set/audit', component: () => import('@/pages/set/Audit')},
|
||||||
|
|
||||||
{path: 'user/list', component: () => import('@/pages/user/List')},
|
{path: 'user/list', component: () => import('@/pages/user/List')},
|
||||||
{path: 'user/online', component: () => import('@/pages/user/Online')},
|
{path: 'user/online', component: () => import('@/pages/user/Online')},
|
||||||
|