summaryrefslogtreecommitdiffstats
path: root/net/ipv4/igmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/ipv4/igmp.c')
-rw-r--r--net/ipv4/igmp.c162
1 files changed, 162 insertions, 0 deletions
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index a3a697f5ffba..651cdf648ec4 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -1339,6 +1339,168 @@ out:
1339} 1339}
1340EXPORT_SYMBOL(ip_mc_inc_group); 1340EXPORT_SYMBOL(ip_mc_inc_group);
1341 1341
1342static int ip_mc_check_iphdr(struct sk_buff *skb)
1343{
1344 const struct iphdr *iph;
1345 unsigned int len;
1346 unsigned int offset = skb_network_offset(skb) + sizeof(*iph);
1347
1348 if (!pskb_may_pull(skb, offset))
1349 return -EINVAL;
1350
1351 iph = ip_hdr(skb);
1352
1353 if (iph->version != 4 || ip_hdrlen(skb) < sizeof(*iph))
1354 return -EINVAL;
1355
1356 offset += ip_hdrlen(skb) - sizeof(*iph);
1357
1358 if (!pskb_may_pull(skb, offset))
1359 return -EINVAL;
1360
1361 iph = ip_hdr(skb);
1362
1363 if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
1364 return -EINVAL;
1365
1366 len = skb_network_offset(skb) + ntohs(iph->tot_len);
1367 if (skb->len < len || len < offset)
1368 return -EINVAL;
1369
1370 skb_set_transport_header(skb, offset);
1371
1372 return 0;
1373}
1374
1375static int ip_mc_check_igmp_reportv3(struct sk_buff *skb)
1376{
1377 unsigned int len = skb_transport_offset(skb);
1378
1379 len += sizeof(struct igmpv3_report);
1380
1381 return pskb_may_pull(skb, len) ? 0 : -EINVAL;
1382}
1383
1384static int ip_mc_check_igmp_query(struct sk_buff *skb)
1385{
1386 unsigned int len = skb_transport_offset(skb);
1387
1388 len += sizeof(struct igmphdr);
1389 if (skb->len < len)
1390 return -EINVAL;
1391
1392 /* IGMPv{1,2}? */
1393 if (skb->len != len) {
1394 /* or IGMPv3? */
1395 len += sizeof(struct igmpv3_query) - sizeof(struct igmphdr);
1396 if (skb->len < len || !pskb_may_pull(skb, len))
1397 return -EINVAL;
1398 }
1399
1400 /* RFC2236+RFC3376 (IGMPv2+IGMPv3) require the multicast link layer
1401 * all-systems destination addresses (224.0.0.1) for general queries
1402 */
1403 if (!igmp_hdr(skb)->group &&
1404 ip_hdr(skb)->daddr != htonl(INADDR_ALLHOSTS_GROUP))
1405 return -EINVAL;
1406
1407 return 0;
1408}
1409
1410static int ip_mc_check_igmp_msg(struct sk_buff *skb)
1411{
1412 switch (igmp_hdr(skb)->type) {
1413 case IGMP_HOST_LEAVE_MESSAGE:
1414 case IGMP_HOST_MEMBERSHIP_REPORT:
1415 case IGMPV2_HOST_MEMBERSHIP_REPORT:
1416 /* fall through */
1417 return 0;
1418 case IGMPV3_HOST_MEMBERSHIP_REPORT:
1419 return ip_mc_check_igmp_reportv3(skb);
1420 case IGMP_HOST_MEMBERSHIP_QUERY:
1421 return ip_mc_check_igmp_query(skb);
1422 default:
1423 return -ENOMSG;
1424 }
1425}
1426
1427static inline __sum16 ip_mc_validate_checksum(struct sk_buff *skb)
1428{
1429 return skb_checksum_simple_validate(skb);
1430}
1431
1432static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed)
1433
1434{
1435 struct sk_buff *skb_chk;
1436 unsigned int transport_len;
1437 unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr);
1438 int ret;
1439
1440 transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb);
1441
1442 skb_get(skb);
1443 skb_chk = skb_checksum_trimmed(skb, transport_len,
1444 ip_mc_validate_checksum);
1445 if (!skb_chk)
1446 return -EINVAL;
1447
1448 if (!pskb_may_pull(skb_chk, len)) {
1449 kfree_skb(skb_chk);
1450 return -EINVAL;
1451 }
1452
1453 ret = ip_mc_check_igmp_msg(skb_chk);
1454 if (ret) {
1455 kfree_skb(skb_chk);
1456 return ret;
1457 }
1458
1459 if (skb_trimmed)
1460 *skb_trimmed = skb_chk;
1461 else
1462 kfree_skb(skb_chk);
1463
1464 return 0;
1465}
1466
1467/**
1468 * ip_mc_check_igmp - checks whether this is a sane IGMP packet
1469 * @skb: the skb to validate
1470 * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional)
1471 *
1472 * Checks whether an IPv4 packet is a valid IGMP packet. If so sets
1473 * skb network and transport headers accordingly and returns zero.
1474 *
1475 * -EINVAL: A broken packet was detected, i.e. it violates some internet
1476 * standard
1477 * -ENOMSG: IP header validation succeeded but it is not an IGMP packet.
1478 * -ENOMEM: A memory allocation failure happened.
1479 *
1480 * Optionally, an skb pointer might be provided via skb_trimmed (or set it
1481 * to NULL): After parsing an IGMP packet successfully it will point to
1482 * an skb which has its tail aligned to the IP packet end. This might
1483 * either be the originally provided skb or a trimmed, cloned version if
1484 * the skb frame had data beyond the IP packet. A cloned skb allows us
1485 * to leave the original skb and its full frame unchanged (which might be
1486 * desirable for layer 2 frame jugglers).
1487 *
1488 * The caller needs to release a reference count from any returned skb_trimmed.
1489 */
1490int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed)
1491{
1492 int ret = ip_mc_check_iphdr(skb);
1493
1494 if (ret < 0)
1495 return ret;
1496
1497 if (ip_hdr(skb)->protocol != IPPROTO_IGMP)
1498 return -ENOMSG;
1499
1500 return __ip_mc_check_igmp(skb, skb_trimmed);
1501}
1502EXPORT_SYMBOL(ip_mc_check_igmp);
1503
1342/* 1504/*
1343 * Resend IGMP JOIN report; used by netdev notifier. 1505 * Resend IGMP JOIN report; used by netdev notifier.
1344 */ 1506 */