All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
To: Xin Long <lucien.xin@gmail.com>
Cc: dev@openvswitch.org, ovs-dev@openvswitch.org,
	Davide Caratti <dcaratti@redhat.com>,
	Jiri Pirko <jiri@resnulli.us>,
	network dev <netdev@vger.kernel.org>,
	Paul Blakey <paulb@nvidia.com>, Florian Westphal <fw@strlen.de>,
	Jamal Hadi Salim <jhs@mojatatu.com>,
	Ilya Maximets <i.maximets@ovn.org>,
	Eric Dumazet <edumazet@google.com>,
	Cong Wang <xiyou.wangcong@gmail.com>,
	kuba@kernel.org, Paolo Abeni <pabeni@redhat.com>,
	davem@davemloft.net, Pablo Neira Ayuso <pablo@netfilter.org>
Subject: Re: [ovs-dev] [PATCHv2 net-next 5/5] net: move the nat function to nf_nat_ovs for ovs and tc
Date: Wed, 23 Nov 2022 12:13:35 -0300	[thread overview]
Message-ID: <20221123151335.ssrnv7jfrdugmcgg@t14s.localdomain> (raw)
In-Reply-To: <Y343wyO20XUvwuvg@t14s.localdomain>

On Wed, Nov 23, 2022 at 12:09:55PM -0300, Marcelo Ricardo Leitner wrote:
> On Tue, Nov 22, 2022 at 12:32:21PM -0500, Xin Long wrote:
> > +int nf_ct_nat(struct sk_buff *skb, struct nf_conn *ct,
> > +	      enum ip_conntrack_info ctinfo, int *action,
> > +	      const struct nf_nat_range2 *range, bool commit)
> > +{
> > +	enum nf_nat_manip_type maniptype;
> > +	int err, ct_action = *action;
> > +
> > +	*action = 0;
> > +
> > +	/* Add NAT extension if not confirmed yet. */
> > +	if (!nf_ct_is_confirmed(ct) && !nf_ct_nat_ext_add(ct))
> > +		return NF_ACCEPT;   /* Can't NAT. */
> > +
> > +	if (ctinfo != IP_CT_NEW && (ct->status & IPS_NAT_MASK) &&
> > +	    (ctinfo != IP_CT_RELATED || commit)) {
> > +		/* NAT an established or related connection like before. */
> > +		if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY)
> > +			/* This is the REPLY direction for a connection
> > +			 * for which NAT was applied in the forward
> > +			 * direction.  Do the reverse NAT.
> > +			 */
> > +			maniptype = ct->status & IPS_SRC_NAT
> > +				? NF_NAT_MANIP_DST : NF_NAT_MANIP_SRC;
> > +		else
> > +			maniptype = ct->status & IPS_SRC_NAT
> > +				? NF_NAT_MANIP_SRC : NF_NAT_MANIP_DST;
> > +	} else if (ct_action & (1 << NF_NAT_MANIP_SRC)) {
> > +		maniptype = NF_NAT_MANIP_SRC;
> > +	} else if (ct_action & (1 << NF_NAT_MANIP_DST)) {
> > +		maniptype = NF_NAT_MANIP_DST;
> > +	} else {
> > +		return NF_ACCEPT;
> > +	}
> > +
> > +	err = nf_ct_nat_execute(skb, ct, ctinfo, action, range, maniptype);
> > +	if (err == NF_ACCEPT && ct->status & IPS_DST_NAT) {
> > +		if (ct->status & IPS_SRC_NAT) {
> > +			if (maniptype == NF_NAT_MANIP_SRC)
> > +				maniptype = NF_NAT_MANIP_DST;
> > +			else
> > +				maniptype = NF_NAT_MANIP_SRC;
> > +
> > +			err = nf_ct_nat_execute(skb, ct, ctinfo, action, range,
> > +						maniptype);
> > +		} else if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) {
> > +			err = nf_ct_nat_execute(skb, ct, ctinfo, action, NULL,
> > +						NF_NAT_MANIP_SRC);
> > +		}
> > +	}
> > +	return err;
> > +}
> > +EXPORT_SYMBOL_GPL(nf_ct_nat);
> > diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c
> > index cc643a556ea1..d03c75165663 100644
> > --- a/net/openvswitch/conntrack.c
> > +++ b/net/openvswitch/conntrack.c
> > @@ -726,144 +726,27 @@ static void ovs_nat_update_key(struct sw_flow_key *key,
> >  	}
> >  }
> >  
> > -/* Modelled after nf_nat_ipv[46]_fn().
> > - * range is only used for new, uninitialized NAT state.
> > - * Returns either NF_ACCEPT or NF_DROP.
> > - */
> > -static int ovs_ct_nat_execute(struct sk_buff *skb, struct nf_conn *ct,
> > -			      enum ip_conntrack_info ctinfo,
> > -			      const struct nf_nat_range2 *range,
> > -			      enum nf_nat_manip_type maniptype, struct sw_flow_key *key)
> > -{
> > -	int hooknum, err = NF_ACCEPT;
> > -
> > -	/* See HOOK2MANIP(). */
> > -	if (maniptype == NF_NAT_MANIP_SRC)
> > -		hooknum = NF_INET_LOCAL_IN; /* Source NAT */
> > -	else
> > -		hooknum = NF_INET_LOCAL_OUT; /* Destination NAT */
> > -
> > -	switch (ctinfo) {
> > -	case IP_CT_RELATED:
> > -	case IP_CT_RELATED_REPLY:
> > -		if (IS_ENABLED(CONFIG_NF_NAT) &&
> > -		    skb->protocol == htons(ETH_P_IP) &&
> > -		    ip_hdr(skb)->protocol == IPPROTO_ICMP) {
> > -			if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo,
> > -							   hooknum))
> > -				err = NF_DROP;
> > -			goto out;
> > -		} else if (IS_ENABLED(CONFIG_IPV6) &&
> > -			   skb->protocol == htons(ETH_P_IPV6)) {
> > -			__be16 frag_off;
> > -			u8 nexthdr = ipv6_hdr(skb)->nexthdr;
> > -			int hdrlen = ipv6_skip_exthdr(skb,
> > -						      sizeof(struct ipv6hdr),
> > -						      &nexthdr, &frag_off);
> > -
> > -			if (hdrlen >= 0 && nexthdr == IPPROTO_ICMPV6) {
> > -				if (!nf_nat_icmpv6_reply_translation(skb, ct,
> > -								     ctinfo,
> > -								     hooknum,
> > -								     hdrlen))
> > -					err = NF_DROP;
> > -				goto out;
> > -			}
> > -		}
> > -		/* Non-ICMP, fall thru to initialize if needed. */
> > -		fallthrough;
> > -	case IP_CT_NEW:
> > -		/* Seen it before?  This can happen for loopback, retrans,
> > -		 * or local packets.
> > -		 */
> > -		if (!nf_nat_initialized(ct, maniptype)) {
> > -			/* Initialize according to the NAT action. */
> > -			err = (range && range->flags & NF_NAT_RANGE_MAP_IPS)
> > -				/* Action is set up to establish a new
> > -				 * mapping.
> > -				 */
> > -				? nf_nat_setup_info(ct, range, maniptype)
> > -				: nf_nat_alloc_null_binding(ct, hooknum);
> > -			if (err != NF_ACCEPT)
> > -				goto out;
> > -		}
> > -		break;
> > -
> > -	case IP_CT_ESTABLISHED:
> > -	case IP_CT_ESTABLISHED_REPLY:
> > -		break;
> > -
> > -	default:
> > -		err = NF_DROP;
> > -		goto out;
> > -	}
> > -
> > -	err = nf_nat_packet(ct, ctinfo, hooknum, skb);
> > -out:
> > -	/* Update the flow key if NAT successful. */
> > -	if (err == NF_ACCEPT)
> > -		ovs_nat_update_key(key, skb, maniptype);
> > -
> > -	return err;
> > -}
> > -
> >  /* Returns NF_DROP if the packet should be dropped, NF_ACCEPT otherwise. */
> >  static int ovs_ct_nat(struct net *net, struct sw_flow_key *key,
> >  		      const struct ovs_conntrack_info *info,
> >  		      struct sk_buff *skb, struct nf_conn *ct,
> >  		      enum ip_conntrack_info ctinfo)
> >  {
> > -	enum nf_nat_manip_type maniptype;
> > -	int err;
> > +	int err, action = 0;
> >  
> >  	if (!(info->nat & OVS_CT_NAT))
> >  		return NF_ACCEPT;
> > +	if (info->nat & OVS_CT_SRC_NAT)
> > +		action |= (1 << NF_NAT_MANIP_SRC);
> > +	if (info->nat & OVS_CT_DST_NAT)
> > +		action |= (1 << NF_NAT_MANIP_DST);
> 
> I'm wondering why this dance at this level with supporting multiple
> MANIPs while actually only one can be used at a time.
> 
> act_ct will reject an action using both:
>         if ((p->ct_action & TCA_CT_ACT_NAT_SRC) &&
>             (p->ct_action & TCA_CT_ACT_NAT_DST)) {
>                 NL_SET_ERR_MSG_MOD(extack, "dnat and snat can't be enabled at the same time");
>                 return -EOPNOTSUPP;
> 
> I couldn't find this kind of check in ovs code right now (didn't look much, I
> confess :)), but even the code here was already doing:
> 
> -	} else if (info->nat & OVS_CT_SRC_NAT) {
> -		maniptype = NF_NAT_MANIP_SRC;
> -	} else if (info->nat & OVS_CT_DST_NAT) {
> -		maniptype = NF_NAT_MANIP_DST;
> 
> And in case of tuple conflict, maniptype will be forcibly updated in
> [*] below.

Ahh.. just found why.. in case of typle conflict, nf_ct_nat() now may
set both.

> 
> Anyhow, if really needed, it would be nice to use BIT(NF_NAT_MANIP_..)
> instead.

But still this.

> 
> >  
> > -	/* Add NAT extension if not confirmed yet. */
> > -	if (!nf_ct_is_confirmed(ct) && !nf_ct_nat_ext_add(ct))
> > -		return NF_ACCEPT;   /* Can't NAT. */
> > +	err = nf_ct_nat(skb, ct, ctinfo, &action, &info->range, info->commit);
> >  
> > -	/* Determine NAT type.
> > -	 * Check if the NAT type can be deduced from the tracked connection.
> > -	 * Make sure new expected connections (IP_CT_RELATED) are NATted only
> > -	 * when committing.
> > -	 */
> > -	if (ctinfo != IP_CT_NEW && ct->status & IPS_NAT_MASK &&
> > -	    (ctinfo != IP_CT_RELATED || info->commit)) {
> > -		/* NAT an established or related connection like before. */
> > -		if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY)
> > -			/* This is the REPLY direction for a connection
> > -			 * for which NAT was applied in the forward
> > -			 * direction.  Do the reverse NAT.
> > -			 */
> > -			maniptype = ct->status & IPS_SRC_NAT
> > -				? NF_NAT_MANIP_DST : NF_NAT_MANIP_SRC;
> > -		else
> > -			maniptype = ct->status & IPS_SRC_NAT
> > -				? NF_NAT_MANIP_SRC : NF_NAT_MANIP_DST;
> > -	} else if (info->nat & OVS_CT_SRC_NAT) {
> > -		maniptype = NF_NAT_MANIP_SRC;
> > -	} else if (info->nat & OVS_CT_DST_NAT) {
> > -		maniptype = NF_NAT_MANIP_DST;
> > -	} else {
> > -		return NF_ACCEPT; /* Connection is not NATed. */
> > -	}
> > -	err = ovs_ct_nat_execute(skb, ct, ctinfo, &info->range, maniptype, key);
> > -
> > -	if (err == NF_ACCEPT && ct->status & IPS_DST_NAT) {
> > -		if (ct->status & IPS_SRC_NAT) {
> > -			if (maniptype == NF_NAT_MANIP_SRC)
> > -				maniptype = NF_NAT_MANIP_DST;
> > -			else
> > -				maniptype = NF_NAT_MANIP_SRC;
> 
> [*]
> 
> > -
> > -			err = ovs_ct_nat_execute(skb, ct, ctinfo, &info->range,
> > -						 maniptype, key);
> > -		} else if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) {
> > -			err = ovs_ct_nat_execute(skb, ct, ctinfo, NULL,
> > -						 NF_NAT_MANIP_SRC, key);
> > -		}
> > -	}
> > +	if (action & (1 << NF_NAT_MANIP_SRC))
> > +		ovs_nat_update_key(key, skb, NF_NAT_MANIP_SRC);
> > +	if (action & (1 << NF_NAT_MANIP_DST))
> > +		ovs_nat_update_key(key, skb, NF_NAT_MANIP_DST);
> >  
> >  	return err;
> >  }
> > diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c
> > index c7782c9a6ab6..0c410220239f 100644
> > --- a/net/sched/act_ct.c
> > +++ b/net/sched/act_ct.c
> > @@ -863,90 +863,6 @@ static void tcf_ct_params_free_rcu(struct rcu_head *head)
> >  	tcf_ct_params_free(params);
> >  }
> >  
> > -#if IS_ENABLED(CONFIG_NF_NAT)
> > -/* Modelled after nf_nat_ipv[46]_fn().
> > - * range is only used for new, uninitialized NAT state.
> > - * Returns either NF_ACCEPT or NF_DROP.
> > - */
> > -static int ct_nat_execute(struct sk_buff *skb, struct nf_conn *ct,
> > -			  enum ip_conntrack_info ctinfo,
> > -			  const struct nf_nat_range2 *range,
> > -			  enum nf_nat_manip_type maniptype)
> > -{
> > -	__be16 proto = skb_protocol(skb, true);
> > -	int hooknum, err = NF_ACCEPT;
> > -
> > -	/* See HOOK2MANIP(). */
> > -	if (maniptype == NF_NAT_MANIP_SRC)
> > -		hooknum = NF_INET_LOCAL_IN; /* Source NAT */
> > -	else
> > -		hooknum = NF_INET_LOCAL_OUT; /* Destination NAT */
> > -
> > -	switch (ctinfo) {
> > -	case IP_CT_RELATED:
> > -	case IP_CT_RELATED_REPLY:
> > -		if (proto == htons(ETH_P_IP) &&
> > -		    ip_hdr(skb)->protocol == IPPROTO_ICMP) {
> > -			if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo,
> > -							   hooknum))
> > -				err = NF_DROP;
> > -			goto out;
> > -		} else if (IS_ENABLED(CONFIG_IPV6) && proto == htons(ETH_P_IPV6)) {
> > -			__be16 frag_off;
> > -			u8 nexthdr = ipv6_hdr(skb)->nexthdr;
> > -			int hdrlen = ipv6_skip_exthdr(skb,
> > -						      sizeof(struct ipv6hdr),
> > -						      &nexthdr, &frag_off);
> > -
> > -			if (hdrlen >= 0 && nexthdr == IPPROTO_ICMPV6) {
> > -				if (!nf_nat_icmpv6_reply_translation(skb, ct,
> > -								     ctinfo,
> > -								     hooknum,
> > -								     hdrlen))
> > -					err = NF_DROP;
> > -				goto out;
> > -			}
> > -		}
> > -		/* Non-ICMP, fall thru to initialize if needed. */
> > -		fallthrough;
> > -	case IP_CT_NEW:
> > -		/* Seen it before?  This can happen for loopback, retrans,
> > -		 * or local packets.
> > -		 */
> > -		if (!nf_nat_initialized(ct, maniptype)) {
> > -			/* Initialize according to the NAT action. */
> > -			err = (range && range->flags & NF_NAT_RANGE_MAP_IPS)
> > -				/* Action is set up to establish a new
> > -				 * mapping.
> > -				 */
> > -				? nf_nat_setup_info(ct, range, maniptype)
> > -				: nf_nat_alloc_null_binding(ct, hooknum);
> > -			if (err != NF_ACCEPT)
> > -				goto out;
> > -		}
> > -		break;
> > -
> > -	case IP_CT_ESTABLISHED:
> > -	case IP_CT_ESTABLISHED_REPLY:
> > -		break;
> > -
> > -	default:
> > -		err = NF_DROP;
> > -		goto out;
> > -	}
> > -
> > -	err = nf_nat_packet(ct, ctinfo, hooknum, skb);
> > -out:
> > -	if (err == NF_ACCEPT) {
> > -		if (maniptype == NF_NAT_MANIP_SRC)
> > -			tc_skb_cb(skb)->post_ct_snat = 1;
> > -		if (maniptype == NF_NAT_MANIP_DST)
> > -			tc_skb_cb(skb)->post_ct_dnat = 1;
> > -	}
> > -	return err;
> > -}
> > -#endif /* CONFIG_NF_NAT */
> > -
> >  static void tcf_ct_act_set_mark(struct nf_conn *ct, u32 mark, u32 mask)
> >  {
> >  #if IS_ENABLED(CONFIG_NF_CONNTRACK_MARK)
> > @@ -986,52 +902,22 @@ static int tcf_ct_act_nat(struct sk_buff *skb,
> >  			  bool commit)
> >  {
> >  #if IS_ENABLED(CONFIG_NF_NAT)
> > -	int err;
> > -	enum nf_nat_manip_type maniptype;
> > +	int err, action = 0;
> >  
> >  	if (!(ct_action & TCA_CT_ACT_NAT))
> >  		return NF_ACCEPT;
> > +	if (ct_action & TCA_CT_ACT_NAT_SRC)
> > +		action |= (1 << NF_NAT_MANIP_SRC);
> > +	if (ct_action & TCA_CT_ACT_NAT_DST)
> > +		action |= (1 << NF_NAT_MANIP_DST);
> >  
> > -	/* Add NAT extension if not confirmed yet. */
> > -	if (!nf_ct_is_confirmed(ct) && !nf_ct_nat_ext_add(ct))
> > -		return NF_ACCEPT;   /* Can't NAT. */
> > -
> > -	if (ctinfo != IP_CT_NEW && (ct->status & IPS_NAT_MASK) &&
> > -	    (ctinfo != IP_CT_RELATED || commit)) {
> > -		/* NAT an established or related connection like before. */
> > -		if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY)
> > -			/* This is the REPLY direction for a connection
> > -			 * for which NAT was applied in the forward
> > -			 * direction.  Do the reverse NAT.
> > -			 */
> > -			maniptype = ct->status & IPS_SRC_NAT
> > -				? NF_NAT_MANIP_DST : NF_NAT_MANIP_SRC;
> > -		else
> > -			maniptype = ct->status & IPS_SRC_NAT
> > -				? NF_NAT_MANIP_SRC : NF_NAT_MANIP_DST;
> > -	} else if (ct_action & TCA_CT_ACT_NAT_SRC) {
> > -		maniptype = NF_NAT_MANIP_SRC;
> > -	} else if (ct_action & TCA_CT_ACT_NAT_DST) {
> > -		maniptype = NF_NAT_MANIP_DST;
> > -	} else {
> > -		return NF_ACCEPT;
> > -	}
> > +	err = nf_ct_nat(skb, ct, ctinfo, &action, range, commit);
> > +
> > +	if (action & (1 << NF_NAT_MANIP_SRC))
> > +		tc_skb_cb(skb)->post_ct_snat = 1;
> > +	if (action & (1 << NF_NAT_MANIP_DST))
> > +		tc_skb_cb(skb)->post_ct_dnat = 1;
> >  
> > -	err = ct_nat_execute(skb, ct, ctinfo, range, maniptype);
> > -	if (err == NF_ACCEPT && ct->status & IPS_DST_NAT) {
> > -		if (ct->status & IPS_SRC_NAT) {
> > -			if (maniptype == NF_NAT_MANIP_SRC)
> > -				maniptype = NF_NAT_MANIP_DST;
> > -			else
> > -				maniptype = NF_NAT_MANIP_SRC;
> > -
> > -			err = ct_nat_execute(skb, ct, ctinfo, range,
> > -					     maniptype);
> > -		} else if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) {
> > -			err = ct_nat_execute(skb, ct, ctinfo, NULL,
> > -					     NF_NAT_MANIP_SRC);
> > -		}
> > -	}
> >  	return err;
> >  #else
> >  	return NF_ACCEPT;
> > -- 
> > 2.31.1
> > 
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> 

  reply	other threads:[~2022-11-23 15:13 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-22 17:32 [PATCHv2 net-next 0/5] net: eliminate the duplicate code in the ct nat functions of ovs and tc Xin Long
2022-11-22 17:32 ` [PATCHv2 net-next 1/5] openvswitch: delete the unncessary skb_pull_rcsum call in ovs_ct_nat_execute Xin Long
2022-11-22 17:32 ` [PATCHv2 net-next 2/5] openvswitch: return NF_ACCEPT when OVS_CT_NAT is net set in info nat Xin Long
2022-11-23 14:24   ` Marcelo Ricardo Leitner
2022-11-22 17:32 ` [PATCHv2 net-next 3/5] net: sched: return NF_ACCEPT when fails to add nat ext in tcf_ct_act_nat Xin Long
2022-11-23 14:23   ` Marcelo Ricardo Leitner
2022-12-01 16:53     ` Xin Long
2022-11-22 17:32 ` [PATCHv2 net-next 4/5] net: sched: update the nat flag for icmp error packets in ct_nat_execute Xin Long
2022-11-22 17:32 ` [PATCHv2 net-next 5/5] net: move the nat function to nf_nat_ovs for ovs and tc Xin Long
2022-11-23 15:09   ` Marcelo Ricardo Leitner
2022-11-23 15:13     ` Marcelo Ricardo Leitner [this message]
2022-11-23 17:31       ` [ovs-dev] " Xin Long
2022-11-23 18:48         ` Marcelo Ricardo Leitner
2022-11-23 18:54           ` Xin Long
2022-11-23 19:17             ` Marcelo Ricardo Leitner
2022-11-23 19:55               ` Xin Long
2022-11-23 21:21                 ` Marcelo Ricardo Leitner
2022-11-23 21:34                   ` Xin Long
2022-12-01 21:37                     ` Marcelo Ricardo Leitner
2022-11-24 10:00                   ` Pablo Neira Ayuso
2022-11-23 18:52   ` Marcelo Ricardo Leitner
2022-12-01 16:26     ` Xin Long
2022-11-23 12:39 ` [PATCHv2 net-next 0/5] net: eliminate the duplicate code in the ct nat functions of " Marcelo Ricardo Leitner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221123151335.ssrnv7jfrdugmcgg@t14s.localdomain \
    --to=marcelo.leitner@gmail.com \
    --cc=davem@davemloft.net \
    --cc=dcaratti@redhat.com \
    --cc=dev@openvswitch.org \
    --cc=edumazet@google.com \
    --cc=fw@strlen.de \
    --cc=i.maximets@ovn.org \
    --cc=jhs@mojatatu.com \
    --cc=jiri@resnulli.us \
    --cc=kuba@kernel.org \
    --cc=lucien.xin@gmail.com \
    --cc=netdev@vger.kernel.org \
    --cc=ovs-dev@openvswitch.org \
    --cc=pabeni@redhat.com \
    --cc=pablo@netfilter.org \
    --cc=paulb@nvidia.com \
    --cc=xiyou.wangcong@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.