From eee99fcb0d596418aa4159805586a28d15f9d9ea Mon Sep 17 00:00:00 2001 From: bjdgyc Date: Tue, 18 Aug 2020 16:39:46 +0800 Subject: [PATCH] init --- .gitignore | 6 +- README.md | 73 ++++++++++++++++++- common/app_ver.go | 6 ++ common/assert_test.go | 10 +++ common/cfg_server.go | 77 ++++++++++++++++++++ common/cfg_user.go | 91 ++++++++++++++++++++++++ common/cfg_user_test.go | 33 +++++++++ common/flag.go | 44 ++++++++++++ common/ip_pool.go | 139 ++++++++++++++++++++++++++++++++++++ common/ip_pool_test.go | 50 +++++++++++++ common/log.go | 7 ++ conf/.gitignore | 3 + conf/server.toml | 48 +++++++++++++ conf/user.toml | 17 +++++ go.mod | 10 +++ go.sum | 16 +++++ handler/base.go | 74 ++++++++++++++++++++ handler/dtls.go | 6 ++ handler/link_auth.go | 151 ++++++++++++++++++++++++++++++++++++++++ handler/link_cstp.go | 107 ++++++++++++++++++++++++++++ handler/link_home.go | 26 +++++++ handler/link_tun.go | 124 +++++++++++++++++++++++++++++++++ handler/link_tunnel.go | 120 +++++++++++++++++++++++++++++++ handler/proto.go | 63 +++++++++++++++++ handler/server.go | 72 +++++++++++++++++++ handler/session.go | 142 +++++++++++++++++++++++++++++++++++++ main.go | 39 +++++++++++ 27 files changed, 1551 insertions(+), 3 deletions(-) create mode 100644 common/app_ver.go create mode 100644 common/assert_test.go create mode 100644 common/cfg_server.go create mode 100644 common/cfg_user.go create mode 100644 common/cfg_user_test.go create mode 100644 common/flag.go create mode 100644 common/ip_pool.go create mode 100644 common/ip_pool_test.go create mode 100644 common/log.go create mode 100644 conf/.gitignore create mode 100644 conf/server.toml create mode 100644 conf/user.toml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handler/base.go create mode 100644 handler/dtls.go create mode 100644 handler/link_auth.go create mode 100644 handler/link_cstp.go create mode 100644 handler/link_home.go create mode 100644 handler/link_tun.go create mode 100644 handler/link_tunnel.go create mode 100644 handler/proto.go create mode 100644 handler/server.go create mode 100644 handler/session.go create mode 100644 main.go diff --git a/.gitignore b/.gitignore index 66fd13c..e26bd7b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,8 @@ *.out # Dependency directories (remove the comment below to include it) -# vendor/ +vendor/ + + +.idea/ +anylink \ No newline at end of file diff --git a/README.md b/README.md index 5b96c1d..6864664 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,71 @@ -# anylink -AnyLink是一个企业级远程办公vpn软件 +# AnyLink + +AnyLink 是一个企业级远程办公vpn软件,可以支持多人同时在线使用。 + +## Introduction + +AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannopoulos-openconnect-02) 协议开发,并且借鉴了 [ocserv](http://ocserv.gitlab.io/www/index.html) 的开发思路,使其可以同时兼容 AnyConnect 客户端。 + +AnyLink 使用TLS/DTLS进行数据加密,因此需要RSA或ECC证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。 + +AnyLink 服务端仅在CentOs7测试通过,如需要安装在其他系统,需要服务端支持tun功能、ip设置命令。 + +## Installation + +``` +git clone https://github.com/bjdgyc/anylink.git +cd anylink +go build -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`" +#注意使用root权限运行 +sudo ./anylink -conf="conf/server.toml" +``` + +## Feature + +- [x] IP分配 +- [x] TLS-TCP通道 +- [x] 兼容AnyConnect +- [x] 多用户支持 +- [ ] DTLS-UDP通道 +- [ ] 后台管理界面 +- [ ] 用户组支持 +- [ ] TOTP令牌支持 +- [ ] 流量控制 +- [ ] 访问权限管理 + +## Config + +- [conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml) +- [conf/user.toml](https://github.com/bjdgyc/anylink/blob/master/conf/user.toml) + +## Setting + +1. 开启服务器转发 + ``` + # flie: /etc/sysctl.conf + net.ipv4.ip_forward = 1 + + #执行如下命令 + sysctl -w net.ipv4.ip_forward=1 + ``` + +2. 设置nat转发规则 + ``` + # eth0为服务器内网网卡 + iptables -t nat -A POSTROUTING -s 192.168.10.0/255.255.255.0 -o eth0 -j MASQUERADE + ``` + +3. 使用AnyConnect客户端连接即可 + + +## License + +本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。 + + + + + + + + diff --git a/common/app_ver.go b/common/app_ver.go new file mode 100644 index 0000000..485658c --- /dev/null +++ b/common/app_ver.go @@ -0,0 +1,6 @@ +package common + +const ( + APP_NAME = "AnyLink" + APP_VER = "0.0.1" +) diff --git a/common/assert_test.go b/common/assert_test.go new file mode 100644 index 0000000..90fe9c2 --- /dev/null +++ b/common/assert_test.go @@ -0,0 +1,10 @@ +package common + +import "testing" + +func AssertTrue(t *testing.T, a bool) { + t.Helper() + if !a { + t.Errorf("Not True %t", a) + } +} diff --git a/common/cfg_server.go b/common/cfg_server.go new file mode 100644 index 0000000..a302ad2 --- /dev/null +++ b/common/cfg_server.go @@ -0,0 +1,77 @@ +package common + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/pelletier/go-toml" +) + +var ( + ServerCfg = &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 { + UserFile string `toml:"user_file"` + ServerAddr string `toml:"server_addr"` + DebugAddr string `toml:"debug_addr"` + CertFile string `toml:"cert_file"` + CertKey string `toml:"cert_key"` + LinkGroups []string `toml:"link_groups"` + DefaultGroup string `toml:"default_group"` + Banner string `toml:"banner"` // 欢迎语 + CstpDpd int `toml:"cstp_dpd"` // Dead peer detection in seconds + CstpKeepalive int `toml:"cstp_keepalive"` // in seconds + SessionTimeout int `toml:"session_timeout"` // in seconds + AuthTimeout int `toml:"auth_timeout"` // in seconds + MaxClient int `toml:"max_client"` + MaxUserClient int `toml:"max_user_client"` + Ipv4Network string `toml:"ipv4_network"` // 192.168.1.0 + Ipv4Netmask string `toml:"ipv4_netmask"` // 255.255.255.0 + Ipv4GateWay string `toml:"-"` + Include []string `toml:"include"` // 10.10.10.0/255.255.255.0 + Exclude []string `toml:"exclude"` // 192.168.5.0/255.255.255.0 + ClientDns []string `toml:"client_dns"` // 114.114.114.114 + AllowLan bool `toml:"allow_lan"` // 允许本地LAN访问vpn网络 +} + +func loadServer() { + b, err := ioutil.ReadFile(serverFile) + if err != nil { + panic(err) + } + err = toml.Unmarshal(b, ServerCfg) + if err != nil { + panic(err) + } + + sf, _ := filepath.Abs(serverFile) + base := filepath.Dir(sf) + + // 转换成绝对路径 + ServerCfg.UserFile = getAbsPath(base, ServerCfg.UserFile) + ServerCfg.CertFile = getAbsPath(base, ServerCfg.CertFile) + ServerCfg.CertKey = getAbsPath(base, ServerCfg.CertKey) + + fmt.Printf("ServerCfg: %+v \n", ServerCfg) +} + +func getAbsPath(base, cfile string) string { + abs := filepath.IsAbs(cfile) + if abs { + return cfile + } + return filepath.Join(base, cfile) +} diff --git a/common/cfg_user.go b/common/cfg_user.go new file mode 100644 index 0000000..f023dc6 --- /dev/null +++ b/common/cfg_user.go @@ -0,0 +1,91 @@ +package common + +import ( + "crypto/sha1" + "fmt" + "io/ioutil" + "sync" + + "github.com/pelletier/go-toml" +) + +var ( + users = map[string]User{} + limitClient = map[string]int{"_all": 0} + limitMux = sync.Mutex{} +) + +type User struct { + Group string `toml:"group"` + Username string `toml:"-"` + Password string `toml:"password"` + OtpSecret string `toml:"otp_secret"` +} + +func CheckUser(name, pwd, group string) bool { + user, ok := users[name] + if !ok { + return false + } + pwdHash := hashPass(pwd) + if user.Password == pwdHash { + return true + } + return false +} + +func hashPass(pwd string) string { + sum := sha1.Sum([]byte(pwd)) + return fmt.Sprintf("%x", sum) +} + +func LimitClient(name string, close bool) bool { + limitMux.Lock() + defer limitMux.Unlock() + // defer fmt.Println(limitClient) + + _all := limitClient["_all"] + c, ok := limitClient[name] + if !ok { // 不存在用户 + limitClient[name] = 0 + } + + if close { + limitClient[name] = c - 1 + limitClient["_all"] = _all - 1 + return true + } + + // 全局判断 + if _all >= ServerCfg.MaxClient { + return false + } + + // 超出同一个用户限制 + if c >= ServerCfg.MaxUserClient { + return false + } + + limitClient[name] = c + 1 + limitClient["_all"] = _all + 1 + return true +} + +func loadUser() { + b, err := ioutil.ReadFile(ServerCfg.UserFile) + if err != nil { + panic(err) + } + err = toml.Unmarshal(b, &users) + if err != nil { + panic(err) + } + + // 添加用户名 + for k, v := range users { + v.Username = k + users[k] = v + } + + fmt.Println("users:", users) +} diff --git a/common/cfg_user_test.go b/common/cfg_user_test.go new file mode 100644 index 0000000..6466534 --- /dev/null +++ b/common/cfg_user_test.go @@ -0,0 +1,33 @@ +package common + +import ( + "testing" +) + +func TestCheckUser(t *testing.T) { + users["user1"] = User{Password: "7c4a8d09ca3762af61e59520943dc26494f8941b"} + users["user2"] = User{Password: "7c4a8d09ca3762af61e59520943dc26494f8941c"} + + var res bool + res = CheckUser("user1", "123456", "") + AssertTrue(t, res == true) + + res = CheckUser("user2", "123457", "") + AssertTrue(t, res == false) +} + +func TestLimitClient(t *testing.T) { + ServerCfg.MaxClient = 2 + ServerCfg.MaxUserClient = 1 + + res1 := LimitClient("user1", false) + res2 := LimitClient("user1", false) + res3 := LimitClient("user2", false) + res4 := LimitClient("user3", false) + + AssertTrue(t, res1 == true) + AssertTrue(t, res2 == false) + AssertTrue(t, res3 == true) + AssertTrue(t, res4 == false) + +} diff --git a/common/flag.go b/common/flag.go new file mode 100644 index 0000000..bf0c437 --- /dev/null +++ b/common/flag.go @@ -0,0 +1,44 @@ +package common + +import ( + "flag" + "fmt" + "os" + "runtime" +) + +var ( + // 提交id + CommitId string + // 配置文件 + serverFile string + passwd string + // 显示版本信息 + rev bool +) + +func initFlag() { + flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config file path") + flag.StringVar(&passwd, "pass", "", "generation a sha1 password") + flag.BoolVar(&rev, "rev", false, "display version info") + flag.Parse() + + if passwd != "" { + pwdHash := hashPass(passwd) + fmt.Printf("passwd-sha1:%s\n", pwdHash) + 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) + } +} + +func InitConfig() { + initFlag() + loadServer() + loadUser() + initIpPool() +} diff --git a/common/ip_pool.go b/common/ip_pool.go new file mode 100644 index 0000000..3f84e20 --- /dev/null +++ b/common/ip_pool.go @@ -0,0 +1,139 @@ +package common + +import ( + "encoding/binary" + "math" + "net" + "sync" + "time" +) + +const ( + // ip租期 (秒) + IpLease = 1209600 +) + +var ( + ipPool = &IpPoolConfig{} + macIps = map[string]*MacIp{} +) + +type MacIp struct { + IsActive bool + Ip net.IP + MacAddr string + LastLogin time.Time +} + +type IpPoolConfig struct { + mux sync.Mutex + // 计算动态ip + Ipv4Net *net.IPNet + Ipv4GateWay net.IP + IpLongMin uint32 + IpLongMax uint32 + IpLongNow uint32 +} + +func initIpPool() { + // ip地址 + ip := net.ParseIP(ServerCfg.Ipv4Network) + // 子网掩码 + maskIp := net.ParseIP(ServerCfg.Ipv4Netmask).To4() + mask := net.IPMask(maskIp) + + ipNet := &net.IPNet{IP: ip, Mask: mask} + ipPool.Ipv4Net = ipNet + + // 网络地址零值 + min := binary.BigEndian.Uint32(ip.Mask(mask)) + // 广播地址 + one, _ := ipNet.Mask.Size() + max := min | uint32(math.Pow(2, float64(32-one))-1) + + min += 1 // 网关 + ipPool.Ipv4GateWay = long2ip(min) + ServerCfg.Ipv4GateWay = ipPool.Ipv4GateWay.String() + // 第一个可用地址 + min += 1 + ipPool.IpLongMin = min + ipPool.IpLongMax = max + ipPool.IpLongNow = min +} + +func long2ip(i uint32) net.IP { + ip := make([]byte, 4) + binary.BigEndian.PutUint32(ip, i) + return ip +} + +// 获取动态ip +func AcquireIp(macAddr string) net.IP { + ipPool.mux.Lock() + defer ipPool.mux.Unlock() + tNow := time.Now() + + // 判断已经分配过 + if mi, ok := macIps[macAddr]; ok { + mi.IsActive = true + mi.LastLogin = tNow + return mi.Ip + } + + // ip池分配完之前 + if ipPool.IpLongNow < ipPool.IpLongMax { + // 递增分配一个ip + ip := long2ip(ipPool.IpLongNow) + mi := &MacIp{IsActive: true, Ip: ip, MacAddr: macAddr, LastLogin: tNow} + macIps[macAddr] = mi + ipPool.IpLongNow += 1 + return ip + } + + // 查找过期数据 + farMi := &MacIp{LastLogin: tNow} + for k, v := range macIps { + // 跳过活跃连接 + if v.IsActive { + continue + } + + // 已经超过租期 + if tNow.Sub(v.LastLogin) > IpLease*time.Second { + delete(macIps, k) + ip := v.Ip + mi := &MacIp{IsActive: true, Ip: ip, MacAddr: macAddr, LastLogin: tNow} + macIps[macAddr] = mi + return ip + } + + // 其他情况判断最早登陆的mac + if v.LastLogin.Before(farMi.LastLogin) { + farMi = v + } + } + + // 全都在线,没有数据可用 + if farMi.MacAddr == "" { + return nil + } + + // 使用最早登陆的mac地址 + delete(macIps, farMi.MacAddr) + ip := farMi.Ip + mi := &MacIp{IsActive: true, Ip: ip, MacAddr: macAddr, LastLogin: tNow} + macIps[macAddr] = mi + return ip +} + +// 回收ip +func ReleaseIp(ip net.IP, macAddr string) { + ipPool.mux.Lock() + defer ipPool.mux.Unlock() + if mi, ok := macIps[macAddr]; ok { + if mi.Ip.Equal(ip) { + mi.IsActive = false + mi.LastLogin = time.Now() + } + } +} diff --git a/common/ip_pool_test.go b/common/ip_pool_test.go new file mode 100644 index 0000000..94ba1ec --- /dev/null +++ b/common/ip_pool_test.go @@ -0,0 +1,50 @@ +package common + +import ( + "fmt" + "net" + "testing" +) + +func TestAcquireIp(t *testing.T) { + ServerCfg.Ipv4Network = "192.168.1.0" + ServerCfg.Ipv4Netmask = "255.255.255.0" + macIps = map[string]*MacIp{} + initIpPool() + + var ip net.IP + + for i := 2; i <= 100; i++ { + ip = AcquireIp(fmt.Sprintf("mac-%d", i)) + } + ip = AcquireIp(fmt.Sprintf("mac-new")) + AssertTrue(t, ip.Equal(net.IPv4(192, 168, 1, 101))) + for i := 102; i <= 254; i++ { + ip = AcquireIp(fmt.Sprintf("mac-%d", i)) + } + ip = AcquireIp(fmt.Sprintf("mac-nil")) + AssertTrue(t, ip == nil) +} + +func TestReleaseIp(t *testing.T) { + ServerCfg.Ipv4Network = "192.168.1.0" + ServerCfg.Ipv4Netmask = "255.255.255.0" + macIps = map[string]*MacIp{} + initIpPool() + + var ip net.IP + + // 分配完所有数据 + for i := 2; i <= 254; i++ { + ip = AcquireIp(fmt.Sprintf("mac-%d", i)) + } + + ip = AcquireIp(fmt.Sprintf("mac-more")) + AssertTrue(t, ip == nil) + + ReleaseIp(net.IPv4(192, 168, 1, 123), "mac-123") + ReleaseIp(net.IPv4(192, 168, 1, 100), "mac-100") + ip = AcquireIp(fmt.Sprintf("mac-new")) + // 最早过期的ip + AssertTrue(t, ip.Equal(net.IPv4(192, 168, 1, 123))) +} diff --git a/common/log.go b/common/log.go new file mode 100644 index 0000000..18b8cb0 --- /dev/null +++ b/common/log.go @@ -0,0 +1,7 @@ +package common + +import "log" + +func init() { + log.SetFlags(log.LstdFlags | log.Lshortfile) +} diff --git a/conf/.gitignore b/conf/.gitignore new file mode 100644 index 0000000..eef0a03 --- /dev/null +++ b/conf/.gitignore @@ -0,0 +1,3 @@ +#过滤本地证书文件 +vpn_cert.key +vpn_cert.pem \ No newline at end of file diff --git a/conf/server.toml b/conf/server.toml new file mode 100644 index 0000000..0d765b8 --- /dev/null +++ b/conf/server.toml @@ -0,0 +1,48 @@ +#服务配置信息 + +#其他配置文件,可以使用绝对路径 +#或者相对于server.toml的路径 +user_file = "./user.toml" +#证书文件 +cert_file = "./vpn_cert.pem" +cert_key = "./vpn_cert.key" + +#服务监听的地址 +server_addr = ":443" +debug_addr = "127.0.0.1:8800" + +#用户组 +link_groups = ["one", "two"] +#默认选择的组 +default_group = "one" + +#登陆成功的欢迎语 +banner = "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为!" + +#客户端失效检测时间(秒) dpd > keepalive +cstp_dpd = 30 +cstp_keepalive = 20 +#session过期时间,用于断线重连,0永不过期 +session_timeout = 3600 +auth_timeout = 0 + + +#最大客户端数量 +max_client = 300 +#单个用户同时在线数量 +max_user_client = 3 + +#客户端分配的ip地址池 +ipv4_network = "192.168.10.0" +ipv4_netmask = "255.255.255.0" +#需加密传输的ip规则 +#include = ["10.10.10.0/255.255.255.0"] +#非加密传输的ip规则 +#exclude = ["192.168.5.0/255.255.255.0"] +#客户端使用的dns +client_dns = ["114.114.114.114"] +#是否允许本地LAN访问vpn网络 +allow_lan = true + + + diff --git a/conf/user.toml b/conf/user.toml new file mode 100644 index 0000000..eb4aa57 --- /dev/null +++ b/conf/user.toml @@ -0,0 +1,17 @@ +#用户信息配置 +[test] +group = "group1" +#密码需要使用 sha1,以下密码为 123456 +password = "7c4a8d09ca3762af61e59520943dc26494f8941b" + + +[user] +group = "group2" +#以下密码为 123456 +password = "7c4a8d09ca3762af61e59520943dc26494f8941b" + + + + + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bb165b6 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/bjdgyc/anylink + +go 1.14 + +require ( + github.com/julienschmidt/httprouter v1.3.0 + github.com/pelletier/go-toml v1.8.0 + github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 + golang.org/x/sys v0.0.0-20200817155316-9781c653f443 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..605c35a --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +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= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/handler/base.go b/handler/base.go new file mode 100644 index 0000000..4dcf5ba --- /dev/null +++ b/handler/base.go @@ -0,0 +1,74 @@ +package handler + +import ( + "encoding/xml" + "fmt" + "net/http" + "strings" + + "github.com/julienschmidt/httprouter" +) + +type ClientRequest struct { + XMLName xml.Name `xml:"config-auth"` + Client string `xml:"client,attr"` // 一般都是 vpn + Type string `xml:"type,attr"` // 请求类型 init logout auth-reply + AggregateAuthVersion string `xml:"aggregate-auth-version,attr"` // 一般都是 2 + Version string `xml:"version"` // 客户端版本号 + GroupAccess string `xml:"group-access"` // 请求的地址 + GroupSelect string `xml:"group-select"` // 选择的组名 + SessionId string `xml:"session-id"` + SessionToken string `xml:"session-token"` + Auth auth `xml:"auth"` + DeviceId deviceId `xml:"device-id"` + MacAddressList macAddressList `xml:"mac-address-list"` +} + +type auth struct { + Username string `xml:"username"` + Password string `xml:"password"` +} + +type deviceId struct { + ComputerName string `xml:"computer-name,attr"` + DeviceType string `xml:"device-type,attr"` + PlatformVersion string `xml:"platform-version,attr"` + UniqueId string `xml:"unique-id,attr"` + UniqueIdGlobal string `xml:"unique-id-global,attr"` +} + +type macAddressList struct { + MacAddress string `xml:"mac-address"` +} + +// 判断anyconnect客户端 +func checkVpnClient(h httprouter.Handle) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + // TODO 调试信息输出 + // hd, _ := httputil.DumpRequest(r, true) + // fmt.Println("DumpRequest: ", string(hd)) + + user_Agent := 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(user_Agent, "anyconnect") && + x_Aggregate_Auth == "1" && x_Transcend_Version == "1" { + h(w, r, ps) + } else { + w.WriteHeader(http.StatusForbidden) + fmt.Fprintf(w, "error request") + } + } +} + +func setCommonHeader(w http.ResponseWriter) { + // Content-Length Date 默认已经存在 + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Transfer-Encoding", "chunked") + w.Header().Set("Connection", "keep-alive") + w.Header().Set("X-Frame-Options", "SAMEORIGIN") + w.Header().Set("X-Aggregate-Auth", "1") + w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") +} diff --git a/handler/dtls.go b/handler/dtls.go new file mode 100644 index 0000000..32c9470 --- /dev/null +++ b/handler/dtls.go @@ -0,0 +1,6 @@ +package handler + +// 暂时没有实现 +func startDtls() { + +} diff --git a/handler/link_auth.go b/handler/link_auth.go new file mode 100644 index 0000000..af79224 --- /dev/null +++ b/handler/link_auth.go @@ -0,0 +1,151 @@ +package handler + +import ( + "encoding/xml" + "io" + "io/ioutil" + "net/http" + "strings" + "text/template" + + "github.com/bjdgyc/anylink/common" + "github.com/julienschmidt/httprouter" +) + +func LinkAuth(w http.ResponseWriter, r *http.Request, params httprouter.Params) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + defer r.Body.Close() + + cr := ClientRequest{} + err = xml.Unmarshal(body, &cr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + // fmt.Printf("%+v \n", cr) + + setCommonHeader(w) + if cr.Type == "logout" { + // 退出删除session信息 + if cr.SessionToken != "" { + DelSessByStoken(cr.SessionToken) + } + w.WriteHeader(http.StatusOK) + return + } + + if cr.Type == "init" { + w.WriteHeader(http.StatusOK) + data := RequestData{Group: cr.GroupSelect, Groups: common.ServerCfg.LinkGroups} + tplRequest(tpl_request, w, data) + return + } + + // 登陆参数判断 + if cr.Type != "auth-reply" || cr.Auth.Username == "" || cr.Auth.Password == "" { + w.WriteHeader(http.StatusBadRequest) + return + } + + // TODO 用户密码校验 + if !common.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect) { + w.WriteHeader(http.StatusOK) + data := RequestData{Group: cr.GroupSelect, Groups: common.ServerCfg.LinkGroups, Error: true} + tplRequest(tpl_request, w, data) + return + } + + // 创建新的session信息 + sess := NewSession() + sess.UserName = cr.Auth.Username + sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress) + cd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token, + Banner: common.ServerCfg.Banner} + w.WriteHeader(http.StatusOK) + tplRequest(tpl_complete, w, cd) +} + +const ( + tpl_request = iota + tpl_complete +) + +func tplRequest(typ int, w io.Writer, data RequestData) { + if typ == tpl_request { + t, _ := template.New("auth_request").Parse(auth_request) + t.Execute(w, data) + return + } + + if strings.Contains(data.Banner, "\n") { + // 替换xml文件的换行符 + data.Banner = strings.ReplaceAll(data.Banner, "\n", " ") + } + t, _ := template.New("auth_complete").Parse(auth_complete) + t.Execute(w, data) +} + +// 设置输出信息 +type RequestData struct { + Groups []string + Group string + Error bool + // complete + SessionId string + SessionToken string + Banner string +} + +var auth_request = ` + + + {{.Group}} + {{.Group}} + 168179266 + 1595829378234 + multiple-cert + single-sign-on-v2 + + + Login + 请输入你的用户名和密码 + + {{if .Error}} + 登陆失败: %s + {{end}} +
+ + + +
+
+
+` + +var auth_complete = ` + + {{.SessionId}} + {{.SessionToken}} + + {{.Banner}} + + + + ssl-dhe + + + + 240B97A685B2BFA66AD699B90AAC49EA66495D69 + + + + +` diff --git a/handler/link_cstp.go b/handler/link_cstp.go new file mode 100644 index 0000000..4d8337e --- /dev/null +++ b/handler/link_cstp.go @@ -0,0 +1,107 @@ +package handler + +import ( + "encoding/binary" + "log" + "net" + "time" + + "github.com/bjdgyc/anylink/common" +) + +func LinkCstp(conn net.Conn, sess *Session) { + // fmt.Println("HandlerCstp") + defer func() { + conn.Close() + sess.Close() + log.Println("LinkCstp return") + }() + + var ( + err error + dataLen uint16 + dead = time.Duration(common.ServerCfg.CstpDpd+2) * time.Second + ) + + go cstpWrite(conn, sess) + + for { + // 设置超时限制 + err = conn.SetDeadline(time.Now().Add(dead)) + if err != nil { + log.Println("SetDeadline: ", err) + return + } + hdata := make([]byte, 1500) + _, err = conn.Read(hdata) + if err != nil { + log.Println("read hdata: ", err) + return + } + + switch hdata[6] { + case 0x07: // KEEPALIVE + // do nothing + // fmt.Println("keepalive") + case 0x05: // DISCONNECT + // fmt.Println("DISCONNECT") + return + case 0x03: // DPD-REQ + // fmt.Println("DPD-REQ") + payload := &Payload{ + ptype: 0x04, // DPD-RESP + } + // 直接返回给客户端 resp + select { + case sess.PayloadOut <- payload: + case <-sess.Closed: + return + } + break + case 0x00: + dataLen = binary.BigEndian.Uint16(hdata[4:6]) // 4,5 + payload := &Payload{ + ptype: 0x00, // DPD-RESP + data: hdata[8 : 8+dataLen], + } + select { + case sess.PayloadIn <- payload: + case <-sess.Closed: + return + } + } + } +} + +func cstpWrite(conn net.Conn, sess *Session) { + defer func() { + conn.Close() + sess.Close() + log.Println("cstpWrite return") + }() + + var ( + err error + header []byte + payload *Payload + ) + + for { + select { + case payload = <-sess.PayloadOut: + case <-sess.Closed: + return + } + + header = []byte{'S', 'T', 'F', 0x01, 0x00, 0x00, payload.ptype, 0x00} + if payload.ptype == 0x00 { // data + binary.BigEndian.PutUint16(header[4:6], uint16(len(payload.data))) + header = append(header, payload.data...) + } + _, err = conn.Write(header) + if err != nil { + log.Println("write err", err) + return + } + } +} diff --git a/handler/link_home.go b/handler/link_home.go new file mode 100644 index 0000000..41aaa7c --- /dev/null +++ b/handler/link_home.go @@ -0,0 +1,26 @@ +package handler + +import ( + "fmt" + "net/http" + "net/http/httputil" + "strings" + + "github.com/julienschmidt/httprouter" +) + +func LinkHome(w http.ResponseWriter, r *http.Request, params httprouter.Params) { + hu, _ := httputil.DumpRequest(r, true) + fmt.Println("DumpHome: ", string(hu)) + fmt.Println(r.RemoteAddr) + + connection := strings.ToLower(r.Header.Get("Connection")) + if connection == "close" { + w.Header().Set("Connection", "close") + w.WriteHeader(http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, "hello world") +} diff --git a/handler/link_tun.go b/handler/link_tun.go new file mode 100644 index 0000000..567a32d --- /dev/null +++ b/handler/link_tun.go @@ -0,0 +1,124 @@ +package handler + +import ( + "fmt" + "log" + "os/exec" + + "github.com/bjdgyc/anylink/common" + "github.com/songgao/water" +) + +func testTun() { + // 测试tun + cfg := water.Config{ + DeviceType: water.TUN, + } + + ifce, err := water.New(cfg) + if err != nil { + log.Fatal("open tun err: ", err) + } + // 测试ip命令 + cmdstr := fmt.Sprintf("ip link set dev %s up mtu %s multicast off", ifce.Name(), "1399") + err = execCmd([]string{cmdstr}) + if err != nil { + log.Fatal("ip cmd err: ", err) + } + ifce.Close() +} + +// 创建tun网卡 +func LinkTun(sess *Session) { + defer func() { + sess.Close() + log.Println("LinkTun return") + }() + + cfg := water.Config{ + DeviceType: water.TUN, + } + + ifce, err := water.New(cfg) + if err != nil { + log.Println(err) + return + } + // log.Printf("Interface Name: %s\n", ifce.Name()) + sess.TunName = ifce.Name() + defer ifce.Close() + + // arp on + cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %s multicast off", ifce.Name(), sess.Mtu) + cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32", + ifce.Name(), common.ServerCfg.Ipv4GateWay, sess.NetIp) + 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 { + return + } + + go tunRead(ifce, sess) + + var payload *Payload + + for { + select { + case payload = <-sess.PayloadIn: + case <-sess.Closed: + return + } + + // ip_src := waterutil.IPv4Source(payload.data) + // ip_des := waterutil.IPv4Destination(payload.data) + // ip_port := waterutil.IPv4DestinationPort(payload.data) + // fmt.Println("write: ", ip_src, ip_des.String(), ip_port, len(payload.data)) + + _, err = ifce.Write(payload.data) + if err != nil { + log.Println("tun Write err", err) + return + } + } + +} + +func tunRead(ifce *water.Interface, sess *Session) { + var ( + err error + n int + ) + + for { + packet := make([]byte, 1500) + n, err = ifce.Read(packet) + if err != nil { + log.Println("tun Read err", n, err) + return + } + + payload := &Payload{ + ptype: 0x00, + data: packet[:n], + } + + select { + case sess.PayloadOut <- payload: + case <-sess.Closed: + return + } + } +} + +func execCmd(cmdStrs []string) error { + for _, cmdStr := range cmdStrs { + cmd := exec.Command("bash", "-c", cmdStr) + b, err := cmd.CombinedOutput() + if err != nil { + log.Println(string(b), err) + return err + } + } + return nil +} diff --git a/handler/link_tunnel.go b/handler/link_tunnel.go new file mode 100644 index 0000000..b8c66ca --- /dev/null +++ b/handler/link_tunnel.go @@ -0,0 +1,120 @@ +package handler + +import ( + "fmt" + "net/http" + "os" + + "github.com/bjdgyc/anylink/common" +) + +var hn string + +func init() { + // 获取主机名称 + hn, _ = os.Hostname() +} + +func LinkTunnel(w http.ResponseWriter, r *http.Request) { + // TODO 调试信息输出 + // hd, _ := httputil.DumpRequest(r, true) + // fmt.Println("DumpRequest: ", string(hd)) + // fmt.Println(r.RemoteAddr) + + // 判断session-token的值 + cookie, err := r.Cookie("webvpn") + if err != nil || cookie.Value == "" { + w.WriteHeader(http.StatusBadRequest) + return + } + + sess := SToken2Sess(cookie.Value) + if sess == nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + // 开启link + sess.StartLink() + if sess.NetIp == nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + // 客户端信息 + cstp_mtu := r.Header.Get("X-CSTP-MTU") + master_Secret := r.Header.Get("X-DTLS-Master-Secret") + sess.MasterSecret = master_Secret + sess.Mtu = cstp_mtu + sess.RemoteAddr = r.RemoteAddr + + w.Header().Set("Server", fmt.Sprintf("%s %s", common.APP_NAME, common.APP_VER)) + w.Header().Set("X-CSTP-Version", "1") + w.Header().Set("X-CSTP-Protocol", "Copyright (c) 2004 Cisco Systems, Inc.") + w.Header().Set("X-CSTP-Address", sess.NetIp.String()) // 分配的ip地址 + w.Header().Set("X-CSTP-Netmask", common.ServerCfg.Ipv4Netmask) // 子网掩码 + w.Header().Set("X-CSTP-Hostname", hn) // 机器名称 + for _, v := range common.ServerCfg.ClientDns { + w.Header().Add("X-CSTP-DNS", v) // dns地址 + } + // 允许本地LAN访问vpn网络,必须放在路由的第一个 + if common.ServerCfg.AllowLan { + w.Header().Set("X-CSTP-Split-Exclude", "0.0.0.0/255.255.255.255") + } + // 允许的路由 + for _, v := range common.ServerCfg.Include { + w.Header().Add("X-CSTP-Split-Include", v) + } + // 不允许的路由 + for _, v := range common.ServerCfg.Exclude { + w.Header().Add("X-CSTP-Split-Exclude", v) + } + // w.Header().Add("X-CSTP-Split-Include", "192.168.0.0/255.255.0.0") + // w.Header().Add("X-CSTP-Split-Exclude", "10.1.5.2/255.255.255.255") + + w.Header().Set("X-CSTP-Lease-Duration", fmt.Sprintf("%d", common.IpLease)) // ip地址租期 + w.Header().Set("X-CSTP-Session-Timeout", "none") + w.Header().Set("X-CSTP-Session-Timeout-Alert-Interval", "60") + w.Header().Set("X-CSTP-Session-Timeout-Remaining", "none") + w.Header().Set("X-CSTP-Idle-Timeout", "18000") + w.Header().Set("X-CSTP-Disconnected-Timeout", "18000") + w.Header().Set("X-CSTP-Keep", "true") + w.Header().Set("X-CSTP-Tunnel-All-DNS", "false") + w.Header().Set("X-CSTP-Rekey-Time", "5400") + w.Header().Set("X-CSTP-Rekey-Method", "new-tunnel") + w.Header().Set("X-CSTP-DPD", fmt.Sprintf("%d", common.ServerCfg.CstpDpd)) // 30 Dead peer detection in seconds + w.Header().Set("X-CSTP-Keepalive", fmt.Sprintf("%d", common.ServerCfg.CstpKeepalive)) // 20 + w.Header().Set("X-CSTP-Banner", "welcome") // urlencode + w.Header().Set("X-CSTP-MSIE-Proxy-Lockdown", "true") + w.Header().Set("X-CSTP-Smartcard-Removal-Disconnect", "true") + + w.Header().Set("X-CSTP-MTU", cstp_mtu) // 1399 + w.Header().Set("X-DTLS-MTU", cstp_mtu) + + w.Header().Set("X-DTLS-Session-ID", sess.DtlsSid) + w.Header().Set("X-DTLS-Port", "4433") + w.Header().Set("X-DTLS-Keepalive", fmt.Sprintf("%d", common.ServerCfg.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") + w.Header().Set("X-CSTP-Quarantine", "false") + w.Header().Set("X-CSTP-Disable-Always-On-VPN", "false") + w.Header().Set("X-CSTP-Client-Bypass-Protocol", "false") + w.Header().Set("X-CSTP-TCP-Keepalive", "false") + // w.Header().Set("X-CSTP-Post-Auth-XML", ``) + w.WriteHeader(http.StatusOK) + + hj := w.(http.Hijacker) + conn, _, err := hj.Hijack() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + // 开始数据处理 + go LinkTun(sess) + go LinkCstp(conn, sess) +} diff --git a/handler/proto.go b/handler/proto.go new file mode 100644 index 0000000..12b51ca --- /dev/null +++ b/handler/proto.go @@ -0,0 +1,63 @@ +package handler + +type Payload struct { + ptype byte + data []byte +} + +/* + var header = []byte{'S', 'T', 'F', 0x01, 0, 0, 0x00, 0} + https://tools.ietf.org/html/draft-mavrogiannopoulos-openconnect-02#section-2.2 + + +---------------------+---------------------------------------------+ + | byte | value | + +---------------------+---------------------------------------------+ + | 0 | fixed to 0x53 (S) | + | | | + | 1 | fixed to 0x54 (T) | + | | | + | 2 | fixed to 0x46 (F) | + | | | + | 3 | fixed to 0x01 | + | | | + | 4-5 | The length of the packet that follows this | + | | header in big endian order | + | | | + | 6 | The type of the payload that follows (see | + | | Table 3 for available types) | + | | | + | 7 | fixed to 0x00 | + +---------------------+---------------------------------------------+ + + + The available payload types are listed in Table 3. + +---------------------+---------------------------------------------+ + | Value | Description | + +---------------------+---------------------------------------------+ + | 0x00 | DATA: the TLS record packet contains an | + | | IPv4 or IPv6 packet | + | | | + | 0x03 | DPD-REQ: used for dead peer detection. Once | + | | sent the peer should reply with a DPD-RESP | + | | packet, that has the same contents as the | + | | original request. | + | | | + | 0x04 | DPD-RESP: used as a response to a | + | | previously received DPD-REQ. | + | | | + | 0x05 | DISCONNECT: sent by the client (or server) | + | | to terminate the session. No data is | + | | associated with this request. The session | + | | will be invalidated after such request. | + | | | + | 0x07 | KEEPALIVE: sent by any peer. No data is | + | | associated with this request. | + | | | + | 0x08 | COMPRESSED DATA: a Data packet which is | + | | compressed prior to encryption. | + | | | + | 0x09 | TERMINATE: sent by the server to indicate | + | | that the server is shutting down. No data | + | | is associated with this request. | + +---------------------+---------------------------------------------+ +*/ diff --git a/handler/server.go b/handler/server.go new file mode 100644 index 0000000..99450e5 --- /dev/null +++ b/handler/server.go @@ -0,0 +1,72 @@ +package handler + +import ( + "crypto/tls" + "fmt" + "log" + "net" + "net/http" + "net/http/httputil" + _ "net/http/pprof" + + "github.com/bjdgyc/anylink/common" + "github.com/julienschmidt/httprouter" +) + +func Start() { + testTun() + go startDebug() + go startDtls() + go startTls() +} + +func startDebug() { + http.ListenAndServe(common.ServerCfg.DebugAddr, nil) +} + +func startTls() { + addr := common.ServerCfg.ServerAddr + certFile := common.ServerCfg.CertFile + keyFile := common.ServerCfg.CertKey + + // 设置tls信息 + tlsConfig := &tls.Config{ + NextProtos: []string{"http/1.1"}, + MinVersion: tls.VersionTLS12, + } + srv := &http.Server{ + Addr: addr, + Handler: initRoute(), + TLSConfig: tlsConfig, + } + + ln, err := net.Listen("tcp", addr) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + srv.SetKeepAlivesEnabled(true) + fmt.Println("listen ", addr) + err = srv.ServeTLS(ln, certFile, keyFile) + if err != nil { + log.Fatal(err) + } +} + +func initRoute() http.Handler { + router := httprouter.New() + router.GET("/", checkVpnClient(LinkHome)) + router.POST("/", checkVpnClient(LinkAuth)) + router.HandlerFunc("CONNECT", "/CSCOSSLC/tunnel", LinkTunnel) + router.NotFound = http.HandlerFunc(notFound) + return router +} + +func notFound(w http.ResponseWriter, r *http.Request) { + hu, _ := httputil.DumpRequest(r, true) + fmt.Println("NotFound: ", string(hu)) + + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, "404 page not found") +} diff --git a/handler/session.go b/handler/session.go new file mode 100644 index 0000000..6bea917 --- /dev/null +++ b/handler/session.go @@ -0,0 +1,142 @@ +package handler + +import ( + "fmt" + "log" + "math/rand" + "net" + "strings" + "sync" + "time" + + "github.com/bjdgyc/anylink/common" +) + +var ( + sessMux = sync.Mutex{} + sessions = make(map[string]*Session) // session_token -> SessUser +) + +type Session struct { + Sid string // auth返回的 session-id + Token string // session信息的唯一token + DtlsSid string // dtls协议的 session_id + MacAddr string // 客户端mac地址 + + // 开启link需要设置的参数 + MasterSecret string // dtls协议的 master_secret + NetIp net.IP // 分配的ip地址 + UserName string // 用户名 + RemoteAddr string + Mtu string + TunName string + IsActive bool + LastLogin time.Time + closeOnce sync.Once + Closed chan struct{} + PayloadIn chan *Payload + PayloadOut chan *Payload +} + +func init() { + rand.Seed(time.Now().UnixNano()) + + // 检测过期的session + go func() { + if common.ServerCfg.SessionTimeout == 0 { + return + } + timeout := time.Duration(common.ServerCfg.SessionTimeout) * time.Second + tick := time.Tick(time.Second * 30) + for range tick { + t := time.Now() + sessMux.Lock() + for k, v := range sessions { + if v.IsActive == true { + continue + } + if t.Sub(v.LastLogin) > timeout { + delete(sessions, k) + } + } + sessMux.Unlock() + } + }() +} + +func NewSession() *Session { + // 生成32位的 token + btoken := make([]byte, 32) + rand.Read(btoken) + + // 生成 dtls session_id + dtlsid := make([]byte, 32) + rand.Read(dtlsid) + + token := fmt.Sprintf("%x", btoken) + sess := &Session{ + Sid: fmt.Sprintf("%d", time.Now().Unix()), + Token: token, + DtlsSid: fmt.Sprintf("%x", dtlsid), + LastLogin: time.Now(), + } + sessMux.Lock() + defer sessMux.Unlock() + sessions[token] = sess + return sess +} + +func (s *Session) StartLink() { + limit := common.LimitClient(s.UserName, false) + if limit == false { + s.NetIp = nil + return + } + s.NetIp = common.AcquireIp(s.MacAddr) + s.IsActive = true + s.closeOnce = sync.Once{} + s.Closed = make(chan struct{}) + s.PayloadIn = make(chan *Payload) + s.PayloadOut = make(chan *Payload) +} + +func (s *Session) Close() { + s.closeOnce.Do(func() { + log.Println("closeOnce") + close(s.Closed) + s.IsActive = false + s.LastLogin = time.Now() + common.ReleaseIp(s.NetIp, s.MacAddr) + common.LimitClient(s.UserName, true) + }) +} + +func SToken2Sess(stoken string) *Session { + stoken = strings.TrimSpace(stoken) + sarr := strings.Split(stoken, "@") + token := sarr[1] + sessMux.Lock() + defer sessMux.Unlock() + if sess, ok := sessions[token]; ok { + return sess + } + + return nil +} + +func Dtls2Sess(dtlsid []byte) *Session { + return nil +} + +func DelSess(token string) { + delete(sessions, token) +} + +func DelSessByStoken(stoken string) { + stoken = strings.TrimSpace(stoken) + sarr := strings.Split(stoken, "@") + token := sarr[1] + sessMux.Lock() + defer sessMux.Unlock() + delete(sessions, token) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..6ba068b --- /dev/null +++ b/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/bjdgyc/anylink/common" + "github.com/bjdgyc/anylink/handler" +) + +var COMMIT_ID string + +func main() { + common.CommitId = COMMIT_ID + common.InitConfig() + handler.Start() + signalWatch() +} + +func signalWatch() { + fmt.Println("Server pid: ", os.Getpid()) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGALRM, syscall.SIGUSR2) + for { + sig := <-sigs + fmt.Printf("Get signal: %v \n", sig) + switch sig { + case syscall.SIGUSR2: + // reload + fmt.Println("reload") + default: + // stop + return + } + } +}