Repository navigation
Conversation
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
Conflicts have been resolved. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
1 similar comment
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
Conflicts have been resolved. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
1 similar comment
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
Conflicts have been resolved. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
1 similar comment
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
Conflicts have been resolved. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
Conflicts have been resolved. |
Signed-off-by: DL6ER <dl6er@dl6er.de>
The 1 MiB buffer for a signed `PATCH /api/config` was granted to any request carrying an `X-FTL-Cluster-Sig` header, before anything about it was checked. The header checks now run first, and the larger buffer is kept for a sender that has already presented a valid signature here, addressed to this node with a sequence it has not used yet. Everybody else gets the regular limit. A peer polls a node before it pushes to it, so the only push this refuses is one above the regular limit that arrives between a restart of this node and the sender's next poll. The catch-up after that poll hands it over. Signed-off-by: DL6ER <dl6er@dl6er.de>
`upgrade_stored_password()` hashes the password with BALLOON before it takes the lock, and installed the result whatever the stored hash had become meanwhile. A password changed through the API, or taken from a peer, in that window was replaced by the upgraded old one, and the new password stopped working. The stored hash is now compared with the legacy hash of this password under the lock, and the upgrade is skipped when it no longer matches. Signed-off-by: DL6ER <dl6er@dl6er.de>
A peer learned the step a node had accepted only from its next poll of that node, so the same code stayed usable on the other nodes for up to one `cluster.interval`. The node that accepts a code now sends the step to its peers in the next tick, as a `totp` parameter of a signed `GET /api/cluster/status`, and a peer adopts it on the same terms as the published one: the second-factor secret is shared and the clocks agree. Measured with two nodes at interval 5: the same code was accepted on the second node 0.06, 0.2 and 1 s after the first had taken it, and is now refused from about 0.1 s on. The help text of `cluster.sync.credentials` claimed a code spends once across the cluster, it now says what holds. Signed-off-by: DL6ER <dl6er@dl6er.de>
The notice that a 2FA code was used is a signed status request, which the peer counts as a poll. Without the `dhcp` and `vip` flags it counted as one from a node that may serve, so a login on the standby could make a server that cannot poll it take it for a second server. Signed-off-by: DL6ER <dl6er@dl6er.de>
| break; | ||
|
|
||
| const double until = double_time() + 0.25; | ||
| for(double left = 0.25; left > 0.0; left = until - double_time()) |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
Conflicts have been resolved. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
Conflicts have been resolved. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
Conflicts have been resolved. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
1 similar comment
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
Conflicts have been resolved. |
What does this implement/fix?
Native clustering of several Pi-holes, in FTL itself. The nodes keep their configuration and their lists in step, watch each other, hand DHCP over when the serving node fails, and move a virtual IP address to whichever node the clients should be talking to. There is no primary: every node is equal, a change made on any of them reaches the others within a round, and any node can be the one that is down.
pihole.tomlstays the place where everything is visible. List changes that did not come through FTL's API (a restoredgravity.db,sqlite3, a Teleporter import on the CLI) are foreign to the cluster, and its lists replace them./api/cluster/...and push a configuration, and nothing else. Joining needs the fingerprint of the node joined, copied from its cluster page, unless certificates are checked againstcluster.tls.ca; from there the nodes learn each other's pins from signed answers. A two-factor code used on one node is refused on the others a moment later.Five commits, one per step: watching and failing over; keeping configuration and lists in step; surviving upgrades, interruptions and a node leaving; deciding DHCP and the address on what a node can actually do; deciding on the merged view. The last carries the fixes of the final review, the smaller commits after them what further reviews found.
The libcurl it needs is in the build image since pi-hole/docker-base-images#188 (
ftl-build:v2.26.1).How to test the change during review
The manual (pi-hole/docs#1429) has a three-container
docker-compose.ymlunder "Trying it out with Docker"; changing a setting on one node and reading it on another is the whole of the first test, and stopping a node, changing something and starting it again is the second. The HA cluster page (pi-hole/web#3864) shows who reaches whom and whose fingerprints differ.In CI,
test/api/test_y_cluster.pycovers the status document, signed and refused requests, replay and time-window refusals, that a peer'sPATCH /api/configis answered without credentials over TLS, a peer that holds no configuration yet, which items of a push are taken, a join without a fingerprint, reported conflicts, whose polls count for a member, a push that could not be written, a large push from an unknown sender, a two-factor code used on a peer, list edits in a swappedgravity.db, and which virtual addresses are refused. The rest of the suite runs unchanged.DHCP failover and the virtual address need real machines (a bridge carries neither broadcasts nor a floating address); I have exercised them in containers with
NET_ADMINandNET_RAWonly, which reaches the netlink and election halves but not the announcement or a killed FTL leaving an address behind. I reproduced and verified the gravity, shutdown, synchronization and failover fixes with two and three nodes on an internal bridge, with core'sgravity.shand a local list server.Additional information
Related issue or feature (if applicable): pi-hole/pi-hole#6734 keeps list edits made while gravity runs
Pull request in docs with documentation (if applicable): pi-hole/docs#1429, with the web page in pi-hole/web#3864
By submitting this pull request, I confirm the following:
git rebase)src/dnsmasq/. That tree is a verbatim copy of upstream dnsmasq and we do not carry anything in it that deviates from upstream. Fixes have to go through thednsmasq-discussmailing list first, we merge them once they are in dnsmasq master.Checklist:
developmentbranch.