Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display useful suggestions in error message on windows when port reserved? (bind EACCES) #2894

Open
ariccio opened this issue Jun 11, 2022 · 6 comments

Comments

@ariccio
Copy link

ariccio commented Jun 11, 2022

Is your feature request related to a problem? Please describe.
A problem I encounter all the time on windows is that Hyper-V (or someone else) will reserve a port like port 3000. It's not in use, it's reserved. If I try to start up rails with the default port, then everything will crash when puma fails to create a TCPServer on that port. It's not obvious from the documentation that TCPServer.new can raise (though it does), so puma does not handle this.

I'm used to diagnosing this, you need to check the reserved ports with netsh interface ipv4 show excludedportrange protocol=tcp, but this is quite surprising if you have no idea what's going on. If you check the ports in use, you will see that port is not in use! The only fix is to reboot, something you may end up doing in the meantime, and likely you won't know why it worked.

Because puma does not handle this, rails (or whatever app you're using) simply crashes with a stacktrace, and the message C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:341:in initialize': Permission denied - bind(2) for "::1" port 3000 (Errno::EACCES)`.

Describe the solution you'd like
One useful option might be to detect when (on Windows) the exception is EACCES instead of EADDRINUSE, and dump a useful error to the screen before reraising. The message could be something like:

bind(2) for "::1" port 3000 failed with EACCES. On Windows, the port might be reserved even if it's not in use by a process. To check if it's reserved, run this command, and check if the port in question is in the listed ranges:
`netsh interface ipv4 show excludedportrange protocol=tcp`

Sometimes, this is the result of Hyper-V reserving ports. In that case, the solution is to reboot the machine until it has allocated a different port.

If in an interactive session, this wouldn't change any behavior, since it would still run through the current exception handling. In any other context, it could cause issues, so I do not expect this to be the solution you adopt :) You could check to see if puma is running in an interactive context too.

Describe alternatives you've considered
One option is puma could run the command in a subshell and parse the results to see if it's in the range before providing a message. That sounds like extra complexity.

Additional context
I suspect many other kernel mode things could allocate/reserve the port, but Hyper-V is certainly the thing that I most commonly encounter. Those would still probably show up in the excludedportrange, but I have no clue if they would also choose a random set of ports on each boot. I don't know why Hyper-V does this specifically... it could be an anti-anti-debugging/anti-sandbox-evasion feature?

@ariccio
Copy link
Author

ariccio commented Jun 11, 2022

Oh, and the full stacktrace is here:

C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:341:in `initialize': Permission denied - bind(2) for "::1" port 3000 (Errno::EACCES)
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:341:in `new'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:341:in `add_tcp_listener'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:335:in `block in add_tcp_listener'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:334:in `each'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:334:in `add_tcp_listener'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:173:in `block in parse'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:156:in `each'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/binder.rb:156:in `parse'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/runner.rb:156:in `load_and_bind'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/single.rb:44:in `run'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/puma/launcher.rb:182:in `run'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/puma-5.6.4/lib/rack/handler/puma.rb:72:in `run'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/rack-2.2.3.1/lib/rack/server.rb:327:in `start'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/railties-6.1.6/lib/rails/commands/server/server_command.rb:39:in `start'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/railties-6.1.6/lib/rails/commands/server/server_command.rb:144:in `block in perform'
        from <internal:kernel>:90:in `tap'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/railties-6.1.6/lib/rails/commands/server/server_command.rb:135:in `perform'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/railties-6.1.6/lib/rails/command/base.rb:69:in `perform'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/railties-6.1.6/lib/rails/command.rb:48:in `invoke'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/railties-6.1.6/lib/rails/commands.rb:18:in `<main>'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/bootsnap-1.12.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/bootsnap-1.12.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from bin/rails:5:in `<main>'

@nateberkopec
Copy link
Member

I'll let the resident Windows Guy take this one 😆

@MSP-Greg
Copy link
Member

MSP-Greg commented Jul 6, 2022

'resident Windows Guy'?

This affects both SSL & TCP listeners (both use TCPServer.new), and given that some new Puma users may not have dealt much with socket errors, I'll improve the error/debug info...

@ariccio
Copy link
Author

ariccio commented Jul 15, 2022

Hey, some of us wear the "resident windows guy" badge with honor 😂

I for one, take pride in complying with the Win32 documented API contract to the letter, checking every single return code, using SAL like it's my only method of human-to-human communication, and compiling one off little test programs with /wall and /analyze. I even feel a degree of relief that no medicine provides of my OCD when I see well written code interacting with any of the windows system APIs. We go once more into that SymSrv breach my friend, once more with gusto.

(I had way too much fun writing that)

@MSP-Greg
Copy link
Member

@ariccio

I have started a branch for this issue, hope to finish this weekend. I assume you've noticed that Puma builds with the Ruby mswin build?

JFYI, I used both the original IBM & Compaq PC's. I'm pretty good with MSFT Office, so I'll always have a Windows PC, but as to coding, it's just another OS. Most of my OSS work is in WSL2/Ubuntu.

@ariccio
Copy link
Author

ariccio commented Jul 15, 2022

Heh, no worries, just poking fun 😉😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants