[PR #4666] [MERGED] Fix ports not being released on Service.Close() #5020

Closed
opened 2026-05-05 14:53:12 -06:00 by gitea-mirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/fatedier/frp/pull/4666
Author: @ubergeek77
Created: 2/11/2025
Status: Merged
Merged: 2/12/2025
Merged by: @fatedier

Base: devHead: dev


📝 Commits (1)

  • 54ef5ba Fix ports not being released on Service.Close()

📊 Changes

1 file changed (+35 additions, -9 deletions)

View changed files

📝 server/service.go (+35 -9)

📄 Description

Description

Service.Close() leaves some listeners open after the frps server is closed, locking the ports until the entire program process is stopped. For CLI users of frps, this isn't an issue because the entire process stops when the server stops. But when using frps like a library, opening a server and then closing it will still leave some ports open. This prevents a new frps server from being opened if one was previously opened and then closed.

This PR fixes this by tracking all listeners frps opens, then closes them in the Close() function along with the other listeners.

Examples

The following code example will start an frps server, stop it, then attempt to start it again.

Code example
package main

import (
	"context"
	"time"

	_ "github.com/fatedier/frp/assets/frpc"
	_ "github.com/fatedier/frp/assets/frps"
	v1 "github.com/fatedier/frp/pkg/config/v1"
	frplog "github.com/fatedier/frp/pkg/util/log"
	"github.com/fatedier/frp/server"
)

// Store an instance of the FRP server globally
var FRPS *server.Service

// Run an Frp server
func startFrpServer() error {
	var defaultCfg v1.ServerConfig
	defaultCfg.Complete()
	svrCfg := &defaultCfg

	// Enable the webserver for demonstration
	svrCfg.WebServer.Addr = "0.0.0.0"
	svrCfg.WebServer.Port = 7500

	// Initialize frp's internal logger
	frplog.InitLogger(svrCfg.Log.To, svrCfg.Log.Level, int(svrCfg.Log.MaxDays), svrCfg.Log.DisablePrintColor)

	// Define a new FRPS server
	svr, err := server.NewService(svrCfg)
	if err != nil {
		panic(err)
	}

	// Start FRPS
	ctx := context.Background()
	go svr.Run(ctx)

	// Store it globally and return
	FRPS = svr
	return nil
}

// Close the frp server
func closeFrpServer() error {
	if err := FRPS.Close(); err != nil {
		return err
	}
	return nil
}

func main() {
	startFrpServer()
	time.Sleep(5 * time.Second)
	closeFrpServer()
	time.Sleep(5 * time.Second)
	startFrpServer()
	time.Sleep(5 * time.Second)
}

Without this PR, this will fail because the webserver port, 7500, is still in use:

2025-02-11 17:20:16.255 [I] [server/service.go:237] frps tcp listen on 0.0.0.0:7000
2025-02-11 17:20:16.256 [I] [server/service.go:351] dashboard listen on 0.0.0.0:7500
2025-02-11 17:20:21.257 [W] [server/service.go:485] Listener for incoming connections from client closed
panic: listen tcp 0.0.0.0:7500: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.

If I disable the webserver in the code example and run it again, the server still fails to start a second time, because port 7000 is still in use:

2025-02-11 17:28:37.204 [I] [server/service.go:237] frps tcp listen on 0.0.0.0:7000
2025-02-11 17:28:42.206 [W] [server/service.go:485] Listener for incoming connections from client closed
panic: create server listener error, listen tcp 0.0.0.0:7000: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.

With this PR, the previous server exits cleanly, and a new server can be started with no issue:

2025-02-11 17:22:22.724 [I] [server/service.go:246] frps tcp listen on 0.0.0.0:7000
2025-02-11 17:22:22.724 [I] [server/service.go:361] dashboard listen on 0.0.0.0:7500
2025-02-11 17:22:27.726 [W] [server/service.go:511] Listener for incoming connections from client closed
2025-02-11 17:22:27.726 [W] [server/service.go:363] dashboard server exit with error: http: Server closed
2025-02-11 17:22:32.759 [I] [server/service.go:246] frps tcp listen on 0.0.0.0:7000
2025-02-11 17:22:32.759 [I] [server/service.go:361] dashboard listen on 0.0.0.0:7500

Notes

I renamed listener to muxListener to disambiguate it from the other types of listeners. It ends up being defined by svr.muxer.DefaultListener(), so I named it that. This doesn't seem to be a breaking change because no parts of the code try to call the original svr.listener directly. But it doesn't matter what any of these are named as long as it gets closed properly.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/fatedier/frp/pull/4666 **Author:** [@ubergeek77](https://github.com/ubergeek77) **Created:** 2/11/2025 **Status:** ✅ Merged **Merged:** 2/12/2025 **Merged by:** [@fatedier](https://github.com/fatedier) **Base:** `dev` ← **Head:** `dev` --- ### 📝 Commits (1) - [`54ef5ba`](https://github.com/fatedier/frp/commit/54ef5ba936efcecbc84b5309c034f72911b6a598) Fix ports not being released on Service.Close() ### 📊 Changes **1 file changed** (+35 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `server/service.go` (+35 -9) </details> ### 📄 Description ### Description `Service.Close()` leaves some listeners open after the `frps` server is closed, locking the ports until the entire program process is stopped. For CLI users of `frps`, this isn't an issue because the entire process stops when the server stops. But when using `frps` like a library, opening a server and then closing it will still leave some ports open. This prevents a new `frps` server from being opened if one was previously opened and then closed. This PR fixes this by tracking all listeners `frps` opens, then closes them in the `Close()` function along with the other listeners. ### Examples The following code example will start an `frps` server, stop it, then attempt to start it again. <details> <summary>Code example</summary> ```go package main import ( "context" "time" _ "github.com/fatedier/frp/assets/frpc" _ "github.com/fatedier/frp/assets/frps" v1 "github.com/fatedier/frp/pkg/config/v1" frplog "github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/server" ) // Store an instance of the FRP server globally var FRPS *server.Service // Run an Frp server func startFrpServer() error { var defaultCfg v1.ServerConfig defaultCfg.Complete() svrCfg := &defaultCfg // Enable the webserver for demonstration svrCfg.WebServer.Addr = "0.0.0.0" svrCfg.WebServer.Port = 7500 // Initialize frp's internal logger frplog.InitLogger(svrCfg.Log.To, svrCfg.Log.Level, int(svrCfg.Log.MaxDays), svrCfg.Log.DisablePrintColor) // Define a new FRPS server svr, err := server.NewService(svrCfg) if err != nil { panic(err) } // Start FRPS ctx := context.Background() go svr.Run(ctx) // Store it globally and return FRPS = svr return nil } // Close the frp server func closeFrpServer() error { if err := FRPS.Close(); err != nil { return err } return nil } func main() { startFrpServer() time.Sleep(5 * time.Second) closeFrpServer() time.Sleep(5 * time.Second) startFrpServer() time.Sleep(5 * time.Second) } ``` </details> Without this PR, this will fail because the webserver port, `7500`, is still in use: ``` 2025-02-11 17:20:16.255 [I] [server/service.go:237] frps tcp listen on 0.0.0.0:7000 2025-02-11 17:20:16.256 [I] [server/service.go:351] dashboard listen on 0.0.0.0:7500 2025-02-11 17:20:21.257 [W] [server/service.go:485] Listener for incoming connections from client closed panic: listen tcp 0.0.0.0:7500: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. ``` If I disable the webserver in the code example and run it again, the server still fails to start a second time, because port `7000` is still in use: ``` 2025-02-11 17:28:37.204 [I] [server/service.go:237] frps tcp listen on 0.0.0.0:7000 2025-02-11 17:28:42.206 [W] [server/service.go:485] Listener for incoming connections from client closed panic: create server listener error, listen tcp 0.0.0.0:7000: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. ``` With this PR, the previous server exits cleanly, and a new server can be started with no issue: ``` 2025-02-11 17:22:22.724 [I] [server/service.go:246] frps tcp listen on 0.0.0.0:7000 2025-02-11 17:22:22.724 [I] [server/service.go:361] dashboard listen on 0.0.0.0:7500 2025-02-11 17:22:27.726 [W] [server/service.go:511] Listener for incoming connections from client closed 2025-02-11 17:22:27.726 [W] [server/service.go:363] dashboard server exit with error: http: Server closed 2025-02-11 17:22:32.759 [I] [server/service.go:246] frps tcp listen on 0.0.0.0:7000 2025-02-11 17:22:32.759 [I] [server/service.go:361] dashboard listen on 0.0.0.0:7500 ``` ### Notes I renamed `listener` to `muxListener` to disambiguate it from the other types of listeners. It ends up being defined by `svr.muxer.DefaultListener()`, so I named it that. This doesn't seem to be a breaking change because no parts of the code try to call the original `svr.listener` directly. But it doesn't matter what any of these are named as long as it gets closed properly. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
gitea-mirror 2026-05-05 14:53:12 -06:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/frp#5020
No description provided.