2 * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * $FreeBSD: src/usr.sbin/ppp/ether.c,v 1.9.2.14 2002/09/01 02:12:26 brian Exp $
27 * $DragonFly: src/usr.sbin/ppp/ether.c,v 1.3 2003/08/08 04:18:47 dillon Exp $
30 #include <sys/param.h>
31 #include <sys/socket.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
37 #include <net/ethernet.h>
39 #include <net/route.h>
40 #include <netinet/in_systm.h>
41 #include <netinet/ip.h>
42 #include <netgraph/ether/ng_ether.h>
43 #include <netgraph/ng_message.h>
44 #include <netgraph/pppoe/ng_pppoe.h>
45 #include <netgraph/socket/ng_socket.h>
52 #include <sys/fcntl.h>
67 #include "throughput.h"
73 #include "descriptor.h"
82 #include "slcompress.h"
100 #define PPPOE_NODE_TYPE_LEN (sizeof NG_PPPOE_NODE_TYPE - 1) /* "PPPoE" */
103 struct device dev
; /* What struct physical knows about */
104 int cs
; /* Control socket */
105 int connected
; /* Are we connected yet ? */
106 int timeout
; /* Seconds attempting to connect */
107 char hook
[sizeof TUN_NAME
+ 11]; /* Our socket node hook */
108 u_int32_t slot
; /* ifindex << 24 | unit */
111 #define device2ether(d) \
112 ((d)->type == ETHER_DEVICE ? (struct etherdevice *)d : NULL)
115 ether_DeviceSize(void)
117 return sizeof(struct etherdevice
);
121 ether_Write(struct physical
*p
, const void *v
, size_t n
)
123 struct etherdevice
*dev
= device2ether(p
->handler
);
125 return NgSendData(p
->fd
, dev
->hook
, v
, n
) == -1 ? -1 : n
;
129 ether_Read(struct physical
*p
, void *v
, size_t n
)
131 char hook
[sizeof TUN_NAME
+ 11];
133 return NgRecvData(p
->fd
, v
, n
, hook
);
137 ether_RemoveFromSet(struct physical
*p
, fd_set
*r
, fd_set
*w
, fd_set
*e
)
139 struct etherdevice
*dev
= device2ether(p
->handler
);
142 if (r
&& dev
->cs
>= 0 && FD_ISSET(dev
->cs
, r
)) {
144 log_Printf(LogTIMER
, "%s: fdunset(ctrl) %d\n", p
->link
.name
, dev
->cs
);
149 /* Careful... physical_RemoveFromSet() called us ! */
151 p
->handler
->removefromset
= NULL
;
152 result
+= physical_RemoveFromSet(p
, r
, w
, e
);
153 p
->handler
->removefromset
= ether_RemoveFromSet
;
159 ether_Free(struct physical
*p
)
161 struct etherdevice
*dev
= device2ether(p
->handler
);
163 physical_SetDescriptor(p
);
170 ether_OpenInfo(struct physical
*p
)
172 struct etherdevice
*dev
= device2ether(p
->handler
);
174 switch (dev
->connected
) {
175 case CARRIER_PENDING
:
176 return "negotiating";
178 return "established";
181 return "disconnected";
185 ether_Slot(struct physical
*p
)
187 struct etherdevice
*dev
= device2ether(p
->handler
);
194 ether_device2iov(struct device
*d
, struct iovec
*iov
, int *niov
,
195 int maxiov
, int *auxfd
, int *nauxfd
)
197 struct etherdevice
*dev
= device2ether(d
);
198 int sz
= physical_MaxDeviceSize();
200 iov
[*niov
].iov_base
= realloc(d
, sz
);
201 if (iov
[*niov
].iov_base
== NULL
) {
202 log_Printf(LogALERT
, "Failed to allocate memory: %d\n", sz
);
203 AbortProgram(EX_OSERR
);
205 iov
[*niov
].iov_len
= sz
;
215 ether_MessageIn(struct etherdevice
*dev
)
217 char msgbuf
[sizeof(struct ng_mesg
) + sizeof(struct ngpppoe_sts
)];
218 struct ng_mesg
*rep
= (struct ng_mesg
*)msgbuf
;
219 struct ngpppoe_sts
*sts
= (struct ngpppoe_sts
*)(msgbuf
+ sizeof *rep
);
220 char *end
, unknown
[14], sessionid
[5];
230 if ((r
= mkfdset()) == NULL
) {
231 log_Printf(LogERROR
, "DoLoop: Cannot create fd_set\n");
238 t
.tv_sec
= t
.tv_usec
= 0;
239 ret
= select(dev
->cs
+ 1, r
, NULL
, NULL
, &t
);
244 if (NgRecvMsg(dev
->cs
, rep
, sizeof msgbuf
, NULL
) <= 0)
247 if (rep
->header
.version
!= NG_VERSION
) {
248 log_Printf(LogWARN
, "%ld: Unexpected netgraph version, expected %ld\n",
249 (long)rep
->header
.version
, (long)NG_VERSION
);
253 if (rep
->header
.typecookie
!= NGM_PPPOE_COOKIE
) {
254 log_Printf(LogWARN
, "%ld: Unexpected netgraph cookie, expected %ld\n",
255 (long)rep
->header
.typecookie
, (long)NGM_PPPOE_COOKIE
);
260 switch (rep
->header
.cmd
) {
261 case NGM_PPPOE_SET_FLAG
: msg
= "SET_FLAG"; break;
262 case NGM_PPPOE_CONNECT
: msg
= "CONNECT"; break;
263 case NGM_PPPOE_LISTEN
: msg
= "LISTEN"; break;
264 case NGM_PPPOE_OFFER
: msg
= "OFFER"; break;
265 case NGM_PPPOE_SUCCESS
: msg
= "SUCCESS"; break;
266 case NGM_PPPOE_FAIL
: msg
= "FAIL"; break;
267 case NGM_PPPOE_CLOSE
: msg
= "CLOSE"; break;
268 case NGM_PPPOE_GET_STATUS
: msg
= "GET_STATUS"; break;
269 case NGM_PPPOE_ACNAME
:
271 if (setenv("ACNAME", sts
->hook
, 1) != 0)
272 log_Printf(LogWARN
, "setenv: cannot set ACNAME=%s: %m", sts
->hook
);
273 asciilen
= rep
->header
.arglen
;
275 case NGM_PPPOE_SESSIONID
:
277 snprintf(sessionid
, sizeof sessionid
, "%04x", *(u_int16_t
*)sts
);
278 if (setenv("SESSIONID", sessionid
, 1) != 0)
279 syslog(LOG_WARNING
, "setenv: cannot set SESSIONID=%s: %m",
281 /* Use this in preference to our interface index */
282 slot
= strtoul(sessionid
, &end
, 16);
283 if (end
!= sessionid
&& *end
== '\0')
287 snprintf(unknown
, sizeof unknown
, "<%d>", (int)rep
->header
.cmd
);
293 log_Printf(LogPHASE
, "Received NGM_PPPOE_%s (hook \"%.*s\")\n",
294 msg
, asciilen
, sts
->hook
);
296 log_Printf(LogPHASE
, "Received NGM_PPPOE_%s\n", msg
);
298 switch (rep
->header
.cmd
) {
299 case NGM_PPPOE_SUCCESS
:
300 dev
->connected
= CARRIER_OK
;
303 case NGM_PPPOE_CLOSE
:
304 dev
->connected
= CARRIER_LOST
;
312 ether_AwaitCarrier(struct physical
*p
)
314 struct etherdevice
*dev
= device2ether(p
->handler
);
316 if (dev
->connected
!= CARRIER_OK
&& !dev
->timeout
--)
317 dev
->connected
= CARRIER_LOST
;
318 else if (dev
->connected
== CARRIER_PENDING
)
319 ether_MessageIn(dev
);
321 return dev
->connected
;
324 static const struct device baseetherdevice
= {
328 { CD_REQUIRED
, DEF_ETHERCDDELAY
},
346 ether_iov2device(int type
, struct physical
*p
, struct iovec
*iov
, int *niov
,
347 int maxiov
, int *auxfd
, int *nauxfd
)
349 if (type
== ETHER_DEVICE
) {
350 struct etherdevice
*dev
= (struct etherdevice
*)iov
[(*niov
)++].iov_base
;
352 dev
= realloc(dev
, sizeof *dev
); /* Reduce to the correct size */
354 log_Printf(LogALERT
, "Failed to allocate memory: %d\n",
356 AbortProgram(EX_OSERR
);
365 /* Refresh function pointers etc */
366 memcpy(&dev
->dev
, &baseetherdevice
, sizeof dev
->dev
);
368 physical_SetupStack(p
, dev
->dev
.name
, PHYSICAL_FORCE_SYNCNOACF
);
376 ether_UpdateSet(struct fdescriptor
*d
, fd_set
*r
, fd_set
*w
, fd_set
*e
, int *n
)
378 struct physical
*p
= descriptor2physical(d
);
379 struct etherdevice
*dev
= device2ether(p
->handler
);
382 if (r
&& dev
->cs
>= 0) {
384 log_Printf(LogTIMER
, "%s(ctrl): fdset(r) %d\n", p
->link
.name
, dev
->cs
);
389 result
+= physical_doUpdateSet(d
, r
, w
, e
, n
, 0);
395 ether_IsSet(struct fdescriptor
*d
, const fd_set
*fdset
)
397 struct physical
*p
= descriptor2physical(d
);
398 struct etherdevice
*dev
= device2ether(p
->handler
);
401 result
= dev
->cs
>= 0 && FD_ISSET(dev
->cs
, fdset
);
402 result
+= physical_IsSet(d
, fdset
);
408 ether_DescriptorRead(struct fdescriptor
*d
, struct bundle
*bundle
,
411 struct physical
*p
= descriptor2physical(d
);
412 struct etherdevice
*dev
= device2ether(p
->handler
);
414 if (dev
->cs
>= 0 && FD_ISSET(dev
->cs
, fdset
)) {
415 ether_MessageIn(dev
);
416 if (dev
->connected
== CARRIER_LOST
) {
417 log_Printf(LogPHASE
, "%s: Device disconnected\n", p
->link
.name
);
418 datalink_Down(p
->dl
, CLOSE_NORMAL
);
423 if (physical_IsSet(d
, fdset
))
424 physical_DescriptorRead(d
, bundle
, fdset
);
427 static struct device
*
428 ether_Abandon(struct etherdevice
*dev
, struct physical
*p
)
430 /* Abandon our node construction */
433 p
->fd
= -2; /* Nobody else need try.. */
440 ether_Create(struct physical
*p
)
443 struct etherdevice
*dev
;
444 struct ng_mesg
*resp
;
445 const struct hooklist
*hlist
;
446 const struct nodeinfo
*ninfo
;
447 char *path
, *sessionid
;
453 if (p
->fd
< 0 && !strncasecmp(p
->name
.full
, NG_PPPOE_NODE_TYPE
,
454 PPPOE_NODE_TYPE_LEN
) &&
455 p
->name
.full
[PPPOE_NODE_TYPE_LEN
] == ':') {
456 const struct linkinfo
*nlink
;
457 struct ngpppoe_init_data
*data
;
458 struct ngm_mkpeer mkp
;
459 struct ngm_connect ngc
;
460 const char *iface
, *provider
;
463 char connectpath
[sizeof dev
->hook
+ 2]; /* .:<hook> */
465 p
->fd
--; /* We own the device - change fd */
467 loadmodules(LOAD_VERBOSLY
, "netgraph", "ng_ether", "ng_pppoe", "ng_socket",
470 if ((dev
= malloc(sizeof *dev
)) == NULL
)
473 iface
= p
->name
.full
+ PPPOE_NODE_TYPE_LEN
+ 1;
475 provider
= strchr(iface
, ':');
477 ifacelen
= provider
- iface
;
479 providerlen
= strlen(provider
);
481 ifacelen
= strlen(iface
);
487 * We're going to do this (where tunN is our tunnel device):
491 * | <iface> | dev->cs
497 * .---------. .-----------.
498 * | pppoe | | socket |
499 * | <iface> |(tunN)<---->(tunN)| <unnamed> |
500 * `--------- `-----------'
507 /* Create a socket node */
508 if (ID0NgMkSockNode(NULL
, &dev
->cs
, &p
->fd
) == -1) {
509 log_Printf(LogWARN
, "Cannot create netgraph socket node: %s\n",
517 * Ask for a list of hooks attached to the "ether" node. This node should
518 * magically exist as a way of hooking stuff onto an ethernet device
520 path
= (char *)alloca(ifacelen
+ 2);
521 sprintf(path
, "%.*s:", ifacelen
, iface
);
522 if (NgSendMsg(dev
->cs
, path
, NGM_GENERIC_COOKIE
, NGM_LISTHOOKS
,
524 log_Printf(LogWARN
, "%s Cannot send a netgraph message: %s\n",
525 path
, strerror(errno
));
526 return ether_Abandon(dev
, p
);
529 /* Get our list back */
530 resp
= (struct ng_mesg
*)rbuf
;
531 if (NgRecvMsg(dev
->cs
, resp
, sizeof rbuf
, NULL
) <= 0) {
532 log_Printf(LogWARN
, "Cannot get netgraph response: %s\n",
534 return ether_Abandon(dev
, p
);
537 hlist
= (const struct hooklist
*)resp
->data
;
538 ninfo
= &hlist
->nodeinfo
;
540 /* Make sure we've got the right type of node */
541 if (strncmp(ninfo
->type
, NG_ETHER_NODE_TYPE
,
542 sizeof NG_ETHER_NODE_TYPE
- 1)) {
543 log_Printf(LogWARN
, "%s Unexpected node type ``%s'' (wanted ``"
544 NG_ETHER_NODE_TYPE
"'')\n", path
, ninfo
->type
);
545 return ether_Abandon(dev
, p
);
548 log_Printf(LogDEBUG
, "List of netgraph node ``%s'' (id %x) hooks:\n",
551 /* look for a hook already attached. */
552 for (f
= 0; f
< ninfo
->hooks
; f
++) {
553 nlink
= &hlist
->link
[f
];
555 log_Printf(LogDEBUG
, " Found %s -> %s\n", nlink
->ourhook
,
558 if (!strcmp(nlink
->ourhook
, NG_ETHER_HOOK_ORPHAN
) ||
559 !strcmp(nlink
->ourhook
, NG_ETHER_HOOK_DIVERT
)) {
561 * Something is using the data coming out of this ``ether'' node.
562 * If it's a PPPoE node, we use that node, otherwise we complain that
563 * someone else is using the node.
565 if (!strcmp(nlink
->nodeinfo
.type
, NG_PPPOE_NODE_TYPE
))
566 /* Use this PPPoE node ! */
567 snprintf(ngc
.path
, sizeof ngc
.path
, "[%x]:", nlink
->nodeinfo
.id
);
569 log_Printf(LogWARN
, "%s Node type ``%s'' is currently active\n",
570 path
, nlink
->nodeinfo
.type
);
571 return ether_Abandon(dev
, p
);
577 if (f
== ninfo
->hooks
) {
579 * Create a new ``PPPoE'' node connected to the ``ether'' node using
580 * the ``orphan'' and ``ethernet'' hooks
582 snprintf(mkp
.type
, sizeof mkp
.type
, "%s", NG_PPPOE_NODE_TYPE
);
583 snprintf(mkp
.ourhook
, sizeof mkp
.ourhook
, "%s", NG_ETHER_HOOK_ORPHAN
);
584 snprintf(mkp
.peerhook
, sizeof mkp
.peerhook
, "%s", NG_PPPOE_HOOK_ETHERNET
);
585 snprintf(etherid
, sizeof etherid
, "[%x]:", ninfo
->id
);
587 log_Printf(LogDEBUG
, "Creating PPPoE netgraph node %s%s -> %s\n",
588 etherid
, mkp
.ourhook
, mkp
.peerhook
);
590 if (NgSendMsg(dev
->cs
, etherid
, NGM_GENERIC_COOKIE
,
591 NGM_MKPEER
, &mkp
, sizeof mkp
) < 0) {
592 log_Printf(LogWARN
, "%s Cannot create PPPoE netgraph node: %s\n",
593 etherid
, strerror(errno
));
594 return ether_Abandon(dev
, p
);
597 snprintf(ngc
.path
, sizeof ngc
.path
, "%s%s", path
, NG_ETHER_HOOK_ORPHAN
);
600 snprintf(dev
->hook
, sizeof dev
->hook
, "%s%d",
601 TUN_NAME
, p
->dl
->bundle
->unit
);
604 * Connect the PPPoE node to our socket node.
605 * ngc.path has already been set up
607 snprintf(ngc
.ourhook
, sizeof ngc
.ourhook
, "%s", dev
->hook
);
608 memcpy(ngc
.peerhook
, ngc
.ourhook
, sizeof ngc
.peerhook
);
610 log_Printf(LogDEBUG
, "Connecting netgraph socket .:%s -> %s:%s\n",
611 ngc
.ourhook
, ngc
.path
, ngc
.peerhook
);
612 if (NgSendMsg(dev
->cs
, ".:", NGM_GENERIC_COOKIE
,
613 NGM_CONNECT
, &ngc
, sizeof ngc
) < 0) {
614 log_Printf(LogWARN
, "Cannot connect PPPoE and socket netgraph "
615 "nodes: %s\n", strerror(errno
));
616 return ether_Abandon(dev
, p
);
619 /* Bring the Ethernet interface up */
620 path
[ifacelen
] = '\0'; /* Remove the trailing ':' */
621 if (!iface_SetFlags(path
, IFF_UP
))
622 log_Printf(LogWARN
, "%s: Failed to set the IFF_UP flag on %s\n",
625 /* And finally, request a connection to the given provider */
627 data
= (struct ngpppoe_init_data
*)alloca(sizeof *data
+ providerlen
);
628 snprintf(data
->hook
, sizeof data
->hook
, "%s", dev
->hook
);
629 memcpy(data
->data
, provider
, providerlen
);
630 data
->data_len
= providerlen
;
632 snprintf(connectpath
, sizeof connectpath
, ".:%s", dev
->hook
);
633 log_Printf(LogDEBUG
, "Sending PPPOE_CONNECT to %s\n", connectpath
);
634 if (NgSendMsg(dev
->cs
, connectpath
, NGM_PPPOE_COOKIE
,
635 NGM_PPPOE_CONNECT
, data
, sizeof *data
+ providerlen
) == -1) {
636 log_Printf(LogWARN
, "``%s'': Cannot start netgraph node: %s\n",
637 connectpath
, strerror(errno
));
638 return ether_Abandon(dev
, p
);
641 /* Hook things up so that we monitor dev->cs */
642 p
->desc
.UpdateSet
= ether_UpdateSet
;
643 p
->desc
.IsSet
= ether_IsSet
;
644 p
->desc
.Read
= ether_DescriptorRead
;
646 memcpy(&dev
->dev
, &baseetherdevice
, sizeof dev
->dev
);
647 switch (p
->cfg
.cd
.necessity
) {
649 dev
->dev
.cd
.delay
= p
->cfg
.cd
.delay
;
652 dev
->dev
.cd
= p
->cfg
.cd
;
655 log_Printf(LogWARN
, "%s: Carrier must be set, using ``set cd %d!''\n",
656 p
->link
.name
, dev
->dev
.cd
.delay
);
661 dev
->timeout
= dev
->dev
.cd
.delay
;
662 dev
->connected
= CARRIER_PENDING
;
663 /* This will be overridden by our session id - if provided by netgraph */
664 dev
->slot
= GetIfIndex(path
);
666 /* See if we're a netgraph socket */
669 if (fstat(p
->fd
, &st
) != -1 && (st
.st_mode
& S_IFSOCK
)) {
670 struct sockaddr_storage ssock
;
671 struct sockaddr
*sock
= (struct sockaddr
*)&ssock
;
675 if (getsockname(p
->fd
, sock
, &sz
) == -1) {
676 log_Printf(LogPHASE
, "%s: Link is a closed socket !\n", p
->link
.name
);
682 if (sock
->sa_family
== AF_NETGRAPH
) {
684 * It's a netgraph node... We can't determine hook names etc, so we
685 * stay pretty impartial....
687 log_Printf(LogPHASE
, "%s: Link is a netgraph node\n", p
->link
.name
);
689 if ((dev
= malloc(sizeof *dev
)) == NULL
) {
690 log_Printf(LogWARN
, "%s: Cannot allocate an ether device: %s\n",
691 p
->link
.name
, strerror(errno
));
695 memcpy(&dev
->dev
, &baseetherdevice
, sizeof dev
->dev
);
698 dev
->connected
= CARRIER_OK
;
702 * If we're being envoked from pppoed(8), we may have a SESSIONID
703 * set in the environment. If so, use it as the slot
705 if ((sessionid
= getenv("SESSIONID")) != NULL
) {
709 slot
= strtoul(sessionid
, &end
, 16);
710 dev
->slot
= end
!= sessionid
&& *end
== '\0' ? slot
: 0;
718 physical_SetupStack(p
, dev
->dev
.name
, PHYSICAL_FORCE_SYNCNOACF
);