I have trouble to find the code line in net/ipv4/igmp.c, which responds to a IGMP v1 query with a IGMP report.

I wrote printk in the beginning of each function in igmp.c. When I send a IGMP v3 query, I can observe via the printk messages straight forward the function calls:

igmp_heard_query() -> igmp_gq_start_timer() -> igmp_gq_timer_expire() -> igmpv3_send_report()

This is what I expected and what I'm looking for, except that I only can observe this on a v3 query.

When I send a IGMP v1 query I can only observe the calls igmp_rcv() -> igmp_heard_query()

That makes absolutely no sense to me, because one function have to send the report. (I assume igmp_send_report())

My setup is fine, I can see the v1 queries/reports in wireshark and verified, that they are sent to my self compiled linux kernel. I'm not sending accidentally the query to my notebook.

console_loglevel is set to 8:

# cat /proc/sys/kernel/printk
8       4       1       7

igmp_heard_query() https://github.com/torvalds/linux/blob/8032bf1233a74627ce69b803608e650f3f35971c/net/ipv4/igmp.c#L940

In this whole function, I don't see anything related for v1 to

  1. starting a timer
  2. trigger a timer to expire
  3. something which then calls igmp_send_report()

/* return true if packet was dropped */
static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
    int len)
{
    struct igmphdr      *ih = igmp_hdr(skb);
    struct igmpv3_query *ih3 = igmpv3_query_hdr(skb);
    struct ip_mc_list   *im;
    __be32          group = ih->group;
    int         max_delay;
    int         mark = 0;
    struct net      *net = dev_net(in_dev->dev);


    if (len == 8) {
        if (ih->code == 0) {
            /* Alas, old v1 router presents here. */

            max_delay = IGMP_QUERY_RESPONSE_INTERVAL;
            in_dev->mr_v1_seen = jiffies +
                (in_dev->mr_qrv * in_dev->mr_qi) +
                in_dev->mr_qri;
            group = 0;
        } else {
            /* v2 router present */
            max_delay = ih->code*(HZ/IGMP_TIMER_SCALE);
            in_dev->mr_v2_seen = jiffies +
                (in_dev->mr_qrv * in_dev->mr_qi) +
                in_dev->mr_qri;
        }
        /* cancel the interface change timer */
        WRITE_ONCE(in_dev->mr_ifc_count, 0);
        if (del_timer(&in_dev->mr_ifc_timer))
            __in_dev_put(in_dev);
        /* clear deleted report items */
        igmpv3_clear_delrec(in_dev);
    } else if (len < 12) {
        return true;    /* ignore bogus packet; freed by caller */
    } else if (IGMP_V1_SEEN(in_dev)) {
        /* This is a v3 query with v1 queriers present */
        max_delay = IGMP_QUERY_RESPONSE_INTERVAL;
        group = 0;
    } else if (IGMP_V2_SEEN(in_dev)) {
        /* this is a v3 query with v2 queriers present;
         * Interpretation of the max_delay code is problematic here.
         * A real v2 host would use ih_code directly, while v3 has a
         * different encoding. We use the v3 encoding as more likely
         * to be intended in a v3 query.
         */
        max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
        if (!max_delay)
            max_delay = 1;  /* can't mod w/ 0 */
    } else { /* v3 */
        if (!pskb_may_pull(skb, sizeof(struct igmpv3_query)))
            return true;

        ih3 = igmpv3_query_hdr(skb);
        if (ih3->nsrcs) {
            if (!pskb_may_pull(skb, sizeof(struct igmpv3_query)
                       + ntohs(ih3->nsrcs)*sizeof(__be32)))
                return true;
            ih3 = igmpv3_query_hdr(skb);
        }

        max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
        if (!max_delay)
            max_delay = 1;  /* can't mod w/ 0 */
        in_dev->mr_maxdelay = max_delay;

        /* RFC3376, 4.1.6. QRV and 4.1.7. QQIC, when the most recently
         * received value was zero, use the default or statically
         * configured value.
         */
        in_dev->mr_qrv = ih3->qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
        in_dev->mr_qi = IGMPV3_QQIC(ih3->qqic)*HZ ?: IGMP_QUERY_INTERVAL;

        /* RFC3376, 8.3. Query Response Interval:
         * The number of seconds represented by the [Query Response
         * Interval] must be less than the [Query Interval].
         */
        if (in_dev->mr_qri >= in_dev->mr_qi)
            in_dev->mr_qri = (in_dev->mr_qi/HZ - 1)*HZ;

        if (!group) { /* general query */
            if (ih3->nsrcs)
                return true;    /* no sources allowed */
            igmp_gq_start_timer(in_dev);
            return false;
        }
        /* mark sources to include, if group & source-specific */
        mark = ih3->nsrcs != 0;
    }

    /*
     * - Start the timers in all of our membership records
     *   that the query applies to for the interface on
     *   which the query arrived excl. those that belong
     *   to a "local" group (224.0.0.X)
     * - For timers already running check if they need to
     *   be reset.
     * - Use the igmp->igmp_code field as the maximum
     *   delay possible
     */
    rcu_read_lock();
    for_each_pmc_rcu(in_dev, im) {
        int changed;

        if (group && group != im->multiaddr)
            continue;
        if (im->multiaddr == IGMP_ALL_HOSTS)
            continue;
        if (ipv4_is_local_multicast(im->multiaddr) &&
            !READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
            continue;
        spin_lock_bh(&im->lock);
        if (im->tm_running)
            im->gsquery = im->gsquery && mark;
        else
            im->gsquery = mark;
        changed = !im->gsquery ||
            igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs);
        spin_unlock_bh(&im->lock);
        if (changed)
            igmp_mod_timer(im, max_delay);
    }
    rcu_read_unlock();
    return false;
}

The only assumption I have, is that changing in_dev->mr_v1_seen will automatically trigger the in_dev->timer? But then still it is unclear, why I don't see the call of igmp_timer_expire() with my printk.

/*
 *  A socket has joined a multicast group on device dev.
 */
static void ____ip_mc_inc_group(struct in_device *in_dev, __be32 addr,
                unsigned int mode, gfp_t gfp)
...
timer_setup(&im->timer, igmp_timer_expire, 0);
1

There are 1 answers

1
Olaf4342 On BEST ANSWER

I found the solution: That's the behavior, if I didn't joined a multicast group.