Windows: manually check if we can bind on the configured port/ports

Windows allows the coexistence of three types of sockets on the same
transport-layer service port, for example, 127.0.0.1:8080, [::1]:8080
and [::ffff:0.0.0.0]:8080

Go don't properly handles this, so we use a ugly hack

Fixes #350
This commit is contained in:
Nicola Murino 2021-03-21 22:21:04 +01:00
parent f7c7e2951d
commit 54c0c1b80d
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
5 changed files with 27 additions and 3 deletions

View file

@ -211,6 +211,7 @@ func (c *Configuration) Initialize(configDir string) error {
go func(s *Server) {
ftpServer := ftpserver.NewFtpServer(s)
logger.Info(logSender, "", "starting FTP serving, binding: %v", s.binding.GetAddress())
utils.CheckTCP4Port(s.binding.Port)
exitChannel <- ftpServer.ListenAndServe()
}(server)

View file

@ -232,6 +232,7 @@ func (c *Configuration) Initialize(configDir string) error {
go func(binding Binding) {
addr := binding.GetAddress()
utils.CheckTCP4Port(binding.Port)
listener, err := net.Listen("tcp", addr)
if err != nil {
logger.Warn(logSender, "", "error starting listener on address %v: %v", addr, err)

View file

@ -34,6 +34,7 @@ import (
const (
logSender = "utils"
osWindows = "windows"
)
// IsStringInSlice searches a string in a slice and returns true if the string is found
@ -373,7 +374,7 @@ func IsFileInputValid(fileInput string) bool {
// the -l flag will be ignored and the -c flag will get the value `C:\ProgramData\SFTPGO" -l sftpgo.log`
// since the backslash after SFTPGO escape the double quote. This is definitely a bad user input
func CleanDirInput(dirInput string) string {
if runtime.GOOS == "windows" {
if runtime.GOOS == osWindows {
for strings.HasSuffix(dirInput, "\"") {
dirInput = strings.TrimSuffix(dirInput, "\"")
}
@ -414,7 +415,7 @@ func HTTPListenAndServe(srv *http.Server, address string, port int, isTLS bool,
var listener net.Listener
var err error
if filepath.IsAbs(address) && runtime.GOOS != "windows" {
if filepath.IsAbs(address) && runtime.GOOS != osWindows {
if !IsFileInputValid(address) {
return fmt.Errorf("invalid socket address %#v", address)
}
@ -427,6 +428,7 @@ func HTTPListenAndServe(srv *http.Server, address string, port int, isTLS bool,
listener, err = net.Listen("unix", address)
} else {
CheckTCP4Port(port)
listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
}
if err != nil {
@ -462,7 +464,7 @@ func GetTLSCiphersFromNames(cipherNames []string) []uint16 {
// This can be verified using openssl x509 -in cert.crt -text -noout
func EncodeTLSCertToPem(tlsCert *x509.Certificate) (string, error) {
if len(tlsCert.Raw) == 0 {
return "", errors.New("Invalid x509 certificate, no der contents")
return "", errors.New("invalid x509 certificate, no der contents")
}
publicKeyBlock := pem.Block{
Type: "CERTIFICATE",
@ -470,3 +472,20 @@ func EncodeTLSCertToPem(tlsCert *x509.Certificate) (string, error) {
}
return string(pem.EncodeToMemory(&publicKeyBlock)), nil
}
// CheckTCP4Port quits the app if bind to the given IPv4 port.
// This is a ugly hack to avoid to bind on an already used port.
// It is required on Windows only.
// https://github.com/golang/go/issues/45150
func CheckTCP4Port(port int) {
if runtime.GOOS != osWindows {
return
}
listener, err := net.Listen("tcp4", fmt.Sprintf(":%d", port))
if err != nil {
logger.ErrorToConsole("unable to bind tcp4 address: %v", err)
logger.Error(logSender, "", "unable to bind tcp4 address: %v", err)
os.Exit(1)
}
listener.Close()
}

View file

@ -141,6 +141,7 @@ type VirtualFolder struct {
QuotaFiles int `json:"quota_files"`
}
// GetFilesystem returns the filesystem for this folder
func (v *VirtualFolder) GetFilesystem(connectionID string) (Fs, error) {
switch v.FsConfig.Provider {
case S3FilesystemProvider:

View file

@ -80,11 +80,13 @@ func (s *webDavServer) listenAndServe(compressor *middleware.Compressor) error {
}
}
logger.Info(logSender, "", "starting HTTPS serving, binding: %v", s.binding.GetAddress())
utils.CheckTCP4Port(s.binding.Port)
return httpServer.ListenAndServeTLS("", "")
}
s.binding.EnableHTTPS = false
serviceStatus.Bindings = append(serviceStatus.Bindings, s.binding)
logger.Info(logSender, "", "starting HTTP serving, binding: %v", s.binding.GetAddress())
utils.CheckTCP4Port(s.binding.Port)
return httpServer.ListenAndServe()
}