1
0
mirror of https://github.com/openbsd/src.git synced 2024-12-21 23:18:00 -08:00

add an AF_FRAME socket domain and an IFT_ETHER protocol family under it.

this allows userland to use sockets to send and receive Ethernet
frames. as per the upcoming frame.4 man page:

     frame protocol family sockets are designed as an alternative to bpf(4)
     for handling low data and packet rate communication protocols.  Rather
     than filtering every frame entering the system before the network stack
     like bpf(4), the frame protocol family processing avoids this overhead by
     running after the built in protocol handlers in the kernel.  For this
     reason, it is not possible to handle IPv4 or IPv6 packets with frame
     protocol sockets because the kernel network stack consumes them before
     the receive handling for frame sockets is run.

if you've used udp sockets then these should feel much the same.

my main motivation is to implement an lldp agent in userland, but
without having to have bpf look at every packet when lldp happens
every minute or two.

the only feedback i had was positive, so i'm putting it in
ok claudio@
This commit is contained in:
dlg 2024-12-15 11:00:05 +00:00
parent 5bfef6125a
commit 6fb93e4770
7 changed files with 1064 additions and 6 deletions

View File

@ -1,4 +1,4 @@
# $OpenBSD: files,v 1.741 2024/10/31 13:55:21 claudio Exp $
# $OpenBSD: files,v 1.742 2024/12/15 11:00:05 dlg Exp $
# $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $
# @(#)files.newconf 7.5 (Berkeley) 5/10/93
@ -601,6 +601,9 @@ pseudo-device pppx: ifnet
pseudo-device vxlan: ifnet, ether, etherbridge
pseudo-device wg: ifnet
pseudo-device af_frame
file net/af_frame.c af_frame needs-flag
pseudo-device ksyms
file dev/ksyms.c ksyms needs-flag

View File

@ -1,4 +1,4 @@
/* $OpenBSD: uipc_domain.c,v 1.68 2024/08/16 09:20:34 mvs Exp $ */
/* $OpenBSD: uipc_domain.c,v 1.69 2024/12/15 11:00:05 dlg Exp $ */
/* $NetBSD: uipc_domain.c,v 1.14 1996/02/09 19:00:44 christos Exp $ */
/*
@ -41,9 +41,14 @@
#include <sys/sysctl.h>
#include <sys/timeout.h>
#include "af_frame.h"
#include "bpfilter.h"
#include "pflow.h"
#if NAF_FRAME > 0
extern const struct domain framedomain;
#endif
const struct domain *const domains[] = {
#ifdef MPLS
&mplsdomain,
@ -57,6 +62,9 @@ const struct domain *const domains[] = {
&inetdomain,
&unixdomain,
&routedomain,
#if NAF_FRAME > 0
&framedomain,
#endif
NULL
};

View File

@ -1,4 +1,4 @@
/* $OpenBSD: uipc_socket.c,v 1.345 2024/11/08 21:47:03 bluhm Exp $ */
/* $OpenBSD: uipc_socket.c,v 1.346 2024/12/15 11:00:05 dlg Exp $ */
/* $NetBSD: uipc_socket.c,v 1.21 1996/02/04 02:17:52 christos Exp $ */
/*
@ -167,6 +167,7 @@ soalloc(const struct protosw *prp, int wait)
case AF_KEY:
case AF_ROUTE:
case AF_UNIX:
case AF_FRAME:
so->so_snd.sb_flags |= SB_MTXLOCK;
so->so_rcv.sb_flags |= SB_MTXLOCK;
break;

62
sys/net/af_frame.c Normal file
View File

@ -0,0 +1,62 @@
/* $OpenBSD: af_frame.c,v 1.1 2024/12/15 11:00:05 dlg Exp $ */
/*
* Copyright (c) 2024 David Gwynne <dlg@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/domain.h>
#include <sys/systm.h>
#include <net/if_types.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
const struct domain framedomain;
/* reach over to if_ethersubr.c */
int ether_frm_ctloutput(int, struct socket *, int, int, struct mbuf *);
extern const struct pr_usrreqs ether_frm_usrreqs;
static const struct protosw framesw[] = {
{
.pr_type = SOCK_DGRAM,
.pr_domain = &framedomain,
.pr_protocol = IFT_ETHER,
.pr_flags = PR_ATOMIC|PR_ADDR|PR_MPINPUT|PR_MPSOCKET,
.pr_ctloutput = ether_frm_ctloutput,
.pr_usrreqs = &ether_frm_usrreqs,
.pr_sysctl = NULL /* ether_frm_sysctl */,
},
};
const struct domain framedomain = {
.dom_family = AF_FRAME,
.dom_name = "frame",
.dom_protosw = framesw,
.dom_protoswNPROTOSW = &framesw[nitems(framesw)],
};
void
af_frameattach(int n)
{
/* nop */
}

47
sys/net/frame.h Normal file
View File

@ -0,0 +1,47 @@
/* $OpenBSD: frame.h,v 1.1 2024/12/15 11:00:05 dlg Exp $ */
/*
* Copyright (c) 2024 David Gwynne <dlg@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _NET_FRAME_H_
#define _NET_FRAME_H_
#define FRAME_ADDRLEN 8 /* big enough for Ethernet */
#define FRAME_DATALEN 32
struct sockaddr_frame {
uint8_t sfrm_len;
uint8_t sfrm_family; /* AF_FRAME */
uint16_t sfrm_proto;
unsigned int sfrm_ifindex;
uint8_t sfrm_addr[FRAME_ADDRLEN];
char sfrm_ifname[IFNAMSIZ];
uint8_t sfrm_data[FRAME_DATALEN];
};
#define FRAME_RECVDSTADDR 0 /* int */
#define FRAME_RECVPRIO 1 /* int */
#define FRAME_ADD_MEMBERSHIP 64 /* struct frame_mreq */
#define FRAME_DEL_MEMBERSHIP 65 /* struct frame_mreq */
#define FRAME_SENDPRIO 66 /* int: IF_HDRPRIO_{MIN-MAX,PACKET} */
struct frame_mreq {
unsigned int fmr_ifindex;
uint8_t fmr_addr[FRAME_ADDRLEN];
char fmr_ifname[IFNAMSIZ];
};
#endif /* _NET_FRAME_H_ */

View File

@ -1,4 +1,4 @@
/* $OpenBSD: if_ethersubr.c,v 1.293 2024/02/14 22:41:48 bluhm Exp $ */
/* $OpenBSD: if_ethersubr.c,v 1.294 2024/12/15 11:00:05 dlg Exp $ */
/* $NetBSD: if_ethersubr.c,v 1.19 1996/05/07 02:40:30 thorpej Exp $ */
/*
@ -140,6 +140,14 @@ didn't get a copy, you may request one from <license@ipv6.nrl.navy.mil>.
#include <netmpls/mpls.h>
#endif /* MPLS */
#include "af_frame.h"
#if NAF_FRAME > 0
#include <net/frame.h>
static struct mbuf *
ether_frm_input(struct ifnet *, struct mbuf *, uint64_t, uint16_t);
#endif
/* #define ETHERDEBUG 1 */
#ifdef ETHERDEBUG
int etherdebug = ETHERDEBUG;
@ -578,6 +586,9 @@ ether_input(struct ifnet *ifp, struct mbuf *m)
return;
#endif
default:
#if NAF_FRAME > 0
m = ether_frm_input(ifp, m, dst, etype);
#endif
goto dropanyway;
}
@ -1247,3 +1258,927 @@ ether_extract_headers(struct mbuf *m0, struct ether_extracted *ext)
ext->tcp ? "tcp," : "", ext->udp ? "udp," : "",
ext->iplen, ext->iphlen, ext->tcphlen, ext->paylen);
}
#if NAF_FRAME > 0
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/domain.h>
/*
* lock order is:
*
* - socket lock
* - ether_pcb_lock
* - socket buffer mtx
*/
struct ether_pcb;
struct ether_pcb_group {
TAILQ_ENTRY(ether_pcb_group)
epg_entry;
struct ether_pcb *
epg_pcb;
unsigned int epg_ifindex;
uint8_t epg_addr[ETHER_ADDR_LEN];
struct task epg_hook;
};
TAILQ_HEAD(ether_pcb_groups, ether_pcb_group);
struct ether_pcb {
TAILQ_ENTRY(ether_pcb)
ep_entry;
struct rwlock ep_lock;
struct socket *ep_socket;
uint64_t ep_laddr;
uint64_t ep_faddr;
unsigned int ep_ifindex;
uint16_t ep_etype;
uint64_t ep_options;
int ep_txprio;
struct ether_pcb_groups
ep_groups;
};
TAILQ_HEAD(ether_pcb_list, ether_pcb);
static int ether_frm_attach(struct socket *, int, int);
static int ether_frm_detach(struct socket *);
static int ether_frm_bind(struct socket *, struct mbuf *, struct proc *);
static int ether_frm_connect(struct socket *, struct mbuf *);
static int ether_frm_disconnect(struct socket *);
static int ether_frm_shutdown(struct socket *);
static int ether_frm_send(struct socket *, struct mbuf *, struct mbuf *,
struct mbuf *);
static int ether_frm_control(struct socket *, u_long, caddr_t,
struct ifnet *);
static int ether_frm_sockaddr(struct socket *, struct mbuf *);
static int ether_frm_peeraddr(struct socket *, struct mbuf *);
const struct pr_usrreqs ether_frm_usrreqs = {
.pru_attach = ether_frm_attach,
.pru_detach = ether_frm_detach,
.pru_bind = ether_frm_bind,
.pru_connect = ether_frm_connect,
.pru_disconnect = ether_frm_disconnect,
.pru_shutdown = ether_frm_shutdown,
.pru_send = ether_frm_send,
.pru_control = ether_frm_control,
.pru_sockaddr = ether_frm_sockaddr,
.pru_peeraddr = ether_frm_peeraddr,
};
static struct rwlock ether_pcb_lock = RWLOCK_INITIALIZER("ethsocks");
static struct ether_pcb_list ether_pcbs = TAILQ_HEAD_INITIALIZER(ether_pcbs);
static int
ether_frm_valid_etype(uint16_t etype)
{
switch (etype) {
case ETHERTYPE_LLDP:
case ETHERTYPE_EAPOL:
return (1);
}
return (0);
}
static int
ether_frm_nam2sfrm(struct sockaddr_frame **sfrmp, const struct mbuf *nam)
{
struct sockaddr_frame *sfrm;
if (nam->m_len != sizeof(*sfrm))
return (EINVAL);
sfrm = mtod(nam, struct sockaddr_frame *);
if (sfrm->sfrm_family != AF_FRAME)
return (EAFNOSUPPORT);
*sfrmp = sfrm;
return (0);
}
static int
ether_frm_ifp(struct ifnet **ifpp, const struct sockaddr_frame *sfrm)
{
struct ifnet *ifp;
if (sfrm->sfrm_ifindex != 0)
ifp = if_get(sfrm->sfrm_ifindex);
else if (sfrm->sfrm_ifname[0] != '\0') {
KERNEL_LOCK();
ifp = if_unit(sfrm->sfrm_ifname);
KERNEL_UNLOCK();
} else {
*ifpp = NULL;
return (0);
}
if (ifp == NULL)
return (ENXIO);
if (ifp->if_type != IFT_ETHER) {
if_put(ifp);
return (EAFNOSUPPORT);
}
*ifpp = ifp;
return (0);
}
static int
ether_frm_attach(struct socket *so, int proto, int wait)
{
struct ether_pcb *ep;
int error;
if (so->so_pcb != NULL)
return (EINVAL);
error = suser(curproc);
if (error != 0)
return (error);
error = soreserve(so, MCLBYTES, MCLBYTES);
if (error != 0)
return (error);
ep = malloc(sizeof(*ep), M_PCB, (wait ? M_WAITOK : M_NOWAIT) | M_ZERO);
if (ep == NULL)
return (ENOMEM);
rw_init(&ep->ep_lock, "ethsock");
so->so_pcb = ep;
ep->ep_socket = so; /* shares a ref with the list */
ep->ep_txprio = IF_HDRPRIO_PACKET;
TAILQ_INIT(&ep->ep_groups);
/* give the ref to the list */
rw_enter_write(&ether_pcb_lock);
TAILQ_INSERT_TAIL(&ether_pcbs, ep, ep_entry);
rw_exit_write(&ether_pcb_lock);
return (0);
}
static int
ether_frm_detach(struct socket *so)
{
struct ether_pcb *ep;
struct ether_pcb_group *epg, *nepg;
struct ifnet *ifp;
soassertlocked(so);
ep = so->so_pcb;
/* take the ref from the list */
rw_enter_write(&ether_pcb_lock);
TAILQ_REMOVE(&ether_pcbs, ep, ep_entry);
rw_exit_write(&ether_pcb_lock);
so->so_pcb = NULL; /* shares a ref with the list */
/* XXX locking */
TAILQ_FOREACH_SAFE(epg, &ep->ep_groups, epg_entry, nepg) {
ifp = if_get(epg->epg_ifindex);
if (ifp != NULL) {
struct ifreq ifr;
struct sockaddr *sa;
if_detachhook_del(ifp, &epg->epg_hook);
memset(&ifr, 0, sizeof(ifr));
strlcpy(ifr.ifr_name, ifp->if_xname,
sizeof(ifr.ifr_name));
sa = &ifr.ifr_addr;
sa->sa_family = AF_UNSPEC;
memcpy(sa->sa_data, &epg->epg_addr, ETHER_ADDR_LEN);
(*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)&ifr);
}
if_put(ifp);
TAILQ_REMOVE(&ep->ep_groups, epg, epg_entry);
free(epg, M_PCB, sizeof(*epg));
}
free(ep, M_PCB, sizeof(*ep));
return (0);
}
static int
ether_frm_bind(struct socket *so, struct mbuf *nam, struct proc *p)
{
struct sockaddr_frame *sfrm;
struct ether_pcb *ep;
struct ether_pcb *epe;
struct ifnet *ifp = NULL;
unsigned int ifindex = 0;
uint16_t etype;
uint64_t laddr;
int error;
soassertlocked(so);
error = ether_frm_nam2sfrm(&sfrm, nam);
if (error != 0)
return (error);
etype = ntohs(sfrm->sfrm_proto);
if (!ether_frm_valid_etype(etype))
return (EADDRNOTAVAIL);
ep = so->so_pcb;
if (ep->ep_etype != 0)
return (EINVAL);
error = ether_frm_ifp(&ifp, sfrm);
if (error != 0)
return (error);
if (ifp != NULL)
ifindex = ifp->if_index;
laddr = ether_addr_to_e64((struct ether_addr *)sfrm->sfrm_addr);
rw_enter_write(&ether_pcb_lock);
TAILQ_FOREACH(epe, &ether_pcbs, ep_entry) {
if (ep == epe)
continue;
/* XXX check stuff */
}
if (error == 0) {
/* serialised by the socket lock */
ep->ep_etype = etype;
ep->ep_ifindex = ifindex;
ep->ep_laddr = laddr;
}
rw_exit_write(&ether_pcb_lock);
if_put(ifp);
return (error);
}
static int
ether_frm_connect(struct socket *so, struct mbuf *nam)
{
struct sockaddr_frame *sfrm;
struct ether_pcb *ep;
struct ether_pcb *epe;
struct ifnet *ifp = NULL;
uint64_t faddr;
uint16_t etype;
int error;
soassertlocked(so);
error = ether_frm_nam2sfrm(&sfrm, nam);
if (error != 0)
return (error);
etype = ntohs(sfrm->sfrm_proto);
if (!ether_frm_valid_etype(etype))
return (EADDRNOTAVAIL);
faddr = ether_addr_to_e64((struct ether_addr *)sfrm->sfrm_addr);
if (faddr == 0)
return (EADDRNOTAVAIL);
error = ether_frm_ifp(&ifp, sfrm);
if (error != 0)
return (error);
if (ifp == NULL)
return (EADDRNOTAVAIL);
ep = so->so_pcb;
if (ep->ep_etype != 0) {
if (ep->ep_faddr != 0 ||
ep->ep_etype != etype) {
error = EISCONN;
goto put;
}
}
if (ep->ep_ifindex != 0) {
if (ep->ep_ifindex != ifp->if_index) {
error = EADDRNOTAVAIL;
goto put;
}
}
rw_enter_write(&ether_pcb_lock);
TAILQ_FOREACH(epe, &ether_pcbs, ep_entry) {
if (ep == epe)
continue;
/* XXX check stuff */
}
if (error == 0) {
/* serialised by the socket lock */
ep->ep_etype = etype;
ep->ep_ifindex = ifp->if_index;
ep->ep_faddr = faddr;
}
rw_exit_write(&ether_pcb_lock);
put:
if_put(ifp);
return (error);
}
static int
ether_frm_disconnect(struct socket *so)
{
struct ether_pcb *ep;
soassertlocked(so);
ep = so->so_pcb;
if (ep->ep_faddr == 0)
return (ENOTCONN);
rw_enter_write(&ether_pcb_lock);
ep->ep_ifindex = 0;
ep->ep_etype = 0;
ep->ep_laddr = 0;
ep->ep_faddr = 0;
rw_exit_write(&ether_pcb_lock);
return (0);
}
static int
ether_frm_shutdown(struct socket *so)
{
soassertlocked(so);
socantsendmore(so);
return (0);
}
static int
ether_frm_send(struct socket *so, struct mbuf *m, struct mbuf *nam,
struct mbuf *control)
{
struct ether_pcb *ep;
int error;
uint16_t etype;
uint64_t laddr;
uint64_t faddr;
struct ifnet *ifp = NULL;
struct arpcom *ac;
struct ether_header *eh;
int txprio;
soassertlocked_readonly(so);
ep = so->so_pcb;
KASSERTMSG(ep != NULL, "%s: NULL pcb on socket %p", __func__, so);
txprio = ep->ep_txprio;
/* XXX get prio out of a cmsg */
m_freem(control);
if (nam != NULL) {
struct sockaddr_frame *sfrm;
error = ether_frm_nam2sfrm(&sfrm, nam);
if (error != 0)
goto drop;
etype = ntohs(sfrm->sfrm_proto);
if (!ether_frm_valid_etype(etype)) {
error = EADDRNOTAVAIL;
goto drop;
}
if (ep->ep_faddr != 0) {
error = EISCONN;
goto drop;
}
faddr = ether_addr_to_e64((struct ether_addr *)sfrm->sfrm_addr);
if (faddr == 0) {
error = EADDRNOTAVAIL;
goto drop;
}
error = ether_frm_ifp(&ifp, sfrm);
if (error != 0)
goto drop;
if (ifp == NULL) {
ifp = if_get(ep->ep_ifindex);
if (ifp == NULL) {
error = EADDRNOTAVAIL;
goto drop;
}
} else {
if (ep->ep_ifindex != 0 &&
ep->ep_ifindex != ifp->if_index) {
error = EADDRNOTAVAIL;
goto drop;
}
}
if (ep->ep_etype != etype) {
if (ep->ep_etype == 0) {
/* this is cheeky */
rw_enter_write(&ether_pcb_lock);
ep->ep_etype = etype;
rw_exit_write(&ether_pcb_lock);
} else {
error = EADDRNOTAVAIL;
goto drop;
}
}
} else {
faddr = ep->ep_faddr;
if (faddr == 0) {
error = ENOTCONN;
goto drop;
}
ifp = if_get(ep->ep_ifindex);
if (ifp == NULL) {
error = ENXIO;
goto drop;
}
etype = ep->ep_etype;
}
if (ifp->if_type != IFT_ETHER) {
error = EAFNOSUPPORT;
goto drop;
}
ac = (struct arpcom *)ifp;
laddr = ether_addr_to_e64((struct ether_addr *)ac->ac_enaddr);
if (ep->ep_laddr != laddr) {
if (ep->ep_laddr != 0) {
error = EADDRNOTAVAIL;
goto drop;
}
}
m = m_prepend(m, ETHER_ALIGN + sizeof(*eh), M_NOWAIT);
if (m == NULL)
goto drop;
m_adj(m, ETHER_ALIGN);
if (txprio != IF_HDRPRIO_PACKET)
m->m_pkthdr.pf.prio = txprio;
eh = mtod(m, struct ether_header *);
ether_e64_to_addr((struct ether_addr *)eh->ether_dhost, faddr);
ether_e64_to_addr((struct ether_addr *)eh->ether_shost, laddr);
eh->ether_type = htons(etype);
error = if_enqueue(ifp, m);
m = NULL;
drop:
if_put(ifp);
m_freem(m);
return (error);
}
static int
ether_frm_control(struct socket *so, u_long cmd, caddr_t data,
struct ifnet *ifp)
{
return (EOPNOTSUPP);
}
static int
ether_frm_sockaddr_frame(struct ether_pcb *ep, struct mbuf *nam, uint64_t addr)
{
struct sockaddr_frame *sfrm;
struct ifnet *ifp;
nam->m_len = sizeof(*sfrm);
sfrm = mtod(nam, struct sockaddr_frame *);
memset(sfrm, 0, sizeof(*sfrm));
sfrm->sfrm_len = sizeof(*sfrm);
sfrm->sfrm_family = AF_FRAME;
ether_e64_to_addr((struct ether_addr *)sfrm->sfrm_addr, addr);
if (ep->ep_etype) {
sfrm->sfrm_proto = htons(ep->ep_etype);
sfrm->sfrm_ifindex = ep->ep_ifindex;
ifp = if_get(ep->ep_ifindex);
if (ifp != NULL) {
strlcpy(sfrm->sfrm_ifname, ifp->if_xname,
sizeof(sfrm->sfrm_ifname));
}
if_put(ifp);
}
return (0);
}
static int
ether_frm_sockaddr(struct socket *so, struct mbuf *nam)
{
struct ether_pcb *ep = so->so_pcb;
return (ether_frm_sockaddr_frame(ep, nam, ep->ep_laddr));
}
static int
ether_frm_peeraddr(struct socket *so, struct mbuf *nam)
{
struct ether_pcb *ep = so->so_pcb;
return (ether_frm_sockaddr_frame(ep, nam, ep->ep_faddr));
}
static void
ether_frm_group_detach(void *arg)
{
struct ether_pcb_group *epg = arg;
struct ether_pcb *ep = epg->epg_pcb;
struct socket *so = ep->ep_socket;
struct ifnet *ifp;
ifp = if_get(epg->epg_ifindex);
/* XXX locking^Wreference counts */
solock(so);
if (ifp != NULL)
if_detachhook_del(ifp, &epg->epg_hook);
TAILQ_REMOVE(&ep->ep_groups, epg, epg_entry);
sounlock(so);
if_put(ifp);
free(epg, M_PCB, sizeof(*epg));
}
static int
ether_frm_group(struct socket *so, int optname, struct mbuf *m)
{
struct frame_mreq *fmr;
struct ifreq ifr;
struct sockaddr *sa;
struct ifnet *ifp;
struct ether_pcb *ep;
struct ether_pcb_group *epg;
u_long cmd;
int error;
soassertlocked(so);
if (m->m_len != sizeof(*fmr))
return (EINVAL);
fmr = mtod(m, struct frame_mreq *);
if (!ETHER_IS_MULTICAST(fmr->fmr_addr))
return (EADDRNOTAVAIL);
if (fmr->fmr_ifindex == 0) {
KERNEL_LOCK();
ifp = if_unit(fmr->fmr_ifname);
KERNEL_UNLOCK();
} else
ifp = if_get(fmr->fmr_ifindex);
if (ifp == NULL)
return (ENXIO);
if (ifp->if_type != IFT_ETHER) {
error = EADDRNOTAVAIL;
goto put;
}
if (ETHER_IS_BROADCAST(fmr->fmr_addr)) {
error = 0;
goto put;
}
ep = so->so_pcb;
TAILQ_FOREACH(epg, &ep->ep_groups, epg_entry) {
if (epg->epg_ifindex != ifp->if_index)
continue;
if (!ETHER_IS_EQ(epg->epg_addr, fmr->fmr_addr))
continue;
break;
}
switch (optname) {
case FRAME_ADD_MEMBERSHIP:
if (epg != NULL) {
error = EISCONN;
goto put;
}
epg = malloc(sizeof(*epg), M_PCB, M_DONTWAIT);
if (epg == NULL) {
error = ENOMEM;
goto put;
}
epg->epg_pcb = ep;
epg->epg_ifindex = ifp->if_index;
memcpy(&epg->epg_addr, fmr->fmr_addr, sizeof(epg->epg_addr));
task_set(&epg->epg_hook, ether_frm_group_detach, epg);
cmd = SIOCADDMULTI;
break;
case FRAME_DEL_MEMBERSHIP:
if (epg == NULL) {
error = ENOTCONN;
goto put;
}
cmd = SIOCDELMULTI;
break;
default:
panic("%s: unexpected optname %d", __func__, optname);
/* NOTREACHED */
}
memset(&ifr, 0, sizeof(ifr));
strlcpy(ifr.ifr_name, ifp->if_xname, sizeof(ifr.ifr_name));
sa = &ifr.ifr_addr;
sa->sa_family = AF_UNSPEC;
memcpy(sa->sa_data, fmr->fmr_addr, ETHER_ADDR_LEN);
/* XXX soref? */
/* this could lead to multiple epgs for the same if/group */
sounlock(so);
KERNEL_LOCK();
NET_LOCK();
error = (*ifp->if_ioctl)(ifp, cmd, (caddr_t)&ifr);
NET_UNLOCK();
KERNEL_UNLOCK();
solock(so);
switch (optname) {
case FRAME_ADD_MEMBERSHIP:
if (error != 0) {
free(epg, M_PCB, sizeof(*epg));
break;
}
TAILQ_INSERT_TAIL(&ep->ep_groups, epg, epg_entry);
if_detachhook_add(ifp, &epg->epg_hook);
break;
case FRAME_DEL_MEMBERSHIP:
if (error != 0)
break;
if_detachhook_del(ifp, &epg->epg_hook);
TAILQ_REMOVE(&ep->ep_groups, epg, epg_entry);
free(epg, M_PCB, sizeof(*epg));
break;
}
put:
if_put(ifp);
return (error);
}
#define ETHER_PCB_OPTM(_v) (1ULL << (_v))
#define ETHER_PCB_OPTS \
ETHER_PCB_OPTM(FRAME_RECVDSTADDR) | \
ETHER_PCB_OPTM(FRAME_RECVPRIO)
static int
ether_frm_setopt(struct ether_pcb *ep, int optname, struct mbuf *m)
{
uint64_t optm = ETHER_PCB_OPTM(optname);
int opt;
if (!ISSET(ETHER_PCB_OPTS, optm))
return (ENOPROTOOPT);
if (m->m_len != sizeof(opt))
return (EINVAL);
opt = *mtod(m, int *);
if (opt)
SET(ep->ep_options, optm);
else
CLR(ep->ep_options, optm);
return (0);
}
static int
ether_frm_setsockopt(struct socket *so, int optname, struct mbuf *m)
{
struct ether_pcb *ep = so->so_pcb;
int error = ENOPROTOOPT;
int v;
if (optname >= 0 && optname < 64)
return (ether_frm_setopt(ep, optname, m));
switch (optname) {
case FRAME_ADD_MEMBERSHIP:
case FRAME_DEL_MEMBERSHIP:
error = ether_frm_group(so, optname, m);
break;
case FRAME_SENDPRIO:
if (m->m_len != sizeof(v)) {
error = EINVAL;
break;
}
v = *mtod(m, int *);
error = if_txhprio_l2_check(v);
if (error != 0)
break;
ep->ep_txprio = v;
break;
default:
break;
}
return (error);
}
static int
ether_frm_getopt(struct ether_pcb *ep, int optname, struct mbuf *m)
{
uint64_t optm = ETHER_PCB_OPTM(optname);
int opt;
if (!ISSET(ETHER_PCB_OPTS, optm))
return (ENOPROTOOPT);
opt = !!ISSET(ep->ep_options, optm);
m->m_len = sizeof(opt);
*mtod(m, int *) = opt;
return (0);
}
static int
ether_frm_getsockopt(struct socket *so, int optname, struct mbuf *m)
{
struct ether_pcb *ep = so->so_pcb;
int error = ENOPROTOOPT;
if (optname >= 0 && optname < 64)
return (ether_frm_getopt(ep, optname, m));
switch (optname) {
default:
break;
}
return (error);
}
int
ether_frm_ctloutput(int op, struct socket *so, int level, int optname,
struct mbuf *m)
{
int error = 0;
if (level != IFT_ETHER)
return (EINVAL);
switch (op) {
case PRCO_SETOPT:
error = ether_frm_setsockopt(so, optname, m);
break;
case PRCO_GETOPT:
error = ether_frm_getsockopt(so, optname, m);
break;
}
return (error);
}
static struct mbuf *
ether_frm_cmsg(struct mbuf *cmsgs, const void *data, size_t datalen,
int type, int level)
{
struct mbuf *cm;
cm = sbcreatecontrol(data, datalen, type, level);
if (cm != NULL) {
cm->m_next = cmsgs;
cmsgs = cm;
}
return (cmsgs);
}
static void
ether_frm_recv(struct socket *so, struct mbuf *m0,
const struct sockaddr_frame *sfrm)
{
struct ether_pcb *ep = so->so_pcb;
struct mbuf *m;
struct mbuf *cmsgs = NULL;
int ok;
/* offset 0 and m_adj cos sbappendaddr needs m_pkthdr.len */
m = m_copym(m0, 0, M_COPYALL, M_DONTWAIT);
if (m == NULL)
return;
m_adj(m, sizeof(struct ether_header));
if (ISSET(ep->ep_options, ETHER_PCB_OPTM(FRAME_RECVPRIO))) {
int rxprio = m0->m_pkthdr.pf.prio;
cmsgs = ether_frm_cmsg(cmsgs, &rxprio, sizeof(rxprio),
FRAME_RECVPRIO, IFT_ETHER);
}
if (ISSET(ep->ep_options, ETHER_PCB_OPTM(FRAME_RECVDSTADDR))) {
struct ether_header *eh = mtod(m0, struct ether_header *);
cmsgs = ether_frm_cmsg(cmsgs, eh->ether_dhost, ETHER_ADDR_LEN,
FRAME_RECVDSTADDR, IFT_ETHER);
}
if (ISSET(so->so_options, SO_TIMESTAMP)) {
struct timeval tv;
m_microtime(m0, &tv);
cmsgs = ether_frm_cmsg(cmsgs, &tv, sizeof(tv),
SCM_TIMESTAMP, SOL_SOCKET);
}
mtx_enter(&so->so_rcv.sb_mtx);
ok = sbappendaddr(so, &so->so_rcv, (struct sockaddr *)sfrm, m, cmsgs);
mtx_leave(&so->so_rcv.sb_mtx);
if (!ok) {
m_freem(m);
m_freem(cmsgs);
return;
}
sorwakeup(so);
}
static struct mbuf *
ether_frm_input(struct ifnet *ifp, struct mbuf *m, uint64_t dst, uint16_t etype)
{
struct sockaddr_frame sfrm = { .sfrm_family = AF_UNSPEC };
struct ether_pcb *ep;
struct ether_header *eh;
uint64_t src;
if (TAILQ_EMPTY(&ether_pcbs))
return (m);
eh = mtod(m, struct ether_header *);
src = ether_addr_to_e64((struct ether_addr *)eh->ether_shost);
if (src == 0)
return (m);
rw_enter_read(&ether_pcb_lock);
TAILQ_FOREACH(ep, &ether_pcbs, ep_entry) {
if (ep->ep_etype == 0) /* bound? */
continue;
if (ep->ep_etype != etype)
continue;
if (ep->ep_ifindex != 0) {
if (ep->ep_ifindex != ifp->if_index)
continue;
}
if (ep->ep_laddr != 0) {
if (ep->ep_laddr != dst)
continue;
}
/* ether_input says dst is valid for local delivery */
if (ep->ep_faddr != 0) { /* connected? */
if (ep->ep_faddr != src)
continue;
}
if (sfrm.sfrm_family == AF_UNSPEC) {
sfrm.sfrm_len = sizeof(sfrm);
sfrm.sfrm_family = AF_FRAME;
sfrm.sfrm_proto = htons(etype);
sfrm.sfrm_ifindex = ifp->if_index;
ether_e64_to_addr((struct ether_addr *)sfrm.sfrm_addr,
src);
strlcpy(sfrm.sfrm_ifname, ifp->if_xname,
sizeof(sfrm.sfrm_ifname));
}
ether_frm_recv(ep->ep_socket, m, &sfrm);
}
rw_exit_read(&ether_pcb_lock);
return (m);
}
#endif /* NAF_FRAME */

View File

@ -1,4 +1,4 @@
/* $OpenBSD: socket.h,v 1.105 2022/09/03 21:13:48 mbuhl Exp $ */
/* $OpenBSD: socket.h,v 1.106 2024/12/15 11:00:05 dlg Exp $ */
/* $NetBSD: socket.h,v 1.14 1996/02/09 18:25:36 christos Exp $ */
/*
@ -200,7 +200,8 @@ struct splice {
#define AF_MPLS 33 /* MPLS */
#define pseudo_AF_PFLOW 34 /* pflow */
#define pseudo_AF_PIPEX 35 /* PIPEX */
#define AF_MAX 36
#define AF_FRAME 36 /* frame (Ethernet) sockets */
#define AF_MAX 37
/*
* Structure used by kernel to store most
@ -284,6 +285,7 @@ struct sockproto {
#define PF_MPLS AF_MPLS
#define PF_PFLOW pseudo_AF_PFLOW
#define PF_PIPEX pseudo_AF_PIPEX
#define PF_FRAME AF_FRAME
#define PF_MAX AF_MAX
/*