#define _IP_VHL
#include "opt_inet.h"
#include "opt_ipdivert.h"
#ifndef INET
#error "IPDIVERT requires INET."
#endif
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/socketvar.h>
#include <sys/socketvar2.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/in_cksum.h>
#include <sys/lock.h>
#include <sys/msgport.h>
#include <net/if.h>
#include <net/route.h>
#include <net/netmsg2.h>
#include <net/netisr2.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet/ip_divert.h>
#define DIVSNDQ (65536 + 100)
#define DIVRCVQ (65536 + 100)
#define DIV_IS_OUTPUT(sin) ((sin) == NULL || (sin)->sin_addr.s_addr == 0)
#define DIV_OUTPUT 0x10000
#define DIV_INPUT 0x20000
static struct inpcbinfo divcbinfo;
static struct inpcbportinfo divcbportinfo;
static u_long div_sendspace = DIVSNDQ;
static u_long div_recvspace = DIVRCVQ;
static struct mbuf *ip_divert(struct mbuf *, int, int);
void
div_init(void)
{
in_pcbinfo_init(&divcbinfo, 0, FALSE);
in_pcbportinfo_init(&divcbportinfo, 1, 0);
divcbinfo.hashbase = hashinit(1, M_PCB, &divcbinfo.hashmask);
in_pcbportinfo_set(&divcbinfo, &divcbportinfo, 1);
divcbinfo.wildcardhashbase = hashinit(1, M_PCB,
&divcbinfo.wildcardhashmask);
divcbinfo.ipi_size = sizeof(struct inpcb);
ip_divert_p = ip_divert;
}
int
div_input(struct mbuf **mp, int *offp, int proto)
{
struct mbuf *m = *mp;
ipstat.ips_noproto++;
m_freem(m);
return(IPPROTO_DONE);
}
static void
div_packet(struct mbuf *m, int incoming, int port)
{
struct sockaddr_in divsrc = { sizeof divsrc, AF_INET };
struct inpcb *inp;
struct socket *sa;
struct m_tag *mtag;
struct divert_info *divinfo;
u_int16_t nport;
ASSERT_NETISR0;
mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL);
divinfo = m_tag_data(mtag);
divsrc.sin_port = divinfo->skipto;
divsrc.sin_addr.s_addr = 0;
if (incoming) {
struct ifaddr_container *ifac;
TAILQ_FOREACH(ifac, &m->m_pkthdr.rcvif->if_addrheads[mycpuid],
ifa_link) {
struct ifaddr *ifa = ifac->ifa;
if (ifa->ifa_addr == NULL)
continue;
if (ifa->ifa_addr->sa_family != AF_INET)
continue;
divsrc.sin_addr =
((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
break;
}
}
if (m->m_pkthdr.rcvif) {
ksnprintf(divsrc.sin_zero, sizeof divsrc.sin_zero, "%s",
m->m_pkthdr.rcvif->if_xname);
}
sa = NULL;
nport = htons((u_int16_t)port);
LIST_FOREACH(inp, &divcbinfo.pcblisthead, inp_list) {
if (inp->inp_flags & INP_PLACEMARKER)
continue;
if (inp->inp_lport == nport)
sa = inp->inp_socket;
}
if (sa) {
lwkt_gettoken(&sa->so_rcv.ssb_token);
if (ssb_appendaddr(&sa->so_rcv, (struct sockaddr *)&divsrc, m, NULL) == 0) {
m_freem(m);
soroverflow(sa);
} else
sorwakeup(sa);
lwkt_reltoken(&sa->so_rcv.ssb_token);
} else {
m_freem(m);
ipstat.ips_noproto++;
ipstat.ips_delivered--;
}
}
static void
div_packet_handler(netmsg_t msg)
{
struct mbuf *m;
int port, incoming = 0;
m = msg->packet.nm_packet;
port = msg->lmsg.u.ms_result32 & 0xffff;
if (msg->lmsg.u.ms_result32 & DIV_INPUT)
incoming = 1;
div_packet(m, incoming, port);
}
static void
divert_packet(struct mbuf *m, int incoming)
{
struct m_tag *mtag;
struct divert_info *divinfo;
int port;
M_ASSERTPKTHDR(m);
if (m->m_len < sizeof(struct ip) &&
(m = m_pullup(m, sizeof(struct ip))) == NULL)
return;
mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL);
KASSERT(mtag != NULL, ("%s no divert tag!", __func__));
divinfo = m_tag_data(mtag);
port = divinfo->port;
KASSERT(port != 0, ("%s: port=0", __func__));
if (mycpuid != 0) {
struct netmsg_packet *nmp;
nmp = &m->m_hdr.mh_netmsg;
netmsg_init(&nmp->base, NULL, &netisr_apanic_rport,
0, div_packet_handler);
nmp->nm_packet = m;
nmp->base.lmsg.u.ms_result32 = port;
if (incoming)
nmp->base.lmsg.u.ms_result32 |= DIV_INPUT;
else
nmp->base.lmsg.u.ms_result32 |= DIV_OUTPUT;
lwkt_sendmsg(netisr_cpuport(0), &nmp->base.lmsg);
} else {
div_packet(m, incoming, port);
}
}
static int
div_output(struct socket *so, struct mbuf *m,
struct sockaddr_in *sin, struct mbuf *control)
{
int error = 0;
struct m_tag *mtag;
struct divert_info *divinfo;
ASSERT_NETISR0;
if (control)
m_freem(control);
mtag = m_tag_get(PACKET_TAG_IPFW_DIVERT, sizeof(*divinfo), M_NOWAIT);
if (mtag == NULL) {
error = ENOBUFS;
goto cantsend;
}
m_tag_prepend(m, mtag);
divinfo = m_tag_data(mtag);
if (sin)
divinfo->skipto = sin->sin_port;
else
divinfo->skipto = 0;
if (DIV_IS_OUTPUT(sin)) {
struct ip *const ip = mtod(m, struct ip *);
if ((u_short)ntohs(ip->ip_len) > m->m_pkthdr.len) {
error = EINVAL;
goto cantsend;
}
ipstat.ips_rawout++;
error = ip_output(m, NULL, NULL,
(so->so_options & SO_DONTROUTE) |
IP_ALLOWBROADCAST | IP_RAWOUTPUT,
NULL, NULL);
} else {
ip_input(m);
}
return error;
cantsend:
m_freem(m);
return error;
}
static void
div_attach(netmsg_t msg)
{
struct socket *so = msg->attach.base.nm_so;
int proto = msg->attach.nm_proto;
struct pru_attach_info *ai = msg->attach.nm_ai;
struct inpcb *inp;
int error;
ASSERT_NETISR0;
inp = so->so_pcb;
if (inp)
panic("div_attach");
error = caps_priv_check(ai->p_ucred, SYSCAP_RESTRICTEDROOT |
__SYSCAP_NULLCRED);
if (error)
goto out;
error = soreserve(so, div_sendspace, div_recvspace, ai->sb_rlimit);
if (error)
goto out;
error = in_pcballoc(so, &divcbinfo);
if (error)
goto out;
inp = (struct inpcb *)so->so_pcb;
inp->inp_ip_p = proto;
inp->inp_flags |= INP_HDRINCL;
sosetstate(so, SS_ISCONNECTED);
error = 0;
out:
lwkt_replymsg(&msg->attach.base.lmsg, error);
}
static void
div_detach(netmsg_t msg)
{
struct socket *so = msg->detach.base.nm_so;
struct inpcb *inp;
ASSERT_NETISR0;
inp = so->so_pcb;
if (inp == NULL)
panic("div_detach");
in_pcbdetach(inp);
lwkt_replymsg(&msg->detach.base.lmsg, 0);
}
static void
div_abort(netmsg_t msg)
{
panic("div_abort is called");
}
static void
div_disconnect(netmsg_t msg)
{
struct socket *so = msg->disconnect.base.nm_so;
int error;
ASSERT_NETISR0;
if (so->so_state & SS_ISCONNECTED) {
soisdisconnected(so);
error = 0;
} else {
error = ENOTCONN;
}
lwkt_replymsg(&msg->disconnect.base.lmsg, error);
}
static void
div_bind(netmsg_t msg)
{
struct socket *so = msg->bind.base.nm_so;
struct sockaddr *nam = msg->bind.nm_nam;
int error;
ASSERT_NETISR0;
if (nam->sa_family != AF_INET) {
error = EAFNOSUPPORT;
} else {
((struct sockaddr_in *)nam)->sin_addr.s_addr = INADDR_ANY;
error = in_pcbbind(so->so_pcb, nam, msg->bind.nm_td);
}
lwkt_replymsg(&msg->bind.base.lmsg, error);
}
static void
div_shutdown(netmsg_t msg)
{
struct socket *so = msg->shutdown.base.nm_so;
ASSERT_NETISR0;
socantsendmore(so);
lwkt_replymsg(&msg->shutdown.base.lmsg, 0);
}
static void
div_send(netmsg_t msg)
{
struct socket *so = msg->send.base.nm_so;
struct mbuf *m = msg->send.nm_m;
struct sockaddr *nam = msg->send.nm_addr;
struct mbuf *control = msg->send.nm_control;
int error;
KASSERT(m->m_len >= sizeof(struct ip), ("IP header not in one mbuf"));
error = div_output(so, m, (struct sockaddr_in *)nam, control);
lwkt_replymsg(&msg->send.base.lmsg, error);
}
SYSCTL_DECL(_net_inet_divert);
SYSCTL_PROC(_net_inet_divert, OID_AUTO, pcblist, CTLFLAG_RD, &divcbinfo, 1,
in_pcblist_range, "S,xinpcb", "List of active divert sockets");
struct pr_usrreqs div_usrreqs = {
.pru_abort = div_abort,
.pru_accept = pr_generic_notsupp,
.pru_attach = div_attach,
.pru_bind = div_bind,
.pru_connect = pr_generic_notsupp,
.pru_connect2 = pr_generic_notsupp,
.pru_control = in_control_dispatch,
.pru_detach = div_detach,
.pru_disconnect = div_disconnect,
.pru_listen = pr_generic_notsupp,
.pru_peeraddr = in_setpeeraddr_dispatch,
.pru_rcvd = pr_generic_notsupp,
.pru_rcvoob = pr_generic_notsupp,
.pru_send = div_send,
.pru_sense = pru_sense_null,
.pru_shutdown = div_shutdown,
.pru_sockaddr = in_setsockaddr_dispatch,
.pru_sosend = sosend,
.pru_soreceive = soreceive
};
static struct mbuf *
ip_divert_out(struct mbuf *m, int tee)
{
struct mbuf *clone = NULL;
if (tee)
clone = m_dup(m, M_NOWAIT);
if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
in_delayed_cksum(m);
m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
}
divert_packet(m, 0);
return clone;
}
static struct mbuf *
ip_divert_in(struct mbuf *m, int tee)
{
struct mbuf *clone = NULL;
struct ip *ip = mtod(m, struct ip *);
struct m_tag *mtag;
if (ip->ip_off & htons(IP_MF | IP_OFFMASK)) {
const struct divert_info *divinfo;
u_short frag_off;
int hlen;
frag_off = ntohs(ip->ip_off) << 3;
if (frag_off != 0) {
mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL);
m_tag_delete(m, mtag);
}
m = ip_reass(m);
if (m == NULL)
return NULL;
ip = mtod(m, struct ip *);
m->m_pkthdr.fw_flags |= FW_MBUF_REDISPATCH;
hlen = IP_VHL_HL(ip->ip_vhl) << 2;
ip->ip_len = htons(ntohs(ip->ip_len) + hlen);
ip->ip_sum = 0;
if (hlen == sizeof(struct ip))
ip->ip_sum = in_cksum_hdr(ip);
else
ip->ip_sum = in_cksum(m, hlen);
mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL);
if (mtag == NULL) {
kprintf("ip_input no divert info\n");
m_freem(m);
return NULL;
}
divinfo = m_tag_data(mtag);
tee = divinfo->tee;
}
if (tee)
clone = m_dup(m, M_NOWAIT);
divert_packet(m, 1);
m = NULL;
ip = NULL;
ipstat.ips_delivered++;
if (clone != NULL) {
mtag = m_tag_find(clone, PACKET_TAG_IPFW_DIVERT, NULL);
KKASSERT(mtag != NULL);
m_tag_delete(clone, mtag);
}
return clone;
}
static struct mbuf *
ip_divert(struct mbuf *m, int tee, int incoming)
{
struct mbuf *ret;
if (incoming)
ret = ip_divert_in(m, tee);
else
ret = ip_divert_out(m, tee);
return ret;
}