mirror of
				https://github.com/bjdgyc/anylink.git
				synced 2025-10-31 16:43:28 +08:00 
			
		
		
		
	init
This commit is contained in:
		
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -12,4 +12,8 @@ | ||||
| *.out | ||||
|  | ||||
| # Dependency directories (remove the comment below to include it) | ||||
| # vendor/ | ||||
| vendor/ | ||||
|  | ||||
|  | ||||
| .idea/ | ||||
| anylink | ||||
							
								
								
									
										73
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								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 文件中。 | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										6
									
								
								common/app_ver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/app_ver.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package common | ||||
|  | ||||
| const ( | ||||
| 	APP_NAME = "AnyLink" | ||||
| 	APP_VER  = "0.0.1" | ||||
| ) | ||||
							
								
								
									
										10
									
								
								common/assert_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								common/assert_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										77
									
								
								common/cfg_server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								common/cfg_server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										91
									
								
								common/cfg_user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								common/cfg_user.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										33
									
								
								common/cfg_user_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								common/cfg_user_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
|  | ||||
| } | ||||
							
								
								
									
										44
									
								
								common/flag.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								common/flag.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||
| } | ||||
							
								
								
									
										139
									
								
								common/ip_pool.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								common/ip_pool.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										50
									
								
								common/ip_pool_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								common/ip_pool_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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))) | ||||
| } | ||||
							
								
								
									
										7
									
								
								common/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								common/log.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| package common | ||||
|  | ||||
| import "log" | ||||
|  | ||||
| func init() { | ||||
| 	log.SetFlags(log.LstdFlags | log.Lshortfile) | ||||
| } | ||||
							
								
								
									
										3
									
								
								conf/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								conf/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #过滤本地证书文件 | ||||
| vpn_cert.key | ||||
| vpn_cert.pem | ||||
							
								
								
									
										48
									
								
								conf/server.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								conf/server.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										17
									
								
								conf/user.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								conf/user.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| #用户信息配置 | ||||
| [test] | ||||
| group = "group1" | ||||
| #密码需要使用 sha1,以下密码为 123456 | ||||
| password = "7c4a8d09ca3762af61e59520943dc26494f8941b" | ||||
|  | ||||
|  | ||||
| [user] | ||||
| group = "group2" | ||||
| #以下密码为 123456 | ||||
| password = "7c4a8d09ca3762af61e59520943dc26494f8941b" | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										10
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| ) | ||||
							
								
								
									
										16
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -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= | ||||
							
								
								
									
										74
									
								
								handler/base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								handler/base.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										6
									
								
								handler/dtls.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package handler | ||||
|  | ||||
| // 暂时没有实现 | ||||
| func startDtls() { | ||||
|  | ||||
| } | ||||
							
								
								
									
										151
									
								
								handler/link_auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								handler/link_auth.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										107
									
								
								handler/link_cstp.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										26
									
								
								handler/link_home.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										124
									
								
								handler/link_tun.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										120
									
								
								handler/link_tunnel.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										63
									
								
								handler/proto.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										72
									
								
								handler/server.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										142
									
								
								handler/session.go
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
							
								
								
									
										39
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user