mirror of https://github.com/bjdgyc/anylink.git
init
This commit is contained in:
parent
7f2d405b72
commit
eee99fcb0d
|
@ -12,4 +12,8 @@
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# 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
|
||||||
AnyLink是一个企业级远程办公vpn软件
|
|
||||||
|
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 文件中。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
const (
|
||||||
|
APP_NAME = "AnyLink"
|
||||||
|
APP_VER = "0.0.1"
|
||||||
|
)
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)))
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
#过滤本地证书文件
|
||||||
|
vpn_cert.key
|
||||||
|
vpn_cert.pem
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#用户信息配置
|
||||||
|
[test]
|
||||||
|
group = "group1"
|
||||||
|
#密码需要使用 sha1,以下密码为 123456
|
||||||
|
password = "7c4a8d09ca3762af61e59520943dc26494f8941b"
|
||||||
|
|
||||||
|
|
||||||
|
[user]
|
||||||
|
group = "group2"
|
||||||
|
#以下密码为 123456
|
||||||
|
password = "7c4a8d09ca3762af61e59520943dc26494f8941b"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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=
|
|
@ -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")
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
// 暂时没有实现
|
||||||
|
func startDtls() {
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
`
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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. |
|
||||||
|
+---------------------+---------------------------------------------+
|
||||||
|
*/
|
|
@ -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")
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue