As of Hugo v0.16, running hugo server --port=80 as an unprivileged user always returns an error message saying Error: Port 80 already in use regardless if the port is actually in use or not.
For comparison, also as an unpriviledged user, running netcat as nc -l 80 to listen on port 80 correctly returns the error nc: Permission denied.
The code in question resides in the server() function in https://github.com/spf13/hugo/blob/v0.16/commands/server.go#L123-L137:
l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(serverPort)))
if err == nil {
l.Close()
} else {
if flagChanged(serverCmd.Flags(), "port") {
// port set explicitly by user -- he/she probably meant it!
return newSystemErrorF("Port %d already in use", serverPort)
}
jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
sp, err := helpers.FindAvailablePort()
if err != nil {
return newSystemError("Unable to find alternative port to use:", err)
}
serverPort = sp.Port
}
The fix should be simple: just need to add some checks what the error actually is instead of assuming "Can't open a port" === "The port is already in use".
Fix will likely involve os.IsPermission.
listen tcp 127.0.0.1:80: bind: permission denied
Error: Port 80 already in use
Thank you for your effort, @g3wanghc!
However, as far as I know, "Port < 1024 can only be opened by root" is not a universal rule. For example, I think non-UNIX platforms like MS Windows has a different way of determining whether an unprivileged user can open a certain port or not.
Furthermore, even on Linux, there are ways to allow non-root users to listen on port < 1024, see, for example:
I just tested the setcap method, and I was happily running hugo server --port=80 as a non-root user!
So, instead of imposing artificial rules in the Hugo code, it is better to let the undelying OS tell us whether a port can be opened or not, and why. :wink:
I think the simplest and bestest (good language?) solution here is to trust the stdlib to provide us with nice error messages. So append the received error to a generic Hugo error and we should be fine. Doing magic IsPermission checks isn't very helpful when there is nothing we can do but inform.
@anthonyfok Thanks for the explanation! I forgot to take port binding rules of different OS into consideration. I will definitely look into those resources.
@g3wanghc:
bind: permission deniedis not recognized by os.isPermission so its checking if the user isrootinstead.
I apologize for not having looked more closely into this, but I am sure there is another way to detect the reason why a port cannot be opened.
Or, simpler yet, maybe just print err instead of making up our own error message (with wrong assumption)? In that case, I guess we don't even need to look for use os.IsPermission or anything like that. ;-)
@anthonyfok Haha no problem, I shouldn't have done something so hacky.
The default messages of err are actually:
bind: permission denied vs
bind: address already in use
Like @bep suggested, we can just append the that err message to create a generic Hugo error.
Like @bep suggested, we can just append the that err message to create a generic Hugo error.
Yes! (I forgot to refresh the page so I missed @bep's message) I agree wholeheartedly with him. :-)
Is there a good way to distinguish between *net.OpError errors in goLang instead of using string comparisons?
switch err.(type) {
case *net.OpError:
// ...
default:
// ...
}
@anthonyfok code in c52bb4efbe40fe91e3dd7db81432702392550991