72 Commits

Author SHA1 Message Date
bjdgyc
fa4c122c11 Merge pull request #19 from bjdgyc/dev
修复bug
2021-04-11 11:08:40 +08:00
bjdgyc
eb5c5b8da2 修复bug 2021-04-11 10:58:37 +08:00
bjdgyc
eacb4eace7 Merge pull request #18 from bjdgyc/dev
添加systemd文件
2021-04-11 10:34:36 +08:00
bjdgyc
b0756c961b 添加systemd文件 2021-04-11 10:16:53 +08:00
bjdgyc
d9d2a0d087 Merge pull request #17 from bjdgyc/dev
更新docker说明
2021-04-10 18:29:36 +08:00
bjdgyc
e8fd96029e 更新docker说明 2021-04-10 18:26:15 +08:00
bjdgyc
bba2690b3e Merge pull request #16 from bjdgyc/dev
修改docker问题
2021-04-10 16:49:14 +08:00
bjdgyc
ed569dfba1 修改docker问题 2021-04-10 16:45:02 +08:00
bjdgyc
d4b8cc3576 修改docker源地址 2021-04-10 15:51:03 +08:00
bjdgyc
b376552085 修改docker源地址 2021-04-10 15:46:49 +08:00
bjdgyc
9992089155 Merge pull request #15 from bjdgyc/dev
修改docker源地址
2021-04-10 15:11:19 +08:00
bjdgyc
9cf5b5f38e 修改docker源地址 2021-04-10 15:08:45 +08:00
bjdgyc
0d59b333a7 Merge pull request #14 from bjdgyc/dev
更新配置方式
2021-04-10 14:45:00 +08:00
bjdgyc
dd32bd4899 修复测试用例 2021-04-10 14:42:00 +08:00
bjdgyc
c98cacc242 Merge branch 'main' into dev 2021-04-10 14:28:35 +08:00
bjdgyc
05202c8524 添加dev分支的action 2021-04-10 14:26:23 +08:00
bjdgyc
4b00c12ef3 修改版本号 2021-04-10 14:07:09 +08:00
bjdgyc
8c70cc2e10 修改版本号 2021-04-10 13:48:50 +08:00
bjdgyc
fae0d269d9 优化Dockerfile使用 2021-04-09 14:50:23 +08:00
bjdgyc
d8eb8ab7ed 优化Dockerfile使用 2021-04-09 14:49:41 +08:00
bjdgyc
9b509c33f3 修改pr方式 2021-04-07 18:35:51 +08:00
bjdgyc
157001be18 修改配置方式,支持配置命令、环境变量、配置文件 2021-04-07 18:31:46 +08:00
bjdgyc
7fae5423b7 合并main分支 2021-04-07 17:33:12 +08:00
bjdgyc
53c7fee5be Merge branch 'main' into dev
# Conflicts:
#	docker/docker_entrypoint.sh
2021-04-07 17:29:59 +08:00
bjdgyc
ae246ed420 fix encode 2021-04-07 17:29:26 +08:00
bjdgyc
130ec45f2a Merge pull request #11 from Jonnyan404/main
增加Dockerfile
2021-03-30 21:09:59 +08:00
jonny
5e5395dabe Update Dockerfile 2021-03-30 15:56:43 +08:00
jonny
fdca297900 Create Dockerfile 2021-03-30 15:53:13 +08:00
jonny
ac412ea6c7 Create server-example.toml 2021-03-30 15:50:24 +08:00
jonny
656eca93a7 Create generate-certs.sh 2021-03-30 15:49:09 +08:00
jonny
bbb35d2138 Create docker_entrypoint.sh 2021-03-30 15:47:40 +08:00
yii
b314b8baa6 Merge branch 'dev' of https://github.com/bjdgyc/anylink into dev 2021-03-17 15:52:12 +08:00
yii
71c9dc2eb5 remove another readme 2021-03-17 15:51:22 +08:00
xbclub
0ceea0fb57 fix docker container daemon 2021-03-17 15:49:33 +08:00
xbclub
04278286ee add docker port foward 2021-03-17 15:44:06 +08:00
xbclub
df2f56817a fix docker doc 2021-03-17 15:41:58 +08:00
yii
d9bc819108 fix docker doc 2021-03-17 15:41:01 +08:00
yii
63175cadb5 fix docker doc 2021-03-17 15:38:49 +08:00
yii
3994d3032a docker support 2021-03-17 15:36:35 +08:00
bjdgyc
cb6813e922 Merge pull request #9 from bjdgyc/dev
Update README.md
2021-03-17 15:15:15 +08:00
bjdgyc
66aeaefcd8 Update README.md 2021-03-17 15:11:29 +08:00
bjdgyc
7342f1f1a9 修改版本 v0.1.8 2021-03-17 14:14:45 +08:00
bjdgyc
7a2d8a3ad0 修改日志默认为标准输出 2021-03-17 14:04:07 +08:00
bjdgyc
b5927a11d3 Merge pull request #8 from xbclub/main
fix systemd
2021-03-17 13:06:57 +08:00
yii
273b3ee1eb fix systemd 2021-03-17 11:37:34 +08:00
bjdgyc
016a43b792 完善测试文件 2021-03-10 17:00:16 +08:00
bjdgyc
ddba116fbf 修复登陆密码判断bug 2021-03-02 15:28:08 +08:00
bjdgyc
dd1eae5d32 Merge pull request #7 from xbclub/master
add systemd service
2021-03-01 17:31:38 +08:00
yii
879b9114ac add systemd service 2021-03-01 17:12:54 +08:00
bjdgyc
e5c4a47a37 修改分支为main,添加otp开关 2021-03-01 16:28:50 +08:00
bjdgyc
a0669e1e32 Update go.yml 2021-03-01 16:19:35 +08:00
bjdgyc
ea7d27b4f0 Update go.yml 2021-03-01 16:15:57 +08:00
bjdgyc
9f2e9de49a Update go.yml 2021-03-01 16:10:48 +08:00
bjdgyc
8709dbaba1 Update go.yml 2021-03-01 16:08:14 +08:00
bjdgyc
4928ad5f62 修改分支为main,添加otp开关 2021-03-01 16:02:00 +08:00
bjdgyc
0f91c779e3 更改目录结构 2021-03-01 15:46:08 +08:00
bjdgyc
3464d1d10e 修改图片地址 2021-02-25 14:17:09 +08:00
bjdgyc
1579e92ba1 修改图片地址 2021-02-25 14:15:53 +08:00
bjdgyc
48327fe8d3 修改jetbrains图片尺寸 2021-02-25 14:11:44 +08:00
bjdgyc
ef7723b03b 修复测试bug 2021-02-22 16:39:58 +08:00
bjdgyc
0baab68bb2 修改日志写入文件内 2021-02-22 14:35:29 +08:00
bjd
665732fc03 添加codecov配置文件 2021-02-04 15:19:51 +08:00
bjd
edb0fe2dc9 修改客户端分配的ip为CIDR格式,请注意原来network格式 2021-02-04 13:32:10 +08:00
bjd
1c6572f5e3 折叠截图文档 2021-02-03 15:20:19 +08:00
bjd
103329c3d0 增加测试覆盖率 2021-02-03 11:44:25 +08:00
bjdgyc
d40b753871 Update go.yml 2021-02-02 20:51:49 +08:00
bjdgyc
fa5a58e98d Update go.yml 2021-02-02 20:42:08 +08:00
bjdgyc
62f30c05ff Update .travis.yml 2021-02-02 20:34:22 +08:00
bjdgyc
c02ffc27c0 Update .travis.yml 2021-02-02 20:28:12 +08:00
bjdgyc
a4e09e7719 Create .travis.yml 2021-02-02 20:27:02 +08:00
bjd
631e49bd41 增加LinkAcl功能,可以限制访问端口 2021-02-01 17:36:59 +08:00
bjd
ef95b1f927 增加LinkAcl功能,可以限制访问端口 2021-02-01 17:34:56 +08:00
129 changed files with 30897 additions and 792 deletions

5
.codecov.yml Normal file
View File

@@ -0,0 +1,5 @@
ignore:
- "screenshot"
- "web"
- "server/conf"
- "server/files"

View File

@@ -2,9 +2,9 @@ name: Go
on: on:
push: push:
branches: [ master ] branches: [ "main", "dev" ]
pull_request: pull_request:
branches: [ master ] branches: [ "main", "dev" ]
jobs: jobs:
@@ -24,10 +24,21 @@ jobs:
- name: Get dependencies - name: Get dependencies
run: | run: |
cd server
go get -v -t -d ./... go get -v -t -d ./...
- name: Build - name: Build
run: go build -v . run: |
cd server
go build -v -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`"
./anylink tool -v
- name: Test - name: Test coverage
run: go test -v . run: |
cd server
go test -race -coverprofile=coverage.txt -covermode=atomic -v ./...
- name: Upload coverage to Codecov
run: |
cd server
bash <(curl -s https://codecov.io/bash)

20
.gitignore vendored
View File

@@ -1,19 +1,5 @@
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
ui/
.idea/ .idea/
anylink anylink-deploy
ui

48
Dockerfile Normal file
View File

@@ -0,0 +1,48 @@
# web
FROM node:lts-alpine as builder_node
WORKDIR /web
COPY ./web /web
RUN npx browserslist@latest --update-db \
&& npm install \
&& npm run build \
&& ls /web/ui
# server
FROM golang: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
RUN cd /anylink/server;go build -o anylink -ldflags "-X main.COMMIT_ID=$(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_node /web/ui /app/ui
COPY --from=builder_golang /anylink/server/anylink /app/
COPY ./server/conf /app/conf
COPY ./server/files /app/files
COPY docker_entrypoint.sh /app/
#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"]

135
README.md
View File

@@ -1,8 +1,11 @@
# AnyLink # AnyLink
[![Go](https://github.com/bjdgyc/anylink/workflows/Go/badge.svg?branch=master)](https://github.com/bjdgyc/anylink/actions)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/bjdgyc/anylink)](https://pkg.go.dev/github.com/bjdgyc/anylink) [![PkgGoDev](https://pkg.go.dev/badge/github.com/bjdgyc/anylink)](https://pkg.go.dev/github.com/bjdgyc/anylink)
[![Go Report Card](https://goreportcard.com/badge/github.com/bjdgyc/anylink)](https://goreportcard.com/report/github.com/bjdgyc/anylink)
[![codecov](https://codecov.io/gh/bjdgyc/anylink/branch/master/graph/badge.svg?token=JTFLIIIBQ0)](https://codecov.io/gh/bjdgyc/anylink)
AnyLink 是一个企业级远程办公ssl vpn软件可以支持多人同时在线使用。 AnyLink 是一个企业级远程办公sslvpn软件,可以支持多人同时在线使用。
## Repo ## Repo
@@ -17,25 +20,37 @@ AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannop
AnyLink 使用TLS/DTLS进行数据加密因此需要RSA或ECC证书可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。 AnyLink 使用TLS/DTLS进行数据加密因此需要RSA或ECC证书可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。
AnyLink 服务端仅在CentOS7测试通过如需要安装在其他系统需要服务端支持tun/tap功能、ip设置命令。 AnyLink 服务端仅在CentOS 7、Ubuntu 18.04测试通过如需要安装在其他系统需要服务端支持tun/tap功能、ip设置命令。
## Screenshot ## Screenshot
![online](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/online.jpg) ![online](screenshot/online.jpg)
## Installation ## Installation
> 没有编程基础的同学建议直接下载release包从下面的地址下载 anylink-deploy.tar.gz
>
> https://github.com/bjdgyc/anylink/releases
> 升级 go version = 1.15 > 升级 go version = 1.15
>
> 需要提前安装好 golang 和 nodejs
```shell ```shell
git clone https://github.com/bjdgyc/anylink.git git clone https://github.com/bjdgyc/anylink.git
cd anylink cd anylink
sh deploy.sh sh build.sh
#注意使用root权限运行 # 注意使用root权限运行
cd anylink-deploy cd anylink-deploy
sudo ./anylink -conf="conf/server.toml" sudo ./anylink -conf="conf/server.toml"
# 默认管理后台访问地址
# http://host:8800
# 默认账号密码
# admin 123456
``` ```
## Feature ## Feature
@@ -49,10 +64,11 @@ sudo ./anylink -conf="conf/server.toml"
- [x] 用户组支持 - [x] 用户组支持
- [x] 多用户支持 - [x] 多用户支持
- [x] TOTP令牌支持 - [x] TOTP令牌支持
- [x] TOTP令牌开关
- [x] 流量控制 - [x] 流量控制
- [x] 后台管理界面 - [x] 后台管理界面
- [x] 访问权限管理
- [ ] 访问权限管理
- [ ] DTLS-UDP通道 - [ ] DTLS-UDP通道
## Config ## Config
@@ -61,13 +77,81 @@ sudo ./anylink -conf="conf/server.toml"
```shell ```shell
# 生成后台密码 # 生成后台密码
./anylink -passwd 123456 ./anylink tool -p 123456
# 生成jwt密钥 # 生成jwt密钥
./anylink -secret ./anylink tool -s
``` ```
[conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml) [conf/server.toml](server/conf/server.toml)
## Systemd
添加 systemd脚本
* anylink 程序目录放入 `/usr/local/anylink-deploy`
systemd 脚本放入:
* centos: `/usr/lib/systemd/system/`
* ubuntu: `/lib/systemd/system/`
操作命令:
* 启动: `systemctl start anylink`
* 停止: `systemctl stop anylink`
* 开机自启: `systemctl enable anylink`
## Docker
1. 获取镜像
```bash
docker pull bjdgyc/anylink:latest
```
2. 生成密码
```bash
docker run -it --rm bjdgyc/anylink tool -p 123456
#Passwd:$2a$10$lCWTCcGmQdE/4Kb1wabbLelu4vY/cUwBwN64xIzvXcihFgRzUvH2a
```
3. 生成jwt secret
```bash
docker run -it --rm bjdgyc/anylink tool -s
#Secret:9qXoIhY01jqhWIeIluGliOS4O_rhcXGGGu422uRZ1JjZxIZmh17WwzW36woEbA
```
4. 启动容器
```bash
docker run -itd --name anylink --privileged \
-p 443:443 -p 8800:8800 \
--restart=always \
bjdgyc/anylink
```
5. 使用自定义参数启动容器
```bash
docker run -itd --name anylink --privileged \
-e IPV4_CIDR=192.168.10.0/24 \
-p 443:443 -p 8800:8800 \
--restart=always \
bjdgyc/anylink \
-c=/etc/server.toml --admin_addr=:8080
```
6. 构建镜像
```bash
#获取仓库源码
git clone https://github.com/bjdgyc/anylink.git
# 构建镜像
docker build -t anylink .
```
## Setting ## Setting
@@ -91,7 +175,7 @@ sudo ./anylink -conf="conf/server.toml"
```shell ```shell
# eth0为服务器内网网卡 # eth0为服务器内网网卡
iptables -t nat -A POSTROUTING -s 192.168.10.0/255.255.255.0 -o eth0 -j MASQUERADE iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE
``` ```
3. 使用AnyConnect客户端连接即可 3. 使用AnyConnect客户端连接即可
@@ -122,33 +206,42 @@ sh bridge-init.sh
## Soft ## Soft
相关软件下载: https://gitee.com/bjdgyc/anylink-soft 相关软件下载: QQ群共享文件: 567510628
## Discussion ## Discussion
![qq.png](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/qq.png) ![qq.png](screenshot/qq.png)
添加QQ群: 567510628 添加QQ群: 567510628
## Contribution ## Contribution
欢迎提交 PR、Issues感谢为AnyLink做出贡献 欢迎提交 PR、Issues感谢为AnyLink做出贡献
注意新建PR需要提交到dev分支其他分支暂不会合并。
## Other Screenshot ## Other Screenshot
![system.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/system.jpg) <details>
![setting.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/setting.jpg) <summary>展开查看</summary>
![users.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/users.jpg)
![ip_map.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/ip_map.jpg) ![system.jpg](screenshot/system.jpg)
![group.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/group.jpg) ![setting.jpg](screenshot/setting.jpg)
![users.jpg](screenshot/users.jpg)
![ip_map.jpg](screenshot/ip_map.jpg)
![group.jpg](screenshot/group.jpg)
</details>
## License ## License
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
## Thank
<a href="https://www.jetbrains.com">
<img src="screenshot/jetbrains.png" width="200" height="200" alt="jetbrains.png" />
</a>

View File

@@ -1,129 +0,0 @@
package base
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/pelletier/go-toml"
)
const (
LinkModeTUN = "tun"
LinkModeTAP = "tap"
)
var (
Cfg = &ServerConfig{}
)
// # ReKey time (in seconds)
// rekey-time = 172800
// # ReKey method
// # Valid options: ssl, new-tunnel
// # ssl: Will perform an efficient rehandshake on the channel allowing
// # a seamless connection during rekey.
// # new-tunnel: Will instruct the client to discard and re-establish the channel.
// # Use this option only if the connecting clients have issues with the ssl
// # option.
// rekey-method = ssl
type ServerConfig struct {
LinkAddr string `toml:"link_addr" info:"vpn服务对外地址"`
ServerAddr string `toml:"server_addr" info:"前台服务监听地址"`
AdminAddr string `toml:"admin_addr" info:"后台服务监听地址"`
ProxyProtocol bool `toml:"proxy_protocol" info:"TCP代理协议"`
DbFile string `toml:"db_file" info:"数据库地址"`
CertFile string `toml:"cert_file" info:"证书文件"`
CertKey string `toml:"cert_key" info:"证书密钥"`
UiPath string `toml:"ui_path" info:"ui文件路径"`
DownFilesPath string `toml:"down_files_path" info:"外部下载文件路径"`
LogLevel string `toml:"log_level" info:"日志等级"`
Issuer string `toml:"issuer" info:"系统名称"`
AdminUser string `toml:"admin_user" info:"管理用户名"`
AdminPass string `toml:"admin_pass" info:"管理用户密码"`
JwtSecret string `toml:"jwt_secret" info:"JWT密钥"`
LinkMode string `toml:"link_mode" info:"虚拟网络类型"` // tun tap
Ipv4Network string `toml:"ipv4_network" info:"ipv4_network"` // 192.168.1.0
Ipv4Netmask string `toml:"ipv4_netmask" info:"ipv4_netmask"` // 255.255.255.0
Ipv4Gateway string `toml:"ipv4_gateway" info:"ipv4_gateway"`
Ipv4Pool []string `toml:"ipv4_pool" info:"IPV4起止地址池"` // Pool[0]=192.168.1.100 Pool[1]=192.168.1.200
IpLease int `toml:"ip_lease" info:"IP租期(秒)"`
MaxClient int `toml:"max_client" info:"最大用户连接"`
MaxUserClient int `toml:"max_user_client" info:"最大单用户连接"`
DefaultGroup string `toml:"default_group" info:"默认用户组"`
CstpKeepalive int `toml:"cstp_keepalive" info:"keepalive时间(秒)"` // in seconds
CstpDpd int `toml:"cstp_dpd" info:"死链接检测时间(秒)"` // Dead peer detection in seconds
MobileKeepalive int `toml:"mobile_keepalive" info:"移动端keepalive接检测时间(秒)"`
MobileDpd int `toml:"mobile_dpd" info:"移动端死链接检测时间(秒)"`
SessionTimeout int `toml:"session_timeout" info:"session过期时间(秒)"` // in seconds
AuthTimeout int `toml:"auth_timeout" info:"auth_timeout"` // in seconds
}
func initServerCfg() {
b, err := ioutil.ReadFile(serverFile)
if err != nil {
panic(err)
}
err = toml.Unmarshal(b, Cfg)
if err != nil {
panic(err)
}
sf, _ := filepath.Abs(serverFile)
base := filepath.Dir(sf)
// 转换成绝对路径
Cfg.DbFile = getAbsPath(base, Cfg.DbFile)
Cfg.CertFile = getAbsPath(base, Cfg.CertFile)
Cfg.CertKey = getAbsPath(base, Cfg.CertKey)
Cfg.UiPath = getAbsPath(base, Cfg.UiPath)
Cfg.DownFilesPath = getAbsPath(base, Cfg.DownFilesPath)
if len(Cfg.JwtSecret) < 20 {
fmt.Println("请设置 jwt_secret 长度20位以上")
os.Exit(0)
}
fmt.Printf("ServerCfg: %+v \n", Cfg)
}
func getAbsPath(base, cfile string) string {
abs := filepath.IsAbs(cfile)
if abs {
return cfile
}
return filepath.Join(base, cfile)
}
func ServerCfg2Slice() interface{} {
ref := reflect.ValueOf(Cfg)
s := ref.Elem()
type cfg struct {
Name string `json:"name"`
Info string `json:"info"`
Data interface{} `json:"data"`
}
var datas []cfg
typ := s.Type()
numFields := s.NumField()
for i := 0; i < numFields; i++ {
field := typ.Field(i)
value := s.Field(i)
tag := field.Tag.Get("toml")
tags := strings.Split(tag, ",")
info := field.Tag.Get("info")
datas = append(datas, cfg{Name: tags[0], Info: info, Data: value.Interface()})
}
return datas
}

View File

@@ -1,54 +0,0 @@
package base
import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"strings"
"time"
"github.com/bjdgyc/anylink/pkg/utils"
)
var (
// 提交id
CommitId string
// 配置文件
serverFile string
// pass明文
passwd string
// 生成密钥
secret bool
// 显示版本信息
rev bool
)
func initFlag() {
flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config file path")
flag.StringVar(&passwd, "passwd", "", "convert the password plaintext")
flag.BoolVar(&secret, "secret", false, "generate a random jwt secret")
flag.BoolVar(&rev, "rev", false, "display version info")
flag.Parse()
if passwd != "" {
pass, _ := utils.PasswordHash(passwd)
fmt.Printf("Passwd:%s\n", pass)
os.Exit(0)
}
if secret {
rand.Seed(time.Now().UnixNano())
s, _ := utils.RandSecret(40, 60)
s = strings.Trim(s, "=")
fmt.Printf("Secret:%s\n", s)
os.Exit(0)
}
if rev {
fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n",
APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId)
os.Exit(0)
}
}

View File

@@ -1,90 +0,0 @@
package base
import (
"fmt"
"log"
"os"
"strings"
)
const (
_Debug = iota
_Info
_Warn
_Error
_Fatal
)
var (
baseLog *log.Logger
baseLevel int
levels map[int]string
)
func initLog() {
baseLog = log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)
baseLevel = logLevel2Int(Cfg.LogLevel)
}
func logLevel2Int(l string) int {
levels = map[int]string{
_Debug: "Debug",
_Info: "Info",
_Warn: "Warn",
_Error: "Error",
_Fatal: "Fatal",
}
lvl := _Info
for k, v := range levels {
if strings.ToLower(l) == strings.ToLower(v) {
lvl = k
}
}
return lvl
}
func output(l int, s ...interface{}) {
lvl := fmt.Sprintf("[%s] ", levels[l])
baseLog.Output(3, lvl+fmt.Sprintln(s...))
}
func Debug(v ...interface{}) {
l := _Debug
if baseLevel > l {
return
}
output(l, v...)
}
func Info(v ...interface{}) {
l := _Info
if baseLevel > l {
return
}
output(l, v...)
}
func Warn(v ...interface{}) {
l := _Warn
if baseLevel > l {
return
}
output(l, v...)
}
func Error(v ...interface{}) {
l := _Error
if baseLevel > l {
return
}
output(l, v...)
}
func Fatal(v ...interface{}) {
l := _Fatal
if baseLevel > l {
return
}
output(l, v...)
os.Exit(1)
}

View File

@@ -1,7 +0,0 @@
package base
func Start() {
initFlag()
initServerCfg()
initLog()
}

47
build.sh Normal file
View File

@@ -0,0 +1,47 @@
#!/bin/env bash
set -x
function RETVAL() {
rt=$1
if [ $rt != 0 ]; then
echo $rt
exit 1
fi
}
#当前目录
cpath=$(pwd)
echo "编译二进制文件"
cd $cpath/server
go build -o anylink -ldflags "-X main.COMMIT_ID=$(git rev-parse HEAD)"
RETVAL $?
echo "编译前端项目"
cd $cpath/web
#国内可替换源加快速度
npm install --registry=https://registry.npm.taobao.org
npm run build --registry=https://registry.npm.taobao.org
#npm install
#npm run build
RETVAL $?
cd $cpath
echo "整理部署文件"
deploy="anylink-deploy"
rm -rf $deploy
mkdir $deploy
mkdir $deploy/log
cp -r server/anylink $deploy
cp -r server/conf $deploy
cp -r server/files $deploy
cp -r server/bridge-init.sh $deploy
cp -r systemd $deploy
cp -r web/ui $deploy
#注意使用root权限运行
#cd anylink-deploy
#sudo ./anylink -conf="conf/server.toml"

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
#编译二进制文件
go build -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`"
#编译前端项目
git clone https://github.com/bjdgyc/anylink-web.git
cd anylink-web
#国内可替换源加快速度
#npm install --registry=https://registry.npm.taobao.org
#npm run build --registry=https://registry.npm.taobao.org
npm install
npm run build
cd ../
#整理部署文件
mkdir anylink-deploy
cp -r anylink anylink-deploy
cp -r anylink-web/ui anylink-deploy
cp -r conf anylink-deploy
cp -r down_files anylink-deploy
#注意使用root权限运行
#cd anylink-deploy
#sudo ./anylink -conf="conf/server.toml"

6
docker/Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
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"]

View File

@@ -0,0 +1,41 @@
#!/bin/sh
USER="admin"
MM=$(pwgen -1s)
CREATE_USER=1
CONFIG_FILE='/app/conf/server.toml'
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
else
echo "user switch not create"
fi
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
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"

View File

@@ -0,0 +1,37 @@
#! /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

41
docker/generate-certs.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/sh
mkdir -p /ssl
OUTPUT_FILENAME="vpn.xx.com"
printf "[req]
prompt = no
default_bits = 4096
default_md = sha256
encrypt_key = no
string_mask = utf8only
distinguished_name = cert_distinguished_name
req_extensions = req_x509v3_extensions
x509_extensions = req_x509v3_extensions
[ cert_distinguished_name ]
C = CN
ST = BJ
L = BJ
O = xx.com
OU = xx.com
CN = xx.com
[req_x509v3_extensions]
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
keyUsage = critical,digitalSignature,keyCertSign,cRLSign #,keyEncipherment
extendedKeyUsage = critical,serverAuth #, clientAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = xx.com
DNS.2 = *.xx.com
">/ssl/${OUTPUT_FILENAME}.conf
openssl req -x509 -newkey rsa:2048 -keyout /ssl/test_vpn_key.pem -out /ssl/test_vpn_cert.pem \
-days 3600 -nodes -config /ssl/${OUTPUT_FILENAME}.conf

23
docker_entrypoint.sh Normal file
View File

@@ -0,0 +1,23 @@
#!/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
/app/anylink "$@"
;;
esac

View File

@@ -1,5 +0,0 @@
# Binaries for programs and plugins
*
!.gitignore
!index.html

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

26
go.mod
View File

@@ -1,26 +0,0 @@
module github.com/bjdgyc/anylink
go 1.15
require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/asdine/storm/v3 v3.2.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/google/gopacket v1.1.19
github.com/gorilla/mux v1.8.0
github.com/mojocn/base64Captcha v1.3.1
github.com/pelletier/go-toml v1.8.1
github.com/shirou/gopsutil v3.20.11+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/stretchr/testify v1.6.1
github.com/xhit/go-simple-mail/v2 v2.6.0
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)

98
go.sum
View File

@@ -1,98 +0,0 @@
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mojocn/base64Captcha v1.3.1 h1:2Wbkt8Oc8qjmNJ5GyOfSo4tgVQPsbKMftqASnq8GlT0=
github.com/mojocn/base64Captcha v1.3.1/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto=
github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xhit/go-simple-mail/v2 v2.6.0 h1:pvPmpDUUWy07cnTgwxwEe5fjdyYtETnxcvdGPQxtv/k=
github.com/xhit/go-simple-mail/v2 v2.6.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
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=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,47 +0,0 @@
package handler
import "github.com/bjdgyc/anylink/sessdata"
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadInData(cSess, payload)
}
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case cSess.PayloadIn <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadOutData(cSess, payload)
}
func payloadOutData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case cSess.PayloadOut <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}

BIN
screenshot/jetbrains.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

5
server/.codecov.yml Normal file
View File

@@ -0,0 +1,5 @@
ignore:
- "screenshot"
- "web"
- "server/conf"
- "server/files"

20
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
ui/
.idea/
anylink
data.db

View File

@@ -10,26 +10,26 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
// 登陆接口 // Login 登陆接口
func Login(w http.ResponseWriter, r *http.Request) { func Login(w http.ResponseWriter, r *http.Request) {
// TODO 调试信息输出 // TODO 调试信息输出
// hd, _ := httputil.DumpRequest(r, true) // hd, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpRequest: ", string(hd)) // fmt.Println("DumpRequest: ", string(hd))
r.ParseForm() _ = r.ParseForm()
admin_user := r.PostFormValue("admin_user") adminUser := r.PostFormValue("admin_user")
admin_pass := r.PostFormValue("admin_pass") adminPass := r.PostFormValue("admin_pass")
// 认证错误 // 认证错误
if !(admin_user == base.Cfg.AdminUser && if !(adminUser == base.Cfg.AdminUser &&
utils.PasswordVerify(admin_pass, base.Cfg.AdminPass)) { utils.PasswordVerify(adminPass, base.Cfg.AdminPass)) {
RespError(w, RespUserOrPassErr) RespError(w, RespUserOrPassErr)
return return
} }
// token有效期 // token有效期
expiresAt := time.Now().Unix() + 3600*3 expiresAt := time.Now().Unix() + 3600*3
jwtData := map[string]interface{}{"admin_user": admin_user} jwtData := map[string]interface{}{"admin_user": adminUser}
tokenString, err := SetJwtData(jwtData, expiresAt) tokenString, err := SetJwtData(jwtData, expiresAt)
if err != nil { if err != nil {
RespError(w, 1, err) RespError(w, 1, err)
@@ -38,7 +38,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
data := make(map[string]interface{}) data := make(map[string]interface{})
data["token"] = tokenString data["token"] = tokenString
data["admin_user"] = admin_user data["admin_user"] = adminUser
data["expires_at"] = expiresAt data["expires_at"] = expiresAt
RespSucess(w, data) RespSucess(w, data)

View File

@@ -10,7 +10,7 @@ import (
) )
func GroupList(w http.ResponseWriter, r *http.Request) { func GroupList(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
pageS := r.FormValue("page") pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS) page, _ := strconv.Atoi(pageS)
if page < 1 { if page < 1 {
@@ -48,7 +48,7 @@ func GroupNames(w http.ResponseWriter, r *http.Request) {
} }
func GroupDetail(w http.ResponseWriter, r *http.Request) { func GroupDetail(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {
@@ -90,7 +90,7 @@ func GroupSet(w http.ResponseWriter, r *http.Request) {
} }
func GroupDel(w http.ResponseWriter, r *http.Request) { func GroupDel(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {

View File

@@ -11,7 +11,7 @@ import (
) )
func UserIpMapList(w http.ResponseWriter, r *http.Request) { func UserIpMapList(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
pageS := r.FormValue("page") pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS) page, _ := strconv.Atoi(pageS)
if page < 1 { if page < 1 {
@@ -39,7 +39,7 @@ func UserIpMapList(w http.ResponseWriter, r *http.Request) {
} }
func UserIpMapDetail(w http.ResponseWriter, r *http.Request) { func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {
@@ -58,7 +58,7 @@ func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
} }
func UserIpMapSet(w http.ResponseWriter, r *http.Request) { func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@@ -92,7 +92,7 @@ func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
} }
func UserIpMapDel(w http.ResponseWriter, r *http.Request) { func UserIpMapDel(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)

View File

@@ -1,7 +1,6 @@
package admin package admin
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"runtime" "runtime"
@@ -84,9 +83,8 @@ func SetSystem(w http.ResponseWriter, r *http.Request) {
} }
func SetSoft(w http.ResponseWriter, r *http.Request) { func SetSoft(w http.ResponseWriter, r *http.Request) {
datas := base.ServerCfg2Slice() data := base.ServerCfg2Slice()
b, _ := json.Marshal(datas) RespSucess(w, data)
w.Write(b)
} }
func decimal(f float64) float64 { func decimal(f float64) float64 {

View File

@@ -19,7 +19,7 @@ import (
) )
func UserList(w http.ResponseWriter, r *http.Request) { func UserList(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
prefix := r.FormValue("prefix") prefix := r.FormValue("prefix")
pageS := r.FormValue("page") pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS) page, _ := strconv.Atoi(pageS)
@@ -58,7 +58,7 @@ func UserList(w http.ResponseWriter, r *http.Request) {
} }
func UserDetail(w http.ResponseWriter, r *http.Request) { func UserDetail(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {
@@ -77,7 +77,7 @@ func UserDetail(w http.ResponseWriter, r *http.Request) {
} }
func UserSet(w http.ResponseWriter, r *http.Request) { func UserSet(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@@ -111,7 +111,7 @@ func UserSet(w http.ResponseWriter, r *http.Request) {
} }
func UserDel(w http.ResponseWriter, r *http.Request) { func UserDel(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
@@ -130,7 +130,7 @@ func UserDel(w http.ResponseWriter, r *http.Request) {
} }
func UserOtpQr(w http.ResponseWriter, r *http.Request) { func UserOtpQr(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
b64 := r.FormValue("b64") b64 := r.FormValue("b64")
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
@@ -148,11 +148,16 @@ func UserOtpQr(w http.ResponseWriter, r *http.Request) {
if b64 == "1" { if b64 == "1" {
data, _ := qr.PNG(300) data, _ := qr.PNG(300)
s := base64.StdEncoding.EncodeToString(data) s := base64.StdEncoding.EncodeToString(data)
fmt.Fprint(w, s) _, err = fmt.Fprint(w, s)
} else { if err != nil {
qr.Write(300, w) base.Error(err)
}
return
}
err = qr.Write(300, w)
if err != nil {
base.Error(err)
} }
} }
// 在线用户 // 在线用户
@@ -169,14 +174,14 @@ func UserOnline(w http.ResponseWriter, r *http.Request) {
} }
func UserOffline(w http.ResponseWriter, r *http.Request) { func UserOffline(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
token := r.FormValue("token") token := r.FormValue("token")
sessdata.CloseSess(token) sessdata.CloseSess(token)
RespSucess(w, nil) RespSucess(w, nil)
} }
func UserReline(w http.ResponseWriter, r *http.Request) { func UserReline(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
token := r.FormValue("token") token := r.FormValue("token")
sessdata.CloseCSess(token) sessdata.CloseCSess(token)
RespSucess(w, nil) RespSucess(w, nil)
@@ -231,7 +236,10 @@ func userAccountMail(user *dbdata.User) error {
} }
w := bytes.NewBufferString("") w := bytes.NewBufferString("")
t, _ := template.New("auth_complete").Parse(htmlBody) t, _ := template.New("auth_complete").Parse(htmlBody)
t.Execute(w, data) err = t.Execute(w, data)
if err != nil {
return err
}
// fmt.Println(w.String()) // fmt.Println(w.String())
return SendMail(base.Cfg.Issuer+"平台通知", user.Email, w.String()) return SendMail(base.Cfg.Issuer+"平台通知", user.Email, w.String())
} }

View File

@@ -8,8 +8,8 @@ import (
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/mojocn/base64Captcha"
mail "github.com/xhit/go-simple-mail/v2" mail "github.com/xhit/go-simple-mail/v2"
// "github.com/mojocn/base64Captcha"
) )
func SetJwtData(data map[string]interface{}, expiresAt int64) (string, error) { func SetJwtData(data map[string]interface{}, expiresAt int64) (string, error) {
@@ -43,15 +43,6 @@ func GetJwtData(jwtToken string) (map[string]interface{}, error) {
return claims, nil return claims, nil
} }
func createCaptcha() {
var store = base64Captcha.DefaultMemStore
var driver base64Captcha.Driver
driverString := &base64Captcha.DriverString{}
driver = driverString.ConvertFonts()
c := base64Captcha.NewCaptcha(driver, store)
_ = c
}
func SendMail(subject, to, htmlBody string) error { func SendMail(subject, to, htmlBody string) error {
dataSmtp := &dbdata.SettingSmtp{} dataSmtp := &dbdata.SettingSmtp{}

View File

@@ -0,0 +1,23 @@
package admin
import (
"testing"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/stretchr/testify/assert"
)
func TestJwtData(t *testing.T) {
assert := assert.New(t)
base.Cfg.JwtSecret = "dsfasfdfsadfasdfasd3sdaf"
data := map[string]interface{}{
"key": "value",
}
expiresAt := time.Now().Add(time.Minute).Unix()
token, err := SetJwtData(data, expiresAt)
assert.Nil(err)
dataN, err := GetJwtData(token)
assert.Nil(err)
assert.Equal(dataN["key"], "value")
}

View File

@@ -43,8 +43,10 @@ func respHttp(w http.ResponseWriter, respCode int, data interface{}, errS ...int
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write(b) _, err = w.Write(b)
if err != nil {
base.Error(err)
}
// 记录返回数据 // 记录返回数据
// logger.Category("response").Debug(string(b)) // logger.Category("response").Debug(string(b))
} }

39
server/admin/resp_test.go Normal file
View File

@@ -0,0 +1,39 @@
package admin
import (
"encoding/json"
"io/ioutil"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRespSucess(t *testing.T) {
assert := assert.New(t)
w := httptest.NewRecorder()
RespSucess(w, "data")
// fmt.Println(w)
assert.Equal(w.Code, 200)
body, _ := ioutil.ReadAll(w.Body)
res := Resp{}
err := json.Unmarshal(body, &res)
assert.Nil(err)
assert.Equal(res.Code, 0)
assert.Equal(res.Data, "data")
}
func TestRespError(t *testing.T) {
assert := assert.New(t)
w := httptest.NewRecorder()
RespError(w, 10, "err-msg")
// fmt.Println(w)
assert.Equal(w.Code, 200)
body, _ := ioutil.ReadAll(w.Body)
res := Resp{}
err := json.Unmarshal(body, &res)
assert.Nil(err)
assert.Equal(res.Code, 10)
assert.Equal(res.Msg, "err-msg")
}

View File

@@ -2,5 +2,5 @@ package base
const ( const (
APP_NAME = "AnyLink" APP_NAME = "AnyLink"
APP_VER = "0.1.0" APP_VER = "0.2.1"
) )

172
server/base/cfg.go Normal file
View File

@@ -0,0 +1,172 @@
package base
import (
"fmt"
"os"
"path/filepath"
"reflect"
"github.com/spf13/viper"
)
const (
LinkModeTUN = "tun"
LinkModeTAP = "tap"
)
var (
Cfg = &ServerConfig{}
)
// # ReKey time (in seconds)
// rekey-time = 172800
// # ReKey method
// # Valid options: ssl, new-tunnel
// # ssl: Will perform an efficient rehandshake on the channel allowing
// # a seamless connection during rekey.
// # new-tunnel: Will instruct the client to discard and re-establish the channel.
// # Use this option only if the connecting clients have issues with the ssl
// # option.
// rekey-method = ssl
type ServerConfig struct {
LinkAddr string `json:"link_addr"`
ServerAddr string `json:"server_addr"`
AdminAddr string `json:"admin_addr"`
ProxyProtocol bool `json:"proxy_protocol"`
DbFile string `json:"db_file"`
CertFile string `json:"cert_file"`
CertKey string `json:"cert_key"`
UiPath string `json:"ui_path"`
FilesPath string `json:"files_path"`
LogPath string `json:"log_path"`
LogLevel string `json:"log_level"`
Issuer string `json:"issuer"`
AdminUser string `json:"admin_user"`
AdminPass string `json:"admin_pass"`
JwtSecret string `json:"jwt_secret"`
LinkMode string `json:"link_mode"` // tun tap
Ipv4CIDR string `json:"ipv4_cidr"` // 192.168.1.0/24
Ipv4Gateway string `json:"ipv4_gateway"`
Ipv4Start string `json:"ipv4_start"` // 192.168.1.100
Ipv4End string `json:"ipv4_end"` // 192.168.1.200
IpLease int `json:"ip_lease"`
MaxClient int `json:"max_client"`
MaxUserClient int `json:"max_user_client"`
DefaultGroup string `json:"default_group"`
CstpKeepalive int `json:"cstp_keepalive"` // in seconds
CstpDpd int `json:"cstp_dpd"` // Dead peer detection in seconds
MobileKeepalive int `json:"mobile_keepalive"`
MobileDpd int `json:"mobile_dpd"`
SessionTimeout int `json:"session_timeout"` // in seconds
AuthTimeout int `json:"auth_timeout"` // in seconds
}
func initServerCfg() {
sf, _ := filepath.Abs(cfgFile)
base := filepath.Dir(sf)
// 转换成绝对路径
Cfg.DbFile = getAbsPath(base, Cfg.DbFile)
Cfg.CertFile = getAbsPath(base, Cfg.CertFile)
Cfg.CertKey = getAbsPath(base, Cfg.CertKey)
Cfg.UiPath = getAbsPath(base, Cfg.UiPath)
Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
Cfg.LogPath = getAbsPath(base, Cfg.LogPath)
if len(Cfg.JwtSecret) < 20 {
fmt.Println("请设置 jwt_secret 长度20位以上")
os.Exit(0)
}
fmt.Printf("ServerCfg: %+v \n", Cfg)
}
func getAbsPath(base, cfile string) string {
if cfile == "" {
return ""
}
abs := filepath.IsAbs(cfile)
if abs {
return cfile
}
return filepath.Join(base, cfile)
}
func initCfg() {
ref := reflect.ValueOf(Cfg)
s := ref.Elem()
typ := s.Type()
numFields := s.NumField()
for i := 0; i < numFields; i++ {
field := typ.Field(i)
value := s.Field(i)
tag := field.Tag.Get("json")
for _, v := range configs {
if v.Name == tag {
if v.Typ == cfgStr {
value.SetString(viper.GetString(v.Name))
}
if v.Typ == cfgInt {
value.SetInt(int64(viper.GetInt(v.Name)))
}
if v.Typ == cfgBool {
value.SetBool(viper.GetBool(v.Name))
}
}
}
}
initServerCfg()
}
type SCfg struct {
Name string `json:"name"`
Env string `json:"env"`
Info string `json:"info"`
Data interface{} `json:"data"`
}
func ServerCfg2Slice() []SCfg {
ref := reflect.ValueOf(Cfg)
s := ref.Elem()
var datas []SCfg
typ := s.Type()
numFields := s.NumField()
for i := 0; i < numFields; i++ {
field := typ.Field(i)
value := s.Field(i)
tag := field.Tag.Get("json")
usage, env := getUsageEnv(tag)
if usage == "" {
continue
}
datas = append(datas, SCfg{Name: tag, Env: env, Info: usage, Data: value.Interface()})
}
return datas
}
func getUsageEnv(name string) (usage, env string) {
for _, v := range configs {
if v.Name == name {
usage = v.Usage
}
}
if e, ok := envs[name]; ok {
env = e
}
return
}

131
server/base/cmd.go Normal file
View File

@@ -0,0 +1,131 @@
package base
import (
"fmt"
"math/rand"
"os"
"runtime"
"strings"
"time"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
// 提交id
CommitId string
// 配置文件
cfgFile string
// pass明文
passwd string
// 生成密钥
secret bool
// 显示版本信息
rev bool
// 获取env名称
env bool
// Used for flags.
runSrv bool
rootCmd *cobra.Command
)
// Execute executes the root command.
func execute() {
err := rootCmd.Execute()
if err != nil {
fmt.Println(err)
os.Exit(0)
}
// viper.Debug()
if !runSrv {
os.Exit(0)
}
}
func init() {
rootCmd = &cobra.Command{
Use: "anylink",
Short: "AnyLink VPN Server",
Long: `AnyLink is a VPN Server application`,
Run: func(cmd *cobra.Command, args []string) {
// fmt.Println("cmd", cmd.Use, args)
runSrv = true
},
}
cobra.OnInitialize(func() {
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
fmt.Println("Using config file:", err)
}
})
viper.SetEnvPrefix("link")
// 基础配置
rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "./conf/server.toml", "config file")
for _, v := range configs {
if v.Typ == cfgStr {
rootCmd.Flags().String(v.Name, v.ValStr, v.Usage)
}
if v.Typ == cfgInt {
rootCmd.Flags().Int(v.Name, v.ValInt, v.Usage)
}
if v.Typ == cfgBool {
rootCmd.Flags().Bool(v.Name, v.ValBool, v.Usage)
}
_ = viper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name))
_ = viper.BindEnv(v.Name)
// viper.SetDefault(v.Name, v.Value)
}
rootCmd.AddCommand(initToolCmd())
}
func initToolCmd() *cobra.Command {
toolCmd := &cobra.Command{
Use: "tool",
Short: "AnyLink tool",
Long: `AnyLink tool is a application`,
}
toolCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info")
toolCmd.Flags().BoolVarP(&secret, "secret", "s", false, "generate a random jwt secret")
toolCmd.Flags().StringVarP(&passwd, "passwd", "p", "", "convert the password plaintext")
toolCmd.Flags().BoolVarP(&env, "env", "e", false, "list the config name and env key")
toolCmd.Run = func(cmd *cobra.Command, args []string) {
switch {
case rev:
fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n",
APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId)
case secret:
rand.Seed(time.Now().UnixNano())
s, _ := utils.RandSecret(40, 60)
s = strings.Trim(s, "=")
fmt.Printf("Secret:%s\n", s)
case passwd != "":
pass, _ := utils.PasswordHash(passwd)
fmt.Printf("Passwd:%s\n", pass)
case env:
for k, v := range envs {
fmt.Printf("%s => %s\n", k, v)
}
default:
fmt.Println("Using [anylink tool -h] for help")
}
}
return toolCmd
}

52
server/base/config.go Normal file
View File

@@ -0,0 +1,52 @@
package base
const (
cfgStr = iota
cfgInt
cfgBool
)
type config struct {
Typ int
Name string
Usage string
ValStr string
ValInt int
ValBool bool
}
var configs = []config{
{Typ: cfgStr, Name: "link_addr", Usage: "vpn服务对外地址", ValStr: "vpn.xx.com"},
{Typ: cfgStr, Name: "server_addr", Usage: "前台服务监听地址", ValStr: ":443"},
{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
{Typ: cfgStr, Name: "db_file", Usage: "数据库地址", ValStr: "./conf/data.db"},
{Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./conf/vpn_cert.pem"},
{Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./conf/vpn_cert.key"},
{Typ: cfgStr, Name: "ui_path", Usage: "ui文件路径", ValStr: "./ui"},
{Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./conf/files"},
{Typ: cfgStr, Name: "log_path", Usage: "日志文件路径", ValStr: ""},
{Typ: cfgStr, Name: "log_level", Usage: "日志等级", ValStr: "info"},
{Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"},
{Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"},
{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: ""},
{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: ""},
{Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型", ValStr: "tun"},
{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: "default_group", Usage: "默认用户组", ValStr: "one"},
{Typ: cfgInt, Name: "ip_lease", Usage: "IP租期(秒)", ValInt: 1209600},
{Typ: cfgInt, Name: "max_client", Usage: "最大用户连接", ValInt: 100},
{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: "session_timeout", Usage: "session过期时间(秒)", ValInt: 3600},
// {Typ: cfgInt, Name: "auth_timeout", Usage: "auth_timeout", ValInt: 0},
}
var envs = map[string]string{"admin_addr": "LINK_ADMIN_ADDR", "admin_pass": "LINK_ADMIN_PASS", "admin_user": "LINK_ADMIN_USER", "cert_file": "LINK_CERT_FILE", "cert_key": "LINK_CERT_KEY", "cstp_dpd": "LINK_CSTP_DPD", "cstp_keepalive": "LINK_CSTP_KEEPALIVE", "db_file": "LINK_DB_FILE", "default_group": "LINK_DEFAULT_GROUP", "files_path": "LINK_FILES_PATH", "ip_lease": "LINK_IP_LEASE", "ipv4_cidr": "LINK_IPV4_CIDR", "ipv4_end": "LINK_IPV4_END", "ipv4_gateway": "LINK_IPV4_GATEWAY", "ipv4_start": "LINK_IPV4_START", "issuer": "LINK_ISSUER", "jwt_secret": "LINK_JWT_SECRET", "link_addr": "LINK_LINK_ADDR", "link_mode": "LINK_LINK_MODE", "log_level": "LINK_LOG_LEVEL", "log_path": "LINK_LOG_PATH", "max_client": "LINK_MAX_CLIENT", "max_user_client": "LINK_MAX_USER_CLIENT", "mobile_dpd": "LINK_MOBILE_DPD", "mobile_keepalive": "LINK_MOBILE_KEEPALIVE", "proxy_protocol": "LINK_PROXY_PROTOCOL", "server_addr": "LINK_SERVER_ADDR", "session_timeout": "LINK_SESSION_TIMEOUT", "ui_path": "LINK_UI_PATH"}

146
server/base/log.go Normal file
View File

@@ -0,0 +1,146 @@
package base
import (
"fmt"
"log"
"os"
"path"
"strings"
"time"
)
const (
_Debug = iota
_Info
_Warn
_Error
_Fatal
)
var (
baseLog *log.Logger
baseLevel int
levels map[int]string
dateFormat = "2006-01-02"
logName = "anylink.log"
)
// 实现 os.Writer 接口
type logWriter struct {
UseStdout bool
FileName string
File *os.File
NowDate string
}
// 实现日志文件的切割
func (lw *logWriter) Write(p []byte) (n int, err error) {
if !lw.UseStdout {
return lw.File.Write(p)
}
date := time.Now().Format(dateFormat)
if lw.NowDate != date {
_ = lw.File.Close()
_ = os.Rename(lw.FileName, lw.FileName+"."+lw.NowDate)
lw.NowDate = date
lw.newFile()
}
return lw.File.Write(p)
}
// 创建新文件
func (lw *logWriter) newFile() {
if lw.UseStdout {
lw.File = os.Stdout
return
}
f, err := os.OpenFile(lw.FileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
lw.File = f
}
func initLog() {
// 初始化 baseLog
baseLw := &logWriter{
UseStdout: Cfg.LogPath == "",
FileName: path.Join(Cfg.LogPath, logName),
NowDate: time.Now().Format(dateFormat),
}
baseLw.newFile()
baseLevel = logLevel2Int(Cfg.LogLevel)
baseLog = log.New(baseLw, "", log.LstdFlags|log.Lshortfile)
}
// 获取 log.Logger
func GetBaseLog() *log.Logger {
return baseLog
}
func logLevel2Int(l string) int {
levels = map[int]string{
_Debug: "Debug",
_Info: "Info",
_Warn: "Warn",
_Error: "Error",
_Fatal: "Fatal",
}
lvl := _Info
for k, v := range levels {
if strings.EqualFold(strings.ToLower(l), strings.ToLower(v)) {
lvl = k
}
}
return lvl
}
func output(l int, s ...interface{}) {
lvl := fmt.Sprintf("[%s] ", levels[l])
_ = baseLog.Output(3, lvl+fmt.Sprintln(s...))
}
func Debug(v ...interface{}) {
l := _Debug
if baseLevel > l {
return
}
output(l, v...)
}
func Info(v ...interface{}) {
l := _Info
if baseLevel > l {
return
}
output(l, v...)
}
func Warn(v ...interface{}) {
l := _Warn
if baseLevel > l {
return
}
output(l, v...)
}
func Error(v ...interface{}) {
l := _Error
if baseLevel > l {
return
}
output(l, v...)
}
func Fatal(v ...interface{}) {
l := _Fatal
if baseLevel > l {
return
}
output(l, v...)
os.Exit(1)
}

11
server/base/start.go Normal file
View File

@@ -0,0 +1,11 @@
package base
func Start() {
execute()
initCfg()
initLog()
}
func Test() {
initLog()
}

View File

@@ -8,10 +8,6 @@
# Define Bridge Interface # Define Bridge Interface
br="anylink0" br="anylink0"
# Define list of TAP interfaces to be bridged,
# for example tap="tap0 tap1 tap2".
tap="tap0"
# Define physical ethernet interface to be bridged # Define physical ethernet interface to be bridged
# with TAP interface(s) above. # with TAP interface(s) above.

View File

@@ -6,11 +6,13 @@
#数据文件 #数据文件
db_file = "./data.db" db_file = "./data.db"
#证书文件 #证书文件
cert_file = "./vpn_cert.pem" cert_file = "./test_vpn_cert.pem"
cert_key = "./vpn_cert.key" cert_key = "./test_vpn_key.pem"
ui_path = "../ui" ui_path = "../ui"
down_files_path = "../down_files" files_path = "../files"
#日志目录,为空写入标准输出
#log_path = "../log"
log_path = ""
log_level = "info" log_level = "info"
#系统名称 #系统名称
@@ -19,7 +21,7 @@ issuer = "XX公司VPN"
admin_user = "admin" admin_user = "admin"
#pass 123456 #pass 123456
admin_pass = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke" admin_pass = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"
jwt_secret = "" jwt_secret = "iLmspvOiz*%ovfcs*wersdf#^heR8pNU^4XxBm&mW$aPCjSRMbYH#&"
#vpn服务对外地址,影响开通邮件二维码 #vpn服务对外地址,影响开通邮件二维码
@@ -35,10 +37,10 @@ proxy_protocol = false
link_mode = "tun" link_mode = "tun"
#客户端分配的ip地址池 #客户端分配的ip地址池
ipv4_network = "192.168.10.0" ipv4_cidr = "192.168.10.0/24"
ipv4_netmask = "255.255.255.0"
ipv4_gateway = "192.168.10.1" ipv4_gateway = "192.168.10.1"
ipv4_pool = ["192.168.10.100", "192.168.10.200"] ipv4_start = "192.168.10.100"
ipv4_end = "192.168.10.200"
#最大客户端数量 #最大客户端数量
max_client = 100 max_client = 100

View File

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

View File

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

View File

@@ -43,28 +43,27 @@ func initData() {
return return
} }
defer Set(SettingBucket, Installed, true) defer func() {
_ = Set(SettingBucket, Installed, true)
}()
smtp := &SettingSmtp{ smtp := &SettingSmtp{
Host: "127.0.0.1", Host: "127.0.0.1",
Port: 25, Port: 25,
From: "vpn@xx.com", From: "vpn@xx.com",
} }
SettingSet(smtp) _ = SettingSet(smtp)
other := &SettingOther{ other := &SettingOther{
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为", Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为",
AccountMail: accountMail, AccountMail: accountMail,
} }
SettingSet(other) _ = SettingSet(other)
} }
func CheckErrNotFound(err error) bool { func CheckErrNotFound(err error) bool {
if err == storm.ErrNotFound { return err == storm.ErrNotFound
return true
}
return false
} }
const accountMail = `<p>您好:</p> const accountMail = `<p>您好:</p>
@@ -86,5 +85,5 @@ const accountMail = `<p>您好:</p>
</ul> </ul>
</div> </div>
<p> <p>
软件下载地址: https://gitee.com/bjdgyc/anylink-soft/blob/master/README.md 软件下载地址: https://{{.LinkAddr}}/files/info.txt
</p>` </p>`

View File

@@ -22,12 +22,13 @@ func closeIpdata() {
} }
func TestDb(t *testing.T) { func TestDb(t *testing.T) {
assert := assert.New(t) ast := assert.New(t)
preIpData() preIpData()
defer closeIpdata() defer closeIpdata()
u := User{Username: "a"} u := User{Username: "a"}
Save(&u) err := Save(&u)
ast.Nil(err)
assert.Equal(u.Id, 1) ast.Equal(u.Id, 1)
} }

View File

@@ -83,8 +83,9 @@ func SetGroup(g *Group) error {
if err != nil { if err != nil {
return errors.New("RouteInclude 错误" + err.Error()) return errors.New("RouteInclude 错误" + err.Error())
} }
vn := ValData{Val: v.Val, IpMask: ipMask}
routeInclude = append(routeInclude, vn) v.IpMask = ipMask
routeInclude = append(routeInclude, v)
} }
} }
g.RouteInclude = routeInclude g.RouteInclude = routeInclude
@@ -95,8 +96,8 @@ func SetGroup(g *Group) error {
if err != nil { if err != nil {
return errors.New("RouteExclude 错误" + err.Error()) return errors.New("RouteExclude 错误" + err.Error())
} }
vn := ValData{Val: v.Val, IpMask: ipMask} v.IpMask = ipMask
routeExclude = append(routeExclude, vn) routeExclude = append(routeExclude, v)
} }
} }
g.RouteExclude = routeExclude g.RouteExclude = routeExclude
@@ -108,9 +109,8 @@ func SetGroup(g *Group) error {
if err != nil { if err != nil {
return errors.New("GroupLinkAcl 错误" + err.Error()) return errors.New("GroupLinkAcl 错误" + err.Error())
} }
vn := v v.IpNet = ipNet
vn.IpNet = ipNet linkAcl = append(linkAcl, v)
linkAcl = append(linkAcl, vn)
} }
} }
g.LinkAcl = linkAcl g.LinkAcl = linkAcl

View File

@@ -0,0 +1,33 @@
package dbdata
import (
"testing"
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/stretchr/testify/assert"
)
func TestGetGroupNames(t *testing.T) {
ast := assert.New(t)
preIpData()
defer closeIpdata()
// 添加 group
g1 := Group{Name: "g1", ClientDns: []ValData{{Val: "114.114.114.114"}}}
err := SetGroup(&g1)
ast.Nil(err)
g2 := Group{Name: "g2", ClientDns: []ValData{{Val: "114.114.114.114"}}}
err = SetGroup(&g2)
ast.Nil(err)
g3 := Group{Name: "g3", ClientDns: []ValData{{Val: "114.114.114.114"}}}
err = SetGroup(&g3)
ast.Nil(err)
// 判断所有数据
gAll := []string{"g1", "g2", "g3"}
gs := GetGroupNames()
for _, v := range gs {
ast.Equal(true, utils.InArrStr(gAll, v))
}
}

View File

@@ -18,6 +18,7 @@ type User struct {
// Password string `json:"password"` // Password string `json:"password"`
PinCode string `json:"pin_code"` PinCode string `json:"pin_code"`
OtpSecret string `json:"otp_secret"` OtpSecret string `json:"otp_secret"`
DisableOtp bool `json:"disable_otp"` // 禁用otp
Groups []string `json:"groups"` Groups []string `json:"groups"`
Status int8 `json:"status"` // 1正常 Status int8 `json:"status"` // 1正常
SendEmail bool `json:"send_email"` SendEmail bool `json:"send_email"`
@@ -34,12 +35,12 @@ func SetUser(v *User) error {
planPass := v.PinCode planPass := v.PinCode
// 自动生成密码 // 自动生成密码
if len(planPass) < 6 { if len(planPass) < 6 {
planPass = utils.RandomNum(8) planPass = utils.RandomRunes(8)
} }
v.PinCode = planPass v.PinCode = planPass
if v.OtpSecret == "" { if v.OtpSecret == "" {
v.OtpSecret = gotp.RandomSecret(24) v.OtpSecret = gotp.RandomSecret(32)
} }
// 判断组是否有效 // 判断组是否有效
@@ -81,22 +82,20 @@ func CheckUser(name, pwd, group string) error {
groupData := &Group{} groupData := &Group{}
err = One("Name", group, groupData) err = One("Name", group, groupData)
if err != nil || groupData.Status != 1 { if err != nil || groupData.Status != 1 {
return fmt.Errorf("%s %s", name, "用户组错误") return fmt.Errorf("%s - %s", name, "用户组错误")
} }
// 判断otp信息 // 判断otp信息
pinCode := pwd
if !v.DisableOtp {
pinCode = pwd[:pl-6]
otp := pwd[pl-6:] otp := pwd[pl-6:]
if !checkOtp(name, otp) { if !checkOtp(name, otp, v.OtpSecret) {
return fmt.Errorf("%s %s", name, "动态码错误") return fmt.Errorf("%s %s", name, "动态码错误")
} }
totp := gotp.NewDefaultTOTP(v.OtpSecret)
unix := time.Now().Unix()
verify := totp.Verify(otp, int(unix))
if !verify {
return fmt.Errorf("%s %s", name, "动态码错误")
} }
pinCode := pwd[:pl-6] // 判断用户密码
if pinCode != v.PinCode { if pinCode != v.PinCode {
return fmt.Errorf("%s %s", name, "密码错误") return fmt.Errorf("%s %s", name, "密码错误")
} }
@@ -126,18 +125,23 @@ func init() {
}() }()
} }
// 令牌只能使用一次 // 判断令牌信息
func checkOtp(username, otp string) bool { func checkOtp(name, otp, secret string) bool {
key := fmt.Sprintf("%s:%s", username, otp) key := fmt.Sprintf("%s:%s", name, otp)
userOtpMux.Lock() userOtpMux.Lock()
defer userOtpMux.Unlock() defer userOtpMux.Unlock()
// 令牌只能使用一次
if _, ok := userOtp[key]; ok { if _, ok := userOtp[key]; ok {
// 已经存在 // 已经存在
return false return false
} }
userOtp[key] = time.Now() userOtp[key] = time.Now()
return true
totp := gotp.NewDefaultTOTP(secret)
unix := time.Now().Unix()
verify := totp.Verify(otp, int(unix))
return verify
} }

View File

@@ -0,0 +1,43 @@
package dbdata
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/xlzd/gotp"
)
func TestCheckUser(t *testing.T) {
ast := assert.New(t)
preIpData()
defer closeIpdata()
group := "group1"
// 添加一个组
dns := []ValData{{Val: "114.114.114.114"}}
route := []ValData{{Val: "192.168.1.1/24"}}
g := Group{Name: group, Status: 1, ClientDns: dns, RouteInclude: route}
err := SetGroup(&g)
ast.Nil(err)
// 判断 IpMask
ast.Equal(g.RouteInclude[0].IpMask, "192.168.1.1/255.255.255.0")
// 添加一个用户
u := User{Username: "aaa", Groups: []string{group}, Status: 1}
err = SetUser(&u)
ast.Nil(err)
// 验证 PinCode + OtpSecret
totp := gotp.NewDefaultTOTP(u.OtpSecret)
secret := totp.Now()
err = CheckUser("aaa", u.PinCode+secret, group)
ast.Nil(err)
// 单独验证密码
u.DisableOtp = true
_ = SetUser(&u)
err = CheckUser("aaa", u.PinCode, group)
ast.Nil(err)
}

0
server/files/index.html Normal file
View File

2
server/files/info.txt Normal file
View File

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

40
server/go.mod Normal file
View File

@@ -0,0 +1,40 @@
module github.com/bjdgyc/anylink
go 1.15
require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
github.com/asdine/storm/v3 v3.2.1
github.com/coreos/go-etcd v2.0.0+incompatible // indirect
github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/google/gopacket v1.1.19
github.com/gorilla/mux v1.8.0
github.com/magiconair/properties v1.8.4 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.8.1
github.com/shirou/gopsutil v3.21.1+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/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.1.3
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.7.0
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect
github.com/xhit/go-simple-mail/v2 v2.8.0
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
gopkg.in/ini.v1 v1.62.0 // indirect
)

394
server/go.sum Normal file
View File

@@ -0,0 +1,394 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
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/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/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=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
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=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil v3.21.1+incompatible h1:2LwXWdbjXwyDgq26Yy/OT4xozlpmssQfy/rtfhWb0bY=
github.com/shirou/gopsutil v3.21.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xhit/go-simple-mail/v2 v2.8.0 h1:w6ZDXvRk0EO+r78LRlQl14ngP2tiRDRRHhr9UaVJ0p4=
github.com/xhit/go-simple-mail/v2 v2.8.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
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=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
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/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=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View File

@@ -2,4 +2,5 @@ package handler
// 暂时没有实现 // 暂时没有实现
func startDtls() { func startDtls() {
} }

View File

@@ -17,10 +17,10 @@ import (
func LinkAuth(w http.ResponseWriter, r *http.Request) { func LinkAuth(w http.ResponseWriter, r *http.Request) {
// 判断anyconnect客户端 // 判断anyconnect客户端
userAgent := strings.ToLower(r.UserAgent()) userAgent := strings.ToLower(r.UserAgent())
x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth") xAggregateAuth := r.Header.Get("X-Aggregate-Auth")
x_Transcend_Version := r.Header.Get("X-Transcend-Version") xTranscendVersion := r.Header.Get("X-Transcend-Version")
if !(strings.Contains(userAgent, "anyconnect") && if !(strings.Contains(userAgent, "anyconnect") &&
x_Aggregate_Auth == "1" && x_Transcend_Version == "1") { xAggregateAuth == "1" && xTranscendVersion == "1") {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "error request") fmt.Fprintf(w, "error request")
return return
@@ -67,7 +67,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
// TODO 用户密码校验 // TODO 用户密码校验
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect) err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
if err != nil { if err != nil {
base.Info(err) base.Warn(err)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"} data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
tplRequest(tpl_request, w, data) tplRequest(tpl_request, w, data)
@@ -87,11 +87,12 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress) sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
other := &dbdata.SettingOther{} other := &dbdata.SettingOther{}
dbdata.SettingGet(other) _ = dbdata.SettingGet(other)
rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token, rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token,
Banner: other.Banner} Banner: other.Banner}
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
tplRequest(tpl_complete, w, rd) tplRequest(tpl_complete, w, rd)
base.Debug("login", cr.Auth.Username)
} }
const ( const (
@@ -102,7 +103,7 @@ const (
func tplRequest(typ int, w io.Writer, data RequestData) { func tplRequest(typ int, w io.Writer, data RequestData) {
if typ == tpl_request { if typ == tpl_request {
t, _ := template.New("auth_request").Parse(auth_request) t, _ := template.New("auth_request").Parse(auth_request)
t.Execute(w, data) _ = t.Execute(w, data)
return return
} }
@@ -111,7 +112,7 @@ func tplRequest(typ int, w io.Writer, data RequestData) {
data.Banner = strings.ReplaceAll(data.Banner, "\n", "&#x0A;") data.Banner = strings.ReplaceAll(data.Banner, "\n", "&#x0A;")
} }
t, _ := template.New("auth_complete").Parse(auth_complete) t, _ := template.New("auth_complete").Parse(auth_complete)
t.Execute(w, data) _ = t.Execute(w, data)
} }
// 设置输出信息 // 设置输出信息

View File

@@ -2,11 +2,9 @@ package handler
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"log" "log"
"net/http" "net/http"
"os/exec" "os/exec"
"strings"
) )
const BufferSize = 2048 const BufferSize = 2048
@@ -43,27 +41,6 @@ type macAddressList struct {
MacAddress string `xml:"mac-address"` MacAddress string `xml:"mac-address"`
} }
// 判断anyconnect客户端
func checkLinkClient(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO 调试信息输出
// hd, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpRequest: ", string(hd))
// fmt.Println(r.RemoteAddr)
userAgent := strings.ToLower(r.UserAgent())
x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth")
x_Transcend_Version := r.Header.Get("X-Transcend-Version")
if strings.Contains(userAgent, "anyconnect") &&
x_Aggregate_Auth == "1" && x_Transcend_Version == "1" {
h(w, r)
} else {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "error request")
}
}
}
func setCommonHeader(w http.ResponseWriter) { func setCommonHeader(w http.ResponseWriter) {
// Content-Length Date 默认已经存在 // Content-Length Date 默认已经存在
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")

View File

@@ -11,8 +11,8 @@ import (
func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) { func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("LinkCstp return") base.Debug("LinkCstp return", cSess.IpAddr)
conn.Close() _ = conn.Close()
cSess.Close() cSess.Close()
}() }()
@@ -72,8 +72,8 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) { func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("cstpWrite return") base.Debug("cstpWrite return", cSess.IpAddr)
conn.Close() _ = conn.Close()
cSess.Close() cSess.Close()
}() }()

View File

@@ -22,11 +22,11 @@ func LinkHome(w http.ResponseWriter, r *http.Request) {
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "hello world") fmt.Fprintln(w, "AnyLink 是一个企业级远程办公 sslvpn 的软件,可以支持多人同时在线使用。")
} }
func LinkOtpQr(w http.ResponseWriter, r *http.Request) { func LinkOtpQr(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
jwtToken := r.FormValue("jwt") jwtToken := r.FormValue("jwt")
data, err := admin.GetJwtData(jwtToken) data, err := admin.GetJwtData(jwtToken)

View File

@@ -29,6 +29,9 @@ func checkTap() {
bridgeHw = brFace.HardwareAddr bridgeHw = brFace.HardwareAddr
addrs, err := brFace.Addrs() addrs, err := brFace.Addrs()
if err != nil {
base.Fatal("testTap err: ", err)
}
for _, addr := range addrs { for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String()) ip, _, err := net.ParseCIDR(addr.String())
if err != nil || ip.To4() == nil { if err != nil || ip.To4() == nil {
@@ -67,7 +70,7 @@ func LinkTap(cSess *sessdata.ConnSession) error {
err = execCmd(cmdStrs) err = execCmd(cmdStrs)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
ifce.Close() _ = ifce.Close()
return err return err
} }
@@ -78,9 +81,9 @@ func LinkTap(cSess *sessdata.ConnSession) error {
func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) { func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("LinkTap return") base.Debug("LinkTap return", cSess.IpAddr)
cSess.Close() cSess.Close()
ifce.Close() _ = ifce.Close()
}() }()
var ( var (
@@ -150,8 +153,8 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) { func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("tapRead return") base.Debug("tapRead return", cSess.IpAddr)
ifce.Close() _ = ifce.Close()
}() }()
var ( var (

View File

@@ -51,7 +51,7 @@ func LinkTun(cSess *sessdata.ConnSession) error {
err = execCmd(cmdStrs) err = execCmd(cmdStrs)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
ifce.Close() _ = ifce.Close()
return err return err
} }
@@ -62,9 +62,9 @@ func LinkTun(cSess *sessdata.ConnSession) error {
func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) { func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("LinkTun return") base.Debug("LinkTun return", cSess.IpAddr)
cSess.Close() cSess.Close()
ifce.Close() _ = ifce.Close()
}() }()
var ( var (
@@ -89,8 +89,8 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) { func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("tunRead return") base.Debug("tunRead return", cSess.IpAddr)
ifce.Close() _ = ifce.Close()
}() }()
var ( var (
err error err error

View File

@@ -74,7 +74,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-CSTP-Version", "1") w.Header().Set("X-CSTP-Version", "1")
w.Header().Set("X-CSTP-Protocol", "Copyright (c) 2004 Cisco Systems, Inc.") w.Header().Set("X-CSTP-Protocol", "Copyright (c) 2004 Cisco Systems, Inc.")
w.Header().Set("X-CSTP-Address", cSess.IpAddr.String()) // 分配的ip地址 w.Header().Set("X-CSTP-Address", cSess.IpAddr.String()) // 分配的ip地址
w.Header().Set("X-CSTP-Netmask", base.Cfg.Ipv4Netmask) // 子网掩码 w.Header().Set("X-CSTP-Netmask", sessdata.IpPool.Ipv4Mask.String()) // 子网掩码
w.Header().Set("X-CSTP-Hostname", hn) // 机器名称 w.Header().Set("X-CSTP-Hostname", hn) // 机器名称
// 允许本地LAN访问vpn网络必须放在路由的第一个 // 允许本地LAN访问vpn网络必须放在路由的第一个
@@ -131,15 +131,16 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
// w.Header().Set("X-CSTP-Post-Auth-XML", ``) // w.Header().Set("X-CSTP-Post-Auth-XML", ``)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
h := w.Header().Clone() hClone := w.Header().Clone()
headers := make([]byte, 0) headers := make([]byte, 0)
buf := bytes.NewBuffer(headers) buf := bytes.NewBuffer(headers)
h.Write(buf) _ = hClone.Write(buf)
base.Debug(string(buf.Bytes())) base.Debug(buf.String())
hj := w.(http.Hijacker) hj := w.(http.Hijacker)
conn, _, err := hj.Hijack() conn, _, err := hj.Hijack()
if err != nil { if err != nil {
base.Error(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }

91
server/handler/payload.go Normal file
View File

@@ -0,0 +1,91 @@
package handler
import (
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
"github.com/songgao/water/waterutil"
)
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadInData(cSess, payload)
}
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
// 进行Acl规则判断
check := checkLinkAcl(cSess.Group, payload)
if !check {
// 校验不通过直接丢弃
return false
}
closed := false
select {
case cSess.PayloadIn <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadOutData(cSess, payload)
}
func payloadOutData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case cSess.PayloadOut <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}
// Acl规则校验
func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 {
} else {
return true
}
ip_dst := waterutil.IPv4Destination(payload.Data)
ip_port := waterutil.IPv4DestinationPort(payload.Data)
// fmt.Println("sent:", ip_dst, ip_port)
// 优先放行dns端口
for _, v := range group.ClientDns {
if v.Val == ip_dst.String() && ip_port == 53 {
return true
}
}
for _, v := range group.LinkAcl {
// 循环判断ip和端口
if v.IpNet.Contains(ip_dst) {
if v.Port == ip_port || v.Port == 0 {
if v.Action == dbdata.Allow {
return true
} else {
return false
}
}
}
}
return false
}

View File

@@ -6,7 +6,6 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"os"
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
@@ -14,22 +13,28 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
func GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey)
return &cert, err
}
func startTls() { func startTls() {
addr := base.Cfg.ServerAddr addr := base.Cfg.ServerAddr
certFile := base.Cfg.CertFile certFile := base.Cfg.CertFile
keyFile := base.Cfg.CertKey keyFile := base.Cfg.CertKey
logger := log.New(os.Stdout, "[SERVER]", log.Lshortfile|log.Ldate)
// 设置tls信息 // 设置tls信息
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"}, NextProtos: []string{"http/1.1"},
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
GetCertificate: GetCertificate,
} }
srv := &http.Server{ srv := &http.Server{
Addr: addr, Addr: addr,
Handler: initRoute(), Handler: initRoute(),
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
ErrorLog: logger, ErrorLog: base.GetBaseLog(),
} }
var ln net.Listener var ln net.Listener
@@ -57,9 +62,9 @@ func initRoute() http.Handler {
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost) r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect) r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet) r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet)
r.PathPrefix("/down_files/").Handler( r.PathPrefix("/files/").Handler(
http.StripPrefix("/down_files/", http.StripPrefix("/files/",
http.FileServer(http.Dir(base.Cfg.DownFilesPath)), http.FileServer(http.Dir(base.Cfg.FilesPath)),
), ),
) )
r.NotFoundHandler = http.HandlerFunc(notFound) r.NotFoundHandler = http.HandlerFunc(notFound)

View File

@@ -21,5 +21,5 @@ func Start() {
} }
func Stop() { func Stop() {
dbdata.Stop() _ = dbdata.Stop()
} }

View File

@@ -48,7 +48,7 @@ func tableLookup(ip net.IP) *Addr {
} }
// 判断老化过期时间 // 判断老化过期时间
tsub := time.Now().Sub(addr.disTime) tsub := time.Since(addr.disTime)
switch addr.Type { switch addr.Type {
case TypeNormal: case TypeNormal:
if tsub > StaleTimeNormal { if tsub > StaleTimeNormal {

View File

@@ -48,7 +48,7 @@ func doPing(ip string) error {
return err return err
} }
conn.SetReadDeadline(time.Now().Add(time.Second * 2)) _ = conn.SetReadDeadline(time.Now().Add(time.Second * 2))
for { for {
buf := make([]byte, 512) buf := make([]byte, 512)

View File

@@ -198,8 +198,10 @@ func (p *Conn) checkPrefixOnce() {
func (p *Conn) checkPrefix() error { func (p *Conn) checkPrefix() error {
if p.proxyHeaderTimeout != 0 { if p.proxyHeaderTimeout != 0 {
readDeadLine := time.Now().Add(p.proxyHeaderTimeout) readDeadLine := time.Now().Add(p.proxyHeaderTimeout)
p.conn.SetReadDeadline(readDeadLine) _ = p.conn.SetReadDeadline(readDeadLine)
defer p.conn.SetReadDeadline(time.Time{}) defer func() {
_ = p.conn.SetReadDeadline(time.Time{})
}()
} }
// Incrementally check each byte of the prefix // Incrementally check each byte of the prefix

View File

@@ -61,7 +61,7 @@ func HumanByte(bf interface{}) string {
return hb return hb
} }
func RandomNum(length int) string { func RandomRunes(length int) string {
letterRunes := []rune("abcdefghijklmnpqrstuvwxy1234567890") letterRunes := []rune("abcdefghijklmnpqrstuvwxy1234567890")
bytes := make([]rune, length) bytes := make([]rune, length)

View File

@@ -45,8 +45,6 @@ func CopyStruct(a interface{}, b interface{}, fields ...string) (err error) {
// a中有同名的字段并且类型一致才复制 // a中有同名的字段并且类型一致才复制
if f.IsValid() && f.Kind() == bValue.Kind() { if f.IsValid() && f.Kind() == bValue.Kind() {
f.Set(bValue) f.Set(bValue)
} else {
// fmt.Printf("no such field or different kind, fieldName: %s\n", name)
} }
} }
return return

View File

@@ -19,7 +19,8 @@ type ipPoolConfig struct {
mux sync.Mutex mux sync.Mutex
// 计算动态ip // 计算动态ip
Ipv4Gateway net.IP Ipv4Gateway net.IP
Ipv4IPNet net.IPNet Ipv4Mask net.IP
Ipv4IPNet *net.IPNet
IpLongMin uint32 IpLongMin uint32
IpLongMax uint32 IpLongMax uint32
} }
@@ -27,11 +28,12 @@ type ipPoolConfig struct {
func initIpPool() { func initIpPool() {
// 地址处理 // 地址处理
// ip地址 _, ipNet, err := net.ParseCIDR(base.Cfg.Ipv4CIDR)
ip := net.ParseIP(base.Cfg.Ipv4Network) if err != nil {
// 子网掩码 panic(err)
maskIp := net.ParseIP(base.Cfg.Ipv4Netmask).To4() }
IpPool.Ipv4IPNet = net.IPNet{IP: ip, Mask: net.IPMask(maskIp)} IpPool.Ipv4IPNet = ipNet
IpPool.Ipv4Mask = net.IP(ipNet.Mask)
IpPool.Ipv4Gateway = net.ParseIP(base.Cfg.Ipv4Gateway) IpPool.Ipv4Gateway = net.ParseIP(base.Cfg.Ipv4Gateway)
// 网络地址零值 // 网络地址零值
@@ -41,8 +43,8 @@ func initIpPool() {
// max := min | uint32(math.Pow(2, float64(32-one))-1) // max := min | uint32(math.Pow(2, float64(32-one))-1)
// ip地址池 // ip地址池
IpPool.IpLongMin = ip2long(net.ParseIP(base.Cfg.Ipv4Pool[0])) IpPool.IpLongMin = ip2long(net.ParseIP(base.Cfg.Ipv4Start))
IpPool.IpLongMax = ip2long(net.ParseIP(base.Cfg.Ipv4Pool[1])) IpPool.IpLongMax = ip2long(net.ParseIP(base.Cfg.Ipv4End))
} }
func long2ip(i uint32) net.IP { func long2ip(i uint32) net.IP {
@@ -74,11 +76,11 @@ func AcquireIp(username, macAddr string) net.IP {
mi.Username = username mi.Username = username
mi.LastLogin = tNow mi.LastLogin = tNow
// 回写db数据 // 回写db数据
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} else { } else {
dbdata.Del(mi) _ = dbdata.Del(mi)
} }
} }
@@ -92,7 +94,7 @@ func AcquireIp(username, macAddr string) net.IP {
if err != nil && dbdata.CheckErrNotFound(err) { if err != nil && dbdata.CheckErrNotFound(err) {
// 该ip没有被使用 // 该ip没有被使用
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -121,10 +123,10 @@ func AcquireIp(username, macAddr string) net.IP {
// 已经超过租期 // 已经超过租期
if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second { if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
dbdata.Del(v) _ = dbdata.Del(v)
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
// 重写db数据 // 重写db数据
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -145,7 +147,7 @@ func AcquireIp(username, macAddr string) net.IP {
ipStr := ip.String() ipStr := ip.String()
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
// 回写db数据 // 回写db数据
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -160,6 +162,6 @@ func ReleaseIp(ip net.IP, macAddr string) {
err := dbdata.One("IpAddr", ip, mi) err := dbdata.One("IpAddr", ip, mi)
if err == nil { if err == nil {
mi.LastLogin = time.Now() mi.LastLogin = time.Now()
dbdata.Save(mi) _ = dbdata.Save(mi)
} }
} }

View File

@@ -12,45 +12,54 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func preIpData() { func preData(tmpDir string) {
base.Cfg.Ipv4Network = "192.168.3.0" base.Test()
base.Cfg.Ipv4Netmask = "255.255.255.0" tmpDb := path.Join(tmpDir, "test.db")
base.Cfg.Ipv4Pool = []string{"192.168.3.1", "192.168.3.199"}
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
base.Cfg.DbFile = tmpDb base.Cfg.DbFile = tmpDb
base.Cfg.Ipv4CIDR = "192.168.3.0/24"
base.Cfg.Ipv4Start = "192.168.3.1"
base.Cfg.Ipv4End = "192.168.3.199"
base.Cfg.MaxClient = 100
base.Cfg.MaxUserClient = 3
dbdata.Start() dbdata.Start()
group := dbdata.Group{
Name: "group1",
Bandwidth: 1000,
}
_ = dbdata.Save(&group)
initIpPool()
} }
func closeIpdata() { func cleardata(tmpDir string) {
dbdata.Stop() _ = dbdata.Stop()
tmpDb := path.Join(os.TempDir(), "anylink_test.db") tmpDb := path.Join(tmpDir, "test.db")
os.Remove(tmpDb) os.Remove(tmpDb)
} }
func TestIpPool(t *testing.T) { func TestIpPool(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
preIpData() tmp := t.TempDir()
defer closeIpdata() preData(tmp)
defer cleardata(tmp)
initIpPool()
var ip net.IP var ip net.IP
for i := 1; i <= 100; i++ { for i := 1; i <= 100; i++ {
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i)) _ = AcquireIp("user", fmt.Sprintf("mac-%d", i))
} }
ip = AcquireIp("user", fmt.Sprintf("mac-new")) ip = AcquireIp("user", "mac-new")
assert.True(net.IPv4(192, 168, 3, 101).Equal(ip)) assert.True(net.IPv4(192, 168, 3, 101).Equal(ip))
for i := 102; i <= 199; i++ { for i := 102; i <= 199; i++ {
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i)) ip = AcquireIp("user", fmt.Sprintf("mac-%d", i))
} }
assert.True(net.IPv4(192, 168, 3, 199).Equal(ip)) assert.True(net.IPv4(192, 168, 3, 199).Equal(ip))
ip = AcquireIp("user", fmt.Sprintf("mac-nil")) ip = AcquireIp("user", "mac-nil")
assert.Nil(ip) assert.Nil(ip)
ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88") ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88")
ReleaseIp(net.IPv4(192, 168, 3, 77), "mac-77") ReleaseIp(net.IPv4(192, 168, 3, 77), "mac-77")
// 最早过期的ip // 从头循环获取可用ip
ip = AcquireIp("user", "mac-release-new") ip = AcquireIp("user", "mac-release-new")
assert.True(net.IPv4(192, 168, 3, 88).Equal(ip)) assert.True(net.IPv4(192, 168, 3, 77).Equal(ip))
} }

View File

@@ -43,10 +43,11 @@ func TestLimitClient(t *testing.T) {
func TestLimitWait(t *testing.T) { func TestLimitWait(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
limit := NewLimitRater(1, 2) limit := NewLimitRater(1, 2)
limit.Wait(2)
start := time.Now()
err := limit.Wait(2) err := limit.Wait(2)
assert.Nil(err) assert.Nil(err)
start := time.Now()
err = limit.Wait(2)
assert.Nil(err)
err = limit.Wait(1) err = limit.Wait(1)
assert.Nil(err) assert.Nil(err)
end := time.Now() end := time.Now()

View File

@@ -34,10 +34,7 @@ func (o Onlines) Len() int {
} }
func (o Onlines) Less(i, j int) bool { func (o Onlines) Less(i, j int) bool {
if bytes.Compare(o[i].Ip, o[j].Ip) < 0 { return bytes.Compare(o[i].Ip, o[j].Ip) < 0
return true
}
return false
} }
func (o Onlines) Swap(i, j int) { func (o Onlines) Swap(i, j int) {

View File

@@ -3,7 +3,6 @@ package sessdata
import ( import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"log"
"math/rand" "math/rand"
"net" "net"
"strconv" "strconv"
@@ -78,13 +77,13 @@ func checkSession() {
return return
} }
timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second
tick := time.Tick(time.Second * 60) tick := time.NewTicker(time.Second * 60)
for range tick { for range tick.C {
sessMux.Lock() sessMux.Lock()
t := time.Now() t := time.Now()
for k, v := range sessions { for k, v := range sessions {
v.mux.Lock() v.mux.Lock()
if v.IsActive != true { if !v.IsActive {
if t.Sub(v.LastLogin) > timeout { if t.Sub(v.LastLogin) > timeout {
delete(sessions, k) delete(sessions, k)
} }
@@ -133,12 +132,12 @@ func (s *Session) NewConn() *ConnSession {
macAddr := s.MacAddr macAddr := s.MacAddr
username := s.Username username := s.Username
s.mux.Unlock() s.mux.Unlock()
if active == true { if active {
s.CSess.Close() s.CSess.Close()
} }
limit := LimitClient(username, false) limit := LimitClient(username, false)
if limit == false { if !limit {
return nil return nil
} }
// 获取客户端mac地址 // 获取客户端mac地址
@@ -191,7 +190,7 @@ func (s *Session) NewConn() *ConnSession {
func (cs *ConnSession) Close() { func (cs *ConnSession) Close() {
cs.closeOnce.Do(func() { cs.closeOnce.Do(func() {
log.Println("closeOnce:", cs.IpAddr) base.Info("closeOnce:", cs.IpAddr)
cs.Sess.mux.Lock() cs.Sess.mux.Lock()
defer cs.Sess.mux.Unlock() defer cs.Sess.mux.Unlock()
@@ -208,8 +207,10 @@ func (cs *ConnSession) Close() {
const BandwidthPeriodSec = 2 // 流量速率统计周期(秒) const BandwidthPeriodSec = 2 // 流量速率统计周期(秒)
func (cs *ConnSession) ratePeriod() { func (cs *ConnSession) ratePeriod() {
tick := time.Tick(time.Second * BandwidthPeriodSec) tick := time.NewTicker(time.Second * BandwidthPeriodSec)
for range tick { defer tick.Stop()
for range tick.C {
select { select {
case <-cs.CloseChan: case <-cs.CloseChan:
return return

View File

@@ -0,0 +1,38 @@
package sessdata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSession(t *testing.T) {
ast := assert.New(t)
sessions = make(map[string]*Session)
sess := NewSession("")
token := sess.Token
v, ok := sessions[token]
ast.True(ok)
ast.Equal(sess, v)
}
func TestConnSession(t *testing.T) {
ast := assert.New(t)
tmp := t.TempDir()
preData(tmp)
defer cleardata(tmp)
sess := NewSession("")
sess.Group = "group1"
sess.MacAddr = "00:15:5d:50:14:43"
cSess := sess.NewConn()
err := cSess.RateLimit(100, true)
ast.Nil(err)
ast.Equal(cSess.BandwidthUp, uint32(100))
err = cSess.RateLimit(200, false)
ast.Nil(err)
ast.Equal(cSess.BandwidthDown, uint32(200))
cSess.Close()
}

View File

@@ -1,30 +0,0 @@
package sessdata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSession(t *testing.T) {
assert := assert.New(t)
sessions = make(map[string]*Session)
sess := NewSession("")
token := sess.Token
v, ok := sessions[token]
assert.True(ok)
assert.Equal(sess, v)
}
func TestConnSession(t *testing.T) {
assert := assert.New(t)
preIpData()
defer closeIpdata()
sess := NewSession("")
cSess := sess.NewConn()
cSess.RateLimit(100, true)
assert.Equal(cSess.BandwidthUp, uint32(100))
cSess.RateLimit(200, false)
assert.Equal(cSess.BandwidthDown, uint32(200))
cSess.Close()
}

13
systemd/anylink.service Normal file
View File

@@ -0,0 +1,13 @@
[Unit]
Description=VPN Server Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory= /usr/local/anylink-deploy
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/anylink-deploy/anylink -conf=conf/server.toml
[Install]
WantedBy=multi-user.target

3
web/.browserslistrc Normal file
View File

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

17
web/.eslintrc.js Normal file
View File

@@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'eslint:recommended'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

24
web/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
.DS_Store
node_modules
/dist
/ui
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

21
web/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 bjdgyc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More