<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hacker News: wh33zle</title><link>https://news.ycombinator.com/user?id=wh33zle</link><description>Hacker News RSS</description><docs>https://hnrss.org/</docs><generator>hnrss v2.1.1</generator><lastBuildDate>Wed, 15 Apr 2026 07:47:55 +0000</lastBuildDate><atom:link href="https://hnrss.org/user?id=wh33zle" rel="self" type="application/rss+xml"></atom:link><item><title><![CDATA[New comment by wh33zle in "Tailscale is pretty useful"]]></title><description><![CDATA[
<p>How low do you need the footprint? At firezone.dev, we also build a ZT product and our headless-client and Gateway are in Rust and use around 15-30 MB of RAM. Could likely be tuned further down if you need it :)</p>
]]></description><pubDate>Thu, 06 Mar 2025 11:14:07 +0000</pubDate><link>https://news.ycombinator.com/item?id=43278811</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=43278811</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=43278811</guid></item><item><title><![CDATA[New comment by wh33zle in "How Core Git Developers Configure Git"]]></title><description><![CDATA[
<p>One of the coolest things I've learned about recently is `.git/info/exclude`. It allows you to ignore files in the local repo without modifying the repo's .gitignore<p>Very useful if you want to add your own .envrc or shell.nix to a repo.</p>
]]></description><pubDate>Tue, 25 Feb 2025 10:44:00 +0000</pubDate><link>https://news.ycombinator.com/item?id=43170267</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=43170267</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=43170267</guid></item><item><title><![CDATA[New comment by wh33zle in "Launch HN: Firezone (YC W22) – Zero-trust access platform built on WireGuard"]]></title><description><![CDATA[
<p>Depends. MASQUE implies QUIC and a lot of coporate networks still block QUIC, forcing a fallback to TCP (for web browsers).<p>Not sure how they want to address this in case of WARP?<p>Nevertheless, MASQUE is some pretty exciting development in the network space.</p>
]]></description><pubDate>Thu, 15 Aug 2024 06:11:39 +0000</pubDate><link>https://news.ycombinator.com/item?id=41253508</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=41253508</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=41253508</guid></item><item><title><![CDATA[New comment by wh33zle in "Don't write Rust like it's Java (2023)"]]></title><description><![CDATA[
<p>I find myself defining my own traits very rarely these days. Traits enable polymorphic actions on data. Most of the time, you simply don't need that.<p>An enum often suffices to express the different combinations.<p>Of course, from a pattern perspective, that is "not extensible" but it often ends up being easier to maintain and in Rust, often compiles down to more efficient code.</p>
]]></description><pubDate>Fri, 09 Aug 2024 12:27:48 +0000</pubDate><link>https://news.ycombinator.com/item?id=41201165</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=41201165</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=41201165</guid></item><item><title><![CDATA[New comment by wh33zle in "Orca: WebAssembly Apps Without the Web"]]></title><description><![CDATA[
<p>WASM shouldn't care about which language the host is written in vs what the apps are written in.<p>Writing the host in C seems like a bad choice though. Isn't that exactly the kind of software you want to ensure is memory-safe?</p>
]]></description><pubDate>Tue, 30 Jul 2024 07:10:51 +0000</pubDate><link>https://news.ycombinator.com/item?id=41106760</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=41106760</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=41106760</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>> An async method, basically does the same, but it means your logic doesn't have to handle the polling, orchestration, selection, ordering, etc.<p>In order to take advantage of that, I'd need to create one `async` method for each handshake with the TURN server, right? Like every `allocate`, every `refresh`, every `bind_channel` would need to be its own async function that takes in a `Sink` and `Stream` in order to get the benefit of having the Rust compiler generate the suspense-point for me upon waiting on the response. Who is driving these futures? Would you expect these to be be spawned into a runtime or would you use structured concurrency a la `FuturesUnordered`?<p>A single `Allocation` sends multiple of these requests and in the case of `bind_channel` concurrently. How would I initialise them? As far as I understand, I would need to be able to "split" an existing `Stream` + `Sink` pair by inserting another multiplexer in order to get a `Stream` that will only return responses for the messages that have been sent via its `Sink`, right?<p>I just don't see the appeal of all this infrastructure code to "gain" the code-gen that the Rust compiler can do for these request-response protocols. It took my a while to wrap my head around how `Node` should work but the state isn't that difficult to manage because it is all just request-response protocols. If the protocols would have more steps (i.e. more `.await` points in an async style), then it might get cumbersome and we'd need to think about an alternative.<p>> > It is doable but creates the kind of spaghetti code of channels where concerns are implemented in many different parts of the code. It also makes these things harder to test because I now need to wire up this orchestrating task with the TURN clients themselves to ensure this response mapping works correctly.<p>> I read through the node code and it feels a bit spaghetti to me as an outsider, because of the sans-io abstractions, not inspite of it.
> That comes from nature of the driving code being external to the code implementing the parts.<p>It is interesting you say that. I've found that I never need to think about the driving code because it is infrastructure that is handled on a completely different layer. Instead, I can focus on the logic I want to implement.<p>The aspect I really like about the structure is that the call-stack in the source code corresponds to the data flow at runtime. That means I can mentally trace a packet via "jump to definition" in my editor. To me, that is the opposite of spaghetti code. Channels and tasks obfuscate the data flow and you end up jumping between definition site of a channel, tracing where it was initialised, followed by navigating to where the other end is handled etc.</p>
]]></description><pubDate>Wed, 17 Jul 2024 06:30:53 +0000</pubDate><link>https://news.ycombinator.com/item?id=40983122</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40983122</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40983122</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>Thinking about your example again made me think of another requirement: Multiplexing.<p>STUN ties requests and responses together with a transaction ID that is encoded as an attribute in the message.<p>Modelling this using `Sink`s and `Stream`s is quite difficult. Sending out the request via a `Sink` is easy but how do you correctly dispatch the incoming response again?<p>For example, let's say you are running 3 TURN clients concurrently in 3 async tasks that use the referenced `Sink` + `Stream` abstractions. How do you go from reading from a single UDP socket to 3 streams that each contain the correct responses based on the transaction ID that was sent out? To read the transaction ID, you'd have to parse the message. We've established that we can do that partially outside and only send the `Result` in. But in addition to the 3 tasks, we'd need an orchestrating task that keeps a temporary state of sent transaction IDs (received via one of the `Sink`s) and then picks the correct `Stream` for the response.<p>It is doable but creates the kind of spaghetti code of channels where concerns are implemented in many different parts of the code. It also makes these things harder to test because I now need to wire up this orchestrating task with the TURN clients themselves to ensure this response mapping works correctly.</p>
]]></description><pubDate>Mon, 15 Jul 2024 05:41:53 +0000</pubDate><link>https://news.ycombinator.com/item?id=40965545</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40965545</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40965545</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>> The stream is a stream of Result<ParsedMessage, ErrorMessage><p>Fair point! Yeah that would allow error handling the state machine.<p>> > Lastly, and this is where using async falls apart: If you try to model these state machines with async, you can't use `&mut` to update shared state between them AND run multiple of these concurrently.<p>> I'm not sure that I see your point here. I think it's just you never have to think about concurrent access if there's no concurrency, but I'm sure I'm misunderstanding this somehow. Maybe you could expand on this a little?<p>Yeah sure! If you are up for it, you can dig into the actual code here: <a href="https://github.com/firezone/firezone/tree/main/rust/connlib/snownet">https://github.com/firezone/firezone/tree/main/rust/connlib/...</a><p>The requirements are:<p><pre><code>    1. Run N clients of the TURN protocol.
    2. Run M connections (ICE agent + WireGuard tunnel).
    3. Allow each connection to use each TURN client.
    4. Everything must run over a single UDP socket (otherwise hole-punching doesn't work).
</code></pre>
In theory, (3) could be relaxed a bit to not be a NxM matrix. It doesn't really change much though because one TURN allocation (i.e. remote address) needs to be usable by multiple connections.<p>All of the above is managed as a single `Node` that is composed into a larger state machine that manages ACLs and uses this library to route IP packets to and from a TUN device. That is where the concurrent access comes in: We receive ACL updates via a WebSocket and change the routing logic as a result. Concurrently, we need to read from the TUN device and work out which WireGuard tunnel the packets needs to go through. A WireGuard tunnel may use one of the TURN clients to relay the packet in case a direct connection isn't possible. Lastly, we also need to read from the UDP socket, index into the correct connection based on the IP and decrypt incoming IP packets.<p>In summary, there are lots of state updates happening from multiple events and the state cannot be isolated into a single task where `&mut` would be an option. Instead, we need `&mut` access to pretty much everything upon each UDP packet from the network or IP packet from the TUN device.<p>> The Stun example is simple enough that it is achievable just with an async state machine.<p>I am fully aware of that. It needed to be simple to fit into the scope of a blog post that can be understood by people without a deep networking background. To explain sans-IO, I deliberately wanted a simple example so I could focus on sans-IO itself and not explaining the problem domain. With the basics in place, people can work out by themselves whether or not it is a good fit for their problem. Chances are, if they are struggling with lots of shared state between tasks, it could be!</p>
]]></description><pubDate>Sun, 14 Jul 2024 06:08:15 +0000</pubDate><link>https://news.ycombinator.com/item?id=40959185</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40959185</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40959185</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>This isn't an adequat comparison in my eyes. Your example moves a very critical component out if the "protocol logic": message parsing.<p>Dealing with invalid messages is crucial in network protocols, meaning the API should be `&[u8]` instead of parsed messages.<p>In addition, you want to be able to parse messages without copying, which will introduce lifetimes into this that will make it difficult to use an exexutor like tokio to run this future. You can use a structured-concurrency approach instead. I linked to an article from withoutboats about this at the end.<p>Lastly, and this is where using async falls apart: If you try to model these state machines with async, you can't use `&mut` to update shared state between them AND run multiple of these concurrently.<p>EDIT: Just to be clear, I'd love to use the Rust compiler to generate these state machines but it simply can't do what I want (yet). I wrote some pseudo-code somewhere else in this thread of how this could be done using co-routines. That gets us closer to this but I've not seen any movement on the front of stabilising coroutines.</p>
]]></description><pubDate>Mon, 08 Jul 2024 04:18:58 +0000</pubDate><link>https://news.ycombinator.com/item?id=40902579</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40902579</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40902579</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>> [EDIT2] Any good recommendations of a tiny protocol that might be a good walk through intro to this?
>
> Something even simpler than Gopher or SMTP? Would be nice to have a really small thing to do a tiny project in.<p>I only have experience in packet-oriented ones so I'd suggest sticking to that. Perhaps WireGuard could be simple enough? It has a handshake and timers so some complexity but nothing too crazy.<p>DNS could be interesting too, because you may need to contact upstream resolvers if you don't have something cached.</p>
]]></description><pubDate>Thu, 04 Jul 2024 14:53:18 +0000</pubDate><link>https://news.ycombinator.com/item?id=40875424</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40875424</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40875424</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>> What am I missing here? From unrelated code, I want to call `get_ip_via_stun_or_timeout(hostnames: &[String], timeout: Duration) -> Option<String>`, is what I'm missing that I need to wrap this state machine in another to pass it up to the level above? That I need to essentially move the who-must-implement-the-event-loop one level up?<p>Essentially yes! For such a simple example as STUN, it may appear silly because the code that is abstracted away in a state machine is almost shorter than the event loop itself.<p>That very quickly changes as the complexity of your protocol increases though. The event loop is always roughly the same size yet the protocol can be almost arbitrarily nested and still reduces down to an API of `handle/poll_timeout`, `handle_input` & `handle_transmit`.<p>For example, we've been considering adding a QUIC stack next to the WireGuard tunnels as a control protocol in `snownet`. By using a sans-IO QUIC implementation like quinn, I can do that entirely as an implementation detail because it just slots into the existing state machine, next to ICE & WireGuard.<p>> At this point if I think I want to write a library that supports both sync and async use cases it feels like feature flags & separate implementations might produce an easier to understand outcome for me -- the sync version can even start as mostly `tokio::Runtime::block_on`s, and graduate to a more performant version with better custom-tailored efficiency (i.e. busy waiting).<p>> Of course, I'm not disparaging the type state pattern here/using state machines -- just that I'd probably just use that from inside an async/sync-gated modules (and be able to share that code between two impls).<p>This is what quinn does: It uses tokio + async to expose an API that uses `AsyncRead` and `AsyncWrite` and thus fully buys into the async ecosystem. The actual protocol implementation however - quinn-proto - is sans-IO.<p>The way I see this is that you can always build more convenience layers, whether or not they are in the same crate or not doesn't really matter for that. The key thing is that they should be optional. The problems of function colouring only exist if you don't focus on building the right thing: an IO-free implementation of your protocol. The protocol implementation is usually the hard bit, the one that needs to be correct and well-tested. Integration with blocking or non-blocking IO is just plumbing work that isn't difficult to write.</p>
]]></description><pubDate>Thu, 04 Jul 2024 13:33:52 +0000</pubDate><link>https://news.ycombinator.com/item?id=40874829</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40874829</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40874829</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>> If I want to write a function that listens or times out in sans-IO style, should I use tokio::select? If so, where is the async runtime coming from, and how will the caller of the function be able to avoid caring?<p>To "time-out" in sans-IO style means that your state machine has an `Instant` internally and, once called at a specific point in the future, compares the provided `now` parameter with the internal timeout and changes its state accordingly. See [0] for an example.<p>> but is absolutely the domain of a random library function that you might want to expose.<p>That entire `main` function is _not_ what you would expose as a library. The event loop should always live as high up in the stack as possible, thereby deferring the use of blocking or non-blocking IO and allowing composition with other sans-IO components.<p>You can absolutely write an event loop without async. You can set the read-timeout of the socket to the value of `poll_timeout() - Instant::now` and call `handle_timeout` in case your `UdpSocket::recv` call errors with a timeout. str0m has an example [1] like that in their repository.<p>> It's a bit jarring to introduce the concept as not requiring choices like async vs not, then immediately require the use of async in the event loop (required to drive the state machine to completion).<p>All the event loops you see in the post are solely there to ensure we have a working program but are otherwise irrelevant, esp. implementation details like using `tokio::select` and the like. Perhaps I should have made that clearer.<p>[0]: <a href="https://github.com/firezone/firezone/blob/1e7d3a40d213c9524adcfeb4eda52b27b0f0591d/rust/connlib/snownet/src/node.rs#L1438-L1445">https://github.com/firezone/firezone/blob/1e7d3a40d213c9524a...</a>
[1]: <a href="https://github.com/algesten/str0m/blob/5b100e8a675cd8838cdd86b40aa8191ad935a34d/examples/chat.rs#L142-L160">https://github.com/algesten/str0m/blob/5b100e8a675cd8838cdd8...</a></p>
]]></description><pubDate>Thu, 04 Jul 2024 12:10:22 +0000</pubDate><link>https://news.ycombinator.com/item?id=40874240</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40874240</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40874240</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>The line between applications and libraries is fairly blurry, isn't it? In my experience, most applications grow to the point where you have internal libraries or could at least split out one or more crates.<p>I would go as far as saying that whatever functionality your application provides, there is a core that can be modelled without depending on IO primitives.</p>
]]></description><pubDate>Thu, 04 Jul 2024 11:40:18 +0000</pubDate><link>https://news.ycombinator.com/item?id=40874082</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40874082</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40874082</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>> I would have been more interested in seeing how they could implement an encapsulated function in the sans-IO style that had to do something like wait on an action or a timer<p>The "encapsulated function" is the `StunBinding` struct. It represents the functionality of a STUN binding. It isn't a single function you can just call, instead it requires an eventloop.<p>The point though is, that `StunBinding` could live in a library and you would be able to use it in your application by composing it into your program's state machine (assuming you are also structuring it in a sans-IO style).<p>The linked `snownet` library does exactly this. Its domain is to combine ICE + WireGuard (without doing IO) which is then used by the `connlib` library that composes ACLs on top of it.<p>Does that make sense?<p>EDIT: There is no busy-waiting. Instead, `StunBinding` has a function that exposes, what it is waiting for using `poll_timeout`. How the caller (i.e. eventloop) makes that happen is up to them. The appropriate action will happen once `handle_timeout` gets called with the corresponding `Instant`.</p>
]]></description><pubDate>Thu, 04 Jul 2024 11:32:15 +0000</pubDate><link>https://news.ycombinator.com/item?id=40874048</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40874048</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40874048</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>I too came from the OOP world to Rust (6 years ago now) and in my first 2-3 years I produced horrible code!<p>Type parameters and traits everywhere. Structs being (ab-)used as class-like structures that provide functionality.<p>Rust works better if you avoid type parameters and defining your own traits for as much as possible.<p>Encapsulation is good if we talk about ensuring invariants are maintained. This blog post about parse, don't validate comes to my mind: <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/" rel="nofollow">https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...</a></p>
]]></description><pubDate>Thu, 04 Jul 2024 11:25:02 +0000</pubDate><link>https://news.ycombinator.com/item?id=40874006</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40874006</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40874006</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>Channels work fine if you are happy for your software to have an actor-like design.<p>But as you say, it comes with problems: Actors / channels can be disconnected for example. You also want to make sure they are bounded otherwise you don't have backpressure. Plus, they require copying so achieving high-throughput may be tricky.</p>
]]></description><pubDate>Thu, 04 Jul 2024 07:16:38 +0000</pubDate><link>https://news.ycombinator.com/item?id=40872995</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40872995</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40872995</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>If Rust ever gets a native generator syntax, this might be become achievable because one would be able to say: `yield transmit` to "write" data whilst staying within the context of your async operation. In other words, every `socket.write` would turn into a `yield transmit`.<p>To read data, the generator would suspend (.await) and wait to be resumed with incoming data. I am not sure if there is nightly syntax for this but it would have to look something like:<p><pre><code>  // Made up `gen` syntax: gen(yield_type, resume_type)
  gen(Transmit, &[u8]) fn stun_binding(server: SocketAddr) -> SocketAddr {
   let req = make_stun_request();

   yield Transmit {
      server,
      payload: req
   };

   let res = .await; // Made up "suspend and resume with argument"-syntax.
   
   let addr = parse_stun_response(res);

   addr
 }</code></pre></p>
]]></description><pubDate>Thu, 04 Jul 2024 06:52:39 +0000</pubDate><link>https://news.ycombinator.com/item?id=40872897</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40872897</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40872897</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>They actually play together fairly well higher up the stack. Non-blocking IO (i.e async) makes it easy to concurrently wait for socket IO and time. You can do it with blocking IO too by setting a read-timeout on the socket but using async primitives makes it a bit easier.<p>But I've also been mulling over the idea how they could be combined! One thing I've arrived at is the issue that async functions compile into opaque types. That makes it hard / impossible to use the compiler's facility of code-generating the state machine because you can't interact with it once it has been created. This also breaks the borrow-checker in some way.<p>For example, if I have an async operation with multiple steps (i.e. `await` points) but only one section of those needs a mutable reference to some shared data structure. As soon as I express this using an `async` function, the mutable reference is captured in the generated `Future` type which spans across all steps. As a result, Rust doesn't allow me to run more than one of those concurrently.<p>Normally, the advice for these situations is "only capture the mutable reference for as short as possible" but in the case of async, I can't do that. And splitting the async function into multiple also gets messy and kind of defeats the point of wanting to express everything in a single function again.</p>
]]></description><pubDate>Thu, 04 Jul 2024 06:07:21 +0000</pubDate><link>https://news.ycombinator.com/item?id=40872691</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40872691</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40872691</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>Yes, traffic is routed to the gateway through a WireGuard tunnel. Broadly speaking, what happens is:<p>- Client and gateway perform ICE to agree on a socket pair (this is where hole-punching happens or if that fails, a relay is used)<p>- The socket pair determined by ICE is used to set up a WireGuard tunnel (i.e. a noise handshake using ephemeral keys).<p>- IP traffic is read from the TUN device and sent via the WireGuard tunnel to the gateway.<p>- Gateway decrypts it and emits it as a packet from its TUN device, thereby forwarding it to the actual destination.<p>It is worth noting that a WireGuard tunnel in this case is "just" the Noise Protocol [0] layered on top of UDP. This ensures the traffic is end-to-end encrypted.<p>[0]: <a href="https://noiseprotocol.org" rel="nofollow">https://noiseprotocol.org</a></p>
]]></description><pubDate>Thu, 04 Jul 2024 05:36:09 +0000</pubDate><link>https://news.ycombinator.com/item?id=40872551</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40872551</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40872551</guid></item><item><title><![CDATA[New comment by wh33zle in "Sans-IO: The secret to effective Rust for network services"]]></title><description><![CDATA[
<p>Haha thank you!<p>Yes there are indeed similarities to rust-libp2p! Over there, things are more interleaved though because the actual streams and connections are still within `Future`-like constructs and not strictly split like in the sans-IO case here.</p>
]]></description><pubDate>Thu, 04 Jul 2024 05:14:58 +0000</pubDate><link>https://news.ycombinator.com/item?id=40872470</link><dc:creator>wh33zle</dc:creator><comments>https://news.ycombinator.com/item?id=40872470</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=40872470</guid></item></channel></rss>