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>
43 #include <netgraph7/ether/ng_ether.h>
44 #include <netgraph7/ng_message.h>
45 #include <netgraph7/pppoe/ng_pppoe.h>
46 #include <netgraph7/socket/ng_socket.h>
48 #include <netgraph/ether/ng_ether.h>
49 #include <netgraph/ng_message.h>
50 #include <netgraph/pppoe/ng_pppoe.h>
51 #include <netgraph/socket/ng_socket.h>
59 #include <sys/fcntl.h>
74 #include "throughput.h"
80 #include "descriptor.h"
89 #include "slcompress.h"
107 #define PPPOE_NODE_TYPE_LEN (sizeof NG_PPPOE_NODE_TYPE - 1) /* "PPPoE" */
110 struct device dev
; /* What struct physical knows about */
111 int cs
; /* Control socket */
112 int connected
; /* Are we connected yet ? */
113 int timeout
; /* Seconds attempting to connect */
114 char hook
[sizeof TUN_NAME
+ 11]; /* Our socket node hook */
115 u_int32_t slot
; /* ifindex << 24 | unit */
118 #define device2ether(d) \
119 ((d)->type == ETHER_DEVICE ? (struct etherdevice *)d : NULL)
122 ether_DeviceSize(void)
124 return sizeof(struct etherdevice
);
128 ether_Write(struct physical
*p
, const void *v
, size_t n
)
130 struct etherdevice
*dev
= device2ether(p
->handler
);
132 return NgSendData(p
->fd
, dev
->hook
, v
, n
) == -1 ? -1 : (ssize_t
)n
;
136 ether_Read(struct physical
*p
, void *v
, size_t n
)
138 char hook
[sizeof TUN_NAME
+ 11];
140 return NgRecvData(p
->fd
, v
, n
, hook
);
144 ether_RemoveFromSet(struct physical
*p
, fd_set
*r
, fd_set
*w
, fd_set
*e
)
146 struct etherdevice
*dev
= device2ether(p
->handler
);
149 if (r
&& dev
->cs
>= 0 && FD_ISSET(dev
->cs
, r
)) {
151 log_Printf(LogTIMER
, "%s: fdunset(ctrl) %d\n", p
->link
.name
, dev
->cs
);
156 /* Careful... physical_RemoveFromSet() called us ! */
158 p
->handler
->removefromset
= NULL
;
159 result
+= physical_RemoveFromSet(p
, r
, w
, e
);
160 p
->handler
->removefromset
= ether_RemoveFromSet
;
166 ether_Free(struct physical
*p
)
168 struct etherdevice
*dev
= device2ether(p
->handler
);
170 physical_SetDescriptor(p
);
177 ether_OpenInfo(struct physical
*p
)
179 struct etherdevice
*dev
= device2ether(p
->handler
);
181 switch (dev
->connected
) {
182 case CARRIER_PENDING
:
183 return "negotiating";
185 return "established";
188 return "disconnected";
192 ether_Slot(struct physical
*p
)
194 struct etherdevice
*dev
= device2ether(p
->handler
);
201 ether_device2iov(struct device
*d
, struct iovec
*iov
, int *niov
,
202 int maxiov __unused
, int *auxfd
, int *nauxfd
)
204 struct etherdevice
*dev
= device2ether(d
);
205 int sz
= physical_MaxDeviceSize();
207 iov
[*niov
].iov_base
= realloc(d
, sz
);
208 if (iov
[*niov
].iov_base
== NULL
) {
209 log_Printf(LogALERT
, "Failed to allocate memory: %d\n", sz
);
210 AbortProgram(EX_OSERR
);
212 iov
[*niov
].iov_len
= sz
;
222 ether_MessageIn(struct etherdevice
*dev
)
224 char msgbuf
[sizeof(struct ng_mesg
) + sizeof(struct ngpppoe_sts
)];
225 struct ng_mesg
*rep
= (struct ng_mesg
*)msgbuf
;
226 struct ngpppoe_sts
*sts
= (struct ngpppoe_sts
*)(msgbuf
+ sizeof *rep
);
227 char *end
, unknown
[14], sessionid
[5];
237 if ((r
= mkfdset()) == NULL
) {
238 log_Printf(LogERROR
, "DoLoop: Cannot create fd_set\n");
245 t
.tv_sec
= t
.tv_usec
= 0;
246 ret
= select(dev
->cs
+ 1, r
, NULL
, NULL
, &t
);
251 if (NgRecvMsg(dev
->cs
, rep
, sizeof msgbuf
, NULL
) <= 0)
254 if (rep
->header
.version
!= NG_VERSION
) {
255 log_Printf(LogWARN
, "%ld: Unexpected netgraph version, expected %ld\n",
256 (long)rep
->header
.version
, (long)NG_VERSION
);
260 if (rep
->header
.typecookie
!= NGM_PPPOE_COOKIE
) {
261 log_Printf(LogWARN
, "%ld: Unexpected netgraph cookie, expected %ld\n",
262 (long)rep
->header
.typecookie
, (long)NGM_PPPOE_COOKIE
);
267 switch (rep
->header
.cmd
) {
268 case NGM_PPPOE_SET_FLAG
: msg
= "SET_FLAG"; break;
269 case NGM_PPPOE_CONNECT
: msg
= "CONNECT"; break;
270 case NGM_PPPOE_LISTEN
: msg
= "LISTEN"; break;
271 case NGM_PPPOE_OFFER
: msg
= "OFFER"; break;
272 case NGM_PPPOE_SUCCESS
: msg
= "SUCCESS"; break;
273 case NGM_PPPOE_FAIL
: msg
= "FAIL"; break;
274 case NGM_PPPOE_CLOSE
: msg
= "CLOSE"; break;
275 case NGM_PPPOE_GET_STATUS
: msg
= "GET_STATUS"; break;
276 case NGM_PPPOE_ACNAME
:
278 if (setenv("ACNAME", sts
->hook
, 1) != 0)
279 log_Printf(LogWARN
, "setenv: cannot set ACNAME=%s: %m", sts
->hook
);
280 asciilen
= rep
->header
.arglen
;
282 case NGM_PPPOE_SESSIONID
:
284 snprintf(sessionid
, sizeof sessionid
, "%04x", *(u_int16_t
*)sts
);
285 if (setenv("SESSIONID", sessionid
, 1) != 0)
286 syslog(LOG_WARNING
, "setenv: cannot set SESSIONID=%s: %m",
288 /* Use this in preference to our interface index */
289 slot
= strtoul(sessionid
, &end
, 16);
290 if (end
!= sessionid
&& *end
== '\0')
294 snprintf(unknown
, sizeof unknown
, "<%d>", (int)rep
->header
.cmd
);
300 log_Printf(LogPHASE
, "Received NGM_PPPOE_%s (hook \"%.*s\")\n",
301 msg
, asciilen
, sts
->hook
);
303 log_Printf(LogPHASE
, "Received NGM_PPPOE_%s\n", msg
);
305 switch (rep
->header
.cmd
) {
306 case NGM_PPPOE_SUCCESS
:
307 dev
->connected
= CARRIER_OK
;
310 case NGM_PPPOE_CLOSE
:
311 dev
->connected
= CARRIER_LOST
;
319 ether_AwaitCarrier(struct physical
*p
)
321 struct etherdevice
*dev
= device2ether(p
->handler
);
323 if (dev
->connected
!= CARRIER_OK
&& !dev
->timeout
--)
324 dev
->connected
= CARRIER_LOST
;
325 else if (dev
->connected
== CARRIER_PENDING
)
326 ether_MessageIn(dev
);
328 return dev
->connected
;
331 static const struct device baseetherdevice
= {
335 { CD_REQUIRED
, DEF_ETHERCDDELAY
},
353 ether_iov2device(int type
, struct physical
*p
, struct iovec
*iov
, int *niov
,
354 int maxiov __unused
, int *auxfd
, int *nauxfd
)
356 if (type
== ETHER_DEVICE
) {
357 struct etherdevice
*dev
= (struct etherdevice
*)iov
[(*niov
)++].iov_base
;
359 dev
= realloc(dev
, sizeof *dev
); /* Reduce to the correct size */
361 log_Printf(LogALERT
, "Failed to allocate memory: %d\n",
363 AbortProgram(EX_OSERR
);
372 /* Refresh function pointers etc */
373 memcpy(&dev
->dev
, &baseetherdevice
, sizeof dev
->dev
);
375 physical_SetupStack(p
, dev
->dev
.name
, PHYSICAL_FORCE_SYNCNOACF
);
383 ether_UpdateSet(struct fdescriptor
*d
, fd_set
*r
, fd_set
*w
, fd_set
*e
, int *n
)
385 struct physical
*p
= descriptor2physical(d
);
386 struct etherdevice
*dev
= device2ether(p
->handler
);
389 if (r
&& dev
->cs
>= 0) {
391 log_Printf(LogTIMER
, "%s(ctrl): fdset(r) %d\n", p
->link
.name
, dev
->cs
);
396 result
+= physical_doUpdateSet(d
, r
, w
, e
, n
, 0);
402 ether_IsSet(struct fdescriptor
*d
, const fd_set
*fdset
)
404 struct physical
*p
= descriptor2physical(d
);
405 struct etherdevice
*dev
= device2ether(p
->handler
);
408 result
= dev
->cs
>= 0 && FD_ISSET(dev
->cs
, fdset
);
409 result
+= physical_IsSet(d
, fdset
);
415 ether_DescriptorRead(struct fdescriptor
*d
, struct bundle
*bundle
,
418 struct physical
*p
= descriptor2physical(d
);
419 struct etherdevice
*dev
= device2ether(p
->handler
);
421 if (dev
->cs
>= 0 && FD_ISSET(dev
->cs
, fdset
)) {
422 ether_MessageIn(dev
);
423 if (dev
->connected
== CARRIER_LOST
) {
424 log_Printf(LogPHASE
, "%s: Device disconnected\n", p
->link
.name
);
425 datalink_Down(p
->dl
, CLOSE_NORMAL
);
430 if (physical_IsSet(d
, fdset
))
431 physical_DescriptorRead(d
, bundle
, fdset
);
434 static struct device
*
435 ether_Abandon(struct etherdevice
*dev
, struct physical
*p
)
437 /* Abandon our node construction */
440 p
->fd
= -2; /* Nobody else need try.. */
447 ether_Create(struct physical
*p
)
450 struct etherdevice
*dev
;
451 struct ng_mesg
*resp
;
452 const struct hooklist
*hlist
;
453 const struct nodeinfo
*ninfo
;
454 char *path
, *sessionid
;
461 if (p
->fd
< 0 && !strncasecmp(p
->name
.full
, NG_PPPOE_NODE_TYPE
,
462 PPPOE_NODE_TYPE_LEN
) &&
463 p
->name
.full
[PPPOE_NODE_TYPE_LEN
] == ':') {
464 const struct linkinfo
*nlink
;
465 struct ngpppoe_init_data
*data
;
466 struct ngm_mkpeer mkp
;
467 struct ngm_connect ngc
;
468 const char *iface
, *provider
;
471 char connectpath
[sizeof dev
->hook
+ 2]; /* .:<hook> */
473 p
->fd
--; /* We own the device - change fd */
475 loadmodules(LOAD_VERBOSLY
, "netgraph", "ng_ether", "ng_pppoe", "ng_socket",
478 if ((dev
= malloc(sizeof *dev
)) == NULL
)
481 iface
= p
->name
.full
+ PPPOE_NODE_TYPE_LEN
+ 1;
483 provider
= strchr(iface
, ':');
485 ifacelen
= provider
- iface
;
487 providerlen
= strlen(provider
);
489 ifacelen
= strlen(iface
);
495 * We're going to do this (where tunN is our tunnel device):
499 * | <iface> | dev->cs
505 * .---------. .-----------.
506 * | pppoe | | socket |
507 * | <iface> |(tunN)<---->(tunN)| <unnamed> |
508 * `--------- `-----------'
515 /* Create a socket node */
516 if (ID0NgMkSockNode(NULL
, &dev
->cs
, &p
->fd
) == -1) {
517 log_Printf(LogWARN
, "Cannot create netgraph socket node: %s\n",
525 * Ask for a list of hooks attached to the "ether" node. This node should
526 * magically exist as a way of hooking stuff onto an ethernet device
528 path
= (char *)alloca(ifacelen
+ 2);
529 sprintf(path
, "%.*s:", (int)ifacelen
, iface
);
530 if (NgSendMsg(dev
->cs
, path
, NGM_GENERIC_COOKIE
, NGM_LISTHOOKS
,
532 log_Printf(LogWARN
, "%s Cannot send a netgraph message: %s\n",
533 path
, strerror(errno
));
534 return ether_Abandon(dev
, p
);
537 /* Get our list back */
538 resp
= (struct ng_mesg
*)rbuf
;
539 if (NgRecvMsg(dev
->cs
, resp
, sizeof rbuf
, NULL
) <= 0) {
540 log_Printf(LogWARN
, "Cannot get netgraph response: %s\n",
542 return ether_Abandon(dev
, p
);
545 hlist
= (const struct hooklist
*)resp
->data
;
546 ninfo
= &hlist
->nodeinfo
;
548 /* Make sure we've got the right type of node */
549 if (strncmp(ninfo
->type
, NG_ETHER_NODE_TYPE
,
550 sizeof NG_ETHER_NODE_TYPE
- 1)) {
551 log_Printf(LogWARN
, "%s Unexpected node type ``%s'' (wanted ``"
552 NG_ETHER_NODE_TYPE
"'')\n", path
, ninfo
->type
);
553 return ether_Abandon(dev
, p
);
556 log_Printf(LogDEBUG
, "List of netgraph node ``%s'' (id %x) hooks:\n",
559 /* look for a hook already attached. */
560 for (f
= 0; f
< ninfo
->hooks
; f
++) {
561 nlink
= &hlist
->link
[f
];
563 log_Printf(LogDEBUG
, " Found %s -> %s\n", nlink
->ourhook
,
566 if (!strcmp(nlink
->ourhook
, NG_ETHER_HOOK_ORPHAN
) ||
567 !strcmp(nlink
->ourhook
, NG_ETHER_HOOK_DIVERT
)) {
569 * Something is using the data coming out of this ``ether'' node.
570 * If it's a PPPoE node, we use that node, otherwise we complain that
571 * someone else is using the node.
573 if (!strcmp(nlink
->nodeinfo
.type
, NG_PPPOE_NODE_TYPE
))
574 /* Use this PPPoE node ! */
575 snprintf(ngc
.path
, sizeof ngc
.path
, "[%x]:", nlink
->nodeinfo
.id
);
577 log_Printf(LogWARN
, "%s Node type ``%s'' is currently active\n",
578 path
, nlink
->nodeinfo
.type
);
579 return ether_Abandon(dev
, p
);
585 if (f
== ninfo
->hooks
) {
587 * Create a new ``PPPoE'' node connected to the ``ether'' node using
588 * the ``orphan'' and ``ethernet'' hooks
590 snprintf(mkp
.type
, sizeof mkp
.type
, "%s", NG_PPPOE_NODE_TYPE
);
591 snprintf(mkp
.ourhook
, sizeof mkp
.ourhook
, "%s", NG_ETHER_HOOK_ORPHAN
);
592 snprintf(mkp
.peerhook
, sizeof mkp
.peerhook
, "%s", NG_PPPOE_HOOK_ETHERNET
);
593 snprintf(etherid
, sizeof etherid
, "[%x]:", ninfo
->id
);
595 log_Printf(LogDEBUG
, "Creating PPPoE netgraph node %s%s -> %s\n",
596 etherid
, mkp
.ourhook
, mkp
.peerhook
);
598 if (NgSendMsg(dev
->cs
, etherid
, NGM_GENERIC_COOKIE
,
599 NGM_MKPEER
, &mkp
, sizeof mkp
) < 0) {
600 log_Printf(LogWARN
, "%s Cannot create PPPoE netgraph node: %s\n",
601 etherid
, strerror(errno
));
602 return ether_Abandon(dev
, p
);
605 snprintf(ngc
.path
, sizeof ngc
.path
, "%s%s", path
, NG_ETHER_HOOK_ORPHAN
);
608 snprintf(dev
->hook
, sizeof dev
->hook
, "%s%d",
609 TUN_NAME
, p
->dl
->bundle
->unit
);
612 * Connect the PPPoE node to our socket node.
613 * ngc.path has already been set up
615 snprintf(ngc
.ourhook
, sizeof ngc
.ourhook
, "%s", dev
->hook
);
616 memcpy(ngc
.peerhook
, ngc
.ourhook
, sizeof ngc
.peerhook
);
618 log_Printf(LogDEBUG
, "Connecting netgraph socket .:%s -> %s:%s\n",
619 ngc
.ourhook
, ngc
.path
, ngc
.peerhook
);
620 if (NgSendMsg(dev
->cs
, ".:", NGM_GENERIC_COOKIE
,
621 NGM_CONNECT
, &ngc
, sizeof ngc
) < 0) {
622 log_Printf(LogWARN
, "Cannot connect PPPoE and socket netgraph "
623 "nodes: %s\n", strerror(errno
));
624 return ether_Abandon(dev
, p
);
627 /* Bring the Ethernet interface up */
628 path
[ifacelen
] = '\0'; /* Remove the trailing ':' */
629 if (!iface_SetFlags(path
, IFF_UP
))
630 log_Printf(LogWARN
, "%s: Failed to set the IFF_UP flag on %s\n",
633 /* And finally, request a connection to the given provider */
635 data
= (struct ngpppoe_init_data
*)alloca(sizeof *data
+ providerlen
);
636 snprintf(data
->hook
, sizeof data
->hook
, "%s", dev
->hook
);
637 memcpy(data
->data
, provider
, providerlen
);
638 data
->data_len
= providerlen
;
640 snprintf(connectpath
, sizeof connectpath
, ".:%s", dev
->hook
);
641 log_Printf(LogDEBUG
, "Sending PPPOE_CONNECT to %s\n", connectpath
);
642 if (NgSendMsg(dev
->cs
, connectpath
, NGM_PPPOE_COOKIE
,
643 NGM_PPPOE_CONNECT
, data
, sizeof *data
+ providerlen
) == -1) {
644 log_Printf(LogWARN
, "``%s'': Cannot start netgraph node: %s\n",
645 connectpath
, strerror(errno
));
646 return ether_Abandon(dev
, p
);
649 /* Hook things up so that we monitor dev->cs */
650 p
->desc
.UpdateSet
= ether_UpdateSet
;
651 p
->desc
.IsSet
= ether_IsSet
;
652 p
->desc
.Read
= ether_DescriptorRead
;
654 memcpy(&dev
->dev
, &baseetherdevice
, sizeof dev
->dev
);
655 switch (p
->cfg
.cd
.necessity
) {
657 dev
->dev
.cd
.delay
= p
->cfg
.cd
.delay
;
660 dev
->dev
.cd
= p
->cfg
.cd
;
663 log_Printf(LogWARN
, "%s: Carrier must be set, using ``set cd %d!''\n",
664 p
->link
.name
, dev
->dev
.cd
.delay
);
669 dev
->timeout
= dev
->dev
.cd
.delay
;
670 dev
->connected
= CARRIER_PENDING
;
671 /* This will be overridden by our session id - if provided by netgraph */
672 dev
->slot
= GetIfIndex(path
);
674 /* See if we're a netgraph socket */
677 if (fstat(p
->fd
, &st
) != -1 && (st
.st_mode
& S_IFSOCK
)) {
678 struct sockaddr_storage ssock
;
679 struct sockaddr
*sock
= (struct sockaddr
*)&ssock
;
683 if (getsockname(p
->fd
, sock
, &sz
) == -1) {
684 log_Printf(LogPHASE
, "%s: Link is a closed socket !\n", p
->link
.name
);
690 if (sock
->sa_family
== AF_NETGRAPH
) {
692 * It's a netgraph node... We can't determine hook names etc, so we
693 * stay pretty impartial....
695 log_Printf(LogPHASE
, "%s: Link is a netgraph node\n", p
->link
.name
);
697 if ((dev
= malloc(sizeof *dev
)) == NULL
) {
698 log_Printf(LogWARN
, "%s: Cannot allocate an ether device: %s\n",
699 p
->link
.name
, strerror(errno
));
703 memcpy(&dev
->dev
, &baseetherdevice
, sizeof dev
->dev
);
706 dev
->connected
= CARRIER_OK
;
710 * If we're being envoked from pppoed(8), we may have a SESSIONID
711 * set in the environment. If so, use it as the slot
713 if ((sessionid
= getenv("SESSIONID")) != NULL
) {
717 slot
= strtoul(sessionid
, &end
, 16);
718 dev
->slot
= end
!= sessionid
&& *end
== '\0' ? slot
: 0;
726 physical_SetupStack(p
, dev
->dev
.name
, PHYSICAL_FORCE_SYNCNOACF
);