{"id":15687300,"url":"https://github.com/passcod/noodle","last_synced_at":"2025-05-07T19:40:58.932Z","repository":{"id":139959840,"uuid":"347634529","full_name":"passcod/noodle","owner":"passcod","description":"Tiny daemon to implement floating IPs with RFC5944 ARP announcements","archived":false,"fork":false,"pushed_at":"2021-03-22T10:44:57.000Z","size":138,"stargazers_count":10,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-31T13:27:28.985Z","etag":null,"topics":["arp","floating-ip","gratuitous-arp","rfc5944"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/passcod.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-14T12:45:20.000Z","updated_at":"2024-06-04T18:55:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"44b21018-9950-4a01-9f08-e6a82ff41493","html_url":"https://github.com/passcod/noodle","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passcod%2Fnoodle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passcod%2Fnoodle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passcod%2Fnoodle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passcod%2Fnoodle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/passcod","download_url":"https://codeload.github.com/passcod/noodle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252945143,"owners_count":21829544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arp","floating-ip","gratuitous-arp","rfc5944"],"created_at":"2024-10-03T17:46:50.554Z","updated_at":"2025-05-07T19:40:58.913Z","avatar_url":"https://github.com/passcod.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Crate release version](https://flat.badgen.net/crates/v/passcod-noodle)](https://crates.io/crates/passcod-noodle)\n[![Crate license: Apache 2.0 or MIT](https://flat.badgen.net/badge/license/Apache%202.0%20or%20MIT)][copyright]\n![MSRV: latest stable](https://flat.badgen.net/badge/MSRV/latest%20stable/orange)\n[![Uses Caretaker Maintainership](https://flat.badgen.net/badge/Caretaker/Maintainership%20👥%20/purple)][caretaker]\n\n# Noodle\n\n_AKA \"someone stop Félix from naming things, please, this is terrible.\"_\n\n- Go directly to the [usage summary](#obtain).\n- [Dual-licensed][copyright] with Apache 2.0 and MIT.\n- Uses [Caretaker Maintainership][caretaker].\n\n[caretaker]: ./CARETAKERS.md\n[copyright]: ./COPYRIGHT\n\n## Introduction\n\nNoodle as in pool noodle, because it floats and IPs and... geddit?\n\nThe idea is based on [MetalLB](https://metallb.universe.tf/concepts/layer2/).\n\nBasically, we emit [RFC5944 \"gratuitous\" ARP announcements](https://tools.ietf.org/html/rfc5944#section-4.6).\n\nInstead of the usual ARP request-reply \"who has 1.2.3.4?\" \"I do\" cycle, we send\n\"I have 1.2.3.4\" and we add 1.2.3.4 to the network interface we do that from,\nso the network stack handles traffic sent to it properly.\n\nNoodle is the little daemon that does the ARP announcements like that.\n\nBut Noodle also listens for ARP on the interface. If it sees ARP on the\ninterface for the same IP it's announcing, but with a different MAC address, it\nstops and exits!\n\nWhen it stops, if it's being managed by an active orchestrator or supervisor,\nit will get restarted. And if it's not, but for some reason wasn't shut down\nwhen the orchestrator or supervisor went down, it wont come back up.\n\nIn any case, an active Noodle will keep spraying its ARP over the subnet\nevery N seconds, so well behaved devices like routers and VMs will keep\ntheir ARP tables updated with the MAC of whatever device Noodle is on.\n\nEssentially it's like MetalLB but we use the network itself as an additional\nconsensus layer, and the main consensus is via whatever orchestrator you use.\nYou can also disable the ARP watcher and/or only send a number of ARPs and then\nquit, so you can build the orchestration a little more granularly yourself if\nyou want. It's up to you!\n\nBTW this was written for use with Nomad but doesn't in any way depend on Nomad.\n\n## Good times\n\n### In a graceful move situation\n\nOrchestrator will boot Noodle on the other Node, and then either the\norchestrator or the new ARP will kill the old Noodle. ARP tables will be\nupdated subnet-wide and most importantly at the router, and the IP will\nessentially have \"floated\" over to the new node in short order.\n\nThe orchestrator, when configured properly, can also keep the old service\nrunning for a bit before killing it even while starting the new one, so traffic\nwill move over as smoothly as it can (long connections may still get broken).\n\n### In a failover situation\n\nThe old node will be dead. Traffic goes nowhere. Devices on the subnet\nand the router still have the old MAC in the ARP tables.\n\nOrchestrator notices a node is down, and reschedules its workload on the alive\nnodes. Noodle starts, blathers ARP announcements over the network, tables get\nupdated, and traffic starts flowing again.\n\n### In a split brain situation\n\nOrchestrator worker node gets separated from the orchestrator's servers, or\ntalks to only one server and _it_ is separated from the others. In either case,\nthat node's orchestrator worker agent will:\n\n- appear dead to the rest of the cluster, which will reschedule work as\n  in the above, and\n- notice that it's dead or doesn't have consensus, and _ideally_ will\n  kill itself.\n\nProceeds mostly the same as failover, but may proceed as crash:\n\n### In an orchestrator crash situation\n\nOrchestrator crashes, leaving the underlying containers or applications\nrunning. Rest of the cluster notices the node is dead and reschedules. The new\nNoodle yells its announce, the old Noodle notices ARP that is not coming from\nitself, and exits. Traffic moves over to the new node.\n\n## Bad times\n\n### Partial split brain where the old node still thinks it's meant to be up\n\nOld orchestrator appears dead to the rest of the cluster, which reschedules.\n\nOld Noodle notices ARP, and kills itself.\n\nOld orchestrator restarts its Noodle.\n\nNew Noodle notices ARP, and kills itself.\n\nNew orchestrator restarts its Noodle.\n\nRound and round it goes, traffic is mightily confused, routers might\ntake a hit.\n\n### In a Noodle crash situation\n\nNoodle crashes in the middle of its loops and doesn't clean up the IP on\nits interface. Linux responds to ARP requests with solicited \"it's me!\"\nwhile other Noodle is screaming out \"it's me it's me it's me\" announces.\n\nTraffic and routers get confused.\n\n_This is always a bug and should be reported._\n\n### Operator accidentally scales the Noodle service for one floating IP to 2 or more instances\n\nProceeds like partial split brain except both nodes are technically legitimate.\n\n## Alternatives\n\n- Native Cloud Provider Floating IP: absolutely use that if available.\n\n- Tunnels: requires router access to set up and tunnel endpoint at the\n  service, can be connected to twice concurrently and then what. Adds a\n  moving piece. But is much quieter on the network, cleaner\n  traffic-wise, maybe clearer to netops.\n\n- BGP: requires router access to set up, and complicated daemon on every\n  node, plus management of said daemons' config. Does offer true IP-level\n  load-balancing instead of just directing traffic to one host. BGP can\n  be a pain and also may interfere with netops.\n\n- DNS: externalises the problem, very slow to update even with TTL=0 because of\n  client behaviour. Some traffic just never dies.\n\n- Single VM acts as load-balancer and is never rebooted: not really an\n  option, this is what we're trying to get away from.\n\n- Transferring the entire NIC at the VM hypervisor level between machines\n  instead of doing the ARP dance: certainly an option, relies on having\n  hypervisor API access, needs some kind of centralised control to avoid\n  split brain and conflicts. Implies downtime during the switching.\n\n- Just changing the hypervisor MAC of the interfaces: same as previous,\n  plus adds even more downtime as MACs can't conflict across VMs and\n  it needs to find a free \"transfer\" mac or shut the old NIC down.\n\n- Changing the ARP table of the router directly with the router's\n  administrative API: doesn't yell so much into the void, but requires access\n  to router, and some kind of synchronisation to avoid conflicting commands.\n\n- VRRP, Tree Spanning, OSPF, bonding, etc: cool tech, may work, requires\n  netops. May not handle intra-subnet traffic, only via router.\n\n## Obtain\n\nOnly works on Linux.\n\nCurrently only ARP (supporting IPv4) is implemented.\n\n### From binary release\n\nThe [release tab on GitHub](https://github.com/passcod/noodle/releases).\n\nBuilds are available for:\n\n- x86-64, both gnu and musl\n- AArch64, both gnu and musl\n- Arm7 HF, both gnu and musl\n\nIt's trivial to add more, so please ask.\n\n### ~~With cargo binstall~~\n\n⚠  Not available yet, depends on [netlink#149](https://github.com/little-dude/netlink/issues/149)\n\n```\ncargo binstall passcod-noodle\n```\n\n### ~~From source~~\n\n⚠  Not available yet, depends on [netlink#149](https://github.com/little-dude/netlink/issues/149)\n\n```\ncargo install passcod-noodle\n```\n\nYou can also compile from the repo as usual.\n\n## Use\n\nRequires sudo or the `NET_ADMIN` capability.\n\nMinimal command:\n\n```\nnoodle --ip 10.9.8.7/24 --interface ens123\n```\n\nMandatory options:\n\n- `--ip IP/SUBNET`: the floating IP to announce.\n- `--interface NAME`: which interface to announce ARP on.\n\nOther options:\n\n- `--mac ADDRESS`: override the MAC address IP is announced for (default=read from interface).\n- `--target ADDRESS`: override the MAC address packets are sent to (default=broadcast).\n- `--log LEVEL`: specify the log level (default=info). All logs are JSON.\n- `--interval DURATION` in seconds (default=10): how often to announce.\n- `--delay DURATION` in seconds (default=0): delay the first announce.\n- `--watch-delay DURATION` in seconds (default=0): delay before watching for competings.\n- `--jitter DURATION` in seconds (default=1): add jitter to each interval up to this value.\n- `--arp-reply`: use ARP reply instead of ARP request as announcement type.\n- `--unmanaged-ip`: leave the interface alone (don't add/remove the ip).\n- `--die-if-ip-exists`: exit with status 1 if the IP already exists on the interface.\n- `--remove-pre-existing-ip`: remove the IP even if we didn't add it ourselves.\n- `--watch BEHAVIOUR`: control the competing announcement watcher:\n  * `fail` (default): exit with status 1 if we see an announcement for this\n    IP by another MAC address\n  * `quit`: exit with status 0 instead\n  * `log`: don't exit, only log it\n  * `no`: don't watch\n- `--watch-immediately`: don't wait until the first announce is sent to start watching.\n- `--count N` (default=0/disabled): only announce this many times.\n- `--once`: shorthand for `--count 1  --delay 0  --jitter 0  --watch no`.\n\nInfo switches:\n\n- `--help`: print the help.\n- `--readme`: print this readme.\n- `--source`: print the source (Cargo.toml and main.rs).\n- `--version`: print the version number.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasscod%2Fnoodle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpasscod%2Fnoodle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasscod%2Fnoodle/lists"}