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:
parent
5bfef6125a
commit
6fb93e4770
@ -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
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
62
sys/net/af_frame.c
Normal 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 = ðer_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
47
sys/net/frame.h
Normal 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_ */
|
@ -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(ðer_pcb_lock);
|
||||
TAILQ_INSERT_TAIL(ðer_pcbs, ep, ep_entry);
|
||||
rw_exit_write(ðer_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(ðer_pcb_lock);
|
||||
TAILQ_REMOVE(ðer_pcbs, ep, ep_entry);
|
||||
rw_exit_write(ðer_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(ðer_pcb_lock);
|
||||
TAILQ_FOREACH(epe, ðer_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(ðer_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(ðer_pcb_lock);
|
||||
TAILQ_FOREACH(epe, ðer_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(ðer_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(ðer_pcb_lock);
|
||||
ep->ep_ifindex = 0;
|
||||
ep->ep_etype = 0;
|
||||
ep->ep_laddr = 0;
|
||||
ep->ep_faddr = 0;
|
||||
rw_exit_write(ðer_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(ðer_pcb_lock);
|
||||
ep->ep_etype = etype;
|
||||
rw_exit_write(ðer_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(ðer_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(ðer_pcb_lock);
|
||||
TAILQ_FOREACH(ep, ðer_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(ðer_pcb_lock);
|
||||
|
||||
return (m);
|
||||
}
|
||||
|
||||
#endif /* NAF_FRAME */
|
||||
|
@ -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
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user