mirror of https://github.com/bjdgyc/anylink.git
添加dtls支持
This commit is contained in:
parent
07163aa33b
commit
c8f090c9e3
|
@ -56,7 +56,7 @@ func authMiddleware(next http.Handler) http.Handler {
|
|||
route := mux.CurrentRoute(r)
|
||||
name := route.GetName()
|
||||
// fmt.Println("bb", r.URL.Path, name)
|
||||
if utils.InArrStr([]string{"login", "index", "static"}, name) {
|
||||
if utils.InArrStr([]string{"login", "index", "static", "debug"}, name) {
|
||||
// 不进行鉴权
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
|
|
|
@ -49,12 +49,14 @@ func StartAdmin() {
|
|||
r.HandleFunc("/group/del", GroupDel)
|
||||
|
||||
// pprof
|
||||
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
r.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
r.HandleFunc("/debug/pprof", location("/debug/pprof/"))
|
||||
r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
if base.Cfg.Pprof {
|
||||
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline).Name("debug")
|
||||
r.HandleFunc("/debug/pprof/profile", pprof.Profile).Name("debug")
|
||||
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol).Name("debug")
|
||||
r.HandleFunc("/debug/pprof/trace", pprof.Trace).Name("debug")
|
||||
r.HandleFunc("/debug/pprof", location("/debug/pprof/"))
|
||||
r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index).Name("debug")
|
||||
}
|
||||
|
||||
base.Info("Listen admin", base.Cfg.AdminAddr)
|
||||
err := http.ListenAndServe(base.Cfg.AdminAddr, r)
|
||||
|
|
|
@ -31,20 +31,22 @@ var (
|
|||
|
||||
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"`
|
||||
ServerAddr string `json:"server_addr"`
|
||||
ServerDTLSAddr string `json:"server_dtls_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"`
|
||||
Pprof bool `json:"pprof"`
|
||||
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
|
||||
|
|
|
@ -17,19 +17,21 @@ type config struct {
|
|||
|
||||
var configs = []config{
|
||||
{Typ: cfgStr, Name: "server_addr", Usage: "前台服务监听地址", ValStr: ":443"},
|
||||
{Typ: cfgStr, Name: "server_dtls_addr", Usage: "前台DTLS监听地址", ValStr: ":4433"},
|
||||
{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
|
||||
{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
|
||||
{Typ: cfgStr, Name: "db_file", Usage: "数据库地址", ValStr: "./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: "db_file", Usage: "数据库地址", ValStr: "./data.db"},
|
||||
{Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./vpn_cert.pem"},
|
||||
{Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./vpn_cert.key"},
|
||||
{Typ: cfgStr, Name: "ui_path", Usage: "ui文件路径", ValStr: "./ui"},
|
||||
{Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./conf/files"},
|
||||
{Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./files"},
|
||||
{Typ: cfgStr, Name: "log_path", Usage: "日志文件路径", ValStr: ""},
|
||||
{Typ: cfgStr, Name: "log_level", Usage: "日志等级", ValStr: "info"},
|
||||
{Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false},
|
||||
{Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"},
|
||||
{Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"},
|
||||
{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: ""},
|
||||
{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: ""},
|
||||
{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"},
|
||||
{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: "iLmspvOiz*%ovfcs*wersdf#heR8pNU4XxBm&mW$aPCjSRMbYH#&"},
|
||||
{Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型", ValStr: "tun"},
|
||||
{Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"},
|
||||
{Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.10.1"},
|
||||
|
@ -48,4 +50,4 @@ var configs = []config{
|
|||
// {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"}
|
||||
var envs = map[string]string{}
|
||||
|
|
|
@ -18,6 +18,7 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
baseLw *logWriter
|
||||
baseLog *log.Logger
|
||||
baseLevel int
|
||||
levels map[int]string
|
||||
|
@ -66,7 +67,7 @@ func (lw *logWriter) newFile() {
|
|||
|
||||
func initLog() {
|
||||
// 初始化 baseLog
|
||||
baseLw := &logWriter{
|
||||
baseLw = &logWriter{
|
||||
UseStdout: Cfg.LogPath == "",
|
||||
FileName: path.Join(Cfg.LogPath, logName),
|
||||
NowDate: time.Now().Format(dateFormat),
|
||||
|
@ -77,6 +78,10 @@ func initLog() {
|
|||
baseLog = log.New(baseLw, "", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
|
||||
func GetBaseLw() *logWriter {
|
||||
return baseLw
|
||||
}
|
||||
|
||||
// 获取 log.Logger
|
||||
func GetBaseLog() *log.Logger {
|
||||
return baseLog
|
||||
|
|
|
@ -14,6 +14,7 @@ files_path = "../files"
|
|||
#log_path = "../log"
|
||||
log_path = ""
|
||||
log_level = "debug"
|
||||
pprof = false
|
||||
|
||||
#系统名称
|
||||
issuer = "XX公司VPN"
|
||||
|
@ -25,7 +26,8 @@ jwt_secret = "iLmspvOiz*%ovfcs*wersdf#heR8pNU4XxBm&mW$aPCjSRMbYH#&"
|
|||
|
||||
|
||||
#前台服务监听地址
|
||||
server_addr = ":4430"
|
||||
server_addr = ":443"
|
||||
server_dtls_addr = ":4433"
|
||||
#后台服务监听地址
|
||||
admin_addr = ":8800"
|
||||
#开启tcp proxy protocol协议
|
||||
|
@ -52,8 +54,8 @@ default_group = "one"
|
|||
#客户端失效检测时间(秒) dpd > keepalive
|
||||
cstp_keepalive = 20
|
||||
cstp_dpd = 30
|
||||
mobile_keepalive = 50
|
||||
mobile_dpd = 60
|
||||
mobile_keepalive = 40
|
||||
mobile_dpd = 50
|
||||
#session过期时间,用于断线重连,0永不过期
|
||||
session_timeout = 3600
|
||||
auth_timeout = 0
|
||||
|
|
|
@ -64,7 +64,7 @@ func SetUser(v *User) error {
|
|||
|
||||
// 验证用户登陆信息
|
||||
func CheckUser(name, pwd, group string) error {
|
||||
// return nil
|
||||
return nil
|
||||
|
||||
pl := len(pwd)
|
||||
if name == "" || pl < 6 {
|
||||
|
|
|
@ -1,37 +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/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 // indirect
|
||||
github.com/pion/dtls/v2 v2.0.9
|
||||
github.com/pion/logging v0.2.2
|
||||
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/xhit/go-simple-mail/v2 v2.8.0
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
|
||||
golang.org/x/net v0.0.0-20210502030024-e5908800b52b
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/pion/dtls/v2 => ../../dtls
|
|
@ -11,6 +11,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
|
|||
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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
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=
|
||||
|
@ -18,8 +19,8 @@ github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t
|
|||
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/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/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=
|
||||
|
@ -46,9 +47,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
|||
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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
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=
|
||||
|
@ -122,16 +122,14 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
|||
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 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
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=
|
||||
|
@ -143,17 +141,15 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
|
|||
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 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
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 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
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/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
|
@ -163,7 +159,6 @@ github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
|||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
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=
|
||||
|
@ -181,8 +176,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||
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/shirou/gopsutil v3.21.4+incompatible h1:fuHcTm5mX+wzo542cmYcV9RTGQLbnHLI5SyQ5ryTVck=
|
||||
github.com/shirou/gopsutil v3.21.4+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=
|
||||
|
@ -197,17 +192,14 @@ github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8o
|
|||
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 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
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 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
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 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
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=
|
||||
|
@ -218,17 +210,20 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4=
|
||||
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
|
||||
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
|
||||
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
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/xhit/go-simple-mail/v2 v2.9.0 h1:vN4fb1Aw5BDtMeJuV/aTP82ufjdT8q0GmqiBjMKPN6I=
|
||||
github.com/xhit/go-simple-mail/v2 v2.9.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=
|
||||
|
@ -246,11 +241,11 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
|
|||
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
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=
|
||||
|
@ -289,8 +284,9 @@ golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210502030024-e5908800b52b h1:jCRjgm6WJHzM8VQrm/es2wXYqqbq0NZ1yXFHHgzkiVQ=
|
||||
golang.org/x/net v0.0.0-20210502030024-e5908800b52b/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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=
|
||||
|
@ -313,10 +309,11 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -381,13 +378,11 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
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 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
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=
|
||||
|
|
|
@ -1,59 +1,78 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
"os"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
"github.com/pion/dtls/v2"
|
||||
"github.com/pion/dtls/v2/pkg/crypto/selfsign"
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
// 因本项目对 github.com/pion/dtls 的代码,进行了大量的修改
|
||||
// 且短时间内无法合并到上游项目
|
||||
// 所以本项目暂时copy了一份代码
|
||||
// 最后,感谢 github.com/pion/dtls 对golang生态做出的贡献
|
||||
|
||||
func startDtls() {
|
||||
certificate, err := selfsign.GenerateSelfSigned()
|
||||
|
||||
logf := logging.NewDefaultLoggerFactory()
|
||||
logf.DefaultLogLevel = logging.LogLevelTrace
|
||||
f, err := os.OpenFile("/tmp/key.log", os.O_TRUNC|os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logf := logging.NewDefaultLoggerFactory()
|
||||
logf.Writer = base.GetBaseLw()
|
||||
// logf.DefaultLogLevel = logging.LogLevelTrace
|
||||
logf.DefaultLogLevel = logging.LogLevelInfo
|
||||
|
||||
config := &dtls.Config{
|
||||
Certificates: []tls.Certificate{certificate},
|
||||
InsecureSkipVerify: true,
|
||||
ExtendedMasterSecret: dtls.DisableExtendedMasterSecret,
|
||||
CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
|
||||
CipherSuites: []dtls.CipherSuiteID{dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
|
||||
LoggerFactory: logf,
|
||||
KeyLogWriter: f,
|
||||
MTU: BufferSize,
|
||||
CiscoCompat: func(sessid []byte) ([]byte, error) {
|
||||
masterSecret := sessdata.Dtls2MasterSecret(hex.EncodeToString(sessid))
|
||||
if masterSecret == "" {
|
||||
return nil, fmt.Errorf("masterSecret is err")
|
||||
}
|
||||
return hex.DecodeString(masterSecret)
|
||||
},
|
||||
ConnectContextMaker: func() (context.Context, func()) {
|
||||
return context.WithTimeout(context.Background(), 5*time.Second)
|
||||
},
|
||||
}
|
||||
|
||||
addr := &net.UDPAddr{IP: net.ParseIP("0.0.0.0"), Port: 4433}
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", base.Cfg.ServerDTLSAddr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ln, err := dtls.Listen("udp", addr, config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
base.Info("listen DTLS server", addr)
|
||||
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Println("Accept error", err)
|
||||
base.Error("DTLS Accept error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
cc := c.(*dtls.Conn)
|
||||
id := hex.EncodeToString(cc.ConnectionState().SessionID)
|
||||
s, ok := ss.Load(id)
|
||||
log.Println("get link", id, ok)
|
||||
cs := s.(*sessdata.ConnSession)
|
||||
LinkDtls(c, cs)
|
||||
// time.Sleep(1 * time.Second)
|
||||
cc := conn.(*dtls.Conn)
|
||||
sessid := hex.EncodeToString(cc.ConnectionState().SessionID)
|
||||
sess := sessdata.Dtls2Sess(sessid)
|
||||
LinkDtls(conn, sess.CSess)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
return
|
||||
case 0x03: // DPD-REQ
|
||||
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
||||
if payloadOut(cSess, sessdata.LTypeIPData, 0x04, nil) {
|
||||
if payloadOutCstp(cSess, sessdata.LTypeIPData, 0x04, nil) {
|
||||
return
|
||||
}
|
||||
case 0x04:
|
||||
|
@ -86,7 +86,7 @@ func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
|
||||
for {
|
||||
select {
|
||||
case payload = <-cSess.PayloadOut:
|
||||
case payload = <-cSess.PayloadOutCstp:
|
||||
case <-cSess.CloseChan:
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,19 +9,33 @@ import (
|
|||
)
|
||||
|
||||
func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
||||
dSess := cSess.NewDtlsConn()
|
||||
if dSess == nil {
|
||||
// 创建失败,直接关闭链接
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
base.Debug("LinkDtls return", cSess.IpAddr)
|
||||
_ = conn.Close()
|
||||
cSess.Close()
|
||||
dSess.Close()
|
||||
}()
|
||||
|
||||
var (
|
||||
dead = time.Duration(cSess.CstpDpd+5) * time.Second
|
||||
)
|
||||
|
||||
go dtlsWrite(conn, cSess)
|
||||
go dtlsWrite(conn, dSess, cSess)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
for {
|
||||
|
||||
if time.Now().Sub(now) > time.Second*30 {
|
||||
// return
|
||||
}
|
||||
|
||||
err := conn.SetReadDeadline(time.Now().Add(dead))
|
||||
if err != nil {
|
||||
base.Error("SetDeadline: ", err)
|
||||
|
@ -48,26 +62,33 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
base.Debug("DISCONNECT", cSess.IpAddr)
|
||||
return
|
||||
case 0x03: // DPD-REQ
|
||||
base.Debug("recv DPD-REQ", cSess.IpAddr)
|
||||
if payloadOut(cSess, sessdata.LTypeIPData, 0x04, nil) {
|
||||
// base.Debug("recv DPD-REQ", cSess.IpAddr)
|
||||
payload := &sessdata.Payload{
|
||||
LType: sessdata.LTypeIPData,
|
||||
PType: 0x04,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
select {
|
||||
case cSess.PayloadOutDtls <- payload:
|
||||
case <-dSess.CloseChan:
|
||||
return
|
||||
}
|
||||
case 0x04:
|
||||
base.Debug("recv DPD-RESP", cSess.IpAddr)
|
||||
// base.Debug("recv DPD-RESP", cSess.IpAddr)
|
||||
case 0x00: // DATA
|
||||
if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[1:]) {
|
||||
if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[1:n]) {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dtlsWrite(conn net.Conn, cSess *sessdata.ConnSession) {
|
||||
func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnSession) {
|
||||
defer func() {
|
||||
base.Debug("dtlsWrite return", cSess.IpAddr)
|
||||
_ = conn.Close()
|
||||
cSess.Close()
|
||||
dSess.Close()
|
||||
}()
|
||||
|
||||
var (
|
||||
|
@ -76,9 +97,10 @@ func dtlsWrite(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
)
|
||||
|
||||
for {
|
||||
// dtls优先推送数据
|
||||
select {
|
||||
case payload = <-cSess.PayloadOut:
|
||||
case <-cSess.CloseChan:
|
||||
case payload = <-cSess.PayloadOutDtls:
|
||||
case <-dSess.CloseChan:
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ func LinkTun(cSess *sessdata.ConnSession) error {
|
|||
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu)
|
||||
cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
|
||||
ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
|
||||
cmdstr3 := "true"
|
||||
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
|
||||
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
|
||||
err = execCmd(cmdStrs)
|
||||
if err != nil {
|
||||
|
|
|
@ -2,23 +2,20 @@ package handler
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"sync"
|
||||
"strings"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
"github.com/pion/dtls/v2"
|
||||
)
|
||||
|
||||
var hn string
|
||||
|
||||
var ss sync.Map
|
||||
var (
|
||||
hn string
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 获取主机名称
|
||||
|
@ -27,9 +24,9 @@ func init() {
|
|||
|
||||
func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO 调试信息输出
|
||||
hd, _ := httputil.DumpRequest(r, true)
|
||||
fmt.Println("DumpRequest: ", string(hd))
|
||||
fmt.Println("LinkTunnel", r.RemoteAddr)
|
||||
// hd, _ := httputil.DumpRequest(r, true)
|
||||
// fmt.Println("DumpRequest: ", string(hd))
|
||||
// fmt.Println("LinkTunnel", r.RemoteAddr)
|
||||
|
||||
// 判断session-token的值
|
||||
cookie, err := r.Cookie("webvpn")
|
||||
|
@ -58,14 +55,6 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
localIp := r.Header.Get("X-Cstp-Local-Address-Ip4")
|
||||
mobile := r.Header.Get("X-Cstp-License")
|
||||
|
||||
preMasterSecret, err := hex.DecodeString(masterSecret)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
dtls.Sessions.Store(sess.DtlsSid, preMasterSecret)
|
||||
|
||||
cSess.SetMtu(cstpMtu)
|
||||
cSess.MasterSecret = masterSecret
|
||||
cSess.RemoteAddr = r.RemoteAddr
|
||||
|
@ -81,6 +70,12 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
cSess.CstpDpd = cstpDpd
|
||||
|
||||
dtlsPort := ""
|
||||
if strings.Contains(base.Cfg.ServerDTLSAddr, ":") {
|
||||
ss := strings.Split(base.Cfg.ServerDTLSAddr, ":")
|
||||
dtlsPort = ss[1]
|
||||
}
|
||||
|
||||
base.Debug(cSess.IpAddr, cSess.MacHw, sess.Username, mobile)
|
||||
|
||||
// 返回客户端数据
|
||||
|
@ -126,17 +121,15 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("X-CSTP-MSIE-Proxy-Lockdown", "true")
|
||||
w.Header().Set("X-CSTP-Smartcard-Removal-Disconnect", "true")
|
||||
|
||||
w.Header().Set("X-MTU", fmt.Sprintf("%d", cSess.Mtu)) // 1399
|
||||
w.Header().Set("X-CSTP-MTU", fmt.Sprintf("%d", cSess.Mtu)) // 1399
|
||||
w.Header().Set("X-DTLS-MTU", fmt.Sprintf("%d", cSess.Mtu))
|
||||
|
||||
w.Header().Set("X-DTLS-Session-ID", sess.DtlsSid)
|
||||
w.Header().Set("X-DTLS-Port", "4433")
|
||||
w.Header().Set("X-DTLS-Port", dtlsPort)
|
||||
w.Header().Set("X-DTLS-DPD", fmt.Sprintf("%d", cstpDpd))
|
||||
w.Header().Set("X-DTLS-Keepalive", fmt.Sprintf("%d", base.Cfg.CstpKeepalive))
|
||||
w.Header().Set("X-DTLS-Keepalive", fmt.Sprintf("%d", cstpKeepalive))
|
||||
w.Header().Set("X-DTLS-Rekey-Time", "5400")
|
||||
w.Header().Set("X-DTLS12-CipherSuite", "ECDHE-ECDSA-AES128-GCM-SHA256")
|
||||
// w.Header().Set("X-DTLS12-CipherSuite", "ECDHE-RSA-AES128-GCM-SHA256")
|
||||
|
||||
w.Header().Set("X-CSTP-License", "accept")
|
||||
w.Header().Set("X-CSTP-Routing-Filtering-Ignore", "false")
|
||||
|
@ -173,7 +166,5 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
ss.Store(cSess.Sess.DtlsSid, cSess)
|
||||
|
||||
go LinkCstp(conn, cSess)
|
||||
}
|
||||
|
|
|
@ -35,20 +35,25 @@ func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool
|
|||
}
|
||||
|
||||
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
||||
dSess := cSess.GetDtlsSession()
|
||||
if dSess == nil {
|
||||
return payloadOutCstp(cSess, lType, pType, data)
|
||||
} else {
|
||||
return payloadOutDtls(dSess, lType, pType, data)
|
||||
}
|
||||
}
|
||||
|
||||
func payloadOutCstp(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.PayloadOutCstp <- payload:
|
||||
case <-cSess.CloseChan:
|
||||
closed = true
|
||||
}
|
||||
|
@ -56,6 +61,21 @@ func payloadOutData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool
|
|||
return closed
|
||||
}
|
||||
|
||||
func payloadOutDtls(dSess *sessdata.DtlsSession, lType sessdata.LType, pType byte, data []byte) bool {
|
||||
payload := &sessdata.Payload{
|
||||
LType: lType,
|
||||
PType: pType,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
select {
|
||||
case dSess.CSess.PayloadOutDtls <- payload:
|
||||
case <-dSess.CloseChan:
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Acl规则校验
|
||||
func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
|
||||
if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 {
|
||||
|
|
|
@ -19,7 +19,6 @@ func main() {
|
|||
|
||||
base.Start()
|
||||
handler.Start()
|
||||
|
||||
signalWatch()
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@ import (
|
|||
var (
|
||||
// session_token -> SessUser
|
||||
sessions = make(map[string]*Session)
|
||||
sessMux sync.Mutex
|
||||
// dtlsId -> session_token
|
||||
dtlsIds = make(map[string]string)
|
||||
sessMux sync.RWMutex
|
||||
)
|
||||
|
||||
// 连接sess
|
||||
|
@ -44,11 +46,23 @@ type ConnSession struct {
|
|||
closeOnce sync.Once
|
||||
CloseChan chan struct{}
|
||||
PayloadIn chan *Payload
|
||||
PayloadOut chan *Payload
|
||||
// PayloadOut chan *Payload // 公共ip数据
|
||||
PayloadOutCstp chan *Payload // Cstp的数据
|
||||
PayloadOutDtls chan *Payload // Dtls的数据
|
||||
|
||||
mux sync.RWMutex
|
||||
dSess *DtlsSession // Dtls Session
|
||||
// DSess *atomic.Value
|
||||
}
|
||||
|
||||
type DtlsSession struct {
|
||||
CSess *ConnSession
|
||||
CloseChan chan struct{}
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
mux sync.Mutex
|
||||
mux sync.RWMutex
|
||||
Sid string // auth返回的 session-id
|
||||
Token string // session信息的唯一token
|
||||
DtlsSid string // dtls协议的 session_id
|
||||
|
@ -122,16 +136,17 @@ func NewSession(token string) *Session {
|
|||
|
||||
sessMux.Lock()
|
||||
sessions[token] = sess
|
||||
dtlsIds[sess.DtlsSid] = token
|
||||
sessMux.Unlock()
|
||||
return sess
|
||||
}
|
||||
|
||||
func (s *Session) NewConn() *ConnSession {
|
||||
s.mux.Lock()
|
||||
s.mux.RLock()
|
||||
active := s.IsActive
|
||||
macAddr := s.MacAddr
|
||||
username := s.Username
|
||||
s.mux.Unlock()
|
||||
s.mux.RUnlock()
|
||||
if active {
|
||||
s.CSess.Close()
|
||||
}
|
||||
|
@ -155,13 +170,14 @@ func (s *Session) NewConn() *ConnSession {
|
|||
}
|
||||
|
||||
cSess := &ConnSession{
|
||||
Sess: s,
|
||||
MacHw: macHw,
|
||||
IpAddr: ip,
|
||||
closeOnce: sync.Once{},
|
||||
CloseChan: make(chan struct{}),
|
||||
PayloadIn: make(chan *Payload),
|
||||
PayloadOut: make(chan *Payload),
|
||||
Sess: s,
|
||||
MacHw: macHw,
|
||||
IpAddr: ip,
|
||||
closeOnce: sync.Once{},
|
||||
CloseChan: make(chan struct{}),
|
||||
PayloadIn: make(chan *Payload),
|
||||
PayloadOutCstp: make(chan *Payload),
|
||||
PayloadOutDtls: make(chan *Payload),
|
||||
}
|
||||
|
||||
// 查询group信息
|
||||
|
@ -204,6 +220,43 @@ func (cs *ConnSession) Close() {
|
|||
})
|
||||
}
|
||||
|
||||
// 创建dtls链接
|
||||
func (cs *ConnSession) NewDtlsConn() *DtlsSession {
|
||||
cs.mux.Lock()
|
||||
defer cs.mux.Unlock()
|
||||
|
||||
if cs.dSess != nil {
|
||||
// 判断原有连接存在,不进行创建
|
||||
return nil
|
||||
}
|
||||
|
||||
dSess := &DtlsSession{
|
||||
CSess: cs,
|
||||
CloseChan: make(chan struct{}),
|
||||
closeOnce: sync.Once{},
|
||||
}
|
||||
cs.dSess = dSess
|
||||
return dSess
|
||||
}
|
||||
|
||||
// 关闭dtls链接
|
||||
func (ds *DtlsSession) Close() {
|
||||
ds.closeOnce.Do(func() {
|
||||
base.Info("closeOnce dtls:", ds.CSess.IpAddr)
|
||||
ds.CSess.mux.Lock()
|
||||
defer ds.CSess.mux.Unlock()
|
||||
|
||||
close(ds.CloseChan)
|
||||
ds.CSess.dSess = nil
|
||||
})
|
||||
}
|
||||
|
||||
func (cs *ConnSession) GetDtlsSession() *DtlsSession {
|
||||
cs.mux.RLock()
|
||||
defer cs.mux.RUnlock()
|
||||
return cs.dSess
|
||||
}
|
||||
|
||||
const BandwidthPeriodSec = 2 // 流量速率统计周期(秒)
|
||||
|
||||
func (cs *ConnSession) ratePeriod() {
|
||||
|
@ -272,13 +325,34 @@ func SToken2Sess(stoken string) *Session {
|
|||
}
|
||||
|
||||
func Token2Sess(token string) *Session {
|
||||
sessMux.Lock()
|
||||
defer sessMux.Unlock()
|
||||
sessMux.RLock()
|
||||
defer sessMux.RUnlock()
|
||||
return sessions[token]
|
||||
}
|
||||
|
||||
func Dtls2Sess(dtlsid []byte) *Session {
|
||||
return nil
|
||||
func Dtls2Sess(did string) *Session {
|
||||
sessMux.RLock()
|
||||
defer sessMux.RUnlock()
|
||||
token := dtlsIds[did]
|
||||
return sessions[token]
|
||||
}
|
||||
|
||||
func Dtls2MasterSecret(did string) string {
|
||||
sessMux.RLock()
|
||||
token := dtlsIds[did]
|
||||
sess := sessions[token]
|
||||
sessMux.RUnlock()
|
||||
|
||||
if sess == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
sess.mux.RLock()
|
||||
defer sess.mux.RUnlock()
|
||||
if sess.CSess == nil {
|
||||
return ""
|
||||
}
|
||||
return sess.CSess.MasterSecret
|
||||
}
|
||||
|
||||
func DelSess(token string) {
|
||||
|
@ -298,8 +372,8 @@ func CloseSess(token string) {
|
|||
}
|
||||
|
||||
func CloseCSess(token string) {
|
||||
sessMux.Lock()
|
||||
defer sessMux.Unlock()
|
||||
sessMux.RLock()
|
||||
defer sessMux.RUnlock()
|
||||
sess, ok := sessions[token]
|
||||
if !ok {
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue