From the RFC2516 it is not clear, what is the correct behavior for a

PPPoE AC, servicing a specific Service-Name, when client sends a PADI
with an empty Service-Name. Should it reply with all available service
names or should it be silent? Our implementation had chosen the latter,
while some other had chosen the former (they say Linux and Cisco). Now
some PPPoE clients appear, that rely on the assumption that AC will
send all names in a PADO reply to a PADI with wildcard Service-Name.
These clients can't connect to FreeBSD AC.

I have requested comments from authors of RFC2516 via email, but
received no reply.

This change makes FreeBSD AC compatible with D-Link DI-614+ and
D-Link DI-624+ SOHO routers, and probably others.

Big thanks to D-Link's Russian office, namely Victor Platov, for
assistance and support in investigation and testing of this change.

Details:
  o Split pppoe_match_svc() into three different functions serving
    different purposes:
    - pppoe_match_svc() - match non-empty Service-Name tag from PADI
      against all available hooks in listening state.
    - pppoe_find_svc() - check that given Service-Name is not yet
      registered.
    - pppoe_broadcast_padi() - send a copy of PADI packet with empty
      Service-Name tag to all listening hooks.
  o For NGM_PPPOE_LISTEN message use pppoe_find_svc().
  o In ng_pppoe_rcvdata() in a PADI case use pppoe_match_svc() for
    a non-empty Service-Name tag, and pppoe_broadcast_padi() in
    either case.

A side effect from the above changes is that now pppoed(8) and mpd
will reply to a empty Service-Name PADI sending a PADO with two
Service-Name tags - an empty one and correct one. This is not fatal,
and will be corrected in pppoed(8) and mpd later. No need to update
node interface version.

Supported by:	D-Link
This commit is contained in:
Gleb Smirnoff 2006-01-26 13:06:49 +00:00
parent 4ecdfdcf91
commit 68b789b23f
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=154862

View File

@ -444,63 +444,129 @@ make_packet(sessp sp) {
}
/**************************************************************************
* Routine to match a service offered *
* Routines to match a service. *
**************************************************************************/
/*
* Find a hook that has a service string that matches that
* we are seeking. for now use a simple string.
* we are seeking. For now use a simple string.
* In the future we may need something like regexp().
* for testing allow a null string to match 1st found and a null service
* to match all requests. Also make '*' do the same.
*
* Null string is a wildcard (ANY service), according to RFC2516.
* And historical FreeBSD wildcard is also "*".
*/
#define NG_MATCH_EXACT 1
#define NG_MATCH_ANY 2
static hook_p
pppoe_match_svc(node_p node, const char *svc_name, int svc_len, int match)
pppoe_match_svc(node_p node, const struct pppoe_tag *tag)
{
sessp sp = NULL;
negp neg = NULL;
priv_p privp = NG_NODE_PRIVATE(node);
hook_p allhook = NULL;
hook_p hook;
priv_p privp = NG_NODE_PRIVATE(node);
hook_p hook;
LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
sessp sp = NG_HOOK_PRIVATE(hook);
negp neg;
/* skip any hook that is debug or ethernet */
if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook)
|| (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook))
/* Skip any hook that is debug or ethernet. */
if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) ||
(NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook))
continue;
sp = NG_HOOK_PRIVATE(hook);
/* Skip any sessions which are not in LISTEN mode. */
if ( sp->state != PPPOE_LISTENING)
if (sp->state != PPPOE_LISTENING)
continue;
neg = sp->neg;
/* Special case for a blank or "*" service name (wildcard) */
if (match == NG_MATCH_ANY && neg->service_len == 1 &&
neg->service.data[0] == '*') {
allhook = hook;
continue;
}
/* Empty Service-Name matches any service. */
if (neg->service_len == 0)
break;
/* Special case for a blank or "*" service name (wildcard). */
if (neg->service_len == 1 && neg->service.data[0] == '*')
break;
/* If the lengths don't match, that aint it. */
if (neg->service_len != svc_len)
if (neg->service_len != ntohs(tag->tag_len))
continue;
/* An exact match? */
if (svc_len == 0)
break;
if (strncmp(svc_name, neg->service.data, svc_len) == 0)
if (strncmp(tag->tag_data, neg->service.data,
ntohs(tag->tag_len)) == 0)
break;
}
CTR3(KTR_NET, "%20s: matched %p for %s", __func__, hook, svc_name);
CTR3(KTR_NET, "%20s: matched %p for %s", __func__, hook, tag->tag_data);
return (hook ? hook : allhook);
return (hook);
}
/*
* Broadcast the PADI packet in m0 to all listening hooks.
* This routine is called when a PADI with empty Service-Name
* tag is received. Client should receive PADOs with all
* available services.
*/
static int
pppoe_broadcast_padi(node_p node, struct mbuf *m0)
{
priv_p privp = NG_NODE_PRIVATE(node);
hook_p hook;
int error = 0;
LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
sessp sp = NG_HOOK_PRIVATE(hook);
struct mbuf *m;
/*
* Go through all listening hooks and
* broadcast the PADI packet up there
*/
if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) ||
(NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook))
continue;
if (sp->state != PPPOE_LISTENING)
continue;
m = m_dup(m0, M_DONTWAIT);
if (m == NULL)
return (ENOMEM);
NG_SEND_DATA_ONLY(error, hook, m);
if (error)
return (error);
}
return (0);
}
/*
* Find a hook, which name equals to given service.
*/
static hook_p
pppoe_find_svc(node_p node, const char *svc_name, int svc_len)
{
priv_p privp = NG_NODE_PRIVATE(node);
hook_p hook;
LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
sessp sp = NG_HOOK_PRIVATE(hook);
negp neg;
/* Skip any hook that is debug or ethernet. */
if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) ||
(NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook))
continue;
/* Skip any sessions which are not in LISTEN mode. */
if (sp->state != PPPOE_LISTENING)
continue;
neg = sp->neg;
if (neg->service_len == svc_len &&
strncmp(svc_name, neg->service.data, svc_len == 0))
return (hook);
}
return (NULL);
}
/**************************************************************************
@ -703,8 +769,8 @@ ng_pppoe_rcvmsg(node_p node, item_p item, hook_p lasthook)
* Ensure we aren't already listening for this
* service.
*/
if (pppoe_match_svc(node, ourmsg->data,
ourmsg->data_len, NG_MATCH_EXACT) != NULL)
if (pppoe_find_svc(node, ourmsg->data,
ourmsg->data_len) != NULL)
LEAVE(EEXIST);
}
@ -1085,18 +1151,26 @@ ng_pppoe_rcvdata(hook_p hook, item_p item)
*/
tag = get_tag(ph, PTT_SRV_NAME);
if (tag == NULL) {
printf("no service tag\n");
LEAVE(ENETUNREACH);
}
sendhook = pppoe_match_svc(NG_HOOK_NODE(hook),
tag->tag_data, ntohs(tag->tag_len),
NG_MATCH_ANY);
if (sendhook) {
NG_FWD_NEW_DATA(error, item,
sendhook, m);
} else {
CTR1(KTR_NET,
"%20s: PADI w/o Service-Name",
__func__);
LEAVE(ENETUNREACH);
}
/*
* If Service-Name is NULL, we broadcast
* the PADI to all listening hooks, otherwise
* we seek for a matching one.
*/
if (ntohs(tag->tag_len) != 0) {
sendhook = pppoe_match_svc(node, tag);
if (sendhook != NULL)
NG_FWD_NEW_DATA(error, item,
sendhook, m);
else
error = ENETUNREACH;
} else
error = pppoe_broadcast_padi(node, m);
break;
case PADO_CODE:
/*