From 641d6127ba36a7c3ec1d92bf242632f2d25c3d24 Mon Sep 17 00:00:00 2001 From: bjdgyc Date: Mon, 22 Apr 2024 17:41:18 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dacl=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/group/List.vue | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/pages/group/List.vue b/web/src/pages/group/List.vue index cc8fc557..b19765be 100644 --- a/web/src/pages/group/List.vue +++ b/web/src/pages/group/List.vue @@ -361,10 +361,11 @@ - - + + + - + @@ -624,7 +625,7 @@ export default { // arr.pop() }, addDomain(arr) { - arr.push({val: "", action: "allow", port: 0}); + arr.push({val: "", action: "allow", port: "0"}); }, submitForm(formName) { this.$refs[formName].validate((valid) => { From 75b138a7a8924102927f6bb28fca64a4d579e16c Mon Sep 17 00:00:00 2001 From: bjdgyc Date: Tue, 23 Apr 2024 16:56:40 +0800 Subject: [PATCH 2/6] Client-Bypass --- server/handler/link_tunnel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handler/link_tunnel.go b/server/handler/link_tunnel.go index a854a076..0c253bf2 100644 --- a/server/handler/link_tunnel.go +++ b/server/handler/link_tunnel.go @@ -180,7 +180,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { HttpSetHeader(w, "X-CSTP-Routing-Filtering-Ignore", "false") HttpSetHeader(w, "X-CSTP-Quarantine", "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", "true") HttpSetHeader(w, "X-CSTP-TCP-Keepalive", "false") // 设置域名拆分隧道(移动端不支持) if mobile != "mobile" { From b313c6fa0051bf28e45f4e3da0816c0b8dca299b Mon Sep 17 00:00:00 2001 From: bjdgyc Date: Tue, 23 Apr 2024 16:59:14 +0800 Subject: [PATCH 3/6] Client-Bypass --- server/handler/link_tunnel.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/handler/link_tunnel.go b/server/handler/link_tunnel.go index 0c253bf2..6630d716 100644 --- a/server/handler/link_tunnel.go +++ b/server/handler/link_tunnel.go @@ -156,9 +156,9 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { HttpSetHeader(w, "X-CSTP-Keep", "true") HttpSetHeader(w, "X-CSTP-Tunnel-All-DNS", "false") - HttpSetHeader(w, "X-CSTP-Rekey-Time", "43200") // 172800 + HttpSetHeader(w, "X-CSTP-Rekey-Time", "86400") // 172800 HttpSetHeader(w, "X-CSTP-Rekey-Method", "new-tunnel") - HttpSetHeader(w, "X-DTLS-Rekey-Time", "43200") + HttpSetHeader(w, "X-DTLS-Rekey-Time", "86400") HttpSetHeader(w, "X-DTLS-Rekey-Method", "new-tunnel") HttpSetHeader(w, "X-CSTP-DPD", fmt.Sprintf("%d", cstpDpd)) From 6d3dab6798962ad16f308922f6a3e95224aff384 Mon Sep 17 00:00:00 2001 From: bjdgyc Date: Tue, 23 Apr 2024 16:59:52 +0800 Subject: [PATCH 4/6] Client-Bypass --- server/handler/link_tunnel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handler/link_tunnel.go b/server/handler/link_tunnel.go index 6630d716..94a52329 100644 --- a/server/handler/link_tunnel.go +++ b/server/handler/link_tunnel.go @@ -86,7 +86,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { } cSess.CstpDpd = cstpDpd - dtlsPort := "4433" + dtlsPort := "443" if strings.Contains(base.Cfg.ServerDTLSAddr, ":") { ss := strings.Split(base.Cfg.ServerDTLSAddr, ":") dtlsPort = ss[1] From 96c95bb6cdd5e2fc80a88056096fb039be548180 Mon Sep 17 00:00:00 2001 From: bjdgyc Date: Wed, 24 Apr 2024 15:27:53 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index aa3545de..aac2daca 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.11.4 \ No newline at end of file +0.12.1 \ No newline at end of file From a7c6791c1edc8f8133f1b15681d0d50b3f5a55e8 Mon Sep 17 00:00:00 2001 From: bjdgyc Date: Wed, 24 Apr 2024 17:39:50 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=88=86=E5=89=B2DNS?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/admin/api_group.go | 4 + server/conf/profile.xml | 1 + server/dbdata/group.go | 15 + server/dbdata/tables.go | 1 + server/handler/link_tunnel.go | 5 + web/src/pages/group/List.vue | 812 ++++++++++++++++++---------------- 6 files changed, 467 insertions(+), 371 deletions(-) diff --git a/server/admin/api_group.go b/server/admin/api_group.go index 44522a92..f5fa6d70 100644 --- a/server/admin/api_group.go +++ b/server/admin/api_group.go @@ -75,6 +75,10 @@ func GroupDetail(w http.ResponseWriter, r *http.Request) { if len(data.Auth) == 0 { data.Auth["type"] = "local" } + // 兼容旧数据 + if data.SplitDns == nil { + data.SplitDns = []dbdata.ValData{} + } RespSucess(w, data) } diff --git a/server/conf/profile.xml b/server/conf/profile.xml index 914f53dd..a821a888 100644 --- a/server/conf/profile.xml +++ b/server/conf/profile.xml @@ -9,6 +9,7 @@ IPSec true false + true AllowRemoteUsers AllowRemoteUsers pinAllowed diff --git a/server/dbdata/group.go b/server/dbdata/group.go index fa2270ed..81a48cdc 100644 --- a/server/dbdata/group.go +++ b/server/dbdata/group.go @@ -215,6 +215,7 @@ func SetGroup(g *Group) error { // DNS 判断 clientDns := []ValData{} for _, v := range g.ClientDns { + v.Val = strings.TrimSpace(v.Val) if v.Val != "" { ip := net.ParseIP(v.Val) if ip.String() != v.Val { @@ -229,6 +230,20 @@ func SetGroup(g *Group) error { return errors.New("默认路由,必须设置一个DNS") } g.ClientDns = clientDns + + splitDns := []ValData{} + for _, v := range g.SplitDns { + v.Val = strings.TrimSpace(v.Val) + if v.Val != "" { + ValidateDomainName(v.Val) + if !ValidateDomainName(v.Val) { + return errors.New("域名 错误") + } + splitDns = append(splitDns, v) + } + } + g.SplitDns = splitDns + // 域名拆分隧道,不能同时填写 g.DsIncludeDomains = strings.TrimSpace(g.DsIncludeDomains) g.DsExcludeDomains = strings.TrimSpace(g.DsExcludeDomains) diff --git a/server/dbdata/tables.go b/server/dbdata/tables.go index b70e45ac..752a2a00 100644 --- a/server/dbdata/tables.go +++ b/server/dbdata/tables.go @@ -11,6 +11,7 @@ type Group struct { Note string `json:"note" xorm:"varchar(255)"` AllowLan bool `json:"allow_lan" xorm:"Bool"` ClientDns []ValData `json:"client_dns" xorm:"Text"` + SplitDns []ValData `json:"split_dns" xorm:"Text"` RouteInclude []ValData `json:"route_include" xorm:"Text"` RouteExclude []ValData `json:"route_exclude" xorm:"Text"` DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"` diff --git a/server/handler/link_tunnel.go b/server/handler/link_tunnel.go index 94a52329..cf618b05 100644 --- a/server/handler/link_tunnel.go +++ b/server/handler/link_tunnel.go @@ -131,6 +131,11 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { for _, v := range cSess.Group.ClientDns { HttpAddHeader(w, "X-CSTP-DNS", v.Val) } + // 分割dns + for _, v := range cSess.Group.SplitDns { + HttpAddHeader(w, "X-CSTP-Split-DNS", v.Val) + } + // 允许的路由 for _, v := range cSess.Group.RouteInclude { if strings.ToLower(v.Val) == dbdata.All { diff --git a/web/src/pages/group/List.vue b/web/src/pages/group/List.vue index b19765be..e06ce3f0 100644 --- a/web/src/pages/group/List.vue +++ b/web/src/pages/group/List.vue @@ -49,10 +49,11 @@ prop="bandwidth" label="带宽限制" width="90"> - + @@ -84,12 +93,20 @@ label="路由排除" width="180"> @@ -108,7 +125,9 @@ {{ item.action }} => {{ item.val }} : {{ item.port }} - {{ readMore[`la_${ scope.row.id }`] ? "▲ 收起" : "▼ 更多" }} + + {{ readMore[`la_${scope.row.id}`] ? "▲ 收起" : "▼ 更多" }} + @@ -178,222 +197,263 @@ - - - - - - - - - - - - - - - - - - - - - -
- 注:本地网络 指的是: - 运行 anyconnect 客户端的PC 所在的的网络,即本地路由网段。 - 开启后,PC本地路由网段的数据就不会走隧道链路转发数据了。 - 同时 anyconnect 客户端需要勾选本地网络(Allow Local Lan)的开关,功能才能生效。 -
-
- - - - 输入IP格式如: 192.168.0.10 - - - - - + + + + + + + + + + + + + + + + + + + + +
+ 注:本地网络 指的是: + 运行 anyconnect 客户端的PC 所在的的网络,即本地路由网段。 + 开启后,PC本地路由网段的数据就不会走隧道链路转发数据了。 + 同时 anyconnect 客户端需要勾选本地网络(Allow Local Lan)的开关,功能才能生效。 +
+
+ + + + 输入IP格式如: 192.168.0.10 + + + + + + + + + + + + + + + + + + + + 一般留空。如果输入域名,只有配置的域名(包含子域名)走配置的dns + + + + + + + + + + + + + + + + + + + + 启用 + 停用 + + +
+ + + + + 本地 + Radius + LDAP + + + + + + + + + + + 输入CIDR格式如: 192.168.1.0/24 + + + + + + + + + - + - - + + - - + + - - - - - - 启用 - 停用 - - - - - - - - 本地 - Radius - LDAP - - - - - - - - - - - 输入CIDR格式如: 192.168.1.0/24 - - - - - - - - - - - - - - - - - - - - - - - - - 输入CIDR格式如: 192.168.2.0/24 - - - - - - - - - - - - - - - - - - - - - - - - - - 输入CIDR格式如: 192.168.3.0/24 端口0表示所有端口,多个端口用','号分隔,连续端口:1234-5678 - - - + @click.prevent="removeDomain(ruleForm.route_include,index)"> +
+ + - - - - - - - - - - - - - - + + + 输入CIDR格式如: 192.168.2.0/24 + + + + + + + + + + + + + - - + + - + @click.prevent="removeDomain(ruleForm.route_exclude,index)"> + - - - - - - - - - -
注:域名拆分隧道,仅支持AnyConnect的windows和MacOS桌面客户端,不支持移动端.
-
-
- - - 测试登录 - - 保存 - 取消 + + + + + + + 输入CIDR格式如: 192.168.3.0/24 + 端口0表示所有端口,多个端口用','号分隔,连续端口:1234-5678 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
注:域名拆分隧道,仅支持AnyConnect的windows和MacOS桌面客户端,不支持移动端.
- - +
+ + + 测试登录 + + 保存 + 取消 + + + - - - - - - - - - 登录 - 取 消 - - + + + + + + + + + 登录 + 取 消 + + + :close-on-click-modal="false" + title="编辑模式" + :visible.sync="ipListDialog" + width="650px" + custom-class="valgin-dialog" + center> - - -
当前共 {{ ipEditForm.ip_list.trim() === '' ? 0 : ipEditForm.ip_list.trim().split("\n").length }} 条(注:AnyConnect客户端最多支持{{ this.maxRouteRows }}条路由)
-
- - 更新 - 取 消 - + + +
当前共 + {{ ipEditForm.ip_list.trim() === '' ? 0 : ipEditForm.ip_list.trim().split("\n").length }} + 条(注:AnyConnect客户端最多支持{{ this.maxRouteRows }}条路由) +
+
+ + 更新 + 取 消 +
@@ -458,47 +523,48 @@ export default { page: 1, tableData: [], count: 10, - activeTab : "general", + activeTab: "general", readMore: {}, - readMinRows : 5, - maxRouteRows : 2500, - defAuth : { - type:'local', - radius:{addr:"", secret:""}, - ldap:{ - addr:"", - tls:false, - base_dn:"", - object_class:"person", - search_attr:"sAMAccountName", - member_of:"", - bind_name:"", - bind_pwd:"", - }, + readMinRows: 5, + maxRouteRows: 2500, + defAuth: { + type: 'local', + radius: {addr: "", secret: ""}, + ldap: { + addr: "", + tls: false, + base_dn: "", + object_class: "person", + search_attr: "sAMAccountName", + member_of: "", + bind_name: "", + bind_pwd: "", + }, }, ruleForm: { bandwidth: 0, bandwidth_format: '0', status: 1, allow_lan: true, - client_dns: [{val: '114.114.114.114'}], + client_dns: [{val: '114.114.114.114', note: '默认dns'}], + split_dns: [], route_include: [{val: 'all', note: '默认全局代理'}], route_exclude: [], link_acl: [], - auth : {}, + auth: {}, }, - authLoginDialog : false, - ipListDialog : false, - authLoginLoading : false, - authLoginForm : { - name : "", - pwd : "", + authLoginDialog: false, + ipListDialog: false, + authLoginLoading: false, + authLoginForm: { + name: "", + pwd: "", }, ipEditForm: { ip_list: "", - type : "", + type: "", }, - ipEditLoading : false, + ipEditLoading: false, authLoginRules: { name: [ {required: true, message: '请输入账号', trigger: 'blur'}, @@ -549,11 +615,11 @@ export default { }, methods: { setAuthData(row) { - if (! row) { + if (!row) { this.ruleForm.auth = JSON.parse(JSON.stringify(this.defAuth)); - return ; + return; } - if (row.auth.type == "ldap" && ! row.auth.ldap.object_class) { + if (row.auth.type == "ldap" && !row.auth.ldap.object_class) { row.auth.ldap.object_class = this.defAuth.ldap.object_class; } this.ruleForm.auth = Object.assign(JSON.parse(JSON.stringify(this.defAuth)), row.auth); @@ -625,7 +691,8 @@ export default { // arr.pop() }, addDomain(arr) { - arr.push({val: "", action: "allow", port: "0"}); + console.log("arr", arr) + arr.push({val: "", action: "allow", port: "0", note: ""}); }, submitForm(formName) { this.$refs[formName].validate((valid) => { @@ -651,29 +718,31 @@ export default { }); }, testAuthLogin() { - this.$refs["authLoginForm"].validate((valid) => { - if (!valid) { - console.log('error submit!!'); - return false; - } - this.authLoginLoading = true; - axios.post('/group/auth_login', {name:this.authLoginForm.name, - pwd:this.authLoginForm.pwd, - auth:this.ruleForm.auth}).then(resp => { - const rdata = resp.data; - if (rdata.code === 0) { - this.$message.success("登录成功"); - } else { - this.$message.error(rdata.msg); - } - this.authLoginLoading = false; - console.log(rdata); - }).catch(error => { - this.$message.error('哦,请求出错'); - console.log(error); - this.authLoginLoading = false; - }); + this.$refs["authLoginForm"].validate((valid) => { + if (!valid) { + console.log('error submit!!'); + return false; + } + this.authLoginLoading = true; + axios.post('/group/auth_login', { + name: this.authLoginForm.name, + pwd: this.authLoginForm.pwd, + auth: this.ruleForm.auth + }).then(resp => { + const rdata = resp.data; + if (rdata.code === 0) { + this.$message.success("登录成功"); + } else { + this.$message.error(rdata.msg); + } + this.authLoginLoading = false; + console.log(rdata); + }).catch(error => { + this.$message.error('哦,请求出错'); + console.log(error); + this.authLoginLoading = false; }); + }); }, openAuthLoginDialog() { this.$refs["ruleForm"].validate((valid) => { @@ -684,7 +753,7 @@ export default { this.authLoginDialog = true; // set authLoginFormName focus this.$nextTick(() => { - this.$refs['authLoginFormName'].focus(); + this.$refs['authLoginFormName'].focus(); }); }); }, @@ -694,63 +763,63 @@ export default { this.ipEditForm.ip_list = this.ruleForm[type].map(item => item.val + (item.note ? "," + item.note : "")).join("\n"); }, ipEdit() { - this.ipEditLoading = true; - let ipList = []; - if (this.ipEditForm.ip_list.trim() !== "") { - ipList = this.ipEditForm.ip_list.trim().split("\n"); + this.ipEditLoading = true; + let ipList = []; + if (this.ipEditForm.ip_list.trim() !== "") { + ipList = this.ipEditForm.ip_list.trim().split("\n"); + } + let arr = []; + for (let i = 0; i < ipList.length; i++) { + let item = ipList[i]; + if (item.trim() === "") { + continue; } - let arr = []; - for (let i = 0; i < ipList.length; i++) { - let item = ipList[i]; - if (item.trim() === "") { - continue; - } - let ip = item.split(","); - if (ip.length > 2) { - ip[1] = ip.slice(1).join(","); - } - let note = ip[1] ? ip[1] : ""; - const pushToArr = () => { - arr.push({val: ip[0], note: note}); - }; - if (this.ipEditForm.type == "route_include" && ip[0] == "all") { - pushToArr(); - continue; - } - let valid = this.isValidCIDR(ip[0]); - if (!valid.valid) { - this.$message.error("错误:CIDR格式错误,建议 " + ip[0] + " 改为 " + valid.suggestion); - this.ipEditLoading = false; - return; - } + let ip = item.split(","); + if (ip.length > 2) { + ip[1] = ip.slice(1).join(","); + } + let note = ip[1] ? ip[1] : ""; + const pushToArr = () => { + arr.push({val: ip[0], note: note}); + }; + if (this.ipEditForm.type == "route_include" && ip[0] == "all") { pushToArr(); + continue; } - this.ruleForm[this.ipEditForm.type] = arr; - this.ipEditLoading = false; - this.ipListDialog = false; + let valid = this.isValidCIDR(ip[0]); + if (!valid.valid) { + this.$message.error("错误:CIDR格式错误,建议 " + ip[0] + " 改为 " + valid.suggestion); + this.ipEditLoading = false; + return; + } + pushToArr(); + } + this.ruleForm[this.ipEditForm.type] = arr; + this.ipEditLoading = false; + this.ipListDialog = false; }, isValidCIDR(input) { - const cidrRegex = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)\/([12]?\d|3[0-2])$/; - if (!cidrRegex.test(input)) { - return { valid: false, suggestion: null }; - } - const [ip, mask] = input.split('/'); - const maskNum = parseInt(mask); - const ipParts = ip.split('.').map(part => parseInt(part)); - const binaryIP = ipParts.map(part => part.toString(2).padStart(8, '0')).join(''); - for (let i = maskNum; i < 32; i++) { - if (binaryIP[i] === '1') { - const binaryNetworkPart = binaryIP.substring(0, maskNum).padEnd(32, '0'); - const networkIPParts = []; - for (let j = 0; j < 4; j++) { - const octet = binaryNetworkPart.substring(j * 8, (j + 1) * 8); - networkIPParts.push(parseInt(octet, 2)); - } - const suggestedIP = networkIPParts.join('.'); - return { valid: false, suggestion: `${suggestedIP}/${mask}` }; - } + const cidrRegex = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)\/([12]?\d|3[0-2])$/; + if (!cidrRegex.test(input)) { + return {valid: false, suggestion: null}; + } + const [ip, mask] = input.split('/'); + const maskNum = parseInt(mask); + const ipParts = ip.split('.').map(part => parseInt(part)); + const binaryIP = ipParts.map(part => part.toString(2).padStart(8, '0')).join(''); + for (let i = maskNum; i < 32; i++) { + if (binaryIP[i] === '1') { + const binaryNetworkPart = binaryIP.substring(0, maskNum).padEnd(32, '0'); + const networkIPParts = []; + for (let j = 0; j < 4; j++) { + const octet = binaryNetworkPart.substring(j * 8, (j + 1) * 8); + networkIPParts.push(parseInt(octet, 2)); + } + const suggestedIP = networkIPParts.join('.'); + return {valid: false, suggestion: `${suggestedIP}/${mask}`}; } - return { valid: true, suggestion: null }; + } + return {valid: true, suggestion: null}; }, resetForm(formName) { this.$refs[formName].resetFields(); @@ -767,7 +836,7 @@ export default { }, beforeTabLeave() { var isSwitch = true - if (! this.user_edit_dialog) { + if (!this.user_edit_dialog) { return isSwitch; } this.$refs['ruleForm'].validate((valid) => { @@ -784,16 +853,16 @@ export default { this.activeTab = "general"; }, convertBandwidth(bandwidth, fromUnit, toUnit) { - const units = { - bps: 1, - Kbps: 1000, - Mbps: 1000000, - Gbps: 1000000000, - BYTE: 8, - }; - const result = bandwidth * units[fromUnit] / units[toUnit]; - const fixedResult = result.toFixed(2); - return parseFloat(fixedResult); + const units = { + bps: 1, + Kbps: 1000, + Mbps: 1000000, + Gbps: 1000000000, + BYTE: 8, + }; + const result = bandwidth * units[fromUnit] / units[toUnit]; + const fixedResult = result.toFixed(2); + return parseFloat(fixedResult); } }, } @@ -814,19 +883,20 @@ export default { width: 80px; } -::v-deep .valgin-dialog{ - display: flex; - flex-direction: column; - margin:0 !important; - position:absolute; - top:50%; - left:50%; - transform:translate(-50%,-50%); - max-height:calc(100% - 30px); - max-width:calc(100% - 30px); +::v-deep .valgin-dialog { + display: flex; + flex-direction: column; + margin: 0 !important; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: calc(100% - 30px); + max-width: calc(100% - 30px); } -::v-deep .valgin-dialog .el-dialog__body{ - flex:1; - overflow: auto; + +::v-deep .valgin-dialog .el-dialog__body { + flex: 1; + overflow: auto; }