mirror of https://github.com/bjdgyc/anylink.git
commit
b84d10d64f
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
name: 问题描述
|
||||
about: 问题描述
|
||||
title: "affected/package: "
|
||||
---
|
||||
|
||||
<!--
|
||||
请先填写下面的问题,再填写具体遇到的问题,感谢!
|
||||
-->
|
||||
|
||||
### 使用的anylink版本 ?
|
||||
|
||||
<pre>
|
||||
./anylink tool -v
|
||||
管理后台也可以查看
|
||||
</pre>
|
||||
|
||||
|
||||
### 使用操作系统的类型和版本?
|
||||
如: centos 7.9
|
||||
<pre>
|
||||
cat /etc/issue
|
||||
cat /etc/redhat-release
|
||||
|
||||
</pre>
|
||||
|
||||
### 使用linux 内核版本?
|
||||
<pre>
|
||||
uname -a
|
||||
|
||||
</pre>
|
||||
|
||||
### 具体遇到的问题,可上传截图
|
||||
|
||||
|
49
Dockerfile
49
Dockerfile
|
@ -1,49 +0,0 @@
|
|||
# web
|
||||
FROM node:lts-alpine as builder_node
|
||||
WORKDIR /web
|
||||
COPY ./web /web
|
||||
RUN yarn install \
|
||||
&& yarn run build \
|
||||
&& ls /web/ui
|
||||
|
||||
# server
|
||||
FROM golang:1.18-alpine as builder_golang
|
||||
#TODO 本地打包时使用镜像
|
||||
ENV GOPROXY=https://goproxy.io
|
||||
ENV GOOS=linux
|
||||
WORKDIR /anylink
|
||||
COPY . /anylink
|
||||
COPY --from=builder_node /web/ui /anylink/server/ui
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache git gcc musl-dev
|
||||
RUN cd /anylink/server;go mod tidy;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
|
||||
&& /anylink/server/anylink tool -v
|
||||
|
||||
# anylink
|
||||
FROM alpine
|
||||
LABEL maintainer="github.com/bjdgyc"
|
||||
|
||||
ENV IPV4_CIDR="192.168.10.0/24"
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder_golang /anylink/server/anylink /app/
|
||||
COPY docker_entrypoint.sh /app/
|
||||
|
||||
COPY ./server/bridge-init.sh /app/
|
||||
COPY ./server/conf /app/conf
|
||||
COPY ./LICENSE /app/LICENSE
|
||||
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache bash iptables \
|
||||
&& chmod +x /app/docker_entrypoint.sh \
|
||||
&& ls /app
|
||||
|
||||
EXPOSE 443 8800
|
||||
|
||||
#CMD ["/app/anylink"]
|
||||
ENTRYPOINT ["/app/docker_entrypoint.sh"]
|
||||
|
72
README.md
72
README.md
|
@ -24,7 +24,7 @@ AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannop
|
|||
|
||||
AnyLink 使用 TLS/DTLS 进行数据加密,因此需要 RSA 或 ECC 证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的 SSL 证书。
|
||||
|
||||
AnyLink 服务端仅在 CentOS 7、Ubuntu 18.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap 功能、ip 设置命令。
|
||||
AnyLink 服务端仅在 CentOS 7、CentOS 8、Ubuntu 18.04、Ubuntu 20.04 测试通过,如需要安装在其他系统,需要服务端支持 tun/tap 功能、ip 设置命令。
|
||||
|
||||
## Screenshot
|
||||
|
||||
|
@ -89,7 +89,7 @@ sudo ./anylink
|
|||
- [x] 基于 tun 设备的 nat 访问模式
|
||||
- [x] 基于 tap 设备的桥接访问模式
|
||||
- [x] 基于 macvtap 设备的桥接访问模式
|
||||
- [x] 支持 [proxy protocol v1](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
|
||||
- [x] 支持 [proxy protocol v1&v2](http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) 协议
|
||||
- [x] 用户组支持
|
||||
- [x] 多用户支持
|
||||
- [x] 用户策略支持
|
||||
|
@ -158,28 +158,33 @@ cat /proc/sys/net/ipv4/ip_forward
|
|||
systemctl stop firewalld.service
|
||||
systemctl disable firewalld.service
|
||||
|
||||
# 新版本支持自动设置nat转发,如有其他需求可以参考下面的命令配置
|
||||
|
||||
# 请根据服务器内网网卡替换 eth0
|
||||
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
|
||||
# iptables -t nat -A POSTROUTING -s 192.168.90.0/24 -o eth0 -j MASQUERADE
|
||||
# 如果执行第一个命令不生效,可以继续执行下面的命令
|
||||
# iptables -A FORWARD -i eth0 -s 192.168.10.0/24 -j ACCEPT
|
||||
# iptables -A FORWARD -i eth0 -s 192.168.90.0/24 -j ACCEPT
|
||||
# 查看设置是否生效
|
||||
iptables -nL -t nat
|
||||
# iptables -nL -t nat
|
||||
```
|
||||
|
||||
2.2 使用全局路由转发(二选一)
|
||||
|
||||
```shell
|
||||
# 假设anylink所在服务器的内网ip: 10.1.0.10
|
||||
# 假设anylink所在服务器的内网ip: 10.1.2.10
|
||||
|
||||
# 首先关闭nat转发功能
|
||||
iptables_nat = false
|
||||
|
||||
# 传统网络架构,在华三交换机添加以下静态路由规则
|
||||
ip route-static 192.168.10.0 255.255.255.0 10.1.0.10
|
||||
ip route-static 192.168.90.0 255.255.255.0 10.1.2.10
|
||||
# 其他品牌的交换机命令,请参考以下地址
|
||||
https://cloud.tencent.com/document/product/216/62007
|
||||
|
||||
# 公有云环境下,需设置vpc下的路由表,添加以下路由策略
|
||||
目的端: 192.168.10.0/24
|
||||
目的端: 192.168.90.0/24
|
||||
下一跳类型: 云服务器
|
||||
下一跳: 10.1.0.10
|
||||
下一跳: 10.1.2.10
|
||||
|
||||
```
|
||||
|
||||
|
@ -194,49 +199,25 @@ https://cloud.tencent.com/document/product/216/62007
|
|||
> 以下参数可以通过执行 `ip a` 查看
|
||||
|
||||
```
|
||||
# 首先关闭nat转发功能
|
||||
iptables_nat = false
|
||||
|
||||
#内网主网卡名称
|
||||
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"
|
||||
ipv4_cidr = "10.1.2.0/24"
|
||||
ipv4_gateway = "10.1.2.1"
|
||||
ipv4_start = "10.1.2.100"
|
||||
ipv4_end = "10.1.2.200"
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>tap设置</summary>
|
||||
|
||||
### ~~tap 设置~~
|
||||
|
||||
1. 创建桥接网卡
|
||||
|
||||
```
|
||||
注意 server.toml 的ip参数,需要与 bridge-init.sh 的配置参数一致
|
||||
```
|
||||
|
||||
2. 修改 bridge-init.sh 内的参数
|
||||
|
||||
> 以下参数可以通过执行 `ip a` 查看
|
||||
|
||||
```
|
||||
eth="eth0"
|
||||
eth_ip="192.168.10.4/24"
|
||||
eth_broadcast="192.168.10.255"
|
||||
eth_gateway="192.168.10.1"
|
||||
```
|
||||
|
||||
3. 执行 bridge-init.sh 文件
|
||||
|
||||
```
|
||||
sh bridge-init.sh
|
||||
```
|
||||
</details>
|
||||
|
||||
## Systemd
|
||||
|
||||
1. 添加 anylink 程序
|
||||
|
||||
- anylink 程序目录放入 `/usr/local/anylink-deploy`
|
||||
- 添加执行权限 `chmod +x /usr/local/anylink-deploy/anylink`
|
||||
|
||||
2. systemd/anylink.service 脚本放入:
|
||||
|
||||
|
@ -254,6 +235,8 @@ sh bridge-init.sh
|
|||
1. 获取镜像
|
||||
|
||||
```bash
|
||||
# 具体tag可以从docker hub获取
|
||||
# https://hub.docker.com/r/bjdgyc/anylink/tags
|
||||
docker pull bjdgyc/anylink:latest
|
||||
```
|
||||
|
||||
|
@ -280,9 +263,7 @@ sh bridge-init.sh
|
|||
5. 启动容器
|
||||
|
||||
```bash
|
||||
# -e IPV4_CIDR=192.168.10.0/24 这个参数要与配置文件内的网段一致
|
||||
docker run -itd --name anylink --privileged \
|
||||
-e IPV4_CIDR=192.168.10.0/24
|
||||
-p 443:443 -p 8800:8800 \
|
||||
--restart=always \
|
||||
bjdgyc/anylink
|
||||
|
@ -292,7 +273,6 @@ sh bridge-init.sh
|
|||
```bash
|
||||
# 参数可以参考 -h 命令
|
||||
docker run -itd --name anylink --privileged \
|
||||
-e IPV4_CIDR=192.168.10.0/24 \
|
||||
-p 443:443 -p 8800:8800 \
|
||||
--restart=always \
|
||||
bjdgyc/anylink \
|
||||
|
@ -305,7 +285,7 @@ sh bridge-init.sh
|
|||
#获取仓库源码
|
||||
git clone https://github.com/bjdgyc/anylink.git
|
||||
# 构建镜像
|
||||
docker build -t anylink .
|
||||
docker build -t anylink -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
|
||||
|
@ -347,7 +327,7 @@ sh bridge-init.sh
|
|||
|
||||
## License
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
|
||||
本项目采用 AGPL-3.0 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
|
||||
|
||||
## Thank
|
||||
|
||||
|
|
2
build.sh
2
build.sh
|
@ -43,7 +43,7 @@ rm -rf $deploy ${deploy}.tar.gz
|
|||
mkdir $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
|
||||
|
|
|
@ -5,7 +5,8 @@ echo $ver
|
|||
|
||||
#docker login -u bjdgyc
|
||||
|
||||
docker build -t bjdgyc/anylink .
|
||||
#docker build -t bjdgyc/anylink .
|
||||
docker build -t bjdgyc/anylink -f docker/Dockerfile .
|
||||
|
||||
docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver
|
||||
|
||||
|
|
|
@ -3,29 +3,36 @@
|
|||
> 如果您觉得 AnyLink 对你有帮助,欢迎给我们打赏,也是帮助 AnyLink 更好的发展。
|
||||
|
||||
<p>
|
||||
<img src="screenshot/wxpay2.png" width="500" />
|
||||
<img src="screenshot/wxpay2.png" width="435" alt="anylink捐赠二维码" />
|
||||
</p>
|
||||
|
||||
## Donator
|
||||
|
||||
> 感谢以下同学的打赏,AnyLink 有你更美好!
|
||||
>
|
||||
> 需要展示主页的同学,可以在QQ群(567510628) 直接联系我添加。
|
||||
|
||||
| 昵称 | 主页 |
|
||||
| -------------- | ---------------------------- |
|
||||
| 代码 oo8 | |
|
||||
| 甘磊 | https://github.com/ganlei333 |
|
||||
| Oo@ | https://github.com/chooop |
|
||||
| 虚极静笃 | |
|
||||
| 请喝可乐 | |
|
||||
| 加油加油 | |
|
||||
| 李建 | |
|
||||
| lanbin | |
|
||||
| 乐在东途 | |
|
||||
| 孤鸿 | |
|
||||
| 刘国华 | |
|
||||
| 改名好无聊 | |
|
||||
| 昵称 | 主页 |
|
||||
|---------| ---------------------------- |
|
||||
| 代码 oo8 | |
|
||||
| 甘磊 | https://github.com/ganlei333 |
|
||||
| Oo@ | https://github.com/chooop |
|
||||
| 虚极静笃 | |
|
||||
| 请喝可乐 | |
|
||||
| 加油加油 | |
|
||||
| 李建 | |
|
||||
| lanbin | |
|
||||
| 乐在东途 | |
|
||||
| 孤鸿 | |
|
||||
| 刘国华 | |
|
||||
| 改名好无聊 | |
|
||||
| 全能互联网专家 | |
|
||||
|
||||
| JCM | |
|
||||
| Eh... | |
|
||||
| 沉 | |
|
||||
| 刘国华 | |
|
||||
| 忧郁的豚骨拉面 | |
|
||||
| 张小旋当爹地 | |
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,49 @@
|
|||
FROM ubuntu:18.04
|
||||
WORKDIR /
|
||||
COPY docker_entrypoint.sh docker_entrypoint.sh
|
||||
RUN mkdir /anylink && apt update && apt install -y wget iptables tar iproute2
|
||||
ENTRYPOINT ["/docker_entrypoint.sh"]
|
||||
#CMD ["/anylink/anylink","-conf=/anylink/conf/server.toml"]
|
||||
# web
|
||||
FROM node:16.17.1-alpine3.15 as builder_node
|
||||
WORKDIR /web
|
||||
COPY ./web /web
|
||||
RUN yarn install \
|
||||
&& yarn run build \
|
||||
&& ls /web/ui
|
||||
|
||||
# server
|
||||
FROM golang:1.18-alpine as builder_golang
|
||||
#TODO 本地打包时使用镜像
|
||||
ENV GOPROXY=https://goproxy.io
|
||||
ENV GOOS=linux
|
||||
WORKDIR /anylink
|
||||
COPY . /anylink
|
||||
COPY --from=builder_node /web/ui /anylink/server/ui
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache git gcc musl-dev
|
||||
RUN cd /anylink/server;go mod tidy;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
|
||||
&& /anylink/server/anylink tool -v
|
||||
|
||||
# anylink
|
||||
FROM alpine
|
||||
LABEL maintainer="github.com/bjdgyc"
|
||||
|
||||
ENV IPV4_CIDR="192.168.10.0/24"
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder_golang /anylink/server/anylink /app/
|
||||
COPY docker/docker_entrypoint.sh /app/
|
||||
|
||||
#COPY ./server/bridge-init.sh /app/
|
||||
COPY ./server/conf /app/conf
|
||||
COPY ./LICENSE /app/LICENSE
|
||||
|
||||
|
||||
#TODO 本地打包时使用镜像
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache bash iptables \
|
||||
&& chmod +x /app/docker_entrypoint.sh \
|
||||
&& ls /app
|
||||
|
||||
EXPOSE 443 8800
|
||||
|
||||
#CMD ["/app/anylink"]
|
||||
ENTRYPOINT ["/app/docker_entrypoint.sh"]
|
||||
|
||||
|
|
|
@ -1,41 +1,23 @@
|
|||
#!/bin/sh
|
||||
USER="admin"
|
||||
MM=$(pwgen -1s)
|
||||
CREATE_USER=1
|
||||
CONFIG_FILE='/app/conf/server.toml'
|
||||
var1=$1
|
||||
|
||||
if [ $CREATE_USER -eq 1 ]; then
|
||||
if [ ! -e $CREATE_USER ]; then
|
||||
MM=$(pwgen -1s)
|
||||
touch $CREATE_USER
|
||||
bash /app/generate-certs.sh
|
||||
cd /app/conf/ && cp *.crt /usr/local/share/ca-certificates/
|
||||
update-ca-certificates --fresh
|
||||
userpass=$(/app/anylink -passwd "${MM}"| cut -d : -f2)
|
||||
echo "${userpass}"
|
||||
jwttoken=$(/app/anylink -secret | cut -d : -f2)
|
||||
echo "-- First container startup --user:${USER} pwd:${MM}"
|
||||
sed -i "s/admin/${USER}/g" /app/server-example.toml
|
||||
sed -i "s/123456/${MM}/g" /app/server-example.toml
|
||||
sed -i "s#usertoken#${userpass}#g" /app/server-example.toml
|
||||
sed -i "s/jwttoken/${jwttoken}/g" /app/server-example.toml
|
||||
else
|
||||
echo "-- Not first container startup --"
|
||||
fi
|
||||
#set -x
|
||||
|
||||
else
|
||||
echo "user switch not create"
|
||||
case $var1 in
|
||||
"bash" | "sh")
|
||||
echo $var1
|
||||
exec "$@"
|
||||
;;
|
||||
|
||||
fi
|
||||
"tool")
|
||||
/app/anylink "$@"
|
||||
;;
|
||||
|
||||
if [ ! -f $CONFIG_FILE ]; then
|
||||
echo "#####Generating configuration file#####"
|
||||
cp /app/server-example.toml /app/conf/server.toml
|
||||
else
|
||||
echo "#####Configuration file already exists#####"
|
||||
fi
|
||||
*)
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
#iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE
|
||||
#iptables -nL -t nat
|
||||
|
||||
rtaddr=$(grep "cidr" /app/conf/server.toml |awk -F \" '{print $2}')
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -t nat -A POSTROUTING -s "${rtaddr}" -o eth0+ -j MASQUERADE
|
||||
/app/anylink -conf="/app/conf/server.toml"
|
||||
exec /app/anylink "$@"
|
||||
;;
|
||||
esac
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
#! /bin/bash
|
||||
version=(`wget -qO- -t1 -T2 "https://api.github.com/repos/bjdgyc/anylink/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g'`)
|
||||
count=(`ls anylink | wc -w `)
|
||||
wget https://github.com/bjdgyc/anylink/releases/download/${version}/anylink-deploy.tar.gz
|
||||
tar xf anylink-deploy.tar.gz
|
||||
rm -rf anylink-deploy.tar.gz
|
||||
if [ ${count} -eq 0 ]; then
|
||||
echo "init anylink"
|
||||
mv anylink-deploy/* anylink/
|
||||
else
|
||||
if [ ! -d "/anylink/log" ]; then
|
||||
mv anylink-deploy/log anylink/
|
||||
fi
|
||||
if [ ! -d "/anylink/conf" ]; then
|
||||
mv anylink-deploy/conf anylink/
|
||||
fi
|
||||
echo "update anylink"
|
||||
rm -rf anylink/ui anylink/anylink anylink/files
|
||||
mv anylink-deploy/ui anylink/
|
||||
mv anylink-deploy/anylink anylink/
|
||||
mv anylink-deploy/files anylink/
|
||||
fi
|
||||
rm -rf anylink-deploy
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
if [[ ${mode} == pro ]];then
|
||||
iptables -t nat -A POSTROUTING -s ${iproute} -o eth0 -j MASQUERADE
|
||||
iptables -L -n -t nat
|
||||
/anylink/anylink -conf=/anylink/conf/server.toml
|
||||
elif [[ ${mode} == password ]];then
|
||||
if [ -z ${password} ];then
|
||||
echo "invalid password"
|
||||
else
|
||||
/anylink/anylink -passwd ${password}
|
||||
fi
|
||||
elif [[ ${mode} -eq jwt ]];then
|
||||
/anylink/anylink -secret
|
||||
fi
|
|
@ -1,23 +0,0 @@
|
|||
#!/bin/sh
|
||||
var1=$1
|
||||
|
||||
#set -x
|
||||
|
||||
case $var1 in
|
||||
"bash" | "sh")
|
||||
echo $var1
|
||||
exec "$@"
|
||||
;;
|
||||
|
||||
"tool")
|
||||
/app/anylink "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE
|
||||
iptables -nL -t nat
|
||||
|
||||
exec /app/anylink "$@"
|
||||
;;
|
||||
esac
|
|
@ -2,7 +2,7 @@ package admin
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
@ -79,7 +79,7 @@ func GroupDetail(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func GroupSet(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
|
|
|
@ -2,7 +2,7 @@ package admin
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
@ -59,7 +59,7 @@ func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
|
|||
func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
|
|
|
@ -3,10 +3,11 @@ package admin
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
)
|
||||
|
||||
|
@ -25,7 +26,7 @@ func setOtherGet(data interface{}, w http.ResponseWriter) {
|
|||
}
|
||||
|
||||
func setOtherEdit(data interface{}, w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
|
@ -82,11 +83,12 @@ func SetOtherAuditLog(w http.ResponseWriter, r *http.Request) {
|
|||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
}
|
||||
data.AuditInterval = base.Cfg.AuditInterval
|
||||
RespSucess(w, data)
|
||||
}
|
||||
|
||||
func SetOtherAuditLogEdit(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
|
|
|
@ -2,7 +2,7 @@ package admin
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
@ -57,7 +57,7 @@ func PolicyDetail(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func PolicySet(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
|
|
|
@ -51,3 +51,29 @@ func SetAuditExport(w http.ResponseWriter, r *http.Request) {
|
|||
gocsv.Marshal(datas, w)
|
||||
|
||||
}
|
||||
|
||||
func UserActLogList(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
pageS := r.FormValue("page")
|
||||
page, _ := strconv.Atoi(pageS)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
var datas []dbdata.UserActLog
|
||||
session := dbdata.UserActLogIns.GetSession(r.Form)
|
||||
count, err := dbdata.FindAndCount(session, &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,
|
||||
"statusOps": dbdata.UserActLogIns.GetStatusOpsWithTag(),
|
||||
"osOps": dbdata.UserActLogIns.OsOps,
|
||||
"clientOps": dbdata.UserActLogIns.ClientOps,
|
||||
}
|
||||
|
||||
RespSucess(w, data)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
"github.com/bjdgyc/anylink/pkg/utils"
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func UserUpload(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseMultipartForm(8 << 20)
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil || !strings.Contains(header.Filename, ".xlsx") || !strings.Contains(header.Filename, ".xls") {
|
||||
RespError(w, RespInternalErr, "文件解析失败:仅支持xlsx或xls文件")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
newFile, err := os.Create(base.Cfg.FilesPath + header.Filename)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, "创建文件失败:", err)
|
||||
return
|
||||
}
|
||||
defer newFile.Close()
|
||||
io.Copy(newFile, file)
|
||||
if err = UploadUser(newFile.Name()); err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
os.Remove(base.Cfg.FilesPath + header.Filename)
|
||||
return
|
||||
}
|
||||
os.Remove(base.Cfg.FilesPath + header.Filename)
|
||||
RespSucess(w, "批量添加成功")
|
||||
}
|
||||
func UploadUser(file string) error {
|
||||
f, err := excelize.OpenFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
rows, err := f.GetRows("Sheet1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rows[0][0] != "id" || rows[0][1] != "username" || rows[0][2] != "nickname" || rows[0][3] != "email" || rows[0][4] != "pin_code" || rows[0][5] != "limittime" || rows[0][6] != "otp_secret" || rows[0][7] != "disable_otp" || rows[0][8] != "groups" || rows[0][9] != "status" || rows[0][10] != "send_email" {
|
||||
return fmt.Errorf("批量添加失败,表格格式不正确")
|
||||
}
|
||||
var k []interface{}
|
||||
for _, v := range dbdata.GetGroupNames() {
|
||||
k = append(k, v)
|
||||
}
|
||||
for index, row := range rows {
|
||||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
id, _ := strconv.Atoi(row[0])
|
||||
if len(row[4]) < 6 {
|
||||
row[4] = utils.RandomRunes(8)
|
||||
}
|
||||
limittime, _ := time.ParseInLocation("2006-01-02 15:04:05", row[5], time.Local)
|
||||
disableOtp, _ := strconv.ParseBool(row[7])
|
||||
var group []string
|
||||
if row[8] == "" {
|
||||
return fmt.Errorf("第%d行数据错误,用户组不允许为空", index)
|
||||
}
|
||||
for _, v := range strings.Split(row[8], ",") {
|
||||
if s := mapset.NewSetFromSlice(k); s.Contains(v) {
|
||||
group = append(group, v)
|
||||
} else {
|
||||
return fmt.Errorf("用户组【%s】不存在,请检查第%d行数据", v, index)
|
||||
}
|
||||
}
|
||||
status := cast.ToInt8(row[9])
|
||||
sendmail, _ := strconv.ParseBool(row[10])
|
||||
// createdAt, _ := time.ParseInLocation("2006-01-02 15:04:05", row[11], time.Local)
|
||||
// updatedAt, _ := time.ParseInLocation("2006-01-02 15:04:05", row[12], time.Local)
|
||||
user := &dbdata.User{
|
||||
Id: id,
|
||||
Username: row[1],
|
||||
Nickname: row[2],
|
||||
Email: row[3],
|
||||
PinCode: row[4],
|
||||
LimitTime: &limittime,
|
||||
OtpSecret: row[6],
|
||||
DisableOtp: disableOtp,
|
||||
Groups: group,
|
||||
Status: status,
|
||||
SendEmail: sendmail,
|
||||
// CreatedAt: createdAt,
|
||||
// UpdatedAt: updatedAt,
|
||||
}
|
||||
if err := dbdata.AddBatch(user); err != nil {
|
||||
return fmt.Errorf("请检查第%d行数据是否导入有重复用户", index)
|
||||
}
|
||||
if user.SendEmail {
|
||||
if err := userAccountMail(user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
@ -80,7 +80,7 @@ func UserDetail(w http.ResponseWriter, r *http.Request) {
|
|||
func UserSet(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
|
@ -107,7 +107,8 @@ func UserSet(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
//修改用户资料后执行过期用户检测
|
||||
sessdata.CloseUserLimittimeSession()
|
||||
RespSucess(w, nil)
|
||||
}
|
||||
|
||||
|
@ -132,33 +133,44 @@ func UserDel(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func UserOtpQr(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
b64 := r.FormValue("b64")
|
||||
b64S := r.FormValue("b64")
|
||||
idS := r.FormValue("id")
|
||||
id, _ := strconv.Atoi(idS)
|
||||
var user dbdata.User
|
||||
err := dbdata.One("Id", id, &user)
|
||||
|
||||
var b64 bool
|
||||
if b64S == "1" {
|
||||
b64 = true
|
||||
}
|
||||
data, err := userOtpQr(id, b64)
|
||||
if err != nil {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
base.Error(err)
|
||||
}
|
||||
io.WriteString(w, data)
|
||||
}
|
||||
|
||||
func userOtpQr(uid int, b64 bool) (string, error) {
|
||||
var user dbdata.User
|
||||
err := dbdata.One("Id", uid, &user)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
issuer := url.QueryEscape(base.Cfg.Issuer)
|
||||
qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret)
|
||||
qr, _ := qrcode.New(qrstr, qrcode.High)
|
||||
|
||||
if b64 == "1" {
|
||||
data, _ := qr.PNG(300)
|
||||
s := base64.StdEncoding.EncodeToString(data)
|
||||
_, err = fmt.Fprint(w, s)
|
||||
if b64 {
|
||||
data, err := qr.PNG(300)
|
||||
if err != nil {
|
||||
base.Error(err)
|
||||
return "", err
|
||||
}
|
||||
return
|
||||
}
|
||||
err = qr.Write(300, w)
|
||||
if err != nil {
|
||||
base.Error(err)
|
||||
s := base64.StdEncoding.EncodeToString(data)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err = qr.Write(300, buf)
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
// 在线用户
|
||||
|
@ -177,7 +189,7 @@ func UserOnline(w http.ResponseWriter, r *http.Request) {
|
|||
func UserOffline(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
token := r.FormValue("token")
|
||||
sessdata.CloseSess(token)
|
||||
sessdata.CloseSess(token, dbdata.UserLogoutAdmin)
|
||||
RespSucess(w, nil)
|
||||
}
|
||||
|
||||
|
@ -189,12 +201,13 @@ func UserReline(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
type userAccountMailData struct {
|
||||
Issuer string
|
||||
LinkAddr string
|
||||
Group string
|
||||
Username string
|
||||
PinCode string
|
||||
OtpImg string
|
||||
Issuer string
|
||||
LinkAddr string
|
||||
Group string
|
||||
Username string
|
||||
PinCode string
|
||||
OtpImg string
|
||||
OtpImgBase64 string
|
||||
}
|
||||
|
||||
func userAccountMail(user *dbdata.User) error {
|
||||
|
@ -235,12 +248,15 @@ func userAccountMail(user *dbdata.User) error {
|
|||
return err
|
||||
}
|
||||
|
||||
otpData, _ := userOtpQr(user.Id, true)
|
||||
|
||||
data := userAccountMailData{
|
||||
LinkAddr: setting.LinkAddr,
|
||||
Group: strings.Join(user.Groups, ","),
|
||||
Username: user.Username,
|
||||
PinCode: user.PinCode,
|
||||
OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString),
|
||||
LinkAddr: setting.LinkAddr,
|
||||
Group: strings.Join(user.Groups, ","),
|
||||
Username: user.Username,
|
||||
PinCode: user.PinCode,
|
||||
OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString),
|
||||
OtpImgBase64: "data:image/png;base64," + otpData,
|
||||
}
|
||||
w := bytes.NewBufferString("")
|
||||
t, _ := template.New("auth_complete").Parse(htmlBody)
|
||||
|
|
|
@ -2,7 +2,7 @@ package admin
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
|
@ -15,7 +15,7 @@ func TestRespSucess(t *testing.T) {
|
|||
RespSucess(w, "data")
|
||||
// fmt.Println(w)
|
||||
assert.Equal(w.Code, 200)
|
||||
body, _ := ioutil.ReadAll(w.Body)
|
||||
body, _ := io.ReadAll(w.Body)
|
||||
res := Resp{}
|
||||
err := json.Unmarshal(body, &res)
|
||||
assert.Nil(err)
|
||||
|
@ -30,7 +30,7 @@ func TestRespError(t *testing.T) {
|
|||
RespError(w, 10, "err-msg")
|
||||
// fmt.Println(w)
|
||||
assert.Equal(w.Code, 200)
|
||||
body, _ := ioutil.ReadAll(w.Body)
|
||||
body, _ := io.ReadAll(w.Body)
|
||||
res := Resp{}
|
||||
err := json.Unmarshal(body, &res)
|
||||
assert.Nil(err)
|
||||
|
|
|
@ -45,10 +45,12 @@ func StartAdmin() {
|
|||
r.HandleFunc("/set/other/audit_log/edit", SetOtherAuditLogEdit)
|
||||
r.HandleFunc("/set/audit/list", SetAuditList)
|
||||
r.HandleFunc("/set/audit/export", SetAuditExport)
|
||||
r.HandleFunc("/set/audit/act_log_list", UserActLogList)
|
||||
|
||||
r.HandleFunc("/user/list", UserList)
|
||||
r.HandleFunc("/user/detail", UserDetail)
|
||||
r.HandleFunc("/user/set", UserSet)
|
||||
r.HandleFunc("/user/uploaduser", UserUpload).Methods(http.MethodPost)
|
||||
r.HandleFunc("/user/del", UserDel)
|
||||
r.HandleFunc("/user/online", UserOnline)
|
||||
r.HandleFunc("/user/offline", UserOffline)
|
||||
|
|
|
@ -3,5 +3,5 @@ package base
|
|||
const (
|
||||
APP_NAME = "AnyLink"
|
||||
// app版本号
|
||||
APP_VER = "0.9.1-beta1"
|
||||
APP_VER = "0.9.2-beta1"
|
||||
)
|
||||
|
|
|
@ -73,7 +73,8 @@ type ServerConfig struct {
|
|||
// AuthTimeout int `json:"auth_timeout"` // in seconds
|
||||
AuditInterval int `json:"audit_interval"` // in seconds
|
||||
|
||||
ShowSQL bool `json:"show_sql"` // bool
|
||||
ShowSQL bool `json:"show_sql"` // bool
|
||||
IptablesNat bool `json:"iptables_nat"`
|
||||
}
|
||||
|
||||
func initServerCfg() {
|
||||
|
|
|
@ -24,7 +24,7 @@ var configs = []config{
|
|||
{Typ: cfgStr, Name: "profile", Usage: "profile.xml file", ValStr: "./conf/profile.xml"},
|
||||
{Typ: cfgStr, Name: "server_addr", Usage: "服务监听地址", ValStr: ":443"},
|
||||
{Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false},
|
||||
{Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":4433"},
|
||||
{Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":443"},
|
||||
{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
|
||||
{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
|
||||
{Typ: cfgStr, Name: "db_type", Usage: "数据库类型 [sqlite3 mysql postgres]", ValStr: "sqlite3"},
|
||||
|
@ -33,7 +33,7 @@ var configs = []config{
|
|||
{Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./conf/vpn_cert.key"},
|
||||
{Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./conf/files"},
|
||||
{Typ: cfgStr, Name: "log_path", Usage: "日志文件路径,默认标准输出", ValStr: ""},
|
||||
{Typ: cfgStr, Name: "log_level", Usage: "日志等级 [debug info warn error]", ValStr: "info"},
|
||||
{Typ: cfgStr, Name: "log_level", Usage: "日志等级 [debug info warn error]", ValStr: "debug"},
|
||||
{Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false},
|
||||
{Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"},
|
||||
{Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"},
|
||||
|
@ -41,26 +41,27 @@ var configs = []config{
|
|||
{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: defaultJwt},
|
||||
{Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型[tun tap macvtap ipvtap]", ValStr: "tun"},
|
||||
{Typ: cfgStr, Name: "ipv4_master", Usage: "ipv4主网卡名称", ValStr: "eth0"},
|
||||
{Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"},
|
||||
{Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.10.1"},
|
||||
{Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.10.100"},
|
||||
{Typ: cfgStr, Name: "ipv4_end", Usage: "IPV4结束", ValStr: "192.168.10.200"},
|
||||
{Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.90.0/24"},
|
||||
{Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.90.1"},
|
||||
{Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.90.100"},
|
||||
{Typ: cfgStr, Name: "ipv4_end", Usage: "IPV4结束", ValStr: "192.168.90.200"},
|
||||
{Typ: cfgStr, Name: "default_group", Usage: "默认用户组", ValStr: "one"},
|
||||
{Typ: cfgStr, Name: "default_domain", Usage: "要发布的默认域", ValStr: ""},
|
||||
|
||||
{Typ: cfgInt, Name: "ip_lease", Usage: "IP租期(秒)", ValInt: 1209600},
|
||||
{Typ: cfgInt, Name: "max_client", Usage: "最大用户连接", ValInt: 100},
|
||||
{Typ: cfgInt, Name: "max_client", Usage: "最大用户连接", ValInt: 200},
|
||||
{Typ: cfgInt, Name: "max_user_client", Usage: "最大单用户连接", ValInt: 3},
|
||||
{Typ: cfgInt, Name: "cstp_keepalive", Usage: "keepalive时间(秒)", ValInt: 20},
|
||||
{Typ: cfgInt, Name: "cstp_dpd", Usage: "死链接检测时间(秒)", ValInt: 30},
|
||||
{Typ: cfgInt, Name: "mobile_keepalive", Usage: "移动端keepalive接检测时间(秒)", ValInt: 50},
|
||||
{Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 60},
|
||||
{Typ: cfgInt, Name: "cstp_keepalive", Usage: "keepalive时间(秒)", ValInt: 4},
|
||||
{Typ: cfgInt, Name: "cstp_dpd", Usage: "死链接检测时间(秒)", ValInt: 10},
|
||||
{Typ: cfgInt, Name: "mobile_keepalive", Usage: "移动端keepalive接检测时间(秒)", ValInt: 7},
|
||||
{Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 15},
|
||||
{Typ: cfgInt, Name: "mtu", Usage: "最大传输单元MTU", ValInt: 1460},
|
||||
{Typ: cfgInt, Name: "session_timeout", Usage: "session过期时间(秒)", ValInt: 3600},
|
||||
// {Typ: cfgInt, Name: "auth_timeout", Usage: "auth_timeout", ValInt: 0},
|
||||
{Typ: cfgInt, Name: "audit_interval", Usage: "审计去重间隔(秒),-1关闭", ValInt: -1},
|
||||
|
||||
{Typ: cfgBool, Name: "show_sql", Usage: "显示sql语句,用于调试", ValBool: false},
|
||||
{Typ: cfgBool, Name: "iptables_nat", Usage: "是否自动添加NAT", ValBool: true},
|
||||
}
|
||||
|
||||
var envs = map[string]string{}
|
||||
|
|
|
@ -30,7 +30,7 @@ jwt_secret = "abcdef.0123456789.abcdef"
|
|||
server_addr = ":443"
|
||||
#开启 DTLS, 默认关闭
|
||||
server_dtls = false
|
||||
server_dtls_addr = ":4433"
|
||||
server_dtls_addr = ":443"
|
||||
#后台服务监听地址
|
||||
admin_addr = ":8800"
|
||||
#开启tcp proxy protocol协议
|
||||
|
@ -40,10 +40,10 @@ link_mode = "tun"
|
|||
|
||||
#客户端分配的ip地址池
|
||||
ipv4_master = "eth0"
|
||||
ipv4_cidr = "192.168.10.0/24"
|
||||
ipv4_gateway = "192.168.10.1"
|
||||
ipv4_start = "192.168.10.100"
|
||||
ipv4_end = "192.168.10.200"
|
||||
ipv4_cidr = "192.168.90.0/24"
|
||||
ipv4_gateway = "192.168.90.1"
|
||||
ipv4_start = "192.168.90.100"
|
||||
ipv4_end = "192.168.90.200"
|
||||
|
||||
#最大客户端数量
|
||||
max_client = 100
|
||||
|
@ -56,10 +56,10 @@ ip_lease = 1209600
|
|||
default_group = "one"
|
||||
|
||||
#客户端失效检测时间(秒) dpd > keepalive
|
||||
cstp_keepalive = 20
|
||||
cstp_dpd = 30
|
||||
mobile_keepalive = 40
|
||||
mobile_dpd = 50
|
||||
cstp_keepalive = 6
|
||||
cstp_dpd = 10
|
||||
mobile_keepalive = 15
|
||||
mobile_dpd = 20
|
||||
|
||||
#设置最大传输单元
|
||||
mtu = 1460
|
||||
|
@ -75,5 +75,7 @@ audit_interval = -1
|
|||
|
||||
show_sql = false
|
||||
|
||||
#是否自动添加nat
|
||||
iptables_nat = true
|
||||
|
||||
|
||||
|
|
|
@ -26,5 +26,12 @@ server_addr = ":443"
|
|||
admin_addr = ":8800"
|
||||
|
||||
|
||||
#客户端分配的ip地址池
|
||||
ipv4_master = "eth0"
|
||||
ipv4_cidr = "192.168.90.0/24"
|
||||
ipv4_gateway = "192.168.90.1"
|
||||
ipv4_start = "192.168.90.100"
|
||||
ipv4_end = "192.168.90.200"
|
||||
|
||||
|
||||
#是否自动添加nat
|
||||
iptables_nat = true
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIF9zCCBN+gAwIBAgIQBNH+cm5YH1O2NhfT+zB+ATANBgkqhkiG9w0BAQsFADBu
|
||||
MIIF9jCCBN6gAwIBAgIQAuUy6Rv6Bo3nDXn5FackbDANBgkqhkiG9w0BAQsFADBu
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
|
||||
RFYgVExTIENBIC0gRzEwHhcNMjExMjEyMDAwMDAwWhcNMjIxMjEzMjM1OTU5WjAc
|
||||
RFYgVExTIENBIC0gRzEwHhcNMjMwMTAzMDAwMDAwWhcNMjQwMTAzMjM1OTU5WjAc
|
||||
MRowGAYDVQQDExF2cG4udGVzdC52cWlsdS5jbjCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAK2XO6Na//i0sMiV0nF+aDTbDibGiTLr+LFlhTIi1KX9IAU2
|
||||
Xboz1B8cxDro3g+CzgrGg0YMI4CxBiY56UT3jUTsLYBNpWPkbhlH+mpf0J7fgH29
|
||||
V1LAZKm2qR28y/krKHIbcGrfMAbXi6iVkVHhc+edvGCdAiDSyJgVSZbYV/s0LXLF
|
||||
0B0BokagwtvGIx7ik5uG4exuRCUKE3z0n6RXdN0eWBvKKHFhWEeaBIGzHjoDgAx/
|
||||
4VJ8XsW0tcwByiVRqpMFa1eG3HLMvi34M1qLzNv7dGPIkr1zjvlvTqhDpimXOi9C
|
||||
4N5ZOfZfNAyR8zU5+tBqSCvByavxLJwC//F7VQcCAwEAAaOCAuEwggLdMB8GA1Ud
|
||||
IwQYMBaAFFV0T7JyT/VgulDR1+ZRXJoBhxrXMB0GA1UdDgQWBBQKyNOGPzBPyqY9
|
||||
nxahHC+B6xT83TAcBgNVHREEFTATghF2cG4udGVzdC52cWlsdS5jbjAOBgNVHQ8B
|
||||
ggEPADCCAQoCggEBANJAJPYBvOP/7v8SgMIkVLIulN/ziPALvFcEwVnQDImUIky8
|
||||
4udy0fmvJ2E3E3NL6Qv14ZHDGtH7CafukimNWTT2BVmQBYiO1ZlUkHcHUX4IoYEh
|
||||
egdy2xw0WwknJWTOyvkRkeDhtT9QUpA/zeemS4q1TG95zRDf5htUR4OMZXsZpkQ2
|
||||
bkSgnLtdyUmw2nhfSWgsD9fbwr6WnOx/swsUe52N3sIDZ6JTgn3N7xeT3/lVJKVN
|
||||
wyYkZldialmRzrs6btr3mmnqpWObcc4FvKr/CLmoOSXl0I1wWsr+HnQ4X9hHsJUk
|
||||
jk3EZKfhH3mM37HF8apqztb6WjC3R96Zam6Z8bMCAwEAAaOCAuAwggLcMB8GA1Ud
|
||||
IwQYMBaAFFV0T7JyT/VgulDR1+ZRXJoBhxrXMB0GA1UdDgQWBBSUpPcW3emC2l0o
|
||||
q6qRBOBDMjQ2rDAcBgNVHREEFTATghF2cG4udGVzdC52cWlsdS5jbjAOBgNVHQ8B
|
||||
Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMD4GA1UdIAQ3
|
||||
MDUwMwYGZ4EMAQIBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQu
|
||||
Y29tL0NQUzCBgAYIKwYBBQUHAQEEdDByMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
|
||||
cC5kaWdpY2VydC5jb20wSgYIKwYBBQUHMAKGPmh0dHA6Ly9jYWNlcnRzLmRpZ2lj
|
||||
ZXJ0LmNvbS9FbmNyeXB0aW9uRXZlcnl3aGVyZURWVExTQ0EtRzEuY3J0MAkGA1Ud
|
||||
EwQCMAAwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2ACl5vvCeOTkh8FZzn2Ol
|
||||
d+W+V32cYAr4+U1dJlwlXceEAAABfa0lBgAAAAQDAEcwRQIgEQ4wS5gyLMK30aeD
|
||||
xF3kWvsUhkd94HKIl13ckYnukGMCIQD1/6fFUAPjdw2k8f/ctJ7STUHeA1WoBy5H
|
||||
O/iXBRCkWgB2AFGjsPX9AXmcVm24N3iPDKR6zBsny/eeiEKaDf7UiwXlAAABfa0l
|
||||
BmYAAAQDAEcwRQIgOoguGrrlpwoxGiJHJNcEWbuH2AOJCDSDiun80DX9hUwCIQCJ
|
||||
cFCOe5E5VbgHrTWbQ0OUFS0epDgUiG8y9kjfkN1M5QB2AEHIyrHfIkZKEMahOglC
|
||||
h15OMYsbA+vrS8do8JBilgb2AAABfa0lBfoAAAQDAEcwRQIhAIHCUjXv+M3/jFOU
|
||||
AzjjMCISczShjqQ5FKqsIYNTUN46AiAom+II914ifwdFiS2xWI0ncSj8cxH6f+WZ
|
||||
UUQj9RczMDANBgkqhkiG9w0BAQsFAAOCAQEALj5oEwyU+gxVKhLFrBBtkoi9F0HQ
|
||||
jjSQZvOcKApSXjKS11VdmLGKuy85FSocw7VvDtZ4o43OhO79GMAMiPXroTnPIS5O
|
||||
ZNxfuusF6HpS+2Dq9UidnlxQmIaJ4A7PkX+NqAI4V6yr839SXKyHJROfXf9hNoJZ
|
||||
PJeZ94oMwXdeNjFkOismFpvaZcYq7t51xi5tkH/NaJHV5FEU8Or4zk/OoaPe3r+b
|
||||
2hpltIIaapoNVYLWLW7YS7hlvhjfwPypsR3ev4bTRWvT1tu9+AE+TG0OZqeWGucP
|
||||
6MjZI5gecOnkQVmBovkRi2lr26PDWrwnAlyoMI3ioU1XaTftIrBL2YalfQ==
|
||||
EwQCMAAwggF9BgorBgEEAdZ5AgQCBIIBbQSCAWkBZwB2AO7N0GTV2xrOxVy3nbTN
|
||||
E6Iyh0Z8vOzew1FIWUZxH7WbAAABhXXkyXMAAAQDAEcwRQIgVhLvLOPcW0V1xhBv
|
||||
5KSeqGHbAnRVhew3kutV3Bu1x+ICIQCbYjRtmkDo1hx6p0YNdNfkZ3N5u+syVjwH
|
||||
Al3a9NpVxgB1AEiw42vapkc0D+VqAvqdMOscUgHLVt0sgdm7v6s52IRzAAABhXXk
|
||||
yY4AAAQDAEYwRAIgcuscG2kkSGNvAsVH9CAtXjNUwk9UJriY0+3OtQ4WVrMCIAsC
|
||||
CkqEI1Ek5M26yrWt0Q7+u+UZ8rXhfYu3kcMMq7PVAHYAO1N3dT4tuYBOizBbBv5A
|
||||
O2fYT8P0x70ADS1yb+H61BcAAAGFdeTJkAAABAMARzBFAiAEJbJTN8hrRUZ6UaaD
|
||||
2TlyDQfzUvTkex0XGT6PGKHkagIhAJ+Kg6tdt/csKde2vdweu+dT01fzg/fq4q3o
|
||||
mjfPhFm1MA0GCSqGSIb3DQEBCwUAA4IBAQAKFTUHbpgKsXARCBIIfEZGqkOvaafm
|
||||
QaoNodc6cj0+LJCbuMzrTlkzmII0X/U52MBG8JCEIO8BPe5R4NIFqqaE066zQANq
|
||||
HOsROOJi2A+WTTZcSEHbH3uhdVwcEQHvDzaOEEJc9Ilz6pdYsrv+trOmeR5PeIxv
|
||||
t1jQacSwN1z6z0N4CRjBpePV/9nwETkEaKjQuXSoYlN+pczK/4nX2W9+E/OnwtZs
|
||||
ScyFffPtTLHf1u4eSYuBT/AdwaKHXetxWzh98GP9LRfQhm63Gs+/WcloYl489dG/
|
||||
FOFjch2TdmrPcUwxxGEbbPt3zXRxSVlzvIaf4gTUl2+PsKwbKy/w4OLS
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEArZc7o1r/+LSwyJXScX5oNNsOJsaJMuv4sWWFMiLUpf0gBTZd
|
||||
ujPUHxzEOujeD4LOCsaDRgwjgLEGJjnpRPeNROwtgE2lY+RuGUf6al/Qnt+Afb1X
|
||||
UsBkqbapHbzL+Ssochtwat8wBteLqJWRUeFz5528YJ0CINLImBVJlthX+zQtcsXQ
|
||||
HQGiRqDC28YjHuKTm4bh7G5EJQoTfPSfpFd03R5YG8oocWFYR5oEgbMeOgOADH/h
|
||||
UnxexbS1zAHKJVGqkwVrV4bccsy+LfgzWovM2/t0Y8iSvXOO+W9OqEOmKZc6L0Lg
|
||||
3lk59l80DJHzNTn60GpIK8HJq/EsnAL/8XtVBwIDAQABAoIBACXjPEELO5Ms3Ojq
|
||||
ymO7E0N2DECqVIeouT7+yXOH5qHT/YkltI9PgJzJyoqRCOaZxh7T9RL000rjWFQ/
|
||||
j4pd/ZdtdQDr8Y077kvWSfGtt/r1DTZkfQqys0XXeFHlQx+/K7S8CG1LCVB0+yZw
|
||||
fqdAbeu/ob30huJjHyUSgF1MGufYvuII6x0CGORwzruWWFniXkg2z+9SP4x4RSfm
|
||||
exMUE4T4tlzR63QaW02xWEDTWCSQw/FgjpCWwryDVCmnLf63UhI+4hITqZLL+ROd
|
||||
sG/8Yp284q7BYBKk4/N1HD4W1vU+dls3glxZ22NCQKx+2RVtqTrRUd/d4AnxOmMR
|
||||
dnfh4AECgYEA7cl9NIRrtQdW+KFcoSdyP2F+SU74nSAh6Uolzwr9lHB+NbMJ5g79
|
||||
eU1zp3RAvSFg249L4cnceaFL1LTPcNN0xhpaJ7v5FQWk5tkddSmy2T3CAh8VwLXF
|
||||
487pgakO1SpS6uz+BtwsAFOS8k/GjYeSbPR4e9F/FbYAvGYwOLNj2ocCgYEAuuL8
|
||||
xnFnt95TwWptu4T97YXTeZRB17jiH1BhX+QawsSafagsWlSKihKMxYhfCHiwztS/
|
||||
KsCnkS6cH9slU3y4gvCiT1S4z1Qkw93ljUQXCzRIVEd9SxXoQMeRi+/5c239Fhnu
|
||||
aoxESAFWNXJZ5r9Jp3qukHvEtYn2FoE1Zkmu0YECgYApULgDdvqr4pGW85p/mbX9
|
||||
Ezh5DlKeImYh/bMiDTvQHdegBvKyWWprOCzfLJDPC8yjeXtqyMMZExB07dGZPfRt
|
||||
M0j03HFD2M41GgZHRC6CFnvuGG6UJEE0+s+Rqskb+pWbof/lOz4d9Gd02K2cC7FC
|
||||
YxvID7dwE0Z/dZXtVCYGYwKBgQCjckPKtoIUcBBmV1NzLiP66REEAuL27Q5ufpk7
|
||||
CT9SWioXfc6Ujd3AVeriE5uxyAQyUCSFGosy0UXgIoRpmOmyMwxxP1KGmTuyRc4u
|
||||
l39j4Czl8MQmuBkxFpk3fwB2sJopCzLV4qkRJIImKkVwJpofLI+hc22dq/QayJRQ
|
||||
Sl7ngQKBgQCkfcbQDvhkL6QKUC/K7MDGw9JMICLUpRyp6D3ibeL7i6WO6dkKde2t
|
||||
O/oLz2XvG0NR0nulhThpWUdyUWco3FZ038jiuY8ZZum5wdVBDOcDcnuBisE3Kzh8
|
||||
p7WycoWItAVxmyTKzHJIZ7pFQULYjap7gFSUPE9uBQZu09VKBtGPHA==
|
||||
MIIEpQIBAAKCAQEA0kAk9gG84//u/xKAwiRUsi6U3/OI8Au8VwTBWdAMiZQiTLzi
|
||||
53LR+a8nYTcTc0vpC/XhkcMa0fsJp+6SKY1ZNPYFWZAFiI7VmVSQdwdRfgihgSF6
|
||||
B3LbHDRbCSclZM7K+RGR4OG1P1BSkD/N56ZLirVMb3nNEN/mG1RHg4xlexmmRDZu
|
||||
RKCcu13JSbDaeF9JaCwP19vCvpac7H+zCxR7nY3ewgNnolOCfc3vF5Pf+VUkpU3D
|
||||
JiRmV2JqWZHOuzpu2veaaeqlY5txzgW8qv8Iuag5JeXQjXBayv4edDhf2EewlSSO
|
||||
TcRkp+EfeYzfscXxqmrO1vpaMLdH3plqbpnxswIDAQABAoIBAGKKzugAi4Q/Vch2
|
||||
ZyPXPF0hCQToE3QSxAzy/R53rRCkfekClMTO44xHpEjjs/mTiCBjd3xGeiEVrIJp
|
||||
hlb0WW3Bq2M9ZeKJs6JAaM9o/jB4oh2wT44DLqALB+oDz3puk+Jl8j34++a3YmMa
|
||||
jIq4veo+rBsJduwkTKjdeQE2ge/ODZEQ6bUmSjYo1P9LNGEyO2wmcVk+jHx0zBi7
|
||||
8fR3oY03Io+byuN0494Di1m3IpIdj3ma0MV5zJf31urLXqqYtOApouWL/yhZOIuo
|
||||
YW+mcuS7ZgK5FsqrUm0vGBcf24GcKhhlBlUu0mfLrCRrWLDsqJDQ/8alvuNP0IVm
|
||||
gqz0H5UCgYEA8yaKzMfkeRXSTERt6NZHo/8ShIn26Yf+pMDpVdYKfVBL254vGTeq
|
||||
B+LQhDpxZV1iMr1FNvkhHtNGQ74ZbWOj4+5Xjsllaw1ao2iGp1w/chuJ6FG/Go4q
|
||||
9FaY3eGiCqRJOQNivBxU/D7sN0y4b48HAEQdmp516pItlGtG+C8TY38CgYEA3VyD
|
||||
6EczHdAmPO7bdbYn/irfe78so1lHT04P0FiVg2W77ZKuQINTNDK9w/alYyZ2tH1b
|
||||
N2JznulJ6UDcl4xw43xJixxhme2jWPaYzmQUuQHviZ0D0tCgmOkN8bUnc9LSvEGA
|
||||
SnaiKbOtUfP8Z3c/mF997wuFNfdmhww8LBpbO80CgYEAmdRKf9/+5bQuhd3NAz99
|
||||
t31KQ9vdAEXvjmAVvx5ZKIrCU0EyXuvegHq4nM80qoJ3+83Omkbm80+K5pTAFXqy
|
||||
VyOU9Vro9N9P9o3MktlDsndFulrtYmmLN2YJ9GYpVD43rQA9WPE7uxI784hwLvP3
|
||||
4+00JXwW8b5lY76y+ZUe2RUCgYEAt6BQN/YgPCH4JmHKIWp64If2HbQntlWQJwRN
|
||||
b/qcBIT3EQu1iwSll85jxtSqu4YjwHOgoGAGI5PIYTsSApFY8AyhAUoI2OTdtSXS
|
||||
+prg6dvmNhTPICk6n73seE5bLOR9NfdsEdk5ijhnlW09OyMb2S2VzR+UYIEbRvnq
|
||||
THeMqR0CgYEAwQ9aAIGD4t4DXjHHtz6Wqpbq6jj6mmYBsL9jqRgu8ASj1/THgWWn
|
||||
iUrlCbFIXWu2vnP1h0SBV56GA7MSTqAt0ZdTzpI1PRkMOIO9z1dN4Q2R1SEya1eF
|
||||
7/LPLqJIoLbILGvy6U3DLYdMckPZaoTPf5BNKD52paZcNbjkXTlVLSY=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
|
@ -43,10 +43,5 @@ func getTimeAgo(days int) string {
|
|||
ts := time.Now().AddDate(0, 0, -days)
|
||||
tsZero := time.Date(ts.Year(), ts.Month(), ts.Day(), 0, 0, 0, 0, time.Local)
|
||||
timeS = tsZero.Format(dbdata.LayoutTimeFormat)
|
||||
// UTC
|
||||
switch base.Cfg.DbType {
|
||||
case "sqlite3", "postgres":
|
||||
timeS = tsZero.UTC().Format(dbdata.LayoutTimeFormat)
|
||||
}
|
||||
return timeS
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package cron
|
||||
|
||||
import (
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
)
|
||||
|
||||
// 清除用户活动日志
|
||||
func ClearUserActLog() {
|
||||
lifeDay, timesUp := isClearTime()
|
||||
if !timesUp {
|
||||
return
|
||||
}
|
||||
// 当审计日志永久保存时,则退出
|
||||
if lifeDay <= 0 {
|
||||
return
|
||||
}
|
||||
affected, err := dbdata.UserActLogIns.ClearUserActLog(getTimeAgo(lifeDay))
|
||||
base.Info("Cron ClearUserActLog: ", affected, err)
|
||||
}
|
|
@ -3,6 +3,7 @@ package cron
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
"github.com/go-co-op/gocron"
|
||||
)
|
||||
|
||||
|
@ -10,5 +11,7 @@ func Start() {
|
|||
s := gocron.NewScheduler(time.Local)
|
||||
s.Cron("0 * * * *").Do(ClearAudit)
|
||||
s.Cron("0 * * * *").Do(ClearStatsInfo)
|
||||
s.Cron("0 * * * *").Do(ClearUserActLog)
|
||||
s.Every(1).Day().At("00:00").Do(sessdata.CloseUserLimittimeSession)
|
||||
s.StartAsync()
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ type SearchCon struct {
|
|||
AccessProto string `json:"access_proto"`
|
||||
Date []string `json:"date"`
|
||||
Info string `json:"info"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
func GetAuditSession(search string) *xorm.Session {
|
||||
|
@ -47,6 +48,11 @@ func GetAuditSession(search string) *xorm.Session {
|
|||
if searchData.Info != "" {
|
||||
session.And("info LIKE ?", "%"+searchData.Info+"%")
|
||||
}
|
||||
if searchData.Sort == 1 {
|
||||
session.OrderBy("id desc")
|
||||
} else {
|
||||
session.OrderBy("id asc")
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ func TestSearchAudit(t *testing.T) {
|
|||
defer closeIpdata()
|
||||
|
||||
currDateVal := "2022-07-24 00:00:00"
|
||||
CreatedAt, _ := time.Parse("2006-01-02 15:04:05", currDateVal)
|
||||
CreatedAt, _ := time.ParseInLocation("2006-01-02 15:04:05", currDateVal, time.Local)
|
||||
|
||||
dataTest := AccessAudit{
|
||||
Username: "Test",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package dbdata
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
|
@ -19,6 +21,9 @@ func GetXdb() *xorm.Engine {
|
|||
func initDb() {
|
||||
var err error
|
||||
xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource)
|
||||
// 初始化xorm时区
|
||||
xdb.DatabaseTZ = time.Local
|
||||
xdb.TZLocation = time.Local
|
||||
if err != nil {
|
||||
base.Fatal(err)
|
||||
}
|
||||
|
@ -28,7 +33,7 @@ func initDb() {
|
|||
}
|
||||
|
||||
// 初始化数据库
|
||||
err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}, &StatsNetwork{}, &StatsCpu{}, &StatsMem{}, &StatsOnline{})
|
||||
err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}, &StatsNetwork{}, &StatsCpu{}, &StatsMem{}, &StatsOnline{}, &UserActLog{})
|
||||
if err != nil {
|
||||
base.Fatal(err)
|
||||
}
|
||||
|
@ -123,6 +128,7 @@ func addInitData() error {
|
|||
AllowLan: true,
|
||||
ClientDns: []ValData{{Val: "114.114.114.114"}},
|
||||
RouteInclude: []ValData{{Val: All}},
|
||||
Status: 1,
|
||||
}
|
||||
err = SetGroup(&g1)
|
||||
if err != nil {
|
||||
|
@ -143,8 +149,12 @@ const accountMail = `<p>您好:</p>
|
|||
用户组: <b>{{.Group}}</b> <br/>
|
||||
用户名: <b>{{.Username}}</b> <br/>
|
||||
用户PIN码: <b>{{.PinCode}}</b> <br/>
|
||||
<!--
|
||||
用户动态码(3天后失效):<br/>
|
||||
<img src="{{.OtpImg}}"/>
|
||||
-->
|
||||
用户动态码(请妥善保存):<br/>
|
||||
<img src="{{.OtpImgBase64}}"/>
|
||||
</p>
|
||||
<div>
|
||||
使用说明:
|
||||
|
|
|
@ -74,6 +74,20 @@ func GetGroupNames() []string {
|
|||
return names
|
||||
}
|
||||
|
||||
func GetGroupNamesNormal() []string {
|
||||
var datas []Group
|
||||
err := FindWhere(&datas, 0, 0, "status=1")
|
||||
if err != nil {
|
||||
base.Error(err)
|
||||
return nil
|
||||
}
|
||||
var names []string
|
||||
for _, v := range datas {
|
||||
names = append(names, v.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func GetGroupNamesIds() []GroupNameId {
|
||||
var datas []Group
|
||||
err := Find(&datas, 0, 0)
|
||||
|
|
|
@ -21,8 +21,9 @@ type SettingSmtp struct {
|
|||
}
|
||||
|
||||
type SettingAuditLog struct {
|
||||
LifeDay int `json:"life_day"`
|
||||
ClearTime string `json:"clear_time"`
|
||||
AuditInterval int `json:"audit_interval"`
|
||||
LifeDay int `json:"life_day"`
|
||||
ClearTime string `json:"clear_time"`
|
||||
}
|
||||
|
||||
type SettingOther struct {
|
||||
|
|
|
@ -199,12 +199,6 @@ func (s *StatsInfo) getScopeDetail(scope string) (sd *ScopeDetail) {
|
|||
}
|
||||
sd.fsTime = sd.sTime.Format(LayoutTimeFormat)
|
||||
sd.feTime = sd.eTime.Format(LayoutTimeFormat)
|
||||
// UTC
|
||||
switch base.Cfg.DbType {
|
||||
case "sqlite3", "postgres":
|
||||
sd.fsTime = sd.sTime.UTC().Format(LayoutTimeFormat)
|
||||
sd.feTime = sd.eTime.UTC().Format(LayoutTimeFormat)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -29,20 +29,38 @@ type User struct {
|
|||
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"`
|
||||
PinCode string `json:"pin_code" xorm:"varchar(32)"`
|
||||
LimitTime *time.Time `json:"limittime,omitempty" xorm:"Datetime limittime"` //值为null时,前端不显示
|
||||
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 UserActLog struct {
|
||||
Id int `json:"id" xorm:"pk autoincr not null"`
|
||||
Username string `json:"username" xorm:"varchar(60)"`
|
||||
GroupName string `json:"group_name" xorm:"varchar(60)"`
|
||||
IpAddr string `json:"ip_addr" xorm:"varchar(32)"`
|
||||
RemoteAddr string `json:"remote_addr" xorm:"varchar(32)"`
|
||||
Os uint8 `json:"os" xorm:"not null default 0 Int"`
|
||||
Client uint8 `json:"client" xorm:"not null default 0 Int"`
|
||||
Version string `json:"version" xorm:"varchar(15)"`
|
||||
DeviceType string `json:"device_type" xorm:"varchar(128) not null default ''"`
|
||||
PlatformVersion string `json:"platform_version" xorm:"varchar(128) not null default ''"`
|
||||
Status uint8 `json:"status" xorm:"not null default 0 Int"`
|
||||
Info string `json:"info" xorm:"varchar(255) not null default ''"` // 详情
|
||||
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
|
||||
}
|
||||
|
||||
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"`
|
||||
UniqueMac bool `json:"unique_mac" xorm:"Bool index"`
|
||||
Username string `json:"username" xorm:"varchar(60)"`
|
||||
Keep bool `json:"keep" xorm:"Bool"` // 保留 ip-mac 绑定
|
||||
KeepTime time.Time `json:"keep_time" xorm:"DateTime"`
|
||||
|
|
|
@ -104,7 +104,12 @@ func checkLocalUser(name, pwd, group string) error {
|
|||
v := &User{}
|
||||
err := One("Username", name, v)
|
||||
if err != nil || v.Status != 1 {
|
||||
return fmt.Errorf("%s %s", name, "用户名错误")
|
||||
switch v.Status {
|
||||
case 0:
|
||||
return fmt.Errorf("%s %s", name, "用户不存在或用户已停用")
|
||||
case 2:
|
||||
return fmt.Errorf("%s %s", name, "用户已过期")
|
||||
}
|
||||
}
|
||||
// 判断用户组信息
|
||||
if !utils.InArrStr(v.Groups, group) {
|
||||
|
@ -128,6 +133,21 @@ func checkLocalUser(name, pwd, group string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// 用户过期时间到达后,更新用户状态,并返回一个状态为过期的用户切片
|
||||
func CheckUserlimittime() (limitUser []interface{}) {
|
||||
if _, err := xdb.Where("limittime <= ?", time.Now()).And("status = ?", 1).Update(&User{Status: 2}); err != nil {
|
||||
return
|
||||
}
|
||||
user := make(map[int64]User)
|
||||
if err := xdb.Where("status != ?", 1).Find(user); err != nil {
|
||||
return
|
||||
}
|
||||
for _, v := range user {
|
||||
limitUser = append(limitUser, v.Username)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
userOtpMux = sync.Mutex{}
|
||||
userOtp = map[string]time.Time{}
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
package dbdata
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/ivpusic/grpool"
|
||||
"github.com/spf13/cast"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
const (
|
||||
UserAuthFail = 0 // 认证失败
|
||||
UserAuthSuccess = 1 // 认证成功
|
||||
UserConnected = 2 // 连线成功
|
||||
UserLogout = 3 // 用户登出
|
||||
UserLogoutLose = 0 // 用户掉线
|
||||
UserLogoutBanner = 1 // 用户banner弹窗取消
|
||||
UserLogoutClient = 2 // 用户主动登出
|
||||
UserLogoutTimeout = 3 // 用户超时登出
|
||||
UserLogoutAdmin = 4 // 账号被管理员踢下线
|
||||
UserLogoutExpire = 5 // 账号过期被踢下线
|
||||
)
|
||||
|
||||
type UserActLogProcess struct {
|
||||
Pool *grpool.Pool
|
||||
StatusOps []string
|
||||
OsOps []string
|
||||
ClientOps []string
|
||||
InfoOps []string
|
||||
}
|
||||
|
||||
var (
|
||||
UserActLogIns = &UserActLogProcess{
|
||||
Pool: grpool.NewPool(1, 100),
|
||||
StatusOps: []string{ // 操作类型
|
||||
UserAuthFail: "认证失败",
|
||||
UserAuthSuccess: "认证成功",
|
||||
UserConnected: "连接成功",
|
||||
UserLogout: "用户登出",
|
||||
},
|
||||
OsOps: []string{ // 操作系统
|
||||
0: "Unknown",
|
||||
1: "Windows",
|
||||
2: "macOS",
|
||||
3: "Linux",
|
||||
4: "Android",
|
||||
5: "iOS",
|
||||
},
|
||||
ClientOps: []string{ // 客户端
|
||||
0: "Unknown",
|
||||
1: "AnyConnect",
|
||||
2: "OpenConnect",
|
||||
3: "AnyLink",
|
||||
},
|
||||
InfoOps: []string{ // 信息
|
||||
UserLogoutLose: "用户掉线",
|
||||
UserLogoutBanner: "用户取消弹窗/客户端发起的logout",
|
||||
UserLogoutClient: "用户/客户端主动断开",
|
||||
UserLogoutTimeout: "Session过期被踢下线",
|
||||
UserLogoutAdmin: "账号被管理员踢下线",
|
||||
UserLogoutExpire: "账号过期被踢下线",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// 异步写入用户操作日志
|
||||
func (ua *UserActLogProcess) Add(u UserActLog, userAgent string) {
|
||||
// os, client, ver
|
||||
os_idx, client_idx, ver := ua.ParseUserAgent(userAgent)
|
||||
u.Os = os_idx
|
||||
u.Client = client_idx
|
||||
u.Version = ver
|
||||
u.RemoteAddr = strings.Split(u.RemoteAddr, ":")[0]
|
||||
// remove extra characters
|
||||
infoSlice := strings.Split(u.Info, " ")
|
||||
infoLen := len(infoSlice)
|
||||
if infoLen > 1 {
|
||||
if u.Username == infoSlice[0] {
|
||||
u.Info = strings.Join(infoSlice[1:], " ")
|
||||
}
|
||||
// delete - char
|
||||
if infoLen > 2 && infoSlice[1] == "-" {
|
||||
u.Info = u.Info[2:]
|
||||
}
|
||||
}
|
||||
// limit the max length of char
|
||||
u.Version = substr(u.Version, 0, 15)
|
||||
u.DeviceType = substr(u.DeviceType, 0, 128)
|
||||
u.PlatformVersion = substr(u.PlatformVersion, 0, 128)
|
||||
u.Info = substr(u.Info, 0, 255)
|
||||
|
||||
UserActLogIns.Pool.JobQueue <- func() {
|
||||
err := Add(u)
|
||||
if err != nil {
|
||||
base.Error("Add UserActLog error: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转义操作类型, 方便vue显示
|
||||
func (ua *UserActLogProcess) GetStatusOpsWithTag() interface{} {
|
||||
type StatusTag struct {
|
||||
Key int `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
var res []StatusTag
|
||||
for k, v := range ua.StatusOps {
|
||||
tag := "info"
|
||||
switch k {
|
||||
case UserAuthFail:
|
||||
tag = "danger"
|
||||
case UserAuthSuccess:
|
||||
tag = "success"
|
||||
case UserConnected:
|
||||
tag = ""
|
||||
}
|
||||
res = append(res, StatusTag{k, v, tag})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ua *UserActLogProcess) GetInfoOpsById(id uint8) string {
|
||||
return ua.InfoOps[id]
|
||||
}
|
||||
|
||||
// 解析user agent
|
||||
func (ua *UserActLogProcess) ParseUserAgent(userAgent string) (os_idx, client_idx uint8, ver string) {
|
||||
// Unknown
|
||||
if len(userAgent) == 0 {
|
||||
return 0, 0, ""
|
||||
}
|
||||
// OS
|
||||
os_idx = 0
|
||||
if strings.Contains(userAgent, "windows") {
|
||||
os_idx = 1
|
||||
} else if strings.Contains(userAgent, "mac os") || strings.Contains(userAgent, "darwin_i386") {
|
||||
os_idx = 2
|
||||
} else if strings.Contains(userAgent, "darwin_arm") || strings.Contains(userAgent, "apple") {
|
||||
os_idx = 5
|
||||
} else if strings.Contains(userAgent, "android") {
|
||||
os_idx = 4
|
||||
} else if strings.Contains(userAgent, "linux") {
|
||||
os_idx = 3
|
||||
}
|
||||
// Client
|
||||
client_idx = 0
|
||||
if strings.Contains(userAgent, "anyconnect") {
|
||||
client_idx = 1
|
||||
} else if strings.Contains(userAgent, "openconnect") {
|
||||
client_idx = 2
|
||||
} else if strings.Contains(userAgent, "anylink") {
|
||||
client_idx = 3
|
||||
}
|
||||
// Verion
|
||||
uaSlice := strings.Split(userAgent, " ")
|
||||
ver = uaSlice[len(uaSlice)-1]
|
||||
if ver[0] == 'v' {
|
||||
ver = ver[1:]
|
||||
}
|
||||
if !regexp.MustCompile(`^(\d+\.?)+$`).MatchString(ver) {
|
||||
ver = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 清除用户操作日志
|
||||
func (ua *UserActLogProcess) ClearUserActLog(ts string) (int64, error) {
|
||||
affected, err := xdb.Where("created_at < '" + ts + "'").Delete(&UserActLog{})
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// 后台筛选用户操作日志
|
||||
func (ua *UserActLogProcess) GetSession(values url.Values) *xorm.Session {
|
||||
session := xdb.Where("1=1")
|
||||
if values.Get("username") != "" {
|
||||
session.And("username = ?", values.Get("username"))
|
||||
}
|
||||
if values.Get("sdate") != "" {
|
||||
session.And("created_at >= ?", values.Get("sdate")+" 00:00:00'")
|
||||
}
|
||||
if values.Get("edate") != "" {
|
||||
session.And("created_at <= ?", values.Get("edate")+" 23:59:59'")
|
||||
}
|
||||
if values.Get("status") != "" {
|
||||
session.And("status = ?", cast.ToUint8(values.Get("status"))-1)
|
||||
}
|
||||
if values.Get("os") != "" {
|
||||
session.And("os = ?", cast.ToUint8(values.Get("os"))-1)
|
||||
}
|
||||
if values.Get("sort") == "1" {
|
||||
session.OrderBy("id desc")
|
||||
} else {
|
||||
session.OrderBy("id asc")
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
// 截取字符串
|
||||
func substr(s string, pos, length int) string {
|
||||
runes := []rune(s)
|
||||
l := pos + length
|
||||
if l > len(runes) {
|
||||
l = len(runes)
|
||||
}
|
||||
return string(runes[pos:l])
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package dbdata
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseUserAgent(t *testing.T) {
|
||||
type args struct {
|
||||
userAgent string
|
||||
}
|
||||
type res struct {
|
||||
os_idx uint8
|
||||
client_idx uint8
|
||||
ver string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want res
|
||||
}{
|
||||
{
|
||||
name: "mac os 1",
|
||||
args: args{userAgent: "cisco anyconnect vpn agent for mac os x 4.10.05085"},
|
||||
want: res{os_idx: 2, client_idx: 1, ver: "4.10.05085"},
|
||||
},
|
||||
{
|
||||
name: "mac os 2",
|
||||
args: args{userAgent: "anyconnect darwin_i386 4.10.05085"},
|
||||
want: res{os_idx: 2, client_idx: 1, ver: "4.10.05085"},
|
||||
},
|
||||
{
|
||||
name: "windows",
|
||||
args: args{userAgent: "cisco anyconnect vpn agent for windows 4.8.02042"},
|
||||
want: res{os_idx: 1, client_idx: 1, ver: "4.8.02042"},
|
||||
},
|
||||
{
|
||||
name: "iPad",
|
||||
args: args{userAgent: "anyconnect applesslvpn_darwin_arm (ipad) 4.10.04060"},
|
||||
want: res{os_idx: 5, client_idx: 1, ver: "4.10.04060"},
|
||||
},
|
||||
{
|
||||
name: "iPhone",
|
||||
args: args{userAgent: "cisco anyconnect vpn agent for apple iphone 4.10.04060"},
|
||||
want: res{os_idx: 5, client_idx: 1, ver: "4.10.04060"},
|
||||
},
|
||||
{
|
||||
name: "android",
|
||||
args: args{userAgent: "anyconnect android 4.10.05096"},
|
||||
want: res{os_idx: 4, client_idx: 1, ver: "4.10.05096"},
|
||||
},
|
||||
{
|
||||
name: "linux",
|
||||
args: args{userAgent: "cisco anyconnect vpn agent for linux v7.08"},
|
||||
want: res{os_idx: 3, client_idx: 1, ver: "7.08"},
|
||||
},
|
||||
{
|
||||
name: "openconnect",
|
||||
args: args{userAgent: "openconnect-gui 1.5.3 v7.08"},
|
||||
want: res{os_idx: 0, client_idx: 2, ver: "7.08"},
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
args: args{userAgent: "unknown 1.4.3 aabcd"},
|
||||
want: res{os_idx: 0, client_idx: 0, ver: ""},
|
||||
},
|
||||
{
|
||||
name: "unknown 2",
|
||||
args: args{userAgent: ""},
|
||||
want: res{os_idx: 0, client_idx: 0, ver: ""},
|
||||
},
|
||||
{
|
||||
name: "anylink",
|
||||
args: args{userAgent: "anylink vpn agent for linux v1.0"},
|
||||
want: res{os_idx: 3, client_idx: 3, ver: "1.0"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if os_idx, client_idx, ver := UserActLogIns.ParseUserAgent(tt.args.userAgent); os_idx != tt.want.os_idx || client_idx != tt.want.client_idx || ver != tt.want.ver {
|
||||
t.Errorf("ParseUserAgent() = %v, %v, %v, want %v, %v, %v", os_idx, client_idx, ver, tt.want.os_idx, tt.want.client_idx, tt.want.ver)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ go 1.18
|
|||
|
||||
require (
|
||||
github.com/arl/statsviz v0.5.1
|
||||
github.com/deckarep/golang-set v1.8.0
|
||||
github.com/go-co-op/gocron v1.17.0
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
|
@ -14,62 +15,70 @@ require (
|
|||
github.com/gorilla/mux v1.8.0
|
||||
github.com/ivpusic/grpool v1.0.0
|
||||
github.com/lib/pq v1.10.2
|
||||
github.com/mattn/go-sqlite3 v1.14.8
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/pion/dtls/v2 v2.1.5
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/shirou/gopsutil v3.21.7+incompatible
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||
github.com/spf13/cast v1.3.1
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||
github.com/xuri/excelize/v2 v2.6.1
|
||||
go.uber.org/atomic v1.10.0
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||
xorm.io/xorm v1.2.2
|
||||
xorm.io/xorm v1.3.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/goccy/go-json v0.7.4 // indirect
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/goccy/go-json v0.8.1 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
||||
github.com/pion/transport v0.13.0 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.3 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.7 // indirect
|
||||
github.com/tklauser/numcpus v0.2.3 // indirect
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
xorm.io/builder v0.3.9 // indirect
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
|
||||
)
|
||||
|
|
166
server/go.sum
166
server/go.sum
|
@ -39,6 +39,7 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
|
@ -85,6 +86,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
|
|||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
|
@ -98,6 +101,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
|
||||
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
|
||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
|
@ -144,8 +149,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
|||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gocarina/gocsv v0.0.0-20220712153207-8b2118da4570 h1:n4E8KiBgNvYdtjgJbAqKov2IFv7tDkULV/2Ld3wj5Hg=
|
||||
github.com/gocarina/gocsv v0.0.0-20220712153207-8b2118da4570/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||
github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k=
|
||||
github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
|
@ -187,8 +192,10 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -222,6 +229,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
|
|||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
|
@ -326,8 +335,9 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22
|
|||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
|
@ -372,9 +382,8 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
|
|||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
|
@ -390,8 +399,11 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
|
@ -436,6 +448,8 @@ github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY
|
|||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
|
@ -462,6 +476,11 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
|||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
||||
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
|
@ -542,6 +561,12 @@ github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkug
|
|||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.6.1 h1:ICBdtw803rmhLN3zfvyEGH3cwSmZv+kde7LhTDT659k=
|
||||
github.com/xuri/excelize/v2 v2.6.1/go.mod h1:tL+0m6DNwSXj/sILHbQTYsLi9IF4TW59H2EF3Yrx1AU=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -596,8 +621,9 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
|
||||
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -610,6 +636,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -679,8 +707,9 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
|||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -754,7 +783,6 @@ golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -765,8 +793,11 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -990,6 +1021,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -1004,37 +1036,119 @@ layeh.com/radius v0.0.0-20210819152912-ad72663a72ab h1:05KeMI4s7jEdIfHb7QCjUr5X2
|
|||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab/go.mod h1:pFWM9De99EY9TPVyHIyA56QmoRViVck/x41WFkUlc9A=
|
||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk=
|
||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
||||
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
|
||||
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
|
||||
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
|
||||
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
|
||||
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
|
||||
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
|
||||
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
|
||||
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
|
||||
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
|
||||
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
|
||||
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
|
||||
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
|
||||
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
|
||||
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
|
||||
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
|
||||
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
|
||||
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
|
||||
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
|
||||
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
|
||||
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
|
||||
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
|
||||
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
|
||||
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
|
||||
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
|
||||
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
|
||||
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
|
||||
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
|
||||
modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
|
||||
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
|
||||
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
|
||||
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8=
|
||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
||||
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
||||
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
||||
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
|
||||
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
|
||||
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
|
||||
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
|
||||
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
|
||||
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
|
||||
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
|
||||
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
|
||||
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
|
||||
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
|
||||
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
|
||||
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
|
||||
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
|
||||
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
|
||||
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
|
||||
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
|
||||
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
|
||||
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
|
||||
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
|
||||
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
|
||||
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
|
||||
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
|
||||
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
|
||||
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
|
||||
modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||
modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
|
||||
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E=
|
||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w=
|
||||
modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A=
|
||||
modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
|
||||
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
|
||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI=
|
||||
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc=
|
||||
xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/xorm v1.2.2 h1:FFBOEvJ++8fYBA9cywf2sxDVmFktl1SpJzTAG1ab06Y=
|
||||
xorm.io/xorm v1.2.2/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0=
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
|
||||
xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
||||
|
|
|
@ -66,9 +66,13 @@ func startDtls() {
|
|||
go func() {
|
||||
// time.Sleep(1 * time.Second)
|
||||
cc := conn.(*dtls.Conn)
|
||||
sessid := hex.EncodeToString(cc.ConnectionState().SessionID)
|
||||
sess := sessdata.Dtls2Sess(sessid)
|
||||
LinkDtls(conn, sess.CSess)
|
||||
did := hex.EncodeToString(cc.ConnectionState().SessionID)
|
||||
cSess := sessdata.Dtls2CSess(did)
|
||||
if cSess == nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
LinkDtls(conn, cSess)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,15 @@ import (
|
|||
var profileHash = ""
|
||||
|
||||
func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO 调试信息输出
|
||||
//hd, _ := httputil.DumpRequest(r, true)
|
||||
//base.Debug("DumpRequest: ", string(hd))
|
||||
|
||||
// 判断anyconnect客户端
|
||||
userAgent := strings.ToLower(r.UserAgent())
|
||||
xAggregateAuth := r.Header.Get("X-Aggregate-Auth")
|
||||
xTranscendVersion := r.Header.Get("X-Transcend-Version")
|
||||
if !((strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect")) &&
|
||||
if !((strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect") || strings.Contains(userAgent, "anylink")) &&
|
||||
xAggregateAuth == "1" && xTranscendVersion == "1") {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, "error request")
|
||||
|
@ -43,7 +47,6 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// fmt.Printf("%+v \n", cr)
|
||||
|
||||
setCommonHeader(w)
|
||||
if cr.Type == "logout" {
|
||||
// 退出删除session信息
|
||||
|
@ -56,7 +59,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if cr.Type == "init" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames()}
|
||||
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNamesNormal()}
|
||||
tplRequest(tpl_request, w, data)
|
||||
return
|
||||
}
|
||||
|
@ -66,16 +69,29 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 用户活动日志
|
||||
ua := dbdata.UserActLog{
|
||||
Username: cr.Auth.Username,
|
||||
GroupName: cr.GroupSelect,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
Status: dbdata.UserAuthSuccess,
|
||||
DeviceType: cr.DeviceId.DeviceType,
|
||||
PlatformVersion: cr.DeviceId.PlatformVersion,
|
||||
}
|
||||
// TODO 用户密码校验
|
||||
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
|
||||
if err != nil {
|
||||
base.Warn(err)
|
||||
ua.Info = err.Error()
|
||||
ua.Status = dbdata.UserAuthFail
|
||||
dbdata.UserActLogIns.Add(ua, userAgent)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
|
||||
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNamesNormal(), Error: "用户名或密码错误"}
|
||||
tplRequest(tpl_request, w, data)
|
||||
return
|
||||
}
|
||||
dbdata.UserActLogIns.Add(ua, userAgent)
|
||||
// if !ok {
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
// data := RequestData{Group: cr.GroupSelect, Groups: base.Cfg.UserGroups, Error: "请先激活用户"}
|
||||
|
@ -89,7 +105,12 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
sess.Group = cr.GroupSelect
|
||||
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
|
||||
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
|
||||
sess.UserAgent = userAgent
|
||||
sess.DeviceType = ua.DeviceType
|
||||
sess.PlatformVersion = ua.PlatformVersion
|
||||
sess.RemoteAddr = r.RemoteAddr
|
||||
// 获取客户端mac地址
|
||||
sess.UniqueMac = true
|
||||
macHw, err := net.ParseMAC(sess.MacAddr)
|
||||
if err != nil {
|
||||
var sum [16]byte
|
||||
|
@ -97,6 +118,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
sum = md5.Sum([]byte(sess.UniqueIdGlobal))
|
||||
} else {
|
||||
sum = md5.Sum([]byte(sess.Token))
|
||||
sess.UniqueMac = false
|
||||
}
|
||||
macHw = sum[0:5] // 5个byte
|
||||
macHw = append([]byte{0x02}, macHw...)
|
||||
|
@ -109,7 +131,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
Banner: other.Banner, ProfileHash: profileHash}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
tplRequest(tpl_complete, w, rd)
|
||||
base.Debug("login", cr.Auth.Username)
|
||||
base.Debug("login", cr.Auth.Username, userAgent)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -44,6 +44,7 @@ type macAddressList struct {
|
|||
|
||||
func setCommonHeader(w http.ResponseWriter) {
|
||||
// Content-Length Date 默认已经存在
|
||||
w.Header().Set("Server", "AnyLink")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-store,no-cache")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
"github.com/bjdgyc/anylink/pkg/utils"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
)
|
||||
|
@ -14,7 +15,7 @@ import (
|
|||
func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) {
|
||||
base.Debug("LinkCstp connect ip:", cSess.IpAddr, "user:", cSess.Username, "rip:", conn.RemoteAddr())
|
||||
defer func() {
|
||||
base.Debug("LinkCstp return", cSess.IpAddr)
|
||||
base.Debug("LinkCstp return", cSess.Username, cSess.IpAddr)
|
||||
_ = conn.Close()
|
||||
cSess.Close()
|
||||
}()
|
||||
|
@ -33,14 +34,14 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio
|
|||
// 设置超时限制
|
||||
err = conn.SetReadDeadline(utils.NowSec().Add(dead))
|
||||
if err != nil {
|
||||
base.Error("SetDeadline: ", err)
|
||||
base.Error("SetDeadline: ", cSess.Username, err)
|
||||
return
|
||||
}
|
||||
// hdata := make([]byte, BufferSize)
|
||||
pl := getPayload()
|
||||
n, err = bufRW.Read(pl.Data)
|
||||
if err != nil {
|
||||
base.Error("read hdata: ", err)
|
||||
base.Error("read hdata: ", cSess.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -55,7 +56,8 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio
|
|||
// do nothing
|
||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||
case 0x05: // DISCONNECT
|
||||
base.Debug("DISCONNECT", cSess.IpAddr)
|
||||
cSess.UserLogoutCode = dbdata.UserLogoutClient
|
||||
base.Debug("DISCONNECT", cSess.Username, cSess.IpAddr)
|
||||
return
|
||||
case 0x03: // DPD-REQ
|
||||
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
||||
|
@ -70,7 +72,7 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio
|
|||
dataLen = binary.BigEndian.Uint16(pl.Data[4:6]) // 4,5
|
||||
// 修复 cstp 数据长度溢出报错
|
||||
if 8+dataLen > BufferSize {
|
||||
base.Error("recv error dataLen", dataLen)
|
||||
base.Error("recv error dataLen", cSess.Username, dataLen)
|
||||
continue
|
||||
}
|
||||
// 去除数据头
|
||||
|
@ -87,7 +89,7 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio
|
|||
|
||||
func cstpWrite(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) {
|
||||
defer func() {
|
||||
base.Debug("cstpWrite return", cSess.IpAddr)
|
||||
base.Debug("cstpWrite return", cSess.Username, cSess.IpAddr)
|
||||
_ = conn.Close()
|
||||
cSess.Close()
|
||||
}()
|
||||
|
@ -128,7 +130,7 @@ func cstpWrite(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessi
|
|||
|
||||
n, err = conn.Write(pl.Data)
|
||||
if err != nil {
|
||||
base.Error("write err", err)
|
||||
base.Error("write err", cSess.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
"github.com/bjdgyc/anylink/pkg/utils"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
)
|
||||
|
@ -19,7 +20,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
}
|
||||
|
||||
defer func() {
|
||||
base.Debug("LinkDtls return", cSess.IpAddr)
|
||||
base.Debug("LinkDtls return", cSess.Username, cSess.IpAddr)
|
||||
_ = conn.Close()
|
||||
dSess.Close()
|
||||
}()
|
||||
|
@ -35,14 +36,14 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
for {
|
||||
err = conn.SetReadDeadline(utils.NowSec().Add(dead))
|
||||
if err != nil {
|
||||
base.Error("SetDeadline: ", err)
|
||||
base.Error("SetDeadline: ", cSess.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
pl := getPayload()
|
||||
n, err = conn.Read(pl.Data)
|
||||
if err != nil {
|
||||
base.Error("read hdata: ", err)
|
||||
base.Error("read hdata: ", cSess.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,8 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
// do nothing
|
||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||
case 0x05: // DISCONNECT
|
||||
base.Debug("DISCONNECT DTLS", cSess.IpAddr)
|
||||
cSess.UserLogoutCode = dbdata.UserLogoutClient
|
||||
base.Debug("DISCONNECT DTLS", cSess.Username, cSess.IpAddr)
|
||||
return
|
||||
case 0x03: // DPD-REQ
|
||||
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
||||
|
@ -83,7 +85,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
|
||||
func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnSession) {
|
||||
defer func() {
|
||||
base.Debug("dtlsWrite return", cSess.IpAddr)
|
||||
base.Debug("dtlsWrite return", cSess.Username, cSess.IpAddr)
|
||||
_ = conn.Close()
|
||||
dSess.Close()
|
||||
}()
|
||||
|
@ -120,7 +122,7 @@ func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnS
|
|||
}
|
||||
n, err := conn.Write(pl.Data)
|
||||
if err != nil {
|
||||
base.Error("write err", err)
|
||||
base.Error("write err", cSess.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ func LinkHome(w http.ResponseWriter, r *http.Request) {
|
|||
// fmt.Println(r.RemoteAddr)
|
||||
// hu, _ := httputil.DumpRequest(r, true)
|
||||
// fmt.Println("DumpHome: ", string(hu))
|
||||
w.Header().Set("Server", "AnyLink")
|
||||
connection := strings.ToLower(r.Header.Get("Connection"))
|
||||
userAgent := strings.ToLower(r.UserAgent())
|
||||
if connection == "close" && (strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect")) {
|
||||
|
@ -21,11 +22,13 @@ func LinkHome(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
index := &dbdata.SettingOther{}
|
||||
dbdata.SettingGet(index)
|
||||
if err := dbdata.SettingGet(index); err != nil {
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if index.Homeindex == "" {
|
||||
index.Homeindex = "AnyLink 是一个企业级远程办公 SSL VPN 软件,可以支持多人同时在线使用。"
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w, index.Homeindex)
|
||||
}
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ func allTapWrite(ifce LinkDriver, cSess *sessdata.ConnSession) {
|
|||
return
|
||||
}
|
||||
|
||||
putPayload(pl)
|
||||
putPayloadInBefore(cSess, pl)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/songgao/water"
|
||||
)
|
||||
|
||||
|
@ -26,6 +27,28 @@ func checkTun() {
|
|||
if err != nil {
|
||||
base.Fatal("testTun err: ", err)
|
||||
}
|
||||
//开启服务器转发
|
||||
if err := execCmd([]string{"sysctl -w net.ipv4.ip_forward=1"}); err != nil {
|
||||
base.Error(err)
|
||||
}
|
||||
if base.Cfg.IptablesNat {
|
||||
//添加NAT转发规则
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
base.Error(err)
|
||||
return
|
||||
}
|
||||
natRule := []string{"-s", base.Cfg.Ipv4CIDR, "-o", base.Cfg.Ipv4Master, "-j", "MASQUERADE"}
|
||||
forwardRule := []string{"-j", "ACCEPT"}
|
||||
if natExists, _ := ipt.Exists("nat", "POSTROUTING", natRule...); !natExists {
|
||||
ipt.Insert("nat", "POSTROUTING", 1, natRule...)
|
||||
}
|
||||
if forwardExists, _ := ipt.Exists("filter", "FORWARD", forwardRule...); !forwardExists {
|
||||
ipt.Insert("filter", "FORWARD", 1, forwardRule...)
|
||||
}
|
||||
base.Info(ipt.List("nat", "POSTROUTING"))
|
||||
base.Info(ipt.List("filter", "FORWARD"))
|
||||
}
|
||||
}
|
||||
|
||||
// 创建tun网卡
|
||||
|
@ -85,7 +108,7 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||
return
|
||||
}
|
||||
|
||||
putPayload(pl)
|
||||
putPayloadInBefore(cSess, pl)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,9 +34,8 @@ func HttpAddHeader(w http.ResponseWriter, key string, value string) {
|
|||
|
||||
func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO 调试信息输出
|
||||
// hd, _ := httputil.DumpRequest(r, true)
|
||||
// fmt.Println("DumpRequest: ", string(hd))
|
||||
// fmt.Println("LinkTunnel", r.RemoteAddr)
|
||||
//hd, _ := httputil.DumpRequest(r, true)
|
||||
//base.Debug("DumpRequest: ", string(hd))
|
||||
|
||||
// 判断session-token的值
|
||||
cookie, err := r.Cookie("webvpn")
|
||||
|
@ -69,6 +68,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
cSess.SetMtu(cstpMtu)
|
||||
cSess.MasterSecret = masterSecret
|
||||
cSess.RemoteAddr = r.RemoteAddr
|
||||
cSess.UserAgent = strings.ToLower(r.UserAgent())
|
||||
cSess.LocalIp = net.ParseIP(localIp)
|
||||
cstpKeepalive := base.Cfg.CstpKeepalive
|
||||
cstpDpd := base.Cfg.CstpDpd
|
||||
|
@ -125,7 +125,8 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
for _, v := range cSess.Group.RouteExclude {
|
||||
HttpAddHeader(w, "X-CSTP-Split-Exclude", v.IpMask)
|
||||
}
|
||||
HttpSetHeader(w, "X-CSTP-Lease-Duration", fmt.Sprintf("%d", base.Cfg.IpLease)) // ip地址租期
|
||||
|
||||
HttpSetHeader(w, "X-CSTP-Lease-Duration", "1209600") // ip地址租期
|
||||
HttpSetHeader(w, "X-CSTP-Session-Timeout", "none")
|
||||
HttpSetHeader(w, "X-CSTP-Session-Timeout-Alert-Interval", "60")
|
||||
HttpSetHeader(w, "X-CSTP-Session-Timeout-Remaining", "none")
|
||||
|
@ -134,8 +135,10 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
HttpSetHeader(w, "X-CSTP-Keep", "true")
|
||||
HttpSetHeader(w, "X-CSTP-Tunnel-All-DNS", "false")
|
||||
|
||||
HttpSetHeader(w, "X-CSTP-Rekey-Time", "172800")
|
||||
HttpSetHeader(w, "X-CSTP-Rekey-Time", "43200") // 172800
|
||||
HttpSetHeader(w, "X-CSTP-Rekey-Method", "new-tunnel")
|
||||
HttpSetHeader(w, "X-DTLS-Rekey-Time", "43200")
|
||||
HttpSetHeader(w, "X-DTLS-Rekey-Method", "new-tunnel")
|
||||
|
||||
HttpSetHeader(w, "X-CSTP-DPD", fmt.Sprintf("%d", cstpDpd))
|
||||
HttpSetHeader(w, "X-CSTP-Keepalive", fmt.Sprintf("%d", cstpKeepalive))
|
||||
|
@ -150,7 +153,6 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
HttpSetHeader(w, "X-DTLS-Port", dtlsPort)
|
||||
HttpSetHeader(w, "X-DTLS-DPD", fmt.Sprintf("%d", cstpDpd))
|
||||
HttpSetHeader(w, "X-DTLS-Keepalive", fmt.Sprintf("%d", cstpKeepalive))
|
||||
HttpSetHeader(w, "X-DTLS-Rekey-Time", "5400")
|
||||
HttpSetHeader(w, "X-DTLS12-CipherSuite", "ECDHE-ECDSA-AES128-GCM-SHA256")
|
||||
|
||||
HttpSetHeader(w, "X-CSTP-License", "accept")
|
||||
|
@ -194,6 +196,15 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
base.Error(err)
|
||||
return
|
||||
}
|
||||
dbdata.UserActLogIns.Add(dbdata.UserActLog{
|
||||
Username: sess.Username,
|
||||
GroupName: sess.Group,
|
||||
IpAddr: cSess.IpAddr.String(),
|
||||
RemoteAddr: cSess.RemoteAddr,
|
||||
DeviceType: sess.DeviceType,
|
||||
PlatformVersion: sess.PlatformVersion,
|
||||
Status: dbdata.UserConnected,
|
||||
}, cSess.UserAgent)
|
||||
|
||||
go LinkCstp(conn, bufRW, cSess)
|
||||
}
|
||||
|
|
|
@ -15,11 +15,6 @@ func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
|
|||
// 校验不通过直接丢弃
|
||||
return false
|
||||
}
|
||||
if base.Cfg.AuditInterval >= 0 {
|
||||
cSess.IpAuditPool.JobQueue <- func() {
|
||||
logAudit(cSess, pl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closed := false
|
||||
|
@ -32,6 +27,15 @@ func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
|
|||
return closed
|
||||
}
|
||||
|
||||
func putPayloadInBefore(cSess *sessdata.ConnSession, pl *sessdata.Payload) {
|
||||
// 异步审计日志
|
||||
if base.Cfg.AuditInterval >= 0 {
|
||||
auditPayload.Add(cSess.Username, pl)
|
||||
return
|
||||
}
|
||||
putPayload(pl)
|
||||
}
|
||||
|
||||
func payloadOut(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
|
||||
dSess := cSess.GetDtlsSession()
|
||||
if dSess == nil {
|
||||
|
|
|
@ -3,13 +3,13 @@ package handler
|
|||
import (
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
"github.com/bjdgyc/anylink/pkg/utils"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
"github.com/ivpusic/grpool"
|
||||
"github.com/songgao/water/waterutil"
|
||||
)
|
||||
|
||||
|
@ -20,73 +20,92 @@ const (
|
|||
acc_proto_http
|
||||
)
|
||||
|
||||
// 保存批量的审计日志
|
||||
var (
|
||||
auditPayload *AuditPayload
|
||||
logBatch *LogBatch
|
||||
)
|
||||
|
||||
// 分析审计日志
|
||||
type AuditPayload struct {
|
||||
Pool *grpool.Pool
|
||||
IpAuditMap utils.IMaps
|
||||
}
|
||||
|
||||
// 保存审计日志
|
||||
type LogBatch struct {
|
||||
Logs []dbdata.AccessAudit
|
||||
Logs []dbdata.AccessAudit
|
||||
LogChan chan dbdata.AccessAudit
|
||||
}
|
||||
|
||||
// 日志池
|
||||
type LogSink struct {
|
||||
logChan chan dbdata.AccessAudit
|
||||
autoCommitChan chan *LogBatch // 超时通知
|
||||
// 异步写入pool
|
||||
func (p *AuditPayload) Add(userName string, pl *sessdata.Payload) {
|
||||
select {
|
||||
case p.Pool.JobQueue <- func() {
|
||||
logAudit(userName, pl)
|
||||
}:
|
||||
default:
|
||||
putPayload(pl)
|
||||
base.Error("AccessAudit: AuditPayload channel is full")
|
||||
}
|
||||
}
|
||||
|
||||
var logAuditSink *LogSink
|
||||
|
||||
// 写入日志通道
|
||||
func logAuditWrite(aa dbdata.AccessAudit) {
|
||||
logAuditSink.logChan <- aa
|
||||
// 数据落盘
|
||||
func (l *LogBatch) Write() {
|
||||
if len(l.Logs) == 0 {
|
||||
return
|
||||
}
|
||||
_ = dbdata.AddBatch(l.Logs)
|
||||
l.Reset()
|
||||
}
|
||||
|
||||
// 批量写入数据表
|
||||
// 清空数据
|
||||
func (l *LogBatch) Reset() {
|
||||
l.Logs = []dbdata.AccessAudit{}
|
||||
}
|
||||
|
||||
// 开启批量写入数据功能
|
||||
func logAuditBatch() {
|
||||
if base.Cfg.AuditInterval < 0 {
|
||||
return
|
||||
}
|
||||
logAuditSink = &LogSink{
|
||||
logChan: make(chan dbdata.AccessAudit, 1000),
|
||||
autoCommitChan: make(chan *LogBatch, 10),
|
||||
auditPayload = &AuditPayload{
|
||||
Pool: grpool.NewPool(10, 10240),
|
||||
IpAuditMap: utils.NewMap("cmap", 0),
|
||||
}
|
||||
logBatch = &LogBatch{
|
||||
LogChan: make(chan dbdata.AccessAudit, 10240),
|
||||
}
|
||||
var (
|
||||
limit = 100 // 超过上限批量写入数据表
|
||||
logAudit dbdata.AccessAudit
|
||||
logBatch *LogBatch
|
||||
commitTimer *time.Timer // 超时自动提交
|
||||
timeOutBatch *LogBatch
|
||||
limit = 100 // 超过上限批量写入数据表
|
||||
outTime = time.NewTimer(time.Second)
|
||||
accessAudit = dbdata.AccessAudit{}
|
||||
)
|
||||
|
||||
for {
|
||||
// 重置超时 时间
|
||||
outTime.Reset(time.Second * 1)
|
||||
select {
|
||||
case logAudit = <-logAuditSink.logChan:
|
||||
if logBatch == nil {
|
||||
logBatch = &LogBatch{}
|
||||
commitTimer = time.AfterFunc(
|
||||
1*time.Second, func(logBatch *LogBatch) func() {
|
||||
return func() {
|
||||
logAuditSink.autoCommitChan <- logBatch
|
||||
}
|
||||
}(logBatch),
|
||||
)
|
||||
}
|
||||
logBatch.Logs = append(logBatch.Logs, logAudit)
|
||||
case accessAudit = <-logBatch.LogChan:
|
||||
logBatch.Logs = append(logBatch.Logs, accessAudit)
|
||||
if len(logBatch.Logs) >= limit {
|
||||
commitTimer.Stop()
|
||||
_ = dbdata.AddBatch(logBatch.Logs)
|
||||
logBatch = nil
|
||||
if !outTime.Stop() {
|
||||
<-outTime.C
|
||||
}
|
||||
logBatch.Write()
|
||||
}
|
||||
case timeOutBatch = <-logAuditSink.autoCommitChan:
|
||||
if timeOutBatch != logBatch {
|
||||
continue
|
||||
}
|
||||
if logBatch != nil {
|
||||
_ = dbdata.AddBatch(logBatch.Logs)
|
||||
}
|
||||
logBatch = nil
|
||||
case <-outTime.C:
|
||||
logBatch.Write()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析IP包的数据
|
||||
func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) {
|
||||
func logAudit(userName string, pl *sessdata.Payload) {
|
||||
defer putPayload(pl)
|
||||
|
||||
if !(pl.LType == sessdata.LTypeIPData && pl.PType == 0x00) {
|
||||
return
|
||||
}
|
||||
ipProto := waterutil.IPv4Protocol(pl.Data)
|
||||
// 访问协议
|
||||
var accessProto uint8
|
||||
|
@ -109,79 +128,48 @@ func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) {
|
|||
copy(key[:16], ipSrc)
|
||||
copy(key[16:32], ipDst)
|
||||
binary.BigEndian.PutUint16(key[32:34], ipPort)
|
||||
key[34] = byte(accessProto)
|
||||
copy(key[35:51], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
|
||||
info := ""
|
||||
nu := utils.NowSec().Unix()
|
||||
if ipProto == waterutil.TCP {
|
||||
plData := waterutil.IPv4Payload(pl.Data)
|
||||
if len(plData) < 14 {
|
||||
tcpPlData := waterutil.IPv4Payload(pl.Data)
|
||||
// 24 (ACK PSH)
|
||||
if len(tcpPlData) < 14 || tcpPlData[13] != 24 {
|
||||
return
|
||||
}
|
||||
flags := plData[13]
|
||||
switch flags {
|
||||
case flags & 0x20:
|
||||
// URG
|
||||
return
|
||||
case flags & 0x14:
|
||||
// RST ACK
|
||||
return
|
||||
case flags & 0x12:
|
||||
// SYN ACK
|
||||
return
|
||||
case flags & 0x11:
|
||||
// Client FIN
|
||||
return
|
||||
case flags & 0x10:
|
||||
// ACK
|
||||
return
|
||||
case flags & 0x08:
|
||||
// PSH
|
||||
return
|
||||
case flags & 0x04:
|
||||
// RST
|
||||
return
|
||||
case flags & 0x02:
|
||||
// SYN
|
||||
return
|
||||
case flags & 0x01:
|
||||
// FIN
|
||||
return
|
||||
case flags & 0x18:
|
||||
// PSH ACK
|
||||
accessProto, info = onTCP(plData)
|
||||
accessProto, info = onTCP(tcpPlData)
|
||||
// HTTPS or HTTP
|
||||
if accessProto != acc_proto_tcp {
|
||||
// 提前存储只含ip数据的key, 避免即记录域名又记录一笔IP数据的记录
|
||||
ipKey := make([]byte, 51)
|
||||
copy(ipKey, key)
|
||||
ipS := utils.BytesToString(ipKey)
|
||||
auditPayload.IpAuditMap.Set(ipS, nu)
|
||||
|
||||
key[34] = byte(accessProto)
|
||||
// 存储含域名的key
|
||||
if info != "" {
|
||||
// 提前存储只含ip数据的key, 避免即记录域名又记录一笔IP数据的记录
|
||||
ipKey := make([]byte, 51)
|
||||
copy(ipKey, key)
|
||||
ipS := utils.BytesToString(ipKey)
|
||||
cSess.IpAuditMap.Set(ipS, nu)
|
||||
// 存储含域名的key
|
||||
key[34] = byte(accessProto)
|
||||
md5Sum := md5.Sum([]byte(info))
|
||||
copy(key[35:51], hex.EncodeToString(md5Sum[:]))
|
||||
copy(key[35:51], md5Sum[:])
|
||||
}
|
||||
case flags & 0x19:
|
||||
// URG
|
||||
return
|
||||
case flags & 0xC2:
|
||||
// SYN-ECE-CWR
|
||||
return
|
||||
}
|
||||
}
|
||||
s := utils.BytesToString(key)
|
||||
|
||||
// 判断已经存在,并且没有过期
|
||||
v, ok := cSess.IpAuditMap.Get(s)
|
||||
v, ok := auditPayload.IpAuditMap.Get(s)
|
||||
if ok && nu-v.(int64) < int64(base.Cfg.AuditInterval) {
|
||||
// 回收byte对象
|
||||
putByte51(b)
|
||||
return
|
||||
}
|
||||
|
||||
cSess.IpAuditMap.Set(s, nu)
|
||||
auditPayload.IpAuditMap.Set(s, nu)
|
||||
|
||||
audit := dbdata.AccessAudit{
|
||||
Username: cSess.Username,
|
||||
Username: userName,
|
||||
Protocol: uint8(ipProto),
|
||||
Src: ipSrc.String(),
|
||||
Dst: ipDst.String(),
|
||||
|
@ -190,5 +178,11 @@ func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) {
|
|||
AccessProto: accessProto,
|
||||
Info: info,
|
||||
}
|
||||
logAuditWrite(audit)
|
||||
|
||||
select {
|
||||
case logBatch.LogChan <- audit:
|
||||
default:
|
||||
base.Error("AccessAudit: LogChan channel is full")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func onTCP(payload []byte) (uint8, string) {
|
|||
}
|
||||
data := payload[ihl:]
|
||||
for _, parser := range tcpParsers {
|
||||
if proto, info := parser(data); info != "" {
|
||||
if proto, info := parser(data); proto != acc_proto_tcp {
|
||||
return proto, info
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,7 @@ func onTCP(payload []byte) (uint8, string) {
|
|||
}
|
||||
|
||||
func sniNewParser(b []byte) (uint8, string) {
|
||||
dataSize := len(b)
|
||||
if dataSize < 2 || b[0] != 0x16 || b[1] != 0x03 {
|
||||
if len(b) < 2 || b[0] != 0x16 || b[1] != 0x03 {
|
||||
return acc_proto_tcp, ""
|
||||
}
|
||||
rest := b[5:]
|
||||
|
@ -51,27 +50,27 @@ func sniNewParser(b []byte) (uint8, string) {
|
|||
// Skip over random number
|
||||
current += 4 + 28
|
||||
if current >= restLen {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
// Skip over session ID
|
||||
sessionIDLength := int(rest[current])
|
||||
current += 1
|
||||
current += sessionIDLength
|
||||
if current >= restLen {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
cipherSuiteLength := (int(rest[current]) << 8) + int(rest[current+1])
|
||||
current += 2
|
||||
current += cipherSuiteLength
|
||||
if current >= restLen {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
compressionMethodLength := int(rest[current])
|
||||
current += 1
|
||||
current += compressionMethodLength
|
||||
|
||||
if current >= restLen {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
current += 2
|
||||
hostname := ""
|
||||
|
@ -84,27 +83,30 @@ func sniNewParser(b []byte) (uint8, string) {
|
|||
// Skip over number of names as we're assuming there's just one
|
||||
current += 2
|
||||
if current >= restLen {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
nameType := rest[current]
|
||||
current += 1
|
||||
if nameType != 0 {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
if current+1 >= restLen {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
nameLen := (int(rest[current]) << 8) + int(rest[current+1])
|
||||
current += 2
|
||||
if current+nameLen >= restLen {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
hostname = string(rest[current : current+nameLen])
|
||||
}
|
||||
current += extensionDataLength
|
||||
}
|
||||
if hostname == "" {
|
||||
return acc_proto_tcp, ""
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
if !validDomainChar(hostname) {
|
||||
return acc_proto_https, ""
|
||||
}
|
||||
return acc_proto_https, hostname
|
||||
}
|
||||
|
@ -150,8 +152,7 @@ func httpNewParser(data []byte) (uint8, string) {
|
|||
}
|
||||
|
||||
func sniParser(data []byte) (uint8, string) {
|
||||
dataSize := len(data)
|
||||
if dataSize < 2 || data[0] != 0x16 || data[1] != 0x03 {
|
||||
if len(data) < 2 || data[0] != 0x16 || data[1] != 0x03 {
|
||||
return acc_proto_tcp, ""
|
||||
}
|
||||
sniRe := regexp.MustCompile("\x00\x00.{4}\x00.{2}([a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,6})\x00")
|
||||
|
@ -169,3 +170,15 @@ func httpParser(data []byte) (uint8, string) {
|
|||
}
|
||||
return acc_proto_tcp, ""
|
||||
}
|
||||
|
||||
// 校验域名的合法字符, 处理乱码问题
|
||||
func validDomainChar(addr string) bool {
|
||||
// Allow a-z A-Z . - 0-9
|
||||
for i := 0; i < len(addr); i++ {
|
||||
c := addr[i]
|
||||
if !((c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 45 && c <= 46) || (c >= 48 && c <= 57)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -51,22 +51,26 @@ func BenchmarkNewHttpParser(b *testing.B) {
|
|||
func TestNewSniParser(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
data := handlerTcpPayload(httpsPacket)
|
||||
_, sni := sniNewParser(data)
|
||||
proto, sni := sniNewParser(data)
|
||||
ast.Equal(sni, httpsSni)
|
||||
ast.Equal(int(proto), acc_proto_https)
|
||||
}
|
||||
|
||||
func TestNewHttpParser(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
// Host
|
||||
data := handlerTcpPayload(httpPacket)
|
||||
_, hostname := httpNewParser(data)
|
||||
proto, hostname := httpNewParser(data)
|
||||
ast.Equal(hostname, httpHost)
|
||||
ast.Equal(int(proto), acc_proto_http)
|
||||
// HOST
|
||||
data = handlerTcpPayload(httpPacket2)
|
||||
_, hostname = httpNewParser(data)
|
||||
proto, hostname = httpNewParser(data)
|
||||
ast.Equal(hostname, httpHost)
|
||||
ast.Equal(int(proto), acc_proto_http)
|
||||
// GET http://www.google.com/index.html HTTP/1.1
|
||||
data = handlerTcpPayload(httpPacket3)
|
||||
_, hostname = httpNewParser(data)
|
||||
proto, hostname = httpNewParser(data)
|
||||
ast.Equal(hostname, httpHost)
|
||||
ast.Equal(int(proto), acc_proto_http)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package handler
|
|||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -10,8 +11,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/pkg/proxyproto"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
func startTls() {
|
||||
|
@ -63,7 +64,10 @@ func startTls() {
|
|||
defer ln.Close()
|
||||
|
||||
if base.Cfg.ProxyProtocol {
|
||||
ln = &proxyproto.Listener{Listener: ln, ProxyHeaderTimeout: time.Second * 5}
|
||||
ln = &proxyproto.Listener{
|
||||
Listener: ln,
|
||||
ReadHeaderTimeout: 40 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
base.Info("listen server", addr)
|
||||
|
@ -88,6 +92,10 @@ func initRoute() http.Handler {
|
|||
http.FileServer(http.Dir(base.Cfg.FilesPath)),
|
||||
),
|
||||
)
|
||||
// 健康检测
|
||||
r.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "ok")
|
||||
}).Methods(http.MethodGet)
|
||||
r.NotFoundHandler = http.HandlerFunc(notFound)
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// AnyLink 是一个企业级远程办公vpn软件,可以支持多人同时在线使用。
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package main
|
||||
|
|
|
@ -1,290 +0,0 @@
|
|||
// copy from: https://github.com/armon/go-proxyproto/blob/master/protocol.go
|
||||
// design: http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt
|
||||
|
||||
// HAProxy proxy proto v1
|
||||
package proxyproto
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// prefix is the string we look for at the start of a connection
|
||||
// to check if this connection is using the proxy protocol
|
||||
prefix = []byte("PROXY ")
|
||||
prefixLen = len(prefix)
|
||||
|
||||
ErrInvalidUpstream = errors.New("upstream connection address not trusted for PROXY information")
|
||||
)
|
||||
|
||||
// SourceChecker can be used to decide whether to trust the PROXY info or pass
|
||||
// the original connection address through. If set, the connecting address is
|
||||
// passed in as an argument. If the function returns an error due to the source
|
||||
// being disallowed, it should return ErrInvalidUpstream.
|
||||
//
|
||||
// If error is not nil, the call to Accept() will fail. If the reason for
|
||||
// triggering this failure is due to a disallowed source, it should return
|
||||
// ErrInvalidUpstream.
|
||||
//
|
||||
// If bool is true, the PROXY-set address is used.
|
||||
//
|
||||
// If bool is false, the connection's remote address is used, rather than the
|
||||
// address claimed in the PROXY info.
|
||||
type SourceChecker func(net.Addr) (bool, error)
|
||||
|
||||
// Listener is used to wrap an underlying listener,
|
||||
// whose connections may be using the HAProxy Proxy Protocol (version 1).
|
||||
// If the connection is using the protocol, the RemoteAddr() will return
|
||||
// the correct client address.
|
||||
//
|
||||
// Optionally define ProxyHeaderTimeout to set a maximum time to
|
||||
// receive the Proxy Protocol Header. Zero means no timeout.
|
||||
type Listener struct {
|
||||
Listener net.Listener
|
||||
ProxyHeaderTimeout time.Duration
|
||||
SourceCheck SourceChecker
|
||||
UnknownOK bool // allow PROXY UNKNOWN
|
||||
}
|
||||
|
||||
// Conn is used to wrap and underlying connection which
|
||||
// may be speaking the Proxy Protocol. If it is, the RemoteAddr() will
|
||||
// return the address of the client instead of the proxy address.
|
||||
type Conn struct {
|
||||
bufReader *bufio.Reader
|
||||
conn net.Conn
|
||||
dstAddr *net.TCPAddr
|
||||
srcAddr *net.TCPAddr
|
||||
useConnAddr bool
|
||||
once sync.Once
|
||||
proxyHeaderTimeout time.Duration
|
||||
unknownOK bool
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener.
|
||||
func (p *Listener) Accept() (net.Conn, error) {
|
||||
// Get the underlying connection
|
||||
conn, err := p.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var useConnAddr bool
|
||||
if p.SourceCheck != nil {
|
||||
allowed, err := p.SourceCheck(conn.RemoteAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !allowed {
|
||||
useConnAddr = true
|
||||
}
|
||||
}
|
||||
newConn := NewConn(conn, p.ProxyHeaderTimeout)
|
||||
newConn.useConnAddr = useConnAddr
|
||||
newConn.unknownOK = p.UnknownOK
|
||||
return newConn, nil
|
||||
}
|
||||
|
||||
// Close closes the underlying listener.
|
||||
func (p *Listener) Close() error {
|
||||
return p.Listener.Close()
|
||||
}
|
||||
|
||||
// Addr returns the underlying listener's network address.
|
||||
func (p *Listener) Addr() net.Addr {
|
||||
return p.Listener.Addr()
|
||||
}
|
||||
|
||||
// NewConn is used to wrap a net.Conn that may be speaking
|
||||
// the proxy protocol into a proxyproto.Conn
|
||||
func NewConn(conn net.Conn, timeout time.Duration) *Conn {
|
||||
pConn := &Conn{
|
||||
bufReader: bufio.NewReader(conn),
|
||||
conn: conn,
|
||||
proxyHeaderTimeout: timeout,
|
||||
}
|
||||
return pConn
|
||||
}
|
||||
|
||||
// Read is check for the proxy protocol header when doing
|
||||
// the initial scan. If there is an error parsing the header,
|
||||
// it is returned and the socket is closed.
|
||||
func (p *Conn) Read(b []byte) (int, error) {
|
||||
var err error
|
||||
p.once.Do(func() { err = p.checkPrefix() })
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.bufReader.Read(b)
|
||||
}
|
||||
|
||||
func (p *Conn) ReadFrom(r io.Reader) (int64, error) {
|
||||
if rf, ok := p.conn.(io.ReaderFrom); ok {
|
||||
return rf.ReadFrom(r)
|
||||
}
|
||||
return io.Copy(p.conn, r)
|
||||
}
|
||||
|
||||
func (p *Conn) WriteTo(w io.Writer) (int64, error) {
|
||||
var err error
|
||||
p.once.Do(func() { err = p.checkPrefix() })
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.bufReader.WriteTo(w)
|
||||
}
|
||||
|
||||
func (p *Conn) Write(b []byte) (int, error) {
|
||||
return p.conn.Write(b)
|
||||
}
|
||||
|
||||
func (p *Conn) Close() error {
|
||||
return p.conn.Close()
|
||||
}
|
||||
|
||||
func (p *Conn) LocalAddr() net.Addr {
|
||||
p.checkPrefixOnce()
|
||||
if p.dstAddr != nil && !p.useConnAddr {
|
||||
return p.dstAddr
|
||||
}
|
||||
return p.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the address of the client if the proxy
|
||||
// protocol is being used, otherwise just returns the address of
|
||||
// the socket peer. If there is an error parsing the header, the
|
||||
// address of the client is not returned, and the socket is closed.
|
||||
// Once implication of this is that the call could block if the
|
||||
// client is slow. Using a Deadline is recommended if this is called
|
||||
// before Read()
|
||||
func (p *Conn) RemoteAddr() net.Addr {
|
||||
p.checkPrefixOnce()
|
||||
if p.srcAddr != nil && !p.useConnAddr {
|
||||
return p.srcAddr
|
||||
}
|
||||
return p.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (p *Conn) SetDeadline(t time.Time) error {
|
||||
return p.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (p *Conn) SetReadDeadline(t time.Time) error {
|
||||
return p.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (p *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return p.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (p *Conn) checkPrefixOnce() {
|
||||
p.once.Do(func() {
|
||||
if err := p.checkPrefix(); err != nil && err != io.EOF {
|
||||
log.Printf("[ERR] Failed to read proxy prefix: %v", err)
|
||||
p.Close()
|
||||
p.bufReader = bufio.NewReader(p.conn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Conn) checkPrefix() error {
|
||||
if p.proxyHeaderTimeout != 0 {
|
||||
readDeadLine := time.Now().Add(p.proxyHeaderTimeout)
|
||||
_ = p.conn.SetReadDeadline(readDeadLine)
|
||||
defer func() {
|
||||
_ = p.conn.SetReadDeadline(time.Time{})
|
||||
}()
|
||||
}
|
||||
|
||||
// Incrementally check each byte of the prefix
|
||||
for i := 1; i <= prefixLen; i++ {
|
||||
inp, err := p.bufReader.Peek(i)
|
||||
|
||||
if err != nil {
|
||||
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a prefix mis-match, quit early
|
||||
if !bytes.Equal(inp, prefix[:i]) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Read the header line
|
||||
header, err := p.bufReader.ReadString('\n')
|
||||
if err != nil {
|
||||
p.conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Strip the carriage return and new line
|
||||
header = header[:len(header)-2]
|
||||
|
||||
// Split on spaces, should be (PROXY <type> <src addr> <dst addr> <src port> <dst port>)
|
||||
parts := strings.Split(header, " ")
|
||||
if len(parts) < 2 {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid header line: %s", header)
|
||||
}
|
||||
|
||||
// Verify the type is known
|
||||
switch parts[1] {
|
||||
case "UNKNOWN":
|
||||
if !p.unknownOK || len(parts) != 2 {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid UNKNOWN header line: %s", header)
|
||||
}
|
||||
p.useConnAddr = true
|
||||
return nil
|
||||
case "TCP4":
|
||||
case "TCP6":
|
||||
default:
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Unhandled address type: %s", parts[1])
|
||||
}
|
||||
|
||||
if len(parts) != 6 {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid header line: %s", header)
|
||||
}
|
||||
|
||||
// Parse out the source address
|
||||
ip := net.ParseIP(parts[2])
|
||||
if ip == nil {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid source ip: %s", parts[2])
|
||||
}
|
||||
port, err := strconv.Atoi(parts[4])
|
||||
if err != nil {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid source port: %s", parts[4])
|
||||
}
|
||||
p.srcAddr = &net.TCPAddr{IP: ip, Port: port}
|
||||
|
||||
// Parse out the destination address
|
||||
ip = net.ParseIP(parts[3])
|
||||
if ip == nil {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid destination ip: %s", parts[3])
|
||||
}
|
||||
port, err = strconv.Atoi(parts[5])
|
||||
if err != nil {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid destination port: %s", parts[5])
|
||||
}
|
||||
p.dstAddr = &net.TCPAddr{IP: ip, Port: port}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,486 +0,0 @@
|
|||
// copy from: https://github.com/armon/go-proxyproto/blob/master/protocol_test.go
|
||||
package proxyproto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
goodAddr = "127.0.0.1"
|
||||
badAddr = "127.0.0.2"
|
||||
errAddr = "9999.0.0.2"
|
||||
)
|
||||
|
||||
var (
|
||||
checkAddr string
|
||||
)
|
||||
|
||||
func TestPassthrough(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
pl := &Listener{Listener: l}
|
||||
|
||||
go func() {
|
||||
conn, err := net.Dial("tcp", pl.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.Write([]byte("ping"))
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("pong")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := pl.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("ping")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
|
||||
if _, err := conn.Write([]byte("pong")); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
clientWriteDelay := 200 * time.Millisecond
|
||||
proxyHeaderTimeout := 50 * time.Millisecond
|
||||
pl := &Listener{Listener: l, ProxyHeaderTimeout: proxyHeaderTimeout}
|
||||
|
||||
go func() {
|
||||
conn, err := net.Dial("tcp", pl.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Do not send data for a while
|
||||
time.Sleep(clientWriteDelay)
|
||||
|
||||
conn.Write([]byte("ping"))
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("pong")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := pl.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Check the remote addr is the original 127.0.0.1
|
||||
remoteAddrStartTime := time.Now()
|
||||
addr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
if addr.IP.String() != "127.0.0.1" {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
remoteAddrDuration := time.Since(remoteAddrStartTime)
|
||||
|
||||
// Check RemoteAddr() call did timeout
|
||||
if remoteAddrDuration >= clientWriteDelay {
|
||||
t.Fatalf("RemoteAddr() took longer than the specified timeout: %v < %v", proxyHeaderTimeout, remoteAddrDuration)
|
||||
}
|
||||
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("ping")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
|
||||
if _, err := conn.Write([]byte("pong")); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_ipv4(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
pl := &Listener{Listener: l}
|
||||
|
||||
go func() {
|
||||
conn, err := net.Dial("tcp", pl.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Write out the header!
|
||||
header := "PROXY TCP4 10.1.1.1 20.2.2.2 1000 2000\r\n"
|
||||
conn.Write([]byte(header))
|
||||
|
||||
conn.Write([]byte("ping"))
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("pong")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := pl.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("ping")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
|
||||
if _, err := conn.Write([]byte("pong")); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check the remote addr
|
||||
addr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
if addr.IP.String() != "10.1.1.1" {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
if addr.Port != 1000 {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_ipv6(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
pl := &Listener{Listener: l}
|
||||
|
||||
go func() {
|
||||
conn, err := net.Dial("tcp", pl.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Write out the header!
|
||||
header := "PROXY TCP6 ffff::ffff ffff::ffff 1000 2000\r\n"
|
||||
conn.Write([]byte(header))
|
||||
|
||||
conn.Write([]byte("ping"))
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("pong")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := pl.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("ping")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
|
||||
if _, err := conn.Write([]byte("pong")); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check the remote addr
|
||||
addr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
if addr.IP.String() != "ffff::ffff" {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
if addr.Port != 1000 {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_Unknown(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
pl := &Listener{Listener: l, UnknownOK: true}
|
||||
|
||||
go func() {
|
||||
conn, err := net.Dial("tcp", pl.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Write out the header!
|
||||
header := "PROXY UNKNOWN\r\n"
|
||||
conn.Write([]byte(header))
|
||||
|
||||
conn.Write([]byte("ping"))
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("pong")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := pl.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("ping")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
|
||||
if _, err := conn.Write([]byte("pong")); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParse_BadHeader(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
pl := &Listener{Listener: l}
|
||||
|
||||
go func() {
|
||||
conn, err := net.Dial("tcp", pl.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Write out the header!
|
||||
header := "PROXY TCP4 what 127.0.0.1 1000 2000\r\n"
|
||||
conn.Write([]byte(header))
|
||||
|
||||
conn.Write([]byte("ping"))
|
||||
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err == nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := pl.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Check the remote addr, should be the local addr
|
||||
addr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
if addr.IP.String() != "127.0.0.1" {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
|
||||
// Read should fail
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err == nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_ipv4_checkfunc(t *testing.T) {
|
||||
checkAddr = goodAddr
|
||||
testParse_ipv4_checkfunc(t)
|
||||
checkAddr = badAddr
|
||||
testParse_ipv4_checkfunc(t)
|
||||
checkAddr = errAddr
|
||||
testParse_ipv4_checkfunc(t)
|
||||
}
|
||||
|
||||
func testParse_ipv4_checkfunc(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
checkFunc := func(addr net.Addr) (bool, error) {
|
||||
tcpAddr := addr.(*net.TCPAddr)
|
||||
if tcpAddr.IP.String() == checkAddr {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pl := &Listener{Listener: l, SourceCheck: checkFunc}
|
||||
|
||||
go func() {
|
||||
conn, err := net.Dial("tcp", pl.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Write out the header!
|
||||
header := "PROXY TCP4 10.1.1.1 20.2.2.2 1000 2000\r\n"
|
||||
conn.Write([]byte(header))
|
||||
|
||||
conn.Write([]byte("ping"))
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("pong")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := pl.Accept()
|
||||
if err != nil {
|
||||
if checkAddr == badAddr {
|
||||
return
|
||||
}
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
recv := make([]byte, 4)
|
||||
_, err = conn.Read(recv)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !bytes.Equal(recv, []byte("ping")) {
|
||||
t.Fatalf("bad: %v", recv)
|
||||
}
|
||||
|
||||
if _, err := conn.Write([]byte("pong")); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check the remote addr
|
||||
addr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
switch checkAddr {
|
||||
case goodAddr:
|
||||
if addr.IP.String() != "10.1.1.1" {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
if addr.Port != 1000 {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
case badAddr:
|
||||
if addr.IP.String() != "127.0.0.1" {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
if addr.Port == 1000 {
|
||||
t.Fatalf("bad: %v", addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testConn struct {
|
||||
readFromCalledWith io.Reader
|
||||
net.Conn // nil; crash on any unexpected use
|
||||
}
|
||||
|
||||
func (c *testConn) ReadFrom(r io.Reader) (int64, error) {
|
||||
c.readFromCalledWith = r
|
||||
return 0, nil
|
||||
}
|
||||
func (c *testConn) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
func (c *testConn) Read(p []byte) (int, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func TestCopyToWrappedConnection(t *testing.T) {
|
||||
innerConn := &testConn{}
|
||||
wrappedConn := NewConn(innerConn, 0)
|
||||
dummySrc := &testConn{}
|
||||
|
||||
io.Copy(wrappedConn, dummySrc)
|
||||
if innerConn.readFromCalledWith != dummySrc {
|
||||
t.Error("Expected io.Copy to delegate to ReadFrom function of inner destination connection")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromWrappedConnection(t *testing.T) {
|
||||
wrappedConn := NewConn(&testConn{}, 0)
|
||||
dummyDst := &testConn{}
|
||||
|
||||
io.Copy(dummyDst, wrappedConn)
|
||||
if dummyDst.readFromCalledWith != wrappedConn.conn {
|
||||
t.Errorf("Expected io.Copy to pass inner source connection to ReadFrom method of destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromWrappedConnectionToWrappedConnection(t *testing.T) {
|
||||
innerConn1 := &testConn{}
|
||||
wrappedConn1 := NewConn(innerConn1, 0)
|
||||
innerConn2 := &testConn{}
|
||||
wrappedConn2 := NewConn(innerConn2, 0)
|
||||
|
||||
io.Copy(wrappedConn1, wrappedConn2)
|
||||
if innerConn1.readFromCalledWith != innerConn2 {
|
||||
t.Errorf("Expected io.Copy to pass inner source connection to ReadFrom of inner destination connection")
|
||||
}
|
||||
|
||||
}
|
|
@ -64,7 +64,8 @@ func getIpLease() {
|
|||
xdb := dbdata.GetXdb()
|
||||
keepIpMaps := []dbdata.IpMap{}
|
||||
sNow := time.Now().Add(-1 * time.Duration(base.Cfg.IpLease) * time.Second)
|
||||
err := xdb.Cols("ip_addr").Where("keep=?", true).Or("last_login>?", sNow).Find(&keepIpMaps)
|
||||
err := xdb.Cols("ip_addr").Where("keep=?", true).
|
||||
Or("unique_mac=? and last_login>?", true, sNow).Find(&keepIpMaps)
|
||||
if err != nil {
|
||||
base.Error(err)
|
||||
}
|
||||
|
@ -78,33 +79,71 @@ func getIpLease() {
|
|||
}
|
||||
|
||||
// AcquireIp 获取动态ip
|
||||
func AcquireIp(username, macAddr string) net.IP {
|
||||
func AcquireIp(username, macAddr string, uniqueMac bool) net.IP {
|
||||
ipPoolMux.Lock()
|
||||
defer ipPoolMux.Unlock()
|
||||
|
||||
tNow := time.Now()
|
||||
var (
|
||||
err error
|
||||
tNow = time.Now()
|
||||
sNow = time.Now().Add(-1 * time.Duration(base.Cfg.IpLease) * time.Second)
|
||||
)
|
||||
|
||||
// 判断是否已经分配过
|
||||
mi := &dbdata.IpMap{}
|
||||
err := dbdata.One("mac_addr", macAddr, mi)
|
||||
// 存在ip记录
|
||||
if err == nil {
|
||||
ipStr := mi.IpAddr
|
||||
ip := net.ParseIP(ipStr)
|
||||
// 跳过活跃连接
|
||||
_, ok := ipActive[ipStr]
|
||||
// 检测原有ip是否在新的ip池内
|
||||
if IpPool.Ipv4IPNet.Contains(ip) && !ok &&
|
||||
utils.Ip2long(ip) >= IpPool.IpLongMin &&
|
||||
utils.Ip2long(ip) <= IpPool.IpLongMax {
|
||||
mi.Username = username
|
||||
mi.LastLogin = tNow
|
||||
// 回写db数据
|
||||
_ = dbdata.Set(mi)
|
||||
ipActive[ipStr] = true
|
||||
return ip
|
||||
if uniqueMac {
|
||||
// 判断是否已经分配过
|
||||
mi := &dbdata.IpMap{}
|
||||
err = dbdata.One("mac_addr", macAddr, mi)
|
||||
// 存在ip记录
|
||||
if err == nil {
|
||||
ipStr := mi.IpAddr
|
||||
ip := net.ParseIP(ipStr)
|
||||
// 跳过活跃连接
|
||||
_, ok := ipActive[ipStr]
|
||||
// 检测原有ip是否在新的ip池内
|
||||
if IpPool.Ipv4IPNet.Contains(ip) && !ok &&
|
||||
utils.Ip2long(ip) >= IpPool.IpLongMin &&
|
||||
utils.Ip2long(ip) <= IpPool.IpLongMax {
|
||||
mi.Username = username
|
||||
mi.LastLogin = tNow
|
||||
mi.UniqueMac = uniqueMac
|
||||
// 回写db数据
|
||||
_ = dbdata.Set(mi)
|
||||
ipActive[ipStr] = true
|
||||
return ip
|
||||
}
|
||||
_ = dbdata.Del(mi)
|
||||
}
|
||||
} else {
|
||||
ipMaps := []dbdata.IpMap{}
|
||||
err = dbdata.FindWhere(&ipMaps, 50, 1, "username=? and unique_mac=?", username, false)
|
||||
if err == nil {
|
||||
//遍历mac记录
|
||||
for _, mi := range ipMaps {
|
||||
ipStr := mi.IpAddr
|
||||
ip := net.ParseIP(ipStr)
|
||||
|
||||
// 跳过活跃连接
|
||||
if _, ok := ipActive[ipStr]; ok {
|
||||
continue
|
||||
}
|
||||
// 跳过ip租期内数据
|
||||
if _, ok := ipLease[ipStr]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if IpPool.Ipv4IPNet.Contains(ip) &&
|
||||
utils.Ip2long(ip) >= IpPool.IpLongMin &&
|
||||
utils.Ip2long(ip) <= IpPool.IpLongMax {
|
||||
mi.LastLogin = tNow
|
||||
mi.MacAddr = macAddr
|
||||
mi.UniqueMac = uniqueMac
|
||||
// 回写db数据
|
||||
_ = dbdata.Set(mi)
|
||||
ipActive[ipStr] = true
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = dbdata.Del(mi)
|
||||
}
|
||||
|
||||
// 全局遍历超过租期和未保留的ip
|
||||
|
@ -121,16 +160,22 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||
continue
|
||||
}
|
||||
|
||||
v := &dbdata.IpMap{}
|
||||
err = dbdata.One("ip_addr", ipStr, v)
|
||||
if err == nil {
|
||||
// 存在记录直接跳过
|
||||
continue
|
||||
mi := &dbdata.IpMap{}
|
||||
err = dbdata.One("ip_addr", ipStr, mi)
|
||||
if err == nil && mi.LastLogin.Before(sNow) {
|
||||
// 存在记录,说明已经超过租期,可以直接使用
|
||||
mi.LastLogin = tNow
|
||||
mi.MacAddr = macAddr
|
||||
mi.UniqueMac = uniqueMac
|
||||
// 回写db数据
|
||||
_ = dbdata.Set(mi)
|
||||
ipActive[ipStr] = true
|
||||
return ip
|
||||
}
|
||||
|
||||
if dbdata.CheckErrNotFound(err) {
|
||||
// 该ip没有被使用
|
||||
mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
||||
mi := &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, UniqueMac: uniqueMac, Username: username, LastLogin: tNow}
|
||||
_ = dbdata.Add(mi)
|
||||
ipActive[ipStr] = true
|
||||
return ip
|
||||
|
|
|
@ -47,21 +47,21 @@ func TestIpPool(t *testing.T) {
|
|||
var ip net.IP
|
||||
|
||||
for i := 1; i <= 100; i++ {
|
||||
_ = AcquireIp("user", fmt.Sprintf("mac-%d", i))
|
||||
_ = AcquireIp("user", fmt.Sprintf("mac-%d", i), true)
|
||||
}
|
||||
ip = AcquireIp("user", "mac-new")
|
||||
ip = AcquireIp("user", "mac-new", true)
|
||||
assert.True(net.IPv4(192, 168, 3, 101).Equal(ip))
|
||||
for i := 102; i <= 199; i++ {
|
||||
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i))
|
||||
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i), true)
|
||||
}
|
||||
assert.True(net.IPv4(192, 168, 3, 199).Equal(ip))
|
||||
ip = AcquireIp("user", "mac-nil")
|
||||
ip = AcquireIp("user", "mac-nil", true)
|
||||
assert.Nil(ip)
|
||||
|
||||
ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88")
|
||||
ReleaseIp(net.IPv4(192, 168, 3, 188), "mac-188")
|
||||
// 从头循环获取可用ip
|
||||
ip = AcquireIp("user", "mac-188")
|
||||
ip = AcquireIp("user", "mac-188", true)
|
||||
t.Log("mac-188", ip)
|
||||
assert.True(net.IPv4(192, 168, 3, 188).Equal(ip))
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ type Online struct {
|
|||
Username string `json:"username"`
|
||||
Group string `json:"group"`
|
||||
MacAddr string `json:"mac_addr"`
|
||||
UniqueMac bool `json:"unique_mac"`
|
||||
Ip net.IP `json:"ip"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
TunName string `json:"tun_name"`
|
||||
|
@ -52,6 +53,7 @@ func OnlineSess() []Online {
|
|||
Username: v.Username,
|
||||
Group: v.Group,
|
||||
MacAddr: v.MacAddr,
|
||||
UniqueMac: v.UniqueMac,
|
||||
RemoteAddr: v.CSess.RemoteAddr,
|
||||
TunName: v.CSess.IfName,
|
||||
Mtu: v.CSess.Mtu,
|
||||
|
|
|
@ -12,8 +12,7 @@ import (
|
|||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
"github.com/bjdgyc/anylink/pkg/utils"
|
||||
"github.com/ivpusic/grpool"
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
atomic2 "go.uber.org/atomic"
|
||||
)
|
||||
|
||||
|
@ -37,6 +36,8 @@ type ConnSession struct {
|
|||
Mtu int
|
||||
IfName string
|
||||
Client string // 客户端 mobile pc
|
||||
UserAgent string // 客户端信息
|
||||
UserLogoutCode uint8 // 用户/客户端主动登出
|
||||
CstpDpd int
|
||||
Group *dbdata.Group
|
||||
Limit *LimitRater
|
||||
|
@ -51,8 +52,6 @@ type ConnSession struct {
|
|||
PayloadIn chan *Payload
|
||||
PayloadOutCstp chan *Payload // Cstp的数据
|
||||
PayloadOutDtls chan *Payload // Dtls的数据
|
||||
IpAuditMap utils.IMaps // 审计的ip数据
|
||||
IpAuditPool *grpool.Pool // 审计的IP包解析池
|
||||
// dSess *DtlsSession
|
||||
dSess *atomic.Value
|
||||
}
|
||||
|
@ -65,17 +64,22 @@ type DtlsSession struct {
|
|||
}
|
||||
|
||||
type Session struct {
|
||||
mux sync.RWMutex
|
||||
Sid string // auth返回的 session-id
|
||||
Token string // session信息的唯一token
|
||||
DtlsSid string // dtls协议的 session_id
|
||||
MacAddr string // 客户端mac地址
|
||||
UniqueIdGlobal string // 客户端唯一标示
|
||||
MacHw net.HardwareAddr
|
||||
Username string // 用户名
|
||||
Group string
|
||||
AuthStep string
|
||||
AuthPass string
|
||||
mux sync.RWMutex
|
||||
Sid string // auth返回的 session-id
|
||||
Token string // session信息的唯一token
|
||||
DtlsSid string // dtls协议的 session_id
|
||||
MacAddr string // 客户端mac地址
|
||||
UniqueIdGlobal string // 客户端唯一标示
|
||||
MacHw net.HardwareAddr
|
||||
UniqueMac bool // 客户端获取到真实设备mac
|
||||
Username string // 用户名
|
||||
Group string
|
||||
AuthStep string
|
||||
AuthPass string
|
||||
RemoteAddr string
|
||||
UserAgent string
|
||||
DeviceType string
|
||||
PlatformVersion string
|
||||
|
||||
LastLogin time.Time
|
||||
IsActive bool
|
||||
|
@ -97,22 +101,46 @@ func checkSession() {
|
|||
timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second
|
||||
tick := time.NewTicker(time.Second * 60)
|
||||
for range tick.C {
|
||||
sessMux.Lock()
|
||||
outToken := []string{}
|
||||
sessMux.RLock()
|
||||
t := time.Now()
|
||||
for k, v := range sessions {
|
||||
v.mux.Lock()
|
||||
v.mux.RLock()
|
||||
if !v.IsActive {
|
||||
if t.Sub(v.LastLogin) > timeout {
|
||||
delete(sessions, k)
|
||||
outToken = append(outToken, k)
|
||||
}
|
||||
}
|
||||
v.mux.Unlock()
|
||||
v.mux.RUnlock()
|
||||
}
|
||||
sessMux.RUnlock()
|
||||
|
||||
// 删除过期session
|
||||
for _, v := range outToken {
|
||||
CloseSess(v, dbdata.UserLogoutTimeout)
|
||||
}
|
||||
sessMux.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 状态为过期的用户踢下线
|
||||
func CloseUserLimittimeSession() {
|
||||
s := mapset.NewSetFromSlice(dbdata.CheckUserlimittime())
|
||||
limitTimeToken := []string{}
|
||||
sessMux.RLock()
|
||||
for _, v := range sessions {
|
||||
v.mux.RLock()
|
||||
if v.IsActive && s.Contains(v.Username) {
|
||||
limitTimeToken = append(limitTimeToken, v.Token)
|
||||
}
|
||||
v.mux.RUnlock()
|
||||
}
|
||||
sessMux.RUnlock()
|
||||
for _, v := range limitTimeToken {
|
||||
CloseSess(v, dbdata.UserLogoutExpire)
|
||||
}
|
||||
}
|
||||
|
||||
func GenToken() string {
|
||||
// 生成32位的 token
|
||||
bToken := make([]byte, 32)
|
||||
|
@ -151,6 +179,7 @@ func (s *Session) NewConn() *ConnSession {
|
|||
macAddr := s.MacAddr
|
||||
macHw := s.MacHw
|
||||
username := s.Username
|
||||
uniqueMac := s.UniqueMac
|
||||
s.mux.RUnlock()
|
||||
if active {
|
||||
s.CSess.Close()
|
||||
|
@ -160,7 +189,7 @@ func (s *Session) NewConn() *ConnSession {
|
|||
if !limit {
|
||||
return nil
|
||||
}
|
||||
ip := AcquireIp(username, macAddr)
|
||||
ip := AcquireIp(username, macAddr, uniqueMac)
|
||||
if ip == nil {
|
||||
LimitClient(username, true)
|
||||
return nil
|
||||
|
@ -187,12 +216,6 @@ func (s *Session) NewConn() *ConnSession {
|
|||
dSess: &atomic.Value{},
|
||||
}
|
||||
|
||||
// ip 审计
|
||||
if base.Cfg.AuditInterval >= 0 {
|
||||
cSess.IpAuditMap = utils.NewMap("cmap", 0)
|
||||
cSess.IpAuditPool = grpool.NewPool(1, 600)
|
||||
}
|
||||
|
||||
dSess := &DtlsSession{
|
||||
isActive: -1,
|
||||
}
|
||||
|
@ -232,6 +255,7 @@ func (cs *ConnSession) Close() {
|
|||
|
||||
ReleaseIp(cs.IpAddr, cs.Sess.MacAddr)
|
||||
LimitClient(cs.Username, true)
|
||||
AddUserActLog(cs)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -356,6 +380,20 @@ func Dtls2Sess(did string) *Session {
|
|||
return sessions[token]
|
||||
}
|
||||
|
||||
func Dtls2CSess(did string) *ConnSession {
|
||||
sessMux.RLock()
|
||||
defer sessMux.RUnlock()
|
||||
token := dtlsIds[did]
|
||||
sess := sessions[token]
|
||||
if sess == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sess.mux.RLock()
|
||||
defer sess.mux.RUnlock()
|
||||
return sess.CSess
|
||||
}
|
||||
|
||||
func Dtls2MasterSecret(did string) string {
|
||||
sessMux.RLock()
|
||||
token := dtlsIds[did]
|
||||
|
@ -378,7 +416,7 @@ func DelSess(token string) {
|
|||
// sessions.Delete(token)
|
||||
}
|
||||
|
||||
func CloseSess(token string) {
|
||||
func CloseSess(token string, code ...uint8) {
|
||||
sessMux.Lock()
|
||||
defer sessMux.Unlock()
|
||||
sess, ok := sessions[token]
|
||||
|
@ -387,7 +425,16 @@ func CloseSess(token string) {
|
|||
}
|
||||
|
||||
delete(sessions, token)
|
||||
sess.CSess.Close()
|
||||
delete(dtlsIds, sess.DtlsSid)
|
||||
|
||||
if sess.CSess != nil {
|
||||
if len(code) > 0 {
|
||||
sess.CSess.UserLogoutCode = code[0]
|
||||
}
|
||||
sess.CSess.Close()
|
||||
return
|
||||
}
|
||||
AddUserActLogBySess(sess)
|
||||
}
|
||||
|
||||
func CloseCSess(token string) {
|
||||
|
@ -398,14 +445,42 @@ func CloseCSess(token string) {
|
|||
return
|
||||
}
|
||||
|
||||
sess.CSess.Close()
|
||||
if sess.CSess != nil {
|
||||
sess.CSess.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func DelSessByStoken(stoken string) {
|
||||
stoken = strings.TrimSpace(stoken)
|
||||
sarr := strings.Split(stoken, "@")
|
||||
token := sarr[1]
|
||||
sessMux.Lock()
|
||||
delete(sessions, token)
|
||||
sessMux.Unlock()
|
||||
CloseSess(token, dbdata.UserLogoutBanner)
|
||||
}
|
||||
|
||||
func AddUserActLog(cs *ConnSession) {
|
||||
ua := dbdata.UserActLog{
|
||||
Username: cs.Sess.Username,
|
||||
GroupName: cs.Sess.Group,
|
||||
IpAddr: cs.IpAddr.String(),
|
||||
RemoteAddr: cs.RemoteAddr,
|
||||
DeviceType: cs.Sess.DeviceType,
|
||||
PlatformVersion: cs.Sess.PlatformVersion,
|
||||
Status: dbdata.UserLogout,
|
||||
}
|
||||
ua.Info = dbdata.UserActLogIns.GetInfoOpsById(cs.UserLogoutCode)
|
||||
dbdata.UserActLogIns.Add(ua, cs.UserAgent)
|
||||
}
|
||||
|
||||
func AddUserActLogBySess(sess *Session) {
|
||||
ua := dbdata.UserActLog{
|
||||
Username: sess.Username,
|
||||
GroupName: sess.Group,
|
||||
IpAddr: "",
|
||||
RemoteAddr: sess.RemoteAddr,
|
||||
DeviceType: sess.DeviceType,
|
||||
PlatformVersion: sess.PlatformVersion,
|
||||
Status: dbdata.UserLogout,
|
||||
}
|
||||
ua.Info = dbdata.UserActLogIns.GetInfoOpsById(dbdata.UserLogoutBanner)
|
||||
dbdata.UserActLogIns.Add(ua, sess.UserAgent)
|
||||
}
|
||||
|
|
|
@ -4,4 +4,5 @@ func Start() {
|
|||
initIpPool()
|
||||
checkSession()
|
||||
saveStatsInfo()
|
||||
CloseUserLimittimeSession()
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,321 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form :model="searchForm" :rules="rules" ref="searchForm" :inline="true" class="search-form">
|
||||
<el-form-item label="用户名:" prop="username">
|
||||
<el-input size="mini" v-model="searchForm.username" clearable style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="源IP地址:" prop="src">
|
||||
<el-input size="mini" v-model="searchForm.src" clearable style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的IP地址:" prop="dst">
|
||||
<el-input size="mini" v-model="searchForm.dst" clearable style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的端口:" prop="dst_port">
|
||||
<el-input size="mini" v-model="searchForm.dst_port" clearable style="width: 80px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问协议:">
|
||||
<el-select size="mini" v-model="searchForm.access_proto" clearable placeholder="请选择" style="width: 100px">
|
||||
<el-option v-for="(item,index) in access_proto" :key="index" :label="item.text" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期范围:">
|
||||
<el-date-picker
|
||||
v-model="searchForm.date"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
size="mini"
|
||||
align="left"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="['00:00:00', '23:59:59']">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="详情:">
|
||||
<el-input size="mini" v-model="searchForm.info" placeholder="请输入关键字" clearable style="width: 200px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleSearch">搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
@click="rest">重置搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport">导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
element-loading-text="玩命加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
:default-sort="{ prop: 'id', order: 'descending' }"
|
||||
@sort-change="sortChange"
|
||||
:header-cell-style="{backgroundColor:'#fcfcfc'}"
|
||||
border>
|
||||
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="ID"
|
||||
sortable="custom"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="src"
|
||||
label="源IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst"
|
||||
label="目的IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst_port"
|
||||
label="目的端口"
|
||||
width="85">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="access_proto"
|
||||
label="访问协议"
|
||||
width="80"
|
||||
:formatter="protoFormat">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="info"
|
||||
label="详情">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="created_at"
|
||||
label="创建时间"
|
||||
width="160"
|
||||
:formatter="tableDateFormat">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="sh-20"></div>
|
||||
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:pager-count="11"
|
||||
:current-page.sync="currentPage"
|
||||
@current-change="pageChange"
|
||||
:total="count">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: "auditAccess",
|
||||
mixins: [],
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
count: 10,
|
||||
currentPage: 1,
|
||||
idSort: 1,
|
||||
activeName: "first",
|
||||
accessProtoArr:["", "UDP", "TCP", "HTTPS", "HTTP"],
|
||||
defSearchForm: {username:'', src:'', dst:'', dst_port:'', access_proto:'', info:'', date:["",""]},
|
||||
searchForm: {},
|
||||
access_proto: [
|
||||
{ text: 'UDP', value: '1' },
|
||||
{ text: 'TCP', value: '2' },
|
||||
{ text: 'HTTPS', value: '3' },
|
||||
{ text: 'HTTP', value: '4' },
|
||||
],
|
||||
maxExportNum: 1000000,
|
||||
loading: false,
|
||||
rules: {
|
||||
username: [
|
||||
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
|
||||
],
|
||||
src: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
dst: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
idSort: {
|
||||
handler(newValue, oldValue) {
|
||||
if (newValue != oldValue) {
|
||||
this.getData(1);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setSearchData() {
|
||||
this.searchForm = JSON.parse(JSON.stringify(this.defSearchForm));
|
||||
},
|
||||
handleSearch() {
|
||||
this.$refs["searchForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
this.getData(1)
|
||||
})
|
||||
},
|
||||
searchEnterFun(e) {
|
||||
var keyCode = window.event ? e.keyCode : e.which;
|
||||
if (keyCode == 13) {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
getData(p) {
|
||||
this.loading = true
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
this.searchForm.sort = this.idSort
|
||||
axios.get('/set/audit/list', {
|
||||
params: {
|
||||
page: p,
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var data = resp.data.data
|
||||
console.log(data);
|
||||
this.tableData = data.datas;
|
||||
this.count = data.count
|
||||
this.loading = false
|
||||
this.currentPage = p;
|
||||
}).catch(error => {
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
pageChange(p) {
|
||||
this.getData(p)
|
||||
},
|
||||
handleExport() {
|
||||
if (this.count > this.maxExportNum) {
|
||||
var formatNum = (this.maxExportNum + "").replace(/\d{1,3}(?=(\d{3})+$)/g,function(s){
|
||||
return s+','
|
||||
})
|
||||
this.$message.error("你导出的数据量超过" + formatNum + "条,请调整搜索条件,再导出");
|
||||
return ;
|
||||
}
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
const exporting = this.$loading({
|
||||
lock: true,
|
||||
text: '玩命导出中,请稍等片刻...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
axios.get('/set/audit/export', {
|
||||
params: {
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var rdata = resp.data
|
||||
if (rdata.code && rdata.code != 0) {
|
||||
exporting.close();
|
||||
this.$message.error(rdata.msg);
|
||||
return ;
|
||||
}
|
||||
exporting.close();
|
||||
this.$message.success("成功导出CSV文件")
|
||||
let csvData = 'data:text/csv;charset=utf-8,\uFEFF' + rdata
|
||||
this.createDownLoadClick(csvData, `anylink_audit_log_` + Date.parse(new Date()) + `.csv`)
|
||||
}).catch(error => {
|
||||
exporting.close();
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
createDownLoadClick(content, fileName) {
|
||||
const link = document.createElement('a')
|
||||
link.href = encodeURI(content)
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
},
|
||||
protoFormat(row) {
|
||||
var access_proto = row.access_proto
|
||||
if (row.access_proto == 0) {
|
||||
switch (row.protocol) {
|
||||
case 6: access_proto = 2; break;
|
||||
case 17: access_proto = 1; break;
|
||||
}
|
||||
}
|
||||
return this.accessProtoArr[access_proto]
|
||||
},
|
||||
rest() {
|
||||
console.log("rest");
|
||||
this.setSearchData();
|
||||
this.handleSearch();
|
||||
},
|
||||
validateIP(rule, value, callback) {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback()
|
||||
} else {
|
||||
const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
if ((!reg.test(value)) && value !== '') {
|
||||
callback(new Error('请输入正确的IP地址'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
},
|
||||
sortChange(column) {
|
||||
let { order } = column;
|
||||
if (order === 'ascending') {
|
||||
this.idSort = 2;
|
||||
} else {
|
||||
this.idSort = 1;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.el-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-form >>> .el-form-item__label {
|
||||
font-size: 12px;
|
||||
}
|
||||
/deep/ .el-table th {
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,263 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form :model="searchForm" ref="searchForm" :inline="true" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input size="mini" v-model="searchForm.username" clearable placeholder="请输入用户名" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-model="searchForm.sdate"
|
||||
type="date"
|
||||
size="mini"
|
||||
placeholder="开始日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 130px"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-model="searchForm.edate"
|
||||
type="date"
|
||||
size="mini"
|
||||
placeholder="结束日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 130px"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item >
|
||||
<el-select size="mini" v-model="searchForm.status" clearable placeholder="操作类型" style="width: 130px">
|
||||
<el-option v-for="(item,index) in statusOps" :key="index" :label="item.value" :value="item.key+1">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select size="mini" v-model="searchForm.os" clearable placeholder="操作系统" style="width: 130px">
|
||||
<el-option v-for="(value,item,index) in osOps" :key="index" :label="value" :value="item+1">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleSearch">搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
@click="rest">重置搜索
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
:default-sort="{ prop: 'id', order: 'descending' }"
|
||||
@sort-change="sortChange"
|
||||
:header-cell-style="{backgroundColor:'#fcfcfc'}"
|
||||
border>
|
||||
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="ID"
|
||||
sortable="custom"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="group_name"
|
||||
label="登陆组"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="操作类型"
|
||||
width="92">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-for="(item, index) in statusOps" :key="index">
|
||||
<el-tag size="small" v-if="row.status == item.key" disable-transitions :type="item.tag">{{item.value}}</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="info"
|
||||
label="操作详情"
|
||||
min-width="200">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="created_at"
|
||||
label="操作时间"
|
||||
width="150"
|
||||
:formatter="tableDateFormat">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="os"
|
||||
label="操作系统"
|
||||
min-width="210">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-for="(value, item, index) in osOps" :key="index">
|
||||
{{ row.os == item? value: "" }}
|
||||
</span>
|
||||
<div class="sub_txt">型号:
|
||||
<span v-if="row.device_type != ''">{{ row.device_type }} / {{ row.platform_version }}</span>
|
||||
<span v-else> - </span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="client"
|
||||
label="客户端"
|
||||
width="150">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-for="(value, item, index) in clientOps" :key="index">
|
||||
{{ row.client == item? value: "" }}
|
||||
</span>
|
||||
{{ row.version }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="ip_addr"
|
||||
label="内网IP"
|
||||
width="120">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remote_addr"
|
||||
label="外网IP"
|
||||
width="120">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="sh-20"></div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:pager-count="11"
|
||||
@current-change="pageChange"
|
||||
:current-page="page"
|
||||
:total="count">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: "List",
|
||||
components: {},
|
||||
mixins: [],
|
||||
created() {
|
||||
this.$emit('update:route_path', this.$route.path)
|
||||
this.$emit('update:route_name', ['用户信息', '登入日志'])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
grouNames: [],
|
||||
tableData: [],
|
||||
idSort: 1,
|
||||
count: 10,
|
||||
searchForm: {username:'', sdate:'', edate:'', status:'', os:''},
|
||||
statusOps:[],
|
||||
osOps:[],
|
||||
clientOps:[],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
idSort: {
|
||||
handler(newValue, oldValue) {
|
||||
if (newValue != oldValue) {
|
||||
this.getData(1);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
this.getData(1)
|
||||
},
|
||||
pageChange(p) {
|
||||
this.getData(p)
|
||||
},
|
||||
searchEnterFun(e) {
|
||||
var keyCode = window.event ? e.keyCode : e.which;
|
||||
if (keyCode == 13) {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
getData(page) {
|
||||
console.log(this.searchForm)
|
||||
this.page = page
|
||||
axios.get('/set/audit/act_log_list', {
|
||||
params: {
|
||||
page: page,
|
||||
username: this.searchForm.username || '',
|
||||
sdate: this.searchForm.sdate || '',
|
||||
edate: this.searchForm.edate || '',
|
||||
status: this.searchForm.status || '',
|
||||
os: this.searchForm.os || '',
|
||||
sort: this.idSort,
|
||||
}
|
||||
}).then(resp => {
|
||||
var data = resp.data.data
|
||||
console.log(data);
|
||||
this.tableData = data.datas;
|
||||
this.count = data.count
|
||||
this.statusOps = data.statusOps
|
||||
this.osOps = data.osOps
|
||||
this.clientOps = data.clientOps
|
||||
}).catch(error => {
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
rest() {
|
||||
console.log("rest");
|
||||
this.searchForm.username = "";
|
||||
this.searchForm.sdate = "";
|
||||
this.searchForm.edate = "";
|
||||
this.searchForm.status = "";
|
||||
this.searchForm.os = "";
|
||||
this.handleSearch();
|
||||
},
|
||||
sortChange(column) {
|
||||
let { order } = column;
|
||||
if (order === 'ascending') {
|
||||
this.idSort = 2;
|
||||
} else {
|
||||
this.idSort = 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.el-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-form >>> .el-form-item__label {
|
||||
font-size: 12px;
|
||||
}
|
||||
/deep/ .el-table th {
|
||||
padding: 5px 0;
|
||||
}
|
||||
/deep/ .el-table td {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.sub_txt {
|
||||
color: #88909B;
|
||||
}
|
||||
</style>
|
|
@ -56,7 +56,7 @@
|
|||
<el-menu-item index="/admin/group/list">用户组列表</el-menu-item>
|
||||
</el-submenu>
|
||||
|
||||
<el-submenu index="3">
|
||||
<el-submenu index="4">
|
||||
<template slot="title">
|
||||
<i class="el-icon-s-order"></i>
|
||||
<span slot="title">调试信息</span>
|
||||
|
@ -91,4 +91,9 @@ export default {
|
|||
.layout-menu {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.el-menu-item a {
|
||||
display: block;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -191,7 +191,7 @@
|
|||
<template slot="append">BYTE/S</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="本地网络" prop="allow_lan">
|
||||
<el-form-item label="排除本地网络" prop="allow_lan">
|
||||
<el-switch
|
||||
v-model="ruleForm.allow_lan">
|
||||
</el-switch>
|
||||
|
|
|
@ -1,300 +1,61 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<el-form :model="searchForm" :rules="rules" ref="searchForm" :inline="true" class="form-inner-error">
|
||||
<el-form-item label="用户名:" prop="username">
|
||||
<el-input size="small" v-model="searchForm.username" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="源IP地址:" prop="src">
|
||||
<el-input size="small" v-model="searchForm.src" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的IP地址:" prop="dst">
|
||||
<el-input size="small" v-model="searchForm.dst" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的端口:" prop="dst_port">
|
||||
<el-input size="small" v-model="searchForm.dst_port" style="width: 80px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问协议:">
|
||||
<el-select size="small" v-model="searchForm.access_proto" style="width: 100px">
|
||||
<el-option v-for="(item,index) in access_proto" :key="index" :label="item.text" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-form-item label="日期范围:">
|
||||
<el-date-picker
|
||||
v-model="searchForm.date"
|
||||
type="datetimerange"
|
||||
size="small"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="~"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期">
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="详情:">
|
||||
<el-input size="small" v-model="searchForm.info" placeholder="请输入关键字" style="width: 200px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleSearch">搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-refresh"
|
||||
@click="rest">重置搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport">导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
element-loading-text="玩命加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
border>
|
||||
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="ID"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="src"
|
||||
label="源IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst"
|
||||
label="目的IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst_port"
|
||||
label="目的端口"
|
||||
width="85">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="access_proto"
|
||||
label="访问协议"
|
||||
width="80"
|
||||
:formatter="protoFormat">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="info"
|
||||
label="详情">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="created_at"
|
||||
label="创建时间"
|
||||
width="150"
|
||||
:formatter="tableDateFormat">
|
||||
</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>
|
||||
|
||||
<el-card>
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="用户活动日志" name="act_log">
|
||||
<AuditActLog ref="auditActLog"></AuditActLog>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="用户访问日志" name="access_audit">
|
||||
<AuditAccess ref="auditAccess"></AuditAccess>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import AuditAccess from "../../components/audit/Access";
|
||||
import AuditActLog from "../../components/audit/ActLog";
|
||||
|
||||
export default {
|
||||
name: "Audit",
|
||||
components: {},
|
||||
components:{
|
||||
AuditAccess,
|
||||
AuditActLog
|
||||
},
|
||||
mixins: [],
|
||||
mounted() {
|
||||
this.upTab();
|
||||
},
|
||||
created() {
|
||||
this.$emit('update:route_path', this.$route.path)
|
||||
this.$emit('update:route_name', ['基础信息', '审计日志'])
|
||||
},
|
||||
mounted() {
|
||||
this.getData(1)
|
||||
this.setSearchData()
|
||||
this.$emit('update:route_name', ['基础信息', '审计日志'])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
count: 10,
|
||||
nowIndex: 0,
|
||||
accessProtoArr:["", "UDP", "TCP", "HTTPS", "HTTP"],
|
||||
defSearchForm: {username:'', src:'', dst:'', dst_port:'', access_proto:'', info:'', date:["",""]},
|
||||
searchForm: {},
|
||||
access_proto: [
|
||||
{ text: '请选择', value: '' },
|
||||
{ text: 'UDP', value: '1' },
|
||||
{ text: 'TCP', value: '2' },
|
||||
{ text: 'HTTPS', value: '3' },
|
||||
{ text: 'HTTP', value: '4' },
|
||||
],
|
||||
maxExportNum: 1000000,
|
||||
loading: false,
|
||||
rules: {
|
||||
username: [
|
||||
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
|
||||
],
|
||||
src: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
dst: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
activeName: "act_log",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setSearchData() {
|
||||
this.searchForm = JSON.parse(JSON.stringify(this.defSearchForm));
|
||||
},
|
||||
handleSearch() {
|
||||
this.$refs["searchForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
this.getData(1)
|
||||
})
|
||||
},
|
||||
searchEnterFun(e) {
|
||||
var keyCode = window.event ? e.keyCode : e.which;
|
||||
if (keyCode == 13) {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
getData(p) {
|
||||
this.loading = true
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
axios.get('/set/audit/list', {
|
||||
params: {
|
||||
page: p,
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var data = resp.data.data
|
||||
console.log(data);
|
||||
this.tableData = data.datas;
|
||||
this.count = data.count
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
pageChange(p) {
|
||||
this.getData(p)
|
||||
},
|
||||
handleExport() {
|
||||
if (this.count > this.maxExportNum) {
|
||||
var formatNum = (this.maxExportNum + "").replace(/\d{1,3}(?=(\d{3})+$)/g,function(s){
|
||||
return s+','
|
||||
})
|
||||
this.$message.error("你导出的数据量超过" + formatNum + "条,请调整搜索条件,再导出");
|
||||
return ;
|
||||
methods: {
|
||||
upTab() {
|
||||
var tabname = this.$route.query.tabname
|
||||
if (tabname) {
|
||||
this.activeName = tabname
|
||||
}
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
const exporting = this.$loading({
|
||||
lock: true,
|
||||
text: '玩命导出中,请稍等片刻...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
axios.get('/set/audit/export', {
|
||||
params: {
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var rdata = resp.data
|
||||
if (rdata.code && rdata.code != 0) {
|
||||
exporting.close();
|
||||
this.$message.error(rdata.msg);
|
||||
return ;
|
||||
}
|
||||
exporting.close();
|
||||
this.$message.success("成功导出CSV文件")
|
||||
let csvData = 'data:text/csv;charset=utf-8,\uFEFF' + rdata
|
||||
this.createDownLoadClick(csvData, `anylink_audit_log_` + Date.parse(new Date()) + `.csv`)
|
||||
}).catch(error => {
|
||||
exporting.close();
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
this.handleClick(this.activeName)
|
||||
},
|
||||
createDownLoadClick(content, fileName) {
|
||||
const link = document.createElement('a')
|
||||
link.href = encodeURI(content)
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
handleClick() {
|
||||
switch (this.activeName) {
|
||||
case "access_audit":
|
||||
this.$refs.auditAccess.setSearchData()
|
||||
this.$refs.auditAccess.getData(1)
|
||||
break
|
||||
case "act_log":
|
||||
this.$refs.auditActLog.getData(1)
|
||||
break
|
||||
}
|
||||
this.$router.push({path: this.$route.path, query: {tabname: this.activeName}})
|
||||
},
|
||||
protoFormat(row) {
|
||||
var access_proto = row.access_proto
|
||||
if (row.access_proto == 0) {
|
||||
switch (row.protocol) {
|
||||
case 6: access_proto = 2; break;
|
||||
case 17: access_proto = 1; break;
|
||||
}
|
||||
}
|
||||
return this.accessProtoArr[access_proto]
|
||||
},
|
||||
rest() {
|
||||
console.log("rest");
|
||||
this.setSearchData();
|
||||
this.handleSearch();
|
||||
},
|
||||
validateIP(rule, value, callback) {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback()
|
||||
} else {
|
||||
const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
if ((!reg.test(value)) && value !== '') {
|
||||
callback(new Error('请输入正确的IP地址'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
|
||||
<el-tab-pane label="审计日志" name="dataAuditLog">
|
||||
<el-form :model="dataAuditLog" ref="dataAuditLog" :rules="rules" label-width="100px" class="tab-one">
|
||||
<el-form-item label="审计去重间隔" prop="audit_interval">
|
||||
<el-input-number v-model="dataAuditLog.audit_interval" :min="-1" size="small" label="秒" :disabled="true"></el-input-number> 秒
|
||||
<p class="input_tip">请手动修改配置文件中的 audit_interval 参数后,再重启服务, <strong style="color:#EA3323;">-1 代表关闭审计日志</strong></p>
|
||||
</el-form-item>
|
||||
<el-form-item label="存储时长" prop="life_day">
|
||||
<el-input-number v-model="dataAuditLog.life_day" :min="0" :max="365" size="small" label="天数"></el-input-number> 天
|
||||
<p class="input_tip">范围: 0 ~ 365天 , <strong style="color:#EA3323;">0 代表永久保存</strong></p>
|
||||
|
@ -253,7 +257,7 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
.tab-one {
|
||||
width: 600px;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.input_tip {
|
||||
|
|
|
@ -35,6 +35,14 @@
|
|||
label="MAC地址">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="unique_mac"
|
||||
label="唯一MAC">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.unique_mac" type="success">是</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名">
|
||||
|
|
|
@ -10,7 +10,24 @@
|
|||
@click="handleEdit('')">添加
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-dropdown size="small" placement="bottom">
|
||||
<el-upload
|
||||
class="uploaduser"
|
||||
action="uploaduser"
|
||||
accept=".xlsx, .xls"
|
||||
:http-request="upLoadUser"
|
||||
:limit="1"
|
||||
:show-file-list="false">
|
||||
<el-button size="small" icon="el-icon-upload2" type="primary">批量添加</el-button>
|
||||
</el-upload>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item>
|
||||
<el-link style="font-size:12px;" type="success" href="批量添加用户模版.xlsx"><i class="el-icon-download"></i>下载模版</el-link>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名:">
|
||||
<el-input size="small" v-model="searchData" placeholder="请输入内容" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
|
@ -87,7 +104,8 @@
|
|||
width="70">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 1" type="success">可用</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
<el-tag v-if="scope.row.status === 0" type="danger">停用</el-tag>
|
||||
<el-tag v-if="scope.row.status === 2" >过期</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
@ -182,6 +200,18 @@
|
|||
<el-input v-model="ruleForm.pin_code" placeholder="不填由系统自动生成"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="过期时间" prop="limittime">
|
||||
<el-date-picker
|
||||
v-model="ruleForm.limittime"
|
||||
type="date"
|
||||
size="small"
|
||||
align="center"
|
||||
style="width:130px"
|
||||
:picker-options="pickerOptions"
|
||||
placeholder="选择日期">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="禁用OTP" prop="disable_otp">
|
||||
<el-switch
|
||||
v-model="ruleForm.disable_otp">
|
||||
|
@ -208,6 +238,7 @@
|
|||
<el-radio-group v-model="ruleForm.status">
|
||||
<el-radio :label="1" border>启用</el-radio>
|
||||
<el-radio :label="0" border>停用</el-radio>
|
||||
<el-radio :label="2" border>过期</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
|
@ -245,6 +276,11 @@ export default {
|
|||
grouNames: [],
|
||||
tableData: [],
|
||||
count: 10,
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() < Date.now();
|
||||
}
|
||||
},
|
||||
searchData: '',
|
||||
otpImgData: {visible: false, username: '', nickname: '', base64Img: ''},
|
||||
ruleForm: {
|
||||
|
@ -264,7 +300,6 @@ export default {
|
|||
{required: true, message: '请输入用户邮箱', trigger: 'blur'},
|
||||
{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
|
||||
],
|
||||
|
||||
password: [
|
||||
{min: 6, message: '长度大于 6 个字符', trigger: 'blur'}
|
||||
],
|
||||
|
@ -285,6 +320,24 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
upLoadUser(item) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", item.file);
|
||||
axios.post('/user/uploaduser', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}).then(resp => {
|
||||
if (resp.data.code === 0) {
|
||||
this.$message.success(resp.data.data);
|
||||
this.getData(1);
|
||||
} else {
|
||||
this.$message.error(resp.data.msg);
|
||||
this.getData(1);
|
||||
}
|
||||
console.log(resp.data);
|
||||
})
|
||||
},
|
||||
getOtpImg(row) {
|
||||
// this.base64Img = Buffer.from(data).toString('base64');
|
||||
this.otpImgData.visible = true
|
||||
|
|
|
@ -27,6 +27,15 @@
|
|||
prop="mac_addr"
|
||||
label="MAC地址">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="unique_mac"
|
||||
label="唯一MAC">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.unique_mac" type="success">是</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="ip"
|
||||
label="IP地址"
|
||||
|
|
Loading…
Reference in New Issue