Compare commits

...
Author SHA1 Message Date
security-bot e8645d11ed chore(image-automation): bump gitea.renner.uno/konairius/shelly-manager:2026-11-02-1004
Updates managed by image-automation-controller.
2026-11-02 10:21:19 +00:00
k.renner f3c9188807 Merge pull request 'fix(shelly-manager): disable TCP black-hole detection in the pod netns' (#1550) from shelly-manager-no-mtu-probing into main
Reviewed-on: #1550
2026-11-02 09:37:08 +00:00
claude-botandClaude Opus 5.5 c68b9ad7c2 fix(shelly-manager): disable TCP black-hole detection in the pod netns
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Cilium sets net.ipv4.tcp_mtu_probing=1 in every pod netns
(packetization-layer-pmtud-mode: blackhole). Shelly devices stop ACKing
for seconds while they write flash during an OTA; after tcp_retries1
timeouts the kernel treats that as a PMTU black hole and halves the MSS
on every stall, from 1440 down to the 48-byte floor. Upward probes need
an open congestion state with cwnd >= 11, which the stalling device
never allows, so the download stays at 48-byte segments and fills the
device's receive buffer.

The tuning plugin of the IoT attachment sets the sysctl to 0 for the pod
netns; the annotation bump rolls the pod so the attachment is re-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0185Qb5YG28FfnVi1ujMEV5C
2026-11-02 10:36:01 +01:00
k.renner 59c9cc29fe Merge pull request 'fix(talos): route LoadBalancer replies through table 47' (#1549) from lb-replies-table-47 into main
Reviewed-on: #1549
2026-11-02 07:45:38 +00:00
claude-botandClaude Opus 5.5 3501586841 fix(talos): route LoadBalancer replies through table 47
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
A reply from a Cilium LoadBalancer VIP reaches the routing decision with
the VIP as its source, so the pod-source rules 1000/1001 missed it and
main sent it out mgmt0 while the request came in over ingress0, the BGP
next hop (k8s-3, 2026-11-02: replies from 10.2.99.90 to an IoT device
seen only on mgmt0). Rules 1002/1003 send the pool to table 47.

The pool becomes a site fact: each site patch carries lb-pool-site
(flux-system), and bgp-pool takes its blocks from it by Flux
substitution in infrastructure-crds. The CIDR blocks cover the same
addresses as the start/stop ranges did (allowFirstLastIPs defaults to
Yes). The qemu and hcloud sites carry the production pool, since
Services pin their addresses from it.

Refs #1547

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0185Qb5YG28FfnVi1ujMEV5C
2026-11-02 08:42:49 +01:00
security-bot 9e1452cfe7 chore(image-automation): bump gitea.renner.uno/konairius/shelly-manager:2026-11-02-0659
Updates managed by image-automation-controller.
2026-11-02 07:21:19 +00:00
8 changed files with 74 additions and 7 deletions
+7
View File
@@ -22,6 +22,13 @@ Two rules, one per family, send every packet with a pod source to table 47
(`talos/patches/site-homelab.yaml`, priorities 1000/1001, below main). The
table decides by longest prefix; no rule carves out exceptions.
A reply from a LoadBalancer VIP is pod traffic as well, but it reaches the
routing decision with the VIP as its source. Two more rules (1002/1003) send
the LoadBalancer pool to table 47, so the reply leaves over `ingress0`, where
the request arrived as the BGP next hop. Without them main sent it out
`mgmt0`. The pool is a site fact: the site patch's `lb-pool-site` ConfigMap,
which `bgp-pool` takes by Flux substitution, and the rules beside it.
bird (`kubernetes/infrastructure/bird/`) owns table 47 and fills it with:
- the node's attached networks — `mgmt0`, `ingress0`, `pod0`, `node0` —
@@ -46,7 +46,7 @@ spec:
k8s.v1.cni.cncf.io/networks: shelly-manager-iot
# Changing this rolls the pod, which is the only way a change to the
# shelly-manager-iot attachment takes effect.
net.renner.uno/iot-attachment-config: "tuning-no-ra"
net.renner.uno/iot-attachment-config: "tuning-no-ra-no-mtu-probing"
spec:
# eth0 (Cilium-managed pod network) is still the primary — it
# carries the ingress, mosquitto LB return path, etc. The macvlan
@@ -64,7 +64,7 @@ spec:
type: RuntimeDefault
containers:
- name: shelly-manager
image: gitea.renner.uno/konairius/shelly-manager:2026-10-30-1343 # {"$imagepolicy": "flux-system:shelly-manager"}
image: gitea.renner.uno/konairius/shelly-manager:2026-11-02-1004 # {"$imagepolicy": "flux-system:shelly-manager"}
imagePullPolicy: IfNotPresent
ports:
- name: http
@@ -50,6 +50,9 @@ spec:
# to IPv6 clients on v30iot leave via net1, bypassing Cilium's LoadBalancer
# reverse NAT. tuning turns RA processing off on net1; the link-local
# address mDNS uses stays.
#
# tcp_mtu_probing (pod-wide; Cilium sets 1): Shelly flash-write stalls
# trip black-hole detection, which cuts firmware downloads to 48-byte MSS.
config: |
{
"cniVersion": "0.3.1",
@@ -67,7 +70,8 @@ spec:
"type": "tuning",
"sysctl": {
"net.ipv6.conf.IFNAME.accept_ra": "0",
"net.ipv6.conf.IFNAME.autoconf": "0"
"net.ipv6.conf.IFNAME.autoconf": "0",
"net.ipv4.tcp_mtu_probing": "0"
}
},
{
@@ -89,6 +89,9 @@ spec:
substituteFrom:
- kind: ConfigMap
name: cluster-settings
# The LoadBalancer pool, from the site's Talos patch (site-*.yaml).
- kind: ConfigMap
name: lb-pool-site
dependsOn:
- name: infrastructure
---
@@ -8,6 +8,10 @@
# Selector is opt-in via `lb-pool: bgp`. There is no default pool —
# Services without this label do not get a LoadBalancer IP.
#
# The ranges are a site fact: lb-pool-site from the site's Talos patch
# (talos/patches/site-*.yaml), by Flux substitution. A CIDR block uses all its
# addresses (allowFirstLastIPs defaults to Yes).
#
# IPv4 + IPv6 ranges keep matching last bytes (10.2.99.x ↔ fd99::x)
# so each LB has the same "tail" in both families.
apiVersion: cilium.io/v2alpha1
@@ -19,10 +23,8 @@ spec:
matchLabels:
lb-pool: bgp
blocks:
- start: "10.2.99.0"
stop: "10.2.99.255"
- start: "fd99::"
stop: "fd99::ff"
- cidr: "${LB_POOL_V4}"
- cidr: "${LB_POOL_V6}"
---
# Cilium BGPv2 control-plane config peering each node with the UDM over the
# ingress VLAN 47 — never over v30iot, which is the untrusted IoT segment.
+12
View File
@@ -67,6 +67,18 @@ cluster:
nameservers:
- 185.12.64.1
- 185.12.64.2
# The LoadBalancer pool (see site-homelab.yaml). Services pin their
# addresses from the production pool, so a test run carries the same one.
- name: lb-pool-site
contents: |
apiVersion: v1
kind: ConfigMap
metadata:
name: lb-pool-site
namespace: flux-system
data:
LB_POOL_V4: 10.2.99.0/24
LB_POOL_V6: fd99::/120
---
# Host DNS asks k8s-gateway first, so containerd resolves in-cluster names
# (gitea.renner.uno) in the cluster; bootstrap.sh installs k8s-gateway right
@@ -85,6 +85,18 @@ cluster:
nameservers:
- fd02:47::1
- 10.2.47.1
# The LoadBalancer pool (bgp-pool, infrastructure/post-crds/cilium-bgp.yaml,
# by Flux substitution). The routing rules 1002/1003 below name it too.
- name: lb-pool-site
contents: |
apiVersion: v1
kind: ConfigMap
metadata:
name: lb-pool-site
namespace: flux-system
data:
LB_POOL_V4: 10.2.99.0/24
LB_POOL_V6: fd99::/120
---
# All pod traffic goes to table 47, and the table decides — longest prefix
# match, no exceptions to carve out. bird fills it with the node's attached
@@ -115,6 +127,21 @@ name: "1001"
src: 10.244.0.0/16
table: "47"
---
# A LoadBalancer reply is pod traffic too, but it reaches the routing decision
# with the VIP as its source, so the rules above miss it and main sends it out
# mgmt0 while the request came in over ingress0. The pool is lb-pool-site above.
apiVersion: v1alpha1
kind: RoutingRuleConfig
name: "1002"
src: fd99::/120
table: "47"
---
apiVersion: v1alpha1
kind: RoutingRuleConfig
name: "1003"
src: 10.2.99.0/24
table: "47"
---
# Host DNS asks k8s-gateway first, so containerd resolves in-cluster names
# (gitea.renner.uno) in the cluster; bootstrap.sh installs k8s-gateway right
# after the CNI, before anything else needs DNS. The UDM is the site's
+12
View File
@@ -47,3 +47,15 @@ cluster:
values.yaml: |
deployment:
dnsPolicy: Default
# The LoadBalancer pool (see site-homelab.yaml). Services pin their
# addresses from the production pool, so a test run carries the same one.
- name: lb-pool-site
contents: |
apiVersion: v1
kind: ConfigMap
metadata:
name: lb-pool-site
namespace: flux-system
data:
LB_POOL_V4: 10.2.99.0/24
LB_POOL_V6: fd99::/120