diff options
Diffstat (limited to 'blog/anything-message-queue.markdown')
| -rw-r--r-- | blog/anything-message-queue.markdown | 707 |
1 files changed, 0 insertions, 707 deletions
diff --git a/blog/anything-message-queue.markdown b/blog/anything-message-queue.markdown deleted file mode 100644 index 8acb0b3..0000000 --- a/blog/anything-message-queue.markdown +++ /dev/null @@ -1,707 +0,0 @@ ---- -title: "Anything can be a message queue if you use it wrongly enough" -date: 2023-06-04 -tags: - - aws - - cursed - - tuntap - - satire ---- - -<div class="warning"><xeblog-conv name="Cadey" mood="coffee" -standalone>Hi, <span id="hnwarning">readers</span>! This post is -satire. Don't treat it as something that is viable for production -workloads. By reading this post you agree to never implement or use -this accursed abomination. This article is released to the public for -educational reasons. Please do not attempt to recreate any of the -absurd acts referenced here.</xeblog-conv></div> - -<xeblog-hero ai="Ligne Claire" file="nihao-xiyatu" prompt="1girl, green hair, green eyes, landscape, hoodie, backpack, space needle"></xeblog-hero> - -<script> -if (document.referrer.match(/news.ycombinator.com/)) { - document.getElementById("hnwarning").innerText = "Hacker News users"; -} -</script> - -You may think that the world is in a state of relative peace. Things -look like they are somewhat stable, but reality couldn't be farther -from the truth. There is an enemy out there that transcends time, -space, logic, reason, and lemon-scented moist towelettes. That enemy -is a scourge of cloud costs that is likely the single reason why -startups die from their cloud bills when they are so young. - -The enemy is [Managed NAT -Gateway](https://aws.amazon.com/blogs/aws/new-managed-nat-network-address-translation-gateway-for-aws/). -It is a service that lets you egress traffic from a VPC to the public -internet at $0.07 per gigabyte. This is something that is probably -literally free for them to run but ends up getting a huge chunk of -their customer's cloud spend. Customers don't even look too deep into -this because they just shrug it off as the cost of doing business. - -This one service has allowed companies like [the duckbill -group](https://www.duckbillgroup.com/) to make _millions_ by showing -companies how to not spend as much on the cloud. - -However, I think I can do one better. What if there was a _better_ way -for your own services? What if there was a way you could reduce that -cost for your own services by up to 700%? What if you could bypass -those pesky network egress costs yet still contact your machines over -normal IP packets? - -<xeblog-conv name="Aoi" mood="coffee">Really, if you are trying to -avoid Managed NAT Gateway in production for egress-heavy workloads -(such as webhooks that need to come from a common IP address), you -should be using a [Tailscale](https://www.tailscale.com) [exit -node](https://tailscale.com/kb/1103/exit-nodes/) with a public -IPv4/IPv6 address attached to it. If you also attach this node to the -same VPC as your webhook egress nodes, you can basically recreate -Managed NAT Gateway at home. You also get the added benefit of -encrypting your traffic further on the wire.<br /><br />This is the -only thing in this article that you can safely copy into your -production workloads.</xeblog-conv> - -## Base facts - -Before I go into more detail about how this genius creation works, -here's some things to consider: - -When AWS launched originally, it had three services: - -- [S3](https://en.wikipedia.org/wiki/Amazon_S3) - Object storage for - cloud-native applications -- [SQS](https://en.wikipedia.org/wiki/Amazon_Simple_Queue_Service) - A - message queue -- [EC2](https://en.wikipedia.org/wiki/Amazon_Elastic_Compute_Cloud) - - A way to run Linux virtual machines somewhere - -Of those foundational services, I'm going to focus the most on S3: the -Simple Storage Service. In essence, S3 is `malloc()` for the cloud. - -<xeblog-conv name="Mara" mood="hacker" standalone>If you already know -what S3 is, please click [here](#postcloud) to skip this explanation. -It may be worth revisiting this if you do though!</xeblog-conv> - -### The C programming language - -When using the C programming language, you normally are working with -memory in the stack. This memory is almost always semi-ephemeral and -all of the contents of the stack are no longer reachable (and -presumably overwritten) when you exit the current function. You can do -many things with this, but it turns out that this isn't very useful in -practice. To work around this (and reliably pass mutable values -between functions), you need to use the -[`malloc()`](https://www.man7.org/linux/man-pages/man3/malloc.3.html) -function. `malloc()` takes in the number of bytes you want to allocate -and returns a pointer to the region of memory that was allocated. - -<xeblog-conv name="Aoi" mood="sus">Huh? That seems a bit easy for C. -Can't allocating memory fail when there's no more free memory to -allocate? How do you handle that?</xeblog-conv> -<xeblog-conv name="Mara" mood="happy">Yes, allocating memory can -fail. When it does fail it returns a null pointer and sets the -[errno](https://www.man7.org/linux/man-pages/man3/errno.3.html) -superglobal variable to the constant `ENOMEM`. From here all behavior -is implementation-defined.</xeblog-conv> -<xeblog-conv name="Aoi" mood="coffee">Isn't "implementation-defined" -code for "it'll probably crash"?</xeblog-conv> -<xeblog-conv name="Mara" mood="hacker">In many cases: yes most of the -time it will crash. Hard. Some applications are smart enough to handle -this more gracefully (IE: try to free memory or run a garbage -collection run), but in many cases it doesn't really make more sense -to do anything but crash the program.</xeblog-conv> -<xeblog-conv name="Aoi" mood="facepalm">Oh. Good. Just what I wanted -to hear.</xeblog-conv> - -When you get a pointer back from `malloc()`, you can store anything in -there as long as it's the same length as you passed or less. - -<xeblog-conv name="Numa" mood="delet" standalone>Fun fact: if you -overwrite the bounds you passed to `malloc()` and anything involved in -the memory you are writing is user input, congradtulations: you just -created a way for a user to either corrupt internal application state -or gain arbitrary code execution. A similar technique is used in -The Legend of Zelda: Ocarina of Time speedruns in order to get -arbitrary code execution via [Stale Reference -Manipulation](https://www.zeldaspeedruns.com/oot/srm/srm-overview).</xeblog-conv> - -Oh, also anything stored in that pointer to memory you got back from -`malloc()` is stored in an area of ram called "the heap", which is -moderately slower to access than it is to access the stack. - -### S3 in a nutshell - -Much in the same way, S3 lets you allocate space for and submit -arbitrary bytes to the cloud, then fetch them back with an address. -It's a lot like the `malloc()` function for the cloud. You can put -bytes there and then refer to them between cloud functions. - -<xeblog-conv name="Mara" mood="hacker" standalone>The bytes are stored -in the cloud, which is slightly slower to read from than it would be -to read data out of the heap.</xeblog-conv> - -And these arbitrary bytes can be _anything_. S3 is usually used for -hosting static assets (like all of the conversation snippet avatars -that a certain website with an orange background hates), but nothing -is stopping you from using it to host literally anything you want. -Logging things into S3 is so common it's literally a core product -offering from Amazon. Your billing history goes into S3. If you -download your tax returns from WealthSimple, it's probably downloading -the PDF files from S3. VRChat avatar uploads and downloads are done -via S3. - -<xeblog-conv name="Mara" mood="happy" standalone>It's like an FTP -server but you don't have to care about running out of disk space on -the FTP server!</xeblog-conv> - -### IPv6 - -You know what else is bytes? [IPv6 -packets](https://en.wikipedia.org/wiki/IPv6_packet). When you send an -IPv6 packet to a destination on the internet, the kernel will prepare -and pack a bunch of bytes together to let the destination and -intermediate hops (such as network routers) know where the packet -comes from and where it is destined to go. - -Normally, IPv6 packets are handled by the kernel and submitted to a -queue for a hardware device to send out over some link to the -Internet. This works for the majority of networks because they deal -with hardware dedicated for slinging bytes around, or in some cases -shouting them through the air (such as when you use Wi-Fi or a mobile -phone's networking card). - -<xeblog-conv name="Aoi" mood="coffee">Wait, did you just say that -Wi-Fi is powered by your devices shouting at eachother?</xeblog-conv> -<xeblog-conv name="Cadey" mood="aha">Yep! Wi-Fi signal strength is -measured in decibels even!</xeblog-conv> -<xeblog-conv name="Numa" mood="delet">Wrong. Wi-Fi is more accurately -_light_, not _sound_. It is much more accurate to say that the devices -are _shining_ at eachother. Wi-Fi is the product of radio waves, which -are the same thing as light (but it's so low frequency that you can't -see it). Boom. Roasted.</xeblog-conv> - -### The core Unix philosophy: everything is a file - -<span id="postcloud"></span> -There is a way to bypass this and have software control how network -links work, and for that we need to think about Unix conceptually for -a second. In the hardcore Unix philosophical view: everything is a -file. Hard drives and storage devices are files. Process information -is viewable as files. Serial devices are files. This core philosophy -is rooted at the heart of just about everything in Unix and Linux -systems, which makes it a lot easier for applications to be -programmed. The same API can be used for writing to files, tape -drives, serial ports, and network sockets. This makes everything a lot -conceptually simpler and reusing software for new purposes trivial. - -<xeblog-conv name="Mara" mood="hacker" standalone>As an example of -this, consider the -[`tar`](https://man7.org/linux/man-pages/man1/tar.1.html) command. The -name `tar` stands for "Tape ARchive". It was a format that was created -for writing backups [to actual magnetic tape -drives](https://en.wikipedia.org/wiki/Tape_drive). Most commonly, it's -used to download source code from GitHub or as an interchange format -for downloading software packages (or other things that need to put -multiple files in one distributable unit).</xeblog-conv> - -In Linux, you can create a -[TUN/TAP](https://en.wikipedia.org/wiki/TUN/TAP) device to let -applications control how network or datagram links work. In essence, -it lets you create a file descriptor that you can read packets from -and write packets to. As long as you get the packets to their intended -destination somehow and get any other packets that come back to the -same file descriptor, the implementation isn't relevant. This is how -OpenVPN, ZeroTier, FreeLAN, Tinc, Hamachi, WireGuard and Tailscale -work: they read packets from the kernel, encrypt them, send them to -the destination, decrypt incoming packets, and then write them back -into the kernel. - -### In essence - -So, putting this all together: - -* S3 is `malloc()` for the cloud, allowing you to share arbitrary - sequences of bytes between consumers. -* IPv6 packets are just bytes like anything else. -* TUN devices let you have arbitrary application code control how - packets get to network destinations. - -In theory, all you'd need to do to save money on your network bills -would be to read packets from the kernel, write them to S3, and then -have another loop read packets from S3 and write those packets back -into the kernel. All you'd need to do is wire things up in the right -way. - -So I did just that. - -Here's some of my friends' reactions to that list of facts: - -- I feel like you've just told me how to build a bomb. I can't belive - this actually works but also I don't see how it wouldn't. This is - evil. -- It's like using a warehouse like a container ship. You've put a - warehouse on wheels. -- I don't know what you even mean by that. That's a storage method. - Are you using an extremely generous definition of "tunnel"? -- sto psto pstop stopstops -- We play with hypervisors and net traffic often enough that we know - that this is something someone wouldn't have thought of. -- Wait are you planning to actually *implement and use* ipv6 over - s3? -- We're paying good money for these shitposts :) -- Is routinely coming up with cursed ideas a requirement for working - at tailscale? -- That is horrifying. Please stop torturing the packets. This is a - violation of the Geneva Convention. -- Please seek professional help. - -<xeblog-conv name="Cadey" mood="enby" standalone>Before any of you -ask, yes, this was the result of a drunken conversation with [Corey -Quinn](https://twitter.com/quinnypig).</xeblog-conv> - -## Hoshino - -Hoshino is a system for putting outgoing IPv6 packets into S3 and then -reading incoming IPv6 packets out of S3 in order to avoid the absolute -dreaded scourge of Managed NAT Gateway. It is a travesty of a tool -that does work, if only barely. - -The name is a reference to the main character of the anime [Oshi no -Ko](https://en.wikipedia.org/wiki/Oshi_no_Ko), Hoshino Ai. Hoshino is -an absolute genius that works as a pop idol for the group B-Komachi. - -Hoshino is a shockingly simple program. It creates a TUN device, -configures the OS networking stack so that programs can use it, and -then starts up two threads to handle reading packets from the kernel -and writing packets into the kernel. - -When it starts up, it creates a new TUN device named either `hoshino0` -or an administrator-defined name with a command line flag. This -interface is only intended to forward IPv6 traffic. - -Each node derives its IPv6 address from the -[`machine-id`](https://www.man7.org/linux/man-pages/man5/machine-id.5.html) -of the system it's running on. This means that you can somewhat -reliably guarantee that every node on the network has a unique address -that you can easily guess (the provided ULA /64 and then the first -half of the `machine-id` in hex). Future improvements may include -publishing these addresses into DNS via Route 53. - -When it configures the OS networking stack with that address, it uses -a [netlink](https://en.wikipedia.org/wiki/Netlink) socket to do this. -Netlink is a Linux-specific socket family type that allows userspace -applications to configure the network stack, communicate to the -kernel, and communicate between processes. Netlink sockets cannot -leave the current host they are connected to, but unlike Unix sockets -which are addressed by filesystem paths, Netlink sockets are addressed -by process ID numbers. - -In order to configure the `hoshino0` device with Netlink, Hoshino does -the following things: - -- Adds the node's IPv6 address to the `hoshino0` interface -- Enables the `hoshino0` interface to be used by the kernel -- Adds a route to the IPv6 subnet via the `hoshino0` interface - -Then it configures the AWS API client and kicks off both of the main -loops that handle reading packets from and writing packets to the -kernel. - -When uploading packets to S3, the key for each packet is derived from -the destination IPv6 address (parsed from outgoing packets using the -handy library -[gopacket](https://pkg.go.dev/github.com/google/gopacket)) and the -packet's unique ID (a -[ULID](https://pkg.go.dev/github.com/oklog/ulid/v2) to ensure that -packets are lexicographically sortable, which will be important to -ensure in-order delivery in the other loop). - -When packets are processed, they are added to a -[bundle](https://pkg.go.dev/within.website/x/internal/bundler) for -later processing by the kernel. This is relatively boring code and -understanding it is mostly an exercise for the reader. `bundler` is -based on the Google package -[`bundler`](https://pkg.go.dev/google.golang.org/api/support/bundler), -but modified to use generic types because the original -implementation of `bundler` predates them. - -### cardio - -However, the last major part of understanding the genius at play here -is by the use of [cardio](https://pkg.go.dev/within.website/x/cardio). -Cardio is a utility in Go that lets you have a "heartbeat" for events -that should happen every so often, but also be able to influence the -rate based on need. This lets you speed up the rate if there is more -work to be done (such as when packets are found in S3), and reduce the -rate if there is no more work to be done (such as when no packets are -found in S3). - -<xeblog-conv name="Aoi" mood="coffee" standalone>Okay, this is also -probably something that you can use outside of this post, but I -promise there won't be any more of these!</xeblog-conv> - -When using cardio, you create the heartbeat channel and signals like -this: - -```go -heartbeat, slower, faster := cardio.Heartbeat(ctx, time.Minute, time.Millisecond) -``` - -The first argument to `cardio.Heartbeat` is a -[`context`](https://pkg.go.dev/context) that lets you cancel the -heartbeat loop. Additionally, if your application uses -[`ln`](https://xeiaso.net/blog/ln-the-natural-logger-2020-10-17)'s -[`opname`](https://pkg.go.dev/within.website/ln/opname) facility, an -[`expvar`](https://pkg.go.dev/expvar) gauge will be created and named -after that operation name. - -The next two arguments are the minimum and maximum heart rate. In this -example, the heartbeat would range between once per minute and once -per millisecond. - -When you signal the heart rate to speed up, it will double the rate. -When you trigger the heart rate to slow down, it will halve the rate. -This will enable applications to spike up and gradually slow down as -demand changes, much like how the human heart will speed up with -exercise and gradually slow down as you stop exercising. - -When the heart rate is too high for the amount of work needed to be -done (such as when the heartbeat is too fast, much like tachycardia in -the human heart), it will automatically back off and signal the heart -rate to slow down (much like I wish would happen to me sometimes). - -This is a package that I always wanted to have exist, but never found -the need to write for myself until now. - -### Terraform - -Like any good recovering SRE, I used -[Terraform](https://www.terraform.io/) to automate creating -[IAM](https://aws.amazon.com/iam/) users and security policies for -each of the nodes on the Hoshino network. This also was used to create -the S3 bucket. Most of the configuration is fairly boring, but I did -run into an issue while creating the policy documents that I feel is -worth pointing out here. - -I made the "create a user account and policies for that account" logic -into a Terraform module because that's how you get functions in -Terraform. It looked like this: - -```hcl -data "aws_iam_policy_document" "policy" { - statement { - actions = [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket", - ] - effect = "Allow" - resources = [ - var.bucket_arn, - ] - } - - statement { - actions = ["s3:ListAllMyBuckets"] - effect = "Allow" - resources = ["*"] - } -} -``` - -When I tried to use it, things didn't work. I had given it the -permission to write to and read from the bucket, but I was being told -that I don't have permission to do either operation. The reason this -happened is because my statement allowed me to put objects to the -bucket, but not to any path INSIDE the bucket. In order to fix this, I -needed to make my policy statement look like this: - -```hcl -statement { - actions = [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket", - ] - effect = "Allow" - resources = [ - var.bucket_arn, - "${var.bucket_arn}/*", # allow every file in the bucket - ] -} -``` - -This does let you do a few cool things though, you can use this to -create per-node credentials in IAM that can only write logs to their -part of the bucket in particular. I can easily see how this can be -used to allow you to have infinite flexibility in what you want to do, -but good lord was it inconvenient to find this out the hard way. - -Terraform also configured the lifecycle policy for objects in the -bucket to delete them after a day. - -```hcl -resource "aws_s3_bucket_lifecycle_configuration" "hoshino" { - bucket = aws_s3_bucket.hoshino.id - - rule { - id = "auto-expire" - - filter {} - - expiration { - days = 1 - } - - status = "Enabled" - } -} -``` - -<xeblog-conv name="Cadey" mood="coffee" standalone>If I could, I would -set this to a few hours at most, but the minimum granularity for S3 -lifecycle enforcement is in days. In a loving world, this should be a -sign that I am horribly misusing the product and should stop. I did -not stop.</xeblog-conv> - -### The horrifying realization that it works - -Once everything was implemented and I fixed the last bugs related to -[the efforts to make Tailscale faster than kernel -wireguard](https://tailscale.com/blog/more-throughput/), I tried to -ping something. I set up two virtual machines with -[waifud](https://xeiaso.net/blog/series/waifud) and installed Hoshino. -I configured their AWS credentials and then started it up. Both -machines got IPv6 addresses and they started their loops. Nervously, I -ran a ping command: - -``` -xe@river-woods:~$ ping fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f -PING fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f(fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f) 56 data bytes -64 bytes from fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f: icmp_seq=1 ttl=64 time=2640 ms -64 bytes from fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f: icmp_seq=2 ttl=64 time=3630 ms -64 bytes from fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f: icmp_seq=3 ttl=64 time=2606 ms -``` - -It worked. I successfully managed to send ping packets over Amazon S3. -At the time, I was in an airport dealing with the aftermath of [Air -Canada's IT system falling the heck -over](https://www.cbc.ca/news/business/air-canada-outage-1.6861923) -and the sheer feeling of relief I felt was better than drugs. - -<xeblog-conv name="Cadey" mood="coffee" standalone>Sometimes I wonder -if I'm an adrenaline junkie for the unique feeling that you get when -your code finally works.</xeblog-conv> - -Then I tested TCP. Logically holding, if ping packets work, then TCP -should too. It would be slow, but nothing in theory would stop it. I -decided to test my luck and tried to open the other node's metrics -page: - -``` -$ curl http://[fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f]:8081 -# skipping expvar "cmdline" (Go type expvar.Func returning []string) with undeclared Prometheus type -go_version{version="go1.20.4"} 1 -# TYPE goroutines gauge -goroutines 208 -# TYPE heartbeat_hoshino.s3QueueLoop gauge -heartbeat_hoshino.s3QueueLoop 500000000 -# TYPE hoshino_bytes_egressed gauge -hoshino_bytes_egressed 3648 -# TYPE hoshino_bytes_ingressed gauge -hoshino_bytes_ingressed 3894 -# TYPE hoshino_dropped_packets gauge -hoshino_dropped_packets 0 -# TYPE hoshino_ignored_packets gauge -hoshino_ignored_packets 98 -# TYPE hoshino_packets_egressed gauge -hoshino_packets_egressed 36 -# TYPE hoshino_packets_ingressed gauge -hoshino_packets_ingressed 38 -# TYPE hoshino_s3_read_operations gauge -hoshino_s3_read_operations 46 -# TYPE hoshino_s3_write_operations gauge -hoshino_s3_write_operations 36 -# HELP memstats_heap_alloc current bytes of allocated heap objects (up/down smoothly) -# TYPE memstats_heap_alloc gauge -memstats_heap_alloc 14916320 -# HELP memstats_total_alloc cumulative bytes allocated for heap objects -# TYPE memstats_total_alloc counter -memstats_total_alloc 216747096 -# HELP memstats_sys total bytes of memory obtained from the OS -# TYPE memstats_sys gauge -memstats_sys 57625662 -# HELP memstats_mallocs cumulative count of heap objects allocated -# TYPE memstats_mallocs counter -memstats_mallocs 207903 -# HELP memstats_frees cumulative count of heap objects freed -# TYPE memstats_frees counter -memstats_frees 176183 -# HELP memstats_num_gc number of completed GC cycles -# TYPE memstats_num_gc counter -memstats_num_gc 12 -process_start_unix_time 1685807899 -# TYPE uptime_sec counter -uptime_sec 27 -version{version="1.42.0-dev20230603-t367c29559-dirty"} 1 -``` - -I was floored. It works. The packets were sitting there in S3, and I -was able to pluck out [the TCP -response](https://cdn.xeiaso.net/file/christine-static/blog/2023/hoshino/01H20ZQ3H9CW1FS9CAX6JX0NPY) -and I opened it with `xxd` and was able to confirm the source and -destination address: - -``` -00000000: 6007 0404 0711 0640 -00000008: fd5e 59b8 f71d 9a3e -00000010: c05f 7f48 de53 428f -00000018: fd5e 59b8 f71d 9a3e -00000020: 59e5 5085 744d 4a66 -``` - -It was `fd5e:59b8:f71d:9a3e:59e5:5085:744d:4a66` trying to reach -`fd5e:59b8:f71d:9a3e:c05f:7f48:de53:428f`. - -<xeblog-conv name="Aoi" mood="wut">Wait, if this is just putting -stuff into S3, can't you do deep packet inspection with Lambda [by -using the workflow for automatically generating -thumbnails](https://docs.aws.amazon.com/lambda/latest/dg/with-s3-example.html)?</xeblog-conv> -<xeblog-conv name="Numa" mood="happy">Yep! This would let you do it -fairly trivially even. I'm not sure how you would prevent things from -getting through, but you could have your lambda handler funge a TCP -packet to either side of the connection with [the `RST` flag -set](https://www.rfc-editor.org/rfc/rfc793.html#section-3.1:~:text=Reset%20Generation%0A%0A%20%20As%20a%20general%20rule%2C%20reset%20(RST)%20must%20be%20sent%20whenever%20a%20segment%20arrives%0A%20%20which%20apparently%20is%20not%20intended%20for%20the%20current%20connection.%20%20A%20reset%0A%20%20must%20not%20be%20sent%20if%20it%20is%20not%20clear%20that%20this%20is%20the%20case.) -(RFC 793: Transmission Control Protocol, the RFC that defines TCP, -page 36, section "Reset Generation"). That could let you kill -connections that meet unwanted criteria, at the cost of having to -invoke a lambda handler. I'm _pretty sure_ this is RFC-compliant, but -I'm a shitposter, not a the network police.</xeblog-conv> -<xeblog-conv name="Aoi" mood="wut">Oh. I see.<br /><br />Wait, how did -you have 1.8 kilobytes of data in that packet? Aren't packets usually -smaller than that?</xeblog-conv> -<xeblog-conv name="Mara" mood="happy">When dealing with networking -hardware, you can sometimes get _frames_ (the networking hardware -equivalent of a packet) to be up to 9000 bytes with [jumbo -frames](https://en.wikipedia.org/wiki/Jumbo_frame), but if your -hardware does support jumbo frames then you can usually get away with -9216 bytes at max.</xeblog-conv> -<xeblog-conv name="Numa" mood="delet">It's over nine-</xeblog-conv> -<xeblog-conv name="Mara" mood="hacker">Yes dear, it's over 9000. Do -keep in mind that we aren't dealing with physical network equipment -here, so realistically our packets can be up to to the limit of the -IPv6 packet header format: the oddly specific number of 65535 bytes. -This is configured by the Maximum Transmission Unit at the OS level -(though usually this defines the limit for network frames and not IP -packets). Regardless, Hoshino defaults to an MTU of 53049, which -should allow you to transfer a bunch of data in a single S3 -object.</xeblog-conv> - -## Cost analysis - -When you count only network traffic costs, the architecture has many -obvious advantages. Access to S3 is zero-rated in many cases with S3, -however the real advantage comes when you are using this cross-region. -This lets you have a worker in us-east-1 communicate with another -worker in us-west-1 without having to incur the high bandwidth cost -per gigabyte when using Managed NAT Gateway. - -However, when you count all of the S3 operations (up to one every -millisecond), Hoshino is hilariously more expensive because of simple -math you can do on your own napkin at home. - -For the sake of argument, consider the case where an idle node is -sitting there and polling S3 for packets. This will happen at the -minimum poll rate of once every 500 milliseconds. There are 24 hours -in a day. There are 60 minutes in an hour. There are 60 seconds in a -minute. There are 1000 milliseconds in a second. This means that each -node will be making 172,800 calls to S3 per day, at a cost of $<span id="hnprice1">0.86</span> -per node per day. And that's what happens with no traffic. When -traffic happens that's at least one additional `PUT`-`GET` call pair -_per-packet_. - -Depending on how big your packets are, this can cause you to easily -triple that number, making you end up with 518,400 calls to S3 per day -($<span id="hnprice2">2.59</span> per node per day). Not to mention -TCP overhead from the three-way handshake and acknowledgement packets. - -This is hilariously unviable and makes the effective cost of -transmitting a gigabyte of data over HTTP through such a contraption -vastly more than $0.07 per gigabyte. - -<script> -if (document.referrer.match(/news.ycombinator.com/) || localStorage["hn_gaslight_multiplicand"]) { - const price1Base = 0.86; - const price2Base = 2.59; - - const multiplicand = localStorage["hn_gaslight_multiplicand"] || Math.random() * (5 - 0.8) + 0.8; - - localStorage["hn_gaslight_multiplicand"] = multiplicand; - - document.getElementById("hnprice1").innerText = `${(price1Base * multiplicand).toFixed(2)}`; - document.getElementById("hnprice2").innerText = `${(price2Base * multiplicand).toFixed(2)}`; -} -</script> - -## Other notes - -This architecture does have a strange advantage to it though: assuming -a perfectly spherical cow, adequate network latency, and sheer luck -this does make UDP a bit more reliable than it should be otherwise. - -With appropriate timeouts and retries at the application level, it may -end up being more reliable than IP transit over the public internet. - -<xeblog-conv name="Aoi" mood="coffee" standalone>Good lord is this an -accursed abomination.</xeblog-conv> - -I guess you could optimize this by replacing the S3 read loop with -some kind of AWS lambda handler that remotely wakes the target -machine, but at that point it may actually be better to have that -lambda POST the contents of the packet to the remote machine. This -would let you bypass the S3 polling costs, but you'd still have to pay -for the egress traffic from lambda and the posting to S3 bit. - -<xeblog-conv name="Cadey" mood="coffee" standalone>Before you comment -about how I could make it better by doing x, y, or z; please consider -that I need to leave room for a part 2. I've already thought about -nearly anything you could have already thought about, including using -SQS, bundling multiple packets into a single S3 object, and other -things that I haven't mentioned here for brevity's sake.</xeblog-conv> - -## Shitposting so hard you create an IP conflict - -Something amusing about this is that it is something that technically -steps into the realm of things that my employer does. This creates a -unique kind of conflict where I can't easily retain the intellectial -property (IP) for this without getting it approved from my employer. -It is a bit of the worst of both worlds where I'm doing it on my own -time with my own equipment to create something that will be ultimately -owned by my employer. This was a bit of a sour grape at first and I -almost didn't implement this until the whole Air Canada debacle -happened and I was very bored. - -However, I am choosing to think about it this way: I have successfully -shitposted so hard that it's a legal consideration and that I am going -to be _absolved of the networking sins I have committed_ by instead -outsourcing those sins to my employer. - -I was told that under these circumstances I could release the source -code and binaries for this atrocity (provided that I release them with -the correct license, which I have rigged to be included in both the -source code and the binary of Hoshino), but I am going to elect to not -let this code see the light of day outside of my homelab. Maybe I'll -change my mind in the future, but honestly this entire situation is so -cursed that I think it's better for me to not for the safety of -humankind's minds and wallets. - -I may try to use the basic technique of Hoshino as a replacement for -DERP, but that sounds like a lot of effort after I have proven that -this is so hilariously unviable. It would work though! - ---- - -<xeblog-conv name="Aoi" mood="grin">This would make a great -[SIGBOVIK](http://sigbovik.org/) paper.</xeblog-conv> -<xeblog-conv name="Cadey" mood="enby">Stay tuned. I have -plans.</xeblog-conv> |
