新增域名动态拆分隧道(域名路由功能)

This commit is contained in:
root 2022-05-29 17:14:50 +08:00
parent ccce143f85
commit 522be82a31
4 changed files with 241 additions and 143 deletions

View File

@ -4,6 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"regexp"
"strings"
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
@ -127,6 +129,20 @@ func SetGroup(g *Group) error {
} }
} }
g.ClientDns = clientDns g.ClientDns = clientDns
// 域名拆分隧道,不能同时填写
if g.DsIncludeDomains != "" && g.DsExcludeDomains != "" {
return errors.New("包含/排除域名不能同时填写")
}
// 校验包含域名的格式
err = CheckDomainNames(g.DsIncludeDomains)
if err != nil {
return errors.New("包含域名有误:" + err.Error())
}
// 校验排除域名的格式
err = CheckDomainNames(g.DsExcludeDomains)
if err != nil {
return errors.New("排除域名有误:" + err.Error())
}
g.UpdatedAt = time.Now() g.UpdatedAt = time.Now()
if g.Id > 0 { if g.Id > 0 {
@ -149,3 +165,27 @@ func parseIpNet(s string) (string, *net.IPNet, error) {
return ipMask, ipNet, nil return ipMask, ipNet, nil
} }
func CheckDomainNames(domains string) error {
if domains == "" {
return nil
}
str_slice := strings.Split(domains, ",")
for _, val := range str_slice {
if val == "" {
return errors.New(val + " 请以逗号分隔域名")
}
if !ValidateDomainName(val) {
return errors.New(val + " 域名有误")
}
}
return nil
}
func ValidateDomainName(domain string) bool {
pos := strings.LastIndex(domain, ".")
if pos != -1 && len(domain[pos+1:]) < 2 {
return false
}
RegExp := regexp.MustCompile(`^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$`)
return RegExp.MatchString(domain)
}

View File

@ -6,18 +6,20 @@ import (
) )
type Group struct { type Group struct {
Id int `json:"id" xorm:"pk autoincr not null"` Id int `json:"id" xorm:"pk autoincr not null"`
Name string `json:"name" xorm:"varchar(60) not null unique"` Name string `json:"name" xorm:"varchar(60) not null unique"`
Note string `json:"note" xorm:"varchar(255)"` Note string `json:"note" xorm:"varchar(255)"`
AllowLan bool `json:"allow_lan" xorm:"Bool"` AllowLan bool `json:"allow_lan" xorm:"Bool"`
ClientDns []ValData `json:"client_dns" xorm:"Text"` ClientDns []ValData `json:"client_dns" xorm:"Text"`
RouteInclude []ValData `json:"route_include" xorm:"Text"` RouteInclude []ValData `json:"route_include" xorm:"Text"`
RouteExclude []ValData `json:"route_exclude" xorm:"Text"` RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"` DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"`
Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制 DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"`
Status int8 `json:"status" xorm:"Int"` // 1正常 LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"` Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"` Status int8 `json:"status" xorm:"Int"` // 1正常
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
} }
type User struct { type User struct {

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"os" "os"
"strings" "strings"
"text/template"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
@ -118,7 +119,6 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
for _, v := range cSess.Group.RouteExclude { for _, v := range cSess.Group.RouteExclude {
HttpAddHeader(w, "X-CSTP-Split-Exclude", v.IpMask) HttpAddHeader(w, "X-CSTP-Split-Exclude", v.IpMask)
} }
HttpSetHeader(w, "X-CSTP-Lease-Duration", fmt.Sprintf("%d", base.Cfg.IpLease)) // ip地址租期 HttpSetHeader(w, "X-CSTP-Lease-Duration", fmt.Sprintf("%d", base.Cfg.IpLease)) // ip地址租期
HttpSetHeader(w, "X-CSTP-Session-Timeout", "none") HttpSetHeader(w, "X-CSTP-Session-Timeout", "none")
HttpSetHeader(w, "X-CSTP-Session-Timeout-Alert-Interval", "60") HttpSetHeader(w, "X-CSTP-Session-Timeout-Alert-Interval", "60")
@ -153,7 +153,11 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
HttpSetHeader(w, "X-CSTP-Disable-Always-On-VPN", "false") HttpSetHeader(w, "X-CSTP-Disable-Always-On-VPN", "false")
HttpSetHeader(w, "X-CSTP-Client-Bypass-Protocol", "false") HttpSetHeader(w, "X-CSTP-Client-Bypass-Protocol", "false")
HttpSetHeader(w, "X-CSTP-TCP-Keepalive", "false") HttpSetHeader(w, "X-CSTP-TCP-Keepalive", "false")
// HttpSetHeader(w, "X-CSTP-Post-Auth-XML", ``) // 设置域名拆分隧道(移动端不支持)
if mobile != "mobile" {
SetPostAuthXml(cSess.Group, w)
}
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
hClone := w.Header().Clone() hClone := w.Header().Clone()
@ -187,3 +191,42 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
go LinkCstp(conn, bufRW, cSess) go LinkCstp(conn, bufRW, cSess)
} }
// 设置域名拆分隧道
func SetPostAuthXml(g *dbdata.Group, w http.ResponseWriter) error {
if g.DsExcludeDomains == "" && g.DsIncludeDomains == "" {
return nil
}
tmpl, err := template.New("post_auth_xml").Parse(ds_domains_xml)
if err != nil {
return err
}
var result bytes.Buffer
_ = tmpl.Execute(&result, g)
if err != nil {
return err
}
if result.String() != "" {
HttpSetHeader(w, "X-CSTP-Post-Auth-XML", result.String())
base.Info(result.String())
}
return nil
}
var ds_domains_xml = `
<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="complete" aggregate-auth-version="2">
<config client="vpn" type="private">
<opaque is-for="vpn-client">
<custom-attr>
{{if .DsExcludeDomains}}
<dynamic-split-exclude-domains><![CDATA[{{.DsExcludeDomains}},]]></dynamic-split-exclude-domains>
{{end}}
{{if .DsIncludeDomains}}
<dynamic-split-include-domains><![CDATA[{{.DsIncludeDomains}}]]></dynamic-split-include-domains>
{{end}}
</custom-attr>
</opaque>
</config>
</config-auth>
`

View File

@ -152,143 +152,155 @@
center> center>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm"> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="用户组ID" prop="id"> <el-tabs v-model="activeTab">
<el-input v-model="ruleForm.id" disabled></el-input> <el-tab-pane label="通用" name="general">
</el-form-item> <el-form-item label="用户组ID" prop="id">
<el-input v-model="ruleForm.id" disabled></el-input>
</el-form-item>
<el-form-item label="组名" prop="name"> <el-form-item label="组名" prop="name">
<el-input v-model="ruleForm.name" :disabled="ruleForm.id > 0"></el-input> <el-input v-model="ruleForm.name" :disabled="ruleForm.id > 0"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="note"> <el-form-item label="备注" prop="note">
<el-input v-model="ruleForm.note"></el-input> <el-input v-model="ruleForm.note"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="带宽限制" prop="bandwidth"> <el-form-item label="带宽限制" prop="bandwidth">
<el-input v-model.number="ruleForm.bandwidth"> <el-input v-model.number="ruleForm.bandwidth">
<template slot="append">BYTE/S</template> <template slot="append">BYTE/S</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="本地网络" prop="allow_lan"> <el-form-item label="本地网络" prop="allow_lan">
<el-switch <el-switch
v-model="ruleForm.allow_lan"> v-model="ruleForm.allow_lan">
</el-switch> </el-switch>
</el-form-item> </el-form-item>
<el-form-item label="客户端DNS" prop="client_dns"> <el-form-item label="客户端DNS" prop="client_dns">
<el-row class="msg-info"> <el-row class="msg-info">
<el-col :span="20">输入IP格式如: 192.168.0.10</el-col> <el-col :span="20">输入IP格式如: 192.168.0.10</el-col>
<el-col :span="4"> <el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.client_dns)"></el-button> @click.prevent="addDomain(ruleForm.client_dns)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-for="(item,index) in ruleForm.client_dns" <el-row v-for="(item,index) in ruleForm.client_dns"
:key="index" style="margin-bottom: 5px" :gutter="10"> :key="index" style="margin-bottom: 5px" :gutter="10">
<el-col :span="10"> <el-col :span="10">
<el-input v-model="item.val"></el-input> <el-input v-model="item.val"></el-input>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-input v-model="item.note" placeholder="备注"></el-input> <el-input v-model="item.note" placeholder="备注"></el-input>
</el-col> </el-col>
<el-col :span="2"> <el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle <el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.client_dns,index)"></el-button> @click.prevent="removeDomain(ruleForm.client_dns,index)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="ruleForm.status">
<el-radio :label="1" border>启用</el-radio>
<el-radio :label="0" border>停用</el-radio>
</el-radio-group>
</el-form-item>
</el-tab-pane>
<el-form-item label="包含路由" prop="route_include"> <el-tab-pane label="路由设置" name="route">
<el-row class="msg-info"> <el-form-item label="包含路由" prop="route_include">
<el-col :span="20">输入CIDR格式如: 192.168.1.0/24</el-col> <el-row class="msg-info">
<el-col :span="4"> <el-col :span="20">输入CIDR格式如: 192.168.1.0/24</el-col>
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-col :span="4">
@click.prevent="addDomain(ruleForm.route_include)"></el-button> <el-button size="mini" type="success" icon="el-icon-plus" circle
</el-col> @click.prevent="addDomain(ruleForm.route_include)"></el-button>
</el-row> </el-col>
<el-row v-for="(item,index) in ruleForm.route_include" </el-row>
:key="index" style="margin-bottom: 5px" :gutter="10"> <el-row v-for="(item,index) in ruleForm.route_include"
<el-col :span="10"> :key="index" style="margin-bottom: 5px" :gutter="10">
<el-input v-model="item.val"></el-input> <el-col :span="10">
</el-col> <el-input v-model="item.val"></el-input>
<el-col :span="12"> </el-col>
<el-input v-model="item.note" placeholder="备注"></el-input> <el-col :span="12">
</el-col> <el-input v-model="item.note" placeholder="备注"></el-input>
<el-col :span="2"> </el-col>
<el-button size="mini" type="danger" icon="el-icon-minus" circle <el-col :span="2">
@click.prevent="removeDomain(ruleForm.route_include,index)"></el-button> <el-button size="mini" type="danger" icon="el-icon-minus" circle
</el-col> @click.prevent="removeDomain(ruleForm.route_include,index)"></el-button>
</el-row> </el-col>
</el-form-item> </el-row>
</el-form-item>
<el-form-item label="排除路由" prop="route_exclude"> <el-form-item label="排除路由" prop="route_exclude">
<el-row class="msg-info"> <el-row class="msg-info">
<el-col :span="20">输入CIDR格式如: 192.168.2.0/24</el-col> <el-col :span="20">输入CIDR格式如: 192.168.2.0/24</el-col>
<el-col :span="4"> <el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button> @click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-for="(item,index) in ruleForm.route_exclude" <el-row v-for="(item,index) in ruleForm.route_exclude"
:key="index" style="margin-bottom: 5px" :gutter="10"> :key="index" style="margin-bottom: 5px" :gutter="10">
<el-col :span="10"> <el-col :span="10">
<el-input v-model="item.val"></el-input> <el-input v-model="item.val"></el-input>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-input v-model="item.note" placeholder="备注"></el-input> <el-input v-model="item.note" placeholder="备注"></el-input>
</el-col> </el-col>
<el-col :span="2"> <el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle <el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button> @click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
</el-tab-pane>
<el-tab-pane label="权限控制" name="link_acl">
<el-form-item label="权限控制" prop="link_acl">
<el-row class="msg-info">
<el-col :span="20">输入CIDR格式如: 192.168.3.0/24 端口0表示所有端口</el-col>
<el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.link_acl)"></el-button>
</el-col>
</el-row>
<el-form-item label="权限控制" prop="link_acl"> <el-row v-for="(item,index) in ruleForm.link_acl"
<el-row class="msg-info"> :key="index" style="margin-bottom: 5px" :gutter="5">
<el-col :span="20">输入CIDR格式如: 192.168.3.0/24 端口0表示所有端口</el-col> <el-col :span="11">
<el-col :span="4"> <el-input placeholder="请输入CIDR地址" v-model="item.val">
<el-button size="mini" type="success" icon="el-icon-plus" circle <el-select v-model="item.action" slot="prepend">
@click.prevent="addDomain(ruleForm.link_acl)"></el-button> <el-option label="允许" value="allow"></el-option>
</el-col> <el-option label="禁止" value="deny"></el-option>
</el-row> </el-select>
</el-input>
</el-col>
<el-col :span="3">
<el-input v-model.number="item.port" placeholder="端口"></el-input>
</el-col>
<el-col :span="8">
<el-input v-model="item.note" placeholder="备注"></el-input>
</el-col>
<el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.link_acl,index)"></el-button>
</el-col>
</el-row>
</el-form-item>
</el-tab-pane>
<el-row v-for="(item,index) in ruleForm.link_acl" <el-tab-pane label="域名拆分隧道" name="ds_domains">
:key="index" style="margin-bottom: 5px" :gutter="5"> <el-form-item label="包含域名" prop="ds_include_domains">
<el-col :span="11"> <el-input type="textarea" :rows="5" v-model="ruleForm.ds_include_domains" placeholder="输入域名用,号分隔,默认匹配所有子域名, 如baidu.com,163.com"></el-input>
<el-input placeholder="请输入CIDR地址" v-model="item.val"> </el-form-item>
<el-select v-model="item.action" slot="prepend"> <el-form-item label="排除域名" prop="ds_exclude_domains">
<el-option label="允许" value="allow"></el-option> <el-input type="textarea" :rows="5" v-model="ruleForm.ds_exclude_domains" placeholder="输入域名用,号分隔,默认匹配所有子域名, 如baidu.com,163.com"></el-input>
<el-option label="禁止" value="deny"></el-option> </el-form-item>
</el-select> </el-tab-pane>
</el-input> <el-form-item>
</el-col> <el-button type="primary" @click="submitForm('ruleForm')">保存</el-button>
<el-col :span="3"> <el-button @click="disVisible">取消</el-button>
<el-input v-model.number="item.port" placeholder="端口"></el-input> </el-form-item>
</el-col> </el-form>
<el-col :span="8">
<el-input v-model="item.note" placeholder="备注"></el-input>
</el-col>
<el-col :span="2">
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.link_acl,index)"></el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="ruleForm.status">
<el-radio :label="1" border>启用</el-radio>
<el-radio :label="0" border>停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">保存</el-button>
<el-button @click="disVisible">取消</el-button>
</el-form-item>
</el-form>
</el-dialog> </el-dialog>
</div> </div>
@ -313,6 +325,7 @@ export default {
page: 1, page: 1,
tableData: [], tableData: [],
count: 10, count: 10,
activeTab : "general",
ruleForm: { ruleForm: {
bandwidth: 0, bandwidth: 0,