diff --git a/.circleci/config.yml b/.circleci/config.yml index 794ce64ad1a..3dff8a52ca6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,24 +2,15 @@ version: 2 jobs: go-version-latest: docker: - - image: cimg/go:1.22-node + - image: cimg/go:1.22-node resource_class: large steps: - - checkout - - run: make - - run: make alltest - go-version-last: - docker: - - image: cimg/go:1.21-node - resource_class: large - steps: - - checkout - - run: make - - run: make alltest + - checkout + - run: make + - run: make alltest workflows: version: 2 build_and_test: jobs: - - go-version-latest - - go-version-last + - go-version-latest diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 18f69d52438..b3e2cb43c39 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -23,7 +23,7 @@ jobs: uses: golangci/golangci-lint-action@v4 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.56 + version: v1.57 # Optional: golangci-lint command line arguments. # args: --issues-exit-code=0 diff --git a/.golangci.yml b/.golangci.yml index cd92e43c89f..e2f8a240991 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,5 @@ service: - golangci-lint-version: 1.56.x # use the fixed version to not introduce new linters unexpectedly + golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly run: concurrency: 4 @@ -8,23 +8,6 @@ run: build-tags: - integ - integfuzz - # which dirs to skip: they won't be analyzed; - # can use regexp here: generated.*, regexp is applied on full path; - # default value is empty list, but next dirs are always skipped independently - # from this option's value: - # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs: - - genfiles$ - - vendor$ - - bin$ - - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - skip-files: - - ".*\\.pb\\.go" - - ".*\\.gen\\.go" linters: disable-all: true @@ -136,6 +119,14 @@ issues: - unparam text: "is always false" + exclude-dirs: + - genfiles$ + - vendor$ + - bin$ + exclude-files: + - ".*\\.pb\\.go" + - ".*\\.gen\\.go" + # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all # excluded by default patterns execute `golangci-lint run --help`. diff --git a/Makefile.cross-compiles b/Makefile.cross-compiles index 3c123df8af6..8887cad4bee 100644 --- a/Makefile.cross-compiles +++ b/Makefile.cross-compiles @@ -1,8 +1,8 @@ -export PATH := $(GOPATH)/bin:$(PATH) +export PATH := $(PATH):`go env GOPATH`/bin export GO111MODULE=on LDFLAGS := -s -w -os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 +os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 android:arm64 all: build @@ -15,8 +15,8 @@ app: gomips=$(shell echo "$(n)" | cut -d : -f 3);\ target_suffix=$${os}_$${arch};\ echo "Build $${os}-$${arch}...";\ - env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_$${target_suffix} ./cmd/frpc;\ - env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\ + env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -tags frpc -o ./release/frpc_$${target_suffix} ./cmd/frpc;\ + env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -tags frps -o ./release/frps_$${target_suffix} ./cmd/frps;\ echo "Build $${os}-$${arch} done";\ ) @mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe diff --git a/README.md b/README.md index cf6b7cf5495..69cce9ddc48 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![Build Status](https://circleci.com/gh/fatedier/frp.svg?style=shield)](https://circleci.com/gh/fatedier/frp) [![GitHub release](https://img.shields.io/github/tag/fatedier/frp.svg?label=release)](https://github.com/fatedier/frp/releases) +[![Go Report Card](https://goreportcard.com/badge/github.com/fatedier/frp)](https://goreportcard.com/report/github.com/fatedier/frp) +[![GitHub Releases Stats](https://img.shields.io/github/downloads/fatedier/frp/total.svg?logo=github)](https://somsubhra.github.io/github-release-stats/?username=fatedier&repository=frp) [README](README.md) | [中文文档](README_zh.md) @@ -11,6 +13,10 @@ +   + + +

@@ -72,6 +78,7 @@ frp also offers a P2P connect mode. * [URL Routing](#url-routing) * [TCP Port Multiplexing](#tcp-port-multiplexing) * [Connecting to frps via PROXY](#connecting-to-frps-via-proxy) + * [Port range mapping](#port-range-mapping) * [Client Plugins](#client-plugins) * [Server Manage Plugins](#server-manage-plugins) * [SSH Tunnel Gateway](#ssh-tunnel-gateway) @@ -1152,6 +1159,24 @@ serverPort = 7000 transport.proxyURL = "http://user:pwd@192.168.1.128:8080" ``` +### Port range mapping + +*Added in v0.56.0* + +We can use the range syntax of Go template combined with the built-in `parseNumberRangePair` function to achieve port range mapping. + +The following example, when run, will create 8 proxies named `test-6000, test-6001 ... test-6007`, each mapping the remote port to the local port. + +``` +{{- range $_, $v := parseNumberRangePair "6000-6006,6007" "6000-6006,6007" }} +[[proxies]] +name = "tcp-{{ $v.First }}" +type = "tcp" +localPort = {{ $v.First }} +remotePort = {{ $v.Second }} +{{- end }} +``` + ### Client Plugins frpc only forwards requests to local TCP or UDP ports by default. diff --git a/README_zh.md b/README_zh.md index d78875c40d4..2c902f9f049 100644 --- a/README_zh.md +++ b/README_zh.md @@ -2,6 +2,8 @@ [![Build Status](https://circleci.com/gh/fatedier/frp.svg?style=shield)](https://circleci.com/gh/fatedier/frp) [![GitHub release](https://img.shields.io/github/tag/fatedier/frp.svg?label=release)](https://github.com/fatedier/frp/releases) +[![Go Report Card](https://goreportcard.com/badge/github.com/fatedier/frp)](https://goreportcard.com/report/github.com/fatedier/frp) +[![GitHub Releases Stats](https://img.shields.io/github/downloads/fatedier/frp/total.svg?logo=github)](https://somsubhra.github.io/github-release-stats/?username=fatedier&repository=frp) [README](README.md) | [中文文档](README_zh.md) @@ -13,6 +15,10 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP +   + + +

diff --git a/Release.md b/Release.md index c0d90bbc868..163ca3728a1 100644 --- a/Release.md +++ b/Release.md @@ -1,3 +1,7 @@ ### Features -* Proxy supports configuring annotations, which will be displayed in the frps dashboard. +* `https2http` and `https2https` plugin now supports `X-Forwared-For` header. + +### Fixes + +* `X-Forwared-For` header is now correctly set in the request to the backend server for proxy type http. diff --git a/assets/frps/static/index-ky4re_ta.js b/assets/frps/static/index-Q42Pu2_S.js similarity index 99% rename from assets/frps/static/index-ky4re_ta.js rename to assets/frps/static/index-Q42Pu2_S.js index cf7b8c3b515..45293e05cfd 100644 --- a/assets/frps/static/index-ky4re_ta.js +++ b/assets/frps/static/index-Q42Pu2_S.js @@ -81,4 +81,4 @@ PERFORMANCE OF THIS SOFTWARE. `:"
",g=c.join(m);this._showOrMove(s,function(){this._updateContentNotChangedOnAxis(r,u)?this._updatePosition(s,d,o[0],o[1],this._tooltipContent,u):this._showTooltipContent(s,g,u,Math.random()+"",o[0],o[1],d,null,h)})},t.prototype._showSeriesItemTooltip=function(r,n,i){var a=this._ecModel,o=dt(n),s=o.seriesIndex,l=a.getSeriesByIndex(s),u=o.dataModel||l,f=o.dataIndex,c=o.dataType,h=u.getData(c),d=this._renderMode,v=r.positionDefault,p=bl([h.getItemModel(f),u,l&&(l.coordinateSystem||{}).model],this._tooltipModel,v?{position:v}:null),m=p.get("trigger");if(!(m!=null&&m!=="item")){var g=u.getDataParams(f,c),y=new up;g.marker=y.makeTooltipMarker("item",ku(g.color),d);var _=FS(u.formatTooltip(f,!1,c)),b=p.get("order"),x=p.get("valueFormatter"),w=_.frag,S=w?WS(x?ue({valueFormatter:x},w):w,y,d,b,a.get("useUTC"),p.get("textStyle")):_.text,C="item_"+u.name+"_"+f;this._showOrMove(p,function(){this._showTooltipContent(p,S,g,C,r.offsetX,r.offsetY,r.position,r.target,y)}),i({type:"showTip",dataIndexInside:f,dataIndex:h.getRawIndex(f),seriesIndex:s,from:this.uid})}},t.prototype._showComponentItemTooltip=function(r,n,i){var a=dt(n),o=a.tooltipConfig,s=o.option||{};if(Ee(s)){var l=s;s={content:l,formatter:l}}var u=[s],f=this._ecModel.getComponent(a.componentMainType,a.componentIndex);f&&u.push(f),u.push({formatter:s.content});var c=r.positionDefault,h=bl(u,this._tooltipModel,c?{position:c}:null),d=h.get("content"),v=Math.random()+"",p=new up;this._showOrMove(h,function(){var m=tt(h.get("formatterParams")||{});this._showTooltipContent(h,d,m,v,r.offsetX,r.offsetY,r.position,n,p)}),i({type:"showTip",from:this.uid})},t.prototype._showTooltipContent=function(r,n,i,a,o,s,l,u,f){if(this._ticket="",!(!r.get("showContent")||!r.get("show"))){var c=this._tooltipContent;c.setEnterable(r.get("enterable"));var h=r.get("formatter");l=l||r.get("position");var d=n,v=this._getNearestPoint([o,s],i,r.get("trigger"),r.get("borderColor")),p=v.color;if(h)if(Ee(h)){var m=r.ecModel.get("useUTC"),g=ye(i)?i[0]:i,y=g&&g.axisType&&g.axisType.indexOf("time")>=0;d=h,y&&(d=Ph(g.axisValue,d,m)),d=pE(d,i,!0)}else if(Ye(h)){var _=xt(function(b,x){b===this._ticket&&(c.setContent(x,f,r,p,l),this._updatePosition(r,l,o,s,c,i,u))},this);this._ticket=a,d=h(i,a,_)}else d=h;c.setContent(d,f,r,p,l),c.show(r,p),this._updatePosition(r,l,o,s,c,i,u)}},t.prototype._getNearestPoint=function(r,n,i,a){if(i==="axis"||ye(n))return{color:a||(this._renderMode==="html"?"#fff":"none")};if(!ye(n))return{color:a||n.color||n.borderColor}},t.prototype._updatePosition=function(r,n,i,a,o,s,l){var u=this._api.getWidth(),f=this._api.getHeight();n=n||r.get("position");var c=o.getSize(),h=r.get("align"),d=r.get("verticalAlign"),v=l&&l.getBoundingRect().clone();if(l&&v.applyTransform(l.transform),Ye(n)&&(n=n([i,a],s,o.el,v,{viewSize:[u,f],contentSize:c.slice()})),ye(n))i=yt(n[0],u),a=yt(n[1],f);else if(Re(n)){var p=n;p.width=c[0],p.height=c[1];var m=Ls(p,{width:u,height:f});i=m.x,a=m.y,h=null,d=null}else if(Ee(n)&&l){var g=$ee(n,v,c,r.get("borderWidth"));i=g[0],a=g[1]}else{var g=Bee(i,a,o,u,f,h?null:20,d?null:20);i=g[0],a=g[1]}if(h&&(i-=CC(h)?c[0]/2:h==="right"?c[0]:0),d&&(a-=CC(d)?c[1]/2:d==="bottom"?c[1]:0),_D(r)){var g=Fee(i,a,o,u,f);i=g[0],a=g[1]}o.moveTo(i,a)},t.prototype._updateContentNotChangedOnAxis=function(r,n){var i=this._lastDataByCoordSys,a=this._cbParamsList,o=!!i&&i.length===r.length;return o&&R(i,function(s,l){var u=s.dataByAxis||[],f=r[l]||{},c=f.dataByAxis||[];o=o&&u.length===c.length,o&&R(u,function(h,d){var v=c[d]||{},p=h.seriesDataIndices||[],m=v.seriesDataIndices||[];o=o&&h.value===v.value&&h.axisType===v.axisType&&h.axisId===v.axisId&&p.length===m.length,o&&R(p,function(g,y){var _=m[y];o=o&&g.seriesIndex===_.seriesIndex&&g.dataIndex===_.dataIndex}),a&&R(h.seriesDataIndices,function(g){var y=g.seriesIndex,_=n[y],b=a[y];_&&b&&b.data!==_.data&&(o=!1)})})}),this._lastDataByCoordSys=r,this._cbParamsList=n,!!o},t.prototype._hide=function(r){this._lastDataByCoordSys=null,r({type:"hideTip",from:this.uid})},t.prototype.dispose=function(r,n){He.node||!n.getDom()||(wm(this,"_updatePosition"),this._tooltipContent.dispose(),Fm("itemTooltip",n))},t.type="tooltip",t}(Mi);function bl(e,t,r){var n=t.ecModel,i;r?(i=new sr(r,n,n),i=new sr(t.option,i,n)):i=t;for(var a=e.length-1;a>=0;a--){var o=e[a];o&&(o instanceof sr&&(o=o.get("tooltip",!0)),Ee(o)&&(o={formatter:o}),o&&(i=new sr(o,i,n)))}return i}function xC(e,t){return e.dispatchAction||xt(t.dispatchAction,t)}function Bee(e,t,r,n,i,a,o){var s=r.getSize(),l=s[0],u=s[1];return a!=null&&(e+l+a+2>n?e-=l+a:e+=a),o!=null&&(t+u+o>i?t-=u+o:t+=o),[e,t]}function Fee(e,t,r,n,i){var a=r.getSize(),o=a[0],s=a[1];return e=Math.min(e+o,n)-o,t=Math.min(t+s,i)-s,e=Math.max(e,0),t=Math.max(t,0),[e,t]}function $ee(e,t,r,n){var i=r[0],a=r[1],o=Math.ceil(Math.SQRT2*n)+8,s=0,l=0,u=t.width,f=t.height;switch(e){case"inside":s=t.x+u/2-i/2,l=t.y+f/2-a/2;break;case"top":s=t.x+u/2-i/2,l=t.y-a-o;break;case"bottom":s=t.x+u/2-i/2,l=t.y+f+o;break;case"left":s=t.x-i-o,l=t.y+f/2-a/2;break;case"right":s=t.x+u+o,l=t.y+f/2-a/2}return[s,l]}function CC(e){return e==="center"||e==="middle"}function Hee(e,t,r){var n=v0(e).queryOptionMap,i=n.keys()[0];if(!(!i||i==="series")){var a=lf(t,i,n.get(i),{useDefault:!1,enableAll:!1,enableNone:!1}),o=a.models[0];if(o){var s=r.getViewOfComponentModel(o),l;if(s.group.traverse(function(u){var f=dt(u).tooltipConfig;if(f&&f.name===e.name)return l=u,!0}),l)return{componentMainType:i,componentIndex:o.componentIndex,el:l}}}}const zee=Nee;function Vee(e){va(yD),e.registerComponentModel(See),e.registerComponentView(zee),e.registerAction({type:"showTip",event:"showTip",update:"tooltip:manuallyShowTip"},Er),e.registerAction({type:"hideTip",event:"hideTip",update:"tooltip:manuallyHideTip"},Er)}var Wee=function(e){ge(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r.layoutMode={type:"box",ignoreSize:!0},r}return t.type="title",t.defaultOption={z:6,show:!0,text:"",target:"blank",subtext:"",subtarget:"blank",left:0,top:0,backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderWidth:0,padding:5,itemGap:10,textStyle:{fontSize:18,fontWeight:"bold",color:"#464646"},subtextStyle:{fontSize:12,color:"#6E7079"}},t}(Mt),Gee=function(e){ge(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r}return t.prototype.render=function(r,n,i){if(this.group.removeAll(),!!r.get("show")){var a=this.group,o=r.getModel("textStyle"),s=r.getModel("subtextStyle"),l=r.get("textAlign"),u=Ze(r.get("textBaseline"),r.get("textVerticalAlign")),f=new mr({style:ha(o,{text:r.get("text"),fill:o.getTextColor()},{disableBox:!0}),z2:10}),c=f.getBoundingRect(),h=r.get("subtext"),d=new mr({style:ha(s,{text:h,fill:s.getTextColor(),y:c.height+r.get("itemGap"),verticalAlign:"top"},{disableBox:!0}),z2:10}),v=r.get("link"),p=r.get("sublink"),m=r.get("triggerEvent",!0);f.silent=!v&&!m,d.silent=!p&&!m,v&&f.on("click",function(){bS(v,"_"+r.get("target"))}),p&&d.on("click",function(){bS(p,"_"+r.get("subtarget"))}),dt(f).eventData=dt(d).eventData=m?{componentType:"title",componentIndex:r.componentIndex}:null,a.add(f),h&&a.add(d);var g=a.getBoundingRect(),y=r.getBoxLayoutParams();y.width=g.width,y.height=g.height;var _=Ls(y,{width:i.getWidth(),height:i.getHeight()},r.get("padding"));l||(l=r.get("left")||r.get("right"),l==="middle"&&(l="center"),l==="right"?_.x+=_.width:l==="center"&&(_.x+=_.width/2)),u||(u=r.get("top")||r.get("bottom"),u==="center"&&(u="middle"),u==="bottom"?_.y+=_.height:u==="middle"&&(_.y+=_.height/2),u=u||"top"),a.x=_.x,a.y=_.y,a.markRedraw();var b={align:l,verticalAlign:u};f.setStyle(b),d.setStyle(b),g=a.getBoundingRect();var x=_.margin,w=r.getItemStyle(["color","opacity"]);w.fill=r.get("backgroundColor");var S=new Gt({shape:{x:g.x-x[3],y:g.y-x[0],width:g.width+x[1]+x[3],height:g.height+x[0]+x[2],r:r.get("borderRadius")},style:w,subPixelOptimize:!0,silent:!0});a.add(S)}},t.type="title",t}(Mi);function Uee(e){e.registerComponentModel(Wee),e.registerComponentView(Gee)}var Yee=function(e,t){if(t==="all")return{type:"all",title:e.getLocaleModel().get(["legend","selector","all"])};if(t==="inverse")return{type:"inverse",title:e.getLocaleModel().get(["legend","selector","inverse"])}},jee=function(e){ge(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r.layoutMode={type:"box",ignoreSize:!0},r}return t.prototype.init=function(r,n,i){this.mergeDefaultAndTheme(r,i),r.selected=r.selected||{},this._updateSelector(r)},t.prototype.mergeOption=function(r,n){e.prototype.mergeOption.call(this,r,n),this._updateSelector(r)},t.prototype._updateSelector=function(r){var n=r.selector,i=this.ecModel;n===!0&&(n=r.selector=["all","inverse"]),ye(n)&&R(n,function(a,o){Ee(a)&&(a={type:a}),n[o]=st(a,Yee(i,a.type))})},t.prototype.optionUpdated=function(){this._updateData(this.ecModel);var r=this._data;if(r[0]&&this.get("selectedMode")==="single"){for(var n=!1,i=0;i=0},t.prototype.getOrient=function(){return this.get("orient")==="vertical"?{index:1,name:"vertical"}:{index:0,name:"horizontal"}},t.type="legend.plain",t.dependencies=["series"],t.defaultOption={z:4,show:!0,orient:"horizontal",left:"center",top:0,align:"auto",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderRadius:0,borderWidth:0,padding:5,itemGap:10,itemWidth:25,itemHeight:14,symbolRotate:"inherit",symbolKeepAspect:!0,inactiveColor:"#ccc",inactiveBorderColor:"#ccc",inactiveBorderWidth:"auto",itemStyle:{color:"inherit",opacity:"inherit",borderColor:"inherit",borderWidth:"auto",borderCap:"inherit",borderJoin:"inherit",borderDashOffset:"inherit",borderMiterLimit:"inherit"},lineStyle:{width:"auto",color:"inherit",inactiveColor:"#ccc",inactiveWidth:2,opacity:"inherit",type:"inherit",cap:"inherit",join:"inherit",dashOffset:"inherit",miterLimit:"inherit"},textStyle:{color:"#333"},selectedMode:!0,selector:!1,selectorLabel:{show:!0,borderRadius:10,padding:[3,5,3,5],fontSize:12,fontFamily:"sans-serif",color:"#666",borderWidth:1,borderColor:"#666"},emphasis:{selectorLabel:{show:!0,color:"#eee",backgroundColor:"#666"}},selectorPosition:"auto",selectorItemGap:7,selectorButtonGap:10,tooltip:{show:!1}},t}(Mt);const $m=jee;var jo=Ot,Hm=R,wc=Ur,qee=function(e){ge(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r.newlineDisabled=!1,r}return t.prototype.init=function(){this.group.add(this._contentGroup=new wc),this.group.add(this._selectorGroup=new wc),this._isFirstRender=!0},t.prototype.getContentGroup=function(){return this._contentGroup},t.prototype.getSelectorGroup=function(){return this._selectorGroup},t.prototype.render=function(r,n,i){var a=this._isFirstRender;if(this._isFirstRender=!1,this.resetInner(),!!r.get("show",!0)){var o=r.get("align"),s=r.get("orient");(!o||o==="auto")&&(o=r.get("left")==="right"&&s==="vertical"?"right":"left");var l=r.get("selector",!0),u=r.get("selectorPosition",!0);l&&(!u||u==="auto")&&(u=s==="horizontal"?"end":"start"),this.renderInner(o,r,n,i,l,s,u);var f=r.getBoxLayoutParams(),c={width:i.getWidth(),height:i.getHeight()},h=r.get("padding"),d=Ls(f,c,h),v=this.layoutInner(r,o,d,a,l,u),p=Ls(ht({width:v.width,height:v.height},f),c,h);this.group.x=p.x-v.x,this.group.y=p.y-v.y,this.group.markRedraw(),this.group.add(this._backgroundEl=bee(v,r))}},t.prototype.resetInner=function(){this.getContentGroup().removeAll(),this._backgroundEl&&this.group.remove(this._backgroundEl),this.getSelectorGroup().removeAll()},t.prototype.renderInner=function(r,n,i,a,o,s,l){var u=this.getContentGroup(),f=je(),c=n.get("selectedMode"),h=[];i.eachRawSeries(function(d){!d.get("legendHoverLink")&&h.push(d.id)}),Hm(n.getData(),function(d,v){var p=d.get("name");if(!this.newlineDisabled&&(p===""||p===` `)){var m=new wc;m.newline=!0,u.add(m);return}var g=i.getSeriesByName(p)[0];if(!f.get(p))if(g){var y=g.getData(),_=y.getVisual("legendLineStyle")||{},b=y.getVisual("legendIcon"),x=y.getVisual("style"),w=this._createItem(g,p,v,d,n,r,_,x,b,c,a);w.on("click",jo(TC,p,null,a,h)).on("mouseover",jo(zm,g.name,null,a,h)).on("mouseout",jo(Vm,g.name,null,a,h)),f.set(p,!0)}else i.eachRawSeries(function(S){if(!f.get(p)&&S.legendVisualProvider){var C=S.legendVisualProvider;if(!C.containName(p))return;var M=C.indexOfName(p),A=C.getItemVisual(M,"style"),P=C.getItemVisual(M,"legendIcon"),E=uo(A.fill);E&&E[3]===0&&(E[3]=.2,A=ue(ue({},A),{fill:s0(E,"rgba")}));var L=this._createItem(S,p,v,d,n,r,{},A,P,c,a);L.on("click",jo(TC,null,p,a,h)).on("mouseover",jo(zm,null,p,a,h)).on("mouseout",jo(Vm,null,p,a,h)),f.set(p,!0)}},this)},this),o&&this._createSelector(o,n,a,s,l)},t.prototype._createSelector=function(r,n,i,a,o){var s=this.getSelectorGroup();Hm(r,function(u){var f=u.type,c=new mr({style:{x:0,y:0,align:"center",verticalAlign:"middle"},onclick:function(){i.dispatchAction({type:f==="all"?"legendAllSelect":"legendInverseSelect"})}});s.add(c);var h=n.getModel("selectorLabel"),d=n.getModel(["emphasis","selectorLabel"]);E0(c,{normal:h,emphasis:d},{defaultText:u.title}),um(c)})},t.prototype._createItem=function(r,n,i,a,o,s,l,u,f,c,h){var d=r.visualDrawType,v=o.get("itemWidth"),p=o.get("itemHeight"),m=o.isSelected(n),g=a.get("symbolRotate"),y=a.get("symbolKeepAspect"),_=a.get("icon");f=_||f||"roundRect";var b=Kee(f,a,l,u,d,m,h),x=new wc,w=a.getModel("textStyle");if(Ye(r.getLegendIcon)&&(!_||_==="inherit"))x.add(r.getLegendIcon({itemWidth:v,itemHeight:p,icon:f,iconRotate:g,itemStyle:b.itemStyle,lineStyle:b.lineStyle,symbolKeepAspect:y}));else{var S=_==="inherit"&&r.getData().getVisual("symbol")?g==="inherit"?r.getData().getVisual("symbolRotate"):g:0;x.add(Xee({itemWidth:v,itemHeight:p,icon:f,iconRotate:S,itemStyle:b.itemStyle,lineStyle:b.lineStyle,symbolKeepAspect:y}))}var C=s==="left"?v+5:-5,M=s,A=o.get("formatter"),P=n;Ee(A)&&A?P=A.replace("{name}",n??""):Ye(A)&&(P=A(n));var E=m?w.getTextColor():a.get("inactiveColor");x.add(new mr({style:ha(w,{text:P,x:C,y:p/2,fill:E,align:M,verticalAlign:"middle"},{inheritColor:E})}));var L=new Gt({shape:x.getBoundingRect(),invisible:!0}),O=a.getModel("tooltip");return O.get("show")&&A0({el:L,componentModel:o,itemName:n,itemTooltipOption:O.option}),x.add(L),x.eachChild(function(N){N.silent=!0}),L.silent=!c,this.getContentGroup().add(x),um(x),x.__legendDataIndex=i,x},t.prototype.layoutInner=function(r,n,i,a,o,s){var l=this.getContentGroup(),u=this.getSelectorGroup();ru(r.get("orient"),l,r.get("itemGap"),i.width,i.height);var f=l.getBoundingRect(),c=[-f.x,-f.y];if(u.markRedraw(),l.markRedraw(),o){ru("horizontal",u,r.get("selectorItemGap",!0));var h=u.getBoundingRect(),d=[-h.x,-h.y],v=r.get("selectorButtonGap",!0),p=r.getOrient().index,m=p===0?"width":"height",g=p===0?"height":"width",y=p===0?"y":"x";s==="end"?d[p]+=f[m]+v:c[p]+=h[m]+v,d[1-p]+=f[g]/2-h[g]/2,u.x=d[0],u.y=d[1],l.x=c[0],l.y=c[1];var _={x:0,y:0};return _[m]=f[m]+v+h[m],_[g]=Math.max(f[g],h[g]),_[y]=Math.min(0,h[y]+d[1-p]),_}else return l.x=c[0],l.y=c[1],this.group.getBoundingRect()},t.prototype.remove=function(){this.getContentGroup().removeAll(),this._isFirstRender=!0},t.type="legend.plain",t}(Mi);function Kee(e,t,r,n,i,a,o){function s(m,g){m.lineWidth==="auto"&&(m.lineWidth=g.lineWidth>0?2:0),Hm(m,function(y,_){m[_]==="inherit"&&(m[_]=g[_])})}var l=t.getModel("itemStyle"),u=l.getItemStyle(),f=e.lastIndexOf("empty",0)===0?"fill":"stroke",c=l.getShallow("decal");u.decal=!c||c==="inherit"?n.decal:Mm(c,o),u.fill==="inherit"&&(u.fill=n[i]),u.stroke==="inherit"&&(u.stroke=n[f]),u.opacity==="inherit"&&(u.opacity=(i==="fill"?n:r).opacity),s(u,n);var h=t.getModel("lineStyle"),d=h.getLineStyle();if(s(d,r),u.fill==="auto"&&(u.fill=n.fill),u.stroke==="auto"&&(u.stroke=n.fill),d.stroke==="auto"&&(d.stroke=n.fill),!a){var v=t.get("inactiveBorderWidth"),p=u[f];u.lineWidth=v==="auto"?n.lineWidth>0&&p?2:0:u.lineWidth,u.fill=t.get("inactiveColor"),u.stroke=t.get("inactiveBorderColor"),d.stroke=h.get("inactiveColor"),d.lineWidth=h.get("inactiveWidth")}return{itemStyle:u,lineStyle:d}}function Xee(e){var t=e.icon||"roundRect",r=j0(t,0,0,e.itemWidth,e.itemHeight,e.itemStyle.fill,e.symbolKeepAspect);return r.setStyle(e.itemStyle),r.rotation=(e.iconRotate||0)*Math.PI/180,r.setOrigin([e.itemWidth/2,e.itemHeight/2]),t.indexOf("empty")>-1&&(r.style.stroke=r.style.fill,r.style.fill="#fff",r.style.lineWidth=2),r}function TC(e,t,r,n){Vm(e,t,r,n),r.dispatchAction({type:"legendToggleSelect",name:e??t}),zm(e,t,r,n)}function xD(e){for(var t=e.getZr().storage.getDisplayList(),r,n=0,i=t.length;ni[o],m=[-d.x,-d.y];n||(m[a]=f[u]);var g=[0,0],y=[-v.x,-v.y],_=Ze(r.get("pageButtonGap",!0),r.get("itemGap",!0));if(p){var b=r.get("pageButtonPosition",!0);b==="end"?y[a]+=i[o]-v[o]:g[a]+=v[o]+_}y[1-a]+=d[s]/2-v[s]/2,f.setPosition(m),c.setPosition(g),h.setPosition(y);var x={x:0,y:0};if(x[o]=p?i[o]:d[o],x[s]=Math.max(d[s],v[s]),x[l]=Math.min(0,v[l]+y[1-a]),c.__rectSize=i[o],p){var w={x:0,y:0};w[o]=Math.max(i[o]-v[o]-_,0),w[s]=x[s],c.setClipPath(new Gt({shape:w})),c.__rectSize=w[o]}else h.eachChild(function(C){C.attr({invisible:!0,silent:!0})});var S=this._getPageInfo(r);return S.pageIndex!=null&&Or(f,{x:S.contentPosition[0],y:S.contentPosition[1]},p?r:null),this._updatePageInfoView(r,S),x},t.prototype._pageGo=function(r,n,i){var a=this._getPageInfo(n)[r];a!=null&&i.dispatchAction({type:"legendScroll",scrollDataIndex:a,legendId:n.id})},t.prototype._updatePageInfoView=function(r,n){var i=this._controllerGroup;R(["pagePrev","pageNext"],function(f){var c=f+"DataIndex",h=n[c]!=null,d=i.childOfName(f);d&&(d.setStyle("fill",h?r.get("pageIconColor",!0):r.get("pageIconInactiveColor",!0)),d.cursor=h?"pointer":"default")});var a=i.childOfName("pageText"),o=r.get("pageFormatter"),s=n.pageIndex,l=s!=null?s+1:0,u=n.pageCount;a&&o&&a.setStyle("text",Ee(o)?o.replace("{current}",l==null?"":l+"").replace("{total}",u==null?"":u+""):o({current:l,total:u}))},t.prototype._getPageInfo=function(r){var n=r.get("scrollDataIndex",!0),i=this.getContentGroup(),a=this._containerGroup.__rectSize,o=r.getOrient().index,s=Rp[o],l=kp[o],u=this._findTargetItemIndex(n),f=i.children(),c=f[u],h=f.length,d=h?1:0,v={contentPosition:[i.x,i.y],pageCount:d,pageIndex:d-1,pagePrevDataIndex:null,pageNextDataIndex:null};if(!c)return v;var p=b(c);v.contentPosition[o]=-p.s;for(var m=u+1,g=p,y=p,_=null;m<=h;++m)_=b(f[m]),(!_&&y.e>g.s+a||_&&!x(_,g.s))&&(y.i>g.i?g=y:g=_,g&&(v.pageNextDataIndex==null&&(v.pageNextDataIndex=g.i),++v.pageCount)),y=_;for(var m=u-1,g=p,y=p,_=null;m>=-1;--m)_=b(f[m]),(!_||!x(y,_.s))&&g.i=S&&w.s<=S+a}},t.prototype._findTargetItemIndex=function(r){if(!this._showController)return 0;var n,i=this.getContentGroup(),a;return i.eachChild(function(o,s){var l=o.__legendDataIndex;a==null&&l!=null&&(a=s),l===r&&(n=s)}),n??a},t.type="legend.scroll",t}(CD);const rte=tte;function nte(e){e.registerAction("legendScroll","legendscroll",function(t,r){var n=t.scrollDataIndex;n!=null&&r.eachComponent({mainType:"legend",subType:"scroll",query:t},function(i){i.setScrollDataIndex(n)})})}function ite(e){va(TD),e.registerComponentModel(ete),e.registerComponentView(rte),nte(e)}function ate(e){va(TD),va(ite)}va([tJ,$Q,GJ,vQ,Uee,Vee,ate,_ee]);function ote(e,t,r){const n=Z0(document.getElementById(e),"macarons");n.showLoading();const i={title:{text:"Network Traffic",subtext:"today",left:"center"},tooltip:{trigger:"item",formatter:function(a){return Mu.fileSize(a.data.value)+" ("+a.percent+"%)"}},legend:{orient:"vertical",left:"left",data:["Traffic In","Traffic Out"]},series:[{type:"pie",radius:"55%",center:["50%","60%"],data:[{value:t,name:"Traffic In"},{value:r,name:"Traffic Out"}],emphasis:{itemStyle:{shadowBlur:10,shadowOffsetX:0,shadowColor:"rgba(0, 0, 0, 0.5)"}}}]};n.setOption(i),n.hideLoading()}function ste(e,t){const r=Z0(document.getElementById(e),"macarons");r.showLoading();const n={title:{text:"Proxies",subtext:"now",left:"center"},tooltip:{trigger:"item",formatter:function(i){return String(i.data.value)}},legend:{orient:"vertical",left:"left",data:[]},series:[{type:"pie",radius:"55%",center:["50%","60%"],data:[],emphasis:{itemStyle:{shadowBlur:10,shadowOffsetX:0,shadowColor:"rgba(0, 0, 0, 0.5)"}}}]};t.proxyTypeCount.tcp!=null&&t.proxyTypeCount.tcp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.tcp,name:"TCP"}),n.legend.data.push("TCP")),t.proxyTypeCount.udp!=null&&t.proxyTypeCount.udp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.udp,name:"UDP"}),n.legend.data.push("UDP")),t.proxyTypeCount.http!=null&&t.proxyTypeCount.http!=0&&(n.series[0].data.push({value:t.proxyTypeCount.http,name:"HTTP"}),n.legend.data.push("HTTP")),t.proxyTypeCount.https!=null&&t.proxyTypeCount.https!=0&&(n.series[0].data.push({value:t.proxyTypeCount.https,name:"HTTPS"}),n.legend.data.push("HTTPS")),t.proxyTypeCount.stcp!=null&&t.proxyTypeCount.stcp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.stcp,name:"STCP"}),n.legend.data.push("STCP")),t.proxyTypeCount.sudp!=null&&t.proxyTypeCount.sudp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.sudp,name:"SUDP"}),n.legend.data.push("SUDP")),t.proxyTypeCount.xtcp!=null&&t.proxyTypeCount.xtcp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.xtcp,name:"XTCP"}),n.legend.data.push("XTCP")),r.setOption(n),r.hideLoading()}function lte(e,t,r){const n={width:"600px",height:"400px"},i=Z0(document.getElementById(e),"macarons",n);i.showLoading(),t=t.reverse(),r=r.reverse();let a=new Date;a=new Date(a.getFullYear(),a.getMonth(),a.getDate()-6);const o=[];for(let l=0;l<7;l++)o.push(a.getFullYear()+"-"+(a.getMonth()+1)+"-"+a.getDate()),a=new Date(a.getFullYear(),a.getMonth(),a.getDate()+1);const s={tooltip:{trigger:"axis",axisPointer:{type:"shadow"},formatter:function(l){let u="";l.length>0&&(u+=l[0].name+"
");for(const f of l){const c='';u+=c+f.seriesName+": "+Mu.fileSize(f.value)+"
"}return u}},legend:{data:["Traffic In","Traffic Out"]},grid:{left:"3%",right:"4%",bottom:"3%",containLabel:!0},xAxis:[{type:"category",data:o}],yAxis:[{type:"value",axisLabel:{formatter:function(l){return Mu.fileSize(l)}}}],series:[{name:"Traffic In",type:"bar",data:t},{name:"Traffic Out",type:"bar",data:r}]};i.setOption(s),i.hideLoading()}const PC=ie({__name:"LongSpan",props:{content:{},length:{}},setup(e){return(t,r)=>{const n=Hs;return G(),ce(ft,null,[Z(n,{content:t.content,placement:"top"},{default:j(()=>[qt(ee("span",null,be(t.content.slice(0,t.length))+"...",513),[[Kn,t.content.length>t.length]])]),_:1},8,["content"]),qt(ee("span",null,be(t.content),513),[[Kn,t.content.length<30]])],64)}}}),ute={class:"source"},fte=ee("div",{id:"traffic",style:{width:"400px",height:"250px","margin-bottom":"30px"}},null,-1),cte=ee("div",{id:"proxies",style:{width:"400px",height:"250px"}},null,-1),dte=ie({__name:"ServerOverview",setup(e){let t=$({version:"",bindPort:0,kcpBindPort:0,quicBindPort:0,vhostHTTPPort:0,vhostHTTPSPort:0,tcpmuxHTTPConnectPort:0,subdomainHost:"",maxPoolCount:0,maxPortsPerClient:"",allowPortsStr:"",tlsForce:!1,heartbeatTimeout:0,clientCounts:0,curConns:0,proxyCounts:0});return(()=>{fetch("../api/serverinfo",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value.version=n.version,t.value.bindPort=n.bindPort,t.value.kcpBindPort=n.kcpBindPort,t.value.quicBindPort=n.quicBindPort,t.value.vhostHTTPPort=n.vhostHTTPPort,t.value.vhostHTTPSPort=n.vhostHTTPSPort,t.value.tcpmuxHTTPConnectPort=n.tcpmuxHTTPConnectPort,t.value.subdomainHost=n.subdomainHost,t.value.maxPoolCount=n.maxPoolCount,t.value.maxPortsPerClient=n.maxPortsPerClient,t.value.maxPortsPerClient=="0"&&(t.value.maxPortsPerClient="no limit"),t.value.allowPortsStr=n.allowPortsStr,t.value.tlsForce=n.tlsForce,t.value.heartbeatTimeout=n.heartbeatTimeout,t.value.clientCounts=n.clientCounts,t.value.curConns=n.curConns,t.value.proxyCounts=0,n.proxyTypeCount!=null&&(n.proxyTypeCount.tcp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.tcp),n.proxyTypeCount.udp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.udp),n.proxyTypeCount.http!=null&&(t.value.proxyCounts+=n.proxyTypeCount.http),n.proxyTypeCount.https!=null&&(t.value.proxyCounts+=n.proxyTypeCount.https),n.proxyTypeCount.stcp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.stcp),n.proxyTypeCount.sudp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.sudp),n.proxyTypeCount.xtcp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.xtcp)),ote("traffic",n.totalTrafficIn,n.totalTrafficOut),ste("proxies",n)}).catch(()=>{Wl({showClose:!0,message:"Get server info from frps failed!",type:"warning"})})})(),(n,i)=>{const a=sA,o=oA,s=$A,l=FA;return G(),ce("div",null,[Z(l,null,{default:j(()=>[Z(s,{md:12},{default:j(()=>[ee("div",ute,[Z(o,{"label-position":"left","label-width":"220px",class:"server_info"},{default:j(()=>[Z(a,{label:"Version"},{default:j(()=>[ee("span",null,be(T(t).version),1)]),_:1}),Z(a,{label:"BindPort"},{default:j(()=>[ee("span",null,be(T(t).bindPort),1)]),_:1}),T(t).kcpBindPort!=0?(G(),de(a,{key:0,label:"KCP Bind Port"},{default:j(()=>[ee("span",null,be(T(t).kcpBindPort),1)]),_:1})):Ae("",!0),T(t).quicBindPort!=0?(G(),de(a,{key:1,label:"QUIC Bind Port"},{default:j(()=>[ee("span",null,be(T(t).quicBindPort),1)]),_:1})):Ae("",!0),T(t).vhostHTTPPort!=0?(G(),de(a,{key:2,label:"Http Port"},{default:j(()=>[ee("span",null,be(T(t).vhostHTTPPort),1)]),_:1})):Ae("",!0),T(t).vhostHTTPSPort!=0?(G(),de(a,{key:3,label:"Https Port"},{default:j(()=>[ee("span",null,be(T(t).vhostHTTPSPort),1)]),_:1})):Ae("",!0),T(t).tcpmuxHTTPConnectPort!=0?(G(),de(a,{key:4,label:"TCPMux HTTPConnect Port"},{default:j(()=>[ee("span",null,be(T(t).tcpmuxHTTPConnectPort),1)]),_:1})):Ae("",!0),T(t).subdomainHost!=""?(G(),de(a,{key:5,label:"Subdomain Host"},{default:j(()=>[Z(PC,{content:T(t).subdomainHost,length:30},null,8,["content"])]),_:1})):Ae("",!0),Z(a,{label:"Max PoolCount"},{default:j(()=>[ee("span",null,be(T(t).maxPoolCount),1)]),_:1}),Z(a,{label:"Max Ports Per Client"},{default:j(()=>[ee("span",null,be(T(t).maxPortsPerClient),1)]),_:1}),T(t).allowPortsStr!=""?(G(),de(a,{key:6,label:"Allow Ports"},{default:j(()=>[Z(PC,{content:T(t).allowPortsStr,length:30},null,8,["content"])]),_:1})):Ae("",!0),T(t).tlsForce===!0?(G(),de(a,{key:7,label:"TLS Force"},{default:j(()=>[ee("span",null,be(T(t).tlsForce),1)]),_:1})):Ae("",!0),Z(a,{label:"HeartBeat Timeout"},{default:j(()=>[ee("span",null,be(T(t).heartbeatTimeout),1)]),_:1}),Z(a,{label:"Client Counts"},{default:j(()=>[ee("span",null,be(T(t).clientCounts),1)]),_:1}),Z(a,{label:"Current Connections"},{default:j(()=>[ee("span",null,be(T(t).curConns),1)]),_:1}),Z(a,{label:"Proxy Counts"},{default:j(()=>[ee("span",null,be(T(t).proxyCounts),1)]),_:1})]),_:1})])]),_:1}),Z(s,{md:12},{default:j(()=>[fte,cte]),_:1})]),_:1})])}}});class qs{constructor(t){zt(this,"name");zt(this,"type");zt(this,"annotations");zt(this,"encryption");zt(this,"compression");zt(this,"conns");zt(this,"trafficIn");zt(this,"trafficOut");zt(this,"lastStartTime");zt(this,"lastCloseTime");zt(this,"status");zt(this,"clientVersion");zt(this,"addr");zt(this,"port");zt(this,"customDomains");zt(this,"hostHeaderRewrite");zt(this,"locations");zt(this,"subdomain");var r,n,i,a,o;if(this.name=t.name,this.type="",this.annotations=new Map,(r=t.conf)!=null&&r.annotations)for(const s in t.conf.annotations)this.annotations.set(s,t.conf.annotations[s]);this.encryption=!1,this.compression=!1,this.encryption=((i=(n=t.conf)==null?void 0:n.transport)==null?void 0:i.useEncryption)||this.encryption,this.compression=((o=(a=t.conf)==null?void 0:a.transport)==null?void 0:o.useCompression)||this.compression,this.conns=t.curConns,this.trafficIn=t.todayTrafficIn,this.trafficOut=t.todayTrafficOut,this.lastStartTime=t.lastStartTime,this.lastCloseTime=t.lastCloseTime,this.status=t.status,this.clientVersion=t.clientVersion,this.addr="",this.port=0,this.customDomains="",this.hostHeaderRewrite="",this.locations="",this.subdomain=""}}class hte extends qs{constructor(t){super(t),this.type="tcp",t.conf!=null?(this.addr=":"+t.conf.remotePort,this.port=t.conf.remotePort):(this.addr="",this.port=0)}}class vte extends qs{constructor(t){super(t),this.type="udp",t.conf!=null?(this.addr=":"+t.conf.remotePort,this.port=t.conf.remotePort):(this.addr="",this.port=0)}}class pte extends qs{constructor(t,r,n){super(t),this.type="http",this.port=r,t.conf&&(this.customDomains=t.conf.customDomains||this.customDomains,this.hostHeaderRewrite=t.conf.hostHeaderRewrite,this.locations=t.conf.locations,t.conf.subdomain&&(this.subdomain=`${t.conf.subdomain}.${n}`))}}class gte extends qs{constructor(t,r,n){super(t),this.type="https",this.port=r,t.conf!=null&&(this.customDomains=t.conf.customDomains||this.customDomains,t.conf.subdomain&&(this.subdomain=`${t.conf.subdomain}.${n}`))}}class mte extends qs{constructor(t){super(t),this.type="stcp"}}class yte extends qs{constructor(t){super(t),this.type="sudp"}}const _te=["id"],bte=ie({__name:"Traffic",props:{proxyName:{}},setup(e){const t=e;return(()=>{let n="../api/traffic/"+t.proxyName;fetch(n,{credentials:"include"}).then(i=>i.json()).then(i=>{lte(t.proxyName,i.trafficIn,i.trafficOut)}).catch(i=>{Wl({showClose:!0,message:"Get traffic info failed!"+i,type:"warning"})})})(),(n,i)=>(G(),ce("div",{id:n.proxyName,style:{width:"600px",height:"400px"}},null,8,_te))}}),wte={key:2},Ste={class:"annotation-key"},xte=ie({__name:"ProxyViewExpand",props:{row:{},proxyType:{}},setup(e){const t=e,r=()=>{const n=[];return t.row.annotations&&t.row.annotations.forEach((i,a)=>{n.push({key:a,value:i})}),n};return(n,i)=>{const a=sA,o=oA,s=VA,l=wW;return G(),ce(ft,null,[n.proxyType==="http"||n.proxyType==="https"?(G(),de(o,{key:0,"label-position":"left",inline:"",class:"proxy-table-expand"},{default:j(()=>[Z(a,{label:"Name"},{default:j(()=>[ee("span",null,be(n.row.name),1)]),_:1}),Z(a,{label:"Type"},{default:j(()=>[ee("span",null,be(n.row.type),1)]),_:1}),Z(a,{label:"Domains"},{default:j(()=>[ee("span",null,be(n.row.customDomains),1)]),_:1}),Z(a,{label:"SubDomain"},{default:j(()=>[ee("span",null,be(n.row.subdomain),1)]),_:1}),Z(a,{label:"locations"},{default:j(()=>[ee("span",null,be(n.row.locations),1)]),_:1}),Z(a,{label:"HostRewrite"},{default:j(()=>[ee("span",null,be(n.row.hostHeaderRewrite),1)]),_:1}),Z(a,{label:"Encryption"},{default:j(()=>[ee("span",null,be(n.row.encryption),1)]),_:1}),Z(a,{label:"Compression"},{default:j(()=>[ee("span",null,be(n.row.compression),1)]),_:1}),Z(a,{label:"Last Start"},{default:j(()=>[ee("span",null,be(n.row.lastStartTime),1)]),_:1}),Z(a,{label:"Last Close"},{default:j(()=>[ee("span",null,be(n.row.lastCloseTime),1)]),_:1})]),_:1})):(G(),de(o,{key:1,"label-position":"left",inline:"",class:"proxy-table-expand"},{default:j(()=>[Z(a,{label:"Name"},{default:j(()=>[ee("span",null,be(n.row.name),1)]),_:1}),Z(a,{label:"Type"},{default:j(()=>[ee("span",null,be(n.row.type),1)]),_:1}),Z(a,{label:"Addr"},{default:j(()=>[ee("span",null,be(n.row.addr),1)]),_:1}),Z(a,{label:"Encryption"},{default:j(()=>[ee("span",null,be(n.row.encryption),1)]),_:1}),Z(a,{label:"Compression"},{default:j(()=>[ee("span",null,be(n.row.compression),1)]),_:1}),Z(a,{label:"Last Start"},{default:j(()=>[ee("span",null,be(n.row.lastStartTime),1)]),_:1}),Z(a,{label:"Last Close"},{default:j(()=>[ee("span",null,be(n.row.lastCloseTime),1)]),_:1})]),_:1})),n.row.annotations&&n.row.annotations.size>0?(G(),ce("div",wte,[Z(s),Z(l,{class:"title-text",size:"large"},{default:j(()=>[mt("Annotations")]),_:1}),ee("ul",null,[(G(!0),ce(ft,null,Hp(r(),u=>(G(),ce("li",null,[ee("span",Ste,be(u.key),1),ee("span",null,be(u.value),1)]))),256))])])):Ae("",!0)],64)}}}),Cte={class:"flex items-center",style:{"margin-right":"30px"}},Ks=ie({__name:"ProxyView",props:{proxies:{},proxyType:{}},emits:["refresh"],setup(e,{emit:t}){const r=t,n=$(!1),i=$(""),a=(l,u)=>Mu.fileSize(l.trafficIn),o=(l,u)=>Mu.fileSize(l.trafficOut),s=()=>{fetch("/api/proxies?status=offline",{method:"DELETE",credentials:"include"}).then(l=>{l.ok?(Wl({message:"Successfully cleared offline proxies",type:"success"}),r("refresh")):Wl({message:"Failed to clear offline proxies: "+l.status+" "+l.statusText,type:"warning"})}).catch(l=>{Wl({message:"Failed to clear offline proxies: "+l.message,type:"warning"})})};return(l,u)=>{const f=yg,c=eV,h=q6,d=gW,v=N8,p=pW,m=bte,g=p6;return G(),ce(ft,null,[ee("div",null,[Z(h,{icon:null,style:{width:"100%","margin-left":"30px","margin-bottom":"20px"}},{title:j(()=>[ee("span",null,be(l.proxyType),1)]),content:j(()=>[]),extra:j(()=>[ee("div",Cte,[Z(c,{title:"Are you sure to clear all data of offline proxies?",onConfirm:s},{reference:j(()=>[Z(f,null,{default:j(()=>[mt("ClearOfflineProxies")]),_:1})]),_:1}),Z(f,{onClick:u[0]||(u[0]=y=>l.$emit("refresh"))},{default:j(()=>[mt("Refresh")]),_:1})])]),_:1}),Z(p,{data:l.proxies,"default-sort":{prop:"name",order:"ascending"},style:{width:"100%"}},{default:j(()=>[Z(d,{type:"expand"},{default:j(y=>[Z(xte,{row:y.row,proxyType:l.proxyType},null,8,["row","proxyType"])]),_:1}),Z(d,{label:"Name",prop:"name",sortable:""}),Z(d,{label:"Port",prop:"port",sortable:""}),Z(d,{label:"Connections",prop:"conns",sortable:""}),Z(d,{label:"Traffic In",prop:"trafficIn",formatter:a,sortable:""}),Z(d,{label:"Traffic Out",prop:"trafficOut",formatter:o,sortable:""}),Z(d,{label:"ClientVersion",prop:"clientVersion",sortable:""}),Z(d,{label:"Status",prop:"status",sortable:""},{default:j(y=>[y.row.status==="online"?(G(),de(v,{key:0,type:"success"},{default:j(()=>[mt(be(y.row.status),1)]),_:2},1024)):(G(),de(v,{key:1,type:"danger"},{default:j(()=>[mt(be(y.row.status),1)]),_:2},1024))]),_:1}),Z(d,{label:"Operations"},{default:j(y=>[Z(f,{type:"primary",name:y.row.name,style:{"margin-bottom":"10px"},onClick:_=>{i.value=y.row.name,n.value=!0}},{default:j(()=>[mt("Traffic ")]),_:2},1032,["name","onClick"])]),_:1})]),_:1},8,["data"])]),Z(g,{modelValue:n.value,"onUpdate:modelValue":u[1]||(u[1]=y=>n.value=y),"destroy-on-close":"true",title:i.value,width:"700px"},{default:j(()=>[Z(m,{proxyName:i.value},null,8,["proxyName"])]),_:1},8,["modelValue","title"])],64)}}}),Tte=ie({__name:"ProxiesTCP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/tcp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new hte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"tcp",onRefresh:r},null,8,["proxies"]))}}),Mte=ie({__name:"ProxiesUDP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/udp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new vte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"udp",onRefresh:r},null,8,["proxies"]))}}),Ate=ie({__name:"ProxiesHTTP",setup(e){let t=$([]);const r=()=>{let n,i;fetch("../api/serverinfo",{credentials:"include"}).then(a=>a.json()).then(a=>{n=a.vhostHTTPPort,i=a.subdomainHost,!(n==null||n==0)&&fetch("../api/proxy/http",{credentials:"include"}).then(o=>o.json()).then(o=>{t.value=[];for(let s of o.proxies)t.value.push(new pte(s,n,i))})})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"http",onRefresh:r},null,8,["proxies"]))}}),Pte=ie({__name:"ProxiesHTTPS",setup(e){let t=$([]);const r=()=>{let n,i;fetch("../api/serverinfo",{credentials:"include"}).then(a=>a.json()).then(a=>{n=a.vhostHTTPSPort,i=a.subdomainHost,!(n==null||n==0)&&fetch("../api/proxy/https",{credentials:"include"}).then(o=>o.json()).then(o=>{t.value=[];for(let s of o.proxies)t.value.push(new gte(s,n,i))})})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"https",onRefresh:r},null,8,["proxies"]))}}),Ete=ie({__name:"ProxiesSTCP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/stcp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new mte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"stcp",onRefresh:r},null,8,["proxies"]))}}),Lte=ie({__name:"ProxiesSUDP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/sudp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new yte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"sudp",onRefresh:r},null,8,["proxies"]))}}),Dte=jG({history:uG(),routes:[{path:"/",name:"ServerOverview",component:dte},{path:"/proxies/tcp",name:"ProxiesTCP",component:Tte},{path:"/proxies/udp",name:"ProxiesUDP",component:Mte},{path:"/proxies/http",name:"ProxiesHTTP",component:Ate},{path:"/proxies/https",name:"ProxiesHTTPS",component:Pte},{path:"/proxies/stcp",name:"ProxiesSTCP",component:Ete},{path:"/proxies/sudp",name:"ProxiesSUDP",component:Lte}]}),MD=oR(GW);MD.use(Dte);MD.mount("#app")});export default Ite(); +`||u==="")){var f=o.isSelected(u);n.hasOwnProperty(u)?n[u]=n[u]&&f:n[u]=f}})}),e==="allSelect"||e==="inverseSelect"?{selected:n}:{name:t.name,selected:n}}function Qee(e){e.registerAction("legendToggleSelect","legendselectchanged",Ot(wl,"toggleSelected")),e.registerAction("legendAllSelect","legendselectall",Ot(wl,"allSelect")),e.registerAction("legendInverseSelect","legendinverseselect",Ot(wl,"inverseSelect")),e.registerAction("legendSelect","legendselected",Ot(wl,"select")),e.registerAction("legendUnSelect","legendunselected",Ot(wl,"unSelect"))}function TD(e){e.registerComponentModel($m),e.registerComponentView(CD),e.registerProcessor(e.PRIORITY.PROCESSOR.SERIES_FILTER,Zee),e.registerSubTypeDefaulter("legend",function(){return"plain"}),Qee(e)}var Jee=function(e){ge(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r}return t.prototype.setScrollDataIndex=function(r){this.option.scrollDataIndex=r},t.prototype.init=function(r,n,i){var a=Rh(r);e.prototype.init.call(this,r,n,i),MC(this,r,a)},t.prototype.mergeOption=function(r,n){e.prototype.mergeOption.call(this,r,n),MC(this,this.option,r)},t.type="legend.scroll",t.defaultOption=rE($m.defaultOption,{scrollDataIndex:0,pageButtonItemGap:5,pageButtonGap:null,pageButtonPosition:"end",pageFormatter:"{current}/{total}",pageIcons:{horizontal:["M0,0L12,-10L12,10z","M0,0L-12,-10L-12,10z"],vertical:["M0,0L20,0L10,-20z","M0,0L20,0L10,20z"]},pageIconColor:"#2f4554",pageIconInactiveColor:"#aaa",pageIconSize:15,pageTextStyle:{color:"#333"},animationDurationUpdate:800}),t}($m);function MC(e,t,r){var n=e.getOrient(),i=[1,1];i[n.index]=0,Ds(t,r,{type:"box",ignoreSize:!!i})}const ete=Jee;var AC=Ur,Rp=["width","height"],kp=["x","y"],tte=function(e){ge(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r.newlineDisabled=!0,r._currentIndex=0,r}return t.prototype.init=function(){e.prototype.init.call(this),this.group.add(this._containerGroup=new AC),this._containerGroup.add(this.getContentGroup()),this.group.add(this._controllerGroup=new AC)},t.prototype.resetInner=function(){e.prototype.resetInner.call(this),this._controllerGroup.removeAll(),this._containerGroup.removeClipPath(),this._containerGroup.__rectSize=null},t.prototype.renderInner=function(r,n,i,a,o,s,l){var u=this;e.prototype.renderInner.call(this,r,n,i,a,o,s,l);var f=this._controllerGroup,c=n.get("pageIconSize",!0),h=ye(c)?c:[c,c];v("pagePrev",0);var d=n.getModel("pageTextStyle");f.add(new mr({name:"pageText",style:{text:"xx/xx",fill:d.getTextColor(),font:d.getFont(),verticalAlign:"middle",align:"center"},silent:!0})),v("pageNext",1);function v(p,m){var g=p+"DataIndex",y=M0(n.get("pageIcons",!0)[n.getOrient().name][m],{onclick:xt(u._pageGo,u,g,n,a)},{x:-h[0]/2,y:-h[1]/2,width:h[0],height:h[1]});y.name=p,f.add(y)}},t.prototype.layoutInner=function(r,n,i,a,o,s){var l=this.getSelectorGroup(),u=r.getOrient().index,f=Rp[u],c=kp[u],h=Rp[1-u],d=kp[1-u];o&&ru("horizontal",l,r.get("selectorItemGap",!0));var v=r.get("selectorButtonGap",!0),p=l.getBoundingRect(),m=[-p.x,-p.y],g=tt(i);o&&(g[f]=i[f]-p[f]-v);var y=this._layoutContentAndController(r,a,g,u,f,h,d,c);if(o){if(s==="end")m[u]+=y[f]+v;else{var _=p[f]+v;m[u]-=_,y[c]-=_}y[f]+=p[f]+v,m[1-u]+=y[d]+y[h]/2-p[h]/2,y[h]=Math.max(y[h],p[h]),y[d]=Math.min(y[d],p[d]+m[1-u]),l.x=m[0],l.y=m[1],l.markRedraw()}return y},t.prototype._layoutContentAndController=function(r,n,i,a,o,s,l,u){var f=this.getContentGroup(),c=this._containerGroup,h=this._controllerGroup;ru(r.get("orient"),f,r.get("itemGap"),a?i.width:null,a?null:i.height),ru("horizontal",h,r.get("pageButtonItemGap",!0));var d=f.getBoundingRect(),v=h.getBoundingRect(),p=this._showController=d[o]>i[o],m=[-d.x,-d.y];n||(m[a]=f[u]);var g=[0,0],y=[-v.x,-v.y],_=Ze(r.get("pageButtonGap",!0),r.get("itemGap",!0));if(p){var b=r.get("pageButtonPosition",!0);b==="end"?y[a]+=i[o]-v[o]:g[a]+=v[o]+_}y[1-a]+=d[s]/2-v[s]/2,f.setPosition(m),c.setPosition(g),h.setPosition(y);var x={x:0,y:0};if(x[o]=p?i[o]:d[o],x[s]=Math.max(d[s],v[s]),x[l]=Math.min(0,v[l]+y[1-a]),c.__rectSize=i[o],p){var w={x:0,y:0};w[o]=Math.max(i[o]-v[o]-_,0),w[s]=x[s],c.setClipPath(new Gt({shape:w})),c.__rectSize=w[o]}else h.eachChild(function(C){C.attr({invisible:!0,silent:!0})});var S=this._getPageInfo(r);return S.pageIndex!=null&&Or(f,{x:S.contentPosition[0],y:S.contentPosition[1]},p?r:null),this._updatePageInfoView(r,S),x},t.prototype._pageGo=function(r,n,i){var a=this._getPageInfo(n)[r];a!=null&&i.dispatchAction({type:"legendScroll",scrollDataIndex:a,legendId:n.id})},t.prototype._updatePageInfoView=function(r,n){var i=this._controllerGroup;R(["pagePrev","pageNext"],function(f){var c=f+"DataIndex",h=n[c]!=null,d=i.childOfName(f);d&&(d.setStyle("fill",h?r.get("pageIconColor",!0):r.get("pageIconInactiveColor",!0)),d.cursor=h?"pointer":"default")});var a=i.childOfName("pageText"),o=r.get("pageFormatter"),s=n.pageIndex,l=s!=null?s+1:0,u=n.pageCount;a&&o&&a.setStyle("text",Ee(o)?o.replace("{current}",l==null?"":l+"").replace("{total}",u==null?"":u+""):o({current:l,total:u}))},t.prototype._getPageInfo=function(r){var n=r.get("scrollDataIndex",!0),i=this.getContentGroup(),a=this._containerGroup.__rectSize,o=r.getOrient().index,s=Rp[o],l=kp[o],u=this._findTargetItemIndex(n),f=i.children(),c=f[u],h=f.length,d=h?1:0,v={contentPosition:[i.x,i.y],pageCount:d,pageIndex:d-1,pagePrevDataIndex:null,pageNextDataIndex:null};if(!c)return v;var p=b(c);v.contentPosition[o]=-p.s;for(var m=u+1,g=p,y=p,_=null;m<=h;++m)_=b(f[m]),(!_&&y.e>g.s+a||_&&!x(_,g.s))&&(y.i>g.i?g=y:g=_,g&&(v.pageNextDataIndex==null&&(v.pageNextDataIndex=g.i),++v.pageCount)),y=_;for(var m=u-1,g=p,y=p,_=null;m>=-1;--m)_=b(f[m]),(!_||!x(y,_.s))&&g.i=S&&w.s<=S+a}},t.prototype._findTargetItemIndex=function(r){if(!this._showController)return 0;var n,i=this.getContentGroup(),a;return i.eachChild(function(o,s){var l=o.__legendDataIndex;a==null&&l!=null&&(a=s),l===r&&(n=s)}),n??a},t.type="legend.scroll",t}(CD);const rte=tte;function nte(e){e.registerAction("legendScroll","legendscroll",function(t,r){var n=t.scrollDataIndex;n!=null&&r.eachComponent({mainType:"legend",subType:"scroll",query:t},function(i){i.setScrollDataIndex(n)})})}function ite(e){va(TD),e.registerComponentModel(ete),e.registerComponentView(rte),nte(e)}function ate(e){va(TD),va(ite)}va([tJ,$Q,GJ,vQ,Uee,Vee,ate,_ee]);function ote(e,t,r){const n=Z0(document.getElementById(e),"macarons");n.showLoading();const i={title:{text:"Network Traffic",subtext:"today",left:"center"},tooltip:{trigger:"item",formatter:function(a){return Mu.fileSize(a.data.value)+" ("+a.percent+"%)"}},legend:{orient:"vertical",left:"left",data:["Traffic In","Traffic Out"]},series:[{type:"pie",radius:"55%",center:["50%","60%"],data:[{value:t,name:"Traffic In"},{value:r,name:"Traffic Out"}],emphasis:{itemStyle:{shadowBlur:10,shadowOffsetX:0,shadowColor:"rgba(0, 0, 0, 0.5)"}}}]};n.setOption(i),n.hideLoading()}function ste(e,t){const r=Z0(document.getElementById(e),"macarons");r.showLoading();const n={title:{text:"Proxies",subtext:"now",left:"center"},tooltip:{trigger:"item",formatter:function(i){return String(i.data.value)}},legend:{orient:"vertical",left:"left",data:[]},series:[{type:"pie",radius:"55%",center:["50%","60%"],data:[],emphasis:{itemStyle:{shadowBlur:10,shadowOffsetX:0,shadowColor:"rgba(0, 0, 0, 0.5)"}}}]};t.proxyTypeCount.tcp!=null&&t.proxyTypeCount.tcp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.tcp,name:"TCP"}),n.legend.data.push("TCP")),t.proxyTypeCount.udp!=null&&t.proxyTypeCount.udp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.udp,name:"UDP"}),n.legend.data.push("UDP")),t.proxyTypeCount.http!=null&&t.proxyTypeCount.http!=0&&(n.series[0].data.push({value:t.proxyTypeCount.http,name:"HTTP"}),n.legend.data.push("HTTP")),t.proxyTypeCount.https!=null&&t.proxyTypeCount.https!=0&&(n.series[0].data.push({value:t.proxyTypeCount.https,name:"HTTPS"}),n.legend.data.push("HTTPS")),t.proxyTypeCount.stcp!=null&&t.proxyTypeCount.stcp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.stcp,name:"STCP"}),n.legend.data.push("STCP")),t.proxyTypeCount.sudp!=null&&t.proxyTypeCount.sudp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.sudp,name:"SUDP"}),n.legend.data.push("SUDP")),t.proxyTypeCount.xtcp!=null&&t.proxyTypeCount.xtcp!=0&&(n.series[0].data.push({value:t.proxyTypeCount.xtcp,name:"XTCP"}),n.legend.data.push("XTCP")),r.setOption(n),r.hideLoading()}function lte(e,t,r){const n={width:"600px",height:"400px"},i=Z0(document.getElementById(e),"macarons",n);i.showLoading(),t=t.reverse(),r=r.reverse();let a=new Date;a=new Date(a.getFullYear(),a.getMonth(),a.getDate()-6);const o=[];for(let l=0;l<7;l++)o.push(a.getFullYear()+"-"+(a.getMonth()+1)+"-"+a.getDate()),a=new Date(a.getFullYear(),a.getMonth(),a.getDate()+1);const s={tooltip:{trigger:"axis",axisPointer:{type:"shadow"},formatter:function(l){let u="";l.length>0&&(u+=l[0].name+"
");for(const f of l){const c='';u+=c+f.seriesName+": "+Mu.fileSize(f.value)+"
"}return u}},legend:{data:["Traffic In","Traffic Out"]},grid:{left:"3%",right:"4%",bottom:"3%",containLabel:!0},xAxis:[{type:"category",data:o}],yAxis:[{type:"value",axisLabel:{formatter:function(l){return Mu.fileSize(l)}}}],series:[{name:"Traffic In",type:"bar",data:t},{name:"Traffic Out",type:"bar",data:r}]};i.setOption(s),i.hideLoading()}const PC=ie({__name:"LongSpan",props:{content:{},length:{}},setup(e){return(t,r)=>{const n=Hs;return G(),ce(ft,null,[Z(n,{content:t.content,placement:"top"},{default:j(()=>[qt(ee("span",null,be(t.content.slice(0,t.length))+"...",513),[[Kn,t.content.length>t.length]])]),_:1},8,["content"]),qt(ee("span",null,be(t.content),513),[[Kn,t.content.length<30]])],64)}}}),ute={class:"source"},fte=ee("div",{id:"traffic",style:{width:"400px",height:"250px","margin-bottom":"30px"}},null,-1),cte=ee("div",{id:"proxies",style:{width:"400px",height:"250px"}},null,-1),dte=ie({__name:"ServerOverview",setup(e){let t=$({version:"",bindPort:0,kcpBindPort:0,quicBindPort:0,vhostHTTPPort:0,vhostHTTPSPort:0,tcpmuxHTTPConnectPort:0,subdomainHost:"",maxPoolCount:0,maxPortsPerClient:"",allowPortsStr:"",tlsForce:!1,heartbeatTimeout:0,clientCounts:0,curConns:0,proxyCounts:0});return(()=>{fetch("../api/serverinfo",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value.version=n.version,t.value.bindPort=n.bindPort,t.value.kcpBindPort=n.kcpBindPort,t.value.quicBindPort=n.quicBindPort,t.value.vhostHTTPPort=n.vhostHTTPPort,t.value.vhostHTTPSPort=n.vhostHTTPSPort,t.value.tcpmuxHTTPConnectPort=n.tcpmuxHTTPConnectPort,t.value.subdomainHost=n.subdomainHost,t.value.maxPoolCount=n.maxPoolCount,t.value.maxPortsPerClient=n.maxPortsPerClient,t.value.maxPortsPerClient=="0"&&(t.value.maxPortsPerClient="no limit"),t.value.allowPortsStr=n.allowPortsStr,t.value.tlsForce=n.tlsForce,t.value.heartbeatTimeout=n.heartbeatTimeout,t.value.clientCounts=n.clientCounts,t.value.curConns=n.curConns,t.value.proxyCounts=0,n.proxyTypeCount!=null&&(n.proxyTypeCount.tcp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.tcp),n.proxyTypeCount.udp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.udp),n.proxyTypeCount.http!=null&&(t.value.proxyCounts+=n.proxyTypeCount.http),n.proxyTypeCount.https!=null&&(t.value.proxyCounts+=n.proxyTypeCount.https),n.proxyTypeCount.stcp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.stcp),n.proxyTypeCount.sudp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.sudp),n.proxyTypeCount.xtcp!=null&&(t.value.proxyCounts+=n.proxyTypeCount.xtcp)),ote("traffic",n.totalTrafficIn,n.totalTrafficOut),ste("proxies",n)}).catch(()=>{Wl({showClose:!0,message:"Get server info from frps failed!",type:"warning"})})})(),(n,i)=>{const a=sA,o=oA,s=$A,l=FA;return G(),ce("div",null,[Z(l,null,{default:j(()=>[Z(s,{md:12},{default:j(()=>[ee("div",ute,[Z(o,{"label-position":"left","label-width":"220px",class:"server_info"},{default:j(()=>[Z(a,{label:"Version"},{default:j(()=>[ee("span",null,be(T(t).version),1)]),_:1}),Z(a,{label:"BindPort"},{default:j(()=>[ee("span",null,be(T(t).bindPort),1)]),_:1}),T(t).kcpBindPort!=0?(G(),de(a,{key:0,label:"KCP Bind Port"},{default:j(()=>[ee("span",null,be(T(t).kcpBindPort),1)]),_:1})):Ae("",!0),T(t).quicBindPort!=0?(G(),de(a,{key:1,label:"QUIC Bind Port"},{default:j(()=>[ee("span",null,be(T(t).quicBindPort),1)]),_:1})):Ae("",!0),T(t).vhostHTTPPort!=0?(G(),de(a,{key:2,label:"Http Port"},{default:j(()=>[ee("span",null,be(T(t).vhostHTTPPort),1)]),_:1})):Ae("",!0),T(t).vhostHTTPSPort!=0?(G(),de(a,{key:3,label:"Https Port"},{default:j(()=>[ee("span",null,be(T(t).vhostHTTPSPort),1)]),_:1})):Ae("",!0),T(t).tcpmuxHTTPConnectPort!=0?(G(),de(a,{key:4,label:"TCPMux HTTPConnect Port"},{default:j(()=>[ee("span",null,be(T(t).tcpmuxHTTPConnectPort),1)]),_:1})):Ae("",!0),T(t).subdomainHost!=""?(G(),de(a,{key:5,label:"Subdomain Host"},{default:j(()=>[Z(PC,{content:T(t).subdomainHost,length:30},null,8,["content"])]),_:1})):Ae("",!0),Z(a,{label:"Max PoolCount"},{default:j(()=>[ee("span",null,be(T(t).maxPoolCount),1)]),_:1}),Z(a,{label:"Max Ports Per Client"},{default:j(()=>[ee("span",null,be(T(t).maxPortsPerClient),1)]),_:1}),T(t).allowPortsStr!=""?(G(),de(a,{key:6,label:"Allow Ports"},{default:j(()=>[Z(PC,{content:T(t).allowPortsStr,length:30},null,8,["content"])]),_:1})):Ae("",!0),T(t).tlsForce===!0?(G(),de(a,{key:7,label:"TLS Force"},{default:j(()=>[ee("span",null,be(T(t).tlsForce),1)]),_:1})):Ae("",!0),Z(a,{label:"HeartBeat Timeout"},{default:j(()=>[ee("span",null,be(T(t).heartbeatTimeout),1)]),_:1}),Z(a,{label:"Client Counts"},{default:j(()=>[ee("span",null,be(T(t).clientCounts),1)]),_:1}),Z(a,{label:"Current Connections"},{default:j(()=>[ee("span",null,be(T(t).curConns),1)]),_:1}),Z(a,{label:"Proxy Counts"},{default:j(()=>[ee("span",null,be(T(t).proxyCounts),1)]),_:1})]),_:1})])]),_:1}),Z(s,{md:12},{default:j(()=>[fte,cte]),_:1})]),_:1})])}}});class qs{constructor(t){zt(this,"name");zt(this,"type");zt(this,"annotations");zt(this,"encryption");zt(this,"compression");zt(this,"conns");zt(this,"trafficIn");zt(this,"trafficOut");zt(this,"lastStartTime");zt(this,"lastCloseTime");zt(this,"status");zt(this,"clientVersion");zt(this,"addr");zt(this,"port");zt(this,"customDomains");zt(this,"hostHeaderRewrite");zt(this,"locations");zt(this,"subdomain");var r,n,i,a,o;if(this.name=t.name,this.type="",this.annotations=new Map,(r=t.conf)!=null&&r.annotations)for(const s in t.conf.annotations)this.annotations.set(s,t.conf.annotations[s]);this.encryption=!1,this.compression=!1,this.encryption=((i=(n=t.conf)==null?void 0:n.transport)==null?void 0:i.useEncryption)||this.encryption,this.compression=((o=(a=t.conf)==null?void 0:a.transport)==null?void 0:o.useCompression)||this.compression,this.conns=t.curConns,this.trafficIn=t.todayTrafficIn,this.trafficOut=t.todayTrafficOut,this.lastStartTime=t.lastStartTime,this.lastCloseTime=t.lastCloseTime,this.status=t.status,this.clientVersion=t.clientVersion,this.addr="",this.port=0,this.customDomains="",this.hostHeaderRewrite="",this.locations="",this.subdomain=""}}class hte extends qs{constructor(t){super(t),this.type="tcp",t.conf!=null?(this.addr=":"+t.conf.remotePort,this.port=t.conf.remotePort):(this.addr="",this.port=0)}}class vte extends qs{constructor(t){super(t),this.type="udp",t.conf!=null?(this.addr=":"+t.conf.remotePort,this.port=t.conf.remotePort):(this.addr="",this.port=0)}}class pte extends qs{constructor(t,r,n){super(t),this.type="http",this.port=r,t.conf&&(this.customDomains=t.conf.customDomains||this.customDomains,this.hostHeaderRewrite=t.conf.hostHeaderRewrite,this.locations=t.conf.locations,t.conf.subdomain&&(this.subdomain=`${t.conf.subdomain}.${n}`))}}class gte extends qs{constructor(t,r,n){super(t),this.type="https",this.port=r,t.conf!=null&&(this.customDomains=t.conf.customDomains||this.customDomains,t.conf.subdomain&&(this.subdomain=`${t.conf.subdomain}.${n}`))}}class mte extends qs{constructor(t){super(t),this.type="stcp"}}class yte extends qs{constructor(t){super(t),this.type="sudp"}}const _te=["id"],bte=ie({__name:"Traffic",props:{proxyName:{}},setup(e){const t=e;return(()=>{let n="../api/traffic/"+t.proxyName;fetch(n,{credentials:"include"}).then(i=>i.json()).then(i=>{lte(t.proxyName,i.trafficIn,i.trafficOut)}).catch(i=>{Wl({showClose:!0,message:"Get traffic info failed!"+i,type:"warning"})})})(),(n,i)=>(G(),ce("div",{id:n.proxyName,style:{width:"600px",height:"400px"}},null,8,_te))}}),wte={key:2},Ste={class:"annotation-key"},xte=ie({__name:"ProxyViewExpand",props:{row:{},proxyType:{}},setup(e){const t=e,r=()=>{const n=[];return t.row.annotations&&t.row.annotations.forEach((i,a)=>{n.push({key:a,value:i})}),n};return(n,i)=>{const a=sA,o=oA,s=VA,l=wW;return G(),ce(ft,null,[n.proxyType==="http"||n.proxyType==="https"?(G(),de(o,{key:0,"label-position":"left",inline:"",class:"proxy-table-expand"},{default:j(()=>[Z(a,{label:"Name"},{default:j(()=>[ee("span",null,be(n.row.name),1)]),_:1}),Z(a,{label:"Type"},{default:j(()=>[ee("span",null,be(n.row.type),1)]),_:1}),Z(a,{label:"Domains"},{default:j(()=>[ee("span",null,be(n.row.customDomains),1)]),_:1}),Z(a,{label:"SubDomain"},{default:j(()=>[ee("span",null,be(n.row.subdomain),1)]),_:1}),Z(a,{label:"locations"},{default:j(()=>[ee("span",null,be(n.row.locations),1)]),_:1}),Z(a,{label:"HostRewrite"},{default:j(()=>[ee("span",null,be(n.row.hostHeaderRewrite),1)]),_:1}),Z(a,{label:"Encryption"},{default:j(()=>[ee("span",null,be(n.row.encryption),1)]),_:1}),Z(a,{label:"Compression"},{default:j(()=>[ee("span",null,be(n.row.compression),1)]),_:1}),Z(a,{label:"Last Start"},{default:j(()=>[ee("span",null,be(n.row.lastStartTime),1)]),_:1}),Z(a,{label:"Last Close"},{default:j(()=>[ee("span",null,be(n.row.lastCloseTime),1)]),_:1})]),_:1})):(G(),de(o,{key:1,"label-position":"left",inline:"",class:"proxy-table-expand"},{default:j(()=>[Z(a,{label:"Name"},{default:j(()=>[ee("span",null,be(n.row.name),1)]),_:1}),Z(a,{label:"Type"},{default:j(()=>[ee("span",null,be(n.row.type),1)]),_:1}),Z(a,{label:"Addr"},{default:j(()=>[ee("span",null,be(n.row.addr),1)]),_:1}),Z(a,{label:"Encryption"},{default:j(()=>[ee("span",null,be(n.row.encryption),1)]),_:1}),Z(a,{label:"Compression"},{default:j(()=>[ee("span",null,be(n.row.compression),1)]),_:1}),Z(a,{label:"Last Start"},{default:j(()=>[ee("span",null,be(n.row.lastStartTime),1)]),_:1}),Z(a,{label:"Last Close"},{default:j(()=>[ee("span",null,be(n.row.lastCloseTime),1)]),_:1})]),_:1})),n.row.annotations&&n.row.annotations.size>0?(G(),ce("div",wte,[Z(s),Z(l,{class:"title-text",size:"large"},{default:j(()=>[mt("Annotations")]),_:1}),ee("ul",null,[(G(!0),ce(ft,null,Hp(r(),u=>(G(),ce("li",null,[ee("span",Ste,be(u.key),1),ee("span",null,be(u.value),1)]))),256))])])):Ae("",!0)],64)}}}),Cte={class:"flex items-center",style:{"margin-right":"30px"}},Ks=ie({__name:"ProxyView",props:{proxies:{},proxyType:{}},emits:["refresh"],setup(e,{emit:t}){const r=t,n=$(!1),i=$(""),a=(l,u)=>Mu.fileSize(l.trafficIn),o=(l,u)=>Mu.fileSize(l.trafficOut),s=()=>{fetch("../api/proxies?status=offline",{method:"DELETE",credentials:"include"}).then(l=>{l.ok?(Wl({message:"Successfully cleared offline proxies",type:"success"}),r("refresh")):Wl({message:"Failed to clear offline proxies: "+l.status+" "+l.statusText,type:"warning"})}).catch(l=>{Wl({message:"Failed to clear offline proxies: "+l.message,type:"warning"})})};return(l,u)=>{const f=yg,c=eV,h=q6,d=gW,v=N8,p=pW,m=bte,g=p6;return G(),ce(ft,null,[ee("div",null,[Z(h,{icon:null,style:{width:"100%","margin-left":"30px","margin-bottom":"20px"}},{title:j(()=>[ee("span",null,be(l.proxyType),1)]),content:j(()=>[]),extra:j(()=>[ee("div",Cte,[Z(c,{title:"Are you sure to clear all data of offline proxies?",onConfirm:s},{reference:j(()=>[Z(f,null,{default:j(()=>[mt("ClearOfflineProxies")]),_:1})]),_:1}),Z(f,{onClick:u[0]||(u[0]=y=>l.$emit("refresh"))},{default:j(()=>[mt("Refresh")]),_:1})])]),_:1}),Z(p,{data:l.proxies,"default-sort":{prop:"name",order:"ascending"},style:{width:"100%"}},{default:j(()=>[Z(d,{type:"expand"},{default:j(y=>[Z(xte,{row:y.row,proxyType:l.proxyType},null,8,["row","proxyType"])]),_:1}),Z(d,{label:"Name",prop:"name",sortable:""}),Z(d,{label:"Port",prop:"port",sortable:""}),Z(d,{label:"Connections",prop:"conns",sortable:""}),Z(d,{label:"Traffic In",prop:"trafficIn",formatter:a,sortable:""}),Z(d,{label:"Traffic Out",prop:"trafficOut",formatter:o,sortable:""}),Z(d,{label:"ClientVersion",prop:"clientVersion",sortable:""}),Z(d,{label:"Status",prop:"status",sortable:""},{default:j(y=>[y.row.status==="online"?(G(),de(v,{key:0,type:"success"},{default:j(()=>[mt(be(y.row.status),1)]),_:2},1024)):(G(),de(v,{key:1,type:"danger"},{default:j(()=>[mt(be(y.row.status),1)]),_:2},1024))]),_:1}),Z(d,{label:"Operations"},{default:j(y=>[Z(f,{type:"primary",name:y.row.name,style:{"margin-bottom":"10px"},onClick:_=>{i.value=y.row.name,n.value=!0}},{default:j(()=>[mt("Traffic ")]),_:2},1032,["name","onClick"])]),_:1})]),_:1},8,["data"])]),Z(g,{modelValue:n.value,"onUpdate:modelValue":u[1]||(u[1]=y=>n.value=y),"destroy-on-close":"true",title:i.value,width:"700px"},{default:j(()=>[Z(m,{proxyName:i.value},null,8,["proxyName"])]),_:1},8,["modelValue","title"])],64)}}}),Tte=ie({__name:"ProxiesTCP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/tcp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new hte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"tcp",onRefresh:r},null,8,["proxies"]))}}),Mte=ie({__name:"ProxiesUDP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/udp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new vte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"udp",onRefresh:r},null,8,["proxies"]))}}),Ate=ie({__name:"ProxiesHTTP",setup(e){let t=$([]);const r=()=>{let n,i;fetch("../api/serverinfo",{credentials:"include"}).then(a=>a.json()).then(a=>{n=a.vhostHTTPPort,i=a.subdomainHost,!(n==null||n==0)&&fetch("../api/proxy/http",{credentials:"include"}).then(o=>o.json()).then(o=>{t.value=[];for(let s of o.proxies)t.value.push(new pte(s,n,i))})})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"http",onRefresh:r},null,8,["proxies"]))}}),Pte=ie({__name:"ProxiesHTTPS",setup(e){let t=$([]);const r=()=>{let n,i;fetch("../api/serverinfo",{credentials:"include"}).then(a=>a.json()).then(a=>{n=a.vhostHTTPSPort,i=a.subdomainHost,!(n==null||n==0)&&fetch("../api/proxy/https",{credentials:"include"}).then(o=>o.json()).then(o=>{t.value=[];for(let s of o.proxies)t.value.push(new gte(s,n,i))})})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"https",onRefresh:r},null,8,["proxies"]))}}),Ete=ie({__name:"ProxiesSTCP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/stcp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new mte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"stcp",onRefresh:r},null,8,["proxies"]))}}),Lte=ie({__name:"ProxiesSUDP",setup(e){let t=$([]);const r=()=>{fetch("../api/proxy/sudp",{credentials:"include"}).then(n=>n.json()).then(n=>{t.value=[];for(let i of n.proxies)t.value.push(new yte(i))})};return r(),(n,i)=>(G(),de(Ks,{proxies:T(t),proxyType:"sudp",onRefresh:r},null,8,["proxies"]))}}),Dte=jG({history:uG(),routes:[{path:"/",name:"ServerOverview",component:dte},{path:"/proxies/tcp",name:"ProxiesTCP",component:Tte},{path:"/proxies/udp",name:"ProxiesUDP",component:Mte},{path:"/proxies/http",name:"ProxiesHTTP",component:Ate},{path:"/proxies/https",name:"ProxiesHTTPS",component:Pte},{path:"/proxies/stcp",name:"ProxiesSTCP",component:Ete},{path:"/proxies/sudp",name:"ProxiesSUDP",component:Lte}]}),MD=oR(GW);MD.use(Dte);MD.mount("#app")});export default Ite(); diff --git a/assets/frps/static/index.html b/assets/frps/static/index.html index 53028f2326e..0a1966e8306 100644 --- a/assets/frps/static/index.html +++ b/assets/frps/static/index.html @@ -4,7 +4,7 @@ frps dashboard - + diff --git a/client/admin_api.go b/client/admin_api.go index e15fc4a4da5..ff889d52355 100644 --- a/client/admin_api.go +++ b/client/admin_api.go @@ -76,9 +76,9 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { strictConfigMode, _ = strconv.ParseBool(strictStr) } - log.Info("api request [/api/reload]") + log.Infof("api request [/api/reload]") defer func() { - log.Info("api response [/api/reload], code [%d]", res.Code) + log.Infof("api response [/api/reload], code [%d]", res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) @@ -89,32 +89,32 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { if err != nil { res.Code = 400 res.Msg = err.Error() - log.Warn("reload frpc proxy config error: %s", res.Msg) + log.Warnf("reload frpc proxy config error: %s", res.Msg) return } if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs); err != nil { res.Code = 400 res.Msg = err.Error() - log.Warn("reload frpc proxy config error: %s", res.Msg) + log.Warnf("reload frpc proxy config error: %s", res.Msg) return } if err := svr.UpdateAllConfigurer(proxyCfgs, visitorCfgs); err != nil { res.Code = 500 res.Msg = err.Error() - log.Warn("reload frpc proxy config error: %s", res.Msg) + log.Warnf("reload frpc proxy config error: %s", res.Msg) return } - log.Info("success reload conf") + log.Infof("success reload conf") } // POST /api/stop func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) { res := GeneralResponse{Code: 200} - log.Info("api request [/api/stop]") + log.Infof("api request [/api/stop]") defer func() { - log.Info("api response [/api/stop], code [%d]", res.Code) + log.Infof("api response [/api/stop], code [%d]", res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) @@ -165,9 +165,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) { res StatusResp = make(map[string][]ProxyStatusResp) ) - log.Info("Http request [/api/status]") + log.Infof("Http request [/api/status]") defer func() { - log.Info("Http response [/api/status]") + log.Infof("Http response [/api/status]") buf, _ = json.Marshal(&res) _, _ = w.Write(buf) }() @@ -198,9 +198,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) { func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) { res := GeneralResponse{Code: 200} - log.Info("Http get request [/api/config]") + log.Infof("Http get request [/api/config]") defer func() { - log.Info("Http get response [/api/config], code [%d]", res.Code) + log.Infof("Http get response [/api/config], code [%d]", res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) @@ -210,7 +210,7 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) { if svr.configFilePath == "" { res.Code = 400 res.Msg = "frpc has no config file path" - log.Warn("%s", res.Msg) + log.Warnf("%s", res.Msg) return } @@ -218,7 +218,7 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) { if err != nil { res.Code = 400 res.Msg = err.Error() - log.Warn("load frpc config file error: %s", res.Msg) + log.Warnf("load frpc config file error: %s", res.Msg) return } res.Msg = string(content) @@ -228,9 +228,9 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) { func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) { res := GeneralResponse{Code: 200} - log.Info("Http put request [/api/config]") + log.Infof("Http put request [/api/config]") defer func() { - log.Info("Http put response [/api/config], code [%d]", res.Code) + log.Infof("Http put response [/api/config], code [%d]", res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) @@ -242,21 +242,21 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) { if err != nil { res.Code = 400 res.Msg = fmt.Sprintf("read request body error: %v", err) - log.Warn("%s", res.Msg) + log.Warnf("%s", res.Msg) return } if len(body) == 0 { res.Code = 400 res.Msg = "body can't be empty" - log.Warn("%s", res.Msg) + log.Warnf("%s", res.Msg) return } if err := os.WriteFile(svr.configFilePath, body, 0o644); err != nil { res.Code = 500 res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err) - log.Warn("%s", res.Msg) + log.Warnf("%s", res.Msg) return } } diff --git a/client/connector.go b/client/connector.go index 8c3cd1ec68d..184194a171f 100644 --- a/client/connector.go +++ b/client/connector.go @@ -84,7 +84,7 @@ func (c *defaultConnectorImpl) Open() error { tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn) } if err != nil { - xl.Warn("fail to build tls configuration, err: %v", err) + xl.Warnf("fail to build tls configuration, err: %v", err) return err } tlsConfig.NextProtos = []string{"frp"} @@ -164,14 +164,14 @@ func (c *defaultConnectorImpl) realConnect() (net.Conn, error) { c.cfg.Transport.TLS.TrustedCaFile, sn) if err != nil { - xl.Warn("fail to build tls configuration, err: %v", err) + xl.Warnf("fail to build tls configuration, err: %v", err) return nil, err } } proxyType, addr, auth, err := libdial.ParseProxyURL(c.cfg.Transport.ProxyURL) if err != nil { - xl.Error("fail to parse proxy url") + xl.Errorf("fail to parse proxy url") return nil, err } dialOptions := []libdial.DialOption{} diff --git a/client/control.go b/client/control.go index b272ee06800..e1916890573 100644 --- a/client/control.go +++ b/client/control.go @@ -124,7 +124,7 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) { xl := ctl.xl workConn, err := ctl.connectServer() if err != nil { - xl.Warn("start new connection to server error: %v", err) + xl.Warnf("start new connection to server error: %v", err) return } @@ -132,24 +132,24 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) { RunID: ctl.sessionCtx.RunID, } if err = ctl.sessionCtx.AuthSetter.SetNewWorkConn(m); err != nil { - xl.Warn("error during NewWorkConn authentication: %v", err) + xl.Warnf("error during NewWorkConn authentication: %v", err) workConn.Close() return } if err = msg.WriteMsg(workConn, m); err != nil { - xl.Warn("work connection write to server error: %v", err) + xl.Warnf("work connection write to server error: %v", err) workConn.Close() return } var startMsg msg.StartWorkConn if err = msg.ReadMsgInto(workConn, &startMsg); err != nil { - xl.Trace("work connection closed before response StartWorkConn message: %v", err) + xl.Tracef("work connection closed before response StartWorkConn message: %v", err) workConn.Close() return } if startMsg.Error != "" { - xl.Error("StartWorkConn contains error: %s", startMsg.Error) + xl.Errorf("StartWorkConn contains error: %s", startMsg.Error) workConn.Close() return } @@ -165,9 +165,9 @@ func (ctl *Control) handleNewProxyResp(m msg.Message) { // Start a new proxy handler if no error got err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error) if err != nil { - xl.Warn("[%s] start error: %v", inMsg.ProxyName, err) + xl.Warnf("[%s] start error: %v", inMsg.ProxyName, err) } else { - xl.Info("[%s] start proxy success", inMsg.ProxyName) + xl.Infof("[%s] start proxy success", inMsg.ProxyName) } } @@ -178,7 +178,7 @@ func (ctl *Control) handleNatHoleResp(m msg.Message) { // Dispatch the NatHoleResp message to the related proxy. ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID) if !ok { - xl.Trace("dispatch NatHoleResp message to related proxy error") + xl.Tracef("dispatch NatHoleResp message to related proxy error") } } @@ -187,12 +187,12 @@ func (ctl *Control) handlePong(m msg.Message) { inMsg := m.(*msg.Pong) if inMsg.Error != "" { - xl.Error("Pong message contains error: %s", inMsg.Error) + xl.Errorf("Pong message contains error: %s", inMsg.Error) ctl.closeSession() return } ctl.lastPong.Store(time.Now()) - xl.Debug("receive heartbeat from server") + xl.Debugf("receive heartbeat from server") } // closeSession closes the control connection. @@ -241,10 +241,10 @@ func (ctl *Control) heartbeatWorker() { if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 { // send heartbeat to server sendHeartBeat := func() (bool, error) { - xl.Debug("send heartbeat to server") + xl.Debugf("send heartbeat to server") pingMsg := &msg.Ping{} if err := ctl.sessionCtx.AuthSetter.SetPing(pingMsg); err != nil { - xl.Warn("error during ping authentication: %v, skip sending ping message", err) + xl.Warnf("error during ping authentication: %v, skip sending ping message", err) return false, err } _ = ctl.msgDispatcher.Send(pingMsg) @@ -269,7 +269,7 @@ func (ctl *Control) heartbeatWorker() { go wait.Until(func() { if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second { - xl.Warn("heartbeat timeout") + xl.Warnf("heartbeat timeout") ctl.closeSession() return } diff --git a/client/health/health.go b/client/health/health.go index 63eec721811..d298e61520d 100644 --- a/client/health/health.go +++ b/client/health/health.go @@ -40,8 +40,8 @@ type Monitor struct { addr string // For http - url string - + url string + header http.Header failedTimes uint64 statusOK bool statusNormalFn func() @@ -73,6 +73,11 @@ func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string, } url = s + cfg.Path } + header := make(http.Header) + for _, h := range cfg.HTTPHeaders { + header.Set(h.Name, h.Value) + } + return &Monitor{ checkType: cfg.Type, interval: time.Duration(cfg.IntervalSeconds) * time.Second, @@ -80,6 +85,7 @@ func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string, maxFailedTimes: cfg.MaxFailed, addr: addr, url: url, + header: header, statusOK: false, statusNormalFn: statusNormalFn, statusFailedFn: statusFailedFn, @@ -112,17 +118,17 @@ func (monitor *Monitor) checkWorker() { } if err == nil { - xl.Trace("do one health check success") + xl.Tracef("do one health check success") if !monitor.statusOK && monitor.statusNormalFn != nil { - xl.Info("health check status change to success") + xl.Infof("health check status change to success") monitor.statusOK = true monitor.statusNormalFn() } } else { - xl.Warn("do one health check failed: %v", err) + xl.Warnf("do one health check failed: %v", err) monitor.failedTimes++ if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil { - xl.Warn("health check status change to failed") + xl.Warnf("health check status change to failed") monitor.statusOK = false monitor.statusFailedFn() } @@ -163,6 +169,8 @@ func (monitor *Monitor) doHTTPCheck(ctx context.Context) error { if err != nil { return err } + req.Header = monitor.header + req.Host = monitor.header.Get("Host") resp, err := http.DefaultClient.Do(req) if err != nil { return err diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index 396539c0837..16295e052c7 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -141,13 +141,13 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor }) } - xl.Trace("handle tcp work connection, useEncryption: %t, useCompression: %t", + xl.Tracef("handle tcp work connection, useEncryption: %t, useCompression: %t", baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression) if baseCfg.Transport.UseEncryption { remote, err = libio.WithEncryption(remote, encKey) if err != nil { workConn.Close() - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return } } @@ -158,40 +158,42 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor // check if we need to send proxy protocol info var extraInfo plugin.ExtraInfo - if baseCfg.Transport.ProxyProtocolVersion != "" { - if m.SrcAddr != "" && m.SrcPort != 0 { - if m.DstAddr == "" { - m.DstAddr = "127.0.0.1" - } - srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort)))) - dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort)))) - h := &pp.Header{ - Command: pp.PROXY, - SourceAddr: srcAddr, - DestinationAddr: dstAddr, - } - - if strings.Contains(m.SrcAddr, ".") { - h.TransportProtocol = pp.TCPv4 - } else { - h.TransportProtocol = pp.TCPv6 - } - - if baseCfg.Transport.ProxyProtocolVersion == "v1" { - h.Version = 1 - } else if baseCfg.Transport.ProxyProtocolVersion == "v2" { - h.Version = 2 - } - - extraInfo.ProxyProtocolHeader = h + if m.SrcAddr != "" && m.SrcPort != 0 { + if m.DstAddr == "" { + m.DstAddr = "127.0.0.1" } + srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort)))) + dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort)))) + extraInfo.SrcAddr = srcAddr + extraInfo.DstAddr = dstAddr + } + + if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 { + h := &pp.Header{ + Command: pp.PROXY, + SourceAddr: extraInfo.SrcAddr, + DestinationAddr: extraInfo.DstAddr, + } + + if strings.Contains(m.SrcAddr, ".") { + h.TransportProtocol = pp.TCPv4 + } else { + h.TransportProtocol = pp.TCPv6 + } + + if baseCfg.Transport.ProxyProtocolVersion == "v1" { + h.Version = 1 + } else if baseCfg.Transport.ProxyProtocolVersion == "v2" { + h.Version = 2 + } + extraInfo.ProxyProtocolHeader = h } if pxy.proxyPlugin != nil { // if plugin is set, let plugin handle connection first - xl.Debug("handle by plugin: %s", pxy.proxyPlugin.Name()) + xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name()) pxy.proxyPlugin.Handle(remote, workConn, &extraInfo) - xl.Debug("handle by plugin finished") + xl.Debugf("handle by plugin finished") return } @@ -201,25 +203,25 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor ) if err != nil { workConn.Close() - xl.Error("connect to local service [%s:%d] error: %v", baseCfg.LocalIP, baseCfg.LocalPort, err) + xl.Errorf("connect to local service [%s:%d] error: %v", baseCfg.LocalIP, baseCfg.LocalPort, err) return } - xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(), + xl.Debugf("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(), localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String()) if extraInfo.ProxyProtocolHeader != nil { if _, err := extraInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil { workConn.Close() - xl.Error("write proxy protocol header to local conn error: %v", err) + xl.Errorf("write proxy protocol header to local conn error: %v", err) return } } _, _, errs := libio.Join(localConn, remote) - xl.Debug("join connections closed") + xl.Debugf("join connections closed") if len(errs) > 0 { - xl.Trace("join connections errors: %v", errs) + xl.Tracef("join connections errors: %v", errs) } if compressionResourceRecycleFn != nil { compressionResourceRecycleFn() diff --git a/client/proxy/proxy_manager.go b/client/proxy/proxy_manager.go index 12e2f6cfee8..95778ce0213 100644 --- a/client/proxy/proxy_manager.go +++ b/client/proxy/proxy_manager.go @@ -152,7 +152,7 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) { } } if len(delPxyNames) > 0 { - xl.Info("proxy removed: %s", delPxyNames) + xl.Infof("proxy removed: %s", delPxyNames) } addPxyNames := make([]string, 0) @@ -170,6 +170,6 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) { } } if len(addPxyNames) > 0 { - xl.Info("proxy added: %s", addPxyNames) + xl.Infof("proxy added: %s", addPxyNames) } } diff --git a/client/proxy/proxy_wrapper.go b/client/proxy/proxy_wrapper.go index 84f24abb668..487e3702b9e 100644 --- a/client/proxy/proxy_wrapper.go +++ b/client/proxy/proxy_wrapper.go @@ -114,7 +114,7 @@ func NewWrapper( addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort)) pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr, pw.statusNormalCallback, pw.statusFailedCallback) - xl.Trace("enable health check monitor") + xl.Tracef("enable health check monitor") } pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter) @@ -197,7 +197,7 @@ func (pw *Wrapper) checkWorker() { (pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) || (pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) { - xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart) + xl.Tracef("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart) pw.Phase = ProxyPhaseWaitStart var newProxyMsg msg.NewProxy @@ -212,7 +212,7 @@ func (pw *Wrapper) checkWorker() { pw.mu.Lock() if pw.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart { pw.close() - xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed) + xl.Tracef("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed) pw.Phase = ProxyPhaseCheckFailed } pw.mu.Unlock() @@ -236,7 +236,7 @@ func (pw *Wrapper) statusNormalCallback() { default: } }) - xl.Info("health check success") + xl.Infof("health check success") } func (pw *Wrapper) statusFailedCallback() { @@ -248,7 +248,7 @@ func (pw *Wrapper) statusFailedCallback() { default: } }) - xl.Info("health check failed") + xl.Infof("health check failed") } func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) { @@ -257,7 +257,7 @@ func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) { pxy := pw.pxy pw.mu.RUnlock() if pxy != nil && pw.Phase == ProxyPhaseRunning { - xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) + xl.Debugf("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) go pxy.InWorkConn(workConn, m) } else { workConn.Close() diff --git a/client/proxy/sudp.go b/client/proxy/sudp.go index 4d06170dc90..ad9db89a940 100644 --- a/client/proxy/sudp.go +++ b/client/proxy/sudp.go @@ -81,7 +81,7 @@ func (pxy *SUDPProxy) Close() { func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { xl := pxy.xl - xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String()) + xl.Infof("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String()) var rwc io.ReadWriteCloser = conn var err error @@ -94,7 +94,7 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token)) if err != nil { conn.Close() - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return } } @@ -133,21 +133,21 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { // first to check sudp proxy is closed or not select { case <-pxy.closeCh: - xl.Trace("frpc sudp proxy is closed") + xl.Tracef("frpc sudp proxy is closed") return default: } var udpMsg msg.UDPPacket if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil { - xl.Warn("read from workConn for sudp error: %v", errRet) + xl.Warnf("read from workConn for sudp error: %v", errRet) return } if errRet := errors.PanicToError(func() { readCh <- &udpMsg }); errRet != nil { - xl.Warn("reader goroutine for sudp work connection closed: %v", errRet) + xl.Warnf("reader goroutine for sudp work connection closed: %v", errRet) return } } @@ -157,21 +157,21 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) { defer func() { closeFn() - xl.Info("writer goroutine for sudp work connection closed") + xl.Infof("writer goroutine for sudp work connection closed") }() var errRet error for rawMsg := range sendCh { switch m := rawMsg.(type) { case *msg.UDPPacket: - xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]", + xl.Tracef("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]", m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String()) case *msg.Ping: - xl.Trace("frpc send ping message to frpc visitor") + xl.Tracef("frpc send ping message to frpc visitor") } if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil { - xl.Error("sudp work write error: %v", errRet) + xl.Errorf("sudp work write error: %v", errRet) return } } @@ -191,11 +191,11 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { if errRet = errors.PanicToError(func() { sendCh <- &msg.Ping{} }); errRet != nil { - xl.Warn("heartbeat goroutine for sudp work connection closed") + xl.Warnf("heartbeat goroutine for sudp work connection closed") return } case <-pxy.closeCh: - xl.Trace("frpc sudp proxy is closed") + xl.Tracef("frpc sudp proxy is closed") return } } diff --git a/client/proxy/udp.go b/client/proxy/udp.go index 38d14ff598b..b08fe1608ad 100644 --- a/client/proxy/udp.go +++ b/client/proxy/udp.go @@ -90,7 +90,7 @@ func (pxy *UDPProxy) Close() { func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { xl := pxy.xl - xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String()) + xl.Infof("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String()) // close resources related with old workConn pxy.Close() @@ -105,7 +105,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token)) if err != nil { conn.Close() - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return } } @@ -125,32 +125,32 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { for { var udpMsg msg.UDPPacket if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil { - xl.Warn("read from workConn for udp error: %v", errRet) + xl.Warnf("read from workConn for udp error: %v", errRet) return } if errRet := errors.PanicToError(func() { - xl.Trace("get udp package from workConn: %s", udpMsg.Content) + xl.Tracef("get udp package from workConn: %s", udpMsg.Content) readCh <- &udpMsg }); errRet != nil { - xl.Info("reader goroutine for udp work connection closed: %v", errRet) + xl.Infof("reader goroutine for udp work connection closed: %v", errRet) return } } } workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) { defer func() { - xl.Info("writer goroutine for udp work connection closed") + xl.Infof("writer goroutine for udp work connection closed") }() var errRet error for rawMsg := range sendCh { switch m := rawMsg.(type) { case *msg.UDPPacket: - xl.Trace("send udp package to workConn: %s", m.Content) + xl.Tracef("send udp package to workConn: %s", m.Content) case *msg.Ping: - xl.Trace("send ping message to udp workConn") + xl.Tracef("send ping message to udp workConn") } if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil { - xl.Error("udp work write error: %v", errRet) + xl.Errorf("udp work write error: %v", errRet) return } } @@ -162,7 +162,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { if errRet = errors.PanicToError(func() { sendCh <- &msg.Ping{} }); errRet != nil { - xl.Trace("heartbeat goroutine for udp work connection closed") + xl.Tracef("heartbeat goroutine for udp work connection closed") break } } diff --git a/client/proxy/xtcp.go b/client/proxy/xtcp.go index e5e5d47e280..31f9ac89734 100644 --- a/client/proxy/xtcp.go +++ b/client/proxy/xtcp.go @@ -59,17 +59,17 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC var natHoleSidMsg msg.NatHoleSid err := msg.ReadMsgInto(conn, &natHoleSidMsg) if err != nil { - xl.Error("xtcp read from workConn error: %v", err) + xl.Errorf("xtcp read from workConn error: %v", err) return } - xl.Trace("nathole prepare start") + xl.Tracef("nathole prepare start") prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer}) if err != nil { - xl.Warn("nathole prepare error: %v", err) + xl.Warnf("nathole prepare error: %v", err) return } - xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v", + xl.Infof("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v", prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs) defer prepareResult.ListenConn.Close() @@ -83,14 +83,14 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC AssistedAddrs: prepareResult.AssistedAddrs, } - xl.Trace("nathole exchange info start") + xl.Tracef("nathole exchange info start") natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second) if err != nil { - xl.Warn("nathole exchange info error: %v", err) + xl.Warnf("nathole exchange info error: %v", err) return } - xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v", + xl.Infof("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v", natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs, natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior) @@ -98,7 +98,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Secretkey)) if err != nil { listenConn.Close() - xl.Warn("make hole error: %v", err) + xl.Warnf("make hole error: %v", err) _ = pxy.msgTransporter.Send(&msg.NatHoleReport{ Sid: natHoleRespMsg.Sid, Success: false, @@ -106,7 +106,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC return } listenConn = newListenConn - xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr) + xl.Infof("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr) _ = pxy.msgTransporter.Send(&msg.NatHoleReport{ Sid: natHoleRespMsg.Sid, @@ -128,14 +128,14 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String()) lConn, err := net.DialUDP("udp", laddr, raddr) if err != nil { - xl.Warn("dial udp error: %v", err) + xl.Warnf("dial udp error: %v", err) return } defer lConn.Close() remote, err := netpkg.NewKCPConnFromUDP(lConn, true, raddr.String()) if err != nil { - xl.Warn("create kcp connection from udp connection error: %v", err) + xl.Warnf("create kcp connection from udp connection error: %v", err) return } @@ -145,7 +145,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s fmuxCfg.LogOutput = io.Discard session, err := fmux.Server(remote, fmuxCfg) if err != nil { - xl.Error("create mux session error: %v", err) + xl.Errorf("create mux session error: %v", err) return } defer session.Close() @@ -153,7 +153,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s for { muxConn, err := session.Accept() if err != nil { - xl.Error("accept connection error: %v", err) + xl.Errorf("accept connection error: %v", err) return } go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Secretkey)) @@ -166,7 +166,7 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star tlsConfig, err := transport.NewServerTLSConfig("", "", "") if err != nil { - xl.Warn("create tls config error: %v", err) + xl.Warnf("create tls config error: %v", err) return } tlsConfig.NextProtos = []string{"frp"} @@ -178,19 +178,19 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star }, ) if err != nil { - xl.Warn("dial quic error: %v", err) + xl.Warnf("dial quic error: %v", err) return } // only accept one connection from raddr c, err := quicListener.Accept(pxy.ctx) if err != nil { - xl.Error("quic accept connection error: %v", err) + xl.Errorf("quic accept connection error: %v", err) return } for { stream, err := c.AcceptStream(pxy.ctx) if err != nil { - xl.Debug("quic accept stream error: %v", err) + xl.Debugf("quic accept stream error: %v", err) _ = c.CloseWithError(0, "") return } diff --git a/client/service.go b/client/service.go index 3d1a21b0e04..7a7f6dc88d2 100644 --- a/client/service.go +++ b/client/service.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "net" + "os" "runtime" "sync" "time" @@ -40,6 +41,12 @@ import ( func init() { crypto.DefaultSalt = "frp" + // Disable quic-go's receive buffer warning. + os.Setenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING", "true") + // Disable quic-go's ECN support by default. It may cause issues on certain operating systems. + if os.Getenv("QUIC_GO_DISABLE_ECN") == "" { + os.Setenv("QUIC_GO_DISABLE_ECN", "true") + } } type cancelErr struct { @@ -174,9 +181,9 @@ func (svr *Service) Run(ctx context.Context) error { if svr.webServer != nil { go func() { - log.Info("admin server listen on %s", svr.webServer.Address()) + log.Infof("admin server listen on %s", svr.webServer.Address()) if err := svr.webServer.Run(); err != nil { - log.Warn("admin server exit with error: %v", err) + log.Warnf("admin server exit with error: %v", err) } }() } @@ -269,14 +276,14 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) { if loginRespMsg.Error != "" { err = fmt.Errorf("%s", loginRespMsg.Error) - xl.Error("%s", loginRespMsg.Error) + xl.Errorf("%s", loginRespMsg.Error) return } svr.runID = loginRespMsg.RunID xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID}) - xl.Info("login to server success, get run id [%s]", loginRespMsg.RunID) + xl.Infof("login to server success, get run id [%s]", loginRespMsg.RunID) return } @@ -284,10 +291,10 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE xl := xlog.FromContextSafe(svr.ctx) loginFunc := func() (bool, error) { - xl.Info("try to connect to server...") + xl.Infof("try to connect to server...") conn, connector, err := svr.login() if err != nil { - xl.Warn("connect to server error: %v", err) + xl.Warnf("connect to server error: %v", err) if firstLoginExit { svr.cancel(cancelErr{Err: err}) } @@ -313,7 +320,7 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE ctl, err := NewControl(svr.ctx, sessionCtx) if err != nil { conn.Close() - xl.Error("NewControl error: %v", err) + xl.Errorf("NewControl error: %v", err) return false, err } ctl.SetInWorkConnCallback(svr.handleWorkConnCb) diff --git a/client/visitor/stcp.go b/client/visitor/stcp.go index 58433879492..b26faf5200c 100644 --- a/client/visitor/stcp.go +++ b/client/visitor/stcp.go @@ -56,7 +56,7 @@ func (sv *STCPVisitor) worker() { for { conn, err := sv.l.Accept() if err != nil { - xl.Warn("stcp local listener closed") + xl.Warnf("stcp local listener closed") return } go sv.handleConn(conn) @@ -68,7 +68,7 @@ func (sv *STCPVisitor) internalConnWorker() { for { conn, err := sv.internalLn.Accept() if err != nil { - xl.Warn("stcp internal listener closed") + xl.Warnf("stcp internal listener closed") return } go sv.handleConn(conn) @@ -79,7 +79,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { xl := xlog.FromContextSafe(sv.ctx) defer userConn.Close() - xl.Debug("get a new stcp user connection") + xl.Debugf("get a new stcp user connection") visitorConn, err := sv.helper.ConnectServer() if err != nil { return @@ -97,7 +97,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { } err = msg.WriteMsg(visitorConn, newVisitorConnMsg) if err != nil { - xl.Warn("send newVisitorConnMsg to server error: %v", err) + xl.Warnf("send newVisitorConnMsg to server error: %v", err) return } @@ -105,13 +105,13 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { _ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second)) err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg) if err != nil { - xl.Warn("get newVisitorConnRespMsg error: %v", err) + xl.Warnf("get newVisitorConnRespMsg error: %v", err) return } _ = visitorConn.SetReadDeadline(time.Time{}) if newVisitorConnRespMsg.Error != "" { - xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error) + xl.Warnf("start new visitor connection error: %s", newVisitorConnRespMsg.Error) return } @@ -120,7 +120,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { if sv.cfg.Transport.UseEncryption { remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey)) if err != nil { - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return } } diff --git a/client/visitor/sudp.go b/client/visitor/sudp.go index 1d489bec42b..284aee10915 100644 --- a/client/visitor/sudp.go +++ b/client/visitor/sudp.go @@ -62,7 +62,7 @@ func (sv *SUDPVisitor) Run() (err error) { sv.sendCh = make(chan *msg.UDPPacket, 1024) sv.readCh = make(chan *msg.UDPPacket, 1024) - xl.Info("sudp start to work, listen on %s", addr) + xl.Infof("sudp start to work, listen on %s", addr) go sv.dispatcher() go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.clientCfg.UDPPacketSize)) @@ -84,17 +84,17 @@ func (sv *SUDPVisitor) dispatcher() { select { case firstPacket = <-sv.sendCh: if firstPacket == nil { - xl.Info("frpc sudp visitor proxy is closed") + xl.Infof("frpc sudp visitor proxy is closed") return } case <-sv.checkCloseCh: - xl.Info("frpc sudp visitor proxy is closed") + xl.Infof("frpc sudp visitor proxy is closed") return } visitorConn, err = sv.getNewVisitorConn() if err != nil { - xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err) + xl.Warnf("newVisitorConn to frps error: %v, try to reconnect", err) continue } @@ -111,7 +111,7 @@ func (sv *SUDPVisitor) dispatcher() { func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) { xl := xlog.FromContextSafe(sv.ctx) - xl.Debug("starting sudp proxy worker") + xl.Debugf("starting sudp proxy worker") wg := &sync.WaitGroup{} wg.Add(2) @@ -134,21 +134,21 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) { // frpc will send heartbeat in workConn to frpc visitor for keeping alive _ = conn.SetReadDeadline(time.Now().Add(60 * time.Second)) if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil { - xl.Warn("read from workconn for user udp conn error: %v", errRet) + xl.Warnf("read from workconn for user udp conn error: %v", errRet) return } _ = conn.SetReadDeadline(time.Time{}) switch m := rawMsg.(type) { case *msg.Ping: - xl.Debug("frpc visitor get ping message from frpc") + xl.Debugf("frpc visitor get ping message from frpc") continue case *msg.UDPPacket: if errRet := errors.PanicToError(func() { sv.readCh <- m - xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content) + xl.Tracef("frpc visitor get udp packet from workConn: %s", m.Content) }); errRet != nil { - xl.Info("reader goroutine for udp work connection closed") + xl.Infof("reader goroutine for udp work connection closed") return } } @@ -165,25 +165,25 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) { var errRet error if firstPacket != nil { if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil { - xl.Warn("sender goroutine for udp work connection closed: %v", errRet) + xl.Warnf("sender goroutine for udp work connection closed: %v", errRet) return } - xl.Trace("send udp package to workConn: %s", firstPacket.Content) + xl.Tracef("send udp package to workConn: %s", firstPacket.Content) } for { select { case udpMsg, ok := <-sv.sendCh: if !ok { - xl.Info("sender goroutine for udp work connection closed") + xl.Infof("sender goroutine for udp work connection closed") return } if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil { - xl.Warn("sender goroutine for udp work connection closed: %v", errRet) + xl.Warnf("sender goroutine for udp work connection closed: %v", errRet) return } - xl.Trace("send udp package to workConn: %s", udpMsg.Content) + xl.Tracef("send udp package to workConn: %s", udpMsg.Content) case <-closeCh: return } @@ -194,7 +194,7 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) { go workConnSenderFn(workConn) wg.Wait() - xl.Info("sudp worker is closed") + xl.Infof("sudp worker is closed") } func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) { @@ -235,7 +235,7 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) { if sv.cfg.Transport.UseEncryption { remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey)) if err != nil { - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return nil, err } } diff --git a/client/visitor/visitor_manager.go b/client/visitor/visitor_manager.go index 4f31f2706ed..6ff65dabae0 100644 --- a/client/visitor/visitor_manager.go +++ b/client/visitor/visitor_manager.go @@ -79,14 +79,14 @@ func (vm *Manager) keepVisitorsRunning() { for { select { case <-vm.stopCh: - xl.Trace("gracefully shutdown visitor manager") + xl.Tracef("gracefully shutdown visitor manager") return case <-ticker.C: vm.mu.Lock() for _, cfg := range vm.cfgs { name := cfg.GetBaseConfig().Name if _, exist := vm.visitors[name]; !exist { - xl.Info("try to start visitor [%s]", name) + xl.Infof("try to start visitor [%s]", name) _ = vm.startVisitor(cfg) } } @@ -115,10 +115,10 @@ func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) { visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper) err = visitor.Run() if err != nil { - xl.Warn("start error: %v", err) + xl.Warnf("start error: %v", err) } else { vm.visitors[name] = visitor - xl.Info("start visitor success") + xl.Infof("start visitor success") } return } @@ -156,7 +156,7 @@ func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) { } } if len(delNames) > 0 { - xl.Info("visitor removed: %v", delNames) + xl.Infof("visitor removed: %v", delNames) } addNames := make([]string, 0) @@ -169,7 +169,7 @@ func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) { } } if len(addNames) > 0 { - xl.Info("visitor added: %v", addNames) + xl.Infof("visitor added: %v", addNames) } } diff --git a/client/visitor/xtcp.go b/client/visitor/xtcp.go index ad773503e03..a1efd72b62b 100644 --- a/client/visitor/xtcp.go +++ b/client/visitor/xtcp.go @@ -93,7 +93,7 @@ func (sv *XTCPVisitor) worker() { for { conn, err := sv.l.Accept() if err != nil { - xl.Warn("xtcp local listener closed") + xl.Warnf("xtcp local listener closed") return } go sv.handleConn(conn) @@ -105,7 +105,7 @@ func (sv *XTCPVisitor) internalConnWorker() { for { conn, err := sv.internalLn.Accept() if err != nil { - xl.Warn("xtcp internal listener closed") + xl.Warnf("xtcp internal listener closed") return } go sv.handleConn(conn) @@ -140,14 +140,14 @@ func (sv *XTCPVisitor) keepTunnelOpenWorker() { case <-sv.ctx.Done(): return case <-ticker.C: - xl.Debug("keepTunnelOpenWorker try to check tunnel...") + xl.Debugf("keepTunnelOpenWorker try to check tunnel...") conn, err := sv.getTunnelConn() if err != nil { - xl.Warn("keepTunnelOpenWorker get tunnel connection error: %v", err) + xl.Warnf("keepTunnelOpenWorker get tunnel connection error: %v", err) _ = sv.retryLimiter.Wait(sv.ctx) continue } - xl.Debug("keepTunnelOpenWorker check success") + xl.Debugf("keepTunnelOpenWorker check success") if conn != nil { conn.Close() } @@ -164,7 +164,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) { } }() - xl.Debug("get a new xtcp user connection") + xl.Debugf("get a new xtcp user connection") // Open a tunnel connection to the server. If there is already a successful hole-punching connection, // it will be reused. Otherwise, it will block and wait for a successful hole-punching connection until timeout. @@ -176,15 +176,15 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) { } tunnelConn, err := sv.openTunnel(ctx) if err != nil { - xl.Error("open tunnel error: %v", err) + xl.Errorf("open tunnel error: %v", err) // no fallback, just return if sv.cfg.FallbackTo == "" { return } - xl.Debug("try to transfer connection to visitor: %s", sv.cfg.FallbackTo) + xl.Debugf("try to transfer connection to visitor: %s", sv.cfg.FallbackTo) if err := sv.helper.TransferConn(sv.cfg.FallbackTo, userConn); err != nil { - xl.Error("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err) + xl.Errorf("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err) return } isConnTrasfered = true @@ -195,7 +195,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) { if sv.cfg.Transport.UseEncryption { muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey)) if err != nil { - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return } } @@ -206,9 +206,9 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) { } _, _, errs := libio.Join(userConn, muxConnRWCloser) - xl.Debug("join connections closed") + xl.Debugf("join connections closed") if len(errs) > 0 { - xl.Trace("join connections errors: %v", errs) + xl.Tracef("join connections errors: %v", errs) } } @@ -239,7 +239,7 @@ func (sv *XTCPVisitor) openTunnel(ctx context.Context) (conn net.Conn, err error if err != nil { if err != ErrNoTunnelSession { - xl.Warn("get tunnel connection error: %v", err) + xl.Warnf("get tunnel connection error: %v", err) } continue } @@ -268,19 +268,19 @@ func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) { // 4. Create a tunnel session using an underlying UDP connection. func (sv *XTCPVisitor) makeNatHole() { xl := xlog.FromContextSafe(sv.ctx) - xl.Trace("makeNatHole start") + xl.Tracef("makeNatHole start") if err := nathole.PreCheck(sv.ctx, sv.helper.MsgTransporter(), sv.cfg.ServerName, 5*time.Second); err != nil { - xl.Warn("nathole precheck error: %v", err) + xl.Warnf("nathole precheck error: %v", err) return } - xl.Trace("nathole prepare start") + xl.Tracef("nathole prepare start") prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer}) if err != nil { - xl.Warn("nathole prepare error: %v", err) + xl.Warnf("nathole prepare error: %v", err) return } - xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v", + xl.Infof("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v", prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs) listenConn := prepareResult.ListenConn @@ -298,30 +298,30 @@ func (sv *XTCPVisitor) makeNatHole() { AssistedAddrs: prepareResult.AssistedAddrs, } - xl.Trace("nathole exchange info start") + xl.Tracef("nathole exchange info start") natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.helper.MsgTransporter(), transactionID, natHoleVisitorMsg, 5*time.Second) if err != nil { listenConn.Close() - xl.Warn("nathole exchange info error: %v", err) + xl.Warnf("nathole exchange info error: %v", err) return } - xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v", + xl.Infof("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v", natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs, natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior) newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.SecretKey)) if err != nil { listenConn.Close() - xl.Warn("make hole error: %v", err) + xl.Warnf("make hole error: %v", err) return } listenConn = newListenConn - xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr) + xl.Infof("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr) if err := sv.session.Init(listenConn, raddr); err != nil { listenConn.Close() - xl.Warn("init tunnel session error: %v", err) + xl.Warnf("init tunnel session error: %v", err) return } } diff --git a/cmd/frpc/main.go b/cmd/frpc/main.go index f9a6c252ad1..f7651bca169 100644 --- a/cmd/frpc/main.go +++ b/cmd/frpc/main.go @@ -17,8 +17,10 @@ package main import ( _ "github.com/fatedier/frp/assets/frpc" "github.com/fatedier/frp/cmd/frpc/sub" + "github.com/fatedier/frp/pkg/util/system" ) func main() { + system.EnableCompatibilityMode() sub.Execute() } diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index 64cac22fff7..b844ddfd5e9 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -46,7 +46,7 @@ func init() { rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc") rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory") rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc") - rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", false, "strict config parsing mode, unknown fields will cause an error") + rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause an errors") } var rootCmd = &cobra.Command{ @@ -136,11 +136,11 @@ func startService( visitorCfgs []v1.VisitorConfigurer, cfgFile string, ) error { - log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor) + log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor) if cfgFile != "" { - log.Info("start frpc service for config file [%s]", cfgFile) - defer log.Info("frpc service for config file [%s] stopped", cfgFile) + log.Infof("start frpc service for config file [%s]", cfgFile) + defer log.Infof("frpc service for config file [%s] stopped", cfgFile) } svr, err := client.NewService(client.ServiceOptions{ Common: cfg, diff --git a/cmd/frps/main.go b/cmd/frps/main.go index 34676a2ba6b..3cb398d8290 100644 --- a/cmd/frps/main.go +++ b/cmd/frps/main.go @@ -15,13 +15,12 @@ package main import ( - "github.com/fatedier/golib/crypto" - _ "github.com/fatedier/frp/assets/frps" _ "github.com/fatedier/frp/pkg/metrics" + "github.com/fatedier/frp/pkg/util/system" ) func main() { - crypto.DefaultSalt = "frp" + system.EnableCompatibilityMode() Execute() } diff --git a/cmd/frps/root.go b/cmd/frps/root.go index cc083518df1..fff487d1074 100644 --- a/cmd/frps/root.go +++ b/cmd/frps/root.go @@ -40,7 +40,7 @@ var ( func init() { rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps") rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps") - rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", false, "strict config parsing mode, unknown fields will cause error") + rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause errors") config.RegisterServerConfigFlags(rootCmd, &serverCfg) } @@ -99,19 +99,19 @@ func Execute() { } func runServer(cfg *v1.ServerConfig) (err error) { - log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor) + log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor) if cfgFile != "" { - log.Info("frps uses config file: %s", cfgFile) + log.Infof("frps uses config file: %s", cfgFile) } else { - log.Info("frps uses command line arguments for config") + log.Infof("frps uses command line arguments for config") } svr, err := server.NewService(cfg) if err != nil { return err } - log.Info("frps started successfully") + log.Infof("frps started successfully") svr.Run(context.Background()) return } diff --git a/conf/frpc_full_example.toml b/conf/frpc_full_example.toml index 59de6fa48e5..0528ddeaf37 100644 --- a/conf/frpc_full_example.toml +++ b/conf/frpc_full_example.toml @@ -216,6 +216,10 @@ healthCheck.path = "/status" healthCheck.intervalSeconds = 10 healthCheck.maxFailed = 3 healthCheck.timeoutSeconds = 3 +# set health check headers +healthCheck.httpHeaders=[ + { name = "x-from-where", value = "frp" } +] [[proxies]] name = "web02" diff --git a/doc/pic/sponsor_daytona.png b/doc/pic/sponsor_daytona.png new file mode 100644 index 00000000000..b00f9327900 Binary files /dev/null and b/doc/pic/sponsor_daytona.png differ diff --git a/go.mod b/go.mod index 49d07fb39c7..d820b96cdd2 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,11 @@ module github.com/fatedier/frp -go 1.21 +go 1.22 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/coreos/go-oidc/v3 v3.6.0 - github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb - github.com/fatedier/golib v0.3.0 + github.com/fatedier/golib v0.4.2 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 @@ -25,7 +24,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.17.1 github.com/xtaci/kcp-go/v5 v5.6.7 - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.18.0 golang.org/x/net v0.19.0 golang.org/x/oauth2 v0.10.0 golang.org/x/sync v0.3.0 @@ -70,7 +69,7 @@ require ( go.uber.org/mock v0.3.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.11.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.9.3 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 2112c7120c7..b00cb78fafd 100644 --- a/go.sum +++ b/go.sum @@ -24,10 +24,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw= -github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk= -github.com/fatedier/golib v0.3.0 h1:xX0QQoYD1ns3IHVDTSysg6gL8buZvGaLyQFvBnWIRF4= -github.com/fatedier/golib v0.3.0/go.mod h1:pmX4FYyp6N4awxDB6jAgIDVOUWuH+QkVP4v30O+iJDM= +github.com/fatedier/golib v0.4.2 h1:k+ZBdUFTTipnP1RHfEhGbzyShRdz/rZtFGnjpXG9D9c= +github.com/fatedier/golib v0.4.2/go.mod h1:gpu+1vXxtJ072NYaNsn/YWgojDL8Ap2kFZQtbzT2qkg= github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo= github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= @@ -164,8 +162,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -216,16 +214,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/package.sh b/package.sh index de3ab2fded0..8d16f72a6fe 100755 --- a/package.sh +++ b/package.sh @@ -1,3 +1,6 @@ +#!/bin/sh +set -e + # compile for version make if [ $? -ne 0 ]; then @@ -14,7 +17,7 @@ make -f ./Makefile.cross-compiles rm -rf ./release/packages mkdir -p ./release/packages -os_all='linux windows darwin freebsd' +os_all='linux windows darwin freebsd android' arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64' cd ./release diff --git a/pkg/config/load.go b/pkg/config/load.go index cdbb8e916f4..f9a705eb213 100644 --- a/pkg/config/load.go +++ b/pkg/config/load.go @@ -80,7 +80,10 @@ func DetectLegacyINIFormatFromFile(path string) bool { } func RenderWithTemplate(in []byte, values *Values) ([]byte, error) { - tmpl, err := template.New("frp").Parse(string(in)) + tmpl, err := template.New("frp").Funcs(template.FuncMap{ + "parseNumberRange": parseNumberRange, + "parseNumberRangePair": parseNumberRangePair, + }).Parse(string(in)) if err != nil { return nil, err } diff --git a/pkg/config/template.go b/pkg/config/template.go new file mode 100644 index 00000000000..44bc456dd7b --- /dev/null +++ b/pkg/config/template.go @@ -0,0 +1,52 @@ +// Copyright 2024 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + + "github.com/fatedier/frp/pkg/util/util" +) + +type NumberPair struct { + First int64 + Second int64 +} + +func parseNumberRangePair(firstRangeStr, secondRangeStr string) ([]NumberPair, error) { + firstRangeNumbers, err := util.ParseRangeNumbers(firstRangeStr) + if err != nil { + return nil, err + } + secondRangeNumbers, err := util.ParseRangeNumbers(secondRangeStr) + if err != nil { + return nil, err + } + if len(firstRangeNumbers) != len(secondRangeNumbers) { + return nil, fmt.Errorf("first and second range numbers are not in pairs") + } + pairs := make([]NumberPair, 0, len(firstRangeNumbers)) + for i := 0; i < len(firstRangeNumbers); i++ { + pairs = append(pairs, NumberPair{ + First: firstRangeNumbers[i], + Second: secondRangeNumbers[i], + }) + } + return pairs, nil +} + +func parseNumberRange(firstRangeStr string) ([]int64, error) { + return util.ParseRangeNumbers(firstRangeStr) +} diff --git a/pkg/config/types/types.go b/pkg/config/types/types.go index fac29d74612..a6cd2e71b0e 100644 --- a/pkg/config/types/types.go +++ b/pkg/config/types/types.go @@ -45,15 +45,6 @@ func NewBandwidthQuantity(s string) (BandwidthQuantity, error) { return q, nil } -func MustBandwidthQuantity(s string) BandwidthQuantity { - q := BandwidthQuantity{} - err := q.UnmarshalString(s) - if err != nil { - panic(err) - } - return q -} - func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool { if q == nil && u == nil { return true diff --git a/pkg/config/v1/common.go b/pkg/config/v1/common.go index 24ec9b0d825..ddb23356823 100644 --- a/pkg/config/v1/common.go +++ b/pkg/config/v1/common.go @@ -129,3 +129,8 @@ type HTTPPluginOptions struct { type HeaderOperations struct { Set map[string]string `json:"set,omitempty"` } + +type HTTPHeader struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/pkg/config/v1/plugin.go b/pkg/config/v1/plugin.go index db9d0d1a0b2..5602a813c3c 100644 --- a/pkg/config/v1/plugin.go +++ b/pkg/config/v1/plugin.go @@ -57,7 +57,7 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error { } if err := decoder.Decode(options); err != nil { - return err + return fmt.Errorf("unmarshal ClientPluginOptions error: %v", err) } c.ClientPluginOptions = options return nil diff --git a/pkg/config/v1/proxy.go b/pkg/config/v1/proxy.go index cf87037ac15..1949cfd34d1 100644 --- a/pkg/config/v1/proxy.go +++ b/pkg/config/v1/proxy.go @@ -97,6 +97,9 @@ type HealthCheckConfig struct { // Path specifies the path to send health checks to if the // health check type is "http". Path string `json:"path,omitempty"` + // HTTPHeaders specifies the headers to send with the health request, if + // the health check type is "http". + HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"` } type DomainConfig struct { @@ -186,7 +189,7 @@ func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error { decoder.DisallowUnknownFields() } if err := decoder.Decode(configurer); err != nil { - return err + return fmt.Errorf("unmarshal ProxyConfig error: %v", err) } c.ProxyConfigurer = configurer return nil diff --git a/pkg/config/v1/visitor.go b/pkg/config/v1/visitor.go index a9b2411ab3d..e9fa166ea57 100644 --- a/pkg/config/v1/visitor.go +++ b/pkg/config/v1/visitor.go @@ -114,7 +114,7 @@ func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error { decoder.DisallowUnknownFields() } if err := decoder.Decode(configurer); err != nil { - return err + return fmt.Errorf("unmarshal VisitorConfig error: %v", err) } c.VisitorConfigurer = configurer return nil diff --git a/pkg/metrics/mem/server.go b/pkg/metrics/mem/server.go index 4f08001b1c4..d92546c4c58 100644 --- a/pkg/metrics/mem/server.go +++ b/pkg/metrics/mem/server.go @@ -62,7 +62,7 @@ func (m *serverMetrics) run() { time.Sleep(12 * time.Hour) start := time.Now() count, total := m.clearUselessInfo(time.Duration(7*24) * time.Hour) - log.Debug("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start)) + log.Debugf("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start)) } }() } @@ -80,7 +80,7 @@ func (m *serverMetrics) clearUselessInfo(continuousOfflineDuration time.Duration time.Since(data.LastCloseTime) > continuousOfflineDuration { delete(m.info.ProxyStatistics, name) count++ - log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String()) + log.Tracef("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String()) } } return count, total diff --git a/pkg/msg/ctl.go b/pkg/msg/ctl.go index 5ccee4a4caf..bf0c71a779b 100644 --- a/pkg/msg/ctl.go +++ b/pkg/msg/ctl.go @@ -42,7 +42,3 @@ func ReadMsgInto(c io.Reader, msg Message) (err error) { func WriteMsg(c io.Writer, msg interface{}) (err error) { return msgCtl.WriteMsg(c, msg) } - -func Pack(msg interface{}) (data []byte, err error) { - return msgCtl.Pack(msg) -} diff --git a/pkg/nathole/controller.go b/pkg/nathole/controller.go index 9c558326566..2eca5929455 100644 --- a/pkg/nathole/controller.go +++ b/pkg/nathole/controller.go @@ -115,7 +115,7 @@ func (c *Controller) CleanWorker(ctx context.Context) { case <-ticker.C: start := time.Now() count, total := c.analyzer.Clean() - log.Trace("clean %d/%d nathole analysis data, cost %v", count, total, time.Since(start)) + log.Tracef("clean %d/%d nathole analysis data, cost %v", count, total, time.Since(start)) case <-ctx.Done(): return } @@ -191,11 +191,11 @@ func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport. return nil }() if err != nil { - log.Warn("handle visitorMsg error: %v", err) + log.Warnf("handle visitorMsg error: %v", err) _ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error())) return } - log.Trace("handle visitor message, sid [%s], server name: %s", sid, m.ProxyName) + log.Tracef("handle visitor message, sid [%s], server name: %s", sid, m.ProxyName) defer func() { c.mu.Lock() @@ -213,14 +213,14 @@ func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport. select { case <-session.notifyCh: case <-time.After(time.Duration(NatHoleTimeout) * time.Second): - log.Debug("wait for NatHoleClient message timeout, sid [%s]", sid) + log.Debugf("wait for NatHoleClient message timeout, sid [%s]", sid) return } // Make hole-punching decisions based on the NAT information of the client and visitor. vResp, cResp, err := c.analysis(session) if err != nil { - log.Debug("sid [%s] analysis error: %v", err) + log.Debugf("sid [%s] analysis error: %v", err) vResp = c.GenNatHoleResponse(session.visitorMsg.TransactionID, nil, err.Error()) cResp = c.GenNatHoleResponse(session.clientMsg.TransactionID, nil, err.Error()) } @@ -257,7 +257,7 @@ func (c *Controller) HandleClient(m *msg.NatHoleClient, transporter transport.Me if !ok { return } - log.Trace("handle client message, sid [%s], server name: %s", session.sid, m.ProxyName) + log.Tracef("handle client message, sid [%s], server name: %s", session.sid, m.ProxyName) session.clientMsg = m session.clientTransporter = transporter select { @@ -271,13 +271,13 @@ func (c *Controller) HandleReport(m *msg.NatHoleReport) { session, ok := c.sessions[m.Sid] c.mu.RUnlock() if !ok { - log.Trace("sid [%s] report make hole success: %v, but session not found", m.Sid, m.Success) + log.Tracef("sid [%s] report make hole success: %v, but session not found", m.Sid, m.Success) return } if m.Success { c.analyzer.ReportSuccess(session.analysisKey, session.recommandMode, session.recommandIndex) } - log.Info("sid [%s] report make hole success: %v, mode %v, index %v", + log.Infof("sid [%s] report make hole success: %v, mode %v, index %v", m.Sid, m.Success, session.recommandMode, session.recommandIndex) } @@ -359,10 +359,10 @@ func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleR }, } - log.Debug("sid [%s] visitor nat: %+v, candidateAddrs: %v; client nat: %+v, candidateAddrs: %v, protocol: %s", + log.Debugf("sid [%s] visitor nat: %+v, candidateAddrs: %v; client nat: %+v, candidateAddrs: %v, protocol: %s", session.sid, *vNatFeature, vm.MappedAddrs, *cNatFeature, cm.MappedAddrs, protocol) - log.Debug("sid [%s] visitor detect behavior: %+v", session.sid, vResp.DetectBehavior) - log.Debug("sid [%s] client detect behavior: %+v", session.sid, cResp.DetectBehavior) + log.Debugf("sid [%s] visitor detect behavior: %+v", session.sid, vResp.DetectBehavior) + log.Debugf("sid [%s] client detect behavior: %+v", session.sid, cResp.DetectBehavior) return vResp, cResp, nil } diff --git a/pkg/nathole/nathole.go b/pkg/nathole/nathole.go index 065d0eb4a6c..bdd0ee83cb2 100644 --- a/pkg/nathole/nathole.go +++ b/pkg/nathole/nathole.go @@ -17,7 +17,7 @@ package nathole import ( "context" "fmt" - "math/rand" + "math/rand/v2" "net" "slices" "strconv" @@ -204,7 +204,7 @@ func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp, for i := 0; i < m.DetectBehavior.ListenRandomPorts; i++ { tmpConn, err := net.ListenUDP("udp4", nil) if err != nil { - xl.Warn("listen random udp addr error: %v", err) + xl.Warnf("listen random udp addr error: %v", err) continue } listenConns = append(listenConns, tmpConn) @@ -216,7 +216,7 @@ func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp, for _, detectAddr := range detectAddrs { for _, conn := range listenConns { if err := sendSidMessage(ctx, conn, m.Sid, transactionID, detectAddr, key, m.DetectBehavior.TTL); err != nil { - xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err) + xl.Tracef("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err) } } } @@ -289,16 +289,16 @@ func waitDetectMessage( if err != nil { return nil, err } - xl.Debug("get udp message local %s, from %s", conn.LocalAddr(), raddr) + xl.Debugf("get udp message local %s, from %s", conn.LocalAddr(), raddr) var m msg.NatHoleSid if err := DecodeMessageInto(buf[:n], key, &m); err != nil { - xl.Warn("decode sid message error: %v", err) + xl.Warnf("decode sid message error: %v", err) continue } pool.PutBuf(buf) if m.Sid != sid { - xl.Warn("get sid message with wrong sid: %s, expect: %s", m.Sid, sid) + xl.Warnf("get sid message with wrong sid: %s, expect: %s", m.Sid, sid) continue } @@ -311,7 +311,7 @@ func waitDetectMessage( m.Response = true buf2, err := EncodeMessage(&m, key) if err != nil { - xl.Warn("encode sid message error: %v", err) + xl.Warnf("encode sid message error: %v", err) continue } _, _ = conn.WriteToUDP(buf2, raddr) @@ -329,7 +329,7 @@ func sendSidMessage( if ttl > 0 { ttlStr = fmt.Sprintf(" with ttl %d", ttl) } - xl.Trace("send sid message from %s to %s%s", conn.LocalAddr(), addr, ttlStr) + xl.Tracef("send sid message from %s to %s%s", conn.LocalAddr(), addr, ttlStr) raddr, err := net.ResolveUDPAddr("udp4", addr) if err != nil { return err @@ -341,7 +341,7 @@ func sendSidMessage( TransactionID: transactionID, Sid: sid, Response: false, - Nonce: strings.Repeat("0", rand.Intn(20)), + Nonce: strings.Repeat("0", rand.IntN(20)), } buf, err := EncodeMessage(m, key) if err != nil { @@ -351,14 +351,14 @@ func sendSidMessage( uConn := ipv4.NewConn(conn) original, err := uConn.TTL() if err != nil { - xl.Trace("get ttl error %v", err) + xl.Tracef("get ttl error %v", err) return err } - xl.Trace("original ttl %d", original) + xl.Tracef("original ttl %d", original) err = uConn.SetTTL(ttl) if err != nil { - xl.Trace("set ttl error %v", err) + xl.Tracef("set ttl error %v", err) } else { defer func() { _ = uConn.SetTTL(original) @@ -382,7 +382,7 @@ func sendSidMessageToRangePorts( for i := portsRange.From; i <= portsRange.To; i++ { detectAddr := net.JoinHostPort(ip, strconv.Itoa(i)) if err := sendFunc(conn, detectAddr); err != nil { - xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err) + xl.Tracef("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err) } time.Sleep(2 * time.Millisecond) } @@ -398,7 +398,7 @@ func sendSidMessageToRandomPorts( used := sets.New[int]() getUnusedPort := func() int { for i := 0; i < 10; i++ { - port := rand.Intn(65535-1024) + 1024 + port := rand.IntN(65535-1024) + 1024 if !used.Has(port) { used.Insert(port) return port @@ -422,7 +422,7 @@ func sendSidMessageToRandomPorts( for _, ip := range slices.Compact(parseIPs(addrs)) { detectAddr := net.JoinHostPort(ip, strconv.Itoa(port)) if err := sendFunc(conn, detectAddr); err != nil { - xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err) + xl.Tracef("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err) } time.Sleep(time.Millisecond * 15) } diff --git a/pkg/plugin/client/http2https.go b/pkg/plugin/client/http2https.go index 7cd3cd4295f..65bc2140b64 100644 --- a/pkg/plugin/client/http2https.go +++ b/pkg/plugin/client/http2https.go @@ -54,6 +54,9 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) { rp := &httputil.ReverseProxy{ Rewrite: func(r *httputil.ProxyRequest) { + r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] + r.Out.Header["X-Forwarded-Host"] = r.In.Header["X-Forwarded-Host"] + r.Out.Header["X-Forwarded-Proto"] = r.In.Header["X-Forwarded-Proto"] req := r.Out req.URL.Scheme = "https" req.URL.Host = p.opts.LocalAddr diff --git a/pkg/plugin/client/https2http.go b/pkg/plugin/client/https2http.go index 389ac849eb8..1dc3840180d 100644 --- a/pkg/plugin/client/https2http.go +++ b/pkg/plugin/client/https2http.go @@ -51,6 +51,8 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) { rp := &httputil.ReverseProxy{ Rewrite: func(r *httputil.ProxyRequest) { + r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] + r.SetXForwarded() req := r.Out req.URL.Scheme = "http" req.URL.Host = p.opts.LocalAddr @@ -98,8 +100,11 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) { return config, nil } -func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { +func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) { wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) + if extra.SrcAddr != nil { + wrapConn.SetRemoteAddr(extra.SrcAddr) + } _ = p.l.PutConn(wrapConn) } diff --git a/pkg/plugin/client/https2https.go b/pkg/plugin/client/https2https.go index da3302b80d2..dd3245f83be 100644 --- a/pkg/plugin/client/https2https.go +++ b/pkg/plugin/client/https2https.go @@ -56,6 +56,8 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) { rp := &httputil.ReverseProxy{ Rewrite: func(r *httputil.ProxyRequest) { + r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] + r.SetXForwarded() req := r.Out req.URL.Scheme = "https" req.URL.Host = p.opts.LocalAddr @@ -104,8 +106,11 @@ func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) { return config, nil } -func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) { +func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) { wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn) + if extra.SrcAddr != nil { + wrapConn.SetRemoteAddr(extra.SrcAddr) + } _ = p.l.PutConn(wrapConn) } diff --git a/pkg/plugin/client/plugin.go b/pkg/plugin/client/plugin.go index 520e379441f..0e5548e9f4e 100644 --- a/pkg/plugin/client/plugin.go +++ b/pkg/plugin/client/plugin.go @@ -50,6 +50,8 @@ func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) { type ExtraInfo struct { ProxyProtocolHeader *pp.Header + SrcAddr net.Addr + DstAddr net.Addr } type Plugin interface { diff --git a/pkg/plugin/server/manager.go b/pkg/plugin/server/manager.go index 516f407e1fd..ed96444ac22 100644 --- a/pkg/plugin/server/manager.go +++ b/pkg/plugin/server/manager.go @@ -86,7 +86,7 @@ func (m *Manager) Login(content *LoginContent) (*LoginContent, error) { for _, p := range m.loginPlugins { res, retContent, err = p.Handle(ctx, OpLogin, *content) if err != nil { - xl.Warn("send Login request to plugin [%s] error: %v", p.Name(), err) + xl.Warnf("send Login request to plugin [%s] error: %v", p.Name(), err) return nil, errors.New("send Login request to plugin error") } if res.Reject { @@ -120,7 +120,7 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) { for _, p := range m.newProxyPlugins { res, retContent, err = p.Handle(ctx, OpNewProxy, *content) if err != nil { - xl.Warn("send NewProxy request to plugin [%s] error: %v", p.Name(), err) + xl.Warnf("send NewProxy request to plugin [%s] error: %v", p.Name(), err) return nil, errors.New("send NewProxy request to plugin error") } if res.Reject { @@ -147,7 +147,7 @@ func (m *Manager) CloseProxy(content *CloseProxyContent) error { for _, p := range m.closeProxyPlugins { _, _, err := p.Handle(ctx, OpCloseProxy, *content) if err != nil { - xl.Warn("send CloseProxy request to plugin [%s] error: %v", p.Name(), err) + xl.Warnf("send CloseProxy request to plugin [%s] error: %v", p.Name(), err) errs = append(errs, fmt.Sprintf("[%s]: %v", p.Name(), err)) } } @@ -179,7 +179,7 @@ func (m *Manager) Ping(content *PingContent) (*PingContent, error) { for _, p := range m.pingPlugins { res, retContent, err = p.Handle(ctx, OpPing, *content) if err != nil { - xl.Warn("send Ping request to plugin [%s] error: %v", p.Name(), err) + xl.Warnf("send Ping request to plugin [%s] error: %v", p.Name(), err) return nil, errors.New("send Ping request to plugin error") } if res.Reject { @@ -213,7 +213,7 @@ func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkConnContent, for _, p := range m.newWorkConnPlugins { res, retContent, err = p.Handle(ctx, OpNewWorkConn, *content) if err != nil { - xl.Warn("send NewWorkConn request to plugin [%s] error: %v", p.Name(), err) + xl.Warnf("send NewWorkConn request to plugin [%s] error: %v", p.Name(), err) return nil, errors.New("send NewWorkConn request to plugin error") } if res.Reject { @@ -247,7 +247,7 @@ func (m *Manager) NewUserConn(content *NewUserConnContent) (*NewUserConnContent, for _, p := range m.newUserConnPlugins { res, retContent, err = p.Handle(ctx, OpNewUserConn, *content) if err != nil { - xl.Info("send NewUserConn request to plugin [%s] error: %v", p.Name(), err) + xl.Infof("send NewUserConn request to plugin [%s] error: %v", p.Name(), err) return nil, errors.New("send NewUserConn request to plugin error") } if res.Reject { diff --git a/pkg/ssh/gateway.go b/pkg/ssh/gateway.go index 90f2228ec18..5716f04038d 100644 --- a/pkg/ssh/gateway.go +++ b/pkg/ssh/gateway.go @@ -75,7 +75,7 @@ func NewGateway( sshConfig.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { authorizedKeysMap, err := loadAuthorizedKeysFromFile(cfg.AuthorizedKeysFile) if err != nil { - log.Error("load authorized keys file error: %v", err) + log.Errorf("load authorized keys file error: %v", err) return nil, fmt.Errorf("internal error") } @@ -120,7 +120,7 @@ func (g *Gateway) handleConn(conn net.Conn) { return } if err := ts.Run(); err != nil { - log.Error("ssh tunnel server run error: %v", err) + log.Errorf("ssh tunnel server run error: %v", err) } } diff --git a/pkg/ssh/server.go b/pkg/ssh/server.go index 7fb93c348c0..2adab17fb5a 100644 --- a/pkg/ssh/server.go +++ b/pkg/ssh/server.go @@ -123,7 +123,7 @@ func (s *TunnelServer) Run() error { // join workConn and ssh channel c, err := s.openConn(addr) if err != nil { - log.Trace("open conn error: %v", err) + log.Tracef("open conn error: %v", err) workConn.Close() return false } @@ -167,7 +167,7 @@ func (s *TunnelServer) Run() error { if ps, err := s.waitProxyStatusReady(pc.GetBaseConfig().Name, time.Second); err != nil { s.writeToClient(err.Error()) - log.Warn("wait proxy status ready error: %v", err) + log.Warnf("wait proxy status ready error: %v", err) } else { // success s.writeToClient(createSuccessInfo(clientCfg.User, pc, ps)) @@ -175,7 +175,7 @@ func (s *TunnelServer) Run() error { } s.vc.Close() - log.Trace("ssh tunnel connection from %v closed", sshConn.RemoteAddr()) + log.Tracef("ssh tunnel connection from %v closed", sshConn.RemoteAddr()) s.closeDoneChOnce.Do(func() { _ = sshConn.Close() close(s.doneCh) diff --git a/pkg/util/log/log.go b/pkg/util/log/log.go index a1391b3260a..f6399e00809 100644 --- a/pkg/util/log/log.go +++ b/pkg/util/log/log.go @@ -15,78 +15,65 @@ package log import ( - "fmt" + "os" - "github.com/fatedier/beego/logs" + "github.com/fatedier/golib/log" ) -// Log is the under log object -var Log *logs.BeeLogger +var Logger *log.Logger func init() { - Log = logs.NewLogger(200) - Log.EnableFuncCallDepth(true) - Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1) + Logger = log.New( + log.WithCaller(true), + log.AddCallerSkip(1), + log.WithLevel(log.InfoLevel), + ) } -func InitLog(logFile string, logLevel string, maxdays int64, disableLogColor bool) { - SetLogFile(logFile, maxdays, disableLogColor) - SetLogLevel(logLevel) -} - -// SetLogFile to configure log params -func SetLogFile(logFile string, maxdays int64, disableLogColor bool) { - if logFile == "console" { - params := "" - if disableLogColor { - params = `{"color": false}` +func InitLogger(logPath string, levelStr string, maxDays int, disableLogColor bool) { + options := []log.Option{} + if logPath == "console" { + if !disableLogColor { + options = append(options, + log.WithOutput(log.NewConsoleWriter(log.ConsoleConfig{ + Colorful: true, + }, os.Stdout)), + ) } - _ = Log.SetLogger("console", params) } else { - params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays) - _ = Log.SetLogger("file", params) + writer := log.NewRotateFileWriter(log.RotateFileConfig{ + FileName: logPath, + Mode: log.RotateFileModeDaily, + MaxDays: maxDays, + }) + writer.Init() + options = append(options, log.WithOutput(writer)) } -} -// SetLogLevel set log level, default is warning -// value: error, warning, info, debug, trace -func SetLogLevel(logLevel string) { - var level int - switch logLevel { - case "error": - level = 3 - case "warn": - level = 4 - case "info": - level = 6 - case "debug": - level = 7 - case "trace": - level = 8 - default: - level = 4 // warning + level, err := log.ParseLevel(levelStr) + if err != nil { + level = log.InfoLevel } - Log.SetLevel(level) + options = append(options, log.WithLevel(level)) + Logger = Logger.WithOptions(options...) } -// wrap log - -func Error(format string, v ...interface{}) { - Log.Error(format, v...) +func Errorf(format string, v ...interface{}) { + Logger.Errorf(format, v...) } -func Warn(format string, v ...interface{}) { - Log.Warn(format, v...) +func Warnf(format string, v ...interface{}) { + Logger.Warnf(format, v...) } -func Info(format string, v ...interface{}) { - Log.Info(format, v...) +func Infof(format string, v ...interface{}) { + Logger.Infof(format, v...) } -func Debug(format string, v ...interface{}) { - Log.Debug(format, v...) +func Debugf(format string, v ...interface{}) { + Logger.Debugf(format, v...) } -func Trace(format string, v ...interface{}) { - Log.Trace(format, v...) +func Tracef(format string, v ...interface{}) { + Logger.Tracef(format, v...) } diff --git a/pkg/util/net/conn.go b/pkg/util/net/conn.go index a5bbe7371c5..20ac73ed1c6 100644 --- a/pkg/util/net/conn.go +++ b/pkg/util/net/conn.go @@ -76,9 +76,11 @@ type WrapReadWriteCloserConn struct { io.ReadWriteCloser underConn net.Conn + + remoteAddr net.Addr } -func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) net.Conn { +func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) *WrapReadWriteCloserConn { return &WrapReadWriteCloserConn{ ReadWriteCloser: rwc, underConn: underConn, @@ -92,7 +94,14 @@ func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr { return (*net.TCPAddr)(nil) } +func (conn *WrapReadWriteCloserConn) SetRemoteAddr(addr net.Addr) { + conn.remoteAddr = addr +} + func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr { + if conn.remoteAddr != nil { + return conn.remoteAddr + } if conn.underConn != nil { return conn.underConn.RemoteAddr() } diff --git a/pkg/util/net/dns.go b/pkg/util/net/dns.go index 5e1d5ccbfc1..441b1e67a3f 100644 --- a/pkg/util/net/dns.go +++ b/pkg/util/net/dns.go @@ -26,8 +26,8 @@ func SetDefaultDNSAddress(dnsAddress string) { // Change default dns server net.DefaultResolver = &net.Resolver{ PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - return net.Dial("udp", dnsAddress) + Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { + return net.Dial(network, dnsAddress) }, } } diff --git a/pkg/util/net/http.go b/pkg/util/net/http.go index 642d15901e3..e9fc52609fc 100644 --- a/pkg/util/net/http.go +++ b/pkg/util/net/http.go @@ -24,30 +24,6 @@ import ( "github.com/fatedier/frp/pkg/util/util" ) -type HTTPAuthWrapper struct { - h http.Handler - user string - passwd string -} - -func NewHTTPBasicAuthWrapper(h http.Handler, user, passwd string) http.Handler { - return &HTTPAuthWrapper{ - h: h, - user: user, - passwd: passwd, - } -} - -func (aw *HTTPAuthWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { - user, passwd, hasAuth := r.BasicAuth() - if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) { - aw.h.ServeHTTP(w, r) - } else { - w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - } -} - type HTTPAuthMiddleware struct { user string passwd string diff --git a/pkg/util/net/websocket.go b/pkg/util/net/websocket.go index 6a8d369c4a9..e642be06f3c 100644 --- a/pkg/util/net/websocket.go +++ b/pkg/util/net/websocket.go @@ -4,7 +4,6 @@ import ( "errors" "net" "net/http" - "strconv" "golang.org/x/net/websocket" ) @@ -50,15 +49,6 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { return } -func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) { - tcpLn, err := net.Listen("tcp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort))) - if err != nil { - return nil, err - } - l := NewWebsocketListener(tcpLn) - return l, nil -} - func (p *WebsocketListener) Accept() (net.Conn, error) { c, ok := <-p.acceptCh if !ok { diff --git a/pkg/util/system/system.go b/pkg/util/system/system.go new file mode 100644 index 00000000000..ae40e85e214 --- /dev/null +++ b/pkg/util/system/system.go @@ -0,0 +1,22 @@ +// Copyright 2024 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !android + +package system + +// EnableCompatibilityMode enables compatibility mode for different system. +// For example, on Android, the inability to obtain the correct time zone will result in incorrect log time output. +func EnableCompatibilityMode() { +} diff --git a/pkg/util/system/system_android.go b/pkg/util/system/system_android.go new file mode 100644 index 00000000000..bfcf401dfb5 --- /dev/null +++ b/pkg/util/system/system_android.go @@ -0,0 +1,66 @@ +// Copyright 2024 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package system + +import ( + "context" + "net" + "os/exec" + "strings" + "time" +) + +func EnableCompatibilityMode() { + fixTimezone() + fixDNSResolver() +} + +// fixTimezone is used to try our best to fix timezone issue on some Android devices. +func fixTimezone() { + out, err := exec.Command("/system/bin/getprop", "persist.sys.timezone").Output() + if err != nil { + return + } + loc, err := time.LoadLocation(strings.TrimSpace(string(out))) + if err != nil { + return + } + time.Local = loc +} + +// fixDNSResolver will first attempt to resolve google.com to check if the current DNS is available. +// If it is not available, it will default to using 8.8.8.8 as the DNS server. +// This is a workaround for the issue that golang can't get the default DNS servers on Android. +func fixDNSResolver() { + // First, we attempt to resolve a domain. If resolution is successful, no modifications are necessary. + // In real-world scenarios, users may have already configured /etc/resolv.conf, or compiled directly + // in the Android environment instead of using cross-platform compilation, so this issue does not arise. + if net.DefaultResolver != nil { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, err := net.DefaultResolver.LookupHost(timeoutCtx, "google.com") + if err == nil { + return + } + } + // If the resolution fails, use 8.8.8.8 as the DNS server. + // Note: If there are other methods to obtain the default DNS servers, the default DNS servers should be used preferentially. + net.DefaultResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { + return net.Dial(network, "8.8.8.8:53") + }, + } +} diff --git a/pkg/util/util/util.go b/pkg/util/util/util.go index d2562b01a27..7758054d948 100644 --- a/pkg/util/util/util.go +++ b/pkg/util/util/util.go @@ -20,7 +20,7 @@ import ( "crypto/subtle" "encoding/hex" "fmt" - mathrand "math/rand" + mathrand "math/rand/v2" "net" "strconv" "strings" @@ -47,16 +47,6 @@ func RandIDWithLen(idLen int) (id string, err error) { return id[:idLen], nil } -// RandIDWithRandLen return a rand string with length between [start, end). -func RandIDWithRandLen(start, end int) (id string, err error) { - if start >= end { - err = fmt.Errorf("start should be less than end") - return - } - idLen := mathrand.Intn(end-start) + start - return RandIDWithLen(idLen) -} - func GetAuthKey(token string, timestamp int64) (key string) { md5Ctx := md5.New() md5Ctx.Write([]byte(token)) @@ -134,7 +124,7 @@ func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Durati if max <= min { n = min } else { - n = mathrand.Int63n(max-min) + min + n = mathrand.Int64N(max-min) + min } d := duration * time.Duration(n) / time.Duration(1000) time.Sleep(d) diff --git a/pkg/util/util/util_test.go b/pkg/util/util/util_test.go index cf76485b281..0061861119e 100644 --- a/pkg/util/util/util_test.go +++ b/pkg/util/util/util_test.go @@ -14,48 +14,6 @@ func TestRandId(t *testing.T) { assert.Equal(16, len(id)) } -func TestRandIDWithRandLen(t *testing.T) { - tests := []struct { - name string - start int - end int - expectErr bool - }{ - { - name: "start and end are equal", - start: 5, - end: 5, - expectErr: true, - }, - { - name: "start is less than end", - start: 5, - end: 10, - expectErr: false, - }, - { - name: "start is greater than end", - start: 10, - end: 5, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - id, err := RandIDWithRandLen(tt.start, tt.end) - if tt.expectErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.GreaterOrEqual(len(id), tt.start) - assert.Less(len(id), tt.end) - } - }) - } -} - func TestGetAuthKey(t *testing.T) { assert := assert.New(t) key := GetAuthKey("1234", 1488720000) diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index 1dc5904c0e4..90af7813120 100644 --- a/pkg/util/version/version.go +++ b/pkg/util/version/version.go @@ -14,34 +14,8 @@ package version -import ( - "strconv" - "strings" -) - -var version = "0.54.0" +var version = "0.56.0" func Full() string { return version } - -func getSubVersion(v string, position int) int64 { - arr := strings.Split(v, ".") - if len(arr) < 3 { - return 0 - } - res, _ := strconv.ParseInt(arr[position], 10, 64) - return res -} - -func Proto(v string) int64 { - return getSubVersion(v, 0) -} - -func Major(v string) int64 { - return getSubVersion(v, 1) -} - -func Minor(v string) int64 { - return getSubVersion(v, 2) -} diff --git a/pkg/util/version/version_test.go b/pkg/util/version/version_test.go deleted file mode 100644 index 2b4077cf33b..00000000000 --- a/pkg/util/version/version_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2016 fatedier, fatedier@gmail.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package version - -import ( - "fmt" - "strconv" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFull(t *testing.T) { - assert := assert.New(t) - version := Full() - arr := strings.Split(version, ".") - assert.Equal(3, len(arr)) - - proto, err := strconv.ParseInt(arr[0], 10, 64) - assert.NoError(err) - assert.True(proto >= 0) - - major, err := strconv.ParseInt(arr[1], 10, 64) - assert.NoError(err) - assert.True(major >= 0) - - minor, err := strconv.ParseInt(arr[2], 10, 64) - assert.NoError(err) - assert.True(minor >= 0) -} - -func TestVersion(t *testing.T) { - assert := assert.New(t) - proto := Proto(Full()) - major := Major(Full()) - minor := Minor(Full()) - parseVersion := fmt.Sprintf("%d.%d.%d", proto, major, minor) - version := Full() - assert.Equal(parseVersion, version) -} diff --git a/pkg/util/vhost/http.go b/pkg/util/vhost/http.go index 7aa67c98f36..7afc7ebbf19 100644 --- a/pkg/util/vhost/http.go +++ b/pkg/util/vhost/http.go @@ -20,7 +20,7 @@ import ( "encoding/base64" "errors" "fmt" - "log" + stdlog "log" "net" "net/http" "net/http/httputil" @@ -32,7 +32,7 @@ import ( "github.com/fatedier/golib/pool" httppkg "github.com/fatedier/frp/pkg/util/http" - logpkg "github.com/fatedier/frp/pkg/util/log" + "github.com/fatedier/frp/pkg/util/log" ) var ErrNoRouteFound = errors.New("no route found") @@ -59,6 +59,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) * proxy := &httputil.ReverseProxy{ // Modify incoming requests by route policies. Rewrite: func(r *httputil.ProxyRequest) { + r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] r.SetXForwarded() req := r.Out req.URL.Scheme = "http" @@ -76,7 +77,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) * // ignore error here, it will use CreateConnFn instead later endpoint, _ = rc.ChooseEndpointFn() reqRouteInfo.Endpoint = endpoint - logpkg.Trace("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]", + log.Tracef("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]", endpoint, oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser) } // Set {domain}.{location}.{routeByHTTPUser}.{endpoint} as URL host here to let http transport reuse connections. @@ -116,9 +117,9 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) * }, }, BufferPool: newWrapPool(), - ErrorLog: log.New(newWrapLogger(), "", 0), + ErrorLog: stdlog.New(newWrapLogger(), "", 0), ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) { - logpkg.Warn("do http proxy request [host: %s] error: %v", req.Host, err) + log.Warnf("do http proxy request [host: %s] error: %v", req.Host, err) rw.WriteHeader(http.StatusNotFound) _, _ = rw.Write(getNotFoundPageContent()) }, @@ -145,7 +146,7 @@ func (rp *HTTPReverseProxy) UnRegister(routeCfg RouteConfig) { func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig { vr, ok := rp.getVhost(domain, location, routeByHTTPUser) if ok { - logpkg.Debug("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser) + log.Debugf("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser) return vr.payload.(*RouteConfig) } return nil @@ -335,6 +336,6 @@ type wrapLogger struct{} func newWrapLogger() *wrapLogger { return &wrapLogger{} } func (l *wrapLogger) Write(p []byte) (n int, err error) { - logpkg.Warn("%s", string(bytes.TrimRight(p, "\n"))) + log.Warnf("%s", string(bytes.TrimRight(p, "\n"))) return len(p), nil } diff --git a/pkg/util/vhost/resource.go b/pkg/util/vhost/resource.go index bf91e13358f..a65e29973ea 100644 --- a/pkg/util/vhost/resource.go +++ b/pkg/util/vhost/resource.go @@ -20,7 +20,7 @@ import ( "net/http" "os" - logpkg "github.com/fatedier/frp/pkg/util/log" + "github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/pkg/util/version" ) @@ -58,7 +58,7 @@ func getNotFoundPageContent() []byte { if NotFoundPagePath != "" { buf, err = os.ReadFile(NotFoundPagePath) if err != nil { - logpkg.Warn("read custom 404 page error: %v", err) + log.Warnf("read custom 404 page error: %v", err) buf = []byte(NotFound) } } else { diff --git a/pkg/util/vhost/vhost.go b/pkg/util/vhost/vhost.go index d529e4249e3..b1370599a70 100644 --- a/pkg/util/vhost/vhost.go +++ b/pkg/util/vhost/vhost.go @@ -203,7 +203,7 @@ func (v *Muxer) handle(c net.Conn) { sConn, reqInfoMap, err := v.vhostFunc(c) if err != nil { - log.Debug("get hostname from http/https request error: %v", err) + log.Debugf("get hostname from http/https request error: %v", err) _ = c.Close() return } @@ -213,7 +213,7 @@ func (v *Muxer) handle(c net.Conn) { httpUser := reqInfoMap["HTTPUser"] l, ok := v.getListener(name, path, httpUser) if !ok { - log.Debug("http request for host [%s] path [%s] httpUser [%s] not found", name, path, httpUser) + log.Debugf("http request for host [%s] path [%s] httpUser [%s] not found", name, path, httpUser) v.failHook(sConn) return } @@ -221,7 +221,7 @@ func (v *Muxer) handle(c net.Conn) { xl := xlog.FromContextSafe(l.ctx) if v.successHook != nil { if err := v.successHook(c, reqInfoMap); err != nil { - xl.Info("success func failure on vhost connection: %v", err) + xl.Infof("success func failure on vhost connection: %v", err) _ = c.Close() return } @@ -232,7 +232,7 @@ func (v *Muxer) handle(c net.Conn) { if l.mux.checkAuth != nil && l.username != "" { ok, err := l.mux.checkAuth(c, l.username, l.password, reqInfoMap) if !ok || err != nil { - xl.Debug("auth failed for user: %s", l.username) + xl.Debugf("auth failed for user: %s", l.username) _ = c.Close() return } @@ -244,12 +244,12 @@ func (v *Muxer) handle(c net.Conn) { } c = sConn - xl.Debug("new request host [%s] path [%s] httpUser [%s]", name, path, httpUser) + xl.Debugf("new request host [%s] path [%s] httpUser [%s]", name, path, httpUser) err = errors.PanicToError(func() { l.accept <- c }) if err != nil { - xl.Warn("listener is already closed, ignore this request") + xl.Warnf("listener is already closed, ignore this request") } } @@ -278,10 +278,10 @@ func (l *Listener) Accept() (net.Conn, error) { if l.mux.rewriteHost != nil { sConn, err := l.mux.rewriteHost(conn, l.rewriteHost) if err != nil { - xl.Warn("host header rewrite failed: %v", err) + xl.Warnf("host header rewrite failed: %v", err) return nil, fmt.Errorf("host header rewrite failed") } - xl.Debug("rewrite host to [%s] success", l.rewriteHost) + xl.Debugf("rewrite host to [%s] success", l.rewriteHost) conn = sConn } return netpkg.NewContextConn(l.ctx, conn), nil diff --git a/pkg/util/wait/backoff.go b/pkg/util/wait/backoff.go index e07c5316ff6..4d01ace328c 100644 --- a/pkg/util/wait/backoff.go +++ b/pkg/util/wait/backoff.go @@ -15,8 +15,7 @@ package wait import ( - "math/rand" - "sync" + "math/rand/v2" "time" "github.com/fatedier/frp/pkg/util/util" @@ -174,21 +173,3 @@ func Until(f func(), period time.Duration, stopCh <-chan struct{}) { return period }), true, stopCh) } - -func MergeAndCloseOnAnyStopChannel[T any](upstreams ...<-chan T) <-chan T { - out := make(chan T) - closeOnce := sync.Once{} - for _, upstream := range upstreams { - ch := upstream - go func() { - select { - case <-ch: - closeOnce.Do(func() { - close(out) - }) - case <-out: - } - }() - } - return out -} diff --git a/pkg/util/xlog/xlog.go b/pkg/util/xlog/xlog.go index 56b07522eb7..184c0c1f7a5 100644 --- a/pkg/util/xlog/xlog.go +++ b/pkg/util/xlog/xlog.go @@ -94,22 +94,22 @@ func (l *Logger) Spawn() *Logger { return nl } -func (l *Logger) Error(format string, v ...interface{}) { - log.Log.Error(l.prefixString+format, v...) +func (l *Logger) Errorf(format string, v ...interface{}) { + log.Logger.Errorf(l.prefixString+format, v...) } -func (l *Logger) Warn(format string, v ...interface{}) { - log.Log.Warn(l.prefixString+format, v...) +func (l *Logger) Warnf(format string, v ...interface{}) { + log.Logger.Warnf(l.prefixString+format, v...) } -func (l *Logger) Info(format string, v ...interface{}) { - log.Log.Info(l.prefixString+format, v...) +func (l *Logger) Infof(format string, v ...interface{}) { + log.Logger.Infof(l.prefixString+format, v...) } -func (l *Logger) Debug(format string, v ...interface{}) { - log.Log.Debug(l.prefixString+format, v...) +func (l *Logger) Debugf(format string, v ...interface{}) { + log.Logger.Debugf(l.prefixString+format, v...) } -func (l *Logger) Trace(format string, v ...interface{}) { - log.Log.Trace(l.prefixString+format, v...) +func (l *Logger) Tracef(format string, v ...interface{}) { + log.Logger.Tracef(l.prefixString+format, v...) } diff --git a/server/control.go b/server/control.go index dbb1af0a038..ea8a34c17ef 100644 --- a/server/control.go +++ b/server/control.go @@ -224,7 +224,7 @@ func (ctl *Control) Close() error { func (ctl *Control) Replaced(newCtl *Control) { xl := ctl.xl - xl.Info("Replaced by client [%s]", newCtl.runID) + xl.Infof("Replaced by client [%s]", newCtl.runID) ctl.runID = "" ctl.conn.Close() } @@ -233,17 +233,17 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) error { xl := ctl.xl defer func() { if err := recover(); err != nil { - xl.Error("panic error: %v", err) - xl.Error(string(debug.Stack())) + xl.Errorf("panic error: %v", err) + xl.Errorf(string(debug.Stack())) } }() select { case ctl.workConnCh <- conn: - xl.Debug("new work connection registered") + xl.Debugf("new work connection registered") return nil default: - xl.Debug("work connection pool is full, discarding") + xl.Debugf("work connection pool is full, discarding") return fmt.Errorf("work connection pool is full, discarding") } } @@ -256,8 +256,8 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { xl := ctl.xl defer func() { if err := recover(); err != nil { - xl.Error("panic error: %v", err) - xl.Error(string(debug.Stack())) + xl.Errorf("panic error: %v", err) + xl.Errorf(string(debug.Stack())) } }() @@ -269,7 +269,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { err = pkgerr.ErrCtlClosed return } - xl.Debug("get work connection from pool") + xl.Debugf("get work connection from pool") default: // no work connections available in the poll, send message to frpc to get more if err := ctl.msgDispatcher.Send(&msg.ReqWorkConn{}); err != nil { @@ -280,13 +280,13 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { case workConn, ok = <-ctl.workConnCh: if !ok { err = pkgerr.ErrCtlClosed - xl.Warn("no work connections available, %v", err) + xl.Warnf("no work connections available, %v", err) return } case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second): err = fmt.Errorf("timeout trying to get work connection") - xl.Warn("%v", err) + xl.Warnf("%v", err) return } } @@ -305,7 +305,7 @@ func (ctl *Control) heartbeatWorker() { if !lo.FromPtr(ctl.serverCfg.Transport.TCPMux) && ctl.serverCfg.Transport.HeartbeatTimeout > 0 { go wait.Until(func() { if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second { - xl.Warn("heartbeat timeout") + xl.Warnf("heartbeat timeout") ctl.conn.Close() return } @@ -356,7 +356,7 @@ func (ctl *Control) worker() { } metrics.Server.CloseClient() - xl.Info("client exit success") + xl.Infof("client exit success") close(ctl.doneCh) } @@ -393,12 +393,12 @@ func (ctl *Control) handleNewProxy(m msg.Message) { ProxyName: inMsg.ProxyName, } if err != nil { - xl.Warn("new proxy [%s] type [%s] error: %v", inMsg.ProxyName, inMsg.ProxyType, err) + xl.Warnf("new proxy [%s] type [%s] error: %v", inMsg.ProxyName, inMsg.ProxyType, err) resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", inMsg.ProxyName), err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)) } else { resp.RemoteAddr = remoteAddr - xl.Info("new proxy [%s] type [%s] success", inMsg.ProxyName, inMsg.ProxyType) + xl.Infof("new proxy [%s] type [%s] success", inMsg.ProxyName, inMsg.ProxyType) metrics.Server.NewProxy(inMsg.ProxyName, inMsg.ProxyType) } _ = ctl.msgDispatcher.Send(resp) @@ -422,14 +422,14 @@ func (ctl *Control) handlePing(m msg.Message) { err = ctl.authVerifier.VerifyPing(inMsg) } if err != nil { - xl.Warn("received invalid ping: %v", err) + xl.Warnf("received invalid ping: %v", err) _ = ctl.msgDispatcher.Send(&msg.Pong{ Error: util.GenerateResponseErrorString("invalid ping", err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)), }) return } ctl.lastPing.Store(time.Now()) - xl.Debug("receive heartbeat") + xl.Debugf("receive heartbeat") _ = ctl.msgDispatcher.Send(&msg.Pong{}) } @@ -452,7 +452,7 @@ func (ctl *Control) handleCloseProxy(m msg.Message) { xl := ctl.xl inMsg := m.(*msg.CloseProxy) _ = ctl.CloseProxy(inMsg) - xl.Info("close proxy [%s] success", inMsg.ProxyName) + xl.Infof("close proxy [%s] success", inMsg.ProxyName) } func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { diff --git a/server/dashboard_api.go b/server/dashboard_api.go index 9ecd5a7c265..62415c968d2 100644 --- a/server/dashboard_api.go +++ b/server/dashboard_api.go @@ -99,14 +99,14 @@ func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) { func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) { res := GeneralResponse{Code: 200} defer func() { - log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) } }() - log.Info("Http request: [%s]", r.URL.Path) + log.Infof("Http request: [%s]", r.URL.Path) serverStats := mem.StatsCollector.GetServer() svrResp := serverInfoResp{ Version: version.Full(), @@ -219,13 +219,13 @@ func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) { proxyType := params["type"] defer func() { - log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) } }() - log.Info("Http request: [%s]", r.URL.Path) + log.Infof("Http request: [%s]", r.URL.Path) proxyInfoResp := GetProxyInfoResp{} proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType) @@ -245,12 +245,12 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok { content, err := json.Marshal(pxy.GetConfigurer()) if err != nil { - log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err) + log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err) continue } proxyInfo.Conf = getConfByType(ps.Type) if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil { - log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err) + log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err) continue } proxyInfo.Status = "online" @@ -291,13 +291,13 @@ func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request name := params["name"] defer func() { - log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) } }() - log.Info("Http request: [%s]", r.URL.Path) + log.Infof("Http request: [%s]", r.URL.Path) var proxyStatsResp GetProxyStatsResp proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name) @@ -319,14 +319,14 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin if pxy, ok := svr.pxyManager.GetByName(proxyName); ok { content, err := json.Marshal(pxy.GetConfigurer()) if err != nil { - log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err) + log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err) code = 400 msg = "parse conf error" return } proxyInfo.Conf = getConfByType(ps.Type) if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil { - log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err) + log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err) code = 400 msg = "parse conf error" return @@ -359,13 +359,13 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) { name := params["name"] defer func() { - log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) } }() - log.Info("Http request: [%s]", r.URL.Path) + log.Infof("Http request: [%s]", r.URL.Path) trafficResp := GetProxyTrafficResp{} trafficResp.Name = name @@ -387,9 +387,9 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) { func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) { res := GeneralResponse{Code: 200} - log.Info("Http request: [%s]", r.URL.Path) + log.Infof("Http request: [%s]", r.URL.Path) defer func() { - log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) @@ -403,5 +403,5 @@ func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) { return } cleared, total := mem.StatsCollector.ClearOfflineProxies() - log.Info("cleared [%d] offline proxies, total [%d] proxies", cleared, total) + log.Infof("cleared [%d] offline proxies, total [%d] proxies", cleared, total) } diff --git a/server/proxy/http.go b/server/proxy/http.go index 44a462b7ecc..cd4c4b968bc 100644 --- a/server/proxy/http.go +++ b/server/proxy/http.go @@ -107,7 +107,7 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) { }) } addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPPort)) - xl.Info("http proxy listen for host [%s] location [%s] group [%s], routeByHTTPUser [%s]", + xl.Infof("http proxy listen for host [%s] location [%s] group [%s], routeByHTTPUser [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.LoadBalancer.Group, pxy.cfg.RouteByHTTPUser) } } @@ -140,7 +140,7 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) { } addrs = append(addrs, util.CanonicalAddr(tmpRouteConfig.Domain, pxy.serverCfg.VhostHTTPPort)) - xl.Info("http proxy listen for host [%s] location [%s] group [%s], routeByHTTPUser [%s]", + xl.Infof("http proxy listen for host [%s] location [%s] group [%s], routeByHTTPUser [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.LoadBalancer.Group, pxy.cfg.RouteByHTTPUser) } } @@ -152,7 +152,7 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err xl := pxy.xl rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr) if errRet != nil { - xl.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet) + xl.Warnf("resolve TCP addr [%s] error: %v", remoteAddr, errRet) // we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled } @@ -166,7 +166,7 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err if pxy.cfg.Transport.UseEncryption { rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token)) if err != nil { - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return } } diff --git a/server/proxy/https.go b/server/proxy/https.go index dc9b77ebe00..4575ac13d08 100644 --- a/server/proxy/https.go +++ b/server/proxy/https.go @@ -64,7 +64,7 @@ func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) { err = errRet return } - xl.Info("https proxy listen for host [%s]", routeConfig.Domain) + xl.Infof("https proxy listen for host [%s]", routeConfig.Domain) pxy.listeners = append(pxy.listeners, l) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) } @@ -76,7 +76,7 @@ func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) { err = errRet return } - xl.Info("https proxy listen for host [%s]", routeConfig.Domain) + xl.Infof("https proxy listen for host [%s]", routeConfig.Domain) pxy.listeners = append(pxy.listeners, l) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) } diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go index f5c850e98af..d5ab0f13ae5 100644 --- a/server/proxy/proxy.go +++ b/server/proxy/proxy.go @@ -112,7 +112,7 @@ func (pxy *BaseProxy) GetConfigurer() v1.ProxyConfigurer { func (pxy *BaseProxy) Close() { xl := xlog.FromContextSafe(pxy.ctx) - xl.Info("proxy closing") + xl.Infof("proxy closing") for _, l := range pxy.listeners { l.Close() } @@ -125,10 +125,10 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, // try all connections from the pool for i := 0; i < pxy.poolCount+1; i++ { if workConn, err = pxy.getWorkConnFn(); err != nil { - xl.Warn("failed to get work connection: %v", err) + xl.Warnf("failed to get work connection: %v", err) return } - xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String()) + xl.Debugf("get a new work connection: [%s]", workConn.RemoteAddr().String()) xl.Spawn().AppendPrefix(pxy.GetName()) workConn = netpkg.NewContextConn(pxy.ctx, workConn) @@ -158,7 +158,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, Error: "", }) if err != nil { - xl.Warn("failed to send message to work connection from pool: %v, times: %d", err, i) + xl.Warnf("failed to send message to work connection from pool: %v, times: %d", err, i) workConn.Close() } else { break @@ -166,7 +166,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, } if err != nil { - xl.Error("try to get work connection failed in the end") + xl.Errorf("try to get work connection failed in the end") return } return @@ -193,15 +193,15 @@ func (pxy *BaseProxy) startCommonTCPListenersHandler() { if max := 1 * time.Second; tempDelay > max { tempDelay = max } - xl.Info("met temporary error: %s, sleep for %s ...", err, tempDelay) + xl.Infof("met temporary error: %s, sleep for %s ...", err, tempDelay) time.Sleep(tempDelay) continue } - xl.Warn("listener is closed: %s", err) + xl.Warnf("listener is closed: %s", err) return } - xl.Info("get a user connection [%s]", c.RemoteAddr().String()) + xl.Infof("get a user connection [%s]", c.RemoteAddr().String()) go pxy.handleUserTCPConnection(c) } }(listener) @@ -225,7 +225,7 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) { } _, err := rc.PluginManager.NewUserConn(content) if err != nil { - xl.Warn("the user conn [%s] was rejected, err:%v", content.RemoteAddr, err) + xl.Warnf("the user conn [%s] was rejected, err:%v", content.RemoteAddr, err) return } @@ -237,12 +237,12 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) { defer workConn.Close() var local io.ReadWriteCloser = workConn - xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t", + xl.Tracef("handler user tcp connection, use_encryption: %t, use_compression: %t", cfg.Transport.UseEncryption, cfg.Transport.UseCompression) if cfg.Transport.UseEncryption { local, err = libio.WithEncryption(local, []byte(serverCfg.Auth.Token)) if err != nil { - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) return } } @@ -258,7 +258,7 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) { }) } - xl.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(), + xl.Debugf("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(), workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String()) name := pxy.GetName() @@ -268,7 +268,7 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) { metrics.Server.CloseConnection(name, proxyType) metrics.Server.AddTrafficIn(name, proxyType, inCount) metrics.Server.AddTrafficOut(name, proxyType, outCount) - xl.Debug("join connections closed") + xl.Debugf("join connections closed") } type Options struct { diff --git a/server/proxy/stcp.go b/server/proxy/stcp.go index 4f08a7c12c3..06b1b17f47b 100644 --- a/server/proxy/stcp.go +++ b/server/proxy/stcp.go @@ -53,7 +53,7 @@ func (pxy *STCPProxy) Run() (remoteAddr string, err error) { return } pxy.listeners = append(pxy.listeners, listener) - xl.Info("stcp proxy custom listen success") + xl.Infof("stcp proxy custom listen success") pxy.startCommonTCPListenersHandler() return diff --git a/server/proxy/sudp.go b/server/proxy/sudp.go index a6d7c798f1b..f37fb4239fd 100644 --- a/server/proxy/sudp.go +++ b/server/proxy/sudp.go @@ -53,7 +53,7 @@ func (pxy *SUDPProxy) Run() (remoteAddr string, err error) { return } pxy.listeners = append(pxy.listeners, listener) - xl.Info("sudp proxy custom listen success") + xl.Infof("sudp proxy custom listen success") pxy.startCommonTCPListenersHandler() return diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go index 4196ad4ae3a..a6eae3a9d6b 100644 --- a/server/proxy/tcp.go +++ b/server/proxy/tcp.go @@ -62,7 +62,7 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) { }() pxy.realBindPort = realBindPort pxy.listeners = append(pxy.listeners, l) - xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.LoadBalancer.Group) + xl.Infof("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.LoadBalancer.Group) } else { pxy.realBindPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) if err != nil { @@ -79,7 +79,7 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) { return } pxy.listeners = append(pxy.listeners, listener) - xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) + xl.Infof("tcp proxy listen port [%d]", pxy.cfg.RemotePort) } pxy.cfg.RemotePort = pxy.realBindPort diff --git a/server/proxy/tcpmux.go b/server/proxy/tcpmux.go index 5b914c3983d..6e95b66b35b 100644 --- a/server/proxy/tcpmux.go +++ b/server/proxy/tcpmux.go @@ -65,7 +65,7 @@ func (pxy *TCPMuxProxy) httpConnectListen( if err != nil { return nil, err } - pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s], group [%s] routeByHTTPUser [%s]", + pxy.xl.Infof("tcpmux httpconnect multiplexer listens for host [%s], group [%s] routeByHTTPUser [%s]", domain, pxy.cfg.LoadBalancer.Group, pxy.cfg.RouteByHTTPUser) pxy.listeners = append(pxy.listeners, l) return append(addrs, util.CanonicalAddr(domain, pxy.serverCfg.TCPMuxHTTPConnectPort)), nil diff --git a/server/proxy/udp.go b/server/proxy/udp.go index ea970818d8e..53a07d52676 100644 --- a/server/proxy/udp.go +++ b/server/proxy/udp.go @@ -97,10 +97,10 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) { udpConn, errRet := net.ListenUDP("udp", addr) if errRet != nil { err = errRet - xl.Warn("listen udp port error: %v", err) + xl.Warnf("listen udp port error: %v", err) return } - xl.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort) + xl.Infof("udp proxy listen port [%d]", pxy.cfg.RemotePort) pxy.udpConn = udpConn pxy.sendCh = make(chan *msg.UDPPacket, 1024) @@ -114,11 +114,11 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) { rawMsg msg.Message errRet error ) - xl.Trace("loop waiting message from udp workConn") + xl.Tracef("loop waiting message from udp workConn") // client will send heartbeat in workConn for keeping alive _ = conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second)) if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil { - xl.Warn("read from workConn for udp error: %v", errRet) + xl.Warnf("read from workConn for udp error: %v", errRet) _ = conn.Close() // notify proxy to start a new work connection // ignore error here, it means the proxy is closed @@ -128,15 +128,15 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) { return } if err := conn.SetReadDeadline(time.Time{}); err != nil { - xl.Warn("set read deadline error: %v", err) + xl.Warnf("set read deadline error: %v", err) } switch m := rawMsg.(type) { case *msg.Ping: - xl.Trace("udp work conn get ping message") + xl.Tracef("udp work conn get ping message") continue case *msg.UDPPacket: if errRet := errors.PanicToError(func() { - xl.Trace("get udp message from workConn: %s", m.Content) + xl.Tracef("get udp message from workConn: %s", m.Content) pxy.readCh <- m metrics.Server.AddTrafficOut( pxy.GetName(), @@ -145,7 +145,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) { ) }); errRet != nil { conn.Close() - xl.Info("reader goroutine for udp work connection closed") + xl.Infof("reader goroutine for udp work connection closed") return } } @@ -159,15 +159,15 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) { select { case udpMsg, ok := <-pxy.sendCh: if !ok { - xl.Info("sender goroutine for udp work connection closed") + xl.Infof("sender goroutine for udp work connection closed") return } if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil { - xl.Info("sender goroutine for udp work connection closed: %v", errRet) + xl.Infof("sender goroutine for udp work connection closed: %v", errRet) conn.Close() return } - xl.Trace("send message to udp workConn: %s", udpMsg.Content) + xl.Tracef("send message to udp workConn: %s", udpMsg.Content) metrics.Server.AddTrafficIn( pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type, @@ -175,7 +175,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) { ) continue case <-ctx.Done(): - xl.Info("sender goroutine for udp work connection closed") + xl.Infof("sender goroutine for udp work connection closed") return } } @@ -207,7 +207,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) { if pxy.cfg.Transport.UseEncryption { rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token)) if err != nil { - xl.Error("create encryption stream error: %v", err) + xl.Errorf("create encryption stream error: %v", err) workConn.Close() continue } diff --git a/server/proxy/xtcp.go b/server/proxy/xtcp.go index fe60c630c57..f69d0790d65 100644 --- a/server/proxy/xtcp.go +++ b/server/proxy/xtcp.go @@ -77,7 +77,7 @@ func (pxy *XTCPProxy) Run() (remoteAddr string, err error) { } errRet = msg.WriteMsg(workConn, m) if errRet != nil { - xl.Warn("write nat hole sid package error, %v", errRet) + xl.Warnf("write nat hole sid package error, %v", errRet) } workConn.Close() } diff --git a/server/service.go b/server/service.go index c2410b06376..325f7f6cbf3 100644 --- a/server/service.go +++ b/server/service.go @@ -22,9 +22,11 @@ import ( "io" "net" "net/http" + "os" "strconv" "time" + "github.com/fatedier/golib/crypto" "github.com/fatedier/golib/net/mux" fmux "github.com/hashicorp/yamux" quic "github.com/quic-go/quic-go" @@ -59,6 +61,16 @@ const ( vhostReadWriteTimeout time.Duration = 30 * time.Second ) +func init() { + crypto.DefaultSalt = "frp" + // Disable quic-go's receive buffer warning. + os.Setenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING", "true") + // Disable quic-go's ECN support by default. It may cause issues on certain operating systems. + if os.Getenv("QUIC_GO_DISABLE_ECN") == "" { + os.Setenv("QUIC_GO_DISABLE_ECN", "true") + } +} + // Server service type Service struct { // Dispatch connections to different handlers listen on same port @@ -172,13 +184,13 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { if err != nil { return nil, fmt.Errorf("create vhost tcpMuxer error, %v", err) } - log.Info("tcpmux httpconnect multiplexer listen on %s, passthough: %v", address, cfg.TCPMuxPassthrough) + log.Infof("tcpmux httpconnect multiplexer listen on %s, passthough: %v", address, cfg.TCPMuxPassthrough) } // Init all plugins for _, p := range cfg.HTTPPlugins { svr.pluginManager.Register(plugin.NewHTTPPluginOptions(p)) - log.Info("plugin [%s] has been registered", p.Name) + log.Infof("plugin [%s] has been registered", p.Name) } svr.rc.PluginManager = svr.pluginManager @@ -222,7 +234,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { ln = svr.muxer.DefaultListener() svr.listener = ln - log.Info("frps tcp listen on %s", address) + log.Infof("frps tcp listen on %s", address) // Listen for accepting connections from client using kcp protocol. if cfg.KCPBindPort > 0 { @@ -231,7 +243,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { if err != nil { return nil, fmt.Errorf("listen on kcp udp address %s error: %v", address, err) } - log.Info("frps kcp listen on udp %s", address) + log.Infof("frps kcp listen on udp %s", address) } if cfg.QUICBindPort > 0 { @@ -246,7 +258,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { if err != nil { return nil, fmt.Errorf("listen on quic udp address %s error: %v", address, err) } - log.Info("frps quic listen on %s", address) + log.Infof("frps quic listen on %s", address) } if cfg.SSHTunnelGateway.BindPort > 0 { @@ -255,7 +267,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { return nil, fmt.Errorf("create ssh gateway error: %v", err) } svr.sshTunnelGateway = sshGateway - log.Info("frps sshTunnelGateway listen on port %d", cfg.SSHTunnelGateway.BindPort) + log.Infof("frps sshTunnelGateway listen on port %d", cfg.SSHTunnelGateway.BindPort) } // Listen for accepting connections from client using websocket protocol. @@ -289,7 +301,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { go func() { _ = server.Serve(l) }() - log.Info("http service listen on %s", address) + log.Infof("http service listen on %s", address) } // Create https vhost muxer. @@ -303,7 +315,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { if err != nil { return nil, fmt.Errorf("create server listener error, %v", err) } - log.Info("https service listen on %s", address) + log.Infof("https service listen on %s", address) } svr.rc.VhostHTTPSMuxer, err = vhost.NewHTTPSMuxer(l, vhostReadWriteTimeout) @@ -335,9 +347,9 @@ func (svr *Service) Run(ctx context.Context) { // run dashboard web server. if svr.webServer != nil { go func() { - log.Info("dashboard listen on %s", svr.webServer.Address()) + log.Infof("dashboard listen on %s", svr.webServer.Address()) if err := svr.webServer.Run(); err != nil { - log.Warn("dashboard server exit with error: %v", err) + log.Warnf("dashboard server exit with error: %v", err) } }() } @@ -408,7 +420,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna _ = conn.SetReadDeadline(time.Now().Add(connReadTimeout)) if rawMsg, err = msg.ReadMsg(conn); err != nil { - log.Trace("Failed to read message: %v", err) + log.Tracef("Failed to read message: %v", err) conn.Close() return } @@ -430,7 +442,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna // If login failed, send error message there. // Otherwise send success message in control's work goroutine. if err != nil { - xl.Warn("register control error: %v", err) + xl.Warnf("register control error: %v", err) _ = msg.WriteMsg(conn, &msg.LoginResp{ Version: version.Full(), Error: util.GenerateResponseErrorString("register control error", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), @@ -443,7 +455,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna } case *msg.NewVisitorConn: if err = svr.RegisterVisitorConn(conn, m); err != nil { - xl.Warn("register visitor conn error: %v", err) + xl.Warnf("register visitor conn error: %v", err) _ = msg.WriteMsg(conn, &msg.NewVisitorConnResp{ ProxyName: m.ProxyName, Error: util.GenerateResponseErrorString("register visitor conn error", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), @@ -456,7 +468,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna }) } default: - log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String()) + log.Warnf("Error message type for the new connection [%s]", conn.RemoteAddr().String()) conn.Close() } } @@ -469,7 +481,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { for { c, err := l.Accept() if err != nil { - log.Warn("Listener for incoming connections from client closed") + log.Warnf("Listener for incoming connections from client closed") return } // inject xlog object into net.Conn context @@ -479,17 +491,17 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { c = netpkg.NewContextConn(xlog.NewContext(ctx, xl), c) if !internal { - log.Trace("start check TLS connection...") + log.Tracef("start check TLS connection...") originConn := c forceTLS := svr.cfg.Transport.TLS.Force var isTLS, custom bool c, isTLS, custom, err = netpkg.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, forceTLS, connReadTimeout) if err != nil { - log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err) + log.Warnf("CheckAndEnableTLSServerConnWithTimeout error: %v", err) originConn.Close() continue } - log.Trace("check TLS connection success, isTLS: %v custom: %v internal: %v", isTLS, custom, internal) + log.Tracef("check TLS connection success, isTLS: %v custom: %v internal: %v", isTLS, custom, internal) } // Start a new goroutine to handle connection. @@ -501,7 +513,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024 session, err := fmux.Server(frpConn, fmuxCfg) if err != nil { - log.Warn("Failed to create mux connection: %v", err) + log.Warnf("Failed to create mux connection: %v", err) frpConn.Close() return } @@ -509,7 +521,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { for { stream, err := session.AcceptStream() if err != nil { - log.Debug("Accept new mux stream error: %v", err) + log.Debugf("Accept new mux stream error: %v", err) session.Close() return } @@ -527,7 +539,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) { for { c, err := l.Accept(context.Background()) if err != nil { - log.Warn("QUICListener for incoming connections from client closed") + log.Warnf("QUICListener for incoming connections from client closed") return } // Start a new goroutine to handle connection. @@ -535,7 +547,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) { for { stream, err := frpConn.AcceptStream(context.Background()) if err != nil { - log.Debug("Accept new quic mux stream error: %v", err) + log.Debugf("Accept new quic mux stream error: %v", err) _ = frpConn.CloseWithError(0, "") return } @@ -560,7 +572,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter xl := xlog.FromContextSafe(ctx) xl.AppendPrefix(loginMsg.RunID) ctx = xlog.NewContext(ctx, xl) - xl.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]", + xl.Infof("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]", ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch) // Check auth. @@ -575,7 +587,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter // TODO(fatedier): use SessionContext ctl, err := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, authVerifier, ctlConn, !internal, loginMsg, svr.cfg) if err != nil { - xl.Warn("create new controller error: %v", err) + xl.Warnf("create new controller error: %v", err) // don't return detailed errors to client return fmt.Errorf("unexpected error when creating new controller") } @@ -601,7 +613,7 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) xl := netpkg.NewLogFromConn(workConn) ctl, exist := svr.ctlManager.GetByID(newMsg.RunID) if !exist { - xl.Warn("No client control found for run id [%s]", newMsg.RunID) + xl.Warnf("No client control found for run id [%s]", newMsg.RunID) return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID) } // server plugin hook @@ -620,7 +632,7 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) err = ctl.authVerifier.VerifyNewWorkConn(newMsg) } if err != nil { - xl.Warn("invalid NewWorkConn with run id [%s]", newMsg.RunID) + xl.Warnf("invalid NewWorkConn with run id [%s]", newMsg.RunID) _ = msg.WriteMsg(workConn, &msg.StartWorkConn{ Error: util.GenerateResponseErrorString("invalid NewWorkConn", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), }) diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index 30887d2a7dc..994ac59deef 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -38,7 +38,7 @@ func RunE2ETests(t *testing.T) { // Randomize specs as well as suites suiteConfig.RandomizeAllSpecs = true - log.Info("Starting e2e run %q on Ginkgo node %d of total %d", + log.Infof("Starting e2e run %q on Ginkgo node %d of total %d", framework.RunID, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal) ginkgo.RunSpecs(t, "frp e2e suite", suiteConfig, reporterConfig) } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 27c36e0c809..e32f89f083f 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -34,7 +34,7 @@ func TestMain(m *testing.M) { os.Exit(1) } - log.InitLog("console", framework.TestContext.LogLevel, 0, true) + log.InitLogger("console", framework.TestContext.LogLevel, 0, true) os.Exit(m.Run()) } diff --git a/test/e2e/framework/client.go b/test/e2e/framework/client.go index 76614354a48..c13cee10a46 100644 --- a/test/e2e/framework/client.go +++ b/test/e2e/framework/client.go @@ -1,11 +1,9 @@ package framework -type FRPClient struct { - port int -} +import ( + clientsdk "github.com/fatedier/frp/pkg/sdk/client" +) -func (f *Framework) FRPClient(port int) *FRPClient { - return &FRPClient{ - port: port, - } +func (f *Framework) APIClientForFrpc(port int) *clientsdk.Client { + return clientsdk.New("127.0.0.1", port) } diff --git a/test/e2e/framework/expect.go b/test/e2e/framework/expect.go index dab6da70ef0..3c357bb02cd 100644 --- a/test/e2e/framework/expect.go +++ b/test/e2e/framework/expect.go @@ -43,6 +43,10 @@ func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) { gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...) } +func ExpectContainSubstring(actual, substr string, explain ...interface{}) { + gomega.ExpectWithOffset(1, actual).To(gomega.ContainSubstring(substr), explain...) +} + // ExpectConsistOf expects actual contains precisely the extra elements. The ordering of the elements does not matter. func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface{}) { gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...) diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index e0be5af14f9..527096cd1b4 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -217,7 +217,7 @@ func (f *Framework) RenderTemplates(templates []string) (outs []string, ports ma } for _, t := range templates { - tmpl, err := template.New("").Parse(t) + tmpl, err := template.New("frp-e2e").Parse(t) if err != nil { return nil, nil, err } diff --git a/test/e2e/framework/process.go b/test/e2e/framework/process.go index ae89e820078..0845914bf06 100644 --- a/test/e2e/framework/process.go +++ b/test/e2e/framework/process.go @@ -31,7 +31,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str ExpectNoError(err) if TestContext.Debug { - flog.Debug("[%s] %s", path, outs[i]) + flog.Debugf("[%s] %s", path, outs[i]) } p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs) @@ -52,7 +52,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str ExpectNoError(err) if TestContext.Debug { - flog.Debug("[%s] %s", path, outs[index]) + flog.Debugf("[%s] %s", path, outs[index]) } p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs) diff --git a/test/e2e/framework/request.go b/test/e2e/framework/request.go index 6b75a2593ad..1fc67fe86ab 100644 --- a/test/e2e/framework/request.go +++ b/test/e2e/framework/request.go @@ -20,7 +20,7 @@ func ExpectResponseCode(code int) EnsureFunc { if resp.Code == code { return true } - flog.Warn("Expect code %d, but got %d", code, resp.Code) + flog.Warnf("Expect code %d, but got %d", code, resp.Code) return false } } @@ -111,14 +111,14 @@ func (e *RequestExpect) Ensure(fns ...EnsureFunc) { if len(fns) == 0 { if !bytes.Equal(e.expectResp, ret.Content) { - flog.Trace("Response info: %+v", ret) + flog.Tracef("Response info: %+v", ret) } - ExpectEqualValuesWithOffset(1, ret.Content, e.expectResp, e.explain...) + ExpectEqualValuesWithOffset(1, string(ret.Content), string(e.expectResp), e.explain...) } else { for _, fn := range fns { ok := fn(ret) if !ok { - flog.Trace("Response info: %+v", ret) + flog.Tracef("Response info: %+v", ret) } ExpectTrueWithOffset(1, ok, e.explain...) } diff --git a/test/e2e/legacy/basic/client.go b/test/e2e/legacy/basic/client.go index d4862e529dc..98f675b823a 100644 --- a/test/e2e/legacy/basic/client.go +++ b/test/e2e/legacy/basic/client.go @@ -8,7 +8,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/request" @@ -54,7 +53,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(p2Port).Ensure() framework.NewRequestExpect(f).Port(p3Port).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) conf, err := client.GetConfig() framework.ExpectNoError(err) @@ -120,7 +119,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(testPort).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) err := client.Stop() framework.ExpectNoError(err) diff --git a/test/e2e/legacy/basic/server.go b/test/e2e/legacy/basic/server.go index ca6717c639a..62bfd62ae71 100644 --- a/test/e2e/legacy/basic/server.go +++ b/test/e2e/legacy/basic/server.go @@ -7,7 +7,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/port" @@ -99,7 +98,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { f.RunProcesses([]string{serverConf}, []string{clientConf}) - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) // tcp random port status, err := client.GetProxyStatus("tcp") diff --git a/test/e2e/legacy/features/monitor.go b/test/e2e/legacy/features/monitor.go index 8968df9f01b..75aa183bb97 100644 --- a/test/e2e/legacy/features/monitor.go +++ b/test/e2e/legacy/features/monitor.go @@ -41,7 +41,7 @@ var _ = ginkgo.Describe("[Feature: Monitor]", func() { framework.NewRequestExpect(f).RequestModify(func(r *request.Request) { r.HTTP().Port(dashboardPort).HTTPPath("/metrics") }).Ensure(func(resp *request.Response) bool { - log.Trace("prometheus metrics response: \n%s", resp.Content) + log.Tracef("prometheus metrics response: \n%s", resp.Content) if resp.Code != 200 { return false } diff --git a/test/e2e/legacy/features/real_ip.go b/test/e2e/legacy/features/real_ip.go index 082df8c6260..f74c62d2d68 100644 --- a/test/e2e/legacy/features/real_ip.go +++ b/test/e2e/legacy/features/real_ip.go @@ -66,7 +66,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { rd := bufio.NewReader(c) ppHeader, err := pp.Read(rd) if err != nil { - log.Error("read proxy protocol error: %v", err) + log.Errorf("read proxy protocol error: %v", err) return } @@ -93,7 +93,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { f.RunProcesses([]string{serverConf}, []string{clientConf}) framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool { - log.Trace("ProxyProtocol get SourceAddr: %s", string(resp.Content)) + log.Tracef("ProxyProtocol get SourceAddr: %s", string(resp.Content)) addr, err := net.ResolveTCPAddr("tcp", string(resp.Content)) if err != nil { return false @@ -121,7 +121,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { rd := bufio.NewReader(c) ppHeader, err := pp.Read(rd) if err != nil { - log.Error("read proxy protocol error: %v", err) + log.Errorf("read proxy protocol error: %v", err) return } srcAddrRecord = ppHeader.SourceAddr.String() @@ -142,7 +142,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { r.HTTP().HTTPHost("normal.example.com") }).Ensure(framework.ExpectResponseCode(404)) - log.Trace("ProxyProtocol get SourceAddr: %s", srcAddrRecord) + log.Tracef("ProxyProtocol get SourceAddr: %s", srcAddrRecord) addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord) framework.ExpectNoError(err, srcAddrRecord) framework.ExpectEqualValues("127.0.0.1", addr.IP.String()) diff --git a/test/e2e/pkg/plugin/plugin.go b/test/e2e/pkg/plugin/plugin.go index 51de01d9383..b807a060a0d 100644 --- a/test/e2e/pkg/plugin/plugin.go +++ b/test/e2e/pkg/plugin/plugin.go @@ -26,7 +26,7 @@ func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler Handler, tl w.WriteHeader(500) return } - log.Trace("plugin request: %s", string(buf)) + log.Tracef("plugin request: %s", string(buf)) err = json.Unmarshal(buf, &r) if err != nil { w.WriteHeader(500) @@ -34,7 +34,7 @@ func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler Handler, tl } resp := handler(r) buf, _ = json.Marshal(resp) - log.Trace("plugin response: %s", string(buf)) + log.Tracef("plugin response: %s", string(buf)) _, _ = w.Write(buf) })), ) diff --git a/test/e2e/v1/basic/client.go b/test/e2e/v1/basic/client.go index b0b258db308..ef5e262f709 100644 --- a/test/e2e/v1/basic/client.go +++ b/test/e2e/v1/basic/client.go @@ -8,7 +8,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/request" @@ -57,7 +56,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(p2Port).Ensure() framework.NewRequestExpect(f).Port(p3Port).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) conf, err := client.GetConfig() framework.ExpectNoError(err) @@ -124,7 +123,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(testPort).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) err := client.Stop() framework.ExpectNoError(err) diff --git a/test/e2e/v1/basic/config.go b/test/e2e/v1/basic/config.go index 686b3c5c498..7dc3cedb3d9 100644 --- a/test/e2e/v1/basic/config.go +++ b/test/e2e/v1/basic/config.go @@ -38,6 +38,51 @@ var _ = ginkgo.Describe("[Feature: Config]", func() { framework.NewRequestExpect(f).PortName(portName).Ensure() }) + + ginkgo.It("Range ports mapping", func() { + serverConf := consts.DefaultServerConfig + clientConf := consts.DefaultClientConfig + + adminPort := f.AllocPort() + + localPortsRange := "13010-13012,13014" + remotePortsRange := "23010-23012,23014" + escapeTemplate := func(s string) string { + return "{{ `" + s + "` }}" + } + clientConf += fmt.Sprintf(` + webServer.port = %d + + %s + [[proxies]] + name = "tcp-%s" + type = "tcp" + localPort = %s + remotePort = %s + %s + `, adminPort, + escapeTemplate(fmt.Sprintf(`{{- range $_, $v := parseNumberRangePair "%s" "%s" }}`, localPortsRange, remotePortsRange)), + escapeTemplate("{{ $v.First }}"), + escapeTemplate("{{ $v.First }}"), + escapeTemplate("{{ $v.Second }}"), + escapeTemplate("{{- end }}"), + ) + + f.RunProcesses([]string{serverConf}, []string{clientConf}) + + client := f.APIClientForFrpc(adminPort) + checkProxyFn := func(name string, localPort, remotePort int) { + status, err := client.GetProxyStatus(name) + framework.ExpectNoError(err) + + framework.ExpectContainSubstring(status.LocalAddr, fmt.Sprintf(":%d", localPort)) + framework.ExpectContainSubstring(status.RemoteAddr, fmt.Sprintf(":%d", remotePort)) + } + checkProxyFn("tcp-13010", 13010, 23010) + checkProxyFn("tcp-13011", 13011, 23011) + checkProxyFn("tcp-13012", 13012, 23012) + checkProxyFn("tcp-13014", 13014, 23014) + }) }) ginkgo.Describe("Includes", func() { diff --git a/test/e2e/v1/basic/server.go b/test/e2e/v1/basic/server.go index f2f4e0a5954..fe8af4d1108 100644 --- a/test/e2e/v1/basic/server.go +++ b/test/e2e/v1/basic/server.go @@ -7,7 +7,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/port" @@ -110,7 +109,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { f.RunProcesses([]string{serverConf}, []string{clientConf}) - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) // tcp random port status, err := client.GetProxyStatus("tcp") diff --git a/test/e2e/v1/features/monitor.go b/test/e2e/v1/features/monitor.go index 5d4b8f9f908..fe92203a159 100644 --- a/test/e2e/v1/features/monitor.go +++ b/test/e2e/v1/features/monitor.go @@ -42,7 +42,7 @@ var _ = ginkgo.Describe("[Feature: Monitor]", func() { framework.NewRequestExpect(f).RequestModify(func(r *request.Request) { r.HTTP().Port(dashboardPort).HTTPPath("/metrics") }).Ensure(func(resp *request.Response) bool { - log.Trace("prometheus metrics response: \n%s", resp.Content) + log.Tracef("prometheus metrics response: \n%s", resp.Content) if resp.Code != 200 { return false } diff --git a/test/e2e/v1/features/real_ip.go b/test/e2e/v1/features/real_ip.go index 85338c62072..94508f7da9a 100644 --- a/test/e2e/v1/features/real_ip.go +++ b/test/e2e/v1/features/real_ip.go @@ -2,6 +2,7 @@ package features import ( "bufio" + "crypto/tls" "fmt" "net" "net/http" @@ -9,6 +10,7 @@ import ( "github.com/onsi/ginkgo/v2" pp "github.com/pires/go-proxyproto" + "github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" @@ -21,23 +23,24 @@ import ( var _ = ginkgo.Describe("[Feature: Real IP]", func() { f := framework.NewDefaultFramework() - ginkgo.It("HTTP X-Forwarded-For", func() { - vhostHTTPPort := f.AllocPort() - serverConf := consts.DefaultServerConfig + fmt.Sprintf(` + ginkgo.Describe("HTTP X-forwarded-For", func() { + ginkgo.It("Client Without Header", func() { + vhostHTTPPort := f.AllocPort() + serverConf := consts.DefaultServerConfig + fmt.Sprintf(` vhostHTTPPort = %d `, vhostHTTPPort) - localPort := f.AllocPort() - localServer := httpserver.New( - httpserver.WithBindPort(localPort), - httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - _, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For"))) - })), - ) - f.RunServer("", localServer) - - clientConf := consts.DefaultClientConfig - clientConf += fmt.Sprintf(` + localPort := f.AllocPort() + localServer := httpserver.New( + httpserver.WithBindPort(localPort), + httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + _, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For"))) + })), + ) + f.RunServer("", localServer) + + clientConf := consts.DefaultClientConfig + clientConf += fmt.Sprintf(` [[proxies]] name = "test" type = "http" @@ -45,14 +48,131 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { customDomains = ["normal.example.com"] `, localPort) - f.RunProcesses([]string{serverConf}, []string{clientConf}) + f.RunProcesses([]string{serverConf}, []string{clientConf}) - framework.NewRequestExpect(f).Port(vhostHTTPPort). - RequestModify(func(r *request.Request) { - r.HTTP().HTTPHost("normal.example.com") - }). - ExpectResp([]byte("127.0.0.1")). - Ensure() + framework.NewRequestExpect(f).Port(vhostHTTPPort). + RequestModify(func(r *request.Request) { + r.HTTP().HTTPHost("normal.example.com") + }). + ExpectResp([]byte("127.0.0.1")). + Ensure() + }) + + ginkgo.It("Client With Header", func() { + vhostHTTPPort := f.AllocPort() + serverConf := consts.DefaultServerConfig + fmt.Sprintf(` + vhostHTTPPort = %d + `, vhostHTTPPort) + + localPort := f.AllocPort() + localServer := httpserver.New( + httpserver.WithBindPort(localPort), + httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + _, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For"))) + })), + ) + f.RunServer("", localServer) + + clientConf := consts.DefaultClientConfig + clientConf += fmt.Sprintf(` + [[proxies]] + name = "test" + type = "http" + localPort = %d + customDomains = ["normal.example.com"] + `, localPort) + + f.RunProcesses([]string{serverConf}, []string{clientConf}) + + framework.NewRequestExpect(f).Port(vhostHTTPPort). + RequestModify(func(r *request.Request) { + r.HTTP().HTTPHost("normal.example.com") + r.HTTP().HTTPHeaders(map[string]string{"x-forwarded-for": "2.2.2.2"}) + }). + ExpectResp([]byte("2.2.2.2, 127.0.0.1")). + Ensure() + }) + + ginkgo.It("http2https plugin", func() { + vhostHTTPPort := f.AllocPort() + serverConf := consts.DefaultServerConfig + fmt.Sprintf(` + vhostHTTPPort = %d + `, vhostHTTPPort) + + localPort := f.AllocPort() + + clientConf := consts.DefaultClientConfig + clientConf += fmt.Sprintf(` + [[proxies]] + name = "test" + type = "http" + customDomains = ["normal.example.com"] + [proxies.plugin] + type = "http2https" + localAddr = "127.0.0.1:%d" + `, localPort) + + f.RunProcesses([]string{serverConf}, []string{clientConf}) + + tlsConfig, err := transport.NewServerTLSConfig("", "", "") + framework.ExpectNoError(err) + + localServer := httpserver.New( + httpserver.WithBindPort(localPort), + httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + _, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For"))) + })), + httpserver.WithTLSConfig(tlsConfig), + ) + f.RunServer("", localServer) + + framework.NewRequestExpect(f).Port(vhostHTTPPort). + RequestModify(func(r *request.Request) { + r.HTTP().HTTPHost("normal.example.com") + r.HTTP().HTTPHeaders(map[string]string{"x-forwarded-for": "2.2.2.2, 3.3.3.3"}) + }). + ExpectResp([]byte("2.2.2.2, 3.3.3.3, 127.0.0.1")). + Ensure() + }) + + ginkgo.It("https2http plugin", func() { + vhostHTTPSPort := f.AllocPort() + serverConf := consts.DefaultServerConfig + fmt.Sprintf(` + vhostHTTPSPort = %d + `, vhostHTTPSPort) + + localPort := f.AllocPort() + + clientConf := consts.DefaultClientConfig + clientConf += fmt.Sprintf(` + [[proxies]] + name = "test" + type = "https" + customDomains = ["normal.example.com"] + [proxies.plugin] + type = "https2http" + localAddr = "127.0.0.1:%d" + `, localPort) + + f.RunProcesses([]string{serverConf}, []string{clientConf}) + + localServer := httpserver.New( + httpserver.WithBindPort(localPort), + httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + _, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For"))) + })), + ) + f.RunServer("", localServer) + + framework.NewRequestExpect(f).Port(vhostHTTPSPort). + RequestModify(func(r *request.Request) { + r.HTTPS().HTTPHost("normal.example.com"). + HTTPHeaders(map[string]string{"x-forwarded-for": "2.2.2.2"}). + TLSConfig(&tls.Config{ServerName: "normal.example.com", InsecureSkipVerify: true}) + }). + ExpectResp([]byte("2.2.2.2, 127.0.0.1")). + Ensure() + }) }) ginkgo.Describe("Proxy Protocol", func() { @@ -67,7 +187,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { rd := bufio.NewReader(c) ppHeader, err := pp.Read(rd) if err != nil { - log.Error("read proxy protocol error: %v", err) + log.Errorf("read proxy protocol error: %v", err) return } @@ -95,7 +215,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { f.RunProcesses([]string{serverConf}, []string{clientConf}) framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool { - log.Trace("ProxyProtocol get SourceAddr: %s", string(resp.Content)) + log.Tracef("ProxyProtocol get SourceAddr: %s", string(resp.Content)) addr, err := net.ResolveTCPAddr("tcp", string(resp.Content)) if err != nil { return false @@ -123,7 +243,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { rd := bufio.NewReader(c) ppHeader, err := pp.Read(rd) if err != nil { - log.Error("read proxy protocol error: %v", err) + log.Errorf("read proxy protocol error: %v", err) return } srcAddrRecord = ppHeader.SourceAddr.String() @@ -145,7 +265,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() { r.HTTP().HTTPHost("normal.example.com") }).Ensure(framework.ExpectResponseCode(404)) - log.Trace("ProxyProtocol get SourceAddr: %s", srcAddrRecord) + log.Tracef("ProxyProtocol get SourceAddr: %s", srcAddrRecord) addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord) framework.ExpectNoError(err, srcAddrRecord) framework.ExpectEqualValues("127.0.0.1", addr.IP.String()) diff --git a/test/e2e/v1/plugin/client.go b/test/e2e/v1/plugin/client.go index f544570ae7e..476adb89e09 100644 --- a/test/e2e/v1/plugin/client.go +++ b/test/e2e/v1/plugin/client.go @@ -30,10 +30,11 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() { name = "%s" type = "tcp" remotePort = {{ .%s }} + `+extra, proxyName, portName) + fmt.Sprintf(` [proxies.plugin] type = "unix_domain_socket" unixPath = "{{ .%s }}" - `+extra, proxyName, portName, framework.UDSEchoServerAddr) + `, framework.UDSEchoServerAddr) } tests := []struct { diff --git a/web/frps/src/components/ProxyView.vue b/web/frps/src/components/ProxyView.vue index 2811a30f5e4..97a6e071f51 100644 --- a/web/frps/src/components/ProxyView.vue +++ b/web/frps/src/components/ProxyView.vue @@ -111,7 +111,7 @@ const formatTrafficOut = (row: BaseProxy, _: TableColumnCtx) => { } const clearOfflineProxies = () => { - fetch('/api/proxies?status=offline', { + fetch('../api/proxies?status=offline', { method: 'DELETE', credentials: 'include', })