增加基于tap设备的桥接访问模式

This commit is contained in:
bjdgyc
2020-09-14 17:17:50 +08:00
parent 3b64de19b8
commit 31b1f12dbe
57 changed files with 2598 additions and 703 deletions

View File

@@ -3,12 +3,14 @@ package handler
import (
"encoding/xml"
"fmt"
"log"
"net/http"
"os/exec"
"strings"
"github.com/julienschmidt/httprouter"
)
const BufferSize = 2048
type ClientRequest struct {
XMLName xml.Name `xml:"config-auth"`
Client string `xml:"client,attr"` // 一般都是 vpn
@@ -42,19 +44,19 @@ type macAddressList struct {
}
// 判断anyconnect客户端
func checkVpnClient(h httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func checkLinkClient(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO 调试信息输出
// hd, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpRequest: ", string(hd))
fmt.Println(r.RemoteAddr)
// fmt.Println(r.RemoteAddr)
user_Agent := strings.ToLower(r.UserAgent())
userAgent := 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") &&
if strings.Contains(userAgent, "anyconnect") &&
x_Aggregate_Auth == "1" && x_Transcend_Version == "1" {
h(w, r, ps)
h(w, r)
} else {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "error request")
@@ -73,3 +75,15 @@ func setCommonHeader(w http.ResponseWriter) {
w.Header().Set("X-Aggregate-Auth", "1")
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
}
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
}

View File

@@ -2,5 +2,4 @@ package handler
// 暂时没有实现
func startDtls() {
}

View File

@@ -9,10 +9,10 @@ import (
"text/template"
"github.com/bjdgyc/anylink/common"
"github.com/julienschmidt/httprouter"
"github.com/bjdgyc/anylink/sessdata"
)
func LinkAuth(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
func LinkAuth(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
@@ -32,7 +32,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request, params httprouter.Params)
if cr.Type == "logout" {
// 退出删除session信息
if cr.SessionToken != "" {
DelSessByStoken(cr.SessionToken)
sessdata.DelSessByStoken(cr.SessionToken)
}
w.WriteHeader(http.StatusOK)
return
@@ -40,29 +40,30 @@ func LinkAuth(w http.ResponseWriter, r *http.Request, params httprouter.Params)
if cr.Type == "init" {
w.WriteHeader(http.StatusOK)
data := RequestData{Group: cr.GroupSelect, Groups: common.ServerCfg.LinkGroups}
data := RequestData{Group: cr.GroupSelect, Groups: common.ServerCfg.UserGroups}
tplRequest(tpl_request, w, data)
return
}
// 登陆参数判断
if cr.Type != "auth-reply" || cr.Auth.Username == "" || cr.Auth.Password == "" {
if cr.Type != "auth-reply" {
w.WriteHeader(http.StatusBadRequest)
return
}
// TODO 用户密码校验
if !common.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect) {
if !CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect) {
w.WriteHeader(http.StatusOK)
data := RequestData{Group: cr.GroupSelect, Groups: common.ServerCfg.LinkGroups, Error: true}
data := RequestData{Group: cr.GroupSelect, Groups: common.ServerCfg.UserGroups, Error: true}
tplRequest(tpl_request, w, data)
return
}
// 创建新的session信息
sess := NewSession()
sess := sessdata.NewSession()
sess.UserName = cr.Auth.Username
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
cd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token,
Banner: common.ServerCfg.Banner}
w.WriteHeader(http.StatusOK)

View File

@@ -2,16 +2,17 @@ package handler
import (
"encoding/binary"
"fmt"
"log"
"net"
"time"
"github.com/bjdgyc/anylink/common"
"github.com/bjdgyc/anylink/sessdata"
)
func LinkCstp(conn net.Conn, sess *ConnSession) {
// fmt.Println("HandlerCstp")
func LinkCstp(conn net.Conn, sess *sessdata.ConnSession) {
log.Println("HandlerCstp")
sessdata.Sess = sess
defer func() {
log.Println("LinkCstp return")
conn.Close()
@@ -20,6 +21,7 @@ func LinkCstp(conn net.Conn, sess *ConnSession) {
var (
err error
n int
dataLen uint16
dead = time.Duration(common.ServerCfg.CstpDpd+2) * time.Second
)
@@ -27,54 +29,53 @@ func LinkCstp(conn net.Conn, sess *ConnSession) {
go cstpWrite(conn, sess)
for {
// 设置超时限制
err = conn.SetDeadline(time.Now().Add(dead))
err = conn.SetReadDeadline(time.Now().Add(dead))
if err != nil {
log.Println("SetDeadline: ", err)
return
}
hdata := make([]byte, 1500)
_, err = conn.Read(hdata)
hdata := make([]byte, BufferSize)
n, err = conn.Read(hdata)
if err != nil {
log.Println("read hdata: ", err)
return
}
// 限流设置
err = sess.RateLimit(n, true)
if err != nil {
log.Println(err)
}
switch hdata[6] {
case 0x07: // KEEPALIVE
// do nothing
// fmt.Println("keepalive")
// log.Println("recv keepalive")
case 0x05: // DISCONNECT
// fmt.Println("DISCONNECT")
// log.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:
// log.Println("recv DPD-REQ")
if payloadOut(sess, sessdata.LTypeIPData, 0x04, nil) {
return
}
break
case 0x00:
case 0x04:
// log.Println("recv DPD-RESP")
case 0x00: // DATA
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:
data := hdata[8 : 8+dataLen]
if payloadIn(sess, sessdata.LTypeIPData, 0x00, data) {
return
}
}
}
}
func cstpWrite(conn net.Conn, sess *ConnSession) {
func cstpWrite(conn net.Conn, sess *sessdata.ConnSession) {
defer func() {
log.Println("cstpWrite return")
conn.Close()
@@ -83,26 +84,37 @@ func cstpWrite(conn net.Conn, sess *ConnSession) {
var (
err error
n int
header []byte
payload *Payload
payload *sessdata.Payload
)
for {
select {
case payload = <-sess.PayloadOut:
case <-sess.Closed:
case <-sess.CloseChan:
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...)
if payload.LType != sessdata.LTypeIPData {
continue
}
_, err = conn.Write(header)
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...)
}
n, err = conn.Write(header)
if err != nil {
log.Println("write err", err)
return
}
// 限流设置
err = sess.RateLimit(n, false)
if err != nil {
log.Println(err)
}
}
}

View File

@@ -5,17 +5,16 @@ import (
"net/http"
"net/http/httputil"
"strings"
"github.com/julienschmidt/httprouter"
)
func LinkHome(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
func LinkHome(w http.ResponseWriter, r *http.Request) {
hu, _ := httputil.DumpRequest(r, true)
fmt.Println("DumpHome: ", string(hu))
fmt.Println(r.RemoteAddr)
connection := strings.ToLower(r.Header.Get("Connection"))
if connection == "close" {
userAgent := strings.ToLower(r.UserAgent())
if connection == "close" && strings.Contains(userAgent, "anyconnect") {
w.Header().Set("Connection", "close")
w.WriteHeader(http.StatusBadRequest)
return

272
handler/link_tap.go Normal file
View File

@@ -0,0 +1,272 @@
package handler
import (
"fmt"
"log"
"net"
"github.com/bjdgyc/anylink/arpdis"
"github.com/bjdgyc/anylink/sessdata"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/songgao/packets/ethernet"
"github.com/songgao/water"
"github.com/songgao/water/waterutil"
)
const bridgeName = "anylink0"
func checkTap() {
brFace, err := net.InterfaceByName(bridgeName)
if err != nil {
log.Fatal("testTap err: ", err)
}
bridgeHw := brFace.HardwareAddr
var bridgeIp net.IP
addrs, err := brFace.Addrs()
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil || ip.To4() == nil {
continue
}
bridgeIp = ip
}
if bridgeIp == nil && bridgeHw == nil {
log.Fatalln("bridgeIp is err")
}
if !sessdata.IpPool.Ipv4IPNet.Contains(bridgeIp) {
log.Fatalln("bridgeIp or Ip network err")
}
// 设置本机ip arp为静态
addr := &arpdis.Addr{IP: bridgeIp.To4(), HardwareAddr: bridgeHw, Type: arpdis.TypeStatic}
arpdis.Add(addr)
}
// 创建tap网卡
func LinkTap(sess *sessdata.ConnSession) {
defer func() {
log.Println("LinkTap return")
sess.Close()
}()
cfg := water.Config{
DeviceType: water.TAP,
}
ifce, err := water.New(cfg)
if err != nil {
log.Println(err)
return
}
sess.TunName = ifce.Name()
defer ifce.Close()
// arp on
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast on", ifce.Name(), sess.Mtu)
cmdstr2 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
cmdstr3 := fmt.Sprintf("ip link set dev %s master %s", ifce.Name(), bridgeName)
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
err = execCmd(cmdStrs)
if err != nil {
return
}
// TODO 测试
// sess.MacHw, _ = net.ParseMAC("3c:8c:40:a0:6a:3d")
go loopArp(sess)
go tapRead(ifce, sess)
var (
payload *sessdata.Payload
)
for {
select {
case payload = <-sess.PayloadIn:
case <-sess.CloseChan:
return
}
var frame ethernet.Frame
switch payload.LType {
default:
log.Println(payload)
case sessdata.LTypeEthernet:
frame = payload.Data
case sessdata.LTypeIPData: // 需要转换成 Ethernet 数据
data := payload.Data
ip_src := waterutil.IPv4Source(data)
if waterutil.IsIPv6(data) || !ip_src.Equal(sess.Ip) {
// 过滤掉IPv6的数据
// 非分配给客户端ip直接丢弃
continue
}
ip_dst := waterutil.IPv4Destination(data)
// fmt.Println("get:", ip_src, ip_dst)
var dstAddr *arpdis.Addr
if !sessdata.IpPool.Ipv4IPNet.Contains(ip_dst) || ip_dst.Equal(sessdata.IpPool.Ipv4Gateway) {
// 不是同一网段使用网关mac地址
ip_dst = sessdata.IpPool.Ipv4Gateway
dstAddr = arpdis.Lookup(ip_dst, false)
if dstAddr == nil {
log.Println("Ipv4Gateway mac err", ip_dst)
return
}
// fmt.Println("Gateway", ip_dst, dstAddr.HardwareAddr)
} else {
// 同一网段内的其他主机
dstAddr = arpdis.Lookup(ip_dst, true)
// fmt.Println("other", ip_src, ip_dst, dstAddr)
if dstAddr == nil || dstAddr.Type == arpdis.TypeUnreachable {
// 异步检测发送数据包
select {
case sess.PayloadArp <- payload:
case <-sess.CloseChan:
return
default:
// PayloadArp 容量已经满了
log.Println("PayloadArp is full", sess.Ip, ip_dst)
}
continue
}
}
frame.Prepare(dstAddr.HardwareAddr, sess.MacHw, ethernet.NotTagged, ethernet.IPv4, len(data))
copy(frame[12+2:], data)
}
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
// fmt.Println("write:", packet)
_, err = ifce.Write(frame)
if err != nil {
log.Println("tap Write err", err)
return
}
}
}
// 异步处理获取ip对应的mac地址的数据
func loopArp(sess *sessdata.ConnSession) {
defer func() {
log.Println("loopArp return")
}()
var (
payload *sessdata.Payload
dstAddr *arpdis.Addr
ip_dst net.IP
)
for {
select {
case payload = <-sess.PayloadArp:
case <-sess.CloseChan:
return
}
ip_dst = waterutil.IPv4Destination(payload.Data)
dstAddr = arpdis.Lookup(ip_dst, false)
// 不可达数据包
if dstAddr == nil || dstAddr.Type == arpdis.TypeUnreachable {
// 直接丢弃数据
// fmt.Println("Lookup", ip_dst)
continue
}
// 正常获取mac地址
if payloadInData(sess, payload) {
return
}
}
}
func tapRead(ifce *water.Interface, sess *sessdata.ConnSession) {
defer func() {
log.Println("tapRead return")
ifce.Close()
}()
var (
err error
n int
buf []byte
)
fmt.Println(sess.MacHw)
for {
var frame ethernet.Frame
frame.Resize(BufferSize)
n, err = ifce.Read(frame)
if err != nil {
log.Println("tap Read err", n, err)
return
}
frame = frame[:n]
switch frame.Ethertype() {
default:
// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
// fmt.Println(packet)
continue
case ethernet.IPv6:
continue
case ethernet.IPv4:
// 发送IP数据
data := frame.Payload()
ip_dst := waterutil.IPv4Destination(data)
if !ip_dst.Equal(sess.Ip) {
// 过滤非本机地址
// log.Println(ip_dst, sess.Ip)
continue
}
if payloadOut(sess, sessdata.LTypeIPData, 0x00, data) {
return
}
case ethernet.ARP:
// 暂时仅实现了ARP协议
packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.NoCopy)
layer := packet.Layer(layers.LayerTypeARP)
arpReq := layer.(*layers.ARP)
// fmt.Println("arp", net.IP(arpReq.SourceProtAddress), sess.Ip)
if !sess.Ip.Equal(arpReq.DstProtAddress) {
// 过滤非本机地址
continue
}
// fmt.Println("arp", arpReq.SourceProtAddress, sess.Ip)
// fmt.Println(packet)
// 返回ARP数据
src := &arpdis.Addr{IP: sess.Ip, HardwareAddr: sess.MacHw}
dst := &arpdis.Addr{IP: arpReq.SourceProtAddress, HardwareAddr: frame.Source()}
buf, err = arpdis.NewARPReply(src, dst)
if err != nil {
log.Println(err)
return
}
// 从接受的arp信息添加arp地址
addr := &arpdis.Addr{}
copy(addr.IP, arpReq.SourceProtAddress)
copy(addr.HardwareAddr, frame.Source())
arpdis.Add(addr)
if payloadIn(sess, sessdata.LTypeEthernet, 0x00, buf) {
return
}
}
}
}

View File

@@ -3,13 +3,13 @@ package handler
import (
"fmt"
"log"
"os/exec"
"github.com/bjdgyc/anylink/common"
"github.com/bjdgyc/anylink/sessdata"
"github.com/songgao/water"
)
func testTun() {
func checkTun() {
// 测试tun
cfg := water.Config{
DeviceType: water.TUN,
@@ -19,17 +19,18 @@ func testTun() {
if err != nil {
log.Fatal("open tun err: ", err)
}
defer ifce.Close()
// 测试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)
log.Fatal("testTun err: ", err)
}
ifce.Close()
}
// 创建tun网卡
func LinkTun(sess *ConnSession) {
func LinkTun(sess *sessdata.ConnSession) {
defer func() {
log.Println("LinkTun return")
sess.Close()
@@ -48,10 +49,9 @@ func LinkTun(sess *ConnSession) {
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)
cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d 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)
ifce.Name(), common.ServerCfg.Ipv4Gateway, sess.Ip)
cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
err = execCmd(cmdStrs)
@@ -61,21 +61,16 @@ func LinkTun(sess *ConnSession) {
go tunRead(ifce, sess)
var payload *Payload
var payload *sessdata.Payload
for {
select {
case payload = <-sess.PayloadIn:
case <-sess.Closed:
case <-sess.CloseChan:
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)
_, err = ifce.Write(payload.Data)
if err != nil {
log.Println("tun Write err", err)
return
@@ -84,7 +79,7 @@ func LinkTun(sess *ConnSession) {
}
func tunRead(ifce *water.Interface, sess *ConnSession) {
func tunRead(ifce *water.Interface, sess *sessdata.ConnSession) {
defer func() {
log.Println("tunRead return")
ifce.Close()
@@ -95,34 +90,25 @@ func tunRead(ifce *water.Interface, sess *ConnSession) {
)
for {
packet := make([]byte, 1500)
n, err = ifce.Read(packet)
data := make([]byte, BufferSize)
n, err = ifce.Read(data)
if err != nil {
log.Println("tun Read err", n, err)
return
}
payload := &Payload{
ptype: 0x00,
data: packet[:n],
}
data = data[:n]
select {
case sess.PayloadOut <- payload:
case <-sess.Closed:
// ip_src := waterutil.IPv4Source(data)
// ip_dst := waterutil.IPv4Destination(data)
// ip_port := waterutil.IPv4DestinationPort(data)
// fmt.Println("sent:", ip_src, ip_dst, ip_port)
// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
// fmt.Println("read:", packet)
if payloadOut(sess, sessdata.LTypeIPData, 0x00, data) {
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
}

View File

@@ -3,10 +3,12 @@ package handler
import (
"fmt"
"log"
"net"
"net/http"
"os"
"github.com/bjdgyc/anylink/common"
"github.com/bjdgyc/anylink/sessdata"
)
var hn string
@@ -20,7 +22,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
// TODO 调试信息输出
// hd, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpRequest: ", string(hd))
fmt.Println("LinkTunnel", r.RemoteAddr)
// fmt.Println("LinkTunnel", r.RemoteAddr)
// 判断session-token的值
cookie, err := r.Cookie("webvpn")
@@ -29,31 +31,41 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
return
}
sess := SToken2Sess(cookie.Value)
sess := sessdata.SToken2Sess(cookie.Value)
if sess == nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// 开启link
cSess := sess.StartConn()
cSess := sess.NewConn()
if cSess == nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
fmt.Println(cSess.Ip, cSess.MacHw)
// 客户端信息
cstp_mtu := r.Header.Get("X-CSTP-MTU")
master_Secret := r.Header.Get("X-DTLS-Master-Secret")
local_ip := r.Header.Get("X-Cstp-Local-Address-Ip4")
mobile := r.Header.Get("X-Cstp-License")
cSess.SetMtu(cstp_mtu)
cSess.MasterSecret = master_Secret
cSess.Mtu = cstp_mtu
cSess.RemoteAddr = r.RemoteAddr
cSess.LocalIp = net.ParseIP(local_ip)
cstpDpd := common.ServerCfg.CstpDpd
if mobile == "mobile" {
// 手机客户端
cstpDpd = common.ServerCfg.MobileDpd
}
// 返回客户端数据
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", cSess.NetIp.String()) // 分配的ip地址
w.Header().Set("X-CSTP-Address", cSess.Ip.String()) // 分配的ip地址
w.Header().Set("X-CSTP-Netmask", common.ServerCfg.Ipv4Netmask) // 子网掩码
w.Header().Set("X-CSTP-Hostname", hn) // 机器名称
for _, v := range common.ServerCfg.ClientDns {
@@ -74,7 +86,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
// 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-Lease-Duration", fmt.Sprintf("%d", sessdata.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")
@@ -82,16 +94,18 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
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-Time", "172800")
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-DPD", fmt.Sprintf("%d", 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-Banner", common.ServerCfg.Banner) // 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-CSTP-MTU", fmt.Sprintf("%d", cSess.Mtu)) // 1399
w.Header().Set("X-DTLS-MTU", fmt.Sprintf("%d", cSess.Mtu))
w.Header().Set("X-DTLS-Session-ID", sess.DtlsSid)
w.Header().Set("X-DTLS-Port", "4433")
@@ -117,6 +131,12 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
}
// 开始数据处理
go LinkTun(cSess)
switch common.ServerCfg.LinkMode {
case common.LinkModeTUN:
go LinkTun(cSess)
case common.LinkModeTAP:
go LinkTap(cSess)
}
go LinkCstp(conn, cSess)
}

47
handler/payload.go Normal file
View File

@@ -0,0 +1,47 @@
package handler
import "github.com/bjdgyc/anylink/sessdata"
func payloadIn(sess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadInData(sess, payload)
}
func payloadInData(sess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case sess.PayloadIn <- payload:
case <-sess.CloseChan:
closed = true
}
return closed
}
func payloadOut(sess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadOutData(sess, payload)
}
func payloadOutData(sess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case sess.PayloadOut <- payload:
case <-sess.CloseChan:
closed = true
}
return closed
}

View File

@@ -1,63 +0,0 @@
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. |
+---------------------+---------------------------------------------+
*/

View File

@@ -3,27 +3,32 @@ package handler
import (
"crypto/tls"
"fmt"
"github.com/bjdgyc/anylink/proxyproto"
"log"
"net"
"net/http"
"net/http/httputil"
_ "net/http/pprof"
"net/http/pprof"
"time"
"github.com/bjdgyc/anylink/common"
"github.com/julienschmidt/httprouter"
"github.com/bjdgyc/anylink/proxyproto"
"github.com/bjdgyc/anylink/router"
)
func Start() {
testTun()
go startDebug()
go startDtls()
go startTls()
}
func startAdmin() {
mux := router.NewHttpMux()
mux.HandleFunc(router.ANY, "/", notFound)
// mux.ServeFile(router.ANY, "/static/*", http.Dir("./static"))
func startDebug() {
http.ListenAndServe(common.ServerCfg.DebugAddr, nil)
// pprof
mux.HandleFunc(router.ANY, "/debug/pprof/*", pprof.Index)
mux.HandleFunc(router.ANY, "/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc(router.ANY, "/debug/pprof/profile", pprof.Profile)
mux.HandleFunc(router.ANY, "/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc(router.ANY, "/debug/pprof/trace", pprof.Trace)
fmt.Println("Listen admin", common.ServerCfg.AdminAddr)
err := http.ListenAndServe(common.ServerCfg.AdminAddr, mux)
fmt.Println(err)
}
func startTls() {
@@ -62,17 +67,18 @@ func startTls() {
}
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
mux := router.NewHttpMux()
mux.HandleFunc("GET", "/", checkLinkClient(LinkHome))
mux.HandleFunc("POST", "/", checkLinkClient(LinkAuth))
mux.HandleFunc("CONNECT", "/CSCOSSLC/tunnel", LinkTunnel)
mux.SetNotFound(http.HandlerFunc(notFound))
return mux
}
func notFound(w http.ResponseWriter, r *http.Request) {
hu, _ := httputil.DumpRequest(r, true)
fmt.Println("NotFound: ", string(hu))
// fmt.Println(r.RemoteAddr)
// hu, _ := httputil.DumpRequest(r, true)
// fmt.Println("NotFound: ", string(hu))
w.WriteHeader(http.StatusNotFound)
fmt.Fprintln(w, "404 page not found")

View File

@@ -1,157 +0,0 @@
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
)
// 连接sess
type ConnSession struct {
Sess *Session
MasterSecret string // dtls协议的 master_secret
NetIp net.IP // 分配的ip地址
RemoteAddr string
Mtu string
TunName string
closeOnce sync.Once
Closed chan struct{}
PayloadIn chan *Payload
PayloadOut chan *Payload
}
type Session struct {
Sid string // auth返回的 session-id
Token string // session信息的唯一token
DtlsSid string // dtls协议的 session_id
MacAddr string // 客户端mac地址
UserName string // 用户名
LastLogin time.Time
IsActive bool
// 开启link需要设置的参数
CSess *ConnSession
}
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) StartConn() *ConnSession {
if s.IsActive == true {
s.CSess.Close()
}
limit := common.LimitClient(s.UserName, false)
if limit == false {
// s.NetIp = nil
return nil
}
s.IsActive = true
cSess := &ConnSession{
Sess: s,
NetIp: common.AcquireIp(s.MacAddr),
closeOnce: sync.Once{},
Closed: make(chan struct{}),
PayloadIn: make(chan *Payload),
PayloadOut: make(chan *Payload),
}
s.CSess = cSess
return cSess
}
func (cs *ConnSession) Close() {
cs.closeOnce.Do(func() {
log.Println("closeOnce")
close(cs.Closed)
cs.Sess.IsActive = false
cs.Sess.LastLogin = time.Now()
common.ReleaseIp(cs.NetIp, cs.Sess.MacAddr)
common.LimitClient(cs.Sess.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)
}

24
handler/start.go Normal file
View File

@@ -0,0 +1,24 @@
package handler
import (
"github.com/bjdgyc/anylink/common"
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
)
func Start() {
dbdata.Start()
sessdata.Start()
checkTun()
if common.ServerCfg.LinkMode == common.LinkModeTAP {
checkTap()
}
go startAdmin()
go startTls()
go startDtls()
}
func Stop() {
dbdata.Stop()
}

71
handler/user.go Normal file
View File

@@ -0,0 +1,71 @@
package handler
import (
"crypto/sha1"
"fmt"
"os"
"time"
"github.com/bjdgyc/anylink/common"
"github.com/bjdgyc/anylink/dbdata"
"github.com/xlzd/gotp"
)
func CheckUser(name, pwd, group string) bool {
return true
pl := len(pwd)
if name == "" || pl < 6 {
return false
}
v := &dbdata.User{}
err := dbdata.Get(dbdata.BucketUser, name, v)
if err != nil {
return false
}
if !common.InArrStr(v.Group, group) {
return false
}
pass := pwd[:pl-6]
pwdHash := hashPass(pass)
if v.Password != pwdHash {
return false
}
otp := pwd[pl-6:]
totp := gotp.NewDefaultTOTP(v.OtpSecret)
unix := time.Now().Unix()
verify := totp.Verify(otp, int(unix))
if !verify {
return false
}
return true
}
func UserAdd(name, pwd string, group []string) dbdata.User {
v := dbdata.User{
Id: dbdata.NextId(dbdata.BucketUser),
Username: name,
Password: hashPass(pwd),
OtpSecret: gotp.RandomSecret(32),
Group: group,
UpdatedAt: time.Now(),
}
fmt.Println(v)
secret := "WHH7WA6POOGGEYVIQYXLZU75QLM7YLUX"
totp := gotp.NewDefaultTOTP(secret)
s := totp.ProvisioningUri("bjdtest", "bjdpro")
fmt.Println(s)
// qr, _ := qrcode.New(s, qrcode.Medium)
// a := qr.ToSmallString(false)
// fmt.Println(a)
// qr.WriteFile(512, "a.png")
os.Exit(0)
return v
}
func hashPass(pwd string) string {
sum := sha1.Sum([]byte(pwd))
return fmt.Sprintf("%x", sum)
}