Skip to contentSkip to conversation

How to find I/F to destination

DISCUSSION David Howells 3 participants 18 messages May 4, 2007 – May 5, 2007

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

Hi,

I would like to determine through which interface packets sent to a particular UDP destination will go through, and so determine the MTU size for that interface. Can anyone suggest a good way of doing this from within the kernel?

Doing this will permit AF_RXRPC to obtain a better initial guess as to the maximum size of the packets that can be sent that way.

I could use the code Patrick gave me to allow AFS to iterate through all the interfaces and then pick the smallest MTU, but that seems wrong somehow - though it probably will result in the correct answer 99% of the time.

David

#1 lore raw
Quoted 2 lines · David Howells

Hi,

Hi David.

Quoted 4 lines

I would like to determine through which interface packets sent to a particular UDP destination will go through, and so determine the MTU size for that interface. Can anyone suggest a good way of doing this from within the kernel?

I used following code in netchannels:

static int netchannel_ip_route_output_flow(struct rtable **rp, struct flowi *flp, int flags)

{
	int err;

	err = __ip_route_output_key(rp, flp);
	if (err)
		return err;

	if (flp->proto) {
		if (!flp->fl4_src)
			flp->fl4_src = (*rp)->rt_src;
		if (!flp->fl4_dst)
			flp->fl4_dst = (*rp)->rt_dst;
	}

	return 0;
}

struct dst_entry *route_get_raw(u32 saddr, u32 daddr, u16 sport, u16 dport, u8 proto)

{
	struct rtable *rt;
	struct flowi fl = { .oif = 0,
			    .nl_u = { .ip4_u =
				      { .saddr = saddr,
					.daddr = daddr,
					.tos = 0 } },
			    .proto = proto,
			    .uli_u = { .ports =
				       { .sport = sport,
					 .dport = dport } } };

	if (netchannel_ip_route_output_flow(&rt, &fl, 0))
		goto no_route;
	return dst_clone(&rt->u.dst);

no_route:

	return NULL;
}

This is basically a copied input route code.
dst entry, obtained from route_get_raw() holds a pointer to network device.

Signature · 1 line
	Evgeniy Polyakov
#2 lore raw
Quoted 1 line · Evgeniy Polyakov
	err = __ip_route_output_key(rp, flp);

Is there any way to get at this without having to link against the ipv4 module?

David

#3 lore raw
Quoted 4 lines · David Howells
Quoted 1 line
	err = __ip_route_output_key(rp, flp);

Is there any way to get at this without having to link against the ipv4 module?

Ah, nevermind. ipv4 can't be a module.

David

#5 lore raw
Quoted 2 lines · David Howells

Should route_get_raw() release the rtable if gets back?

Yes, dst entry should be released when not used anymore.

Quoted 1 line

David

Signature · 1 line
	Evgeniy Polyakov
#6 lore raw
Quoted 3 lines · Evgeniy Polyakov
Quoted 1 line

Should route_get_raw() release the rtable if gets back?

Yes, dst entry should be released when not used anymore.

I meant the rtable returned by __ip_route_output_key(), not the dst.

David

#7 lore raw
Quoted 7 lines · David Howells
Quoted 3 lines · Evgeniy Polyakov
Quoted 1 line

Should route_get_raw() release the rtable if gets back?

Yes, dst entry should be released when not used anymore.

I meant the rtable returned by __ip_route_output_key(), not the dst.

That is the same, dst is dereferenced as rtable.
Cloned dst is returned, so it must be put back at the ned of the usage.

Quoted 1 line

David

Signature · 1 line
	Evgeniy Polyakov
#8 lore raw
Quoted 2 lines · Evgeniy Polyakov

That is the same, dst is dereferenced as rtable.
Cloned dst is returned, so it must be put back at the ned of the usage.

If that's the case, then why do you bother to clone it in route_get_raw()? Surely that'll give you *two* clones...

BTW, it seems to work. The attached function gives me:

	[0mount ] <== rxrpc_assess_MTU_size() [if_mtu 1500]

Thanks!

David

---
static void rxrpc_assess_MTU_size(struct rxrpc_peer *peer)

{
	struct rtable *rt;
	struct flowi fl;
	int ret;

	peer->if_mtu = 1500;

	memset(&fl, 0, sizeof(fl));

	switch (peer->srx.transport.family) {
	case AF_INET:
		fl.oif = 0;
		fl.proto = IPPROTO_UDP,
		fl.nl_u.ip4_u.saddr = 0;
		fl.nl_u.ip4_u.daddr = peer->srx.transport.sin.sin_addr.s_addr;
		fl.nl_u.ip4_u.tos = 0;
		/* assume AFS.CM talking to AFS.FS */
		fl.uli_u.ports.sport = htonl(7001);
		fl.uli_u.ports.dport = htonl(7000);
		break;
	default:
		BUG();
	}

	ret = ip_route_output_key(&rt, &fl);
	if (ret < 0) {
		kleave(" [route err %d]", ret);
		return;
	}

	peer->if_mtu = dst_mtu(&rt->u.dst);
	kleave(" [if_mtu %u]", peer->if_mtu);
}
#9 lore raw
Quoted 12 lines · David Howells

static void rxrpc_assess_MTU_size(struct rxrpc_peer *peer)

{
	struct rtable *rt;
	struct flowi fl;
	int ret;

	peer->if_mtu = 1500;

	memset(&fl, 0, sizeof(fl));

	switch (peer->srx.transport.family) {
	case AF_INET:
Quoted 8 lines
		fl.oif = 0;
		fl.proto = IPPROTO_UDP,
		fl.nl_u.ip4_u.saddr = 0;
		fl.nl_u.ip4_u.daddr = peer->srx.transport.sin.sin_addr.s_addr;
		fl.nl_u.ip4_u.tos = 0;
		/* assume AFS.CM talking to AFS.FS */
		fl.uli_u.ports.sport = htonl(7001);
		fl.uli_u.ports.dport = htonl(7000);

htons()

Quoted 5 lines
		break;
	default:
		BUG();
	}
Quoted 7 lines
	ret = ip_route_output_key(&rt, &fl);
	if (ret < 0) {
		kleave(" [route err %d]", ret);
		return;
	}

	peer->if_mtu = dst_mtu(&rt->u.dst);

You need dst_release(&rt->u.dst) here.

#10 lore raw
Quoted 1 line · Patrick McHardy

htons()

Blech. Thanks. Does it actually matter what ports are specified?

Quoted 1 line

You need dst_release(&rt->u.dst) here.

Thanks. I think Evgeniy's code may be wrong then. He ends with a dst_clone(), which I think is superfluous.

David

#11 lore raw
Quoted 7 lines · David Howells
Quoted 1 line · Patrick McHardy

htons()

Blech. Thanks. Does it actually matter what ports are specified?

It matters when IPsec port selectors are used to find the correct policy.

Quoted 5 lines
Quoted 1 line

You need dst_release(&rt->u.dst) here.

Thanks. I think Evgeniy's code may be wrong then. He ends with a dst_clone(), which I think is superfluous.

Yes, that looks wrong.

#12 lore raw
Quoted 8 lines · Patrick McHardy
Quoted 5 lines
Quoted 1 line

You need dst_release(&rt->u.dst) here.

Thanks. I think Evgeniy's code may be wrong then. He ends with a dst_clone(), which I think is superfluous.

Yes, that looks wrong.

Main idea is to get a reference, and then clone it for each user. Then each user drops its reference, and when system is not used anymore, main reference is dropped too.

Signature · 1 line
	Evgeniy Polyakov
#13 lore raw
In reply to David Howells
Quoted 35 lines · David Howells
Quoted 2 lines · Evgeniy Polyakov

That is the same, dst is dereferenced as rtable.
Cloned dst is returned, so it must be put back at the ned of the usage.

If that's the case, then why do you bother to clone it in route_get_raw()? Surely that'll give you *two* clones...

BTW, it seems to work. The attached function gives me:

	[0mount ] <== rxrpc_assess_MTU_size() [if_mtu 1500]

Thanks!

David

---
static void rxrpc_assess_MTU_size(struct rxrpc_peer *peer)

{
	struct rtable *rt;
	struct flowi fl;
	int ret;

	peer->if_mtu = 1500;

	memset(&fl, 0, sizeof(fl));

	switch (peer->srx.transport.family) {
	case AF_INET:
		fl.oif = 0;
		fl.proto = IPPROTO_UDP,
		fl.nl_u.ip4_u.saddr = 0;
		fl.nl_u.ip4_u.daddr = peer->srx.transport.sin.sin_addr.s_addr;
		fl.nl_u.ip4_u.tos = 0;
		/* assume AFS.CM talking to AFS.FS */
Quoted 8 lines
		fl.uli_u.ports.sport = htonl(7001);
		fl.uli_u.ports.dport = htonl(7000);
		break;
	default:
		BUG();
	}

	ret = ip_route_output_key(&rt, &fl);

This one is quite slow compared to atomic reference increase, so yes, there are two clones - one for main 'route', which then is being cloned for each used in fast path. When it is not needed anymore (netchannel is removed), the first one is dropped.

Quoted 8 lines
	if (ret < 0) {
		kleave(" [route err %d]", ret);
		return;
	}

	peer->if_mtu = dst_mtu(&rt->u.dst);
	kleave(" [if_mtu %u]", peer->if_mtu);
}
Signature · 1 line
	Evgeniy Polyakov
#14 lore raw
In reply to David Howells
Quoted 2 lines · David Howells

Should route_get_raw() release the rtable if gets back?

You can also cache returned entry and then just clone it and check ->obsolete() callback.

Something like this:
struct dst_entry *route_get(struct dst_entry *dst)

{
	if (dst && dst->obsolete && dst->ops->check(dst, 0) == NULL) {
		dst_release(dst);
		return NULL;
	}
	return dst_clone(dst);
}

Copied from route code too.

Quoted 1 line

David

Signature · 1 line
	Evgeniy Polyakov
#15 lore raw
In reply to Evgeniy Polyakov
Quoted 1 line · Evgeniy Polyakov

static int netchannel_ip_route_output_flow(struct rtable **rp, struct flowi *flp, int flags)

What's proto? Should that be IPPROTO_UDP?

David

#16 lore raw
Quoted 5 lines · David Howells
Quoted 1 line · Evgeniy Polyakov

static int netchannel_ip_route_output_flow(struct rtable **rp, struct flowi *flp, int flags)

What's proto? Should that be IPPROTO_UDP?

Yep.

Quoted 1 line

David

Signature · 1 line
	Evgeniy Polyakov
#17 lore raw
In reply to David Howells
Quoted 4 lines · David Howells

I would like to determine through which interface packets sent to a particular UDP destination will go through, and so determine the MTU size for that interface. Can anyone suggest a good way of doing this from within the kernel?

Do a route lookup (ip_route_output_key), then either use dst_mtu to get the PMTU value or dst->dev in case you really want the device's MTU.