Skip to content

Commit 2cf5b36

Browse files
committed
clusterer_controller: zero-config HA clusterer control module (devel)
Port of the clusterer_controller module and its clusterer-module integration to the devel branch. Feature parity with the earlier 3.4 work: zero-config HA clusterer control over encrypted UDP multicast, per-cluster node identity, hybrid native+controller topologies, cl_ctr_* MI/variables/functions, join flood-DoS hardening, and input validation. Devel adaptations: - cc_discover_bin_sockets() iterates protos[PROTO_BIN].listeners as struct socket_info_full (next/prev), reading the embedded socket_info -- the socket-list refactor that landed on devel. - The clusterer core edits are re-seated onto devel's clusterer.c / clusterer_mod.c / node_info.h, merging cleanly with the new inter-cluster bridges feature and the clusterer_enable_rerouting guard. Simultaneous-start stabilisation (cosmetic/robustness on a noisy-but-convergent cold start): - cc_decrypt_pkt distinguishes bootstrap-key failures (real wrong-password / foreign-cluster / tampering -> WARN) from transient session-key mismatches during a (re)key or split-brain heal (-> DBG). - the split-brain defer budget resets on a fresh higher-IP JOIN_REQ so a lower-IP node waits for a live higher-IP peer instead of self-promoting (bounded by CC_JOIN_DEFER_HARDMAX). - JOIN_REQ transmissions are rate-limited (CC_JOIN_REQ_MIN_US). clusterer: on a fresh/simultaneous start a seed node queues a capability sync, finds no peer with data, and after seed_fb_interval falls back to synced. sync_check_timer logged that at ERROR when peers were present -- that is the seed fallback working as designed, so it is downgraded to DBG (the genuine SYNC_IN_PROGRESS partial-sync failure path is unchanged). Validated end-to-end on a 3-node cluster: controller / native / hybrid modes, master failover, MI outputs and error paths, buffer overrun/underrun fuzzing, simultaneous cold start (highest-IP master, no split-brain, no spurious warnings, no sync ERROR), and wrong-password rejection.
1 parent 9ad68c0 commit 2cf5b36

19 files changed

Lines changed: 10676 additions & 109 deletions

‎modules/clusterer/api.h‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,5 @@ static inline module_dependency_t *get_deps_clusterer(const param_export_t *para
345345
return alloc_module_dep(MOD_TYPE_DEFAULT, "clusterer", DEP_ABORT);
346346
}
347347

348-
349348
#endif /* CLUSTERER_API_H */
350349

‎modules/clusterer/clusterer.c‎

Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ void sync_check_timer(utime_t ticks, void *param)
8787
lock_start_read(cl_list_lock);
8888

8989
for (cl = *cluster_list; cl; cl = cl->next) {
90+
if (!cl->current_node) continue;
9091
lock_get(cl->current_node->lock);
9192
if (!(cl->current_node->flags & NODE_STATE_ENABLED)) {
9293
lock_release(cl->current_node->lock);
@@ -109,11 +110,27 @@ void sync_check_timer(utime_t ticks, void *param)
109110
cap->flags &= ~(CAP_SYNC_PENDING|CAP_SYNC_STARTUP);
110111
sr_set_status(cl_srg, STR2CI(cap->reg.sr_id), CAP_SR_SYNCED,
111112
STR2CI(CAP_SR_STATUS_STR(CAP_SR_SYNCED)), 0);
112-
sr_add_report_fmt(cl_srg, STR2CI(cap->reg.sr_id), 0,
113-
"ERROR: Sync request aborted! (no donor found in due time)"
114-
" => fallback to synced state");
115-
LM_ERR("Sync request aborted! (no donor found in due time)"
116-
", falling back to synced state\n");
113+
/* Seed-fallback for a still-PENDING sync (the transfer never
114+
* started - no donor responded to our request within
115+
* seed_fb_interval). This is exactly what the seed
116+
* fallback is *for*: on a fresh or simultaneous cold start
117+
* no peer has any data to donate, so the seed proceeds as
118+
* authoritative. It is expected, not an error - the state
119+
* is still recorded in the status report for visibility. A
120+
* genuine partial-sync failure is a *different* branch (the
121+
* SYNC_IN_PROGRESS timeout below) and is logged there. */
122+
if (cl->node_list == NULL)
123+
sr_add_report_fmt(cl_srg, STR2CI(cap->reg.sr_id), 0,
124+
"No peers present — self-synced as first node in cluster");
125+
else
126+
sr_add_report_fmt(cl_srg, STR2CI(cap->reg.sr_id), 0,
127+
"Seed fallback — no donor sent data in due time, "
128+
"self-synced as seed");
129+
LM_DBG("Seed fallback in cluster %d: capability '%.*s' "
130+
"self-marked as synced (%s)\n", cl->cluster_id,
131+
cap->reg.name.len, cap->reg.name.s,
132+
cl->node_list == NULL ? "first/lone node"
133+
: "no donor sent data in due time");
117134
/* send update about the state of this capability */
118135
send_single_cap_update(cl, cap, 1);
119136

@@ -155,7 +172,7 @@ int cl_set_state(int cluster_id, int node_id, enum cl_node_state state)
155172
return -1;
156173
}
157174

158-
if (node_id != current_id) {
175+
if (node_id != cluster_self_id(cluster)) {
159176
node = get_node_by_id(cluster, node_id);
160177
if (!node) {
161178
lock_stop_read(cl_list_lock);
@@ -190,13 +207,16 @@ int cl_set_state(int cluster_id, int node_id, enum cl_node_state state)
190207
LM_INFO("Set state: %s for node: %d in cluster: %d\n",
191208
state ? "enabled" : "disabled", node_id, cluster_id);
192209

193-
if (db_mode && update_db_state(cluster_id, node_id, state) < 0)
210+
if (cl_db_mode(cluster) && update_db_state(cluster_id, node_id, state) < 0)
194211
LM_ERR("Failed to update state in clusterer DB for node [%d] cluster [%d]\n",
195212
node_id, cluster_id);
196213

197214
return 0;
198215
}
199216

217+
if (!cluster->current_node)
218+
return -1;
219+
200220
lock_get(cluster->current_node->lock);
201221

202222
if (state == STATE_DISABLED && cluster->current_node->flags & NODE_STATE_ENABLED)
@@ -226,7 +246,7 @@ int cl_set_state(int cluster_id, int node_id, enum cl_node_state state)
226246
LM_INFO("Set state: %s for local node in cluster: %d\n",
227247
state ? "enabled" : "disabled", cluster_id);
228248

229-
if (db_mode && update_db_state(cluster_id, current_id, state) < 0)
249+
if (cl_db_mode(cluster) && update_db_state(cluster_id, current_id, state) < 0)
230250
LM_ERR("Failed to update state in clusterer DB for cluster [%d]\n", cluster->cluster_id);
231251

232252
return 0;
@@ -628,11 +648,27 @@ clusterer_bridges_bcast_msg(bin_packet_t *packet, int src_cid)
628648
}
629649

630650

651+
/* This node's id in @cluster_id, resolved without taking cl_list_lock: clusters
652+
* persist for the module's lifetime, and callers may already hold cl_list_lock
653+
* (read) or a different lock (shtags) whose order is cl_list_lock -> shtags_lock,
654+
* so taking cl_list_lock here would risk a reverse-order deadlock. Matches the
655+
* lock-free nature of the GET_CURRENT_ID this replaces. Returns -1 if the
656+
* cluster is unknown or this node's identity in it is not yet established. */
657+
static int trailer_self_id(int cluster_id)
658+
{
659+
cluster_info_t *cl;
660+
661+
for (cl = *cluster_list; cl; cl = cl->next)
662+
if (cl->cluster_id == cluster_id)
663+
return cluster_self_id(cl);
664+
return -1;
665+
}
666+
631667
int msg_add_trailer(bin_packet_t *packet, int cluster_id, int dst_id)
632668
{
633669
if (bin_push_int(packet, cluster_id) < 0)
634670
return -1;
635-
if (bin_push_int(packet, current_id) < 0)
671+
if (bin_push_int(packet, trailer_self_id(cluster_id)) < 0)
636672
return -1;
637673
if (bin_push_int(packet, dst_id) < 0)
638674
return -1;
@@ -947,7 +983,7 @@ static void handle_cap_update(bin_packet_t *packet, node_info_t *source)
947983
for (i = 0; i < nr_nodes; i++) {
948984
bin_pop_int(packet, &node_id);
949985

950-
if (node_id == current_id) {
986+
if (node_id == cluster_self_id(source->cluster)) {
951987
bin_pop_int(packet, &nr_cap);
952988
for (j = 0; j < nr_cap; j++) {
953989
bin_pop_str(packet, &cap);
@@ -1128,12 +1164,15 @@ static void handle_remove_node(bin_packet_t *packet, cluster_info_t *cl)
11281164
bin_pop_int(packet, &target_node);
11291165
LM_DBG("Received remove node command for node id: [%d]\n", target_node);
11301166

1131-
if (db_mode) {
1167+
if (cl_db_mode(cl)) {
11321168
LM_DBG("We are in DB mode, ignoring received remove node command\n");
11331169
return;
11341170
}
11351171

1136-
if (target_node == current_id) {
1172+
if (target_node == cluster_self_id(cl)) {
1173+
if (!cl->current_node)
1174+
return;
1175+
11371176
lock_get(cl->current_node->lock);
11381177

11391178
if (cl->current_node->flags & NODE_STATE_ENABLED) {
@@ -1180,14 +1219,9 @@ void bin_rcv_cl_extra_packets(bin_packet_t *packet, int packet_type,
11801219
LM_DBG("received clusterer message from: %s:%hu with source id: %d and"
11811220
" cluster id: %d\n", ip, port, source_id, cluster_id);
11821221

1183-
if (source_id == current_id) {
1184-
LM_ERR("Received message with bad source - same node id as this instance\n");
1185-
return;
1186-
}
1187-
11881222
gettimeofday(&now, NULL);
11891223

1190-
if (!db_mode && packet_type == CLUSTERER_REMOVE_NODE)
1224+
if ((!db_mode || use_controller) && packet_type == CLUSTERER_REMOVE_NODE)
11911225
lock_start_write(cl_list_lock);
11921226
else
11931227
lock_start_read(cl_list_lock);
@@ -1199,6 +1233,11 @@ void bin_rcv_cl_extra_packets(bin_packet_t *packet, int packet_type,
11991233
goto exit;
12001234
}
12011235

1236+
if (source_id == cluster_self_id(cl)) {
1237+
LM_ERR("Received message with bad source - same node id as this instance\n");
1238+
goto exit;
1239+
}
1240+
12021241
lock_get(cl->current_node->lock);
12031242
if (!(cl->current_node->flags & NODE_STATE_ENABLED)) {
12041243
lock_release(cl->current_node->lock);
@@ -1236,7 +1275,7 @@ void bin_rcv_cl_extra_packets(bin_packet_t *packet, int packet_type,
12361275
} else
12371276
lock_release(node->lock);
12381277

1239-
if (dest_id != current_id) {
1278+
if (dest_id != cluster_self_id(cl)) {
12401279
if (clusterer_enable_rerouting == 0) {
12411280
LM_WARN("Received message for destination id [%d] but rerouting disabled\n", dest_id);
12421281
goto exit;
@@ -1292,7 +1331,7 @@ void bin_rcv_cl_extra_packets(bin_packet_t *packet, int packet_type,
12921331
}
12931332

12941333
exit:
1295-
if (!db_mode && packet_type == CLUSTERER_REMOVE_NODE)
1334+
if ((!db_mode || use_controller) && packet_type == CLUSTERER_REMOVE_NODE)
12961335
lock_stop_write(cl_list_lock);
12971336
else
12981337
lock_stop_read(cl_list_lock);
@@ -1318,12 +1357,7 @@ void bin_rcv_cl_packets(bin_packet_t *packet, int packet_type,
13181357
LM_DBG("received clusterer message from: %s:%hu with source id: %d and "
13191358
"cluster id: %d\n", ip, port, source_id, cl_id);
13201359

1321-
if (source_id == current_id) {
1322-
LM_ERR("Received message with bad source - same node id as this instance\n");
1323-
return;
1324-
}
1325-
1326-
if (!db_mode && (packet_type == CLUSTERER_NODE_DESCRIPTION ||
1360+
if ((!db_mode || use_controller) && (packet_type == CLUSTERER_NODE_DESCRIPTION ||
13271361
packet_type == CLUSTERER_FULL_TOP_UPDATE))
13281362
lock_start_write(cl_list_lock);
13291363
else
@@ -1335,6 +1369,22 @@ void bin_rcv_cl_packets(bin_packet_t *packet, int packet_type,
13351369
goto exit;
13361370
}
13371371

1372+
/* current_node is legitimately NULL while this node's identity is being
1373+
* (re)established for a dynamically constructed cluster (clusterer_ctrl
1374+
* update_identity: the cluster can already exist and receive BIN packets
1375+
* before current_node is assigned). Drop the packet instead of
1376+
* dereferencing NULL. */
1377+
if (!cl->current_node) {
1378+
LM_INFO("Received message for cluster [%d] before local identity is "
1379+
"established, ignoring\n", cl_id);
1380+
goto exit;
1381+
}
1382+
1383+
if (source_id == cluster_self_id(cl)) {
1384+
LM_ERR("Received message with bad source - same node id as this instance\n");
1385+
goto exit;
1386+
}
1387+
13381388
lock_get(cl->current_node->lock);
13391389
if (!(cl->current_node->flags & NODE_STATE_ENABLED)) {
13401390
lock_release(cl->current_node->lock);
@@ -1347,7 +1397,7 @@ void bin_rcv_cl_packets(bin_packet_t *packet, int packet_type,
13471397

13481398
if (!node) {
13491399
LM_INFO("Received message with unknown source id [%d]\n", source_id);
1350-
if (!db_mode)
1400+
if (!cl_db_mode(cl))
13511401
handle_internal_msg_unknown(packet, cl, packet_type, &ri->src_su,
13521402
ri->proto, source_id);
13531403
} else {
@@ -1374,7 +1424,7 @@ void bin_rcv_cl_packets(bin_packet_t *packet, int packet_type,
13741424
}
13751425

13761426
exit:
1377-
if (!db_mode && (packet_type == CLUSTERER_NODE_DESCRIPTION ||
1427+
if ((!db_mode || use_controller) && (packet_type == CLUSTERER_NODE_DESCRIPTION ||
13781428
packet_type == CLUSTERER_FULL_TOP_UPDATE))
13791429
lock_stop_write(cl_list_lock);
13801430
else
@@ -1472,11 +1522,6 @@ static void bin_rcv_mod_packets(bin_packet_t *packet, int packet_type,
14721522
"cluster ids: %d->%d\n", ip, port, source_id, src_cluster_id, cluster_id);
14731523
}
14741524

1475-
if (source_id == current_id) {
1476-
LM_ERR("Received message with bad source - same node id as this instance\n");
1477-
return;
1478-
}
1479-
14801525
cap = (struct capability_reg *)ptr;
14811526
if (!cap) {
14821527
LM_ERR("Failed to get bin callback parameter\n");
@@ -1493,6 +1538,11 @@ static void bin_rcv_mod_packets(bin_packet_t *packet, int packet_type,
14931538
goto exit;
14941539
}
14951540

1541+
if (source_id == cluster_self_id(cl)) {
1542+
LM_ERR("Received message with bad source - same node id as this instance\n");
1543+
goto exit;
1544+
}
1545+
14961546
lock_get(cl->current_node->lock);
14971547
if (!(cl->current_node->flags & NODE_STATE_ENABLED)) {
14981548
lock_release(cl->current_node->lock);
@@ -1543,7 +1593,7 @@ static void bin_rcv_mod_packets(bin_packet_t *packet, int packet_type,
15431593
} else
15441594
lock_release(node->lock);
15451595

1546-
if (dest_id != current_id) {
1596+
if (dest_id != cluster_self_id(cl)) {
15471597
/* route the message */
15481598
bin_push_int(packet, cluster_id);
15491599
bin_push_int(packet, source_id);
@@ -1619,6 +1669,7 @@ int send_single_cap_update(cluster_info_t *cluster, struct local_cap *cap,
16191669

16201670
timestamp = (int)(unsigned long)time(NULL);
16211671

1672+
if (!cluster->current_node) return -1;
16221673
lock_get(cluster->current_node->lock);
16231674

16241675
for (neigh = cluster->current_node->neighbour_list; neigh;
@@ -1637,7 +1688,7 @@ int send_single_cap_update(cluster_info_t *cluster, struct local_cap *cap,
16371688
return -1;
16381689
}
16391690
bin_push_int(&packet, cluster->cluster_id);
1640-
bin_push_int(&packet, current_id);
1691+
bin_push_int(&packet, cluster_self_id(cluster));
16411692

16421693
bin_push_int(&packet, ++cluster->current_node->cap_seq_no);
16431694
bin_push_int(&packet, timestamp);
@@ -1646,7 +1697,7 @@ int send_single_cap_update(cluster_info_t *cluster, struct local_cap *cap,
16461697

16471698
/* only the current node */
16481699
bin_push_int(&packet, 1);
1649-
bin_push_int(&packet, current_id);
1700+
bin_push_int(&packet, cluster_self_id(cluster));
16501701

16511702
/* only a single capability */
16521703
bin_push_int(&packet, 1);
@@ -1656,7 +1707,7 @@ int send_single_cap_update(cluster_info_t *cluster, struct local_cap *cap,
16561707
bin_push_int(&packet, 0); /* don't require reply */
16571708

16581709
bin_push_int(&packet, 1); /* path length is 1, only current node at this point */
1659-
bin_push_int(&packet, current_id);
1710+
bin_push_int(&packet, cluster_self_id(cluster));
16601711
bin_get_buffer(&packet, &bin_buffer);
16611712

16621713
for (i = 0; i < no_dests; i++)
@@ -1704,7 +1755,7 @@ int send_cap_update(node_info_t *dest_node, int require_reply)
17041755
return -1;
17051756
}
17061757
bin_push_int(&packet, dest_node->cluster->cluster_id);
1707-
bin_push_int(&packet, current_id);
1758+
bin_push_int(&packet, cluster_self_id(dest_node->cluster));
17081759

17091760
lock_get(dest_node->cluster->current_node->lock);
17101761

@@ -1719,7 +1770,7 @@ int send_cap_update(node_info_t *dest_node, int require_reply)
17191770
for (cl_cap = dest_node->cluster->capabilities, nr_cap = 0; cl_cap;
17201771
cl_cap = cl_cap->next, nr_cap++) ;
17211772
if (nr_cap) {
1722-
bin_push_int(&packet, current_id);
1773+
bin_push_int(&packet, cluster_self_id(dest_node->cluster));
17231774
bin_push_int(&packet, nr_cap);
17241775
for (cl_cap=dest_node->cluster->capabilities;cl_cap;cl_cap=cl_cap->next) {
17251776
bin_push_str(&packet, &cl_cap->reg.name);
@@ -1750,7 +1801,7 @@ int send_cap_update(node_info_t *dest_node, int require_reply)
17501801
bin_push_int(&packet, require_reply);
17511802

17521803
bin_push_int(&packet, 1); /* path length is 1, only current node at this point */
1753-
bin_push_int(&packet, current_id);
1804+
bin_push_int(&packet, cluster_self_id(dest_node->cluster));
17541805
bin_get_buffer(&packet, &bin_buffer);
17551806

17561807
if (msg_send(dest_node->cluster->send_sock, dest_node->proto, &dest_node->addr,
@@ -1903,9 +1954,25 @@ int cl_register_cap(str *cap, cl_packet_cb_f packet_cb, cl_event_cb_f event_cb,
19031954

19041955
cluster = get_cluster_by_id(cluster_id);
19051956
if (!cluster) {
1906-
LM_ERR("cluster id %d is not defined in the %s\n", cluster_id,
1907-
db_mode ? "DB" : "script");
1908-
return -1;
1957+
if (use_controller) {
1958+
cluster = shm_malloc(sizeof *cluster);
1959+
if (!cluster) { LM_ERR("no shm\n"); return -1; }
1960+
memset(cluster, 0, sizeof *cluster);
1961+
cluster->cluster_id = cluster_id;
1962+
cluster->controller_managed = 1; /* on-demand controller stub */
1963+
if ((cluster->lock = lock_alloc()) == NULL || !lock_init(cluster->lock)) {
1964+
shm_free(cluster); return -1;
1965+
}
1966+
if (cl_list_lock) lock_start_write(cl_list_lock);
1967+
cluster->next = *cluster_list;
1968+
*cluster_list = cluster;
1969+
if (cl_list_lock) lock_stop_write(cl_list_lock);
1970+
LM_INFO("clusterer: auto-created stub for cluster %d\n", cluster_id);
1971+
} else {
1972+
LM_ERR("cluster id %d is not defined in the %s\n", cluster_id,
1973+
db_mode ? "DB" : "script");
1974+
return -1;
1975+
}
19091976
}
19101977

19111978
new_cl_cap = shm_malloc(sizeof *new_cl_cap + cap->len + CAP_SR_ID_PREFIX_LEN);

0 commit comments

Comments
 (0)