This commit is contained in:
bjdgyc
2020-08-18 16:39:46 +08:00
parent 7f2d405b72
commit eee99fcb0d
27 changed files with 1551 additions and 3 deletions

74
handler/base.go Normal file
View File

@@ -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")
}

6
handler/dtls.go Normal file
View File

@@ -0,0 +1,6 @@
package handler
// 暂时没有实现
func startDtls() {
}

151
handler/link_auth.go Normal file
View File

@@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="auth-request" aggregate-auth-version="2">
<opaque is-for="sg">
<tunnel-group>{{.Group}}</tunnel-group>
<group-alias>{{.Group}}</group-alias>
<aggauth-handle>168179266</aggauth-handle>
<config-hash>1595829378234</config-hash>
<auth-method>multiple-cert</auth-method>
<auth-method>single-sign-on-v2</auth-method>
</opaque>
<auth id="main">
<title>Login</title>
<message>请输入你的用户名和密码</message>
<banner></banner>
{{if .Error}}
<error id="88" param1="用户名或密码错误" param2="">登陆失败: %s</error>
{{end}}
<form>
<input type="text" name="username" label="Username:"></input>
<input type="password" name="password" label="Password:"></input>
<select name="group_list" label="GROUP:">
{{range $v := .Groups}}
<option {{if eq $v $.Group}} selected="true"{{end}}>{{$v}}</option>
{{end}}
</select>
</form>
</auth>
</config-auth>
`
var auth_complete = `<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="complete" aggregate-auth-version="2">
<session-id>{{.SessionId}}</session-id>
<session-token>{{.SessionToken}}</session-token>
<auth id="success">
<banner>{{.Banner}}</banner>
<message id="0" param1="" param2=""></message>
</auth>
<capabilities>
<crypto-supported>ssl-dhe</crypto-supported>
</capabilities>
<config client="vpn" type="private">
<vpn-base-config>
<server-cert-hash>240B97A685B2BFA66AD699B90AAC49EA66495D69</server-cert-hash>
</vpn-base-config>
<opaque is-for="vpn-client"></opaque>
</config>
</config-auth>
`

107
handler/link_cstp.go Normal file
View File

@@ -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
}
}
}

26
handler/link_home.go Normal file
View File

@@ -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")
}

124
handler/link_tun.go Normal file
View File

@@ -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
}

120
handler/link_tunnel.go Normal file
View File

@@ -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)
}

63
handler/proto.go Normal file
View File

@@ -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. |
+---------------------+---------------------------------------------+
*/

72
handler/server.go Normal file
View File

@@ -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")
}

142
handler/session.go Normal file
View File

@@ -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)
}