Skip to content

Commit b8e9fcd

Browse files
committed
Addressed copilot's comment
1 parent 4abab85 commit b8e9fcd

3 files changed

Lines changed: 141 additions & 1 deletion

File tree

src/test/unit/unit.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ Suite *wolf_suite(void)
234234
tcase_add_test(tc_utils, test_multicast_udp_receive_requires_join);
235235
tcase_add_test(tc_utils, test_multicast_udp_send_mac_ttl_loop_and_options);
236236
tcase_add_test(tc_utils, test_multicast_igmp_query_refreshes_report);
237+
tcase_add_test(tc_utils, test_multicast_join_requires_configured_ip);
238+
tcase_add_test(tc_utils, test_multicast_if_pins_egress_interface);
237239
#endif
238240
tcase_add_test(tc_utils, test_tcp_no_rst_for_broadcast_dst);
239241
tcase_add_test(tc_utils, test_tcp_no_rst_for_multicast_dst);

src/test/unit/unit_tests_multicast.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,130 @@ START_TEST(test_multicast_igmp_query_refreshes_report)
252252
}
253253
END_TEST
254254

255+
START_TEST(test_multicast_join_requires_configured_ip)
256+
{
257+
struct wolfIP s;
258+
int sd;
259+
struct wolfIP_ip_mreq mreq;
260+
ip4 group = 0xE9020101U;
261+
262+
/* No wolfIP_ipconfig_set on primary: interface has no source IP. */
263+
wolfIP_init(&s);
264+
mock_link_init(&s);
265+
sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
266+
ck_assert_int_gt(sd, 0);
267+
268+
/* Join via IPADDR_ANY must fail when the route-selected interface has no
269+
* configured source IP: otherwise the join would be recorded but the
270+
* IGMP report could never be built, announcing membership only locally. */
271+
multicast_mreq(&mreq, group, IPADDR_ANY);
272+
ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP,
273+
WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), -WOLFIP_EINVAL);
274+
ck_assert_uint_eq(s.mcast[0].refs, 0);
275+
276+
/* Once the interface has a source IP, the same join succeeds and a
277+
* report is emitted. */
278+
wolfIP_ipconfig_set(&s, 0x0A000002U, 0xFFFFFF00U, 0);
279+
last_frame_sent_size = 0;
280+
ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP,
281+
WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0);
282+
ck_assert_uint_eq(s.mcast[0].refs, 1);
283+
ck_assert_uint_gt(last_frame_sent_size, 0);
284+
ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + 9], WI_IPPROTO_IGMP);
285+
}
286+
END_TEST
287+
288+
START_TEST(test_multicast_if_pins_egress_interface)
289+
{
290+
struct wolfIP s;
291+
int sd;
292+
struct tsocket *ts;
293+
struct wolfIP_mreq_addr addr;
294+
struct wolfIP_mreq_addr got;
295+
socklen_t gotlen = sizeof(got);
296+
struct wolfIP_sockaddr_in bind_addr;
297+
struct wolfIP_sockaddr_in dst;
298+
uint8_t primary_mac[6];
299+
uint8_t secondary_mac[6];
300+
struct wolfIP_ll_dev *ll_primary;
301+
struct wolfIP_ll_dev *ll_secondary;
302+
ip4 primary_ip = 0x0A000002U; /* 10.0.0.2/24 */
303+
ip4 secondary_ip = 0x0A000102U; /* 10.0.1.2/24 */
304+
ip4 group = 0xEF010203U; /* 239.1.2.3 */
305+
const char payload[] = "if";
306+
307+
setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip);
308+
ll_primary = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF);
309+
ll_secondary = wolfIP_getdev_ex(&s, TEST_SECOND_IF);
310+
ck_assert_ptr_nonnull(ll_primary);
311+
ck_assert_ptr_nonnull(ll_secondary);
312+
memcpy(primary_mac, ll_primary->mac, 6);
313+
memcpy(secondary_mac, ll_secondary->mac, 6);
314+
315+
sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
316+
ck_assert_int_gt(sd, 0);
317+
ts = &s.udpsockets[SOCKET_UNMARK(sd)];
318+
memset(&bind_addr, 0, sizeof(bind_addr));
319+
bind_addr.sin_family = AF_INET;
320+
bind_addr.sin_port = ee16(5002);
321+
ck_assert_int_eq(wolfIP_sock_bind(&s, sd,
322+
(struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)), 0);
323+
324+
/* Pin egress to the secondary interface. */
325+
memset(&addr, 0, sizeof(addr));
326+
addr.s_addr = ee32(secondary_ip);
327+
ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP,
328+
WOLFIP_IP_MULTICAST_IF, &addr, sizeof(addr)), 0);
329+
ck_assert_uint_eq(ts->sock.udp.mcast_if_set, 1);
330+
ck_assert_uint_eq(ts->sock.udp.mcast_if_idx, TEST_SECOND_IF);
331+
332+
/* getsockopt reports the address of the pinned interface. */
333+
memset(&got, 0, sizeof(got));
334+
ck_assert_int_eq(wolfIP_sock_getsockopt(&s, sd, WOLFIP_SOL_IP,
335+
WOLFIP_IP_MULTICAST_IF, &got, &gotlen), 0);
336+
ck_assert_uint_eq(ee32(got.s_addr), secondary_ip);
337+
338+
/* A multicast sendto must egress on the secondary interface — verify via
339+
* the source MAC of the transmitted frame (mock_send is shared across
340+
* interfaces but eth_output_add_header uses the egress dev's MAC). */
341+
memset(&dst, 0, sizeof(dst));
342+
dst.sin_family = AF_INET;
343+
dst.sin_port = ee16(5002);
344+
dst.sin_addr.s_addr = ee32(group);
345+
last_frame_sent_size = 0;
346+
ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0,
347+
(struct wolfIP_sockaddr *)&dst, sizeof(dst)),
348+
(int)sizeof(payload));
349+
ck_assert_int_eq(wolfIP_poll(&s, 1), 0);
350+
ck_assert_uint_gt(last_frame_sent_size, 0);
351+
ck_assert_mem_eq(last_frame_sent + 6, secondary_mac, 6);
352+
353+
/* Clearing with INADDR_ANY reverts to per-destination routing (Linux
354+
* IP_MULTICAST_IF semantics). */
355+
memset(&addr, 0, sizeof(addr));
356+
addr.s_addr = ee32(IPADDR_ANY);
357+
ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP,
358+
WOLFIP_IP_MULTICAST_IF, &addr, sizeof(addr)), 0);
359+
ck_assert_uint_eq(ts->sock.udp.mcast_if_set, 0);
360+
ck_assert_uint_eq(ts->sock.udp.mcast_if_idx, 0);
361+
362+
/* Next multicast sendto goes via the default route — primary interface. */
363+
last_frame_sent_size = 0;
364+
ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0,
365+
(struct wolfIP_sockaddr *)&dst, sizeof(dst)),
366+
(int)sizeof(payload));
367+
ck_assert_int_eq(wolfIP_poll(&s, 1), 0);
368+
ck_assert_uint_gt(last_frame_sent_size, 0);
369+
ck_assert_mem_eq(last_frame_sent + 6, primary_mac, 6);
370+
371+
/* After clearing, getsockopt falls back to the socket's current interface
372+
* (which is the primary route for the previous sendto). */
373+
gotlen = sizeof(got);
374+
memset(&got, 0, sizeof(got));
375+
ck_assert_int_eq(wolfIP_sock_getsockopt(&s, sd, WOLFIP_SOL_IP,
376+
WOLFIP_IP_MULTICAST_IF, &got, &gotlen), 0);
377+
ck_assert_uint_eq(ee32(got.s_addr), primary_ip);
378+
}
379+
END_TEST
380+
255381
#endif /* IP_MULTICAST */

src/wolfip.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6173,13 +6173,18 @@ int wolfIP_sock_read(struct wolfIP *s, int sockfd, void *buf, size_t len)
61736173
static int mcast_if_from_addr(struct wolfIP *s, ip4 if_addr, ip4 group,
61746174
unsigned int *if_idx)
61756175
{
6176+
struct ipconf *conf;
61766177
int found = 0;
61776178

61786179
if (!s || !if_idx || !wolfIP_ip_is_multicast(group))
61796180
return -WOLFIP_EINVAL;
61806181
if (if_addr == IPADDR_ANY) {
61816182
*if_idx = wolfIP_route_for_ip(s, group);
6182-
if (wolfIP_ipconf_at(s, *if_idx))
6183+
conf = wolfIP_ipconf_at(s, *if_idx);
6184+
/* Require a configured source IP so igmp_send_report can build a
6185+
* valid report; otherwise the join would succeed locally but never
6186+
* be announced on the wire. */
6187+
if (conf && conf->ip != IPADDR_ANY)
61836188
return 0;
61846189
return -WOLFIP_EINVAL;
61856190
}
@@ -6352,6 +6357,13 @@ int wolfIP_sock_setsockopt(struct wolfIP *s, int sockfd, int level, int optname,
63526357
if (!addr || optlen < (socklen_t)sizeof(*addr))
63536358
return -WOLFIP_EINVAL;
63546359
if_addr = ee32(addr->s_addr);
6360+
/* Linux IP_MULTICAST_IF with INADDR_ANY clears the pinned
6361+
* interface and reverts to per-destination routing. */
6362+
if (if_addr == IPADDR_ANY) {
6363+
ts->sock.udp.mcast_if_set = 0;
6364+
ts->sock.udp.mcast_if_idx = 0;
6365+
return 0;
6366+
}
63556367
ret = mcast_if_from_addr(s, if_addr, IGMP_ALL_HOSTS, &if_idx);
63566368
if (ret < 0)
63576369
return ret;

0 commit comments

Comments
 (0)