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

Add compatibility with only websocket-capable clients #2132

Merged
merged 11 commits into from
Sep 21, 2024

Conversation

enoperm
Copy link
Contributor

@enoperm enoperm commented Sep 13, 2024

  • have read the CONTRIBUTING.md file
  • raised a GitHub issue or discussed it on the projects chat beforehand
  • added unit tests
  • added integration tests
  • updated documentation if needed
  • updated CHANGELOG.md

I have not touched CHANGELOG.md, as I can see Headscale already entered a release candidate phase and I do not expect anything other than bugfixes would be accepted at this point. I'll do so if/when the changes are deemed acceptable and it is clear what version of Headscale they may become a part of.

This is not a new feature, nor does it change the configuration, the patches merely improve compatibility with existing Tailscale client code, hence no documentation changes (although I'm considering that maybe it could save someone a bunch of debugging if the docs mentioned that the Tailscale DERP-over-websocket code ignores the port number in the configured DERP map).

I'd refrain from calling these changes a bugfix, though, because not supporting a (currently entirely optional) client capability does not quite sound like a bug.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced WebSocket support for the DERP server, allowing better communication and flexibility.
    • Added new logging capabilities for Tailscale clients and containers, improving debugging and monitoring.
    • Introduced a configuration option to enable or disable WebSocket DERP connections.
    • New test scenarios for validating client configurations with WebSocket and plain connections.
    • Added a new test case specifically for WebSocket interactions with the DERP server.
  • Bug Fixes

    • Improved error handling in integration tests to enhance robustness.
  • Tests

    • Expanded testing coverage for WebSocket interactions and DERP server scenarios.

Csaba Sarkadi added 4 commits September 11, 2024 21:49
The necessary behaviour is already in place,
but the wasm build only issued GETs, and the handler was not invoked.
Still needs some way to assert that clients are connected through websockets,
rather than the TCP hijacking version of DERP.
Copy link
Contributor

coderabbitai bot commented Sep 13, 2024

Walkthrough

The pull request introduces several enhancements to the Headscale application, including the addition of a new test case for WebSocket interactions with the DERP server, modifications to HTTP method handling, and the introduction of new logging capabilities. Changes to dependency management are also made, along with various refactorings and updates to existing methods and structures to improve testing coverage and functionality.

Changes

File Change Summary
.github/workflows/test-integration.yaml Added a new test case, TestDERPServerWebsocketScenario, to enhance testing of WebSocket interactions with the DERP server.
go.mod Updated to include github.com/coder/websocket as a direct dependency at version v1.8.12, removing its previous indirect requirement.
hscontrol/app.go Modified NoiseUpgradeHandler to handle both POST and GET requests, expanding API accessibility.
hscontrol/derp/server/derp_server.go Introduced serveWebsocket method for managing WebSocket connections and modified DERPHandler to route requests based on the "Sec-Websocket-Protocol" header.
hscontrol/types/users.go Updated a comment in the profilePicURL method; no functional changes.
hscontrol/util/net.go Removed a blank line in GrpcSocketDialer; cosmetic change only.
integration/dns_test.go Minor formatting change in TestValidateResolvConf function; no functional changes.
integration/dockertestutil/logs.go Refactored logging functionality by splitting SaveLog into WriteLog and SaveLog, improving modularity.
integration/embedded_derp_test.go Introduced ClientsSpec type and two new test functions for validating client behaviour under different connection types.
integration/general_test.go Updated assertions in Test2118DeletingOnlineNodePanics to use getter methods for improved encapsulation.
integration/hsic/hsic.go Added WriteLogs method to HeadscaleInContainer for enhanced logging capabilities.
integration/tailscale.go Added WriteLogs method to TailscaleClient interface for logging output.
integration/tsic/tsic.go Introduced withWebsocketDERP boolean option and new logging methods in TailscaleInContainer.
integration/utils.go Added didClientUseWebsocketForDERP and countMatchingLines functions for improved log analysis and modified error handling in pingAllHelper.

Possibly related PRs

Poem

In the meadow where rabbits play,
Changes hop along the way.
New tests for webs and logs so bright,
Enhancing code with pure delight.
With every leap, our code does grow,
A joyful dance, let progress flow! 🐇✨


Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between f509a43 and 4fe9ba3.

Files selected for processing (1)
  • integration/embedded_derp_test.go (7 hunks)
Files skipped from review as they are similar to previous changes (1)
  • integration/embedded_derp_test.go

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    -- I pushed a fix in commit <commit_id>, please review it.
    -- Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    -- @coderabbitai generate interesting stats about this repository and render them as a table.
    -- @coderabbitai read src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Outside diff range and nitpick comments (1)
integration/dockertestutil/logs.go (1)

49-51: Valid suggestion for potential improvement.

The comment suggesting the use of bufio.Writer to simplify the logging process is a valid point. Using bufio.Writer could potentially improve the efficiency of the logging mechanism by reducing the number of write operations.

Consider creating a follow-up issue to track this potential improvement and discuss the implementation details with the team.

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between fe68f50 and 73e3383.

Files selected for processing (14)
  • .github/workflows/test-integration.yaml (1 hunks)
  • go.mod (1 hunks)
  • hscontrol/app.go (1 hunks)
  • hscontrol/derp/server/derp_server.go (3 hunks)
  • hscontrol/types/users.go (1 hunks)
  • hscontrol/util/net.go (0 hunks)
  • integration/dns_test.go (0 hunks)
  • integration/dockertestutil/logs.go (3 hunks)
  • integration/embedded_derp_test.go (7 hunks)
  • integration/general_test.go (3 hunks)
  • integration/hsic/hsic.go (1 hunks)
  • integration/tailscale.go (2 hunks)
  • integration/tsic/tsic.go (5 hunks)
  • integration/utils.go (4 hunks)
Files not reviewed due to no reviewable changes (2)
  • hscontrol/util/net.go
  • integration/dns_test.go
Files skipped from review due to trivial changes (2)
  • hscontrol/types/users.go
  • integration/general_test.go
Additional comments not posted (28)
integration/tailscale.go (1)

45-46: LGTM!

The addition of the WriteLogs method to the TailscaleClient interface is a valuable enhancement. It provides a well-defined mechanism for logging output to specified writers for standard output and error, which can be crucial for debugging and monitoring the behaviour of Tailscale clients.

The method signature follows Go conventions and does not affect existing methods or the overall structure of the interface. It expands the utility of the interface without introducing any breaking changes.

Great work on enhancing the interface's functionality while maintaining compatibility!

integration/dockertestutil/logs.go (2)

Line range hint 17-37: Excellent refactoring to improve modularity and flexibility!

The changes to the WriteLog function signature and implementation are spot on. By accepting io.Writer parameters for stdout and stderr, the function is now more modular and flexible, allowing for easier testing and reuse of the logging functionality.

The separation of concerns between writing logs and managing the logging environment is a great improvement to the codebase's maintainability.


39-55: Great refactoring to clarify responsibilities and improve control flow!

The changes to the SaveLog function are excellent. By focusing on preparing the logging environment and invoking WriteLog to perform the actual logging, the responsibilities of the function are now clearer, and the control flow is improved.

This refactoring enhances the maintainability of the codebase by separating concerns and making the code easier to understand.

.github/workflows/test-integration.yaml (1)

44-44: Excellent addition of a new test case for the DERP server WebSocket scenario.

The inclusion of the TestDERPServerWebsocketScenario test case in the test matrix is a valuable enhancement to the integration test suite. It ensures that the WebSocket functionality of the DERP server is thoroughly tested, improving the overall test coverage and helping to catch potential issues related to WebSocket communication.

Running this test case against both Postgres and SQLite databases further strengthens the testing process by verifying the WebSocket behaviour across different database configurations.

Well done on expanding the test coverage!

integration/embedded_derp_test.go (10)

18-21: LGTM!

The ClientsSpec struct provides a clear and concise way to specify the number of clients using different connection types in test scenarios. The field names are descriptive and the use of int type is appropriate.


30-35: Looks good!

The spec variable provides a clear and concise way to specify the client configuration for the test scenario. Using len(MustTestVersions) for the number of plain clients ensures that the test covers all the required versions.


37-51: Test scenario looks good!

The DERPServerScenario function is called with the appropriate spec and a closure that performs additional assertions. Checking that no client used a WebSocket connection ensures that the expected behaviour is met. The test failure is correctly reported using t.Fail().


54-77: New test scenario looks good!

The TestDERPServerWebsocketScenario function is well-structured and tests the expected behaviour correctly. Specifying a spec where all clients are expected to use WebSocket connections and checking this in the additional assertions ensures that the scenario is thoroughly tested. The test failure is correctly reported using t.Fail().


79-83: Function signature changes look good!

The modifications to the DERPServerScenario function signature are well-thought-out and provide a clearer way to specify the client configuration and additional assertions for the test scenario. Accepting a map[string]ClientsSpec for the spec parameter allows for more granular control over the client setup, and the variadic furtherAssertions parameter provides flexibility in extending the test scenario with custom assertions.


101-107: Additional options look good!

The additional options passed to the CreateHeadscaleEnv function are well-defined and provide the necessary configuration for the Headscale server in the test scenario. Specifying the port number, enabling TLS, setting the hostname as the server URL, and providing additional configuration environment variables ensures that the server is set up correctly for the test.


136-139: Failure condition looks good!

The code correctly checks if the number of successful pings is less than the total expected pings and marks the test as failed using t.FailNow() if the condition is true. Returning immediately after failing the test ensures that the test execution is stopped and prevents further assertions from being executed.


162-164: Failure condition looks good!

The code correctly checks if the number of successful pings is less than the total expected pings and marks the test as failed using t.Fail() if the condition is true. This ensures that the test failure is recorded accurately.


182-184: Additional assertions look good!

The code correctly iterates over the furtherAssertions slice and calls each function with a pointer to the EmbeddedDERPServerScenario. This allows for extending the test scenario with custom assertions, providing flexibility in verifying additional conditions or behaviours.


Line range hint 188-245: Modifications to CreateHeadscaleEnv look good!

The changes made to the CreateHeadscaleEnv method are well-structured and correctly handle the creation of Tailscale nodes based on the specified ClientsSpec for each user. The method appropriately iterates over the users map and creates nodes with the default DERP configuration when clientCount.Plain is greater than 0, and nodes with the DERP-over-WebSocket configuration when clientCount.WebsocketDERP is greater than 0.

The use of the CreateTailscaleIsolatedNodesInUser method with the appropriate options ensures that the nodes are created with the correct configuration. The error handling and return statements are properly implemented.

hscontrol/derp/server/derp_server.go (4)

138-142: LGTM!

The changes to the DERPHandler method correctly route requests based on the presence of the "Sec-Websocket-Protocol" header, directing WebSocket requests to the new serveWebsocket method and other requests to the existing servePlain method. This enhances the server's capability to handle different types of connections more effectively.


145-184: Excellent work on the new serveWebsocket method!

The method correctly handles the WebSocket upgrade process, including:

  • Attempting to upgrade the HTTP connection to a WebSocket connection and handling errors appropriately.
  • Validating the subprotocol and closing the connection with a policy violation status if it's not "derp".
  • Utilising a buffered read-write connection and binary message transmission for communicating with the Tailscale DERP service.
  • Delegating the connection handling to the tailscaleDERP.Accept method with the necessary parameters.

The implementation is well-structured and follows best practices for handling WebSocket connections in the context of the DERP server.


4-4: Import statement looks good.

The "bufio" package is correctly imported and is necessary for the implementation of the new serveWebsocket method, where it is used to create a buffered read-write connection.


22-22: Import statement looks good.

The "tailscale.com/net/wsconn" package is correctly imported and is necessary for the implementation of the new serveWebsocket method, where it is used to create a NetConn instance for the WebSocket connection.

go.mod (1)

7-7: Approve the addition of the github.com/coder/websocket package as a direct dependency.

The addition of the github.com/coder/websocket package as a direct dependency at version v1.8.12 is a positive change. It indicates that the project now directly relies on this package for its WebSocket functionality, which aligns with the objective of enhancing compatibility with WebSocket-capable clients.

The removal of the indirect requirement for github.com/coder/websocket further streamlines the dependency management by clarifying that this package is directly used by the project.

integration/utils.go (3)

84-101: LGTM!

The didClientUseWebsocketForDERP function is well-implemented and enhances the ability to verify WebSocket usage in client logs. It follows a clear logic flow, handles errors appropriately, and has a descriptive name.


346-362: LGTM!

The countMatchingLines function is well-implemented and provides a reusable utility for counting matching lines from an io.Reader. It follows a clear logic flow, uses a buffered scanner for efficient processing of large input, and has a descriptive name.


138-138: LGTM!

The change from a fatal log message to a regular log message when a ping fails improves the robustness of the pingAllHelper function. By allowing the function to continue executing even if a ping fails, the test can potentially identify more issues and is more resilient.

integration/hsic/hsic.go (1)

464-468: LGTM!

The addition of the WriteLogs method to the HeadscaleInContainer struct is a useful enhancement for more flexible log handling. The implementation looks concise and correct:

  • Appropriate method signature using io.Writer interfaces for stdout and stderr.
  • Delegation to dockertestutil.WriteLog for the actual logging operation.
  • Proper error handling by returning the error from dockertestutil.WriteLog.

Great job!

integration/tsic/tsic.go (4)

70-70: LGTM!

The new withWebsocketDERP boolean field in the TailscaleInContainer struct is well-named and typed appropriately.


132-136: LGTM!

The WithWebsocketDERP function provides a clean way to configure the withWebsocketDERP field of TailscaleInContainer. The implementation looks good.


218-225: LGTM!

The new code block in the New function correctly appends the TS_DEBUG_DERP_WS_CLIENT environment variable to tailscaleOptions.Env based on the value of withWebsocketDERP. The implementation looks good.


372-378: LGTM!

The new Logs and WriteLogs methods of TailscaleInContainer provide convenient ways to access and write container logs using the dockertestutil.WriteLog function. The implementations look good.

Also applies to: 1037-1041

hscontrol/app.go (1)

428-428: Verify if allowing GET requests to the upgrade endpoint is necessary and safe.

Modifying the NoiseUpgradeHandler to allow GET requests in addition to POST is an unusual change for an endpoint performing an upgrade operation. While this may enhance compatibility with certain client code, it's important to verify that this change is actually necessary and doesn't introduce any unintended side effects or security risks.

Please confirm that allowing GET requests aligns with the expected behaviour of the relevant Tailscale client code and doesn't open up any potential vulnerabilities. If this change is indeed required and safe, please provide a brief explanation for future reference.

@enoperm
Copy link
Contributor Author

enoperm commented Sep 13, 2024

I do not remember touching integration/general_test.go. I guess there may have been upstream patches I forgot to merge since last evening. Let me see if a merge makes it go away.

@enoperm
Copy link
Contributor Author

enoperm commented Sep 13, 2024

git fetch did not retrieve any new commits, so I guess I messed up a rebase earlier.

Copy link
Collaborator

@kradalby kradalby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing really jumps out at me as problematic and unmaintainable, so I'm happy for this to move along as long as the tests pass.

Comment on lines 49 to 53
// Wouldn't it be simpler to
// open and wrap the destination files in a
// bufio.Writer, and pass those in docker.LogsOptions?
var stdout bytes.Buffer
var stderr bytes.Buffer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially, not tested or was aware of it, happy for that to be changed, maybe you can test in a followup pr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested it either, I just thought maybe doing the equivalent of > output.file is simpler than doing an extra lap around the same thing. I don't expect any significant improvements or breakages from it either, other than the code becoming shorter. I don't mind doing an extra PR, but should it include only this spot, or all other file operations that may involve redundant io.Writers?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have time, all would of course be useful. Since this is in the integration tests, its not really that critical, but always nice to have it neater.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to look for other manually buffered disk writes with git grep, but as far as I can see, this was the only spot where it happened.

I can submit an MR with the changes, but... the code did not become much shorter. The tally comes to -5 lines, three of which were the comment I added. I could still see some value in it, because unlike bytes.Buffer, which (at least if I remember correctly) by default extends however many times it needs to fit things into memory, bufio.Writer has a defined buffer size and automatically flushes when it is about to cross it. So, in theory it could make it so that longer logs can be dumped without issue. I'm still waiting for the integration tests to run on github, but I do not expect problems with it.

(My machine does not seem to like the idea of running the entire test suite at once too much - best I could do was 34 to go before it timed out, and I think I might have raised the timeout before reaching that result).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaaand it actually failed ina few tests - must have forgot about a flag.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair, this isnt really critical, if it isnt much simpler or neater, it can be dropped. I'm happy either way as it currently does work, good suggestion tho. You can submit it if it seem to work, and we can take if from there but you dont have to spend that much time on it.

Copy link
Contributor Author

@enoperm enoperm Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the comment for now. I may take another look at it, but at this point I would not really mind leaving it as it is either.

integration/embedded_derp_test.go Outdated Show resolved Hide resolved
@kradalby
Copy link
Collaborator

@enoperm looks good now, could you add a changelog entry?

@enoperm
Copy link
Contributor Author

enoperm commented Sep 19, 2024

@enoperm looks good now, could you add a changelog entry?

Sure - would you prefer that I add a specific version number, or use a placeholder like NEXT? The current state of CHANGELOG.md does not reflect the next intended release yet. I think it also needs to be amended a bit, because it claims that yesterday's release was made in 2023. :)

@kradalby
Copy link
Collaborator

Next is good like I did here https://github.com/juanfont/headscale/pull/2020/files#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4edR3, the release cycle should not be that long this time ™️

@enoperm
Copy link
Contributor Author

enoperm commented Sep 20, 2024

Added new changelog entry, updated the timestamp on the 0.23 release to align more with reality.

@kradalby
Copy link
Collaborator

Added new changelog entry, updated the timestamp on the 0.23 release to align more with reality.

Haha thanks, I must have been a bit quick there, well, technically I was really slow 🫠

@kradalby kradalby enabled auto-merge (squash) September 21, 2024 10:05
@kradalby kradalby merged commit 1e61084 into juanfont:main Sep 21, 2024
121 checks passed
kradalby pushed a commit to kradalby/headscale that referenced this pull request Sep 30, 2024
* handle control protocol through websocket

The necessary behaviour is already in place,
but the wasm build only issued GETs, and the handler was not invoked.

* get DERP-over-websocket working for wasm clients

* Prepare for testing builtin websocket-over-DERP

Still needs some way to assert that clients are connected through websockets,
rather than the TCP hijacking version of DERP.

* integration tests: properly differentiate between DERP transports

* do not touch unrelated code

* linter fixes

* integration testing: unexport common implementation of derp server scenario

* fixup! integration testing: unexport common implementation of derp server scenario

* dockertestutil/logs: remove unhelpful comment

* update changelog

---------

Co-authored-by: Csaba Sarkadi <sarkadicsa@tutanota.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants