From 8e584af0d4b2c4fe623e73b2d1ed95f501a0f51f Mon Sep 17 00:00:00 2001 From: SakuraFallingMad <31793080+SakuraFallingMad@users.noreply.github.com> Date: Sun, 8 Feb 2026 09:35:23 +0800 Subject: [PATCH] luci-app-passwall: bump to 26.2.14 luci-app-passwall: optimize xray kcp finalmask/udp logic luci-app-passwall: balancing/urltest supports batch addition of nodes luci-app-passwall: fixup luci-app-passwall: optimize luci-app-passwall: optimize (cherry picked from commit a3920443f9ce7e524922177227879a8a78fd49aa) --- applications/luci-app-passwall/Makefile | 2 +- .../resources/view/passwall/qrcode.min.js | 2 +- .../passwall/client/include/shunt_options.lua | 81 +- .../model/cbi/passwall/client/node_config.lua | 17 +- .../cbi/passwall/client/node_subscribe.lua | 12 + .../passwall/client/node_subscribe_config.lua | 12 + .../model/cbi/passwall/client/type/ray.lua | 156 +- .../cbi/passwall/client/type/sing-box.lua | 114 +- .../cbi/passwall/client/type/ss-rust.lua | 5 +- .../model/cbi/passwall/client/type/ss.lua | 5 +- .../model/cbi/passwall/client/type/ssr.lua | 9 +- .../cbi/passwall/client/type/trojan-plus.lua | 5 +- .../model/cbi/passwall/server/type/ray.lua | 18 +- .../cbi/passwall/server/type/sing-box.lua | 2 +- .../luci-app-passwall/luasrc/passwall/api.lua | 108 +- .../luasrc/passwall/util_shadowsocks.lua | 4 +- .../luasrc/passwall/util_sing-box.lua | 333 +- .../luasrc/passwall/util_trojan.lua | 2 +- .../luasrc/passwall/util_xray.lua | 395 +-- .../view/passwall/include/shunt_options.htm | 3 +- .../passwall/node_config/link_share_man.htm | 76 +- .../luci-app-passwall/po/zh_Hans/passwall.po | 49 +- .../root/usr/share/passwall/0_default_config | 8 +- .../root/usr/share/passwall/app.sh | 2 +- .../root/usr/share/passwall/iptables.sh | 48 +- .../root/usr/share/passwall/nftables.sh | 140 +- .../root/usr/share/passwall/rules/chnlist | 3104 +++++++++-------- .../root/usr/share/passwall/rules/chnroute | 220 +- .../root/usr/share/passwall/rules/chnroute6 | 215 +- .../root/usr/share/passwall/rules/gfwlist | 1902 +--------- .../root/usr/share/passwall/subscribe.lua | 42 +- .../root/usr/share/passwall/utils.sh | 110 +- 32 files changed, 2822 insertions(+), 4379 deletions(-) diff --git a/applications/luci-app-passwall/Makefile b/applications/luci-app-passwall/Makefile index d00bde9d2a8..733f63a3b53 100644 --- a/applications/luci-app-passwall/Makefile +++ b/applications/luci-app-passwall/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall -PKG_VERSION:=26.2.6 +PKG_VERSION:=26.2.14 PKG_RELEASE:=1 PKG_CONFIG_DEPENDS:= \ diff --git a/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js b/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js index 5da870e4029..94d9fac567d 100644 --- a/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js +++ b/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js @@ -1 +1 @@ -var QRCode;!function(){function t(t){this.mode=r.MODE_8BIT_BYTE,this.data=t,this.parsedData=[];for(var e=0,o=this.data.length;e65536?(i[0]=240|(1835008&n)>>>18,i[1]=128|(258048&n)>>>12,i[2]=128|(4032&n)>>>6,i[3]=128|63&n):n>2048?(i[0]=224|(61440&n)>>>12,i[1]=128|(4032&n)>>>6,i[2]=128|63&n):n>128?(i[0]=192|(1984&n)>>>6,i[1]=128|63&n):i[0]=n,this.parsedData.push(i)}this.parsedData=Array.prototype.concat.apply([],this.parsedData),this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function e(t,e){this.typeNumber=t,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}t.prototype={getLength:function(t){return this.parsedData.length},write:function(t){for(var e=0,r=this.parsedData.length;e=7&&this.setupTypeNumber(t),null==this.dataCache&&(this.dataCache=e.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,r)},setupPositionProbePattern:function(t,e){for(var r=-1;r<=7;r++)if(!(t+r<=-1||this.moduleCount<=t+r))for(var o=-1;o<=7;o++)e+o<=-1||this.moduleCount<=e+o||(this.modules[t+r][e+o]=0<=r&&r<=6&&(0==o||6==o)||0<=o&&o<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=o&&o<=4)},getBestMaskPattern:function(){for(var t=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var o=g.getLostPoint(this);(0==r||t>o)&&(t=o,e=r)}return e},createMovieClip:function(t,e,r){var o=t.createEmptyMovieClip(e,r);this.make();for(var i=0;i>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=o}for(r=0;r<18;r++){o=!t&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=o}},setupTypeInfo:function(t,e){for(var r=this.errorCorrectLevel<<3|e,o=g.getBCHTypeInfo(r),i=0;i<15;i++){var n=!t&&1==(o>>i&1);i<6?this.modules[i][8]=n:i<8?this.modules[i+1][8]=n:this.modules[this.moduleCount-15+i][8]=n}for(i=0;i<15;i++){n=!t&&1==(o>>i&1);i<8?this.modules[8][this.moduleCount-i-1]=n:i<9?this.modules[8][15-i-1+1]=n:this.modules[8][15-i-1]=n}this.modules[this.moduleCount-8][8]=!t},mapData:function(t,e){for(var r=-1,o=this.moduleCount-1,i=7,n=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var s=0;s<2;s++)if(null==this.modules[o][a-s]){var h=!1;n>>i&1)),g.getMask(e,o,a-s)&&(h=!h),this.modules[o][a-s]=h,-1==--i&&(n++,i=7)}if((o+=r)<0||this.moduleCount<=o){o-=r,r=-r;break}}}},e.PAD0=236,e.PAD1=17,e.createData=function(t,r,o){for(var i=m.getRSBlocks(t,r),n=new _,a=0;a8*h)throw new Error("code length overflow. ("+n.getLengthInBits()+">"+8*h+")");for(n.getLengthInBits()+4<=8*h&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*h||(n.put(e.PAD0,8),n.getLengthInBits()>=8*h));)n.put(e.PAD1,8);return e.createBytes(n,i)},e.createBytes=function(t,e){for(var r=0,o=0,i=0,n=new Array(e.length),a=new Array(e.length),s=0;s=0?d.get(c):0}}var m=0;for(u=0;u=0;)e^=g.G15<=0;)e^=g.G18<>>=1;return e},getPatternPosition:function(t){return g.PATTERN_POSITION_TABLE[t-1]},getMask:function(t,e,r){switch(t){case i:return(e+r)%2==0;case n:return e%2==0;case a:return r%3==0;case s:return(e+r)%3==0;case h:return(Math.floor(e/2)+Math.floor(r/3))%2==0;case l:return e*r%2+e*r%3==0;case u:return(e*r%2+e*r%3)%2==0;case f:return(e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}},getErrorCorrectPolynomial:function(t){for(var e=new p([1],0),r=0;r5&&(r+=3+n-5)}for(o=0;o=256;)t-=255;return d.EXP_TABLE[t]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},c=0;c<8;c++)d.EXP_TABLE[c]=1<>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}};var v=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];function C(){var t=!1,e=navigator.userAgent;if(/android/i.test(e)){t=!0;var r=e.toString().match(/android ([0-9]\.[0-9])/i);r&&r[1]&&(t=parseFloat(r[1]))}return t}var w=function(){var t=function(t,e){this._el=t,this._htOption=e};return t.prototype.draw=function(t){var e=this._htOption,r=this._el,o=t.getModuleCount();Math.floor(e.width/o),Math.floor(e.height/o);function i(t,e){var r=document.createElementNS("http://www.w3.org/2000/svg",t);for(var o in e)e.hasOwnProperty(o)&&r.setAttribute(o,e[o]);return r}this.clear();var n=i("svg",{viewBox:"0 0 "+String(o)+" "+String(o),width:"100%",height:"100%",fill:e.colorLight});n.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),r.appendChild(n),n.appendChild(i("rect",{fill:e.colorLight,width:"100%",height:"100%"})),n.appendChild(i("rect",{fill:e.colorDark,width:"1",height:"1",id:"template"}));for(var a=0;a'],s=0;s");for(var h=0;h');a.push("")}a.push(""),r.innerHTML=a.join("");var l=r.childNodes[0],u=(e.width-l.offsetWidth)/2,f=(e.height-l.offsetHeight)/2;u>0&&f>0&&(l.style.margin=f+"px "+u+"px")},t.prototype.clear=function(){this._el.innerHTML=""},t}():function(){function t(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}if(this._android&&this._android<=2.1){var e=1/window.devicePixelRatio,r=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(t,o,i,n,a,s,h,l,u){if("nodeName"in t&&/img/i.test(t.nodeName))for(var f=arguments.length-1;f>=1;f--)arguments[f]=arguments[f]*e;else void 0===l&&(arguments[1]*=e,arguments[2]*=e,arguments[3]*=e,arguments[4]*=e);r.apply(this,arguments)}}var o=function(t,e){this._bIsPainted=!1,this._android=C(),this._htOption=e,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=e.width,this._elCanvas.height=e.height,t.appendChild(this._elCanvas),this._el=t,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.alt="Scan me!",this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return o.prototype.draw=function(t){var e=this._elImage,r=this._oContext,o=this._htOption,i=t.getModuleCount(),n=o.width/i,a=o.height/i,s=Math.round(n),h=Math.round(a);e.style.display="none",this.clear();for(var l=0;lv.length)throw new Error("Too long data");return r}(QRCode=function(t,e){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:o.H},"string"==typeof e&&(e={text:e}),e)for(var r in e)this._htOption[r]=e[r];"string"==typeof t&&(t=document.getElementById(t)),this._htOption.useSVG&&(D=w),this._android=C(),this._el=t,this._oQRCode=null,this._oDrawing=new D(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)}).prototype.makeCode=function(t){this._oQRCode=new e(A(t,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(t),this._oQRCode.make(),this._el.title=t,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=o}(),"undefined"!=typeof module&&(module.exports=QRCode); \ No newline at end of file +var QRCode;!function(){function t(t){this.mode=r.MODE_8BIT_BYTE,this.data=t,this.parsedData=[];for(var e=0,o=this.data.length;e65536?(i[0]=240|(1835008&n)>>>18,i[1]=128|(258048&n)>>>12,i[2]=128|(4032&n)>>>6,i[3]=128|63&n):n>2048?(i[0]=224|(61440&n)>>>12,i[1]=128|(4032&n)>>>6,i[2]=128|63&n):n>128?(i[0]=192|(1984&n)>>>6,i[1]=128|63&n):i[0]=n,this.parsedData.push(i)}this.parsedData=Array.prototype.concat.apply([],this.parsedData),this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function e(t,e){this.typeNumber=t,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}t.prototype={getLength:function(t){return this.parsedData.length},write:function(t){for(var e=0,r=this.parsedData.length;e=7&&this.setupTypeNumber(t),null==this.dataCache&&(this.dataCache=e.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,r)},setupPositionProbePattern:function(t,e){for(var r=-1;r<=7;r++)if(!(t+r<=-1||this.moduleCount<=t+r))for(var o=-1;o<=7;o++)e+o<=-1||this.moduleCount<=e+o||(this.modules[t+r][e+o]=0<=r&&r<=6&&(0==o||6==o)||0<=o&&o<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=o&&o<=4)},getBestMaskPattern:function(){for(var t=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var o=g.getLostPoint(this);(0==r||t>o)&&(t=o,e=r)}return e},createMovieClip:function(t,e,r){var o=t.createEmptyMovieClip(e,r);this.make();for(var i=0;i>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=o}for(r=0;r<18;r++){o=!t&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=o}},setupTypeInfo:function(t,e){for(var r=this.errorCorrectLevel<<3|e,o=g.getBCHTypeInfo(r),i=0;i<15;i++){var n=!t&&1==(o>>i&1);i<6?this.modules[i][8]=n:i<8?this.modules[i+1][8]=n:this.modules[this.moduleCount-15+i][8]=n}for(i=0;i<15;i++){n=!t&&1==(o>>i&1);i<8?this.modules[8][this.moduleCount-i-1]=n:i<9?this.modules[8][15-i-1+1]=n:this.modules[8][15-i-1]=n}this.modules[this.moduleCount-8][8]=!t},mapData:function(t,e){for(var r=-1,o=this.moduleCount-1,i=7,n=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var s=0;s<2;s++)if(null==this.modules[o][a-s]){var h=!1;n>>i&1)),g.getMask(e,o,a-s)&&(h=!h),this.modules[o][a-s]=h,-1==--i&&(n++,i=7)}if((o+=r)<0||this.moduleCount<=o){o-=r,r=-r;break}}}},e.PAD0=236,e.PAD1=17,e.createData=function(t,r,o){for(var i=m.getRSBlocks(t,r),n=new _,a=0;a8*h)throw new Error("code length overflow. ("+n.getLengthInBits()+">"+8*h+")");for(n.getLengthInBits()+4<=8*h&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*h||(n.put(e.PAD0,8),n.getLengthInBits()>=8*h));)n.put(e.PAD1,8);return e.createBytes(n,i)},e.createBytes=function(t,e){for(var r=0,o=0,i=0,n=new Array(e.length),a=new Array(e.length),s=0;s=0?d.get(c):0}}var m=0;for(u=0;u=0;)e^=g.G15<=0;)e^=g.G18<>>=1;return e},getPatternPosition:function(t){return g.PATTERN_POSITION_TABLE[t-1]},getMask:function(t,e,r){switch(t){case i:return(e+r)%2==0;case n:return e%2==0;case a:return r%3==0;case s:return(e+r)%3==0;case h:return(Math.floor(e/2)+Math.floor(r/3))%2==0;case l:return e*r%2+e*r%3==0;case u:return(e*r%2+e*r%3)%2==0;case f:return(e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}},getErrorCorrectPolynomial:function(t){for(var e=new p([1],0),r=0;r5&&(r+=3+n-5)}for(o=0;o=256;)t-=255;return d.EXP_TABLE[t]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},c=0;c<8;c++)d.EXP_TABLE[c]=1<>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}};var v=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];function C(){var t=!1,e=navigator.userAgent;if(/android/i.test(e)){t=!0;var r=e.toString().match(/android ([0-9]\.[0-9])/i);r&&r[1]&&(t=parseFloat(r[1]))}return t}var w=function(){var t=function(t,e){this._el=t,this._htOption=e};return t.prototype.draw=function(t){var e=this._htOption,r=this._el,o=t.getModuleCount();Math.floor(e.width/o),Math.floor(e.height/o);function i(t,e){var r=document.createElementNS("http://www.w3.org/2000/svg",t);for(var o in e)e.hasOwnProperty(o)&&r.setAttribute(o,e[o]);return r}this.clear();var n=i("svg",{viewBox:"0 0 "+String(o)+" "+String(o),width:"100%",height:"100%",fill:e.colorLight});n.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),r.appendChild(n),n.appendChild(i("rect",{fill:e.colorLight,width:"100%",height:"100%"})),n.appendChild(i("rect",{fill:e.colorDark,width:"1",height:"1",id:"template"}));for(var a=0;a'],s=0;s");for(var h=0;h');a.push("")}a.push(""),r.innerHTML=a.join("");var l=r.childNodes[0],u=(e.width-l.offsetWidth)/2,f=(e.height-l.offsetHeight)/2;u>0&&f>0&&(l.style.margin=f+"px "+u+"px")},t.prototype.clear=function(){this._el.innerHTML=""},t}():function(){function t(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}if(this._android&&this._android<=2.1){var e=1/window.devicePixelRatio,r=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(t,o,i,n,a,s,h,l,u){if("nodeName"in t&&/img/i.test(t.nodeName))for(var f=arguments.length-1;f>=1;f--)arguments[f]=arguments[f]*e;else void 0===l&&(arguments[1]*=e,arguments[2]*=e,arguments[3]*=e,arguments[4]*=e);r.apply(this,arguments)}}var o=function(t,e){this._bIsPainted=!1,this._android=C(),this._htOption=e,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=e.width,this._elCanvas.height=e.height,t.appendChild(this._elCanvas),this._el=t,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.alt="Scan me!",this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return o.prototype.draw=function(t){var e=this._elImage,r=this._oContext,o=this._htOption,i=t.getModuleCount(),n=o.width/i,a=o.height/i,s=Math.round(n),h=Math.round(a);e.style.display="none",this.clear();for(var l=0;lv.length)throw new Error("Too long data");return r}(QRCode=function(t,e){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:o.H},"string"==typeof e&&(e={text:e}),e)for(var r in e)this._htOption[r]=e[r];"string"==typeof t&&(t=document.getElementById(t)),this._htOption.useSVG&&(D=w),this._android=C(),this._el=t,this._oQRCode=null,this._oDrawing=new D(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)}).prototype.makeCode=function(t){this._oQRCode=new e(A(t,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(t),this._oQRCode.make(),this._el.title=t,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=o}(),"undefined"!=typeof module&&(module.exports=QRCode); diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua index 9d571a295f3..ed3708b9a3c 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua @@ -5,6 +5,7 @@ if not data.node_id or not data.node then end local current_node_id = data.node_id +local node_list = data.node_list or api.get_node_list() local function get_cfgvalue() return function(self, section) @@ -87,13 +88,6 @@ if data.node.type == "Xray" then o:value("linear") end -o = add_option(Flag, "preproxy_enabled", translate("Preproxy") .. " " .. translate("Main switch")) - -main_node = add_option(ListValue, "main_node", string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not.")) -add_depends(main_node, {["preproxy_enabled"] = true}) -main_node.template = appname .. "/cbi/nodes_listvalue" -main_node.group = {} - o = add_option(Flag, "fakedns", 'FakeDNS' .. " " .. translate("Main switch"), translate("Use FakeDNS work in the domain that proxy.") .. "
" .. translate("Suitable scenarios for let the node servers get the target domain names.") .. "
" .. translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc.")) @@ -160,73 +154,36 @@ o.remove = function(self, section) return m:del(current_node_id, shunt_rules[section]["_fakedns_option"]) end -o = s2:option(ListValue, "_proxy_tag", string.format('%s', translate("Preproxy"))) ---TODO Choose any node as a pre-proxy. Instead of main node. -o.template = appname .. "/cbi/nodes_listvalue" -o.group = {"",""} -o:value("", translate("Close (Not use)")) -o:value("main", translate("Use preproxy node")) -o.cfgvalue = function(self, section) +proxy_tag_node = s2:option(ListValue, "_proxy_tag", string.format('%s', + translate("Set the node to be used as a pre-proxy.") .. "\n" .. translate("Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."), + translate("Preproxy"))) +proxy_tag_node.template = appname .. "/cbi/nodes_listvalue" +proxy_tag_node.group = {""} +proxy_tag_node:value("", translate("Close (Not use)")) +proxy_tag_node.cfgvalue = function(self, section) return m:get(current_node_id, shunt_rules[section]["_proxy_tag_option"]) end -o.write = function(self, section, value) +proxy_tag_node.write = function(self, section, value) return m:set(current_node_id, shunt_rules[section]["_proxy_tag_option"], value) end -o.remove = function(self, section) +proxy_tag_node.remove = function(self, section) return m:del(current_node_id, shunt_rules[section]["_proxy_tag_option"]) end -if data.socks_list then - for k, v in pairs(data.socks_list) do - main_node:value(v.id, v.remark) - main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - _node:value(v.id, v.remark) - _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end -end -if data.urltest_list then - for k, v in pairs(data.urltest_list) do - main_node:value(v.id, v.remark) - main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - _node:value(v.id, v.remark) - _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end -end -if data.balancing_list then - for k, v in pairs(data.balancing_list) do - main_node:value(v.id, v.remark) - main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - _node:value(v.id, v.remark) - _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end -end -if data.iface_list then - for k, v in pairs(data.iface_list) do - main_node:value(v.id, v.remark) - main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - _node:value(v.id, v.remark) - _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end -end -if data.normal_list then - for k, v in pairs(data.normal_list) do - main_node:value(v.id, v.remark) - main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") +for k1, v1 in pairs(node_list) do + if k1 ~= "shunt_list" then + for i, v in ipairs(v1) do + _node:value(v.id, v.remark) + _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - _node:value(v.id, v.remark) - _node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + proxy_tag_node:value(v.id, v.remark) + proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end end end -if #main_node.keylist > 0 then - main_node.default = main_node.keylist[1] -end - local footer = Template(appname .. "/include/shunt_options") footer.api = api footer.id = current_node_id +footer.normal_list = api.jsonc.stringify(node_list.normal_list) m:append(footer) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua index 5c11d8b2307..380749082d6 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua @@ -17,6 +17,15 @@ m:append(header) m:append(Template(appname .. "/cbi/nodes_multivalue_com")) m:append(Template(appname .. "/cbi/nodes_listvalue_com")) +groups = {} +m.uci:foreach(appname, "nodes", function(s) + if s[".name"] ~= arg[1] then + if s.group and s.group ~= "" then + groups[s.group] = true + end + end +end) + s = m:section(NamedSection, arg[1], "nodes", "") s.addremove = false s.dynamic = false @@ -33,14 +42,6 @@ o.rmempty = false o = s:option(Value, "group", translate("Group Name")) o.default = "" o:value("", translate("default")) -local groups = {} -m.uci:foreach(appname, "nodes", function(s) - if s[".name"] ~= arg[1] then - if s.group and s.group ~= "" then - groups[s.group] = true - end - end -end) for k, v in pairs(groups) do o:value(k) end diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua index 8b17b0bef91..40a266ef2df 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua @@ -188,6 +188,18 @@ o.write = function(self, section, value) if e["group"] and e["group"]:lower() == old:lower() then m.uci:set(appname, e[".name"], "group", value) end + if e["protocol"] and (e["protocol"] == "_balancing" or e["protocol"] == "_urltest") and e["node_group"] then + local gs = "" + for g in e["node_group"]:gmatch("%S+") do + if api.UrlEncode(old) == g then + gs = gs .. " " .. api.UrlEncode(value) + else + gs = gs .. " " .. g + end + end + gs = api.trim(gs) + m.uci:set(appname, e[".name"], "node_group", gs) + end end) end return Value.write(self, section, value) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua index 90b5f81e2e5..d33ff7767a6 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua @@ -106,6 +106,18 @@ o.write = function(self, section, value) if e["group"] and e["group"]:lower() == old:lower() then m.uci:set(appname, e[".name"], "group", value) end + if e["protocol"] and (e["protocol"] == "_balancing" or e["protocol"] == "_urltest") and e["node_group"] then + local gs = "" + for g in e["node_group"]:gmatch("%S+") do + if api.UrlEncode(old) == g then + gs = gs .. " " .. api.UrlEncode(value) + else + gs = gs .. " " .. g + end + end + gs = api.trim(gs) + m.uci:set(appname, e[".name"], "node_group", gs) + end end) end return Value.write(self, section, value) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua index ee2090d8280..3d17b3a4140 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua @@ -38,6 +38,10 @@ local ss_method_list = { local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" } +local header_type_list = { + "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" +} + local xray_version = api.get_app_version("xray") o = s:option(ListValue, _n("protocol"), translate("Protocol")) @@ -75,72 +79,41 @@ if not arg_select_proto:find("_") then load_normal_options = true end -local nodes_list = {} -local balancing_list = {} +local node_list = api.get_node_list() local fallback_list = {} -local iface_list = {} local is_balancer = nil -for k, e in ipairs(api.get_valid_nodes()) do - if e.node_type == "normal" then - nodes_list[#nodes_list + 1] = { - id = e[".name"], - remark = e["remark"], - type = e["type"], - address = e["address"], - chain_proxy = e["chain_proxy"], - group = e["group"] - } - end - if e.protocol == "_balancing" then - balancing_list[#balancing_list + 1] = { - id = e[".name"], - remark = e["remark"], - group = e["group"] - } - if e[".name"] ~= arg[1] then - fallback_list[#fallback_list + 1] = { - id = e[".name"], - remark = e["remark"], - fallback = e["fallback_node"], - group = e["group"] - } - else - is_balancer = true - end - end - if e.protocol == "_iface" then - iface_list[#iface_list + 1] = { - id = e[".name"], +for k, e in ipairs(node_list.balancing_list or {}) do + if e.id ~= arg[1] then + fallback_list[#fallback_list + 1] = { + id = e["id"], remark = e["remark"], - group = e["group"] + group = e["group"], + fallback = e.o["fallback_node"], } + else + is_balancer = true end end -local socks_list = {} -m.uci:foreach(appname, "socks", function(s) - if s.enabled == "1" and s.node then - socks_list[#socks_list + 1] = { - id = "Socks_" .. s[".name"], - remark = translate("Socks Config") .. " " .. string.format("[%s %s]", s.port, translate("Port")), - group = "Socks" - } - end -end) +if load_balancing_options then -- [[ Load balancing Start ]] + o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method")) + o:depends({ [_n("protocol")] = "_balancing" }) + o.default = "manual" + o:value("manual", translate("Manual")) + o:value("batch", translate("Batch")) -if load_balancing_options then -- [[ 负载均衡 Start ]] o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, document")) - o:depends({ [_n("protocol")] = "_balancing" }) + o:depends({ [_n("node_add_mode")] = "manual" }) o.widget = "checkbox" o.template = appname .. "/cbi/nodes_multivalue" o.group = {} - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" - end - for i, v in pairs(nodes_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" + for k1, v1 in pairs(node_list) do + if k1 == "socks_list" or k1 == "normal_list" then + for i, v in ipairs(v1) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" + end + end end -- 读取旧 DynamicList function o.cfgvalue(self, section) @@ -167,6 +140,21 @@ if load_balancing_options then -- [[ 负载均衡 Start ]] end end + o = s:option(MultiValue, _n("node_group"), translate("Select Group")) + o:depends({ [_n("node_add_mode")] = "batch" }) + o.widget = "checkbox" + o:value("default", translate("default")) + for k, v in pairs(groups) do + o:value(api.UrlEncode(k), k) + end + + o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules")) + o:depends({ [_n("node_add_mode")] = "batch" }) + local descrStr = "Example: ^A && B && !C && D$
" + descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
" + descrStr = descrStr .. "Conditions are joined by &&, and their order does not affect the result." + o.description = translate(descrStr) + o = s:option(ListValue, _n("balancingStrategy"), translate("Balancing Strategy")) o:depends({ [_n("protocol")] = "_balancing" }) o:value("random") @@ -193,17 +181,17 @@ if load_balancing_options then -- [[ 负载均衡 Start ]] if is_balancer then check_fallback_chain(arg[1]) end - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(fallback_list) do + for i, v in ipairs(fallback_list) do o:value(v.id, v.remark) o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") end - for k, v in pairs(nodes_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + for k1, v1 in pairs(node_list) do + if k1 == "socks_list" or k1 == "normal_list" then + for i, v in ipairs(v1) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + end end -- 探测地址 @@ -255,7 +243,7 @@ o.datatype = "port" local protocols = s.fields[_n("protocol")].keylist if #protocols > 0 then for index, value in ipairs(protocols) do - if not value:find("_") then + if not value:find("^_") then s.fields[_n("address")]:depends({ [_n("protocol")] = value }) s.fields[_n("port")]:depends({ [_n("protocol")] = value }) end @@ -333,7 +321,7 @@ o:value("salamander") o:depends({ [_n("protocol")] = "hysteria2" }) o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password")) -o:depends({ [_n("protocol")] = "hysteria2" }) +o:depends({ [_n("hysteria2_obfs_type")] = "salamander" }) o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password")) o.password = true @@ -524,17 +512,11 @@ o:depends({ [_n("tcp_guise")] = "http" }) -- [[ mKCP部分 ]]-- o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.')) -o:value("none", "none") -o:value("header-srtp", "srtp") -o:value("header-utp", "utp") -o:value("header-wechat", "wechat-video") -o:value("header-dtls", "dtls") -o:value("header-wireguard", "wireguard") -o:value("header-dns", "dns") +for a, t in ipairs(header_type_list) do o:value(t) end o:depends({ [_n("transport")] = "mkcp" }) o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain.")) -o:depends({ [_n("mkcp_guise")] = "header-dns" }) +o:depends({ [_n("mkcp_guise")] = "dns" }) o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU")) o.default = "1350" @@ -699,7 +681,7 @@ o = s:option(Value, _n("xudp_concurrency"), translate("XUDP Mux concurrency")) o.default = 8 o:depends({ [_n("mux")] = true }) -o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) +o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) o.default = 0 --[[tcpMptcp]] @@ -716,7 +698,7 @@ o:value("", translate("Close(Not use)")) o:value("1", translate("Preproxy Node")) o:value("2", translate("Landing Node")) for i, v in ipairs(s.fields[_n("protocol")].keylist) do - if not v:find("_") then + if not v:find("^_") then o:depends({ [_n("protocol")] = v }) end end @@ -731,17 +713,24 @@ o2:depends({ [_n("chain_proxy")] = "2" }) o2.template = appname .. "/cbi/nodes_listvalue" o2.group = {} -for k, v in pairs(nodes_list) do - if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then - o1:value(v.id, v.remark) - o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - o2:value(v.id, v.remark) - o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") +for k1, v1 in pairs(node_list) do + if k1 ~= "shunt_list" and k1 ~= "iface_list" then + for i, v in ipairs(v1) do + if v.id ~= arg[1] then + o1:value(v.id, v.remark) + o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if k1 == "normal_list" then + -- Landing Node not support use special node. + o2:value(v.id, v.remark) + o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + end + end end end for i, v in ipairs(s.fields[_n("protocol")].keylist) do - if not v:find("_") and v ~= "hysteria2" then + if not v:find("^_") and v ~= "hysteria2" then s.fields[_n("tcp_fast_open")]:depends({ [_n("protocol")] = v }) s.fields[_n("tcpMptcp")]:depends({ [_n("protocol")] = v }) s.fields[_n("chain_proxy")]:depends({ [_n("protocol")] = v }) @@ -759,9 +748,6 @@ if load_shunt_options then setfenv(shunt_lua, getfenv(1))(m, s, { node_id = arg[1], node = current_node, - socks_list = socks_list, - balancing_list = balancing_list, - iface_list = iface_list, - normal_list = nodes_list + node_list = node_list, }) end diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua index 38909ea4373..79e14609cfd 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua @@ -1,16 +1,12 @@ local m, s = ... -local api = require "luci.passwall.api" - local singbox_bin = api.finded_com("sing-box") if not singbox_bin then return end -local appname = "passwall" - -local type_name = "sing-box" +type_name = "sing-box" -- [[ sing-box ]] @@ -96,60 +92,27 @@ if not arg_select_proto:find("_") then load_normal_options = true end -local nodes_list = {} -local iface_list = {} -local urltest_list = {} -for k, e in ipairs(api.get_valid_nodes()) do - if e.node_type == "normal" then - nodes_list[#nodes_list + 1] = { - id = e[".name"], - remark = e["remark"], - type = e["type"], - address = e["address"], - chain_proxy = e["chain_proxy"], - group = e["group"] - } - end - if e.protocol == "_iface" then - iface_list[#iface_list + 1] = { - id = e[".name"], - remark = e["remark"], - group = e["group"] - } - end - if e.protocol == "_urltest" then - urltest_list[#urltest_list + 1] = { - id = e[".name"], - remark = e["remark"], - group = e["group"] - } - end -end - -local socks_list = {} -m.uci:foreach(appname, "socks", function(s) - if s.enabled == "1" and s.node then - socks_list[#socks_list + 1] = { - id = "Socks_" .. s[".name"], - remark = translate("Socks Config") .. " " .. string.format("[%s %s]", s.port, translate("Port")), - group = "Socks" - } - end -end) +local node_list = api.get_node_list() if load_urltest_options then -- [[ URLTest Start ]] - o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, document")) + o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method")) o:depends({ [_n("protocol")] = "_urltest" }) + o.default = "manual" + o:value("manual", translate("Manual")) + o:value("batch", translate("Batch")) + + o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, document")) + o:depends({ [_n("node_add_mode")] = "manual" }) o.widget = "checkbox" o.template = appname .. "/cbi/nodes_multivalue" o.group = {} - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" - end - for i, v in pairs(nodes_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" + for k1, v1 in pairs(node_list) do + if k1 == "socks_list" or k1 == "normal_list" then + for i, v in ipairs(v1) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" + end + end end -- 读取旧 DynamicList function o.cfgvalue(self, section) @@ -176,6 +139,21 @@ if load_urltest_options then -- [[ URLTest Start ]] end end + o = s:option(MultiValue, _n("node_group"), translate("Select Group")) + o:depends({ [_n("node_add_mode")] = "batch" }) + o.widget = "checkbox" + o:value("default", translate("default")) + for k, v in pairs(groups) do + o:value(api.UrlEncode(k), k) + end + + o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules")) + o:depends({ [_n("node_add_mode")] = "batch" }) + local descrStr = "Example: ^A && B && !C && D$
" + descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
" + descrStr = descrStr .. "Conditions are joined by &&, and their order does not affect the result." + o.description = translate(descrStr) + o = s:option(Value, _n("urltest_url"), translate("Probe URL")) o:depends({ [_n("protocol")] = "_urltest" }) o:value("https://cp.cloudflare.com/", "Cloudflare") @@ -233,7 +211,7 @@ o.datatype = "port" local protocols = s.fields[_n("protocol")].keylist if #protocols > 0 then for index, value in ipairs(protocols) do - if not value:find("_") then + if not value:find("^_") then s.fields[_n("address")]:depends({ [_n("protocol")] = value }) s.fields[_n("port")]:depends({ [_n("protocol")] = value }) end @@ -401,7 +379,7 @@ if singbox_tags:find("with_quic") then o:depends({ [_n("protocol")] = "hysteria2" }) o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password")) - o:depends({ [_n("protocol")] = "hysteria2" }) + o:depends({ [_n("hysteria2_obfs_type")] = "salamander" }) o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password")) o.password = true @@ -762,7 +740,7 @@ o:value("", translate("Close(Not use)")) o:value("1", translate("Preproxy Node")) o:value("2", translate("Landing Node")) for i, v in ipairs(s.fields[_n("protocol")].keylist) do - if not v:find("_") then + if not v:find("^_") then o:depends({ [_n("protocol")] = v }) end end @@ -777,12 +755,19 @@ o2:depends({ [_n("chain_proxy")] = "2" }) o2.template = appname .. "/cbi/nodes_listvalue" o2.group = {} -for k, v in pairs(nodes_list) do - if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then - o1:value(v.id, v.remark) - o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - o2:value(v.id, v.remark) - o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") +for k1, v1 in pairs(node_list) do + if k1 ~= "shunt_list" and k1 ~= "iface_list" then + for i, v in ipairs(v1) do + if v.id ~= arg[1] then + o1:value(v.id, v.remark) + o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if k1 == "normal_list" then + -- Landing Node not support use special node. + o2:value(v.id, v.remark) + o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + end + end end end @@ -797,9 +782,6 @@ if load_shunt_options then setfenv(shunt_lua, getfenv(1))(m, s, { node_id = arg[1], node = current_node, - socks_list = socks_list, - urltest_list = urltest_list, - iface_list = iface_list, - normal_list = nodes_list + node_list = node_list, }) end diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua index 9f6946d7aeb..18eac26556c 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua @@ -45,9 +45,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout")) o.datatype = "uinteger" o.default = 300 -o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) -o:value("false") -o:value("true") +o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) +o.default = 0 o = s:option(Flag, _n("plugin_enabled"), translate("plugin")) o.default = 0 diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua index 537cc9ecbc4..02f31115ddc 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua @@ -46,9 +46,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout")) o.datatype = "uinteger" o.default = 300 -o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) -o:value("false") -o:value("true") +o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) +o.default = 0 o = s:option(Flag, _n("plugin_enabled"), translate("plugin")) o.default = 0 diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua index be7823faa07..9689c68cf4c 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua @@ -39,10 +39,6 @@ local ssr_obfs_list = { "tls1.0_session_auth", "tls1.2_ticket_auth" } -o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol -o:depends({ [_n("__hide")] = "1" }) -o.rewrite_option = "protocol" - o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) o = s:option(Value, _n("port"), translate("Port")) @@ -68,8 +64,7 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout")) o.datatype = "uinteger" o.default = 300 -o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) -o:value("false") -o:value("true") +o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) +o.default = 0 api.luci_types(arg[1], m, s, type_name, option_prefix) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua index 27694f72d39..1da77f6f395 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua @@ -32,9 +32,8 @@ o.datatype = "port" o = s:option(Value, _n("password"), translate("Password")) o.password = true -o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) -o:value("false") -o:value("true") +o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) +o.default = 0 o = s:option(Flag, _n("tls"), translate("TLS")) o.default = 0 diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua index a5ab5c6c9c3..5f9c97d7f9d 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua @@ -20,6 +20,10 @@ local x_ss_method_list = { "none", "plain", "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" } +local header_type_list = { + "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" +} + -- [[ Xray ]] s.fields["type"]:value(type_name, "Xray") @@ -314,17 +318,11 @@ o:depends({ [_n("tcp_guise")] = "http" }) -- [[ mKCP部分 ]]-- o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.')) -o:value("none", "none") -o:value("header-srtp", "srtp") -o:value("header-utp", "utp") -o:value("header-wechat", "wechat-video") -o:value("header-dtls", "dtls") -o:value("header-wireguard", "wireguard") -o:value("header-dns", "dns") +for a, t in ipairs(header_type_list) do o:value(t) end o:depends({ [_n("transport")] = "mkcp" }) o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain.")) -o:depends({ [_n("mkcp_guise")] = "header-dns" }) +o:depends({ [_n("mkcp_guise")] = "dns" }) o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU")) o.default = "1350" @@ -364,6 +362,10 @@ o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), o.default = "0" o:depends({ [_n("custom")] = false }) +o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) +o.default = "0" +o:depends({ [_n("custom")] = false }) + -- [[ Fallback部分 ]]-- o = s:option(Flag, _n("fallback"), translate("Fallback")) o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" }) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua index b54e23cdf98..047fa144697 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua @@ -168,7 +168,7 @@ if singbox_tags:find("with_quic") then o:depends({ [_n("protocol")] = "hysteria2" }) o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password")) - o:depends({ [_n("protocol")] = "hysteria2" }) + o:depends({ [_n("hysteria2_obfs_type")] = "salamander" }) o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password")) o.password = true diff --git a/applications/luci-app-passwall/luasrc/passwall/api.lua b/applications/luci-app-passwall/luasrc/passwall/api.lua index d1140c03e24..9729e8974c7 100644 --- a/applications/luci-app-passwall/luasrc/passwall/api.lua +++ b/applications/luci-app-passwall/luasrc/passwall/api.lua @@ -21,8 +21,6 @@ LOG_FILE = "/tmp/log/" .. appname .. ".log" TMP_PATH = "/tmp/etc/" .. appname TMP_IFACE_PATH = TMP_PATH .. "/iface" -NEW_PORT = nil - function log(...) local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") local f, err = io.open(LOG_FILE, "a") @@ -98,12 +96,7 @@ end function get_new_port() local cmd_format = ". /usr/share/passwall/utils.sh ; echo -n $(get_new_port %s tcp,udp)" - local set_port = 0 - if NEW_PORT and tonumber(NEW_PORT) then - set_port = tonumber(NEW_PORT) + 1 - end - NEW_PORT = tonumber(sys.exec(string.format(cmd_format, set_port == 0 and "auto" or set_port))) - return NEW_PORT + return tonumber(sys.exec(string.format(cmd_format, "auto"))) end function exec_call(cmd) @@ -142,6 +135,18 @@ function base64Encode(text) return result end +function UrlEncode(szText) + return szText:gsub("([^%w%-_%.%~])", function(c) + return string.format("%%%02X", string.byte(c)) + end) +end + +function UrlDecode(szText) + return szText and szText:gsub("%+", " "):gsub("%%(%x%x)", function(h) + return string.char(tonumber(h, 16)) + end) or nil +end + --提取URL中的域名和端口(no ip) function get_domain_port_from_url(url) local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)") @@ -518,6 +523,46 @@ function get_valid_nodes() return nodes end +function get_node_list() + local node_list = { + socks_list = {}, + normal_list = {}, + } + uci:foreach(appname, "socks", function(s) + if s.enabled == "1" and s.node then + node_list.socks_list[#node_list.socks_list + 1] = { + id = "Socks_" .. s[".name"], + remark = i18n.translate("Socks Config") .. " [" .. s.port .. i18n.translate("Port") .. "]", + group = "Socks" + } + end + end) + for k, e in ipairs(get_valid_nodes()) do + if e.node_type == "normal" then + node_list.normal_list[#node_list.normal_list + 1] = { + id = e[".name"], + remark = e["remark"], + type = e["type"], + chain_proxy = e["chain_proxy"], + group = e["group"] + } + end + if e.protocol and e.protocol:find("^_") then + local proto = e.protocol:sub(2) + if not node_list[proto .. "_list"] then + node_list[proto .. "_list"] = {} + end + node_list[proto .. "_list"][#node_list[proto .. "_list"] + 1] = { + id = e[".name"], + remark = e["remark"], + group = e["group"], + o = e, + } + end + end + return node_list +end + function get_node_remarks(n) local remarks = "" if n then @@ -1397,3 +1442,50 @@ function apply_redirect(m) sys.call("/bin/rm -f " .. tmp_uci_file) end end + +function match_node_rule(name, rule) + if not name then return false end + if not rule or rule == "" then return true end + -- split rule by && + local function split_and(expr) + local t = {} + for part in expr:gmatch("[^&]+") do + part = part:gsub("^%s+", ""):gsub("%s+$", "") + if part ~= "" then + table.insert(t, part) + end + end + return t + end + -- match single condition + local function match_cond(str, cond) + if cond == "" then + return true + end + -- exclude: !xxx + if cond:sub(1, 1) == "!" then + local k = cond:sub(2) + if k == "" then return true end + return not str:find(k, 1, true) + end + -- prefix: ^xxx + if cond:sub(1, 1) == "^" then + local k = cond:sub(2) + return str:sub(1, #k) == k + end + -- suffix: xxx$ + if cond:sub(-1) == "$" then + local k = cond:sub(1, -2) + return str:sub(-#k) == k + end + -- contains + return str:find(cond, 1, true) ~= nil + end + -- AND logic + for _, cond in ipairs(split_and(rule)) do + if not match_cond(name, cond) then + return false + end + end + return true +end diff --git a/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua b/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua index d17e91e0d66..3551ad57905 100644 --- a/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua +++ b/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua @@ -75,7 +75,7 @@ function gen_config(var) password = node.password, method = node.method, timeout = tonumber(node.timeout), - fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false, + fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false, reuse_port = true, tcp_tproxy = var["tcp_tproxy"] and true or nil } @@ -103,7 +103,7 @@ function gen_config(var) } }, locals = {}, - fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false + fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false } if local_socks_address and local_socks_port then table.insert(config.locals, { diff --git a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 43678e081af..196557d2bec 100644 --- a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -89,6 +89,7 @@ function gen_outbound(flag, node, tag, proxy_table) if tag == nil then tag = node_id end + local remarks = node.remarks local proxy_tag = nil local fragment = nil @@ -127,12 +128,17 @@ function gen_outbound(flag, node, tag, proxy_table) address = "127.0.0.1", port = new_port } + proxy_tag = "socks <- " .. node_id else if proxy_tag then node.detour = proxy_tag end end + if remarks then + tag = tag .. ":" .. remarks + end + result = { _id = node_id, _flag = flag, @@ -474,10 +480,10 @@ function gen_outbound(flag, node, tag, proxy_table) end)(), up_mbps = (node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil, down_mbps = (node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil, - obfs = { + obfs = node.hysteria2_obfs_type and { type = node.hysteria2_obfs_type, password = node.hysteria2_obfs_password - }, + } or nil, password = node.hysteria2_auth_password or nil, tls = { enabled = true, @@ -787,10 +793,10 @@ function gen_config_server(node) protocol_table = { up_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil, down_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil, - obfs = { + obfs = node.hysteria2_obfs_type and { type = node.hysteria2_obfs_type, password = node.hysteria2_obfs_password - }, + } or nil, users = { { name = "user1", @@ -1087,9 +1093,10 @@ function gen_config(var) local socks_node = uci:get_all(appname, socks_id) or nil if socks_node then if not remarks then - remarks = "Socks_" .. socks_node.port + remarks = socks_node.port end result = { + [".name"] = "Socksid_" .. socks_id, remarks = remarks, type = "sing-box", protocol = "socks", @@ -1101,17 +1108,59 @@ function gen_config(var) return result end - function gen_urltest(_node) + local nodes_list = {} + function get_urltest_batch_nodes(_node) + if #nodes_list == 0 then + for k, e in ipairs(api.get_valid_nodes()) do + if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then + nodes_list[#nodes_list + 1] = { + id = e[".name"], + remarks = e["remarks"], + group = e["group"] + } + end + end + end + if not _node.node_group or _node.node_group == "" then return {} end + local nodes = {} + for g in _node.node_group:gmatch("%S+") do + g = api.UrlDecode(g) + for k, v in pairs(nodes_list) do + local gn = (v.group and v.group ~= "") and v.group or "default" + if gn:lower() == g:lower() and api.match_node_rule(v.remarks, _node.node_match_rule) then + nodes[#nodes + 1] = v.id + end + end + end + return nodes + end + + function get_node_by_id(node_id) + if not node_id or node_id == "" or node_id == "nil" then return nil end + if node_id:find("Socks_") then + return gen_socks_config_node(node_id) + else + return uci:get_all(appname, node_id) + end + end + + function gen_urltest_outbound(_node) local urltest_id = _node[".name"] local urltest_tag = "urltest-" .. urltest_id -- existing urltest for _, v in ipairs(outbounds) do if v.tag == urltest_tag then - return urltest_tag + return v, true end end -- new urltest - local ut_nodes = _node.urltest_node + local ut_nodes + if _node.node_add_mode and _node.node_add_mode == "batch" then + ut_nodes = get_urltest_batch_nodes(_node) + else + ut_nodes = _node.urltest_node + end + if #ut_nodes == 0 then return nil end local valid_nodes = {} for i = 1, #ut_nodes do local ut_node_id = ut_nodes[i] @@ -1125,21 +1174,9 @@ function gen_config(var) end end if is_new_ut_node then - local ut_node - if ut_node_id:find("Socks_") then - ut_node = gen_socks_config_node(ut_node_id) - else - ut_node = uci:get_all(appname, ut_node_id) - end - if ut_node then - local outbound = gen_outbound(flag, ut_node, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) - if outbound then - if ut_node.remarks then - outbound.tag = outbound.tag .. ":" .. ut_node.remarks - end - table.insert(outbounds, outbound) - valid_nodes[#valid_nodes + 1] = outbound.tag - end + local outboundTag = gen_outbound_get_tag(flag, ut_node_id, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) + if outboundTag then + valid_nodes[#valid_nodes + 1] = outboundTag end end end @@ -1154,8 +1191,7 @@ function gen_config(var) idle_timeout = (api.format_go_time(_node.urltest_idle_timeout) ~= "0s") and api.format_go_time(_node.urltest_idle_timeout) or "30m", interrupt_exist_connections = (_node.urltest_interrupt_exist_connections == "true" or _node.urltest_interrupt_exist_connections == "1") and true or false } - table.insert(outbounds, outbound) - return urltest_tag + return outbound end function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name) @@ -1189,24 +1225,35 @@ function gen_config(var) if outbound["_flag_proxy_tag"] then --Ignore else - local preproxy_node = uci:get_all(appname, node.preproxy_node) + local preproxy_node = get_node_by_id(node.preproxy_node) if preproxy_node then - local preproxy_outbound = gen_outbound(node[".name"], preproxy_node) - if preproxy_outbound then - preproxy_outbound.tag = preproxy_node[".name"] - if preproxy_node.remarks then - preproxy_outbound.tag = preproxy_outbound.tag .. ":" .. preproxy_node.remarks + local preproxy_outbound, exist + if preproxy_node.protocol == "_urltest" then + if preproxy_node.urltest_node then + preproxy_outbound, exist = gen_urltest_outbound(preproxy_node) end + else + preproxy_outbound = gen_outbound(node[".name"], preproxy_node) + end + if preproxy_outbound then outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag outbound.detour = preproxy_outbound.tag - last_insert_outbound = preproxy_outbound + if not exist then + last_insert_outbound = preproxy_outbound + end default_outTag = outbound.tag end end end end if node.chain_proxy == "2" and node.to_node then - local to_node = uci:get_all(appname, node.to_node) + local to_node = get_node_by_id(node.to_node) + if to_node then + -- Landing Node not support use special node. + if to_node.protocol:find("^_") then + to_node = nil + end + end if to_node then local to_outbound if to_node.type ~= "sing-box" then @@ -1241,9 +1288,6 @@ function gen_config(var) to_outbound.tag = outbound.tag outbound.tag = node[".name"] else - if to_node.remarks then - to_outbound.tag = to_outbound.tag .. ":" .. to_node.remarks - end to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag end if to_node.type == "sing-box" then @@ -1257,122 +1301,100 @@ function gen_config(var) return default_outTag, last_insert_outbound end + function gen_outbound_get_tag(flag, node_id, tag, proxy_table) + if not node_id or node_id == "nil" then return nil end + local node + if type(node_id) == "string" then + node = get_node_by_id(node_id) + elseif type(node_id) == "table" then + node = node_id + end + if node then + if node.protocol == "_iface" then + if node.iface then + local outbound = { + tag = tag, + type = "direct", + bind_interface = node.iface, + routing_mark = 255, + } + table.insert(outbounds, outbound) + sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) + return outbound.tag + end + return nil + end + if proxy_table.chain_proxy == "1" or proxy_table.chain_proxy == "2" then + node.chain_proxy = proxy_table.chain_proxy + node.preproxy_node = proxy_table.chain_proxy == "1" and proxy_table.preproxy_node + node.to_node = proxy_table.chain_proxy == "2" and proxy_table.to_node + proxy_table.chain_proxy = nil + proxy_table.preproxy_node = nil + proxy_table.to_node = nil + end + local outbound, exist + if node.protocol == "_urltest" then + if node.urltest_node then + outbound, exist = gen_urltest_outbound(node) + if exist then + return outbound.tag + end + end + else + for _, _outbound in ipairs(outbounds) do + -- Avoid generating duplicate nested processes + if _outbound["_flag_proxy_tag"] and _outbound["_flag_proxy_tag"]:find("socks <- " .. node[".name"], 1, true) then + outbound = api.clone(_outbound) + outbound.tag = tag + break + end + end + if not outbound then + outbound = gen_outbound(flag, node, tag, proxy_table) + end + end + if outbound then + local default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) + table.insert(outbounds, outbound) + if last_insert_outbound then + table.insert(outbounds, last_insert_outbound) + end + return default_outbound_tag + end + end + end + rules = {} if node.protocol == "_shunt" then - local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil - local preproxy_tag = preproxy_rule_name - local preproxy_node_id = preproxy_rule_name and node["main_node"] or nil - inner_fakedns = node.fakedns or "0" local function gen_shunt_node(rule_name, _node_id) if not rule_name then return nil, nil end if not _node_id then _node_id = node[rule_name] end - local rule_outboundTag if _node_id == "_direct" then - rule_outboundTag = "direct" + return "direct" elseif _node_id == "_blackhole" then - rule_outboundTag = "block" + return "block" elseif _node_id == "_default" and rule_name ~= "default" then - rule_outboundTag = "default" - elseif _node_id and _node_id:find("Socks_") then - local socks_node = gen_socks_config_node(_node_id) - local _outbound = gen_outbound(flag, socks_node, rule_name) - if _outbound then - table.insert(outbounds, _outbound) - rule_outboundTag = _outbound.tag - end + return "default" elseif _node_id then - local _node = uci:get_all(appname, _node_id) - if not _node then return nil, nil end - - if api.is_normal_node(_node) then - local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id - local copied_outbound - for index, value in ipairs(outbounds) do - if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then - copied_outbound = api.clone(value) - break - end - end - if copied_outbound then - copied_outbound.tag = rule_name .. ":" .. _node.remarks - table.insert(outbounds, copied_outbound) - rule_outboundTag = copied_outbound.tag - else - if use_proxy then - local pre_proxy = nil - if _node.type ~= "sing-box" then - pre_proxy = true - end - if pre_proxy then - local new_port = api.get_new_port() - table.insert(inbounds, { - type = "direct", - tag = "proxy_" .. rule_name, - listen = "127.0.0.1", - listen_port = new_port, - override_address = _node.address, - override_port = tonumber(_node.port), - }) - if _node.tls_serverName == nil then - _node.tls_serverName = _node.address - end - _node.address = "127.0.0.1" - _node.port = new_port - table.insert(rules, 1, { - inbound = {"proxy_" .. rule_name}, - outbound = preproxy_tag, - }) - end - end - local proxy_table = { - tag = use_proxy and preproxy_tag or nil, - run_socks_instance = not no_run - } - if not proxy_table.tag then - if singbox_settings.fragment == "1" then - proxy_table.fragment = true - end - if singbox_settings.record_fragment == "1" then - proxy_table.record_fragment = true - end - end - local _outbound = gen_outbound(flag, _node, rule_name, proxy_table) - if _outbound then - _outbound.tag = _outbound.tag .. ":" .. _node.remarks - rule_outboundTag, last_insert_outbound = set_outbound_detour(_node, _outbound, outbounds, rule_name) - table.insert(outbounds, _outbound) - if last_insert_outbound then - table.insert(outbounds, last_insert_outbound) - end - end - end - elseif _node.protocol == "_urltest" then - rule_outboundTag = gen_urltest(_node) - elseif _node.protocol == "_iface" then - if _node.iface then - local _outbound = { - type = "direct", - tag = rule_name .. ":" .. _node.remarks, - bind_interface = _node.iface, - routing_mark = 255, - } - table.insert(outbounds, _outbound) - rule_outboundTag = _outbound.tag - sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface)) - end + local proxy_table = { + fragment = singbox_settings.fragment == "1", + record_fragment = singbox_settings.record_fragment == "1", + run_socks_instance = not no_run, + } + local preproxy_node_id = node[rule_name .. "_proxy_tag"] + if preproxy_node_id == _node_id then preproxy_node_id = nil end + if preproxy_node_id then + proxy_table.chain_proxy = "2" + proxy_table.to_node = _node_id + return gen_outbound_get_tag(flag, preproxy_node_id, rule_name, proxy_table) + else + return gen_outbound_get_tag(flag, _node_id, rule_name, proxy_table) end end - return rule_outboundTag - end - - if preproxy_tag and preproxy_node_id then - local preproxy_outboundTag = gen_shunt_node(preproxy_rule_name, preproxy_node_id) - if preproxy_outboundTag then - preproxy_tag = preproxy_outboundTag - end + return nil end --default_node @@ -1487,6 +1509,7 @@ function gen_config(var) if e.domain_list then local domain_table = { + shunt_tag = e[".name"], outboundTag = outboundTag, domain = {}, domain_suffix = {}, @@ -1578,32 +1601,12 @@ function gen_config(var) table.insert(rules, rule) end end) - elseif node.protocol == "_urltest" then - if node.urltest_node then - COMMON.default_outbound_tag = gen_urltest(node) - end - elseif node.protocol == "_iface" then - if node.iface then - local outbound = { - type = "direct", - tag = node.remarks or node_id, - bind_interface = node.iface, - routing_mark = 255, - } - table.insert(outbounds, outbound) - COMMON.default_outbound_tag = outbound.tag - sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) - end else - local outbound = gen_outbound(flag, node, nil, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) - if outbound then - outbound.tag = outbound.tag .. ":" .. node.remarks - COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) - table.insert(outbounds, outbound) - if last_insert_outbound then - table.insert(outbounds, last_insert_outbound) - end - end + COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, { + fragment = singbox_settings.fragment == "1" or nil, + record_fragment = singbox_settings.record_fragment == "1" or nil, + run_socks_instance = not no_run + }) end for index, value in ipairs(rules) do @@ -1835,7 +1838,7 @@ function gen_config(var) if dns_socks_address and dns_socks_port then else if node_id and (tcp_redir_port or udp_redir_port) then - local node = uci:get_all(appname, node_id) + local node = get_node_by_id(node_id) if node.protocol == "_shunt" then if node.default_node == "_direct" then default_dns_flag = "direct" @@ -1885,7 +1888,7 @@ function gen_config(var) dns_rule.client_subnet = (version_ge_1_12_0 and remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil --Migrate to 1.12 DNS if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then local remote_shunt_server = api.clone(remote_server) - remote_shunt_server.tag = value.outboundTag + remote_shunt_server.tag = value.shunt_tag local is_local = (remote_server.address and api.is_local_ip(remote_server.address)) or (remote_server.server and api.is_local_ip(remote_server.server)) --dns为本地ip,不走代理 remote_shunt_server.detour = is_local and "direct" or value.outboundTag diff --git a/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua b/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua index 29cb467d8d8..94fd5758a9f 100644 --- a/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua +++ b/applications/luci-app-passwall/luasrc/passwall/util_trojan.lua @@ -87,7 +87,7 @@ function gen_config(var) no_delay = true, keep_alive = true, reuse_port = true, - fast_open = (node.tcp_fast_open == "true") and true or false, + fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false, fast_open_qlen = 20 } } diff --git a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua index a23d1cf061a..4e234d15d43 100644 --- a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -45,6 +45,7 @@ function gen_outbound(flag, node, tag, proxy_table) if tag == nil then tag = node_id end + local remarks = node.remarks local proxy_tag = nil local fragment = nil @@ -57,7 +58,7 @@ function gen_outbound(flag, node, tag, proxy_table) run_socks_instance = proxy_table.run_socks_instance end - if node.type ~= "Xray" then + if node.type ~= "Xray" or node.protocol == "_balancing" then if node.type == "Socks" then node.protocol = "socks" node.transport = "tcp" @@ -68,6 +69,9 @@ function gen_outbound(flag, node, tag, proxy_table) if tag and node_id and not tag:find(node_id) then config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port) end + if node.protocol == "_balancing" then + config_file = string.format("%s_%s_%s.json", flag, node_id, new_port) + end if run_socks_instance then sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null', appname, @@ -88,6 +92,7 @@ function gen_outbound(flag, node, tag, proxy_table) node.port = new_port end node.stream_security = "none" + proxy_tag = "socks <- " .. node_id else if proxy_tag then node.proxySettings = { @@ -127,6 +132,10 @@ function gen_outbound(flag, node, tag, proxy_table) node.stream_security = "tls" end + if remarks then + tag = tag .. ":" .. remarks + end + result = { _id = node_id, _flag = flag, @@ -286,9 +295,11 @@ function gen_outbound(flag, node, tag, proxy_table) finalmask = (node.transport == "mkcp") and { udp = (function() local t = {} + local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat", + dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"} if node.mkcp_guise and node.mkcp_guise ~= "none" then - local g = { type = node.mkcp_guise } - if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then + local g = { type = map[node.mkcp_guise] } + if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then g.settings = { domain = node.mkcp_domain } end t[#t + 1] = g @@ -597,9 +608,11 @@ function gen_config_server(node) finalmask = (node.transport == "mkcp") and { udp = (function() local t = {} + local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat", + dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"} if node.mkcp_guise and node.mkcp_guise ~= "none" then - local g = { type = node.mkcp_guise } - if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then + local g = { type = map[node.mkcp_guise] } + if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then g.settings = { domain = node.mkcp_domain } end t[#t + 1] = g @@ -613,6 +626,7 @@ function gen_config_server(node) end)() } or nil, sockopt = { + tcpFastOpen = (node.tcp_fast_open == "1") and true or nil, acceptProxyProtocol = (node.acceptProxyProtocol and node.acceptProxyProtocol == "1") and true or false } } @@ -769,9 +783,10 @@ function gen_config(var) local socks_node = uci:get_all(appname, socks_id) or nil if socks_node then if not remarks then - remarks = "Socks_" .. socks_node.port + remarks = socks_node.port end result = { + [".name"] = "Socksid_" .. socks_id, remarks = remarks, type = "Xray", protocol = "socks", @@ -784,6 +799,42 @@ function gen_config(var) return result end + function get_node_by_id(node_id) + if not node_id or node_id == "" or node_id == "nil" then return nil end + if node_id:find("Socks_") then + return gen_socks_config_node(node_id) + else + return uci:get_all(appname, node_id) + end + end + + local nodes_list = {} + function get_balancer_batch_nodes(_node) + if #nodes_list == 0 then + for k, e in ipairs(api.get_valid_nodes()) do + if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then + nodes_list[#nodes_list + 1] = { + id = e[".name"], + remarks = e["remarks"], + group = e["group"] + } + end + end + end + if not _node.node_group or _node.node_group == "" then return {} end + local nodes = {} + for g in _node.node_group:gmatch("%S+") do + g = api.UrlDecode(g) + for k, v in pairs(nodes_list) do + local gn = (v.group and v.group ~= "") and v.group or "default" + if gn:lower() == g:lower() and api.match_node_rule(v.remarks, _node.node_match_rule) then + nodes[#nodes + 1] = v.id + end + end + end + return nodes + end + function gen_loopback(outbound_tag, loopback_dst) if not outbound_tag or outbound_tag == "" then return nil end local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo" @@ -808,7 +859,12 @@ function gen_config(var) end end -- new balancer - local blc_nodes = _node.balancing_node + local blc_nodes + if _node.node_add_mode and _node.node_add_mode == "batch" then + blc_nodes = get_balancer_batch_nodes(_node) + else + blc_nodes = _node.balancing_node + end local valid_nodes = {} for i = 1, #blc_nodes do local blc_node_id = blc_nodes[i] @@ -822,21 +878,9 @@ function gen_config(var) end end if is_new_blc_node then - local blc_node - if blc_node_id:find("Socks_") then - blc_node = gen_socks_config_node(blc_node_id) - else - blc_node = uci:get_all(appname, blc_node_id) - end - if blc_node then - local outbound = gen_outbound(flag, blc_node, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run }) - if outbound then - if blc_node.remarks then - outbound.tag = outbound.tag .. ":" .. blc_node.remarks - end - table.insert(outbounds, outbound) - valid_nodes[#valid_nodes + 1] = outbound.tag - end + local outboundTag = gen_outbound_get_tag(flag, blc_node_id, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) + if outboundTag then + valid_nodes[#valid_nodes + 1] = outboundTag end end end @@ -856,21 +900,12 @@ function gen_config(var) end end if is_new_node then - local fallback_node - if fallback_node_id:find("Socks_") then - fallback_node = gen_socks_config_node(fallback_node_id) - else - fallback_node = uci:get_all(appname, fallback_node_id) - end + local fallback_node = get_node_by_id(fallback_node_id) if fallback_node then if fallback_node.protocol ~= "_balancing" then - local outbound = gen_outbound(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run }) - if outbound then - if fallback_node.remarks then - outbound.tag = outbound.tag .. ":" .. fallback_node.remarks - end - table.insert(outbounds, outbound) - fallback_node_tag = outbound.tag + local outboundTag = gen_outbound_get_tag(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) + if outboundTag then + fallback_node_tag = outboundTag end else if gen_balancer(fallback_node) then @@ -935,14 +970,10 @@ function gen_config(var) if outbound["_flag_proxy_tag"] then --Ignore else - local preproxy_node = uci:get_all(appname, node.preproxy_node) + local preproxy_node = get_node_by_id(node.preproxy_node) if preproxy_node then local preproxy_outbound = gen_outbound(node[".name"], preproxy_node) if preproxy_outbound then - preproxy_outbound.tag = preproxy_node[".name"] - if preproxy_node.remarks then - preproxy_outbound.tag = preproxy_outbound.tag .. ":" .. preproxy_node.remarks - end outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag outbound.proxySettings = { tag = preproxy_outbound.tag, @@ -955,7 +986,13 @@ function gen_config(var) end end if node.chain_proxy == "2" and node.to_node then - local to_node = uci:get_all(appname, node.to_node) + local to_node = get_node_by_id(node.to_node) + if to_node then + -- Landing Node not support use special node. + if to_node.protocol:find("^_") then + to_node = nil + end + end if to_node then local to_outbound if to_node.type ~= "Xray" then @@ -989,9 +1026,6 @@ function gen_config(var) to_outbound.tag = outbound.tag outbound.tag = node[".name"] else - if to_node.remarks then - to_outbound.tag = to_outbound.tag .. ":" .. to_node.remarks - end to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag end if to_node.type == "Xray" then @@ -1008,13 +1042,69 @@ function gen_config(var) return default_outTag, last_insert_outbound end - if node.protocol == "_shunt" then - local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil - local preproxy_tag = preproxy_rule_name - local preproxy_node_id = preproxy_rule_name and node["main_node"] or nil - local preproxy_outbound_tag, preproxy_balancer_tag - local preproxy_nodes + function gen_outbound_get_tag(flag, node_id, tag, proxy_table) + if not node_id or node_id == "nil" then return nil end + local node + if type(node_id) == "string" then + node = get_node_by_id(node_id) + elseif type(node_id) == "table" then + node = node_id + end + if node then + if node.protocol == "_iface" then + if node.iface then + local outbound = { + tag = tag, + protocol = "freedom", + streamSettings = { + sockopt = { + mark = 255, + interface = node.iface + } + } + } + table.insert(outbounds, outbound) + sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) + return outbound.tag + end + return nil + end + if proxy_table.chain_proxy == "1" or proxy_table.chain_proxy == "2" then + node.chain_proxy = proxy_table.chain_proxy + node.preproxy_node = proxy_table.chain_proxy == "1" and proxy_table.preproxy_node + node.to_node = proxy_table.chain_proxy == "2" and proxy_table.to_node + proxy_table.chain_proxy = nil + proxy_table.preproxy_node = nil + proxy_table.to_node = nil + end + local outbound + for _, _outbound in ipairs(outbounds) do + -- Avoid generating duplicate nested processes + if _outbound["_flag_proxy_tag"] and _outbound["_flag_proxy_tag"]:find("socks <- " .. node[".name"], 1, true) then + outbound = api.clone(_outbound) + outbound.tag = tag + break + end + end + if not outbound then + outbound = gen_outbound(flag, node, tag, proxy_table) + end + if outbound then + local default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) + if tag == "default" then + table.insert(outbounds, 1, outbound) + else + table.insert(outbounds, outbound) + end + if last_insert_outbound then + table.insert(outbounds, last_insert_outbound) + end + return default_outbound_tag + end + end + end + if node.protocol == "_shunt" then inner_fakedns = node.fakedns or "0" local function gen_shunt_node(rule_name, _node_id) @@ -1027,144 +1117,25 @@ function gen_config(var) return "direct", nil elseif _node_id == "_blackhole" then return "blackhole", nil - elseif _node_id == "_default" then + elseif _node_id == "_default" and rule_name ~= "default" then return "default", nil - elseif _node_id and _node_id:find("Socks_") then - local socks_tag - local socks_node = gen_socks_config_node(_node_id) - local outbound = gen_outbound(flag, socks_node, rule_name) - if outbound then - if rule_name == "default" then - table.insert(outbounds, 1, outbound) - else - table.insert(outbounds, outbound) - end - socks_tag = outbound.tag - end - return socks_tag, nil - end - - local _node = uci:get_all(appname, _node_id) - if not _node then return nil, nil end - - if api.is_normal_node(_node) then - local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id - if use_proxy and preproxy_balancer_tag and preproxy_nodes[_node_id] then use_proxy = false end - local copied_outbound - for index, value in ipairs(outbounds) do - if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then - copied_outbound = api.clone(value) - break - end - end - if copied_outbound then - copied_outbound.tag = rule_name - table.insert(outbounds, copied_outbound) - return copied_outbound.tag, nil - end - --new outbound - if use_proxy and _node.type ~= "Xray" then - local new_port = api.get_new_port() - table.insert(inbounds, { - tag = "proxy_" .. rule_name, - listen = "127.0.0.1", - port = new_port, - protocol = "dokodemo-door", - settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)} - }) - if _node.tls_serverName == nil then - _node.tls_serverName = _node.address - end - _node.address = "127.0.0.1" - _node.port = new_port - table.insert(rules, 1, { - inboundTag = {"proxy_" .. rule_name}, - outboundTag = not preproxy_balancer_tag and preproxy_tag or nil, - balancerTag = preproxy_balancer_tag - }) - end + elseif _node_id then local proxy_table = { - tag = use_proxy and preproxy_tag or nil, - run_socks_instance = not no_run + fragment = xray_settings.fragment == "1", + noise = xray_settings.noise == "1", + run_socks_instance = not no_run, } - if not proxy_table.tag then - if xray_settings.fragment == "1" then - proxy_table.fragment = true - end - if xray_settings.noise == "1" then - proxy_table.noise = true - end - end - local outbound = gen_outbound(flag, _node, rule_name, proxy_table) - local outbound_tag - if outbound then - outbound.tag = outbound.tag .. ":" .. _node.remarks - outbound_tag, last_insert_outbound = set_outbound_detour(_node, outbound, outbounds, rule_name) - if rule_name == "default" then - table.insert(outbounds, 1, outbound) - else - table.insert(outbounds, outbound) - end - if last_insert_outbound then - table.insert(outbounds, last_insert_outbound) - end - end - return outbound_tag, nil - elseif _node.protocol == "_balancing" then - local blc_tag = gen_balancer(_node, rule_name) - if rule_name == "default" then - for i, ob in ipairs(outbounds) do - if ob.protocol == "loopback" and ob.tag == "default" then - if i > 1 then table.insert(outbounds, 1, table.remove(outbounds, i)) end - break - end - end - end - return nil, blc_tag - elseif _node.protocol == "_iface" then - local outbound_tag - if _node.iface then - local outbound = { - protocol = "freedom", - tag = rule_name, - streamSettings = { - sockopt = { - mark = 255, - interface = _node.iface - } - } - } - outbound_tag = outbound.tag - if rule_name == "default" then - table.insert(outbounds, 1, outbound) - else - table.insert(outbounds, outbound) - end - sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface)) - end - return outbound_tag, nil - end - end - - if preproxy_tag and preproxy_node_id then - preproxy_outbound_tag, preproxy_balancer_tag = gen_shunt_node(preproxy_rule_name, preproxy_node_id) - if preproxy_balancer_tag then - local _node_id = preproxy_node_id - preproxy_nodes = {} - while _node_id do - _node = uci:get_all(appname, _node_id) - if not _node then break end - if _node.protocol ~= "_balancing" then - preproxy_nodes[_node_id] = true - break - end - local _blc_nodes = _node.balancing_node - for i = 1, #_blc_nodes do preproxy_nodes[_blc_nodes[i]] = true end - _node_id = _node.fallback_node + local preproxy_node_id = node[rule_name .. "_proxy_tag"] + if preproxy_node_id == _node_id then preproxy_node_id = nil end + if preproxy_node_id then + proxy_table.chain_proxy = "2" + proxy_table.to_node = _node_id + return gen_outbound_get_tag(flag, preproxy_node_id, rule_name, proxy_table), nil + else + return gen_outbound_get_tag(flag, _node_id, rule_name, proxy_table), nil end - else - preproxy_tag = preproxy_outbound_tag end + return nil, nil end --default_node @@ -1301,7 +1272,13 @@ function gen_config(var) rules = rules } elseif node.protocol == "_balancing" then - if node.balancing_node then + local blc_nodes + if node.node_add_mode and node.node_add_mode == "batch" then + blc_nodes = get_balancer_batch_nodes(node) + else + blc_nodes = node.balancing_node + end + if blc_nodes and #blc_nodes > 0 then local balancer_tag = gen_balancer(node) if balancer_tag then table.insert(rules, { network = "tcp,udp", balancerTag = balancer_tag }) @@ -1312,42 +1289,24 @@ function gen_config(var) } COMMON.default_balancer_tag = balancer_tag end - elseif node.protocol == "_iface" then - if node.iface then - local outbound = { - protocol = "freedom", - tag = node.remarks or node_id, - streamSettings = { - sockopt = { - mark = 255, - interface = node.iface - } - } - } - table.insert(outbounds, outbound) - COMMON.default_outbound_tag = outbound.tag - sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) - end else - local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run }) - if outbound then - outbound.tag = outbound.tag .. ":" .. node.remarks - COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) - table.insert(outbounds, outbound) - if last_insert_outbound then - table.insert(outbounds, last_insert_outbound) - end - end - routing = { - domainStrategy = "AsIs", - domainMatcher = "hybrid", - rules = rules - } - table.insert(routing.rules, { - ruleTag = "default", - outboundTag = COMMON.default_outbound_tag, - network = "tcp,udp" + COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, { + fragment = xray_settings.fragment == "1" or nil, + noise = xray_settings.noise == "1" or nil, + run_socks_instance = not no_run }) + if COMMON.default_outbound_tag then + routing = { + domainStrategy = "AsIs", + domainMatcher = "hybrid", + rules = rules + } + table.insert(routing.rules, { + ruleTag = "default", + outboundTag = COMMON.default_outbound_tag, + network = "tcp,udp" + }) + end end if tcp_redir_port or udp_redir_port then diff --git a/applications/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm b/applications/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm index e5aabfafc0b..32510c314e2 100644 --- a/applications/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm +++ b/applications/luci-app-passwall/luasrc/view/passwall/include/shunt_options.htm @@ -6,6 +6,7 @@ @@ -1736,7 +1764,7 @@