#include "use_loop.h"
#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_clone.h>
#include <net/ifq_var.h>
#include <net/netisr.h>
#include <net/route.h>
#include <net/bpf.h>
#include <net/bpfdesc.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_var.h>
#endif
#ifdef INET6
#ifndef INET
#include <netinet/in.h>
#endif
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#endif
static int lo_clone_create(struct if_clone *, int, caddr_t, caddr_t);
static int lo_clone_destroy(struct ifnet *);
static int lo_output(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
static int lo_ioctl(struct ifnet *, u_long, caddr_t, struct ucred *);
static void lo_rtrequest(int, struct rtentry *);
#ifdef ALTQ
static void lo_altqstart(struct ifnet *, struct ifaltq_subque *);
#endif
#if defined(TINY_LOMTU)
#define LOMTU (1024+512)
#elif defined(LARGE_LOMTU)
#define LOMTU 131072
#else
#define LOMTU 16384
#endif
#define LO_CSUM_FEATURES (CSUM_IP | CSUM_UDP | CSUM_TCP)
struct ifnet *loif;
static struct if_clone lo_cloner = IF_CLONE_INITIALIZER("lo",
lo_clone_create, lo_clone_destroy, NLOOP, IF_MAXUNIT);
static void
lo_sysinit(void *dummy __unused)
{
if_clone_attach(&lo_cloner);
}
SYSINIT(lo_sysinit, SI_SUB_PSEUDO, SI_ORDER_ANY, lo_sysinit, NULL);
static int
lo_clone_create(struct if_clone *ifc, int unit,
caddr_t params __unused, caddr_t data __unused)
{
struct ifnet *ifp;
ifp = kmalloc(sizeof(*ifp), M_IFNET, M_WAITOK | M_ZERO);
if_initname(ifp, ifc->ifc_name, unit);
ifp->if_mtu = LOMTU;
ifp->if_flags = IFF_LOOPBACK | IFF_MULTICAST;
ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_RSS;
ifp->if_hwassist = LO_CSUM_FEATURES;
ifp->if_capenable = ifp->if_capabilities;
ifp->if_ioctl = lo_ioctl;
ifp->if_output = lo_output;
ifp->if_type = IFT_LOOP;
ifq_set_maxlen(&ifp->if_snd, ifqmaxlen);
ifq_set_ready(&ifp->if_snd);
#ifdef ALTQ
ifp->if_start = lo_altqstart;
#endif
if_attach(ifp, NULL);
bpfattach(ifp, DLT_NULL, sizeof(uint32_t));
if (loif == NULL) {
KASSERT(unit == 0, ("loif is %s", ifp->if_xname));
loif = ifp;
}
return (0);
}
static int
lo_clone_destroy(struct ifnet *ifp)
{
if (loif == ifp)
return (EPERM);
bpfdetach(ifp);
if_detach(ifp);
kfree(ifp, M_IFNET);
return (0);
}
static int
lo_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
M_ASSERTPKTHDR(m);
if (rt && rt->rt_flags & (RTF_REJECT|RTF_BLACKHOLE)) {
m_freem(m);
return (rt->rt_flags & RTF_BLACKHOLE ? 0 :
rt->rt_flags & RTF_HOST ? EHOSTUNREACH : ENETUNREACH);
}
IFNET_STAT_INC(ifp, opackets, 1);
IFNET_STAT_INC(ifp, obytes, m->m_pkthdr.len);
#if 1
switch (dst->sa_family) {
case AF_INET:
case AF_INET6:
break;
default:
kprintf("lo_output: af=%d unexpected\n", dst->sa_family);
m_freem(m);
return (EAFNOSUPPORT);
}
#endif
if (ifp->if_capenable & IFCAP_RXCSUM) {
int csum_flags = 0;
if (m->m_pkthdr.csum_flags & CSUM_IP)
csum_flags |= (CSUM_IP_CHECKED | CSUM_IP_VALID);
if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA)
csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR);
m->m_pkthdr.csum_flags |= csum_flags;
if (csum_flags & CSUM_DATA_VALID)
m->m_pkthdr.csum_data = 0xffff;
}
if ((ifp->if_capenable & IFCAP_RSS) == 0)
m->m_flags &= ~M_HASH;
return (if_simloop(ifp, m, dst->sa_family, 0));
}
int
if_simloop(struct ifnet *ifp, struct mbuf *m, int af, int hlen)
{
int isr;
KASSERT((m->m_flags & M_PKTHDR) != 0, ("if_simloop: no HDR"));
m->m_pkthdr.rcvif = ifp;
if (af == AF_UNSPEC) {
KASSERT(m->m_len >= sizeof(int), ("if_simloop: m_len"));
af = *(mtod(m, int *));
m->m_len -= sizeof(int);
m->m_pkthdr.len -= sizeof(int);
m->m_data += sizeof(int);
}
if (ifp->if_bpf) {
bpf_gettoken();
if (ifp->if_bpf == NULL)
goto rel;
if (ifp->if_bpf->bif_dlt == DLT_NULL) {
uint32_t bpf_af = (uint32_t)af;
bpf_ptap(ifp->if_bpf, m, &bpf_af, 4);
} else {
bpf_mtap(ifp->if_bpf, m);
}
rel:
bpf_reltoken();
}
if (hlen > 0)
m_adj(m, hlen);
#ifdef ALTQ
if (ifq_is_enabled(&ifp->if_snd) && ifp->if_start == lo_altqstart) {
struct altq_pktattr pktattr;
int32_t *afp;
ifq_classify(&ifp->if_snd, m, af, &pktattr);
M_PREPEND(m, sizeof(int32_t), M_NOWAIT);
if (m == NULL)
return(ENOBUFS);
afp = mtod(m, int32_t *);
*afp = (int32_t)af;
return ifq_dispatch(ifp, m, &pktattr);
}
#endif
switch (af) {
#ifdef INET
case AF_INET:
isr = NETISR_IP;
break;
#endif
#ifdef INET6
case AF_INET6:
m->m_flags |= M_LOOP;
isr = NETISR_IPV6;
break;
#endif
default:
kprintf("if_simloop: can't handle af=%d\n", af);
m_freem(m);
return (EAFNOSUPPORT);
}
IFNET_STAT_INC(ifp, ipackets, 1);
IFNET_STAT_INC(ifp, ibytes, m->m_pkthdr.len);
netisr_queue(isr, m);
return (0);
}
#ifdef ALTQ
static void
lo_altqstart(struct ifnet *ifp, struct ifaltq_subque *ifsq)
{
struct mbuf *m;
int32_t af, *afp;
int isr;
while (1) {
crit_enter();
m = ifsq_dequeue(ifsq);
crit_exit();
if (m == NULL)
return;
afp = mtod(m, int32_t *);
af = *afp;
m_adj(m, sizeof(int32_t));
switch (af) {
#ifdef INET
case AF_INET:
isr = NETISR_IP;
break;
#endif
#ifdef INET6
case AF_INET6:
m->m_flags |= M_LOOP;
isr = NETISR_IPV6;
break;
#endif
default:
kprintf("lo_altqstart: can't handle af%d\n", af);
m_freem(m);
return;
}
IFNET_STAT_INC(ifp, ipackets, 1);
IFNET_STAT_INC(ifp, ibytes, m->m_pkthdr.len);
netisr_queue(isr, m);
}
}
#endif
static void
lo_rtrequest(int cmd, struct rtentry *rt)
{
if (rt) {
rt->rt_rmx.rmx_mtu = rt->rt_ifp->if_mtu;
rt->rt_rmx.rmx_recvpipe = rt->rt_rmx.rmx_sendpipe = 3 * LOMTU;
}
}
static int
lo_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data, struct ucred *cr)
{
struct ifaddr *ifa;
struct ifreq *ifr = (struct ifreq *)data;
int error = 0, mask;
switch (cmd) {
case SIOCSIFADDR:
ifp->if_flags |= IFF_UP | IFF_RUNNING;
ifa = (struct ifaddr *)data;
ifa->ifa_rtrequest = lo_rtrequest;
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
if (ifr == NULL) {
error = EAFNOSUPPORT;
break;
}
switch (ifr->ifr_addr.sa_family) {
#ifdef INET
case AF_INET:
break;
#endif
#ifdef INET6
case AF_INET6:
break;
#endif
default:
error = EAFNOSUPPORT;
break;
}
break;
case SIOCSIFMTU:
ifp->if_mtu = ifr->ifr_mtu;
break;
case SIOCSIFFLAGS:
break;
case SIOCSIFCAP:
mask = ifr->ifr_reqcap ^ ifp->if_capenable;
if (mask & IFCAP_HWCSUM) {
ifp->if_capenable ^= (mask & IFCAP_HWCSUM);
if (IFCAP_TXCSUM & ifp->if_capenable)
ifp->if_hwassist = LO_CSUM_FEATURES;
else
ifp->if_hwassist = 0;
}
if (mask & IFCAP_RSS)
ifp->if_capenable ^= IFCAP_RSS;
break;
default:
error = EINVAL;
}
return (error);
}