From c76627592d5b2f8b2628bac48b089f5ffe348869 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Fri, 4 Sep 2026 16:29:44 +0200 Subject: [PATCH] rose: keep the route needed for routing loop detection rose_route_frame() walks the route list before parsing the Call Request facilities. When a CALL REQUEST arrives on a LCI that already belongs to a route, the "Remove an existing unused route" branch deletes that route with rose_remove_route() and breaks out of the walk, so that a new call is able to reuse a stale LCI. The routing loop detection that follows looks for a route recording the same random number and the same pair of callsigns as the incoming call. The route that has just been deleted is exactly the one it would have matched, so a call coming back to us around a routing loop is not detected: it is routed again, a new route is allocated, and the next copy repeats the cycle. Two nodes whose routes point at each other then relay the same call to each other as fast as the CPU allows. This was observed on an AX.25 network where two nodes each believed the route to a third one went through the other. The same CALL REQUEST, carrying the same random number, was re-sent every 400 us, interleaved with CLEAR REQUEST "Not obtainable", diagnostic 120, until the AX.25 link collapsed. Over nine days one node emitted 204 million frames on a link that normally carries a few frames per minute. The outgoing LCI stayed at 001 the whole time, because deleting the route freed it for immediate reuse. Parse the facilities before walking the route list, and in both branches remove the route only when the incoming call differs from the one it records. When it is the same call, keep the route and clear the call with diagnostic 120, which is what the loop detection would have done. The predicate is factored out as rose_same_call() and reused by the loop detection itself. Tested with two ROSE nodes routing a call back to each other. Before this change the kernel relayed 377 CALL REQUEST in ten seconds, never emitted a CLEAR, and the AX.25 link dropped. After it, the first call is relayed once, the next copy is cleared with diagnostic 120, and the link stays up. Signed-off-by: Bernard Pidoux --- net/rose/rose_route.c | 71 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/net/rose/rose_route.c b/net/rose/rose_route.c index e31842e..2d7454b 100644 --- a/net/rose/rose_route.c +++ b/net/rose/rose_route.c @@ -851,6 +851,21 @@ void rose_link_device_down(struct net_device *dev) } } +/* + * Is this Call Request the very same call as the one an existing route + * was built for? Both the loop detection and the reuse of a stale LCI + * below rely on this test. + */ +static bool rose_same_call(const struct rose_route *rose_route, + const rose_address *src_addr, + const struct rose_facilities_struct *facilities) +{ + return rose_route->rand == facilities->rand && + rosecmp(src_addr, &rose_route->src_addr) == 0 && + ax25cmp(&facilities->dest_call, &rose_route->src_call) == 0 && + ax25cmp(&facilities->source_call, &rose_route->dest_call) == 0; +} + /* * Route a frame to an appropriate AX.25 connection. * A NULL ax25_cb indicates an internally generated frame. @@ -961,6 +976,16 @@ int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25) goto out; } + memset(&facilities, 0x00, sizeof(struct rose_facilities_struct)); + + if (frametype == ROSE_CALL_REQUEST && + !rose_parse_facilities(skb->data + ROSE_CALL_REQ_FACILITIES_OFF, + skb->len - ROSE_CALL_REQ_FACILITIES_OFF, + &facilities)) { + rose_transmit_clear_request(rose_neigh, lci, ROSE_INVALID_FACILITY, 76); + goto out; + } + /* * Route it to the next in line if we have an entry for it. */ @@ -969,7 +994,21 @@ int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25) if (rose_route->lci1 == lci && rose_route->neigh1 == rose_neigh) { if (frametype == ROSE_CALL_REQUEST) { - /* F6FBB - Remove an existing unused route */ + if (rose_same_call(rose_route, src_addr, + &facilities)) { + /* + * The very same call coming back + * to us: a routing loop. Keep + * the route, the loop detection + * below is what matches on it. + */ + rose_transmit_clear_request(rose_neigh, + lci, + ROSE_NOT_OBTAINABLE, + 120); + goto out; + } + /* Remove an existing unused route */ rose_remove_route(rose_route); break; } else if (rose_route->neigh2 != NULL) { @@ -990,7 +1029,21 @@ int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25) if (rose_route->lci2 == lci && rose_route->neigh2 == rose_neigh) { if (frametype == ROSE_CALL_REQUEST) { - /* F6FBB - Remove an existing unused route */ + if (rose_same_call(rose_route, src_addr, + &facilities)) { + /* + * The very same call coming back + * to us: a routing loop. Keep + * the route, the loop detection + * below is what matches on it. + */ + rose_transmit_clear_request(rose_neigh, + lci, + ROSE_NOT_OBTAINABLE, + 120); + goto out; + } + /* Remove an existing unused route */ rose_remove_route(rose_route); break; } else if (rose_route->neigh1 != NULL) { @@ -1021,24 +1074,12 @@ int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25) goto out; } - memset(&facilities, 0x00, sizeof(struct rose_facilities_struct)); - - if (!rose_parse_facilities(skb->data + ROSE_CALL_REQ_FACILITIES_OFF, - skb->len - ROSE_CALL_REQ_FACILITIES_OFF, - &facilities)) { - rose_transmit_clear_request(rose_neigh, lci, ROSE_INVALID_FACILITY, 76); - goto out; - } - /* * Check for routing loops. */ rose_route = rose_route_list; while (rose_route != NULL) { - if (rose_route->rand == facilities.rand && - rosecmp(src_addr, &rose_route->src_addr) == 0 && - ax25cmp(&facilities.dest_call, &rose_route->src_call) == 0 && - ax25cmp(&facilities.source_call, &rose_route->dest_call) == 0) { + if (rose_same_call(rose_route, src_addr, &facilities)) { rose_transmit_clear_request(rose_neigh, lci, ROSE_NOT_OBTAINABLE, 120); goto out; }