From 5de6083d9544973e02182ffc88f89b733615aeb5 Mon Sep 17 00:00:00 2001 From: Philippe Houdoin Date: Thu, 29 Aug 2002 08:07:10 +0000 Subject: [PATCH] Network stack kernel modules moved to /current tree. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@934 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/add-ons/kernel/network/Jamfile | 6 + src/add-ons/kernel/network/core/Jamfile | 2 + src/add-ons/kernel/network/core/cksum.c | 121 ++ src/add-ons/kernel/network/core/core.c | 1039 ++++++++++ src/add-ons/kernel/network/core/if.c | 429 ++++ src/add-ons/kernel/network/core/ifq.c | 53 + src/add-ons/kernel/network/core/in.c | 480 +++++ src/add-ons/kernel/network/core/inpcb.c | 464 +++++ src/add-ons/kernel/network/core/mbuf.c | 522 +++++ src/add-ons/kernel/network/core/misc.c | 68 + src/add-ons/kernel/network/core/net_timer.c | 229 +++ src/add-ons/kernel/network/core/nhash.c | 150 ++ src/add-ons/kernel/network/core/pools.c | 324 ++++ src/add-ons/kernel/network/core/radix.c | 1092 +++++++++++ src/add-ons/kernel/network/core/route.c | 427 ++++ src/add-ons/kernel/network/core/sockbuf.c | 307 +++ src/add-ons/kernel/network/core/socket.c | 1564 +++++++++++++++ src/add-ons/kernel/network/interfaces/Jamfile | 5 + .../network/interfaces/ethernet/Jamfile | 2 + .../network/interfaces/ethernet/ethernet.c | 938 +++++++++ .../network/interfaces/loopback/Jamfile | 2 + .../network/interfaces/loopback/loopback.c | 178 ++ src/add-ons/kernel/network/protocols/Jamfile | 8 + .../kernel/network/protocols/icmp/Jamfile | 2 + .../kernel/network/protocols/icmp/icmp.c | 503 +++++ .../kernel/network/protocols/ipv4/Jamfile | 2 + .../kernel/network/protocols/ipv4/ipv4.c | 1266 ++++++++++++ .../kernel/network/protocols/raw/raw.c | 378 ++++ .../kernel/network/protocols/tcp/arc4random.c | 146 ++ .../kernel/network/protocols/tcp/tcp.c | 600 ++++++ .../kernel/network/protocols/tcp/tcp_debug.c | 209 ++ .../kernel/network/protocols/tcp/tcp_input.c | 1728 +++++++++++++++++ .../kernel/network/protocols/tcp/tcp_output.c | 454 +++++ .../kernel/network/protocols/tcp/tcp_timer.c | 182 ++ .../kernel/network/protocols/udp/udp.c | 449 +++++ 35 files changed, 14329 insertions(+) create mode 100644 src/add-ons/kernel/network/Jamfile create mode 100644 src/add-ons/kernel/network/core/Jamfile create mode 100644 src/add-ons/kernel/network/core/cksum.c create mode 100644 src/add-ons/kernel/network/core/core.c create mode 100644 src/add-ons/kernel/network/core/if.c create mode 100644 src/add-ons/kernel/network/core/ifq.c create mode 100644 src/add-ons/kernel/network/core/in.c create mode 100644 src/add-ons/kernel/network/core/inpcb.c create mode 100644 src/add-ons/kernel/network/core/mbuf.c create mode 100644 src/add-ons/kernel/network/core/misc.c create mode 100644 src/add-ons/kernel/network/core/net_timer.c create mode 100644 src/add-ons/kernel/network/core/nhash.c create mode 100644 src/add-ons/kernel/network/core/pools.c create mode 100644 src/add-ons/kernel/network/core/radix.c create mode 100644 src/add-ons/kernel/network/core/route.c create mode 100644 src/add-ons/kernel/network/core/sockbuf.c create mode 100644 src/add-ons/kernel/network/core/socket.c create mode 100644 src/add-ons/kernel/network/interfaces/Jamfile create mode 100644 src/add-ons/kernel/network/interfaces/ethernet/Jamfile create mode 100644 src/add-ons/kernel/network/interfaces/ethernet/ethernet.c create mode 100644 src/add-ons/kernel/network/interfaces/loopback/Jamfile create mode 100644 src/add-ons/kernel/network/interfaces/loopback/loopback.c create mode 100644 src/add-ons/kernel/network/protocols/Jamfile create mode 100644 src/add-ons/kernel/network/protocols/icmp/Jamfile create mode 100644 src/add-ons/kernel/network/protocols/icmp/icmp.c create mode 100644 src/add-ons/kernel/network/protocols/ipv4/Jamfile create mode 100644 src/add-ons/kernel/network/protocols/ipv4/ipv4.c create mode 100644 src/add-ons/kernel/network/protocols/raw/raw.c create mode 100644 src/add-ons/kernel/network/protocols/tcp/arc4random.c create mode 100644 src/add-ons/kernel/network/protocols/tcp/tcp.c create mode 100644 src/add-ons/kernel/network/protocols/tcp/tcp_debug.c create mode 100644 src/add-ons/kernel/network/protocols/tcp/tcp_input.c create mode 100644 src/add-ons/kernel/network/protocols/tcp/tcp_output.c create mode 100644 src/add-ons/kernel/network/protocols/tcp/tcp_timer.c create mode 100644 src/add-ons/kernel/network/protocols/udp/udp.c diff --git a/src/add-ons/kernel/network/Jamfile b/src/add-ons/kernel/network/Jamfile new file mode 100644 index 0000000000..fa6c85a771 --- /dev/null +++ b/src/add-ons/kernel/network/Jamfile @@ -0,0 +1,6 @@ +SubDir OBOS_TOP src add-ons kernel network ; + +SubInclude OBOS_TOP src add-ons kernel network core ; +SubInclude OBOS_TOP src add-ons kernel network interfaces ; +SubInclude OBOS_TOP src add-ons kernel network protocols ; + diff --git a/src/add-ons/kernel/network/core/Jamfile b/src/add-ons/kernel/network/core/Jamfile new file mode 100644 index 0000000000..6be8bc5e2c --- /dev/null +++ b/src/add-ons/kernel/network/core/Jamfile @@ -0,0 +1,2 @@ +SubDir OBOS_TOP src add-ons kernel network core ; + diff --git a/src/add-ons/kernel/network/core/cksum.c b/src/add-ons/kernel/network/core/cksum.c new file mode 100644 index 0000000000..e15addfae5 --- /dev/null +++ b/src/add-ons/kernel/network/core/cksum.c @@ -0,0 +1,121 @@ +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "net_misc.h" + +/* This from Stevens Vol.2 */ +#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x) +#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1];ADDCARRY(sum);} + +uint16 in_cksum(struct mbuf *m, int len, int off) +{ + uint16 *w; + int sum = 0; + int mlen = 0; + int byte_swapped = 0; + struct mbuf *orig_m = m; + + union { + uint8 c[2]; + uint16 s; + } s_util; + union { + uint16 s[2]; + uint32 l; + } l_util; + + if (off) { + m->m_len -= off; + m->m_data += off; + if (m->m_flags & M_PKTHDR) + m->m_pkthdr.len -= off; + } + + for (; m && len; m=m->m_next) { + if (m->m_len == 0) + continue; + w = mtod(m, uint16 *); + if (mlen == -1) { + /* first byte is a continuation of + * a 16 bit word spanning this mbuf + * and the previous one. + * + * s_util.c[0] is already saved. + */ + s_util.c[1] = *(char*)w; + sum += s_util.s; + w = (uint16*) ((char*) w + 1); + mlen = m->m_len - 1; + len--; + } else + mlen = m->m_len; + if (len < mlen) + mlen = len; + len -= mlen; + /* force to even boundry */ + if ((1 & (int)w) && (mlen > 0)) { + REDUCE; + sum <<= 8; + s_util.c[0] = *(char*)w; + w = (uint16*)((char*)w + 1); + mlen--; + byte_swapped = 1; + } + /* unroll the loop to make overhead from branches + * &c small. + */ + while ((mlen -= 32) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7]; + sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11]; + sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15]; + + w += 16; + } + mlen += 32; + while ((mlen -= 8) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + + w += 4; + } + mlen += 8; + if (mlen == 0 && byte_swapped == 0) + continue; + REDUCE; + while ((mlen -= 2) >= 0) { + sum += *w++; + } + if (byte_swapped) { + REDUCE; + sum <<= 8; + byte_swapped = 0; + if (mlen == -1) { + s_util.c[1] = *(char*)w; + sum += s_util.s; + mlen = 0; + } else + mlen = -1; + } else if (mlen == -1) + s_util.c[0] = *(char*)w; + } + if (len) + printf("cksum: out of data!\n"); + if (mlen == -1) { + /* last mbuf was an odd number of bytes! */ + s_util.c[1] = 0; + sum += s_util.s; + } + REDUCE; + + if (off) { + orig_m->m_len += off; + orig_m->m_data -= off; + if (orig_m->m_flags & M_PKTHDR) + orig_m->m_pkthdr.len += off; + } + return (uint16)(~sum & 0xffff); +} + diff --git a/src/add-ons/kernel/network/core/core.c b/src/add-ons/kernel/network/core/core.c new file mode 100644 index 0000000000..252796b1e2 --- /dev/null +++ b/src/add-ons/kernel/network/core/core.c @@ -0,0 +1,1039 @@ +/* net_server.c */ + +/* this is just a hack to get us an application that + * we can develop while we continue to develop the net stack + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _KERNEL_ +#include +#include +#include +#define spawn_thread spawn_kernel_thread +#endif + +#include + +#include "core_module.h" +#include "sys/socket.h" +#include "sys/socketvar.h" +#include "net/if.h" /* for ifnet definition */ +#include "net_server/net_server.h" +#include "protocols.h" +#include "net_module.h" +#include "net_timer.h" +#include "net_misc.h" +#include "nhash.h" +#include "netinet/in_var.h" +#include "netinet/in_pcb.h" +#include "sys/domain.h" +#include "sys/protosw.h" +#include "net/route.h" +#include "net_malloc.h" +#include "net/if_arp.h" +#include "netinet/if_ether.h" + +/* Defines we need */ +#define NETWORK_INTERFACES "network/interface" +#define NETWORK_PROTOCOLS "network/protocol" +#define PPP_DEVICES "ppp/devices" + +/* Variables used in other core modules */ +int ndevs = 0; + +/* Static variables, used only in this file */ +struct ifnet *devices = NULL; +struct ifnet *pdevices = NULL; +static sem_id dev_lock = -1; +static sem_id proto_lock = -1; +static sem_id dom_lock = -1; +static int timer_on = 0; +static int init_done = 0; + +/* Forward prototypes... */ +/* Private for this file */ +#ifdef _KERNEL_ +static status_t core_std_ops(int32 op, ...); +#else +#define core_std_ops NULL +#endif +static int start_stack(void); +static int stop_stack(void); +static void add_protosw(struct protosw *[], int layer); +static struct net_module *module_list = NULL; +/* Wider scoped prototypes */ +int net_sysctl(int *name, uint namelen, void *oldp, size_t *oldlenp, + void *newp, size_t newlen); + +_EXPORT struct core_module_info core_info = { + { + CORE_MODULE_PATH, + B_KEEP_LOADED, + core_std_ops + }, + + start_stack, + stop_stack, + add_domain, + remove_domain, + add_protocol, + remove_protocol, + add_protosw, + start_rx_thread, + start_tx_thread, + + net_add_timer, + net_remove_timer, + + start_ifq, + stop_ifq, + + pool_init, + pool_get, + pool_put, + pool_destroy, + + sonewconn, + soreserve, + sbreserve, + sbappend, + sbappendaddr, + sbdrop, + sbflush, + sowakeup, + soisconnected, + soisconnecting, + soisdisconnected, + soisdisconnecting, + sohasoutofband, + socantrcvmore, + socantsendmore, + + in_pcballoc, + in_pcbdetach, + in_pcbbind, + in_pcbconnect, + in_pcbdisconnect, + in_pcblookup, + in_control, + in_losing, + in_canforward, + in_localaddr, + in_pcbrtentry, + in_setsockaddr, + in_setpeeraddr, + in_pcbnotify, + inetctlerr, + + m_gethdr, + m_get, + m_cat, + m_adj, + m_prepend, + m_pullup, + m_copyback, + m_copydata, + m_copym, + m_free, + m_freem, + m_reserve, + m_devget, + + net_server_add_device, + get_interfaces, + in_broadcast, + + rtalloc, + rtalloc1, + rtfree, + rtrequest, + rn_addmask, + rn_head_search, + get_rt_tables, + rt_setgate, + + ifa_ifwithdstaddr, + ifa_ifwithnet, + if_attach, + if_detach, + ifa_ifwithaddr, + ifa_ifwithroute, + ifaof_ifpforaddr, + ifafree, + + get_primary_addr, + + initsocket, + socreate, + soclose, + sobind, + solisten, + soconnect, + recvit, + sendit, + soo_ioctl, + net_sysctl, + writeit, + readit, + sosetopt, + sogetopt, + set_socket_event_callback, + sogetpeername, + sogetsockname, + soaccept +}; + +static int32 if_thread(void *data) +{ + struct ifnet *i = (struct ifnet *)data; + status_t status; + char buffer[ETHER_MAX_LEN]; + size_t len = ETHER_MAX_LEN; + + while ((status = read(i->devid, buffer, len)) >= B_OK) { + struct mbuf *mb = m_devget(buffer, status, 0, i, NULL); + if (!(i->if_flags & IFF_UP)) + break; + IFQ_ENQUEUE(i->devq, mb); + atomic_add(&i->if_ipackets, 1); + len = ETHER_MAX_LEN; + } + printf("%s: terminating if_thread\n", i->if_name); + return 0; +} + +/* This is used when we don't have a dev to read/write from as we're using + * a virtual device, e.g. a loopback driver! + * + * It simply queue's the buf's and drags them off as normal in the tx thread. + */ +static int32 rx_thread(void *data) +{ + struct ifnet *i = (struct ifnet *)data; + struct mbuf *m; + + while (1) { + acquire_sem_etc(i->rxq->pop, 1, B_CAN_INTERRUPT|B_DO_NOT_RESCHEDULE, 0); + if (!(i->if_flags & IFF_UP)) + break; + IFQ_DEQUEUE(i->rxq, m); + + if (i->input) + i->input(m); + else + printf("%s: no input function!\n", i->if_name); + } + printf("%s: terminating rx_thread\n", i->if_name); + return 0; +} + +/* This is the same regardless of either method of getting the packets... */ +/* The buffer size is now a variable. Why? + * MTU's vary, but they don't take into account the size of possible media headers, + * so while an ethernet cards mtu is 1500, a valid packet can be up to 1514, mtu + if_hdrlen! + * However, loopback MTU is 16k, so our previous 2k buffer was too small for loopback and wasteful + * for ethernet cards. So, now we make it flexible and correctly sized. + */ +static int32 tx_thread(void *data) +{ + struct ifnet *i = (struct ifnet *)data; + struct mbuf *m; + char *buffer = malloc(i->if_mtu + i->if_hdrlen); //--- mwcc doesn't allow dynamically sized arrays + size_t len = 0, maxlen = i->if_mtu + i->if_hdrlen; + status_t status; +#if SHOW_DEBUG + int txc = 0; +#endif + + while (1) { + acquire_sem_etc(i->txq->pop,1,B_CAN_INTERRUPT|B_DO_NOT_RESCHEDULE, 0); + if (!(i->if_flags & IFF_UP)) + break; + + IFQ_DEQUEUE(i->txq, m); + + if (m) { + if (m->m_flags & M_PKTHDR) + len = m->m_pkthdr.len; + else + len = m->m_len; + + if (len > maxlen) { + printf("%s: tx_thread: packet was too big (%ld bytes vs max size of %ld)!\n", i->if_name, + len, maxlen); + } else { + m_copydata(m, 0, len, buffer); +#if SHOW_DEBUG + dump_buffer(buffer, len); +#endif + status = write(i->devid, buffer, len); + if (status < B_OK) { + printf("Error sending data [%s]!\n", strerror(status)); + /* ??? - should we exit at this point? */ + } + } + m_freem(m); + } + } + printf("%s: terminating tx_thread\n", i->if_name); + free(buffer); + return 0; +} + +/* Start an RX thread and an RX queue if reqd */ +void start_rx_thread(struct ifnet *dev) +{ + int32 priority = B_NORMAL_PRIORITY; + char name[B_OS_NAME_LENGTH]; /* 32 */ + sprintf(name, "%s_rx_thread", dev->if_name); + + if (dev->if_type != IFT_ETHER) { + if (!dev->rxq) + dev->rxq = start_ifq(); + if (!dev->rxq) + return; + dev->rx_thread = spawn_thread(rx_thread, name, + priority, dev); + } else { + /* don't need an rxq... */ + dev->rx_thread = spawn_thread(if_thread, name, + priority, dev); + } + + if (dev->rx_thread < 0) { + printf("Failed to start the rx_thread for %s\n", dev->if_name); + dev->rx_thread = -1; + return; + } + resume_thread(dev->rx_thread); +} + +/* Start a TX thread and a TX queue */ +void start_tx_thread(struct ifnet *dev) +{ + int32 priority = B_NORMAL_PRIORITY; + char name[B_OS_NAME_LENGTH]; /* 32 */ + if (!dev->txq) + dev->txq = start_ifq(); + if (!dev->txq) + return; + + sprintf(name, "%s_tx_thread", dev->if_name); + dev->tx_thread = spawn_thread(tx_thread, name, priority, dev); + if (dev->tx_thread < 0) { + printf("Failed to start the tx_thread for %s\n", dev->if_name); + dev->tx_thread = -1; + return; + } + resume_thread(dev->tx_thread); +} + +void net_server_add_device(struct ifnet *ifn) +{ + char dname[16]; + + if (!ifn) + return; + + sprintf(dname, "%s%d", ifn->name, ifn->if_unit); + ifn->if_name = strdup(dname); + + if (ifn->if_type != IFT_ETHER) { + /* pseudo device... */ + if (pdevices) + ifn->if_next = pdevices; + else + ifn->if_next = NULL; + pdevices = ifn; + } else { + if (devices) + ifn->if_next = devices; + else + ifn->if_next = NULL; + ifn->id = ndevs++; + devices = ifn; + } +} + +/* +static void merge_devices(void) +{ + struct ifnet *d = NULL; + + if (!devices && !pdevices) { + printf("No devices!\n"); + return; + } + + acquire_sem(dev_lock); + if (devices) { + for (d = devices; d->if_next != NULL; d = d->if_next) { + continue; + } + } + if (pdevices) { + if (d) { + d->if_next = pdevices; + d = d->if_next; + } else { + devices = pdevices; + d = devices; + } + while (d) { + d->id = ndevs++; + d = d->if_next; + } + } + release_sem(dev_lock); +} +*/ + +/* Hmm, we should do this via the ioctl for the device/module to tell it that + * the card is going down... + * XXX - implement this correctly. + */ +static void close_devices(void) +{ + struct ifnet *d = devices; + while (d) { + d->if_flags &= ~IFF_UP; + kill_thread(d->rx_thread); + kill_thread(d->tx_thread); + close(d->devid); + d = d->if_next; + } + devices = NULL; +} + +static struct domain af_inet_domain = { + AF_INET, + "internet", + NULL, + NULL, + NULL, + rn_inithead, + 32, + sizeof(struct sockaddr_in) +}; + +/* Domain support */ +void add_domain(struct domain *dom, int fam) +{ + struct domain *dm = domains; + struct domain *ndm; + + for(; dm; dm = dm->dom_next) { + if (dm->dom_family == fam) + /* already done */ + return; + } + + acquire_sem_etc(dom_lock, 1, B_CAN_INTERRUPT, 0); + if (dom == NULL) { + /* we're trying to add a builtin domain! */ + + switch (fam) { + case AF_INET: + /* ok, add it... */ + ndm = (struct domain*)malloc(sizeof(*ndm)); + *ndm = af_inet_domain; + if (dm) + dm->dom_next = ndm; + else + domains = ndm; + /* avoids too many release_sem_etc... */ + goto domain_done; + default: + printf("Don't know how to add domain %d\n", fam); + } + } else { + /* find the end of the chain and add ourselves there */ + for (dm = domains; dm->dom_next;dm = dm->dom_next) + continue; + if (dm) + dm->dom_next = dom; + else + domains = dom; + } +domain_done: + release_sem_etc(dom_lock, 1, B_CAN_INTERRUPT); + return; +} + +#ifdef SHOW_DEBUG +void walk_protocols(void) +{ + struct protosw *pr = protocols; + printf("Protocols:\n"); + for (;pr;pr = pr->pr_next) { + printf(" Protocol %s (%p)->(%p)\n", pr->name, pr, pr->pr_next); + } + printf("End of Protocol list\n"); +} +#endif + +void add_protocol(struct protosw *pr, int fam) +{ + struct protosw *psw = protocols; + struct domain *dm = domains; + + /* first find the correct domain... */ + for (;dm; dm = dm->dom_next) { + if (dm->dom_family == fam) + break; + } + + if (dm == NULL) { + printf("Unable to add protocol due to no domain available!\n"); + return; + } + + acquire_sem_etc(proto_lock, 1, B_CAN_INTERRUPT, 0); + /* OK, we can add it... */ + for (;psw;psw = psw->pr_next) { + if (psw->pr_type == pr->pr_type && + psw->pr_protocol == pr->pr_protocol && + psw->pr_domain == dm) { + release_sem_etc(proto_lock, 1, B_CAN_INTERRUPT); + printf("Duplicate protocol detected (%s)!!\n", pr->name); + return; + } + } + + pr->pr_next = NULL; + pr->dom_next = NULL; + pr->pr_domain = NULL; + /* find last entry in protocols list */ + if (protocols) { + for (psw = protocols;psw->pr_next; psw = psw->pr_next) + continue; + psw->pr_next = pr; + } else + protocols = pr; + + release_sem_etc(proto_lock, 1, B_CAN_INTERRUPT); + + pr->pr_domain = dm; + + /* Now add to domain */ + acquire_sem_etc(dom_lock, 1, B_CAN_INTERRUPT, 0); + if (dm->dom_protosw) { + psw = dm->dom_protosw; + for (;psw->dom_next;psw = psw->dom_next) + continue; + psw->dom_next = pr; + } else + dm->dom_protosw = pr; + release_sem_etc(dom_lock, 1, B_CAN_INTERRUPT); + return; +} + +void remove_protocol(struct protosw *pr) +{ + struct protosw *psw = protocols, *opr = NULL; + struct domain *dm; + + acquire_sem_etc(proto_lock, 1, B_CAN_INTERRUPT, 0); + for (;psw;psw = psw->pr_next) { + if (psw == pr) { + if (opr) { + opr->pr_next = psw->pr_next; + } else { + /* first entry */ + protocols = psw->pr_next; + } + break; + } + opr = psw; + } + pr->pr_next = NULL; + + dm = pr->pr_domain; + opr = NULL; + + acquire_sem_etc(dom_lock, 1, B_CAN_INTERRUPT, 0); + + for (psw = dm->dom_protosw; psw; psw = psw->dom_next) { + if (psw == pr) { + if (opr) + opr->dom_next = psw->dom_next; + else + dm->dom_protosw = psw->dom_next; + break; + } + opr = psw; + } + + release_sem_etc(dom_lock, 1, B_CAN_INTERRUPT); + release_sem_etc(proto_lock, 1, B_CAN_INTERRUPT); + + pr->dom_next = NULL; + pr->pr_domain = NULL; +} + +static void walk_domains(void) +{ + struct domain *d; + struct protosw *p; + + for (d = domains;d;d = d->dom_next) { + p = d->dom_protosw; + for (;p;p = p->dom_next) { + printf("\t%s provided by %s\n", p->name, p->mod_path); + } + } +} + +void remove_domain(int fam) +{ + struct domain *dmp = domains, *opr; + + for (; dmp; dmp = dmp->dom_next) { + if (dmp->dom_family == fam) + break; + opr = dmp; + } + if (!dmp || dmp->dom_protosw != NULL) + return; + + acquire_sem_etc(dom_lock, 1, B_CAN_INTERRUPT, 0); + /* we're ok to remove it! */ + if (opr) + opr->dom_next = dmp->dom_next; + else + domains = dmp->dom_next; + dmp->dom_next = NULL; + if (dmp->dom_family == AF_INET) + free(dmp); + release_sem_etc(dom_lock, 1, B_CAN_INTERRUPT); + + return; +} + +static void add_protosw(struct protosw *prt[], int layer) +{ + struct protosw *p; + + for (p = protocols; p; p = p->pr_next) { + if (p->layer == layer) + prt[p->pr_protocol] = p; + if (layer == NET_LAYER3 && p->layer == NET_LAYER2) + prt[p->pr_protocol] = p; + if (layer == NET_LAYER1 && p->layer == NET_LAYER2) + prt[p->pr_protocol] = p; + if (layer == NET_LAYER2 && p->layer == NET_LAYER3) + prt[p->pr_protocol] = p; + } +} + +static void domain_init(void) +{ + struct domain *d; + struct protosw *p; + + for (d = domains;d;d = d->dom_next) { + if (d->dom_init) + d->dom_init(); + + for (p = d->dom_protosw;p;p = p->dom_next) { + if (p->pr_init) + p->pr_init(); + } + } +} + + +/* Add protocol modules. Each module is loaded and this triggers + * the init routine which should call the add_domain and add_protocol + * functions to make sure we know what it does! + * NB these don't have any additional functions so we just use the + * system defined module_info structures + */ +#ifdef _KERNEL_ +static void find_protocol_modules(void) +{ + void *ml = open_module_list(NETWORK_PROTOCOLS); + size_t sz = B_PATH_NAME_LENGTH; + char name[sz]; + struct net_module *nm = NULL; + int rv; + + if (ml == NULL) { + printf("failed to open the %s directory\n", + NETWORK_PROTOCOLS); + return; + } + + while (read_next_module_name(ml, name, &sz) == B_OK) { + nm = (struct net_module *)malloc(sizeof(struct net_module)); + if (!nm) + return; + memset(nm, 0, sizeof(*nm)); + nm->name = strdup(name); + printf("module: %s\n", name); + rv = get_module(name, (module_info **)&nm->ptr); + if (rv == 0) { + nm->next = module_list; + module_list = nm; + } else { + free(nm); + } + sz = B_PATH_NAME_LENGTH; + } + + close_module_list(ml); + + /* we call these here as we want to load all modules ourselves first + */ + for (nm = module_list; nm; nm = nm->next) { + nm->ptr->start(NULL); + nm->status = 1; + } +} + + +/* This is a little misnamed. This goes through and tries to + * load all the interface modules it finds and calls each one's + * init function. The init functions should build a lit of devices + * that can be used and add each one to the stack. Until we run + * start_devices() they'll not do anything and other apps can use + * them (AFAIK), so this shouldn't be an issue. + */ +static void find_interface_modules(void) +{ + void *ml = open_module_list(NETWORK_INTERFACES); + size_t sz = B_PATH_NAME_LENGTH; + char name[sz]; + struct net_module *nm = NULL; + int rv; + + if (ml == NULL) { + printf("failed to open the %s directory\n", + NETWORK_INTERFACES); + return; + } + + while (read_next_module_name(ml, name, &sz) == B_OK) { + nm = (struct net_module *)malloc(sizeof(struct net_module)); + if (!nm) + return; + memset(nm, 0, sizeof(*nm)); + nm->name = strdup(name); + printf("module: %s\n", name); + rv = get_module(name, (module_info**)&nm->ptr); + if (rv == 0) { + nm->next = module_list; + module_list = nm; + nm->ptr->start(NULL); + nm->status = 1; + } else { + free(nm); + } + + sz = B_PATH_NAME_LENGTH; + } + + close_module_list(ml); +} + +#else + +static void _find_interface_modules(char *dirpath) +{ + char path[PATH_MAX]; + DIR *dir; + struct dirent *fe; + struct net_module *nm = NULL; + status_t status; + + dir = opendir(dirpath); + if (!dir) + return; + + while ((fe = readdir(dir)) != NULL) { + /* last 2 entries are only valid for development... */ + if (strcmp(fe->d_name, ".") == 0 || strcmp(fe->d_name, "..") == 0 + || strcmp(fe->d_name, ".cvsignore") == 0 + || strcmp(fe->d_name, "CVS") == 0) + continue; + sprintf(path, "%s/%s", dirpath, fe->d_name); + + nm = (struct net_module*)malloc(sizeof(struct net_module)); + if (!nm) + return; + nm->name = strdup(fe->d_name); + nm->iid = load_add_on(path); + if (nm->iid > 0) { + status = get_image_symbol(nm->iid, "device_info", + B_SYMBOL_TYPE_DATA, (void**)&nm->ptr); + if (status == B_OK) { + nm->next = module_list; + module_list = nm; + nm->ptr->start(&core_info); + nm->status = 1; + printf("\t%s\n", path); + } else { + free(nm); + } + } + } +} + +static void find_interface_modules(void) +{ + char cdir[PATH_MAX], path[PATH_MAX]; + getcwd(cdir, PATH_MAX); + sprintf(path, "%s/modules/interface", cdir); + _find_interface_modules(path); + sprintf(path, "%s/modules/ppp/devices", cdir); + _find_interface_modules(path); +} + +static void find_protocol_modules(void) +{ + char path[PATH_MAX], cdir[PATH_MAX]; + DIR *dir; + struct dirent *m; + struct net_module *nm = NULL; + status_t status; + +printf("userland: find_protocol_modules...\n"); + + getcwd(cdir, PATH_MAX); + sprintf(cdir, "%s/modules/protocol", cdir); + + dir = opendir(cdir); + if (!dir) + return; + + while ((m = readdir(dir)) != NULL) { + /* last 2 entries are only valid for development... */ + if (strcmp(m->d_name, ".") == 0 || strcmp(m->d_name, "..") == 0 + || strcmp(m->d_name, ".cvsignore") == 0 + || strcmp(m->d_name, "CVS") == 0) + continue; + /* ok so we try it... */ + sprintf(path, "%s/%s", cdir, m->d_name); + + nm = (struct net_module*)malloc(sizeof(struct net_module)); + if (!nm) + return; + nm->name = strdup(m->d_name); + nm->iid = load_add_on(path); + if (nm->iid > 0) { + status = get_image_symbol(nm->iid, "protocol_info", + B_SYMBOL_TYPE_DATA, (void**)&nm->ptr); + if (status == B_OK) { + nm->next = module_list; + module_list = nm; + nm->ptr->start(&core_info); + nm->status = 1; + } else { + free(nm); + } + } + } + + printf("\n"); +} + +#endif + +struct protosw *pffindtype(int domain, int type) +{ + struct domain *d; + struct protosw *p; + + for (d = domains; d; d = d->dom_next) { + if (d->dom_family == domain) + goto found; + } + return NULL; +found: + for (p=d->dom_protosw; p; p = p->dom_next) { + if (p->pr_type && p->pr_type == type) { + return p; + } + } + return NULL; +} + +struct protosw *pffindproto(int domain, int protocol, int type) +{ + struct domain *d; + struct protosw *p, *maybe = NULL; + + if (domain == 0) + return NULL; + + for (d = domains; d; d = d->dom_next) { + if (d->dom_family == domain) + goto found; + } + return NULL; + +found: + for (p=d->dom_protosw;p;p = p->dom_next) { + if (p->pr_protocol == protocol && p->pr_type == type) + return p; + /* deal with SOCK_RAW and AF_UNSPEC */ + if (type == SOCK_RAW && p->pr_type == SOCK_RAW && + p->pr_protocol == AF_UNSPEC && maybe == NULL) + maybe = p; + } + return maybe; +} + +int net_sysctl(int *name, uint namelen, void *oldp, size_t *oldlenp, + void *newp, size_t newlen) +{ + struct domain *dp; + struct protosw *pr; + int family, protocol; + + if (namelen < 3) { + printf("net_sysctl: EINVAL (namelen < 3, %d)\n", namelen); + return EINVAL; // EISDIR?? + } + family = name[0]; + protocol = name[1]; + + if (family == 0) + return 0; + + for (dp=domains; dp; dp= dp->dom_next) + if (dp->dom_family == family) + goto found; + return EINVAL; //EPROTOOPT; +found: + for (pr=dp->dom_protosw; pr; pr = pr->dom_next) { + if (pr->pr_protocol == protocol && pr->pr_sysctl) { + return ((*pr->pr_sysctl)(name+2, namelen -2, oldp, oldlenp, newp, newlen)); + } + } + return EINVAL;//EPROTOOPT; +} + +static int start_stack(void) +{ + if (init_done) + return 0; + + if (timer_on == 0) { + net_init_timer(); + timer_on = 1; + } + + domains = NULL; + protocols = NULL; + + find_protocol_modules(); + + walk_domains(); + + domain_init(); + + mbinit(); + sockets_init(); + inpcb_init(); + route_init(); + if_init(); + + if (dev_lock == -1) + dev_lock = create_sem(1, "device_lock"); + if (proto_lock == -1) + proto_lock = create_sem(1, "protocol_lock"); + if (dom_lock == -1) + dom_lock = create_sem(1, "domain_lock"); + +#ifdef _KERNEL_ + set_sem_owner(dev_lock, B_SYSTEM_TEAM); + set_sem_owner(proto_lock, B_SYSTEM_TEAM); + set_sem_owner(dom_lock, B_SYSTEM_TEAM); +#endif + + find_interface_modules(); + + init_done = 1; + + return 0; +} + +static int stop_stack(void) +{ + struct net_module *nm = module_list, *onm = NULL; + + printf("core network module: Stopping network stack!\n"); + + close_devices(); + + /* unload all modules... */ + printf("trying to stop modules\n"); + for (;nm; nm = nm->next) { + printf("stopping %s\n", nm->name); + if (nm->ptr->stop) + nm->ptr->stop(); + } + printf("trying to unload modules\n"); + nm = module_list; + do { +#ifdef _KERNEL_ + put_module(nm->name); +#else + unload_add_on(nm->iid); +#endif + onm = nm; + nm = nm->next; + onm->next = NULL; + free(onm); + } while (nm); + + /* This is more likely the correct place for this... + * can't call it at the moment due to the way we call this function! + sockets_shutdown(); + */ + + delete_sem(dev_lock); + delete_sem(proto_lock); + delete_sem(dom_lock); + dev_lock = proto_lock = dom_lock = -1; + init_done = 0; + module_list = NULL; + ndevs = 0; + + return 0; +} + +#ifdef _KERNEL_ +static status_t core_std_ops(int32 op, ...) +{ + switch(op) { + case B_MODULE_INIT: + load_driver_symbols("core"); + break; + case B_MODULE_UNINIT: + // the stack is keeping loaded, so don't stop it + printf("core: B_MODULE_UNINIT\n"); + break; + + default: + return B_ERROR; + } + return B_OK; +} + +_EXPORT module_info *modules[] = { + (module_info *) &core_info, + NULL +}; + +#else +_EXPORT module_info *modules = (module_info *)&core_info; +#endif diff --git a/src/add-ons/kernel/network/core/if.c b/src/add-ons/kernel/network/core/if.c new file mode 100644 index 0000000000..c6fa1c4771 --- /dev/null +++ b/src/add-ons/kernel/network/core/if.c @@ -0,0 +1,429 @@ +/* if helper functions */ + +#include +#include +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "netinet/in.h" +#include "sys/socketvar.h" +#include "net/if.h" +#include "net/if_dl.h" +#include "sys/sockio.h" +#include "netinet/in_var.h" +#include "net/route.h" +#include "sys/protosw.h" + +extern struct ifnet *devices; +extern int ndevs; + +/* Variables used outside this file */ +struct ifaddr **ifnet_addrs; + +/* Private variables */ +static int if_index; +static int if_indexlim; + +struct ifnet *get_interfaces(void) +{ + return devices; +} + +void if_init(void) +{ + ifnet_addrs = NULL; + if_index = 0; + if_indexlim = 8; /* initial value */ +} + +int ifioctl(struct socket *so, int cmd, caddr_t data) +{ + struct ifnet *ifp; + struct ifreq *ifr; + + if (cmd == SIOCGIFCONF) + return (ifconf(cmd, data)); + + ifr = (struct ifreq*) data; + ifp = ifunit(ifr->ifr_name); + if (ifp == NULL) + return ENXIO; + + switch(cmd) { + case SIOCGIFFLAGS: + /* get interface flags */ + ifr->ifr_flags = ifp->if_flags; + break; + case SIOCGIFMETRIC: + /* get interface metric */ + ifr->ifr_metric = ifp->if_metric; + break; + case SIOCGIFMTU: + /* get interface MTU */ + ifr->ifr_mtu = ifp->if_mtu; + break; + case SIOCSIFFLAGS: + ifp->if_flags = ifr->ifr_flags; + /* restart card with new settings... */ + break; + case SIOCSIFMETRIC: + /* set interface metric */ + ifp->if_metric = ifr->ifr_metric; + break; + default: + if (so->so_proto == NULL) + return EOPNOTSUPP; + return (*so->so_proto->pr_userreq)(so, PRU_CONTROL, + (struct mbuf*)cmd, (struct mbuf*)data, (struct mbuf*)ifp); + + } + + + return 0; +} + +struct ifnet *ifunit(char *name) +{ + ifnet *d = devices; + + for (d=devices;d;d = d->if_next) + if (strcmp(d->if_name, name) == 0) + return d; + return NULL; +} + +void dump_sockaddr(void *ptr) +{ + struct sockaddr *sa = (struct sockaddr *)ptr; + uint8 *d = NULL; + int i; + + switch (sa->sa_family) { + case AF_LINK: { + struct sockaddr_dl *sdl = (struct sockaddr_dl *)ptr; + if (sdl->sdl_type == IFT_ETHER) { + printf("\t\tETHERNET: "); + printf("Interface "); + d = (unsigned char *)&sdl->sdl_data[0]; + for (i=0;isdl_nlen;i++, d++) { + printf("%c", *d); + } + printf(" -> "); + for (i=0;isdl_alen;i++, d++) { + printf("%02x", *d); + if (i< 5) + printf(":"); + } + printf("\n"); + } + break; + } + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)ptr; + struct in_addr ho; + ho.s_addr = sin->sin_addr.s_addr; + printf("\t\tIPv4: "); + d = (uint8*)&ho.s_addr; + for (i=0;i<4;i++, d++) { + printf("%d", *d); + if (i < 3) + printf("."); + } + printf("\n"); + break; + } + default: + printf("Unknown type... %d\n", sa->sa_family); + } +} + +void *protocol_address(struct ifnet *ifa, int family) +{ + struct ifaddr *a = ifa->if_addrlist; + + for (; a != NULL; a = a->ifa_next) { + if (a->ifa_addr->sa_family == family) { + if (family == AF_INET) { + return &((struct sockaddr_in*)a->ifa_addr)->sin_addr; + } else { + return &a->ifa_addr->sa_data; + } + } + } + return NULL; +} + +#define equal(a1, a2) \ + (memcmp((caddr_t)(a1), (caddr_t)(a2), ((struct sockaddr *)(a1))->sa_len) == 0) + +/* + * Find an interface address specific to an interface best matching + * a given address. + */ +struct ifaddr *ifaof_ifpforaddr(struct sockaddr *addr, + struct ifnet *ifp) +{ + struct ifaddr *ifa; + char *cp, *cp2, *cp3; + char *cplim; + struct ifaddr *ifa_maybe = 0; + uint af = addr->sa_family; + + if (af >= AF_MAX) + return (NULL); + + for (ifa = ifp->if_addrlist; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family != af) + continue; + ifa_maybe = ifa; + if (ifa->ifa_netmask == 0) { + if (equal(addr, ifa->ifa_addr) || + (ifa->ifa_dstaddr && equal(addr, ifa->ifa_dstaddr))) + return (ifa); + continue; + } + cp = (char *)addr->sa_data; + cp2 = (char *)ifa->ifa_addr->sa_data; + cp3 = (char *)ifa->ifa_netmask->sa_data; + cplim = ifa->ifa_netmask->sa_len + (char *)ifa->ifa_netmask; + for (; cp3 < cplim; cp3++) + if ((*cp++ ^ *cp2++) & *cp3) + break; + if (cp3 == cplim) + return (ifa); + } + return (ifa_maybe); +} + +struct ifaddr *ifa_ifwithdstaddr(struct sockaddr *addr) +{ + struct ifnet *ifp; + struct ifaddr *ifa; + + for (ifp = devices; ifp != NULL; ifp = ifp->if_next) + if (ifp->if_flags & IFF_POINTOPOINT) + for (ifa = ifp->if_addrlist; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family != addr->sa_family || + ifa->ifa_dstaddr == NULL) + continue; + if (equal(addr, ifa->ifa_dstaddr)) + return (ifa); + } + return (NULL); +} + +struct ifaddr *ifa_ifwithaddr(struct sockaddr *addr) +{ + struct ifnet *ifp; + struct ifaddr *ifa; + + for (ifp = devices; ifp != NULL; ifp = ifp->if_next) { + for (ifa = ifp->if_addrlist; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family != addr->sa_family) + continue; + if (equal(addr, ifa->ifa_addr)) + return (ifa); + if ((ifp->if_flags & IFF_BROADCAST) && ifa->ifa_broadaddr && + /* IPv6 doesn't have broadcast */ + ifa->ifa_broadaddr->sa_len != 0 && + equal(ifa->ifa_broadaddr, addr)) + return (ifa); + } + } + return (NULL); +} + +/* + * Find an interface on a specific network. If many, choice + * is most specific found. + */ +struct ifaddr *ifa_ifwithnet(struct sockaddr *addr) +{ + struct ifnet *ifp; + struct ifaddr *ifa; + struct ifaddr *ifa_maybe = NULL; + uint af = addr->sa_family; + char *addr_data = (char *)addr->sa_data, *cplim; + + if (af == AF_LINK) { + struct sockaddr_dl *sdl = (struct sockaddr_dl *)addr; + if (sdl->sdl_index && sdl->sdl_index <= ndevs) + return (ifnet_addrs[sdl->sdl_index]); + } + for (ifp = devices; ifp != NULL; ifp = ifp->if_next) + for (ifa = ifp->if_addrlist; ifa != NULL; ifa = ifa->ifa_next) { + register char *cp, *cp2, *cp3; + + if (ifa->ifa_addr->sa_family != af || + ifa->ifa_netmask == 0) +next: + continue; + cp = addr_data; + cp2 = (char *)ifa->ifa_addr->sa_data; + cp3 = (char *)ifa->ifa_netmask->sa_data; + cplim = (char *)ifa->ifa_netmask + + ifa->ifa_netmask->sa_len; + while (cp3 < cplim) + if ((*cp++ ^ *cp2++) & *cp3++) + /* want to continue for() loop */ + goto next; + if (ifa_maybe == 0 || + rn_refines((caddr_t)ifa->ifa_netmask, + (caddr_t)ifa_maybe->ifa_netmask)) + ifa_maybe = ifa; + } + return (ifa_maybe); +} + +/* XXX - we have a memory leak here! When we clean up we need to free the memory + * that is malloc'd here + */ +void if_attach(struct ifnet *ifp) +{ + uint socksize, ifasize; + int namelen, masklen; + struct ifnet **p = &devices; + struct sockaddr_dl *sdl; + struct ifaddr *ifa; + char dname[IFNAMSIZ]; + + if (!ifp) + return; + + sprintf(dname, "%s%d", ifp->name, ifp->if_unit); + ifp->if_name = strdup(dname); + + while (*p) + p = &((*p)->if_next); + + *p = ifp; + ifp->if_index = ++if_index; /* atomic add ? */ + + /* allocate memory for ifnet_addrs if required... */ + if (ifnet_addrs == NULL || if_index >= if_indexlim) { + uint n = (if_indexlim <<= 1) * sizeof(*ifa); + struct ifaddr **q = (struct ifaddr**)malloc(n); + if (ifnet_addrs) { + memcpy((caddr_t)q, (caddr_t)ifnet_addrs, n / 2); + free(ifnet_addrs); + } + ifnet_addrs = q; + } + + /* get the unit # as a string */ + namelen = strlen(ifp->if_name); + + /* memory: we need to allocate enough memory for the following... + * struct ifaddr + * struct sockaddr_dl that will hold the link level address and name + * struct sockaddr_dl that will hold the mask + */ + +#define _offsetof(t, m) ((int)((caddr_t)&((t *)0)->m)) + masklen = _offsetof(struct sockaddr_dl, sdl_data[0]) + namelen; + socksize = masklen + ifp->if_addrlen; +#define ROUNDUP(a) (1 + (((a) - 1) | (sizeof(int32) -1))) + socksize = ROUNDUP(socksize); + + if (socksize < sizeof(*sdl)) + socksize = sizeof(*sdl); + ifasize = sizeof(*ifa) + 2 * socksize; + if ((ifa = (struct ifaddr*)malloc(ifasize))) { + memset(ifa, 0, ifasize); + + sdl = (struct sockaddr_dl *)(ifa + 1); + sdl->sdl_len = socksize; + sdl->sdl_family = AF_LINK; + memcpy(&sdl->sdl_data, ifp->if_name, namelen); + sdl->sdl_nlen = namelen; + sdl->sdl_index = ifp->if_index; + sdl->sdl_type = ifp->if_type; + ifnet_addrs[if_index - 1] = ifa; + ifa->ifa_ifp = ifp; + ifa->ifa_next = ifp->if_addrlist; + ifp->if_addrlist = ifa; + ifa->ifa_addr = (struct sockaddr*)sdl; + + /* now do mask... */ + sdl = (struct sockaddr_dl *)(socksize + (caddr_t)sdl); + ifa->ifa_netmask = (struct sockaddr*)sdl; + sdl->sdl_len = masklen; + /* build the mask */ + while (namelen != 0) + sdl->sdl_data[--namelen] = 0xff; + } +} + +void if_detach(struct ifnet *ifp) +{ + struct ifnet **p = &devices, *q; + + for (; (*p)->if_next != ifp ; (*p) = (*p)->if_next) + continue; + q = (*p)->if_next->if_next; + (*p)->if_next = q; +} + +/* XXX - memcpy used as copyin / copyout not available. I did look + * for the source for them but was unable to find them in the + * code jungle that is OpenBSD and FreeBSD! + * + * copyin / copyout should maybe be added if they'd add a speed improvement as + * they're used in a lot of other places as well + */ +int ifconf(int cmd, caddr_t data) +{ + struct ifconf *ifc = (struct ifconf*)data; + struct ifnet *ifp = devices; + struct ifaddr *ifa = NULL; + char *cp, *ep; + struct ifreq ifr, *ifrp; + int space = ifc->ifc_len; /* how big the buffer is */ + void *copyptr = NULL; + + ifrp = ifc->ifc_req; + ep = ifr.ifr_name + sizeof(ifr.ifr_name) - 2; + + for (; space > sizeof(ifr) && ifp; ifp = ifp->if_next) { + strncpy(ifr.ifr_name, ifp->if_name, sizeof(ifr.ifr_name) - 2); + for (cp = ifr.ifr_name;cp < ep && *cp; cp++) + continue; + *cp = '\0'; + if ((ifa = ifp->if_addrlist) == NULL) { + memset((caddr_t)&ifr.ifr_addr, 0, sizeof(ifr.ifr_addr)); + copyptr = memcpy((caddr_t) ifrp, (caddr_t) &ifr, sizeof(ifr)); + if (copyptr == NULL) + break; + space -= sizeof(ifr), ifrp++; + } else { + for (; space > sizeof(ifr) && ifa; ifa = ifa->ifa_next) { + struct sockaddr *sa = ifa->ifa_addr; + if (sa->sa_len <= sizeof(*sa)) { + printf("sa->sa_len = %d compared to %ld, sa->sa_family = %d\n", + sa->sa_len, sizeof(*sa), sa->sa_family); + + ifr.ifr_addr = *sa; + copyptr = memcpy((caddr_t)ifrp, (caddr_t)&ifr, sizeof(ifr)); + ifrp++; + } else { + space -= sa->sa_len - sizeof(*sa); + if (space < sizeof(ifr)) + break; + copyptr = memcpy((caddr_t)ifrp, (caddr_t)&ifr, sizeof(ifr.ifr_name)); + if (copyptr != NULL) + copyptr = memcpy((caddr_t)&ifrp->ifr_addr, (caddr_t)sa, sa->sa_len); + ifrp = (struct ifreq*)(sa->sa_len + (caddr_t)&ifrp->ifr_addr); + } + if (copyptr == NULL) + break; + space -= sizeof(ifr); + } + } + } + ifc->ifc_len -= space; + /* Yuck! */ + return (copyptr == NULL ? -1 : 0); +} diff --git a/src/add-ons/kernel/network/core/ifq.c b/src/add-ons/kernel/network/core/ifq.c new file mode 100644 index 0000000000..ce362cc5c3 --- /dev/null +++ b/src/add-ons/kernel/network/core/ifq.c @@ -0,0 +1,53 @@ +/* some misc functions... */ + +#include +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "net/if.h" + +struct ifq *start_ifq(void) +{ + struct ifq *nifq = (struct ifq*)malloc(sizeof(*nifq)); + + if (!nifq) + return NULL; + + memset(nifq, 0, sizeof(*nifq)); + + nifq->lock = create_sem(1, "ifq_lock"); + nifq->pop = create_sem(0, "ifq_pop"); +#ifdef _KERNEL_ + set_sem_owner(nifq->lock, B_SYSTEM_TEAM); + set_sem_owner(nifq->pop, B_SYSTEM_TEAM); +#endif + + if (nifq->lock < B_OK || nifq->pop < B_OK) + return NULL; + + nifq->len = 0; + nifq->maxlen = 50; + nifq->head = nifq->tail = NULL; + return nifq; +} + +void stop_ifq(struct ifq *q) +{ + struct mbuf *m = NULL; + + acquire_sem_etc(q->lock, 1, B_CAN_INTERRUPT, 0); + while (q->head) { + m = q->head; + q->head = m->m_nextpkt; + m->m_nextpkt = NULL; + m_freem(m); + } + q->len = 0; + delete_sem(q->pop); + release_sem_etc(q->lock, 1, B_CAN_INTERRUPT); + delete_sem(q->lock); + free(q); +} diff --git a/src/add-ons/kernel/network/core/in.c b/src/add-ons/kernel/network/core/in.c new file mode 100644 index 0000000000..73d6fec78d --- /dev/null +++ b/src/add-ons/kernel/network/core/in.c @@ -0,0 +1,480 @@ +/* in.c */ + +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "netinet/in.h" +#include "netinet/in_var.h" +#include "sys/socketvar.h" +#include "net/if.h" +#include "sys/sockio.h" +#include "net/route.h" + +extern struct ifnet **ifnet_addrs; + +struct in_ifaddr *get_primary_addr(void) +{ + return in_ifaddr; +} + +/* + * Trim a mask in a sockaddr + */ +void in_socktrim(struct sockaddr_in *ap) +{ + char *cplim = (char *) &ap->sin_addr; + char *cp = (char *) (&ap->sin_addr + 1); + + ap->sin_len = 0; + while (--cp >= cplim) + if (*cp) { + (ap)->sin_len = cp - (char *) (ap) + 1; + break; + } +} + +#define rtinitflags(x) \ + ((((x)->ia_ifp->if_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) != 0) \ + ? RTF_HOST : 0) +/* + * remove a route to prefix ("connected route" in cisco terminology). + * re-installs the route by using another interface address, if there's one + * with the same prefix (otherwise we lose the route mistakenly). + */ +static int in_scrubprefix(struct in_ifaddr *target) +{ + struct in_ifaddr *ia; + struct in_addr prefix, mask, p; + int error; + + if ((target->ia_flags & IFA_ROUTE) == 0) + return 0; + + if (rtinitflags(target)) + prefix = target->ia_dstaddr.sin_addr; + else + prefix = target->ia_addr.sin_addr; + mask = target->ia_sockmask.sin_addr; + prefix.s_addr &= mask.s_addr; + + for (ia = in_ifaddr; ia; ia = ia->ia_next) { + /* easy one first */ + if (mask.s_addr != ia->ia_sockmask.sin_addr.s_addr) + continue; + + if (rtinitflags(ia)) + p = ia->ia_dstaddr.sin_addr; + else + p = ia->ia_addr.sin_addr; + p.s_addr &= ia->ia_sockmask.sin_addr.s_addr; + if (prefix.s_addr != p.s_addr) + continue; + + /* + * if we got a matching prefix route, move IFA_ROUTE to him + */ + if ((ia->ia_flags & IFA_ROUTE) == 0) { + rtinit(&(target->ia_ifa), (int)RTM_DELETE, + rtinitflags(target)); + target->ia_flags &= ~IFA_ROUTE; + + error = rtinit(&ia->ia_ifa, (int)RTM_ADD, + rtinitflags(ia) | RTF_UP); + if (error == 0) + ia->ia_flags |= IFA_ROUTE; + return error; + } + } + + /* + * noone seem to have prefix route. remove it. + */ + rtinit(&(target->ia_ifa), (int)RTM_DELETE, rtinitflags(target)); + target->ia_flags &= ~IFA_ROUTE; + return 0; +} + +#undef rtinitflags + +int in_ifinit(struct ifnet *dev, struct in_ifaddr *ia, struct sockaddr_in *sin, + int scrub) +{ + uint32 i = sin->sin_addr.s_addr; + struct sockaddr_in oldsin; + int error; + int flags = RTF_UP; + + oldsin = ia->ia_addr; + ia->ia_addr = *sin; + + if (dev && dev->ioctl) { + error = (*dev->ioctl)(dev, SIOCSIFADDR, (caddr_t)ia); + if (error) { + ia->ia_addr = oldsin; + return error; + } + } + + if (dev->if_type == IFT_ETHER) { + ia->ia_ifa.ifa_flags |= RTF_CLONING; + } + + if (scrub) { + ia->ia_ifa.ifa_addr = (struct sockaddr*)&oldsin; + in_scrubprefix(ia); + ia->ia_ifa.ifa_addr = (struct sockaddr*)&ia->ia_addr; + } + + if (IN_CLASSA(i)) + ia->ia_netmask = IN_CLASSA_NET; + else if (IN_CLASSB(i)) + ia->ia_netmask = IN_CLASSB_NET; + else + ia->ia_netmask = IN_CLASSC_NET; + + if (ia->ia_subnetmask == 0) { + ia->ia_subnetmask = ia->ia_netmask; + ia->ia_sockmask.sin_addr.s_addr = ia->ia_subnetmask; + } else + ia->ia_netmask &= ia->ia_subnetmask; + + ia->ia_net = i & ia->ia_netmask; + ia->ia_subnet = i & ia->ia_subnetmask; + in_socktrim(&ia->ia_sockmask); + + ia->ia_ifa.ifa_metric = dev->if_metric; + if (dev->if_flags & IFF_BROADCAST) { + ia->ia_broadaddr.sin_addr.s_addr = ia->ia_subnet | ~ia->ia_subnetmask; + ia->ia_netbroadcast.s_addr = ia->ia_net | ~ia->ia_netmask; + } else if (dev->if_flags & IFF_LOOPBACK) { + ia->ia_ifa.ifa_dstaddr = ia->ia_ifa.ifa_addr; + flags |= RTF_HOST; + } else if (dev->if_flags & IFF_POINTOPOINT) { + if (ia->ia_dstaddr.sin_family != AF_INET) + return 0; + flags |= RTF_HOST; + } + + /* This is really useful debugging code, but not needed at the moment... */ +#if 0 + printf("in_ifaddr:\n"); + printf(" : ia_net : %08lx\n", ia->ia_net); + printf(" : ia_netmask : %08lx\n", ia->ia_netmask); + printf(" : ia_subnet : %08lx\n", ia->ia_subnet); + printf(" : ia_subnetmask : %08lx\n", ia->ia_subnetmask); + printf(" : ia_netbroadcast : %08lx\n", ia->ia_netbroadcast.s_addr); + printf(" : ia_addr : %08lx\n", ia->ia_addr.sin_addr.s_addr); + printf(" : ia_dstaddr : %08lx\n", ia->ia_dstaddr.sin_addr.s_addr); + printf(" : ia_sockmask : %08lx\n", ia->ia_sockmask.sin_addr.s_addr); +#endif + + error = rtinit(&(ia->ia_ifa), (int)RTM_ADD, flags); + if (error == 0) + ia->ia_flags |= IFA_ROUTE; + + /* XXX - Multicast address list */ + + return error; +} + +int in_control(struct socket *so, int cmd, caddr_t data, struct ifnet *ifp) +{ + struct ifreq *ifr = (struct ifreq*)data; + struct in_ifaddr *ia = NULL; + struct ifaddr *ifa; + struct in_ifaddr *oia; + struct in_aliasreq *ifra = (struct in_aliasreq*)data; + struct sockaddr_in oldaddr; + int error = 0, hostIsNew, maskIsNew; + long i; + + if (ifp) /* we need to find the in_ifaddr */ + for (ia = in_ifaddr;ia; ia = ia->ia_next) + if (ia->ia_ifp == ifp) + break; + + switch (cmd) { + case SIOCAIFADDR: + printf("SIOCAIFADDR\n"); + /* add an address */ + case SIOCDIFADDR: + printf("SIODIFADDR\n"); + /* delete an address */ + if (ifra->ifra_addr.sin_family == AF_INET) + for (oia = ia; ia; ia = ia->ia_next) { + if (ia->ia_ifp == ifp && + ia->ia_addr.sin_addr.s_addr == ifra->ifra_addr.sin_addr.s_addr) + break; + } + if (cmd == SIOCDIFADDR && ia == NULL) + return EADDRNOTAVAIL; + case SIOCSIFADDR: + printf("SIOCSIFADDR\n"); + /* set an address */ + case SIOCSIFNETMASK: + printf("SIOCSIFNETMASK #1 (%d)\n", cmd); + /* set a net mask */ + case SIOCSIFDSTADDR: + /* set the destination address of a point to point link */ + if (!ifp) { + printf("No interface pointer!\n"); + return EINVAL; + } + if (ia == NULL) { + oia = (struct in_ifaddr*)malloc(sizeof(struct in_ifaddr)); + if (oia == NULL) + return ENOMEM; + memset(oia, 0, sizeof(*oia)); + if ((ia = in_ifaddr)) { + /* we've got other structures - add at end */ + for (; ia->ia_next; ia = ia->ia_next) + continue; + ia->ia_next = oia; + } else + in_ifaddr = oia; + + ia = oia; + if ((ifa = ifp->if_addrlist)) { + for (; ifa->ifa_next; ifa = ifa->ifa_next) + continue; + ifa->ifa_next = (struct ifaddr*)ia; + } else + ifp->if_addrlist = (struct ifaddr*)ia; + + ia->ia_ifa.ifa_addr = (struct sockaddr*) &ia->ia_addr; + ia->ia_ifa.ifa_dstaddr = (struct sockaddr*) &ia->ia_dstaddr; + ia->ia_ifa.ifa_netmask = (struct sockaddr*) &ia->ia_sockmask; + ia->ia_sockmask.sin_len = 8; + if (ifp->if_flags & IFF_BROADCAST) { + ia->ia_broadaddr.sin_len = sizeof(ia->ia_addr); + ia->ia_broadaddr.sin_family = AF_INET; + } + ia->ia_ifp = ifp; + } + break; + case SIOCSIFBRDADDR: + case SIOCGIFADDR: + case SIOCGIFNETMASK: + case SIOCGIFDSTADDR: + case SIOCGIFBRDADDR: + if (ia == NULL) + return EADDRNOTAVAIL; + break; + + } + + printf("loop #2 : %d [%ld]\n", cmd, SIOCSIFNETMASK); + + switch(cmd) { + case SIOCGIFADDR: + /* get interface address */ + *((struct sockaddr_in*) &ifr->ifr_addr) = ia->ia_addr; + break; + case SIOCGIFDSTADDR: + /* get interface point to point destination address */ + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) + /* we're not a point to point interface */ + return EINVAL; + *((struct sockaddr_in*) &ifr->ifr_dstaddr) = ia->ia_dstaddr; + break; + case SIOCGIFBRDADDR: + /* get interface broadcast address */ + if ((ifp->if_flags & IFF_BROADCAST) == 0) + /* we're not a broadcast capable interface */ + return EINVAL; + *((struct sockaddr_in*) &ifr->ifr_dstaddr) = ia->ia_broadaddr; + break; + case SIOCGIFNETMASK: + /* get interface netmask */ + *((struct sockaddr_in*) &ifr->ifr_addr) = ia->ia_sockmask; + break; + case SIOCSIFADDR: + printf("SIOCSIFADDR #2\n"); + return in_ifinit(ifp, ia, (struct sockaddr_in*)&ifr->ifr_addr, 1); + case SIOCSIFNETMASK: + printf("Setting netmask\n"); + /* set the netmask for the interface... */ + /* set i to the network netmask (network host order) */ + i = ifra->ifra_addr.sin_addr.s_addr; + /* set the host byte order netmask into ia_subnetmask */ + ia->ia_subnetmask = ntohl((ia->ia_sockmask.sin_addr.s_addr = i)); +printf("ia->ia_subnetmask: %08lx\n", ia->ia_subnetmask); + break; + case SIOCSIFDSTADDR: + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) + return EINVAL; + oldaddr = ia->ia_dstaddr; + ia->ia_dstaddr = *(struct sockaddr_in*)&ifr->ifr_dstaddr; + /* update the interface if required */ + if (ifp->ioctl) { + error = ifp->ioctl(ifp, SIOCSIFDSTADDR, (caddr_t) ia); + if (error) { + ia->ia_dstaddr = oldaddr; + return error; + } + } + /* change the routing info if it's been set */ + if (ia->ia_flags & IFA_ROUTE) { + ia->ia_ifa.ifa_dstaddr = (struct sockaddr*)&oldaddr; + rtinit(&(ia->ia_ifa), RTM_DELETE, RTF_HOST); + ia->ia_ifa.ifa_dstaddr = (struct sockaddr*)&ia->ia_dstaddr; + rtinit(&(ia->ia_ifa), RTM_ADD, RTF_HOST|RTF_UP); + } + break; + + case SIOCSIFBRDADDR: + /* set the broadcast address if interface supports it */ + if ((ifp->if_flags & IFF_BROADCAST) == 0) + /* we don't support broadcast on that interface */ + return EINVAL; + ia->ia_broadaddr = *(struct sockaddr_in*) &ifr->ifr_broadaddr; + break; + case SIOCAIFADDR: + maskIsNew = 0; + hostIsNew = 1; + error = 0; + if (ia->ia_addr.sin_family == AF_INET) { + if (ifra->ifra_addr.sin_len == 0) { + ifra->ifra_addr = ia->ia_addr; + hostIsNew = 0; + } else if (ifra->ifra_addr.sin_addr.s_addr == + ia->ia_addr.sin_addr.s_addr) + hostIsNew = 0; + } + if (ifra->ifra_mask.sin_len) { + in_scrubprefix(ia); + ia->ia_sockmask = ifra->ifra_mask; + ia->ia_subnetmask = ia->ia_sockmask.sin_addr.s_addr; + maskIsNew = 1; + } + if ((ifp->if_flags & IFF_POINTOPOINT) && + (ifra->ifra_dstaddr.sin_family == AF_INET)) { + in_scrubprefix(ia); + ia->ia_dstaddr = ifra->ifra_dstaddr; + maskIsNew = 1; + } + if (ifra->ifra_addr.sin_family == AF_INET && + (hostIsNew || maskIsNew)) + error = in_ifinit(ifp, ia, &ifra->ifra_addr, 0); + if ((ifp->if_flags & IFF_BROADCAST) && + (ifra->ifra_broadaddr.sin_family == AF_INET)) + ia->ia_broadaddr = ifra->ifra_broadaddr; + return error; + default: + printf("2nd iteration: default (%d)\n", cmd); + /* if we don't have enough to do the default, return */ + if (ifp == NULL || ifp->ioctl == NULL) + return EINVAL; /* XXX - should be EOPNOTSUPP */ + /* send to the card and let it process it */ + return ifp->ioctl(ifp, cmd, data); + } + return 0; +} + + + +/* + * Return 1 if the address might be a local broadcast address. + */ +int in_broadcast(struct in_addr in, struct ifnet *ifp) +{ + struct ifnet *ifn, *if_first, *if_target; + struct ifaddr *ifa; + + if (in.s_addr == INADDR_BROADCAST || + in.s_addr == INADDR_ANY) + return 1; + if (ifp && ((ifp->if_flags & IFF_BROADCAST) == 0)) + return 0; + + if (ifp == NULL) { + if_first = *ifnet_addrs; + if_target = 0; + } else { + if_first = ifp; + if_target = ifp->if_next; + } + +#define ia (ifatoia(ifa)) + /* + * Look through the list of addresses for a match + * with a broadcast address. + * If ifp is NULL, check against all the interfaces. + */ + for (ifn = if_first; ifn != if_target; ifn = ifn->if_next) { + for (ifa = ifn->if_addrlist; ifa; ifa = ifa->ifa_next) { + if (!ifp) { + if (ifa->ifa_addr->sa_family == AF_INET && + ((ia->ia_subnetmask != 0xffffffff && + (((ifn->if_flags & IFF_BROADCAST) && + in.s_addr == ia->ia_broadaddr.sin_addr.s_addr) || + in.s_addr == ia->ia_subnet)) || + /* + * Check for old-style (host 0) broadcast. + */ + (in.s_addr == ia->ia_netbroadcast.s_addr || + in.s_addr == ia->ia_net))) + return 1; + else + if (ifa->ifa_addr->sa_family == AF_INET && + (((ifn->if_flags & IFF_BROADCAST) && + in.s_addr == ia->ia_broadaddr.sin_addr.s_addr) || + in.s_addr == ia->ia_netbroadcast.s_addr || + /* + * Check for old-style (host 0) broadcast. + */ + in.s_addr == ia->ia_subnet || + in.s_addr == ia->ia_net)) + return 1; + } + } + } + return (0); +#undef ia +} + +#ifndef SUBNETSARELOCAL +#define SUBNETSARELOCAL 0 +#endif +int subnetsarelocal = SUBNETSARELOCAL; +/* + * Return 1 if an internet address is for a ``local'' host + * (one to which we have a connection). If subnetsarelocal + * is true, this includes other subnets of the local net. + * Otherwise, it includes only the directly-connected (sub)nets. + */ +int in_localaddr(struct in_addr in) +{ + struct in_ifaddr *ia; + + if (subnetsarelocal) { + for (ia = in_ifaddr; ia != 0; ia = ia->ia_next) + if ((in.s_addr & ia->ia_netmask) == ia->ia_net) + return (1); + } else { + for (ia = in_ifaddr; ia != 0; ia = ia->ia_next) + if ((in.s_addr & ia->ia_subnetmask) == ia->ia_subnet) + return (1); + } + return (0); +} + +int in_canforward(struct in_addr in) +{ + uint32 i = ntohl(in.s_addr); + uint32 net; + + if (IN_EXPERIMENTAL(i) || IN_MULTICAST(i)) + return 0; + if (IN_CLASSA(i)) { + net = i & IN_CLASSA_NET; + if (net == 0 || net == (IN_LOOPBACKNET << IN_CLASSA_NSHIFT)) + return 0; + } + return 1; +} + + diff --git a/src/add-ons/kernel/network/core/inpcb.c b/src/add-ons/kernel/network/core/inpcb.c new file mode 100644 index 0000000000..ac1f2effde --- /dev/null +++ b/src/add-ons/kernel/network/core/inpcb.c @@ -0,0 +1,464 @@ +/* inpcb.c + * + * implementation of internet control blocks code + */ + +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "pools.h" +#include "sys/socketvar.h" +#include "netinet/in.h" +#include "netinet/in_pcb.h" +#include "net/if.h" +#include "netinet/in_var.h" +#include "sys/protosw.h" + +static struct pool_ctl *pcbpool = NULL; +static struct in_addr zeroin_addr; + +int inetctlerrmap[PRC_NCMDS] = { + 0, 0, 0, 0, + 0, EMSGSIZE, EHOSTDOWN, EHOSTUNREACH, + EHOSTUNREACH, EHOSTUNREACH, ECONNREFUSED, ECONNREFUSED, + EMSGSIZE, EHOSTUNREACH, 0, 0, + 0, 0, 0, 0, + ENOPROTOOPT +}; + +int inpcb_init(void) +{ + in_ifaddr = NULL; + + if (!pcbpool) + pool_init(&pcbpool, sizeof(struct inpcb)); + + if (!pcbpool) { + printf("inpcb_init: ENOMEM\n"); + return ENOMEM; + } + + zeroin_addr.s_addr = 0; + return 0; +} + +int in_pcballoc(struct socket *so, struct inpcb *head) +{ + struct inpcb *inp; + + inp = (struct inpcb *)pool_get(pcbpool); + + if (!inp) { + printf("in_pcballoc: ENOMEM\n"); + return ENOMEM; + } + + memset(inp, 0, sizeof(*inp)); + + inp->inp_head = head; + /* associate ourselves with the socket */ + inp->inp_socket = so; + insque(inp, head); + so->so_pcb = (caddr_t)inp; + return 0; +} + +void in_pcbdetach(struct inpcb *inp) +{ + struct socket *so = inp->inp_socket; + + so->so_pcb = NULL; + /* BSD sockets would call sofree here - we can't. + * The first thing that sofree does in BSD is check whether + * there are still file system references to the socket + * (SS_NOFDREF) and if there are it doesn't free. We don't have + * the same relationship to our sockets, as we use the socket for + * the kernel cookie, and freeing it here would lead to real problems, + * so we leave the socket until we call soclose() + * This may need to be reviewed and an extra layer of abstraction + * added at some point if we find it's using too much system resource. + */ + + if (inp->inp_options) + m_free(inp->inp_options); + if (inp->inp_route.ro_rt) + rtfree(inp->inp_route.ro_rt); + + remque(inp); + pool_put(pcbpool, inp); +} + +int in_pcbbind(struct inpcb *inp, struct mbuf *nam) +{ + struct socket *so = inp->inp_socket; + struct inpcb *head = inp->inp_head; + struct sockaddr_in *sin; + uint16 lport = 0; + int wild = 0; + int reuseport = (so->so_options & SO_REUSEPORT); + + if (inp->lport || inp->laddr.s_addr != INADDR_ANY) { + printf("in_pcbbind: EINVAL (%08lx:%d)\n", inp->laddr.s_addr, inp->lport); + return EINVAL; + } + + /* XXX - yuck! Try to format this better */ + /* This basically checks all the options that might be set that allow + * us to use wildcard searches. + */ + if (((so->so_options & (SO_REUSEADDR | SO_REUSEPORT)) == 0) && + ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 || + (so->so_options & SO_ACCEPTCONN) == 0)) + wild = INPLOOKUP_WILDCARD; + + if (nam) { + sin = mtod(nam, struct sockaddr_in *); + if (nam->m_len != sizeof(*sin)) { + printf("in_pcbind: EINVAL (m_len = %ld vs %ld)\n", + nam->m_len, sizeof(*sin)); + /* whoops, too much data! */ + return EINVAL; + } + + /* Apparently this may not be correctly + * in older programs, so this may need + * to be commented out... + */ + if (sin->sin_family != AF_INET) { + printf("in_pcbbind: EAFNOSUPPORT\n"); + return EAFNOSUPPORT; + } + + lport = sin->sin_port; + + if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) { + /* need special case for multicast. We'll + * allow the complete binding to be duplicated if + * SO_REUSEPORT is set, or if we have + * SO_REUSEADDR set and both sockets have + * a multicast address bound. + * We'll exit with the reuseport variable set + * correctly. + */ + if (so->so_options & SO_REUSEADDR) + reuseport = SO_REUSEADDR | SO_REUSEPORT; + } else if (sin->sin_addr.s_addr != INADDR_ANY) { + sin->sin_port = 0; /* must be zero for next step */ + if (ifa_ifwithaddr((struct sockaddr*)sin) == NULL) { + printf("in_pcbbind: EADDRNOTAVAIL\n"); + return EADDRNOTAVAIL; + } + + } + if (lport) { + struct inpcb *t; + /* we have something to work with... */ + /* XXX - reserved ports have no meaning for us */ + /* XXX - fix me if we ever have multi-user */ + t = in_pcblookup(head, zeroin_addr, 0, + sin->sin_addr, lport, wild); + if (t && (reuseport & t->inp_socket->so_options) == 0) { + printf("in_pcbbind: EADDRINUSE\n"); + return EADDRINUSE; + } + } + inp->laddr = sin->sin_addr; + } + /* if we have an ephemereal port, find a suitable port to use */ + if (lport == 0) { + /* ephemereal port!! */ + do { + if (head->lport++ < IPPORT_RESERVED || + head->lport > IPPORT_USERRESERVED) { + head->lport = IPPORT_RESERVED; + } + lport = htons(head->lport); + } while (in_pcblookup(head, zeroin_addr, 0, + inp->laddr, lport, wild)); + } + + inp->lport = lport; + return 0; +} + +struct inpcb *in_pcblookup(struct inpcb *head, struct in_addr faddr, + uint16 fport_a, struct in_addr laddr, + uint16 lport_a, int flags) +{ + struct inpcb *inp; + struct inpcb *match = NULL; + int matchwild = 3; + int wildcard; + uint16 fport = fport_a; + uint16 lport = lport_a; + + for (inp = head->inp_next; inp != head; inp = inp->inp_next) { + if (inp->lport != lport) + continue; /* local ports don't match */ + wildcard = 0; + /* Here we try to find the best match. wildcard is set to 0 + * and bumped by one every time we find something that doesn't match + * so we can have a suitable match at the end + */ + if (inp->laddr.s_addr != INADDR_ANY) { + if (laddr.s_addr == INADDR_ANY) + wildcard++; + else if (inp->laddr.s_addr != laddr.s_addr) + continue; + } else { + if (laddr.s_addr != INADDR_ANY) + wildcard++; + } + + if (inp->faddr.s_addr != INADDR_ANY) { + if (faddr.s_addr == INADDR_ANY) + wildcard++; + else if (inp->faddr.s_addr != faddr.s_addr || + inp->fport != fport) + continue; + } else { + if (faddr.s_addr != INADDR_ANY) + wildcard++; + } + if (wildcard && ((flags & INPLOOKUP_WILDCARD) == 0)) { + continue; /* wildcard match is not allowed!! */ + } + + if (wildcard < matchwild) { + match = inp; + matchwild = wildcard; + if (matchwild == 0) + break; /* exact match!! */ + } + } + return match; +} + +int in_pcbconnect(struct inpcb *inp, struct mbuf *nam) +{ + struct in_ifaddr *ia = NULL; + struct sockaddr_in *ifaddr = NULL; + struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *); + + if (nam->m_len != sizeof(*sin)) { + printf("in_pcbconnect: EINVAL\n"); + return EINVAL; + } + if (sin->sin_family != AF_INET) { + printf("in_pcbconnect: EAFNOSUPPORT (sin_family = %d, not %d)\n", + sin->sin_family, AF_INET); + return EAFNOSUPPORT; + } + if (sin->sin_port == 0) { + printf("in_pcbconnect: EADDRNOTAVAIL\n"); + return EADDRNOTAVAIL; + } + + if (in_ifaddr) { + if (sin->sin_addr.s_addr == INADDR_ANY) + sin->sin_addr = IA_SIN(in_ifaddr)->sin_addr; + + /* we need to handle INADDR_BROADCAST here as well */ + + } + + if (inp->laddr.s_addr == INADDR_ANY) { + struct route *ro; + + ro = &inp->inp_route; + + if (ro && ro->ro_rt && + (satosin(&ro->ro_dst)->sin_addr.s_addr != sin->sin_addr.s_addr + || inp->inp_socket->so_options & SO_DONTROUTE)) { + RTFREE(ro->ro_rt); + ro->ro_rt = NULL; + } + if ((inp->inp_socket->so_options & SO_DONTROUTE) == 0 + && (ro->ro_rt == NULL + || ro->ro_rt->rt_ifp == NULL)) { + /* we don't have a route, try to get one */ + memset(&ro->ro_dst, 0, sizeof(ro->ro_dst)); + ro->ro_dst.sa_family = AF_INET; + ro->ro_dst.sa_len = sizeof(struct sockaddr_in); + ((struct sockaddr_in*)&ro->ro_dst)->sin_addr = sin->sin_addr; + rtalloc(ro); + } + /* did we find a route?? */ + if (ro->ro_rt && (ro->ro_rt->rt_ifp->if_flags & IFF_LOOPBACK)) + ia = ifatoia(ro->ro_rt->rt_ifa); + + if (ia == NULL) { + uint16 fport = sin->sin_port; + + sin->sin_port = 0; + ia = ifatoia(ifa_ifwithdstaddr(sintosa(sin))); + if (ia == NULL) + ia = ifatoia(ifa_ifwithnet(sintosa(sin))); + sin->sin_port = fport; + if (ia == NULL) + ia = in_ifaddr; + if (ia == NULL) { + printf("in_pcbconnect: EADDRNOTAVAIL\n"); + return EADDRNOTAVAIL; + } + } + /* XXX - handle multicast */ + ifaddr = (struct sockaddr_in*) &ia->ia_addr; + } + + if (in_pcblookup(inp->inp_head, sin->sin_addr, sin->sin_port, + inp->laddr.s_addr ? inp->laddr : ifaddr->sin_addr, + inp->lport, 0)) { + printf("in_pcbconnect: EADDRINUSE\n"); + return EADDRINUSE; + } + + if (inp->laddr.s_addr == INADDR_ANY) { + if (inp->lport == 0) + in_pcbbind(inp, NULL); + inp->laddr = ifaddr->sin_addr; + } + inp->faddr = sin->sin_addr; + inp->fport = sin->sin_port; + return 0; +} + +/* XXX - why is this an int? */ +int in_pcbdisconnect(struct inpcb *inp) +{ + inp->faddr.s_addr = INADDR_ANY; + inp->fport = 0; + if (inp->inp_socket->so_state & SS_NOFDREF) + in_pcbdetach(inp); + return 0; +} + +void in_losing(struct inpcb *inp) +{ + struct rtentry *rt; + struct rt_addrinfo info; + + if ((rt = inp->inp_route.ro_rt)) { + inp->inp_route.ro_rt = NULL; + memset(&info, 0, sizeof(info)); + info.rti_info[RTAX_DST] = (struct sockaddr*)&inp->inp_route.ro_dst; + info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; + info.rti_info[RTAX_NETMASK] = rt_mask(rt); + //rt_missmsg + + if (rt->rt_flags & RTF_DYNAMIC) + rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway, rt_mask(rt), + rt->rt_flags, NULL); + else + rtfree(rt); + } +} + +struct rtentry *in_pcbrtentry(struct inpcb *inp) +{ + struct route *ro; + + ro = &inp->inp_route; + + /* + * No route yet, so try to acquire one. + */ + if (ro->ro_rt == NULL) { + memset(ro, 0, sizeof(struct route)); + + if (inp->faddr.s_addr != INADDR_ANY) { + /* this probably isn't needed, but better safe than sorry */ + memset(&ro->ro_dst, 0, sizeof(ro->ro_dst)); + ro->ro_dst.sa_family = AF_INET; + ro->ro_dst.sa_len = sizeof(ro->ro_dst); + satosin(&ro->ro_dst)->sin_addr = inp->faddr; + rtalloc(ro); + } + } + return (ro->ro_rt); +} + +int inetctlerr(int cmd) +{ + return inetctlerrmap[cmd]; +} + +/* remove the route associated with a control block (if there is one) + * forcing the route to be allocated next time it's used + */ +static void in_rtchange(struct inpcb *inp, int err) +{ + if (inp->inp_route.ro_rt) { + rtfree(inp->inp_route.ro_rt); + inp->inp_route.ro_rt = NULL; + } +} + +void in_pcbnotify(struct inpcb *head, struct sockaddr *dst, + uint16 fport_arg, struct in_addr laddr, + uint16 lport_arg, int cmd, + void (*notify)(struct inpcb *, int)) +{ + struct inpcb *inp, *oinp; + struct in_addr faddr; + uint16 fport = fport_arg, lport = lport_arg; + int err = 0; + + if ((uint)cmd > PRC_NCMDS || dst->sa_family != AF_INET) + return; + faddr = satosin(dst)->sin_addr; + if (faddr.s_addr == INADDR_ANY) + return; + + if (PRC_IS_REDIRECT(cmd) || cmd == PRC_HOSTDEAD) { + fport = lport = 0; + laddr.s_addr = 0; + if (cmd != PRC_HOSTDEAD) + notify = in_rtchange; + } + err = inetctlerrmap[cmd]; + for (inp = head->inp_next; inp != head;) { + if (inp->faddr.s_addr != faddr.s_addr || + inp->inp_socket == NULL || + inp->fport != fport || + inp->lport != lport || + (laddr.s_addr && inp->laddr.s_addr != laddr.s_addr)) { + inp = inp->inp_next; + continue; + } + oinp = inp; + inp = inp->inp_next; + if (notify) + (*notify)(oinp, err); + } +} + +void in_setsockaddr(struct inpcb *inp, struct mbuf *nam) +{ + struct sockaddr_in *sin; + + nam->m_len = sizeof(*sin); + sin = mtod(nam, struct sockaddr_in *); + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_port = inp->lport; + sin->sin_addr = inp->laddr; +} + +void in_setpeeraddr(struct inpcb *inp, struct mbuf *nam) +{ + struct sockaddr_in *sin; + + nam->m_len = sizeof(*sin); + sin = mtod(nam, struct sockaddr_in *); + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_port = inp->fport; + sin->sin_addr = inp->faddr; +} + diff --git a/src/add-ons/kernel/network/core/mbuf.c b/src/add-ons/kernel/network/core/mbuf.c new file mode 100644 index 0000000000..7c52e4cf14 --- /dev/null +++ b/src/add-ons/kernel/network/core/mbuf.c @@ -0,0 +1,522 @@ +/* mbuf.c + * network buffer implementation + */ + +#ifndef _KERNEL_ +#include +#endif + +#include +#include +#include + +#include "net_misc.h" +#include "pools.h" + +#ifdef _KERNEL_ +#include +#endif + +#define MBUF_ALLOCSIZE 4096 + +void dump_freelist(void) +{ + pool_debug_walk(mbpool); +} + +/* init the mbuf data structures */ +void mbinit(void) +{ + if (!mbpool) + pool_init(&mbpool, sizeof(struct mbuf)); + if (!clpool) + pool_init(&clpool, MCLBYTES); + /* XXX - move me to the protocol init routines! */ + max_linkhdr = 14; + max_protohdr = 40; + max_hdr = max_linkhdr + max_protohdr; +} + +struct mbuf *m_get(int type) +{ + struct mbuf *mnew; + MGET(mnew, type); + return mnew; +} + +struct mbuf *m_getclr(int type) +{ + struct mbuf *mnew; + MGET(mnew, type); + if (!mnew) + return NULL; + memset(mtod(mnew, char *), 0, MLEN); + return mnew; +} + +struct mbuf *m_gethdr(int type) +{ + struct mbuf *mnew; + MGETHDR(mnew, type); + return mnew; +} + +struct mbuf *m_free(struct mbuf *mfree) +{ + struct mbuf *succ; /* successor if there is one! */ + MFREE(mfree, succ); + return succ; +} + +/* Free the entire chain */ +void m_freem(struct mbuf *m) +{ + struct mbuf *n = NULL; + + if (!m) + return; + do { + MFREE(m, n); +//printf("m_freem(%p, %p)\n", m, n); + } while ((m = n) != NULL); +} + +struct mbuf *m_prepend(struct mbuf *m, int len) +{ + struct mbuf *mnew; + + if (M_LEADINGSPACE(m) >= len) { + m->m_data -= len; + m->m_len += len; + } else { + MGET(mnew, m->m_type); + if (!mnew) { + /* free chain */ + return NULL; + } + if (m->m_flags & M_PKTHDR) + M_MOVE_PKTHDR(mnew, m); + mnew->m_next = m; + m = mnew; + if (len < MHLEN) + MH_ALIGN(m, len); + m->m_len = len; + } + if (m && m->m_flags & M_PKTHDR) + m->m_pkthdr.len += len; + + return m; +} + + +struct mbuf *m_devget(char *buf, int totlen, int off0, + struct ifnet *ifp, + void (*copy)(const void *, void *, size_t)) +{ + struct mbuf *m; + struct mbuf *top = NULL, **mp = ⊤ + int off = off0, len; + char *cp; + char *epkt; + + cp = buf; + epkt = cp + totlen; + if (off) { + /* + * If 'off' is non-zero, packet is trailer-encapsulated, + * so we have to skip the type and length fields. + */ + cp += off + 2 * sizeof(uint16); + totlen -= 2 * sizeof(uint16); + } + MGETHDR(m, MT_DATA); + if (m == NULL) + return (NULL); + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = totlen; + m->m_len = MHLEN; + + while (totlen > 0) { + if (top != NULL) { + MGET(m, MT_DATA); + if (m == NULL) { + m_freem(top); + return (NULL); + } + m->m_len = MLEN; + } + len = min(totlen, epkt - cp); + if (len >= MINCLSIZE) { + MCLGET(m); + if (m->m_flags & M_EXT) + m->m_len = len = min(len, MCLBYTES); + else + len = m->m_len; + } else { + /* + * Place initial small packet/header at end of mbuf. + */ + if (len < m->m_len) { + if (top == NULL && + len + max_linkhdr <= m->m_len) + m->m_data += max_linkhdr; + m->m_len = len; + } else + len = m->m_len; + } + if (copy) + copy(cp, mtod(m, void *), (size_t)len); + else + memmove(mtod(m, void *), cp, (size_t)len); + cp += len; + *mp = m; + mp = &m->m_next; + totlen -= len; + if (cp == epkt) + cp = buf; + } + return (top); +} + +void m_reserve(struct mbuf *mp, int len) +{ + if (mp->m_len == 0) { + /* empty buffer! */ + if (mp->m_flags & M_PKTHDR) { + if (len < MHLEN) { + mp->m_data += len; + mp->m_len -= len; + mp->m_pkthdr.len -= len; + return; + } + /* ?? */ + } else { + if (len <= MLEN) { + mp->m_data += len; + mp->m_len -= len; + return; + } + } + } + + if (len > 0) { + if (len <= mp->m_len) { + mp->m_data += len; + mp->m_len -= len; + } + } + if (mp->m_flags & M_PKTHDR) + mp->m_pkthdr.len -= len; +} + +void m_cat(struct mbuf *m, struct mbuf *n) +{ + while (m->m_next) + m = m->m_next; + + while (n) { + if (m->m_flags & M_EXT || + m->m_data + m->m_len + n->m_len >= &m->m_dat[MLEN]) { + /* just join them :) */ + m->m_next = n; + return; + } + memcpy((void*)(mtod(m, char *) + m->m_len), mtod(n, void*), n->m_len); + m->m_len += n->m_len; + n = m_free(n); + } +} + +void m_adj(struct mbuf *mp, int req_len) +{ + struct mbuf *m; + int len = req_len, count = 0; + + if ((m = mp) == NULL) + return; + + if (len >= 0) { + /* trim from the head */ + while (m!= NULL && len > 0) { + if (m->m_len <= len) { + /* this whole mbuf isn't enough... */ + len -= m->m_len; + m->m_len = 0; + m = m->m_next; + } else { + /* this mbuf just needs trimming */ + m->m_len -= len; + m->m_data += len; + len = 0; + } + } + m = mp; + if (mp->m_flags & M_PKTHDR) + m->m_pkthdr.len -= (req_len - len); + } else { + /* trim from tail... */ + len = -len; + count = 0; + for (;;) { + count += m->m_len; + if (m->m_next == NULL) + break; + m = m->m_next; + } + if (m->m_len >= len) { + m->m_len -= len; + if (mp->m_flags & M_PKTHDR) + mp->m_pkthdr.len -= len; + return; + } + count -= len; + if (count < 0) + count = 0; + /* The correct length for the chain is now "count". + * find the last mbuf, adjust it's length and toss + * remaining mbufs... + */ + m = mp; /* first mbuf */ + if (m->m_flags & M_PKTHDR) + m->m_pkthdr.len = count; + for (; m; m= m->m_next) { + if (m->m_len >= count) { + m->m_len = count; + break; + } + count -= m->m_len; + } + while (m->m_next) + (m = m->m_next)->m_len = 0; + } +} + +void m_copydata(struct mbuf *m, int off, int len, caddr_t cp) +{ + uint count = 0; + + if (off < 0) { + printf("m_copydata: off %d < 0", off); + return; + } + if (len < 0) { + printf("m_copydata: len %d < 0", len); + return; + } + while (off > 0) { + if (m == NULL) { + printf("m_copydata: null mbuf in skip"); + return; + } + if (off < m->m_len) + break; + off -= m->m_len; + m = m->m_next; + } + while (len > 0) { + if (m == NULL) { + printf("m_copydata: null mbuf"); + return; + } + count = min(m->m_len - off, len); + memcpy(cp, (void*)(mtod(m, char *) + off), count); + len -= count; + cp += count; + off = 0; + m = m->m_next; + } +} + +struct mbuf *m_copym(struct mbuf *m, int off0, int len) +{ + struct mbuf *n, **np; + int off = off0; + struct mbuf *top; + int copyhdr = 0; + + if (off < 0 || len < 0) { + printf("PANIC: m_copym: m: off %d, len %d\n", off, len); + return NULL; + } + if (off == 0 && m->m_flags & M_PKTHDR) + copyhdr = 1; + while (off > 0) { + if (!m) { + printf("PANIC: m_copym: null mbuf\n"); + return NULL; + } + if (off < m->m_len) + break; + off -= m->m_len; + m = m->m_next; + } + np = ⊤ + top = NULL; + while (len > 0) { + if (!m) { + if (len != M_COPYALL) { + printf("PANIC: m_copym: m == NULL and not COPYALL\n"); + return NULL; + } + break; + } + MGET(n, m->m_type); + *np = n; + if (!n) + goto nospace; + if (copyhdr) { + M_DUP_PKTHDR(n, m); + if (len == M_COPYALL) + n->m_pkthdr.len -= off0; + else + n->m_pkthdr.len = len; + copyhdr = 0; + } + n->m_len = min(len, m->m_len - off); + if (m->m_flags & M_EXT) { + /* + * we are unsure about the way m was allocated. + * copy into multiple MCLBYTES cluster mbufs. + */ + MCLGET(n); + n->m_len = 0; + n->m_len = M_TRAILINGSPACE(n); + n->m_len = min(n->m_len, len); + n->m_len = min(n->m_len, m->m_len - off); + memcpy(mtod(n, caddr_t), (void *)(mtod(m, char *) + off), + (unsigned)n->m_len); + } else + memcpy(mtod(n, caddr_t), (void*)(mtod(m, char *)+off), + (unsigned)n->m_len); + if (len != M_COPYALL) + len -= n->m_len; + off += n->m_len; + if (off == m->m_len) { + m = m->m_next; + off = 0; + } + np = &n->m_next; + } + return (top); +nospace: + m_freem(top); + return (NULL); +} + +/* Rearrange an mbuf chain so that len bytes are contiguous + * and in the data area of an mbuf (so that mtod and dtom + * will work for a structure of size len). Returns the resulting + * mbuf chain on success, frees it and returns null on failure. + * If there is room, it will add up to max_protohdr-len extra bytes to the + * contiguous region in an attempt to avoid being called next time. + */ +int MPFail = 0; +struct mbuf *m_pullup(struct mbuf *n, int len) +{ + struct mbuf *m; + int count; + int space; + + if (n->m_len <= len) + return n; + + /* + * If first mbuf has no cluster, and has room for len bytes + * without shifting current data, pullup into it, + * otherwise allocate a new mbuf to prepend to the chain. + */ + if ((n->m_flags & M_EXT) == 0 && + n->m_data + len < &n->m_dat[MLEN] && n->m_next) { + if (n->m_len >= len) + return (n); + m = n; + n = n->m_next; + len -= m->m_len; + } else { + if (len > MHLEN) + goto bad; + MGET(m, n->m_type); + if (m == NULL) + goto bad; + m->m_len = 0; + if (n->m_flags & M_PKTHDR) { + M_MOVE_PKTHDR(m, n); + } + } + space = &m->m_dat[MLEN] - (m->m_data + m->m_len); + do { + count = min(min(max(len, max_protohdr), space), n->m_len); + memcpy((void *)(mtod(m, caddr_t) + m->m_len), mtod(n, void *), (uint)count); + len -= count; + m->m_len += count; + n->m_len -= count; + space -= count; + if (n->m_len) + n->m_data += count; + else + n = m_free(n); + } while (len > 0 && n); + + if (len > 0) { +printf("m_pullup: failed: len = %d\n", len); + (void)m_free(m); + goto bad; + } + m->m_next = n; + return (m); +bad: + m_freem(n); + MPFail++; + return (NULL); +} + +/* + * Copy data from a buffer back into the indicated mbuf chain, + * starting "off" bytes from the beginning, extending the mbuf + * chain if necessary. The mbuf needs to be properly initalized + * including the setting of m_len. + */ +void m_copyback(struct mbuf *m0, int off, int len, caddr_t cp) +{ + int mlen; + struct mbuf *m = m0, *n; + int totlen = 0; + + if (m0 == 0) + return; + while (off > (mlen = m->m_len)) { + off -= mlen; + totlen += mlen; + if (m->m_next == 0) { + n = m_getclr(m->m_type); + if (n == 0) + goto out; + n->m_len = min(MLEN, len + off); + m->m_next = n; + } + m = m->m_next; + } + while (len > 0) { + mlen = min (m->m_len - off, len); + memcpy(off + mtod(m, caddr_t), cp, (unsigned)mlen); + cp += mlen; + len -= mlen; + mlen += off; + off = 0; + totlen += mlen; + if (len == 0) + break; + if (m->m_next == 0) { + n = m_get(m->m_type); + if (n == 0) + break; + n->m_len = min(MLEN, len); + m->m_next = n; + } + m = m->m_next; + } +out: + if (((m = m0)->m_flags & M_PKTHDR) && (m->m_pkthdr.len < totlen)) + m->m_pkthdr.len = totlen; +} + diff --git a/src/add-ons/kernel/network/core/misc.c b/src/add-ons/kernel/network/core/misc.c new file mode 100644 index 0000000000..e0f073b6f0 --- /dev/null +++ b/src/add-ons/kernel/network/core/misc.c @@ -0,0 +1,68 @@ +/* some misc functions... */ + +#include +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "net_misc.h" +#include "sys/socket.h" + +/* Basically use dump_ to see the address plus message on a line, + * print_ to simply have the address printed with nothing else... + */ + +void dump_ipv4_addr(char *msg, void *ad) +{ + uint8 *b = (uint8*)ad; + printf("%s %d.%d.%d.%d\n", msg, b[0], b[1], b[2], b[3]); +} + +void print_ipv4_addr(void *ad) +{ + uint8 *b = (uint8*)ad; + printf("%d.%d.%d.%d", b[0], b[1], b[2], b[3]); +} + +void dump_ether_addr(char *msg, void *ea) +{ + uint8 *b = (uint8*)ea; + printf("%s %02x:%02x:%02x:%02x:%02x:%02x\n", msg, + b[0], b[1], b[2], + b[3], b[4], b[5]); +} + +void print_ether_addr(void *ea) +{ + uint8 *b = (uint8*)ea; + printf("%02x:%02x:%02x:%02x:%02x:%02x", + b[0], b[1], b[2], + b[3], b[4], b[5]); +} + +void dump_buffer(char *buffer, int len) +{ + uint8 *b = (uint8 *)buffer; + int i; + + printf (" "); + for (i=0;isa_len == 4) /* IPv4 address, basically a uint32 */ + return (*(a->sa_data) = *(b->sa_data)); + return memcmp((void*)a->sa_data, (void*)b->sa_data, a->sa_len); +} + diff --git a/src/add-ons/kernel/network/core/net_timer.c b/src/add-ons/kernel/network/core/net_timer.c new file mode 100644 index 0000000000..24289f76ba --- /dev/null +++ b/src/add-ons/kernel/network/core/net_timer.c @@ -0,0 +1,229 @@ +/* net_timer.h - a small and more or less inaccurate timer for net modules. +** The registered hooks will be called in the thread of the timer. +** +** Initial version by Axel Dörfler, axeld@pinc-software.de +** +** This file may be used under the terms of the OpenBeOS License. +*/ + +#include +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "net_timer.h" + + + +struct timer_entry { + struct timer_entry *te_next; + net_timer_hook te_hook; + void *te_data; + bigtime_t te_interval; + bigtime_t te_until; + net_timer_id te_id; +}; + +struct timer_info { + struct timer_entry *ti_first; + + sem_id ti_lock; + sem_id ti_wait; + int32 ti_counter; + volatile int32 ti_inUse; +}; + +int32 net_timer(void *_data); + +struct timer_info gTimerInfo; + +status_t +net_init_timer(void) +{ + thread_id thread; + + memset(&gTimerInfo,0,sizeof(struct timer_info)); + + gTimerInfo.ti_lock = create_sem(1,"net timer lock"); + if (gTimerInfo.ti_lock < B_OK) + return B_ERROR; + + gTimerInfo.ti_wait = create_sem(0,"net timer wait"); + if (gTimerInfo.ti_wait < B_OK) + return B_ERROR; + +#ifdef _KERNEL_ + set_sem_owner(gTimerInfo.ti_lock, B_SYSTEM_TEAM); + set_sem_owner(gTimerInfo.ti_wait, B_SYSTEM_TEAM); + + thread = spawn_kernel_thread(net_timer,"net timer",B_NORMAL_PRIORITY,&gTimerInfo); +#else + thread = spawn_thread(net_timer,"net timer",B_NORMAL_PRIORITY,&gTimerInfo); +#endif + if (thread < B_OK) + return thread; + + return resume_thread(thread); +} + + +void +net_shutdown_timer(void) +{ + struct timer_entry *te,*next; + int32 tries = 20; + + delete_sem(gTimerInfo.ti_wait); + delete_sem(gTimerInfo.ti_lock); + gTimerInfo.ti_wait = -1; + gTimerInfo.ti_lock = -1; + + // make sure the structure isn't used anymore + while (gTimerInfo.ti_inUse != 0 && tries-- > 0) + snooze(1000); + + // free the remaining timer entries + for (te = gTimerInfo.ti_first;te;te = next) { + next = te->te_next; + free(te); + } +} + + +int32 +net_timer(void *_data) +{ + struct timer_info *timer = (struct timer_info *)_data; + status_t status = B_OK; + + do { + bigtime_t timeout = B_INFINITE_TIMEOUT; + struct timer_entry *te; + + // get access to the info structure + if (status == B_TIMED_OUT || status == B_OK) { + if (acquire_sem(timer->ti_lock) == B_OK) { + for (te = timer->ti_first;te;te = te->te_next) { + // new entry? + if (te->te_until == -1) + te->te_until = system_time() + te->te_interval; + + // execute timer? + if (te->te_until < system_time()) { + te->te_until += te->te_interval; + te->te_hook(te->te_data); + } + + // calculate new timeout + if (te->te_until < timeout) + timeout = te->te_until; + } + + release_sem(timer->ti_lock); + } + } + + status = acquire_sem_etc(timer->ti_wait,1,B_ABSOLUTE_TIMEOUT,timeout); + // the wait sem normally can't be acquired, so we + // have to look at the status value the call returns: + // + // B_OK - someone wanted to notify us + // B_TIMED_OUT - look for timers to be executed + // B_BAD_SEM_ID - our sem got deleted + + } while (status != B_BAD_SEM_ID); + + return 0; +} + + +net_timer_id +net_add_timer(net_timer_hook hook,void *data,bigtime_t interval) +{ + struct timer_entry *te; + status_t status; + + if (interval < 100) + return B_BAD_VALUE; + + atomic_add(&gTimerInfo.ti_inUse,1); + + // get access to the timer info structure + status = acquire_sem(gTimerInfo.ti_lock); + if (status < B_OK) { + atomic_add(&gTimerInfo.ti_inUse,-1); + return status; + } + + te = (struct timer_entry *)malloc(sizeof(struct timer_entry)); + if (te == NULL) { + atomic_add(&gTimerInfo.ti_inUse,-1); + release_sem(gTimerInfo.ti_lock); + return B_NO_MEMORY; + } + + te->te_hook = hook; + te->te_data = data; + te->te_interval = interval; + te->te_until = -1; + te->te_id = ++gTimerInfo.ti_counter; + + // add the new entry + te->te_next = gTimerInfo.ti_first; + gTimerInfo.ti_first = te; + + atomic_add(&gTimerInfo.ti_inUse,-1); + release_sem(gTimerInfo.ti_lock); + + // notify timer about the change + release_sem(gTimerInfo.ti_wait); + + return te->te_id; +} + + +status_t +net_remove_timer(net_timer_id id) +{ + struct timer_entry *te,*last; + status_t status; + + if (id <= B_OK) + return B_BAD_VALUE; + + atomic_add(&gTimerInfo.ti_inUse,1); + + // get access to the timer info structure + status = acquire_sem(gTimerInfo.ti_lock); + if (status < B_OK) { + atomic_add(&gTimerInfo.ti_inUse,-1); + return status; + } + + // search the list for the right timer + + // little hack that relies on ti_first being on the same position + // in the structure as te_next + last = (struct timer_entry *)&gTimerInfo; + for (te = gTimerInfo.ti_first;te;te = te->te_next) { + if (te->te_id == id) { + last->te_next = te->te_next; + free(te); + break; + } + last = te; + } + atomic_add(&gTimerInfo.ti_inUse,-1); + release_sem(gTimerInfo.ti_lock); + + if (te == NULL) + return B_ENTRY_NOT_FOUND; + + // notify timer about the change + release_sem(gTimerInfo.ti_wait); + + return B_OK; +} + diff --git a/src/add-ons/kernel/network/core/nhash.c b/src/add-ons/kernel/network/core/nhash.c new file mode 100644 index 0000000000..fbad823572 --- /dev/null +++ b/src/add-ons/kernel/network/core/nhash.c @@ -0,0 +1,150 @@ +/* nhash.c + * net hash + */ + +#include +#include + +#include "net_malloc.h" +#include "nhash.h" + +#define MAX_INITIAL 15; + +net_hash *nhash_make(void) +{ + net_hash *nn; + + nn = (net_hash *)malloc(sizeof(net_hash)); + + if (!nn) + return NULL; + + nn->count = 0; + nn->max = MAX_INITIAL; + + nn->array = (net_hash_entry **)malloc(sizeof(net_hash_entry) * (nn->max + 1)); + memset(nn->array, 0, sizeof(net_hash_entry) * (nn->max +1)); + pool_init(&nn->pool, sizeof(net_hash_entry)); + if (!nn->pool) + return NULL; + return nn; +} + +net_hash_index *nhash_next(net_hash_index *hi) +{ + hi->this = hi->next; + while (!hi->this) { + if (hi->index > hi->nh->max) + return NULL; + hi->this = hi->nh->array[hi->index++]; + } + hi->next = hi->this->next; + return hi; +} + +net_hash_index *nhash_first(net_hash *nh) +{ + net_hash_index *hi = &nh->iterator; + hi->nh = nh; + hi->index = 0; + hi->this = hi->next = NULL; + return nhash_next(hi); +} + +static void expand_array(net_hash *nh) +{ + net_hash_index *hi; + net_hash_entry **new_array; + int new_max = nh->max * 2 +1; + int i; + + new_array = (net_hash_entry **)malloc(sizeof(net_hash_entry) * new_max); + memset(new_array, 0, sizeof(net_hash_entry) * new_max); + for (hi = nhash_first(nh); hi; hi = nhash_next(hi)) { + i = hi->this->hash & new_max; + hi->this->next = new_array[i]; + new_array[i] = hi->this; + } + free(nh->array); + nh->array = new_array; + nh->max = new_max; +} + +void nhash_this(net_hash_index *hi, const void **key, ssize_t *klen, + void **val) +{ + if (key) *key = hi->this->key; + if (klen) *klen = hi->this->klen; + if (val) *val = (void*)hi->this->val; +} + +static net_hash_entry **find_entry(net_hash *nh, const void *key, + ssize_t klen, const void *val) +{ + net_hash_entry **hep; + net_hash_entry *he; + const unsigned char *p; + int hash = 0; + ssize_t i; + + if (!nh) + return NULL; + + for (p=key, i=klen; i; i--, p++) + hash = hash * 33 + *p; + + for (hep = &nh->array[hash & nh->max], he = *hep; he; + hep = &he->next, he = *hep) { + if (he->hash == hash && he->klen == klen + && memcmp(he->key, key, klen) == 0) { + break; + } + } + + if (he || !val) + return hep; + + /* add a new linked-list entry */ + he = (net_hash_entry *)pool_get(nh->pool); + he->next = NULL; + he->hash = hash; + he->key = key; + he->klen = klen; + he->val = val; + *hep = he; + nh->count++; + return hep; +} + +void *nhash_get(net_hash *nh, const void *key, ssize_t klen) +{ + net_hash_entry *he; + he = *find_entry(nh, key, klen, NULL); + if (he) + return (void*)he->val; + else + return NULL; +} + +void nhash_set(net_hash *nh, const void *key, ssize_t klen, const void *val) +{ + net_hash_entry **hep; + net_hash_entry *old; + hep = find_entry(nh, key, klen, val); + + if (*hep) { + if (!val) { + /* delete it */ + old = *hep; + *hep = (*hep)->next; + --nh->count; + pool_put(nh->pool, old); + } else { + /* replace it */ + (*hep)->val = val; + if (nh->count > nh->max) + expand_array(nh); + } + } +} + diff --git a/src/add-ons/kernel/network/core/pools.c b/src/add-ons/kernel/network/core/pools.c new file mode 100644 index 0000000000..d0abead657 --- /dev/null +++ b/src/add-ons/kernel/network/core/pools.c @@ -0,0 +1,324 @@ +/* pools.c */ + +#include +#include +#include +#include "pools.h" +#include "net_misc.h" +#include "net_malloc.h" + +#ifdef _KERNEL_ +#include +#define AREA_ADDR_FLAG B_ANY_KERNEL_ADDRESS +#define AREA_FLAGS B_NO_LOCK +#else +#define AREA_ADDR_FLAG B_ANY_ADDRESS +#define AREA_FLAGS B_FULL_LOCK +#endif + +static sem_id init_sem = -1; + +#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1)) + +#ifdef WALK_POOL_LIST +void walk_pool_list(struct pool_ctl *p) +{ + struct pool_mem *pb = p->list; + + printf("Pool: %p\n", p); + printf(" -> list = %p\n", pb); + while (pb) { + printf(" -> mem_block %p, %p\n", pb, pb->next); + pb = pb->next; + } +} +#endif + +void pool_debug_walk(struct pool_ctl *p) +{ + char *ptr; + int i = 1; + + printf("%ld byte blocks allocated, but now free:\n\n", p->alloc_size); + + #if POOL_USES_BENAPHORES + ACQUIRE_BENAPHORE(p->lock); + #else + ACQUIRE_READ_LOCK(p->lock); + #endif + ptr = p->freelist; + while (ptr) { + printf(" %02d: %p\n", i++, ptr); + ptr = ((struct free_blk*)ptr)->next; + } + #if POOL_USES_BENAPHORES + RELEASE_BENAPHORE(p->lock); + #else + RELEASE_READ_LOCK(p->lock); + #endif +} + +void pool_debug(struct pool_ctl *p, char *name) +{ + p->debug = 1; + if (strlen(name) < POOL_DEBUG_NAME_SZ) + strncpy(p->name, name, strlen(name)); + else + strncpy(p->name, name, POOL_DEBUG_NAME_SZ); +} + +static struct pool_mem *get_mem_block(struct pool_ctl *pool) +{ + struct pool_mem *block; + + block = (struct pool_mem *)malloc(sizeof(struct pool_mem)); + if (block == NULL) + return NULL; + + memset(block, 0, sizeof(*block)); + + block->aid = create_area("net_stack_pools_block", + (void**)&block->base_addr, + AREA_ADDR_FLAG, pool->block_size, + AREA_FLAGS, + B_READ_AREA|B_WRITE_AREA); + if (block->aid < B_OK) { + free(block); + return NULL; + } + + block->mem_size = block->avail = pool->block_size; + block->ptr = block->base_addr; + INIT_BENAPHORE(block->lock, "pool_mem_lock"); + + if (CHECK_BENAPHORE(block->lock) >= B_OK) { + #if POOL_USES_BENAPHORES + ACQUIRE_BENAPHORE(pool->lock); + #else + ACQUIRE_WRITE_LOCK(pool->lock); + #endif + + // insert block at the beginning of the pools + if (pool->list) + block->next = pool->list; + + pool->list = block; + +#ifdef WALK_POOL_LIST + walk_pool_list(pool); +#endif + + #if POOL_USES_BENAPHORES + RELEASE_BENAPHORE(pool->lock); + #else + RELEASE_WRITE_LOCK(pool->lock); + #endif + + return block; + } + UNINIT_BENAPHORE(block->lock); + + delete_area(block->aid); + free(block); + + return NULL; +} + + +status_t pool_init(struct pool_ctl **_newPool, size_t size) +{ + struct pool_ctl *pool = NULL; + + if (init_sem == -1) + create_sem(1, "pool_init_sem"); + + /* minimum block size is sizeof the free_blk structure */ + if (size < sizeof(struct free_blk)) + return B_BAD_VALUE; + +// acquire_sem_etc(init_sem, 1, B_CAN_INTERRUPT, 0); + + pool = (struct pool_ctl*)malloc(sizeof(struct pool_ctl)); + if (pool == NULL) + return B_NO_MEMORY; + + memset(pool, 0, sizeof(*pool)); + + #if POOL_USES_BENAPHORES + INIT_BENAPHORE(pool->lock, "pool_lock"); + if (CHECK_BENAPHORE(pool->lock) < B_OK) { + free(pool); + return B_ERROR; + } + #else + INIT_RW_LOCK(pool->lock, "pool_lock"); + if (CHECK_RW_LOCK(pool->lock) < B_OK) { + free(pool); + return B_ERROR; + } + #endif + + // 4 puddles will always fit in one pool + pool->block_size = ROUND_TO_PAGE_SIZE(size * 8); + pool->alloc_size = size; + pool->list = NULL; + pool->freelist = NULL; + + /* now add a first block */ + get_mem_block(pool); + if (!pool->list) { + #if POOL_USES_BENAPHORES + UNINIT_BENAPHORE(pool->lock); + #else + UNINIT_RW_LOCK(pool->lock); + #endif + free(pool); + return B_NO_MEMORY; + } + + *_newPool = pool; + +// release_sem_etc(init_sem, 1, B_CAN_INTERRUPT); + return B_OK; +} + + +char *pool_get(struct pool_ctl *p) +{ + /* ok, so now we look for a suitable block... */ + struct pool_mem *mp = p->list; + char *rv = NULL; + + #if POOL_USES_BENAPHORES + ACQUIRE_BENAPHORE(p->lock); + #else + ACQUIRE_WRITE_LOCK(p->lock); + #endif + + if (p->freelist) { + /* woohoo, just grab a block! */ + + rv = p->freelist; + + if (p->debug) + printf("%s: allocating %p, setting freelist to %p\n", + p->name, p->freelist, + ((struct free_blk*)rv)->next); + + p->freelist = ((struct free_blk*)rv)->next; + + #if POOL_USES_BENAPHORES + RELEASE_BENAPHORE(p->lock); + #else + RELEASE_WRITE_LOCK(p->lock); + #endif + + memset(rv, 0, p->alloc_size); + return rv; + } + #if !POOL_USES_BENAPHORES + RELEASE_WRITE_LOCK(p->lock); + ACQUIRE_READ_LOCK(p->lock); + #endif + + /* no free blocks, try to allocate of the top of the memory blocks + ** we must hold the global pool lock while iterating through the list! + */ + + do { + ACQUIRE_BENAPHORE(mp->lock); + + if (mp->avail >= p->alloc_size) { + rv = mp->ptr; + mp->ptr += p->alloc_size; + mp->avail -= p->alloc_size; + RELEASE_BENAPHORE(mp->lock); + break; + } + RELEASE_BENAPHORE(mp->lock); + } while ((mp = mp->next) != NULL); + + #if POOL_USES_BENAPHORES + RELEASE_BENAPHORE(p->lock); + #else + RELEASE_READ_LOCK(p->lock); + #endif + + if (rv) { + memset(rv, 0, p->alloc_size); + return rv; + } + + mp = get_mem_block(p); + if (mp == NULL) + return NULL; + + ACQUIRE_BENAPHORE(mp->lock); + + if (mp->avail >= p->alloc_size) { + rv = mp->ptr; + mp->ptr += p->alloc_size; + mp->avail -= p->alloc_size; + } + RELEASE_BENAPHORE(mp->lock); + + memset(rv, 0, p->alloc_size); + return rv; +} + + +void pool_put(struct pool_ctl *p, void *ptr) +{ + #if POOL_USES_BENAPHORES + ACQUIRE_BENAPHORE(p->lock); + #else + ACQUIRE_WRITE_LOCK(p->lock); + #endif + + memset(ptr, 0, p->alloc_size); + ((struct free_blk*)ptr)->next = p->freelist; + + if (p->debug) { + printf("%s: adding %p, setting next = %p\n", + p->name, ptr, p->freelist); + } + + p->freelist = ptr; + + if (p->debug) + printf("%s: freelist = %p\n", p->name, p->freelist); + + #if POOL_USES_BENAPHORES + RELEASE_BENAPHORE(p->lock); + #else + RELEASE_WRITE_LOCK(p->lock); + #endif +} + + +void pool_destroy(struct pool_ctl *p) +{ + struct pool_mem *mp,*temp; + + if (p == NULL) + return; + + /* the semaphore will be deleted, so we don't have to unlock */ + ACQUIRE_WRITE_LOCK(p->lock); + + mp = p->list; + while (mp != NULL) { + delete_area(mp->aid); + temp = mp; + mp = mp->next; + UNINIT_BENAPHORE(mp->lock); + free(temp); + } + + #if POOL_USES_BENAPHORES + UNINIT_BENAPHORE(p->lock); + #else + UNINIT_RW_LOCK(p->lock); + #endif + free(p); +} diff --git a/src/add-ons/kernel/network/core/radix.c b/src/add-ons/kernel/network/core/radix.c new file mode 100644 index 0000000000..7a5999ef2e --- /dev/null +++ b/src/add-ons/kernel/network/core/radix.c @@ -0,0 +1,1092 @@ +/* + * Copyright (c) 1988, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)radix.c 8.4 (Berkeley) 11/2/94 + * $FreeBSD: src/sys/net/radix.c,v 1.20.2.2 2001/03/06 00:56:50 obrien Exp $ + */ + +#include +#include +#include + +#include "net_malloc.h" + +#include "netinet/in.h" + +#ifdef _KERNEL_ +#include +#endif + +/* + * Routines to build and maintain radix trees for routing lookups. + */ +#ifndef _RADIX_H_ +#define M_DONTWAIT M_NOWAIT +#include +#include "net/radix.h" +#endif + +#include "sys/domain.h" + +static int rn_walktree_from (struct radix_node_head *h, void *a, + void *m, walktree_f_t *f, void *w); +static int rn_walktree (struct radix_node_head *, walktree_f_t *, void *); +static struct radix_node + *rn_insert (void *, struct radix_node_head *, int *, + struct radix_node [2]), + *rn_newpair (void *, int, struct radix_node[2]), + *rn_search (void *, struct radix_node *), + *rn_search_m (void *, struct radix_node *, void *); + +static int max_keylen; +static struct radix_mask *rn_mkfreelist; +static struct radix_node_head *mask_rnhead; +static char *addmask_key; +static char normal_chars[] = {0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, -1}; +static char *rn_zeros, *rn_ones; + +#define rn_masktop (mask_rnhead->rnh_treetop) +/* +#undef Bcmp +#define Bcmp(a, b, l) \ + (l == 0 ? 0 : memcpy((caddr_t)(a), (caddr_t)(b), (u_long)l)) +*/ + +static int rn_lexobetter (void *m_arg, void *n_arg); +static struct radix_mask * + rn_new_radix_mask (struct radix_node *tt, + struct radix_mask *next); +static int rn_satsifies_leaf (char *trial, struct radix_node *leaf, + int skip); + +struct radix_node *rn_head_search(void *arg_v) +{ + return rn_search(arg_v, mask_rnhead->rnh_treetop); +} + +/* + * The data structure for the keys is a radix tree with one way + * branching removed. The index rn_bit at an internal node n represents a bit + * position to be tested. The tree is arranged so that all descendants + * of a node n have keys whose bits all agree up to position rn_bit - 1. + * (We say the index of n is rn_bit.) + * + * There is at least one descendant which has a one bit at position rn_bit, + * and at least one with a zero there. + * + * A route is determined by a pair of key and mask. We require that the + * bit-wise logical and of the key and mask to be the key. + * We define the index of a route to associated with the mask to be + * the first bit number in the mask where 0 occurs (with bit number 0 + * representing the highest order bit). + * + * We say a mask is normal if every bit is 0, past the index of the mask. + * If a node n has a descendant (k, m) with index(m) == index(n) == rn_bit, + * and m is a normal mask, then the route applies to every descendant of n. + * If the index(m) < rn_bit, this implies the trailing last few bits of k + * before bit b are all 0, (and hence consequently true of every descendant + * of n), so the route applies to all descendants of the node as well. + * + * Similar logic shows that a non-normal mask m such that + * index(m) <= index(n) could potentially apply to many children of n. + * Thus, for each non-host route, we attach its mask to a list at an internal + * node as high in the tree as we can go. + * + * The present version of the code makes use of normal routes in short- + * circuiting an explict mask and compare operation when testing whether + * a key satisfies a normal route, and also in remembering the unique leaf + * that governs a subtree. + */ + +static struct radix_node * +rn_search(v_arg, head) + void *v_arg; + struct radix_node *head; +{ + register struct radix_node *x; + register caddr_t v; + + for (x = head, v = v_arg; x->rn_bit >= 0;) { + if (x->rn_bmask & v[x->rn_offset]) + x = x->rn_right; + else + x = x->rn_left; + } + return (x); +} + +static struct radix_node * +rn_search_m(v_arg, head, m_arg) + struct radix_node *head; + void *v_arg, *m_arg; +{ + register struct radix_node *x; + register caddr_t v = v_arg, m = m_arg; + + for (x = head; x->rn_bit >= 0;) { + if ((x->rn_bmask & m[x->rn_offset]) && + (x->rn_bmask & v[x->rn_offset])) + x = x->rn_right; + else + x = x->rn_left; + } + return x; +} + +int +rn_refines(m_arg, n_arg) + void *m_arg, *n_arg; +{ + register caddr_t m = m_arg, n = n_arg; + register caddr_t lim, lim2 = lim = n + *(u_char *)n; + int longer = (*(u_char *)n++) - (int)(*(u_char *)m++); + int masks_are_equal = 1; + + if (longer > 0) + lim -= longer; + while (n < lim) { + if (*n & ~(*m)) + return 0; + if (*n++ != *m++) + masks_are_equal = 0; + } + while (n < lim2) + if (*n++) + return 0; + if (masks_are_equal && (longer < 0)) + for (lim2 = m - longer; m < lim2; ) + if (*m++) + return 1; + return (!masks_are_equal); +} + +struct radix_node * +rn_lookup(v_arg, m_arg, head) + void *v_arg, *m_arg; + struct radix_node_head *head; +{ + register struct radix_node *x; + caddr_t netmask = 0; + + if (m_arg) { + x = rn_addmask(m_arg, 1, head->rnh_treetop->rn_offset); + if (x == 0) + return (0); + netmask = x->rn_key; + } + x = rn_match(v_arg, head); + if (x && netmask) { + while (x && x->rn_mask != netmask) + x = x->rn_dupedkey; + } + return x; +} + +static int +rn_satsifies_leaf(trial, leaf, skip) + char *trial; + register struct radix_node *leaf; + int skip; +{ + register char *cp = trial, *cp2 = leaf->rn_key, *cp3 = leaf->rn_mask; + char *cplim; + int length = min(*(u_char *)cp, *(u_char *)cp2); + + if (cp3 == 0) + cp3 = rn_ones; + else + length = min(length, *(u_char *)cp3); + cplim = cp + length; cp3 += skip; cp2 += skip; + for (cp += skip; cp < cplim; cp++, cp2++, cp3++) + if ((*cp ^ *cp2) & *cp3) + return 0; + return 1; +} + +struct radix_node * +rn_match(v_arg, head) + void *v_arg; + struct radix_node_head *head; +{ + caddr_t v = v_arg; + register struct radix_node *t = head->rnh_treetop, *x; + register caddr_t cp = v, cp2; + caddr_t cplim; + struct radix_node *saved_t, *top = t; + int off = t->rn_offset, vlen = *(u_char *)cp, matched_off; + register int test, b, rn_bit; + + /* + * Open code rn_search(v, top) to avoid overhead of extra + * subroutine call. + */ + for (; t->rn_bit >= 0; ) { + if (t->rn_bmask & cp[t->rn_offset]) + t = t->rn_right; + else + t = t->rn_left; + } + /* + * See if we match exactly as a host destination + * or at least learn how many bits match, for normal mask finesse. + * + * It doesn't hurt us to limit how many bytes to check + * to the length of the mask, since if it matches we had a genuine + * match and the leaf we have is the most specific one anyway; + * if it didn't match with a shorter length it would fail + * with a long one. This wins big for class B&C netmasks which + * are probably the most common case... + */ + if (t->rn_mask) + vlen = *(u_char *)t->rn_mask; + cp += off; cp2 = t->rn_key + off; cplim = v + vlen; + for (; cp < cplim; cp++, cp2++) + if (*cp != *cp2) + goto on1; + /* + * This extra grot is in case we are explicitly asked + * to look up the default. Ugh! + * + * Never return the root node itself, it seems to cause a + * lot of confusion. + */ + if (t->rn_flags & RNF_ROOT) + t = t->rn_dupedkey; + return t; +on1: + test = (*cp ^ *cp2) & 0xff; /* find first bit that differs */ + for (b = 7; (test >>= 1) > 0;) + b--; + matched_off = cp - v; + b += matched_off << 3; + rn_bit = -1 - b; + /* + * If there is a host route in a duped-key chain, it will be first. + */ + if ((saved_t = t)->rn_mask == 0) + t = t->rn_dupedkey; + for (; t; t = t->rn_dupedkey) + /* + * Even if we don't match exactly as a host, + * we may match if the leaf we wound up at is + * a route to a net. + */ + if (t->rn_flags & RNF_NORMAL) { + if (rn_bit <= t->rn_bit) + return t; + } else if (rn_satsifies_leaf(v, t, matched_off)) + return t; + t = saved_t; + /* start searching up the tree */ + do { + register struct radix_mask *m; + t = t->rn_parent; + m = t->rn_mklist; + /* + * If non-contiguous masks ever become important + * we can restore the masking and open coding of + * the search and satisfaction test and put the + * calculation of "off" back before the "do". + */ + while (m) { + if (m->rm_flags & RNF_NORMAL) { + if (rn_bit <= m->rm_bit) + return (m->rm_leaf); + } else { + off = min(t->rn_offset, matched_off); + x = rn_search_m(v, t, m->rm_mask); + while (x && x->rn_mask != m->rm_mask) + x = x->rn_dupedkey; + if (x && rn_satsifies_leaf(v, x, off)) + return x; + } + m = m->rm_mklist; + } + } while (t != top); + return 0; +} + +#ifdef RN_DEBUG +int rn_nodenum; +struct radix_node *rn_clist; +int rn_saveinfo; +int rn_debug = 1; +#endif + +static struct radix_node * +rn_newpair(v, b, nodes) + void *v; + int b; + struct radix_node nodes[2]; +{ + register struct radix_node *tt = nodes, *t = tt + 1; + t->rn_bit = b; + t->rn_bmask = 0x80 >> (b & 7); + t->rn_left = tt; + t->rn_offset = b >> 3; + tt->rn_bit = -1; + tt->rn_key = (caddr_t)v; + tt->rn_parent = t; + tt->rn_flags = t->rn_flags = RNF_ACTIVE; + tt->rn_mklist = t->rn_mklist = 0; +#ifdef RN_DEBUG + tt->rn_info = rn_nodenum++; t->rn_info = rn_nodenum++; + tt->rn_twin = t; + tt->rn_ybro = rn_clist; + rn_clist = tt; +#endif + return t; +} + +static struct radix_node * +rn_insert(v_arg, head, dupentry, nodes) + void *v_arg; + struct radix_node_head *head; + int *dupentry; + struct radix_node nodes[2]; +{ + caddr_t v = v_arg; + struct radix_node *top = head->rnh_treetop; + int head_off = top->rn_offset, vlen = (int)*((u_char *)v); + register struct radix_node *t = rn_search(v_arg, top); + register caddr_t cp = v + head_off; + register int b; + struct radix_node *tt; + /* + * Find first bit at which v and t->rn_key differ + */ + { + register caddr_t cp2 = t->rn_key + head_off; + register int cmp_res; + caddr_t cplim = v + vlen; + + while (cp < cplim) + if (*cp2++ != *cp++) + goto on1; + *dupentry = 1; + return t; +on1: + *dupentry = 0; + cmp_res = (cp[-1] ^ cp2[-1]) & 0xff; + for (b = (cp - v) << 3; cmp_res; b--) + cmp_res >>= 1; + } + { + register struct radix_node *p, *x = top; + cp = v; + do { + p = x; + if (cp[x->rn_offset] & x->rn_bmask) + x = x->rn_right; + else + x = x->rn_left; + } while (b > (unsigned) x->rn_bit); + /* x->rn_bit < b && x->rn_bit >= 0 */ +#ifdef RN_DEBUG + if (rn_debug) + log(LOG_DEBUG, "rn_insert: Going In:\n"), traverse(p); +#endif + t = rn_newpair(v_arg, b, nodes); + tt = t->rn_left; + if ((cp[p->rn_offset] & p->rn_bmask) == 0) + p->rn_left = t; + else + p->rn_right = t; + x->rn_parent = t; + t->rn_parent = p; /* frees x, p as temp vars below */ + if ((cp[t->rn_offset] & t->rn_bmask) == 0) { + t->rn_right = x; + } else { + t->rn_right = tt; + t->rn_left = x; + } +#ifdef RN_DEBUG + if (rn_debug) + log(LOG_DEBUG, "rn_insert: Coming Out:\n"), traverse(p); +#endif + } + return (tt); +} + +struct radix_node * +rn_addmask(n_arg, search, skip) + int search, skip; + void *n_arg; +{ + caddr_t netmask = (caddr_t)n_arg; + register struct radix_node *x; + register caddr_t cp, cplim; + register int b = 0, mlen, j; + int maskduplicated, m0, isnormal; + struct radix_node *saved_x; + static int last_zeroed = 0; + + if ((mlen = *(u_char *)netmask) > max_keylen) + mlen = max_keylen; + if (skip == 0) + skip = 1; + if (mlen <= skip) + return (mask_rnhead->rnh_nodes); + if (skip > 1) + Bcopy(rn_ones + 1, addmask_key + 1, skip - 1); + + if ((m0 = mlen) > skip) + Bcopy(netmask + skip, addmask_key + skip, mlen - skip); + + /* + * Trim trailing zeroes. + */ + for (cp = addmask_key + mlen; (cp > addmask_key) && cp[-1] == 0;) + cp--; + mlen = cp - addmask_key; + + if (mlen <= skip) { + if (m0 >= last_zeroed) + last_zeroed = mlen; + return (mask_rnhead->rnh_nodes); + } + if (m0 < last_zeroed) + Bzero(addmask_key + m0, last_zeroed - m0); + *addmask_key = last_zeroed = mlen; + + x = rn_search(addmask_key, rn_masktop); + if (memcmp(addmask_key, x->rn_key, mlen) != 0) + x = NULL; + if (x || search) + return (x); + R_Malloc(x, struct radix_node *, max_keylen + 2 * sizeof (*x)); + if ((saved_x = x) == 0) + return (0); + Bzero(x, max_keylen + 2 * sizeof (*x)); + netmask = cp = (caddr_t)(x + 2); + Bcopy(addmask_key, cp, mlen); + + x = rn_insert(cp, mask_rnhead, &maskduplicated, x); + if (maskduplicated) { + printf("rn_addmask: mask impossibly already in tree\n"); + Free(saved_x); + return (x); + } + /* + * Calculate index of mask, and check for normalcy. + */ + cplim = netmask + mlen; isnormal = 1; + for (cp = netmask + skip; (cp < cplim) && *(u_char *)cp == 0xff;) + cp++; + if (cp != cplim) { + for (j = 0x80; (j & *cp) != 0; j >>= 1) + b++; + if (*cp != normal_chars[b] || cp != (cplim - 1)) + isnormal = 0; + } + b += (cp - netmask) << 3; + x->rn_bit = -1 - b; + if (isnormal) + x->rn_flags |= RNF_NORMAL; + return (x); +} + +static int /* XXX: arbitrary ordering for non-contiguous masks */ +rn_lexobetter(m_arg, n_arg) + void *m_arg, *n_arg; +{ + register u_char *mp = m_arg, *np = n_arg, *lim; + + if (*mp > *np) + return 1; /* not really, but need to check longer one first */ + if (*mp == *np) + for (lim = mp + *mp; mp < lim;) + if (*mp++ > *np++) + return 1; + return 0; +} + +static struct radix_mask * +rn_new_radix_mask(tt, next) + register struct radix_node *tt; + register struct radix_mask *next; +{ + register struct radix_mask *m; + + MKGet(m); + if (m == 0) { + printf("Mask for route not entered\n"); + return (0); + } + Bzero(m, sizeof *m); + m->rm_bit = tt->rn_bit; + m->rm_flags = tt->rn_flags; + if (tt->rn_flags & RNF_NORMAL) + m->rm_leaf = tt; + else + m->rm_mask = tt->rn_mask; + m->rm_mklist = next; + tt->rn_mklist = m; + return m; +} + +struct radix_node * +rn_addroute(v_arg, n_arg, head, treenodes) + void *v_arg, *n_arg; + struct radix_node_head *head; + struct radix_node treenodes[2]; +{ + caddr_t v = (caddr_t)v_arg, netmask = (caddr_t)n_arg; + register struct radix_node *t, *x = 0, *tt; + struct radix_node *saved_tt, *top = head->rnh_treetop; + short b = 0, b_leaf = 0; + int keyduplicated; + caddr_t mmask; + struct radix_mask *m, **mp; + + /* + * In dealing with non-contiguous masks, there may be + * many different routes which have the same mask. + * We will find it useful to have a unique pointer to + * the mask to speed avoiding duplicate references at + * nodes and possibly save time in calculating indices. + */ + if (netmask) { + if ((x = rn_addmask(netmask, 0, top->rn_offset)) == 0) + return (0); + b_leaf = x->rn_bit; + b = -1 - x->rn_bit; + netmask = x->rn_key; + } + /* + * Deal with duplicated keys: attach node to previous instance + */ + saved_tt = tt = rn_insert(v, head, &keyduplicated, treenodes); + if (keyduplicated) { + for (t = tt; tt; t = tt, tt = tt->rn_dupedkey) { + if (tt->rn_mask == netmask) + return (0); + if (netmask == 0 || + (tt->rn_mask && + ((b_leaf < tt->rn_bit) /* index(netmask) > node */ + || rn_refines(netmask, tt->rn_mask) + || rn_lexobetter(netmask, tt->rn_mask)))) + break; + } + /* + * If the mask is not duplicated, we wouldn't + * find it among possible duplicate key entries + * anyway, so the above test doesn't hurt. + * + * We sort the masks for a duplicated key the same way as + * in a masklist -- most specific to least specific. + * This may require the unfortunate nuisance of relocating + * the head of the list. + */ + if (tt == saved_tt) { + struct radix_node *xx = x; + /* link in at head of list */ + (tt = treenodes)->rn_dupedkey = t; + tt->rn_flags = t->rn_flags; + tt->rn_parent = x = t->rn_parent; + t->rn_parent = tt; /* parent */ + if (x->rn_left == t) + x->rn_left = tt; + else + x->rn_right = tt; + saved_tt = tt; x = xx; + } else { + (tt = treenodes)->rn_dupedkey = t->rn_dupedkey; + t->rn_dupedkey = tt; + tt->rn_parent = t; /* parent */ + if (tt->rn_dupedkey) /* parent */ + tt->rn_dupedkey->rn_parent = tt; /* parent */ + } +#ifdef RN_DEBUG + t=tt+1; tt->rn_info = rn_nodenum++; t->rn_info = rn_nodenum++; + tt->rn_twin = t; tt->rn_ybro = rn_clist; rn_clist = tt; +#endif + tt->rn_key = (caddr_t) v; + tt->rn_bit = -1; + tt->rn_flags = RNF_ACTIVE; + } + /* + * Put mask in tree. + */ + if (netmask) { + tt->rn_mask = netmask; + tt->rn_bit = x->rn_bit; + tt->rn_flags |= x->rn_flags & RNF_NORMAL; + } + t = saved_tt->rn_parent; + if (keyduplicated) + goto on2; + b_leaf = -1 - t->rn_bit; + if (t->rn_right == saved_tt) + x = t->rn_left; + else + x = t->rn_right; + /* Promote general routes from below */ + if (x->rn_bit < 0) { + for (mp = &t->rn_mklist; x; x = x->rn_dupedkey) + if (x->rn_mask && (x->rn_bit >= b_leaf) && x->rn_mklist == 0) { + *mp = m = rn_new_radix_mask(x, 0); + if (m) + mp = &m->rm_mklist; + } + } else if (x->rn_mklist) { + /* + * Skip over masks whose index is > that of new node + */ + for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) + if (m->rm_bit >= b_leaf) + break; + t->rn_mklist = m; *mp = 0; + } +on2: + /* Add new route to highest possible ancestor's list */ + if ((netmask == 0) || (b > t->rn_bit )) + return tt; /* can't lift at all */ + b_leaf = tt->rn_bit; + do { + x = t; + t = t->rn_parent; + } while (b <= t->rn_bit && x != top); + /* + * Search through routes associated with node to + * insert new route according to index. + * Need same criteria as when sorting dupedkeys to avoid + * double loop on deletion. + */ + for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) { + if (m->rm_bit < b_leaf) + continue; + if (m->rm_bit > b_leaf) + break; + if (m->rm_flags & RNF_NORMAL) { + mmask = m->rm_leaf->rn_mask; + if (tt->rn_flags & RNF_NORMAL) { + printf( + "Non-unique normal route, mask not entered"); + return tt; + } + } else + mmask = m->rm_mask; + if (mmask == netmask) { + m->rm_refs++; + tt->rn_mklist = m; + return tt; + } + if (rn_refines(netmask, mmask) + || rn_lexobetter(netmask, mmask)) + break; + } + *mp = rn_new_radix_mask(tt, *mp); + return tt; +} + +struct radix_node * +rn_delete(v_arg, netmask_arg, head) + void *v_arg, *netmask_arg; + struct radix_node_head *head; +{ + register struct radix_node *t, *p, *x, *tt; + struct radix_mask *m, *saved_m, **mp; + struct radix_node *dupedkey, *saved_tt, *top; + caddr_t v, netmask; + int b, head_off, vlen; + + v = v_arg; + netmask = netmask_arg; + x = head->rnh_treetop; + tt = rn_search(v, x); + head_off = x->rn_offset; + vlen = *(u_char *)v; + saved_tt = tt; + top = x; + if (tt == 0 || + Bcmp(v + head_off, tt->rn_key + head_off, vlen - head_off)) + return (0); + /* + * Delete our route from mask lists. + */ + if (netmask) { + if ((x = rn_addmask(netmask, 1, head_off)) == 0) + return (0); + netmask = x->rn_key; + while (tt->rn_mask != netmask) + if ((tt = tt->rn_dupedkey) == 0) + return (0); + } + if (tt->rn_mask == 0 || (saved_m = m = tt->rn_mklist) == 0) + goto on1; + if (tt->rn_flags & RNF_NORMAL) { + if (m->rm_leaf != tt || m->rm_refs > 0) { + printf("rn_delete: inconsistent annotation\n"); + return 0; /* dangling ref could cause disaster */ + } + } else { + if (m->rm_mask != tt->rn_mask) { + printf("rn_delete: inconsistent annotation\n"); + goto on1; + } + if (--m->rm_refs >= 0) + goto on1; + } + b = -1 - tt->rn_bit; + t = saved_tt->rn_parent; + if (b > t->rn_bit) + goto on1; /* Wasn't lifted at all */ + do { + x = t; + t = t->rn_parent; + } while (b <= t->rn_bit && x != top); + for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) + if (m == saved_m) { + *mp = m->rm_mklist; + MKFree(m); + break; + } + if (m == 0) { + printf("rn_delete: couldn't find our annotation\n"); + if (tt->rn_flags & RNF_NORMAL) + return (0); /* Dangling ref to us */ + } +on1: + /* + * Eliminate us from tree + */ + if (tt->rn_flags & RNF_ROOT) + return (0); +#ifdef RN_DEBUG + /* Get us out of the creation list */ + for (t = rn_clist; t && t->rn_ybro != tt; t = t->rn_ybro) {} + if (t) t->rn_ybro = tt->rn_ybro; +#endif + t = tt->rn_parent; + dupedkey = saved_tt->rn_dupedkey; + if (dupedkey) { + /* + * at this point, tt is the deletion target and saved_tt + * is the head of the dupekey chain + */ + if (tt == saved_tt) { + /* remove from head of chain */ + x = dupedkey; x->rn_parent = t; + if (t->rn_left == tt) + t->rn_left = x; + else + t->rn_right = x; + } else { + /* find node in front of tt on the chain */ + for (x = p = saved_tt; p && p->rn_dupedkey != tt;) + p = p->rn_dupedkey; + if (p) { + p->rn_dupedkey = tt->rn_dupedkey; + if (tt->rn_dupedkey) /* parent */ + tt->rn_dupedkey->rn_parent = p; + /* parent */ + } else printf("rn_delete: couldn't find us\n"); + } + t = tt + 1; + if (t->rn_flags & RNF_ACTIVE) { +#ifndef RN_DEBUG + *++x = *t; + p = t->rn_parent; +#else + b = t->rn_info; + *++x = *t; + t->rn_info = b; + p = t->rn_parent; +#endif + if (p->rn_left == t) + p->rn_left = x; + else + p->rn_right = x; + x->rn_left->rn_parent = x; + x->rn_right->rn_parent = x; + } + goto out; + } + if (t->rn_left == tt) + x = t->rn_right; + else + x = t->rn_left; + p = t->rn_parent; + if (p->rn_right == t) + p->rn_right = x; + else + p->rn_left = x; + x->rn_parent = p; + /* + * Demote routes attached to us. + */ + if (t->rn_mklist) { + if (x->rn_bit >= 0) { + for (mp = &x->rn_mklist; (m = *mp);) + mp = &m->rm_mklist; + *mp = t->rn_mklist; + } else { + /* If there are any key,mask pairs in a sibling + duped-key chain, some subset will appear sorted + in the same order attached to our mklist */ + for (m = t->rn_mklist; m && x; x = x->rn_dupedkey) + if (m == x->rn_mklist) { + struct radix_mask *mm = m->rm_mklist; + x->rn_mklist = NULL;/*XXX*/ + if (--(m->rm_refs) < 0) + MKFree(m); + m = mm; + } + if (m) + printf( + "rn_delete: Orphaned Mask %p at %p\n", + (void *)m, (void *)x); + } + } + /* + * We may be holding an active internal node in the tree. + */ + x = tt + 1; + if (t != x) { +#ifndef RN_DEBUG + *t = *x; +#else + b = t->rn_info; + *t = *x; + t->rn_info = b; +#endif + t->rn_left->rn_parent = t; + t->rn_right->rn_parent = t; + p = x->rn_parent; + if (p->rn_left == x) + p->rn_left = t; + else + p->rn_right = t; + } +out: + tt->rn_flags &= ~RNF_ACTIVE; + tt[1].rn_flags &= ~RNF_ACTIVE; + return (tt); +} + +/* + * This is the same as rn_walktree() except for the parameters and the + * exit. + */ +static int +rn_walktree_from(h, a, m, f, w) + struct radix_node_head *h; + void *a, *m; + walktree_f_t *f; + void *w; +{ + int error; + struct radix_node *base, *next; + u_char *xa = (u_char *)a; + u_char *xm = (u_char *)m; + register struct radix_node *rn, *last = NULL /* shut up gcc */; + int stopping = 0; + int lastb; + + /* + * rn_search_m is sort-of-open-coded here. + */ + /* printf("about to search\n"); */ + for (rn = h->rnh_treetop; rn->rn_bit >= 0; ) { + last = rn; + /* printf("rn_bit %d, rn_bmask %x, xm[rn_offset] %x\n", + rn->rn_bit, rn->rn_bmask, xm[rn->rn_offset]); */ + if (!(rn->rn_bmask & xm[rn->rn_offset])) { + break; + } + if (rn->rn_bmask & xa[rn->rn_offset]) { + rn = rn->rn_right; + } else { + rn = rn->rn_left; + } + } + /* printf("done searching\n"); */ + + /* + * Two cases: either we stepped off the end of our mask, + * in which case last == rn, or we reached a leaf, in which + * case we want to start from the last node we looked at. + * Either way, last is the node we want to start from. + */ + rn = last; + lastb = rn->rn_bit; + + /* printf("rn %p, lastb %d\n", rn, lastb);*/ + + /* + * This gets complicated because we may delete the node + * while applying the function f to it, so we need to calculate + * the successor node in advance. + */ + while (rn->rn_bit >= 0) + rn = rn->rn_left; + + while (!stopping) { + /* printf("node %p (%d)\n", rn, rn->rn_bit); */ + base = rn; + /* If at right child go back up, otherwise, go right */ + while (rn->rn_parent->rn_right == rn + && !(rn->rn_flags & RNF_ROOT)) { + rn = rn->rn_parent; + + /* if went up beyond last, stop */ + if (rn->rn_bit < lastb) { + stopping = 1; + /* printf("up too far\n"); */ + } + } + + /* Find the next *leaf* since next node might vanish, too */ + for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;) + rn = rn->rn_left; + next = rn; + /* Process leaves */ + while ((rn = base) != NULL) { + base = rn->rn_dupedkey; + /* printf("leaf %p\n", rn); */ + if (!(rn->rn_flags & RNF_ROOT) + && (error = (*f)(rn, w))) + return (error); + } + rn = next; + + if (rn->rn_flags & RNF_ROOT) { + /* printf("root, stopping"); */ + stopping = 1; + } + + } + return 0; +} + +static int +rn_walktree(h, f, w) + struct radix_node_head *h; + walktree_f_t *f; + void *w; +{ + int error; + struct radix_node *base, *next; + register struct radix_node *rn = h->rnh_treetop; + /* + * This gets complicated because we may delete the node + * while applying the function f to it, so we need to calculate + * the successor node in advance. + */ + /* First time through node, go left */ + while (rn->rn_bit >= 0) + rn = rn->rn_left; + for (;;) { + base = rn; + /* If at right child go back up, otherwise, go right */ + while (rn->rn_parent->rn_right == rn + && (rn->rn_flags & RNF_ROOT) == 0) + rn = rn->rn_parent; + /* Find the next *leaf* since next node might vanish, too */ + for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;) + rn = rn->rn_left; + next = rn; + /* Process leaves */ + while ((rn = base)) { + base = rn->rn_dupedkey; + if (!(rn->rn_flags & RNF_ROOT) + && (error = (*f)(rn, w))) + return (error); + } + rn = next; + if (rn->rn_flags & RNF_ROOT) + return (0); + } + /* NOTREACHED */ +} + +int +rn_inithead(head, off) + void **head; + int off; +{ + register struct radix_node_head *rnh; + register struct radix_node *t, *tt, *ttt; + if (*head) + return (1); + R_Malloc(rnh, struct radix_node_head *, sizeof (*rnh)); + if (rnh == 0) + return (0); + Bzero(rnh, sizeof (*rnh)); + *head = rnh; + t = rn_newpair(rn_zeros, off, rnh->rnh_nodes); + ttt = rnh->rnh_nodes + 2; + t->rn_right = ttt; + t->rn_parent = t; + tt = t->rn_left; + tt->rn_flags = t->rn_flags = RNF_ROOT | RNF_ACTIVE; + tt->rn_bit = -1 - off; + *ttt = *tt; + ttt->rn_key = rn_ones; + rnh->rnh_addaddr = rn_addroute; + rnh->rnh_deladdr = rn_delete; + rnh->rnh_matchaddr = rn_match; + rnh->rnh_lookup = rn_lookup; + rnh->rnh_walktree = rn_walktree; + rnh->rnh_walktree_from = rn_walktree_from; + rnh->rnh_treetop = t; + return (1); +} + +void +rn_init() +{ + char *cp, *cplim; + struct domain *dom; + + for (dom = domains; dom; dom = dom->dom_next) + if (dom->dom_maxrtkey > max_keylen) + max_keylen = dom->dom_maxrtkey; + + if (max_keylen == 0) { + printf( + "rn_init: radix functions require max_keylen be set\n"); + return; + } + R_Malloc(rn_zeros, char *, 3 * max_keylen); + if (rn_zeros == NULL) + printf("rn_init"); + Bzero(rn_zeros, 3 * max_keylen); + rn_ones = cp = rn_zeros + max_keylen; + addmask_key = cplim = rn_ones + max_keylen; + while (cp < cplim) + *cp++ = -1; + if (rn_inithead((void **)&mask_rnhead, 0) == 0) + printf("rn_init 2"); +} + diff --git a/src/add-ons/kernel/network/core/route.c b/src/add-ons/kernel/network/core/route.c new file mode 100644 index 0000000000..d1bae659da --- /dev/null +++ b/src/add-ons/kernel/network/core/route.c @@ -0,0 +1,427 @@ +/* route.c */ + +#ifndef _KERNEL_ +#include +#endif +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "net_malloc.h" + +#include "sys/domain.h" +#include "net/route.h" /* includes net/radix.h */ +#include "protocols.h" +#include "net/if.h" + +int rttrash = 0; /* routes in table that should have been freed but hevn't been */ + +#define SA(p) ((struct sockaddr *)(p)) +#define ROUNDUP(a) (a >0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) + +struct radix_node_head **get_rt_tables(void) +{ + return (struct radix_node_head**)rt_tables; +} + +struct rtentry *rtalloc1(struct sockaddr *dst, int report) +{ + struct radix_node_head *rnh = rt_tables[dst->sa_family]; + struct rtentry *rt; + struct radix_node *rn; + struct rtentry *newrt = NULL; + struct rt_addrinfo info; + int msgtype = RTM_MISS; + int err; + + if (rnh && (rn = rnh->rnh_matchaddr((caddr_t)dst, rnh)) + && ((rn->rn_flags & RNF_ROOT) == 0)) { + newrt = rt = (struct rtentry*) rn; + if (report && (rt->rt_flags & RTF_CLONING)) { + err = rtrequest(RTM_RESOLVE, dst, NULL, + NULL, 0, &newrt); + if (err) { + newrt = rt; + rt->rt_refcnt++; + goto miss; + } + if ((rt = newrt) && (rt->rt_flags & RTF_XRESOLVE)) { + msgtype = RTM_RESOLVE; + goto miss; + } + } else + rt->rt_refcnt++; + } + /* XXX - stats? */ + +miss: + if (report) { + memset((caddr_t)&info, 0, sizeof(info)); + info.rti_info[RTAX_DST] = dst; + //rt_missmsg(msgtype, &info, 0, err); + } + return (newrt); +} + +void rtalloc(struct route *ro) +{ + /* can we use what we have?? */ + if (ro && ro->ro_rt && ro->ro_rt->rt_ifp && + (ro->ro_rt->rt_flags & RTF_UP)) { + /* yes */ + return; + } + /* no, get a new route */ + ro->ro_rt = rtalloc1(&ro->ro_dst, 1); +} + +void rtfree(struct rtentry *rt) +{ + struct ifaddr *ifa; + + if (!rt) { + printf("rtfree on a NULL pointer!\n"); + return; + } + + rt->rt_refcnt--; + if (rt->rt_refcnt <= 0 && (rt->rt_flags && RTF_UP) == 0) { + if (rt->rt_nodes->rn_flags & (RNF_ACTIVE | RNF_ROOT)) { + printf("Trying to free nodes we shouldn't be!\n"); + return; + } + rttrash--; + if (rt->rt_refcnt < 0) { + printf("rtfree: %p not freed as the refcnt is negative!\n", rt); + return; + } + ifa = rt->rt_ifa; + IFAFREE(ifa); + Free(rt_key(rt)); + Free(rt); + } +} + +int rtrequest(int req, struct sockaddr *dst, + struct sockaddr *gateway, + struct sockaddr *netmask, + int flags, + struct rtentry **ret_nrt) +{ + int error = 0; + struct rtentry *rt; + struct radix_node *rn = NULL; + struct radix_node_head *rnh; + struct ifaddr *ifa; + struct sockaddr *ndst; + +#define snderr(x) {error = x; goto bad; } + + if ((rnh = rt_tables[dst->sa_family]) == NULL) + snderr(ESRCH); + if (flags & RTF_HOST) + netmask = NULL; + + switch(req) { + case RTM_DELETE: + if ((rn = rnh->rnh_deladdr(dst, netmask, rnh)) == NULL) + snderr(ESRCH); + if (rn->rn_flags & (RNF_ACTIVE | RNF_ROOT)) { + /* XXX - should be panic */ + printf("rtrequest: delete: cannot delete route!\n"); + return -1; + } + rt = (struct rtentry *)rn; + rt->rt_flags &= ~RTF_UP; /* mark route as down */ + if (rt->rt_gwroute) { + rt = rt->rt_gwroute; + RTFREE(rt); + (rt = (struct rtentry*)rn)->rt_gwroute = NULL; + } + if ((ifa = rt->rt_ifa) && ifa->ifa_rtrequest) + ifa->ifa_rtrequest(RTM_DELETE, rt, NULL); + rttrash++; + if (ret_nrt) + *ret_nrt = rt; + else if (rt->rt_refcnt <= 0) { + rt->rt_refcnt++; + rtfree(rt); + } + break; + case RTM_RESOLVE: + if (ret_nrt == NULL || (rt = *ret_nrt) == NULL) + snderr(EINVAL); + ifa = rt->rt_ifa; + flags = rt->rt_flags & ~RTF_CLONING; + gateway = rt->rt_gateway; + if ((netmask = rt->rt_genmask) == NULL) + flags |= RTF_HOST; + /* fall through */ + goto makeroute; + case RTM_ADD: + /* can we find a route to it? */ + if ((ifa = ifa_ifwithroute(flags, dst, gateway)) == NULL) { + printf("ENETUNREACH!\n"); + snderr(ENETUNREACH); + } +makeroute: + R_Malloc(rt, struct rtentry *, sizeof(*rt)); + if (!rt) + snderr(ENOMEM); + Bzero(rt, sizeof(*rt)); + rt->rt_flags = RTF_UP | flags; + if (rt_setgate(rt, dst, gateway)) { + Free(rt); + snderr(ENOMEM); + } + + ndst = rt_key(rt); + if (netmask) + rt_maskedcopy(dst, ndst, netmask); + else + Bcopy(dst, ndst, dst->sa_len); + + rn = rnh->rnh_addaddr((caddr_t) ndst, (caddr_t) netmask, + rnh, rt->rt_nodes); + + if (!rn) { + if (rt->rt_gwroute) + rtfree(rt->rt_gwroute); + Free(rt_key(rt)); + Free(rt); + snderr(EEXIST); + } + ifa->ifa_refcnt++; + rt->rt_ifa = ifa; + rt->rt_ifp = ifa->ifa_ifp; + /* if we've fallen through - copy metrics */ + if (req == RTM_RESOLVE) + rt->rt_rmx = (*ret_nrt)->rt_rmx; + if (ifa->ifa_rtrequest) + ifa->ifa_rtrequest(req, rt, SA(ret_nrt ? *ret_nrt : 0)); + if (ret_nrt) { + *ret_nrt = rt; + rt->rt_refcnt++; + } + break; + } +bad: + return (error); +} + +struct ifaddr *ifa_ifwithroute(int flags, + struct sockaddr *dst, + struct sockaddr *gateway) +{ + struct ifaddr *ifa; + + if ((flags & RTF_GATEWAY) == 0) { + /* + * If we are adding a route to an interface, + * and the interface is a pt to pt link + * we should search for the destination + * as our clue to the interface. Otherwise + * we can use the local address. + */ + ifa = NULL; + if (flags & RTF_HOST) + ifa = ifa_ifwithdstaddr(dst); + if (ifa == NULL) + ifa = ifa_ifwithaddr(gateway); + } else { + /* + * If we are adding a route to a remote net + * or host, the gateway may still be on the + * other end of a pt to pt link. + */ + ifa = ifa_ifwithdstaddr(gateway); + } + if (ifa == NULL) + ifa = ifa_ifwithnet(gateway); + if (ifa == NULL) { + struct rtentry *rt = rtalloc1(gateway, 0); + if (rt == NULL) + return (NULL); + rt->rt_refcnt--; + /* The gateway must be local if the same address family. */ + if ((rt->rt_flags & RTF_GATEWAY) && + rt_key(rt)->sa_family == dst->sa_family) + return (0); + if ((ifa = rt->rt_ifa) == NULL) + return (NULL); + } + if (ifa->ifa_addr->sa_family != dst->sa_family) { + struct ifaddr *oifa = ifa; + ifa = ifaof_ifpforaddr(dst, ifa->ifa_ifp); + if (ifa == NULL) + ifa = oifa; + } + return (ifa); +} + +int rt_setgate(struct rtentry *rt0, + struct sockaddr *dst, + struct sockaddr *gate) +{ + caddr_t new, old = NULL; + int dlen = ROUNDUP(dst->sa_len), glen = ROUNDUP(gate->sa_len); + struct rtentry *rt = rt0; + + + if (rt->rt_gateway == NULL || glen > ROUNDUP(rt->rt_gateway->sa_len)) { + old = (caddr_t)rt_key(rt); + R_Malloc(new, caddr_t, dlen + glen); + if (new == NULL) + return 1; + rt->rt_nodes->rn_key = new; + } else { + new = rt->rt_nodes->rn_key; + old = NULL; + } + + Bcopy(gate, (rt->rt_gateway = (struct sockaddr *)(new + dlen)), glen); + if (old) { + Bcopy(dst, new, dlen); + Free(old); + } + + if (rt->rt_gwroute != NULL) { + rt = rt->rt_gwroute; + RTFREE(rt); + rt = rt0; + rt->rt_gwroute = NULL; + } + + if (rt->rt_flags & RTF_GATEWAY) { + rt->rt_gwroute = rtalloc1(gate, 1); + /* + * If we switched gateways, grab the MTU from the new + * gateway route if the current MTU is 0 or greater + * than the MTU of gateway. + */ + if (rt->rt_gwroute && !(rt->rt_rmx.rmx_locks & RTV_MTU) && + (rt->rt_rmx.rmx_mtu == 0 || + rt->rt_rmx.rmx_mtu > rt->rt_gwroute->rt_rmx.rmx_mtu)) { + rt->rt_rmx.rmx_mtu = rt->rt_gwroute->rt_rmx.rmx_mtu; + } + } + + return 0; +} + +void rt_maskedcopy(struct sockaddr *src, + struct sockaddr *dst, + struct sockaddr *netmask) +{ + uchar *cp1 = (uchar *)src; + uchar *cp2 = (uchar *)dst; + uchar *cp3 = (uchar *)netmask; + uchar *cplim = cp2 + *cp3; + uchar *cplim2 = cp2 + *cp1; + + *cp2++ = *cp1++; *cp2++ = *cp1++; /* copies sa_len & sa_family */ + cp3 += 2; + if (cplim > cplim2) + cplim = cplim2; + while (cp2 < cplim) + *cp2++ = *cp1++ & *cp3++; + if (cp2 < cplim2) + memset((caddr_t)cp2, 0, (unsigned)(cplim2 - cp2)); +} + +void ifafree(struct ifaddr *ifa) +{ + if (ifa == NULL) { + printf("ifafree"); + return; + } + if (ifa->ifa_refcnt == 0) + free(ifa); + else + ifa->ifa_refcnt--; +} + +/* + * Set up a routing table entry, normally + * for an interface. + */ +int rtinit(struct ifaddr *ifa, int cmd, int flags) +{ + struct rtentry *rt; + struct sockaddr *dst; + struct sockaddr *deldst; + struct mbuf *m = NULL; + struct rtentry *nrt = NULL; + int error; + + dst = flags & RTF_HOST ? ifa->ifa_dstaddr : ifa->ifa_addr; + if (cmd == RTM_DELETE) { + if ((flags & RTF_HOST) == 0 && ifa->ifa_netmask) { + m = m_get(MT_SONAME); + if (m == NULL) + return(ENOBUFS); + deldst = mtod(m, struct sockaddr *); + rt_maskedcopy(dst, deldst, ifa->ifa_netmask); + dst = deldst; + } + if ((rt = rtalloc1(dst, 0)) != NULL) { + rt->rt_refcnt--; + if (rt->rt_ifa != ifa) { + if (m != NULL) + (void) m_free(m); + return (flags & RTF_HOST ? EHOSTUNREACH : ENETUNREACH); + } + } + } + + error = rtrequest(cmd, dst, ifa->ifa_addr, ifa->ifa_netmask, + flags | ifa->ifa_flags, &nrt); + + if (cmd == RTM_DELETE && error == 0 && (rt = nrt) != NULL) { + /* XXX - add this when we have routing sockets! + rt_newaddrmsg(cmd, ifa, error, nrt); + */ + if (rt->rt_refcnt <= 0) { + rt->rt_refcnt++; + rtfree(rt); + } + } + if (cmd == RTM_ADD && error == 0 && (rt = nrt) != NULL) { + rt->rt_refcnt--; + if (rt->rt_ifa != ifa) { + printf("rtinit: wrong ifa (%p) was (%p)\n", ifa, rt->rt_ifa); + + if (rt->rt_ifa->ifa_rtrequest) + rt->rt_ifa->ifa_rtrequest(RTM_DELETE, rt, NULL); + IFAFREE(rt->rt_ifa); + rt->rt_ifa = ifa; + rt->rt_ifp = ifa->ifa_ifp; + rt->rt_rmx.rmx_mtu = ifa->ifa_ifp->if_mtu; + ifa->ifa_refcnt++; + if (ifa->ifa_rtrequest) + ifa->ifa_rtrequest(RTM_ADD, rt, NULL); + } + /* XXX - add this when we have routing sockets! + rt_newaddrmsg(cmd, ifa, error, nrt); + */ + } + return (error); +} + +void rtable_init(void **table) +{ + struct domain *dom; + + for (dom = domains; dom; dom = dom->dom_next) + if (dom->dom_rtattach) + dom->dom_rtattach(&table[dom->dom_family], dom->dom_rtoffset); +} + +void route_init(void) +{ + rn_init(); + + rtable_init((void**)rt_tables); +} diff --git a/src/add-ons/kernel/network/core/sockbuf.c b/src/add-ons/kernel/network/core/sockbuf.c new file mode 100644 index 0000000000..3408cc53f1 --- /dev/null +++ b/src/add-ons/kernel/network/core/sockbuf.c @@ -0,0 +1,307 @@ +/* socket "server" */ + +#include +#include + +#ifdef _KERNEL_ +#include +#endif + +#include "sys/socket.h" +#include "sys/socketvar.h" +#include "pools.h" +#include "netinet/in_pcb.h" +#include "net_misc.h" +#include "protocols.h" + +uint32 sb_max = SB_MAX; /* hard value, recompile needed to alter :( */ + +/* + * Allot mbufs to a sockbuf. + * Attempt to scale mbmax so that mbcnt doesn't become limiting + * if buffering efficiency is near the normal case. + */ +int sbreserve(struct sockbuf *sb, uint32 cc) +{ + uint64 dd = (uint64)cc; + uint64 ee = (sb_max * MCLBYTES) / ((MSIZE) + (MCLBYTES)); + + if (cc == 0) + return 0; + if (dd > ee) + return 0; + + sb->sb_hiwat = cc; + sb->sb_mbmax = min((cc * 2), sb_max); + if (sb->sb_lowat > sb->sb_hiwat) + sb->sb_lowat = sb->sb_hiwat; + return (1); +} + +void sbdrop(struct sockbuf *sb, int len) +{ + struct mbuf *m, *mn; + struct mbuf *next; + + next = (m = sb->sb_mb) ? m->m_nextpkt : NULL; + while (len > 0) { + if (m == NULL) { + if (next == NULL) + return; + + m = next; + next = m->m_nextpkt; + continue; + } + if (m->m_len > len) { + m->m_len -= len; + m->m_data += len; + sb->sb_cc -= len; + break; + } + len -= m->m_len; + sbfree(sb, m); + MFREE(m, mn); + m = mn; + } + while (m && m->m_len == 0) { + sbfree(sb, m); + MFREE(m, mn); + m = mn; + } + if (m) { + sb->sb_mb = m; + m->m_nextpkt = next; + } else + sb->sb_mb = next; +} + +/* + * Free all mbufs in a sockbuf. + * Check that all resources are reclaimed. + */ +void sbflush(struct sockbuf *sb) +{ + if (sb->sb_flags & SB_LOCK) { + return; + } + while (sb->sb_mbcnt) + sbdrop(sb, (int)sb->sb_cc); + if (sb->sb_cc || sb->sb_mb) + return; +} + +/* + * Free mbufs held by a socket, and reserved mbuf space. + */ +void sbrelease(struct sockbuf *sb) +{ + sbflush(sb); + sb->sb_hiwat = sb->sb_mbmax = 0; +} + +void sbappend(struct sockbuf *sb, struct mbuf *m) +{ + struct mbuf *n; + + if (!m) + return; + if ((n = sb->sb_mb) != NULL) { + while (n->m_nextpkt) + n = n->m_nextpkt; + do { + if (n->m_flags & M_EOR) { + sbappendrecord(sb, m); /* XXXXXX!!!! */ + return; + } + } while (n->m_next && (n = n->m_next)); + } + sbcompress(sb, m, n); +} + +void sbappendrecord(struct sockbuf *sb, struct mbuf *m0) +{ + struct mbuf *m; + + if (!m0) + return; + if ((m = sb->sb_mb) != NULL) + while (m->m_nextpkt) + m = m->m_nextpkt; + /* + * Put the first mbuf on the queue. + * Note this permits zero length records. + */ + sballoc(sb, m0); + if (m) + m->m_nextpkt = m0; + else + sb->sb_mb = m0; + m = m0->m_next; + m0->m_next = 0; + if (m && (m0->m_flags & M_EOR)) { + m0->m_flags &= ~M_EOR; + m->m_flags |= M_EOR; + } + sbcompress(sb, m, m0); +} + +int sbappendaddr(struct sockbuf *sb, struct sockaddr *asa, + struct mbuf *m0, struct mbuf *control) +{ + struct mbuf *m, *n; + int space = asa->sa_len; + + if (m0 && (m0->m_flags & M_PKTHDR) == 0) + return(-1); + + if (m0) + space += m0->m_pkthdr.len; + for (n = control; n; n = n->m_next) { + space += n->m_len; + if (n->m_next == 0) /* keep pointer to last control buf */ + break; + } + if (space > sbspace(sb)) + return (0); + if (asa->sa_len > MLEN) + return (0); + MGET(m, MT_SONAME); + if (m == NULL) + return (0); + m->m_len = asa->sa_len; + memcpy(mtod(m, caddr_t), (caddr_t)asa, asa->sa_len); + if (n) + n->m_next = m0; /* concatenate data to control */ + else + control = m0; + m->m_next = control; + for (n = m; n; n = n->m_next) + sballoc(sb, n); + if ((n = sb->sb_mb) != NULL) { + while (n->m_nextpkt) + n = n->m_nextpkt; + n->m_nextpkt = m; + } else + sb->sb_mb = m; + + return (1); +} + +void sbcompress(struct sockbuf *sb, struct mbuf *m, struct mbuf *n) +{ + int eor = 0; + struct mbuf *o; + + while (m) { + eor |= m->m_flags & M_EOR; + if (m->m_len == 0 && + (eor == 0 || + (((o = m->m_next) || (o = n)) && + o->m_type == m->m_type))) { + m = m_free(m); + continue; + } + if (n && (n->m_flags & (M_EXT | M_EOR)) == 0 && + (n->m_data + n->m_len + m->m_len) < &n->m_dat[MLEN] && + n->m_type == m->m_type) { + memcpy(mtod(n, caddr_t) + n->m_len, mtod(m, caddr_t), + (unsigned)m->m_len); + n->m_len += m->m_len; + sb->sb_cc += m->m_len; + m = m_free(m); + continue; + } + if (n) + n->m_next = m; + else + sb->sb_mb = m; + sballoc(sb, m); + n = m; + m->m_flags &= ~M_EOR; + m = m->m_next; + n->m_next = 0; + } + if (eor) { + if (n) + n->m_flags |= eor; + else + printf("semi-panic: sbcompress\n"); + } +} + +void sbdroprecord(struct sockbuf *sb) +{ + struct mbuf *m, *mn; + + m = sb->sb_mb; + if (m) { + sb->sb_mb = m->m_nextpkt; + do { + sbfree(sb, m); + MFREE(m, mn); + } while ((m = mn) != NULL); + } +} + +int sbwait(struct sockbuf *sb) +{ + if (sb->sb_cc > 0) + return 0; + sb->sb_flags |= SB_WAIT; + return nsleep(sb->sb_pop, "sbwait", sb->sb_timeo); +} + +void sbinsertoob(struct sockbuf *sb, struct mbuf *m0) +{ + struct mbuf *m; + struct mbuf **mp; + + if (m0 == NULL) + return; + for (mp = &sb->sb_mb; (m = *mp) != NULL; mp = &((*mp)->m_nextpkt)) { +again: + switch (m->m_type) { + case MT_OOBDATA: + continue; /* WANT next train */ + case MT_CONTROL: + if ((m = m->m_next) != NULL) + goto again; /* inspect THIS + * train further */ + } + break; + } + /* + * Put the first mbuf on the queue. + * Note this permits zero length records. + */ + sballoc(sb, m0); + m0->m_nextpkt = *mp; + *mp = m0; + m = m0->m_next; + m0->m_next = 0; + if (m && (m0->m_flags & M_EOR)) { + m0->m_flags &= ~M_EOR; + m->m_flags |= M_EOR; + } + sbcompress(sb, m, m0); +} + +/* + * Lock a sockbuf already known to be locked; + * return any error returned from sleep (EINTR). + */ +int sb_lock(struct sockbuf *sb) +{ + int error; + + while (sb->sb_flags & SB_LOCK) { + sb->sb_flags |= SB_WANT; + error = nsleep(sb->sb_sleep, "sb_lock", 0); + if (error) + return (error); + } + sb->sb_flags |= SB_LOCK; + return (0); +} + diff --git a/src/add-ons/kernel/network/core/socket.c b/src/add-ons/kernel/network/core/socket.c new file mode 100644 index 0000000000..97b5290634 --- /dev/null +++ b/src/add-ons/kernel/network/core/socket.c @@ -0,0 +1,1564 @@ +/* socket "server" */ + +#include +#include +#include +#include +#include +#include + +#include "sys/socket.h" +#include "sys/socketvar.h" +#include "sys/sockio.h" +#include "sys/protosw.h" +#include "pools.h" +#include "net/if.h" +#include "netinet/in.h" +#include "netinet/in_pcb.h" +#include "net_misc.h" +#include "protocols.h" +#include "sys/net_uio.h" +#ifdef _KERNEL_ +#include +#include "core_module.h" +#endif + +#define SBLOCKWAIT(f) (((f) & MSG_DONTWAIT) ? M_NOWAIT : M_WAITOK) + +/* Private prototypes */ +static int checkevent(struct socket *so); + +/* Static global objects... */ +static pool_ctl *spool; +static benaphore sockets_lock; + +/* OpenBSD sets this at 128??? */ +static int somaxconn = SOMAXCONN; + +/* for now - should be moved to be_error.h */ +#define EDESTADDRREQ EINVAL + +int sockets_init(void) +{ + if (!spool) + pool_init(&spool, sizeof(struct socket)); + + if (!spool) + return ENOMEM; + + INIT_BENAPHORE(sockets_lock, "sockets_lock"); + return CHECK_BENAPHORE(sockets_lock); +} + + +void sockets_shutdown(void) +{ + pool_destroy(spool); + + UNINIT_BENAPHORE(sockets_lock); +} + + +/* uiomove! */ + +int uiomove(caddr_t cp, int n, struct uio *uio) +{ + struct iovec *iov; + uint cnt; + int error = 0; + void *ptr = NULL; + + while (n > 0 && uio->uio_resid) { + iov = uio->uio_iov; + cnt = iov->iov_len; + + if (cnt == 0) { + uio->uio_iov++; + uio->uio_iovcnt--; + continue; + } + if (cnt > n) + cnt = n; + + switch (uio->uio_segflg) { + /* XXX - once "properly" in kernel space, revisit and + * fix this for kernel moves... + */ + case UIO_USERSPACE: + case UIO_SYSSPACE: + if (uio->uio_rw == UIO_READ) + ptr = memcpy(iov->iov_base, cp, cnt); + else + ptr = memcpy(cp, iov->iov_base, cnt); + + if (!ptr) + return (errno); + break; + } + iov->iov_base = (caddr_t)iov->iov_base + cnt; + iov->iov_len -= cnt; + uio->uio_resid -= cnt; + uio->uio_offset += cnt; + cp += cnt; + n -= cnt; + } + return (error); +} + +int initsocket(void **sp) +{ + struct socket *so; + + so = (struct socket*)pool_get(spool); + + if (so == NULL) { + printf("initsocket: ENOMEM\n"); + return ENOMEM; + } + + memset(so, 0, sizeof(*so)); + + *sp = so; + + return 0; +} + +int socreate(int dom, void *sp, int type, int proto) +{ + struct protosw *prm = NULL; /* protocol module */ + struct socket *so = (struct socket*)sp; + int error; + + if (so == NULL) { + printf("socreate: EINVAL\n"); + return EINVAL; + } + + if (proto) + prm = pffindproto(dom, proto, type); + else + prm = pffindtype(dom, type); + + if (!prm || !prm->pr_userreq) { + printf("socreate: EPROTONOSUPPORT\n"); + return EPROTONOSUPPORT; + } + + if (prm->pr_type != type) { + printf("socreate: EPROTOTYPE\n"); + return EPROTOTYPE; + } + + so->so_type = type; + so->so_proto = prm; + /* Our sem's... don't like using so many here - find another way :( */ + so->so_rcv.sb_pop = create_sem(0, "so_rcv.sb_pop sem"); + so->so_snd.sb_pop = create_sem(0, "so_snd.sb_pop sem"); + so->so_timeo = create_sem(0, "so_timeo sem"); + so->so_rcv.sb_sleep = create_sem(0, "so_rcv.sb_sleep sem"); + so->so_snd.sb_sleep = create_sem(0, "so_snd.sb_sleep sem"); + + if (so->so_rcv.sb_pop < 0 || + so->so_rcv.sb_sleep < 0 || + so->so_snd.sb_pop < 0 || + so->so_snd.sb_sleep < 0 || + so->so_timeo < 0) + return ENOMEM; + +#ifdef _KERNEL_ + set_sem_owner(so->so_rcv.sb_pop, B_SYSTEM_TEAM); + set_sem_owner(so->so_snd.sb_pop, B_SYSTEM_TEAM); + set_sem_owner(so->so_timeo, B_SYSTEM_TEAM); + set_sem_owner(so->so_rcv.sb_sleep, B_SYSTEM_TEAM); + set_sem_owner(so->so_snd.sb_pop, B_SYSTEM_TEAM); +#endif + + error = prm->pr_userreq(so, PRU_ATTACH, NULL, (struct mbuf *)proto, NULL); + if (error) { + so->so_state |= SS_NOFDREF; /* so we free the socket */ + sofree(so); + return error; + } + + return 0; +} + + +int soreserve(struct socket *so, uint32 sndcc, uint32 rcvcc) +{ + if (sbreserve(&so->so_snd, sndcc) == 0) + goto bad; + if (sbreserve(&so->so_rcv, rcvcc) == 0) + goto bad2; + + if (so->so_rcv.sb_lowat == 0) + so->so_rcv.sb_lowat = 1; + if (so->so_snd.sb_lowat == 0) + so->so_snd.sb_lowat = MCLBYTES; + if (so->so_snd.sb_lowat > so->so_snd.sb_hiwat) + so->so_snd.sb_lowat = so->so_snd.sb_hiwat; + + return (0); + +bad2: + sbrelease(&so->so_snd); +bad: + printf("soreserve: ENOBUFS\n"); + return (ENOBUFS); +} + + +int sobind(void *sp, caddr_t data, int len) +{ + int error; + struct mbuf *nam; + struct socket *so = (struct socket*)sp; + + nam = m_get(MT_SONAME); + if (!nam) { + printf("sobind: ENOMEM\n"); + return ENOMEM; + } + + nam->m_len = len; + memcpy(mtod(nam, char*), data, len); + + /* xxx - locking! */ + error = (*so->so_proto->pr_userreq) (so, PRU_BIND, NULL, nam, NULL); + + m_freem(nam); + + return error; +} + +int solisten(void *sp, int backlog) +{ + struct socket *so = (struct socket *)sp; + int error; + + error = so->so_proto->pr_userreq(so, PRU_LISTEN, NULL, NULL, NULL); + if (error) + return error; + + if (so->so_q == 0) + so->so_options |= SO_ACCEPTCONN; + if (backlog < 0 || backlog > somaxconn) + backlog = somaxconn; + /* OpenBSD defines a minimum of 80...hmmm... */ + if (backlog < 0) + backlog = 0; + so->so_qlimit = backlog; + return 0; +} + +int soconnect(void *sp, caddr_t data, int len) +{ + struct socket *so = (struct socket *)sp; + struct mbuf *nam = m_get(MT_SONAME); + int error; + + if (!nam) + return ENOMEM; + + if ((so->so_state & SS_NBIO) && (so->so_state & SS_ISCONNECTING)) + return EALREADY; + + if ((so->so_options & SO_ACCEPTCONN)) + return (EOPNOTSUPP); + + nam->m_len = len; + memcpy(mtod(nam, char*), data, len); + + /* + * If protocol is connection-based, can only connect once. + * Otherwise, if connected, try to disconnect first. + * This allows user to disconnect by connecting to, e.g., + * a null address. + */ + if ((so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) && + ((so->so_proto->pr_flags & PR_CONNREQUIRED) || + (error = sodisconnect(so)))) { + error = EISCONN; + } else { + error = so->so_proto->pr_userreq(so, PRU_CONNECT, + NULL, nam, NULL); + } + + if (error) { + goto bad; + } + + if ((so->so_state & SS_NBIO) && (so->so_state && SS_ISCONNECTING)) { + m_freem(nam); + return EINPROGRESS; + } + + while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) + if ((error = nsleep(so->so_timeo, "soconnect", 0))) + break; + + if (error == 0) { + error = so->so_error; + so->so_error = 0; + } + +bad: + so->so_state &= ~SS_ISCONNECTING; + m_freem(nam); + + return error; +} + +struct socket *sonewconn(struct socket *head, int connstatus) +{ + struct socket *so; + int soqueue = connstatus ? 1 : 0; + + if (head->so_qlen + head->so_q0len > 3 * head->so_qlimit / 2) + return NULL; + if (initsocket((void**)&so) < 0) + return NULL; + + so->so_type = head->so_type; + so->so_options = head->so_options & ~SO_ACCEPTCONN; + so->so_linger = head->so_linger; + so->so_state = head->so_state | SS_NOFDREF; + so->so_proto = head->so_proto; + so->so_timeo = head->so_timeo; + + soreserve(so, head->so_snd.sb_hiwat, head->so_rcv.sb_hiwat); + soqinsque(head, so, soqueue); + + if ((*so->so_proto->pr_userreq)(so, PRU_ATTACH, NULL, NULL, NULL)) { + soqremque(so, soqueue); + pool_put(spool, so); + return NULL; + } + if(connstatus) { + sorwakeup(head); + wakeup(head->so_timeo); + so->so_state |= connstatus; + } + return so; +} + +int soshutdown(void *sp, int how) +{ + struct socket *so = (struct socket*)sp; + struct protosw *pr = so->so_proto; + + how++; + if (how & SHUT_RD) + sorflush(so); + if (how & SHUT_WR) + return pr->pr_userreq(so, PRU_SHUTDOWN, NULL, NULL, NULL); + return 0; +} + +int sendit(void *sp, struct msghdr *mp, int flags, int *retsize) +{ + struct socket *so = (struct socket *)sp; + struct uio auio; + struct iovec *iov; + int i; + struct mbuf *to; + struct mbuf *control; + int len; + int error; + + auio.uio_iov = mp->msg_iov; + auio.uio_iovcnt = mp->msg_iovlen; + auio.uio_segflg = UIO_USERSPACE; + auio.uio_rw = UIO_WRITE; + auio.uio_offset = 0; + auio.uio_resid = 0; + iov = mp->msg_iov; + + /* Make sure we don't exceed max size... */ + for (i=0;i < mp->msg_iovlen;i++, iov++) { + if (iov->iov_len > SSIZE_MAX || + (auio.uio_resid += iov->iov_len) > SSIZE_MAX) + return EINVAL; + } + if (mp->msg_name) { + /* stick msg_name into an mbuf */ + to = m_get(MT_SONAME); + to->m_len = mp->msg_namelen; + memcpy(mtod(to, char*), mp->msg_name, mp->msg_namelen); + } else + to = NULL; + + if (mp->msg_control) { + if (mp->msg_controllen < sizeof(struct cmsghdr)) { + error = EINVAL; + goto bad; + } + control = m_get(MT_CONTROL); + control->m_len = mp->msg_controllen; + memcpy(mtod(control, char*), mp->msg_control, mp->msg_controllen); + } else + control = NULL; + + len = auio.uio_resid; + + error = sosend(so, to, &auio, NULL, control, flags); + if (error) { + /* what went wrong! */ + if (auio.uio_resid != len && (/*error == ERESTART || */ + error == EINTR || error == EWOULDBLOCK)) + error = 0; /* not really an error */ + } + if (error == 0) + *retsize = len - auio.uio_resid; /* tell them how much we did send */ +bad: + if (to) + m_freem(to); + + return error; +} + +int writeit(void *sp, struct iovec *iov, int flags) +{ + struct socket *so = (struct socket *)sp; + struct uio auio; + int len = iov->iov_len; + int error; + + auio.uio_iov = iov; + auio.uio_iovcnt = 1; + auio.uio_segflg = UIO_USERSPACE; + auio.uio_rw = UIO_WRITE; + auio.uio_offset = 0; + auio.uio_resid = iov->iov_len; + + error = sosend(so, NULL, &auio, NULL, NULL, flags); + + if (error < 0) + return error; + return (len - auio.uio_resid); +} + +int sosend(struct socket *so, struct mbuf *addr, struct uio *uio, struct mbuf *top, + struct mbuf *control, int flags) +{ + struct mbuf **mp; + struct mbuf *m; + int32 space; + int32 len; + uint64 resid; + int clen = 0; + int error, dontroute, mlen; + int atomic = sosendallatonce(so) || top; + + if (uio) + resid = uio->uio_resid; + else + resid = top->m_pkthdr.len; + + /* resid shouldn't be below 0 and also a flag of MSG_EOR on a + * SOCK_STREAM isn't allowed (doesn't even make sense!) + */ + if (resid < 0 || (so->so_type == SOCK_STREAM && (flags & MSG_EOR))) { + error = EINVAL; + goto release; + } + + dontroute = (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 && + (so->so_proto->pr_flags & PR_ATOMIC); + + if (control) + clen = control->m_len; + +#define snderr(errno) { error = errno; /* unlock */ goto release; } +restart: + if ((error = sblock(&so->so_snd, SBLOCKWAIT(flags)))) + goto out; + + /* Main Loop! We should loop here until resid == 0 */ + do { + if ((so->so_state & SS_CANTSENDMORE)) + snderr(EPIPE); + if (so->so_error) + snderr(so->so_error); + if ((so->so_state & SS_ISCONNECTED) == 0) { + if (so->so_proto->pr_flags & PR_CONNREQUIRED) { + /* we need to be connected and we're not... */ + if ((so->so_state & SS_ISCONFIRMING) == 0 && + !(resid == 0 && clen != 0)) + /* we're not even trying to connect and we + * have data to send, so it's an error! + * return ENOTCONN + */ + snderr(ENOTCONN); + } else if (addr == NULL) + /* UDP is a connectionless protocol, so it can work + * without being connected as long as we tell it where we + * want to send the data :) + */ + /* Doh! No address to send to (UDP) */ + snderr(EDESTADDRREQ); + } + space = sbspace(&so->so_snd); + + if (flags & MSG_OOB) + space += 1024; + + if ((atomic && resid > so->so_snd.sb_hiwat) || clen > so->so_snd.sb_hiwat) + snderr(EMSGSIZE); + + if (space < resid + clen && uio && + (atomic || space < so->so_snd.sb_lowat || space < clen)) { + if ((so->so_state & SS_NBIO)) { /* non blocking set */ + printf("so->so_state & SS_NBIO (%d)\n", so->so_state & SS_NBIO); + snderr(EWOULDBLOCK); + } + /* free lock - we're waiting on send buffer space */ + sbunlock(&so->so_snd); + error = sbwait(&so->so_snd); + if (error) + goto out; + goto restart; + } + mp = ⊤ + space -= clen; + + do { + if (!uio) { + /* data is actually just packaged as top. */ + resid = 0; + if (flags & MSG_EOR) + top->m_flags |= M_EOR; + } else do { + if (!top) { + MGETHDR(m, MT_DATA); + mlen = MHLEN; + m->m_pkthdr.len = 0; + m->m_pkthdr.rcvif = NULL; + } else { + MGET(m, MT_DATA); + mlen = MLEN; + } + if (resid >= MINCLSIZE && space >= MCLBYTES) { + MCLGET(m); + if ((m->m_flags & M_EXT) == 0) + /* didn't get a cluster */ + goto nopages; + + mlen = MCLBYTES; + if (atomic && !top) { + len = min(MCLBYTES - max_hdr, resid); + m->m_data += max_hdr; + } else + len = min(MCLBYTES, resid); + space -= len; + } else { +nopages: + len = min(min(mlen, resid), space); + space -= len; + /* leave room for headers if required */ + if (atomic && !top && len < mlen) + MH_ALIGN(m, len); + } + + error = uiomove(mtod(m, caddr_t), (int)len, uio); + resid = uio->uio_resid; + m->m_len = len; + *mp = m; + top->m_pkthdr.len += len; + if (error) + goto release; + mp = &m->m_next; + if (resid <= 0) { + if (flags & MSG_EOR) + /* we're the last record */ + top->m_flags |= M_EOR; + break; + } + } while (space > 0 && atomic); + + if (dontroute) + so->so_options |= SO_DONTROUTE; + + /* XXX - locking */ + error = (*so->so_proto->pr_userreq)(so, (flags & MSG_OOB) ? PRU_SENDOOB: PRU_SEND, + top, addr, control); + + /* XXX - unlock */ + if (dontroute) + so->so_options &= ~SO_DONTROUTE; + clen = 0; + top = NULL; + control = NULL; + mp = ⊤ + if (error) + goto release; + } while (resid && space > 0); + } while (resid); + +release: + sbunlock(&so->so_snd); +out: + if (top) + m_freem(top); + if (control) + m_freem(control); + return (error); +} + +int readit(void *sp, struct iovec *iov, int *flags) +{ + struct socket *so = (struct socket *)sp; + struct uio auio; + int len = iov->iov_len; + int error; + + auio.uio_iov = iov; + auio.uio_iovcnt = 1; + auio.uio_segflg = UIO_USERSPACE; + auio.uio_rw = UIO_READ; + auio.uio_offset = 0; + auio.uio_resid = iov->iov_len; + + error = soreceive(so, NULL, &auio, NULL, NULL, flags); + if (error != 0) + return error; + return (len - auio.uio_resid); +} + +int recvit(void *sp, struct msghdr *mp, caddr_t namelenp, int *retsize) +{ + struct socket *so = (struct socket*)sp; + struct uio auio; + struct iovec *iov; + struct mbuf *control = NULL; + struct mbuf *from = NULL; + int error = 0, i, len = 0; + + auio.uio_iov = mp->msg_iov; + auio.uio_iovcnt = mp->msg_iovlen; + auio.uio_segflg = UIO_USERSPACE; + auio.uio_rw = UIO_READ; + auio.uio_offset = 0; + auio.uio_resid = 0; + iov = mp->msg_iov; + + for (i=0; i < mp->msg_iovlen; i++, iov++) { + if (iov->iov_len < 0) + return EINVAL; + if ((auio.uio_resid += iov->iov_len) < 0) + return EINVAL; + } + len = auio.uio_resid; + + if ((error = soreceive(so, &from, &auio, + NULL, mp->msg_control ? &control : NULL, + &mp->msg_flags)) != 0) { + if (auio.uio_resid != len && (error == EINTR || error == EWOULDBLOCK)) + error = 0; + } + + if (error) + goto out; + + *retsize = len - auio.uio_resid; + + if (mp->msg_name) { + len = mp->msg_namelen; + + if (len <= 0 || !from) { + len = 0; + } else { + if (len > from->m_len) + len = from->m_len; + memcpy((caddr_t)mp->msg_name, mtod(from, caddr_t), len); + } + mp->msg_namelen = len; + if (namelenp) + memcpy(namelenp, (caddr_t)&len, sizeof(int)); + } + + /* XXX - add control handling */ +out: + if (from) + m_freem(from); + if (control) + m_freem(control); + + return error; +} + + +int soreceive(struct socket *so, struct mbuf **paddr, struct uio *uio, struct mbuf**mp0, + struct mbuf **controlp, int *flagsp) +{ + struct mbuf *m, **mp; + int flags = 0; + int len, error = 0, offset; + struct mbuf *nextrecord; + int moff, type = 0; + int orig_resid = uio->uio_resid; + struct protosw *pr = so->so_proto; + + mp = mp0; + if (paddr) + *paddr = NULL; + if (controlp) + *controlp = NULL; + + /* ensure we don't have MSG_EOR set */ + if (flagsp) + flags = (*flagsp) & ~MSG_EOR; + + if (flags & MSG_OOB) { + m = m_get(MT_DATA); + error = (*pr->pr_userreq)(so, PRU_RCVOOB, m, (struct mbuf*)(flags & MSG_PEEK), NULL); + if (error) + goto bad; + do { + error = uiomove(mtod(m, caddr_t), (int) min(uio->uio_resid, m->m_len), uio); + m = m_free(m); + } while (uio->uio_resid && error == 0 && m); +bad: + if (m) + m_freem(m); + return error; + } + if (mp) + *mp = NULL; + if ((so->so_state & SS_ISCONFIRMING) && uio->uio_resid) + (*pr->pr_userreq)(so, PRU_RCVD, NULL, NULL, NULL); + +restart: + if ((error = sblock(&so->so_rcv, SBLOCKWAIT(flags)))) + return error; + m = so->so_rcv.sb_mb; + /* + * If we have less data than requested, block awaiting more + * (subject to any timeout) if: + * 1. the current count is less than the low water mark, + * 2. MSG_WAITALL is set, and it is possible to do the entire + * receive operation at once if we block (resid <= hiwat), or + * 3. MSG_DONTWAIT is not set. + * If MSG_WAITALL is set but resid is larger than the receive buffer, + * we have to do the receive in sections, and thus risk returning + * a short count if a timeout or signal occurs after we start. + */ + if (m == NULL || (((flags & MSG_DONTWAIT) == 0 && + so->so_rcv.sb_cc < uio->uio_resid) && + (so->so_rcv.sb_cc < so->so_rcv.sb_lowat || + ((flags & MSG_WAITALL) && uio->uio_resid <= so->so_rcv.sb_hiwat)) && + m->m_nextpkt == 0 && (pr->pr_flags & PR_ATOMIC) == 0)) { + + if (so->so_error) { + if (m) + goto dontblock; + error = so->so_error; + if ((flags & MSG_PEEK) == 0) + so->so_error = 0; + goto release; + } + if ((so->so_state & SS_CANTRCVMORE)) { + if (m) + goto dontblock; + else + goto release; + } + for (;m; m = m->m_next) + if (m->m_type == MT_OOBDATA || (m->m_flags & M_EOR)) { + m = so->so_rcv.sb_mb; + goto dontblock; + } + if ((so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING)) == 0 && + (so->so_proto->pr_flags & PR_CONNREQUIRED)) { + error = ENOTCONN; + goto release; + } + if (uio->uio_resid == 0) + goto release; + if ((so->so_state & SS_NBIO) || (flags & MSG_DONTWAIT)) { + error = EWOULDBLOCK; + goto release; + } + sbunlock(&so->so_rcv); + error = sbwait(&so->so_rcv); + if (error) + return error; + goto restart; + } + +dontblock: + nextrecord = m->m_nextpkt; + if (pr->pr_flags & PR_ADDR) { + orig_resid = 0; + if (flags & MSG_PEEK) { + if (!paddr) + *paddr = m_copym(m, 0, m->m_len); + m = m->m_next; + } else { + sbfree(&so->so_rcv, m); + if (paddr) { + *paddr = m; + so->so_rcv.sb_mb = m->m_next; + m->m_next = NULL; + m = so->so_rcv.sb_mb; + } else { + MFREE(m, so->so_rcv.sb_mb); + m = so->so_rcv.sb_mb; + } + } + } + while (m && m->m_type == MT_CONTROL && error == 0) { + if ((flags & MSG_PEEK)) { + if (controlp) + *controlp = m_copym(m, 0, m->m_len); + m = m->m_next; + } else { + sbfree(&so->so_rcv, m); + if (controlp) { + /* XXX technically we should look at control rights here, + * but so far we have no notion of them... + */ + *controlp = m; + so->so_rcv.sb_mb = m->m_next; + m->m_next = NULL; + m = so->so_rcv.sb_mb; + } else { + MFREE(m, so->so_rcv.sb_mb); + m = so->so_rcv.sb_mb; + } + } + if (controlp) { + orig_resid = 0; + controlp = &(*controlp)->m_next; + } + } + if (m) { + if ((flags & MSG_PEEK) == 0) + m->m_nextpkt = nextrecord; + type = m->m_type; + if (type == MT_OOBDATA) + flags |= MSG_OOB; + } + moff = 0; + offset = 0; + while (m && uio->uio_resid > 0 && error == 0) { + if (m->m_type == MT_OOBDATA) { + if (type != MT_OOBDATA) + break; + } else if (type == MT_OOBDATA) { + break; + } + so->so_state &= ~SS_RCVATMARK; + len = uio->uio_resid; + if (so->so_oobmark && len > so->so_oobmark - offset) + len = so->so_oobmark - offset; + if (len > m->m_len - moff) + len = m->m_len - moff; + if (!mp) + error = uiomove(mtod(m, caddr_t) + moff, (int)len, uio); + else + uio->uio_resid -= len; + if (len == m->m_len - moff) { + if (m->m_flags & M_EOR) + flags |= MSG_EOR; + if (flags & MSG_PEEK) { + m = m->m_next; + moff = 0; + } else { + nextrecord = m->m_nextpkt; + sbfree(&so->so_rcv, m); + if (mp) { + *mp = m; + mp = &m->m_next; + so->so_rcv.sb_mb = m = m->m_next; + *mp = NULL; + } else { + MFREE(m, so->so_rcv.sb_mb); + m = so->so_rcv.sb_mb; + } + if (m) + m->m_nextpkt = nextrecord; + } + } else { + if ((flags & MSG_PEEK)) + moff += len; + else { + if (mp) + *mp = m_copym(m, 0, len); + m->m_data += len; + m->m_len -= len; + so->so_rcv.sb_cc -= len; + } + } + if (so->so_oobmark) { + if ((flags & MSG_PEEK) == 0) { + so->so_oobmark -= len; + if (so->so_oobmark == 0) { + so->so_state |= SS_RCVATMARK; + break; + } + } else { + offset += len; + if (offset == so->so_oobmark) + break; + } + } + if (flags & MSG_EOR) + break; + while (flags & MSG_WAITALL && m == NULL && uio->uio_resid > 0 && + !sosendallatonce(so) && !nextrecord) { + if (so->so_error || so->so_state & SS_CANTRCVMORE) + break; + error = sbwait(&so->so_rcv); + if (error) { + sbunlock(&so->so_rcv); + return 0; + } + if ((m = so->so_rcv.sb_mb)) + nextrecord = m->m_nextpkt; + } + } + + if (m && pr->pr_flags & PR_ATOMIC) { + flags |= MSG_TRUNC; + if ((flags & MSG_PEEK) == 0) + sbdroprecord(&so->so_rcv); + } + if ((flags & MSG_PEEK) == 0) { + if (!m) + so->so_rcv.sb_mb = nextrecord; + if (pr->pr_flags & PR_WANTRCVD && so->so_pcb) + (*pr->pr_userreq)(so, PRU_RCVD, (struct mbuf*)flags, NULL, NULL); + } + if (orig_resid == uio->uio_resid && orig_resid && + (flags & MSG_EOR) == 0 && (so->so_state & SS_CANTRCVMORE) == 0) { + sbunlock(&so->so_rcv); + goto restart; + } + if (flagsp) + *flagsp |= flags; + +release: + sbunlock(&so->so_rcv); + return error; +} + +int soo_ioctl(void *sp, int cmd, caddr_t data) +{ + struct socket *so = (struct socket*)sp; + + switch (cmd) { + case FIONBIO: + if (*(int*)data) + so->so_state |= SS_NBIO; + else + so->so_state &= ~SS_NBIO; + return 0; + case FIONREAD: + /* how many bytes do we have waiting... */ + *(int*)data = so->so_rcv.sb_cc; + return 0; + case SIOCATMARK: + *(int*)data = (so->so_state & SS_RCVATMARK) != 0; + return 0; + } + + if (IOCGROUP(cmd) == 'i') { + return ifioctl(so, cmd, data); + } + if (IOCGROUP(cmd) == 'r') { + return EINVAL; /* EOPNOTSUPP */ + } + return (*so->so_proto->pr_userreq)(so, PRU_CONTROL, + (struct mbuf*)cmd, (struct mbuf*)data, NULL); +} + + +int soclose(void *sp) +{ + struct socket *so = (struct socket*)sp; + int error = 0; + + /* we don't want any more events... */ + so->event_callback = NULL; + so->event_callback_cookie = NULL; + + if (so->so_options & SO_ACCEPTCONN) { + while (so->so_q0) + (*so->so_proto->pr_userreq)(so, PRU_ABORT, NULL, NULL, NULL); + while (so->so_q) + (*so->so_proto->pr_userreq)(so, PRU_ABORT, NULL, NULL, NULL); + } + + if (so->so_pcb == NULL) + goto discard; + + if (so->so_state & SS_ISCONNECTED) { + if ((so->so_state & SS_ISDISCONNECTING) == 0) { + error = sodisconnect(so); + if (error) + goto drop; + } + if (so->so_options & SO_LINGER) { + if ((so->so_state & SS_ISDISCONNECTING) && + (so->so_state & SS_NBIO)) + goto drop; + while (so->so_state & SS_ISCONNECTED) + if ((error = nsleep(so->so_timeo, "lingering close", so->so_linger))) + break; + } + } + +drop: + if (so->so_pcb) { + int error2 = (*so->so_proto->pr_userreq)(so, PRU_DETACH, NULL, NULL, NULL); + if (error2 == 0) + error = error2; + } + +discard: + if (so->so_state & SS_NOFDREF) + printf("PANIC: soclose: NOFDREF"); + so->so_state |= SS_NOFDREF; + sofree(so); + return error; +} + +int sorflush(struct socket *so) +{ + struct sockbuf *sb = &so->so_rcv; + struct sockbuf asb; + + sb->sb_flags |= SB_NOINTR; + sblock(sb, M_WAITOK); + socantrcvmore(so); + sbunlock(sb); + asb = *sb; + memset(sb, 0, sizeof(*sb)); + + sbrelease(&asb); + return 0; +} + +int sodisconnect(struct socket *so) +{ + int error; + + if ((so->so_state & SS_ISCONNECTED) == 0) { + error = ENOTCONN; + goto bad; + } + if (so->so_state & SS_ISDISCONNECTING) { + error = EALREADY; + goto bad; + } + error = so->so_proto->pr_userreq(so, PRU_DISCONNECT, + NULL, NULL, NULL); + +bad: + return error; +} + +void sofree(struct socket *so) +{ + if (so->so_pcb || (so->so_state & SS_NOFDREF) == 0) + return; + + if (so->so_head) { + if (!soqremque(so, 0) && !soqremque(so, 1)) { + printf("PANIC: sofree: couldn't dq socket\n"); + return; + } + so->so_head = NULL; + } + sbrelease(&so->so_snd); + sorflush(so); + + delete_sem(so->so_rcv.sb_pop); + delete_sem(so->so_snd.sb_pop); + delete_sem(so->so_timeo); + delete_sem(so->so_rcv.sb_sleep); + delete_sem(so->so_snd.sb_sleep); + + pool_put(spool, so); + + return; +} + +int sosetopt(void *sp, int level, int optnum, const void *data, size_t datalen) +{ + struct socket *so = (struct socket*)sp; + struct mbuf *m, *m0; + int error = 0; + + m = m_get(MT_SOOPTS); + if (!m) + return ENOMEM; + if (memcpy(mtod(m, void*), data, datalen) == NULL) + return ENOMEM; + m->m_len = datalen; + m0 = m; + + if (level != SOL_SOCKET) { + if (so->so_proto && so->so_proto->pr_ctloutput) + return (*so->so_proto->pr_ctloutput)(PRCO_SETOPT, so, level, optnum, &m0); + error = ENOPROTOOPT; + } else { + switch (optnum) { + case SO_LINGER: + if (datalen != sizeof(struct linger)) { + error = EINVAL; + goto bad; + } + so->so_linger = mtod(m, struct linger*)->l_linger; + /* fall thru... */ + case SO_DEBUG: + case SO_KEEPALIVE: + case SO_DONTROUTE: + case SO_USELOOPBACK: + case SO_BROADCAST: + case SO_REUSEADDR: + case SO_REUSEPORT: + case SO_OOBINLINE: + if (datalen < sizeof(int)) { + error = EINVAL; + goto bad; + } + if (*mtod(m, int*)) + so->so_options |= optnum; + else + so->so_options &= ~optnum; + break; + case SO_SNDBUF: + case SO_RCVBUF: + case SO_SNDLOWAT: + case SO_RCVLOWAT: + if (datalen < sizeof(int)) { + error = EINVAL; + goto bad; + } + switch (optnum) { + case SO_SNDBUF: + case SO_RCVBUF: + if (sbreserve(optnum == SO_SNDBUF ? &so->so_snd : &so->so_rcv, + (uint32)*mtod(m, int32*)) == 0) { + error = ENOBUFS; + goto bad; + } + break; + case SO_SNDLOWAT: + so->so_snd.sb_lowat = *mtod(m, int*); + break; + case SO_RCVLOWAT: + so->so_rcv.sb_lowat = *mtod(m, int*); + break; + } + break; + case SO_SNDTIMEO: + case SO_RCVTIMEO: + /* bsd has the timeouts as int16, we're using int32... */ + { + struct timeval *tv; + int32 val; + + if (datalen < sizeof(*tv)) { + error = EINVAL; + goto bad; + } + tv = mtod(m, struct timeval *); + val = tv->tv_sec * 1000000 + tv->tv_usec; + switch (optnum) { + case SO_SNDTIMEO: + so->so_snd.sb_timeo = val; + break; + case SO_RCVTIMEO: + so->so_rcv.sb_timeo = val; + break; + } + break; + } + default: + error = ENOPROTOOPT; + } + if (error == 0 && so->so_proto && so->so_proto->pr_ctloutput) { + (*so->so_proto->pr_ctloutput)(PRCO_SETOPT, so, level, optnum, &m0); + m = NULL; + } + } +bad: + if (m) + m_free(m); + + return error; +} + +int sogetopt(void *sp, int level, int optnum, void *data, size_t *datalen) +{ + struct socket *so = (struct socket*)sp; + struct mbuf *m; + + m = m_get(MT_SOOPTS); + if (memcpy(mtod(m, void*), data, *datalen) == NULL) + return ENOMEM; + if (*datalen < sizeof(int)) + return EINVAL; + m->m_len = sizeof(int); + + if (level != SOL_SOCKET) { + if (so->so_proto && so->so_proto->pr_ctloutput) { + return (*so->so_proto->pr_ctloutput)(PRCO_GETOPT, so, level, optnum, &m); + } else + return ENOPROTOOPT; + } else { + switch(optnum) { + case SO_LINGER: + m->m_len = sizeof(struct linger); + mtod(m, struct linger*)->l_onoff = so->so_options & SO_LINGER; + mtod(m, struct linger*)->l_linger = so->so_linger; + break; + case SO_DEBUG: + case SO_KEEPALIVE: + case SO_DONTROUTE: + case SO_USELOOPBACK: + case SO_BROADCAST: + case SO_REUSEADDR: + case SO_REUSEPORT: + case SO_OOBINLINE: + *mtod(m, int*) = so->so_options & optnum; + break; + case SO_TYPE: + *mtod(m, int*) = so->so_type; + break; + case SO_ERROR: + *mtod(m, int*) = so->so_error; + so->so_error = 0; /* cleared once read */ + break; + case SO_SNDBUF: + *mtod(m, int*) = so->so_snd.sb_hiwat; + break; + case SO_RCVBUF: + *mtod(m, int*) = so->so_rcv.sb_hiwat; + break; + case SO_SNDLOWAT: + *mtod(m, int*) = so->so_snd.sb_lowat; + break; + case SO_RCVLOWAT: + *mtod(m, int*) = so->so_rcv.sb_lowat; + break; + case SO_SNDTIMEO: + case SO_RCVTIMEO: + { + int32 val = (optnum == SO_SNDTIMEO ? so->so_snd.sb_timeo : so->so_rcv.sb_timeo); + + m->m_len = sizeof(struct timeval); + mtod(m, struct timeval*)->tv_sec = val / 1000000; + mtod(m, struct timeval*)->tv_usec = val % 1000000; + break; + } + default: + m_free(m); + return ENOPROTOOPT; + } + if (m->m_len > *datalen) + /* XXX - horrible fudge... */ + m->m_len = *datalen; + memcpy(data, mtod(m, void *), m->m_len); + *datalen = m->m_len; + return 0; + } +} + + +int set_socket_event_callback(void * sp, socket_event_callback cb, void * cookie, int event) +{ + struct socket *so = (struct socket *) sp; + + so->event_callback = cb; + so->event_callback_cookie = cookie; + if (cb) { + if (event == 3) + so->sel_ev |= SEL_EX; + else + so->sel_ev |= event; + } else { + if (event == 3) + so->sel_ev &= ~SEL_EX; + else + so->sel_ev &= ~event; + } + checkevent(so); /* notify any event condition ASAP! */ + return B_OK; +} + + +static int checkevent(struct socket *so) +{ + if (!so || !so->event_callback) + return B_OK; + + /* XXX - The checks here may seem a bit excessive, but we have a race + * condition here. If we're checking for soreadable() in one + * thread while we're closing in another, things will blow up as + * so->event_callback will have been set to NULL. + * + * ??? Maybe we should lock the socket for things like this? + */ + if (so && (so->sel_ev & SEL_READ) && soreadable(so)) { + if (so->event_callback) + so->event_callback(so, 1, so->event_callback_cookie); + so->sel_ev &= ~SEL_READ; + } + if (so && (so->sel_ev & SEL_WRITE) && sowriteable(so)) { + if (so->event_callback) + so->event_callback(so, 2, so->event_callback_cookie); + so->sel_ev &= ~SEL_WRITE; + } + if (so && (so->sel_ev & SEL_EX) && + (so->so_oobmark || (so->so_state & SS_RCVATMARK))) { + if (so->event_callback) + so->event_callback(so, 3, so->event_callback_cookie); + so->sel_ev &= ~SEL_EX; + } + + return B_OK; +} + +void sowakeup(struct socket *so, struct sockbuf *sb) +{ + if (!so || !sb) + return; + + sb->sb_flags &= ~SB_SEL; + if (sb->sb_flags & SB_WAIT) { + sb->sb_flags &= ~SB_WAIT; + /* release the lock here... */ + release_sem_etc(sb->sb_pop, 1, B_CAN_INTERRUPT); + } + checkevent(so); +} + +void soqinsque(struct socket *head, struct socket *so, int q) +{ + struct socket **prev; + so->so_head = head; + if (q == 0) { + head->so_q0len++; + so->so_q0 = 0; + for (prev = &(head->so_q0); *prev; ) + prev = &((*prev)->so_q0); + } else { + head->so_qlen++; + so->so_q = NULL; + for (prev = &(head->so_q); *prev; ) + prev = &((*prev)->so_q); + } + *prev = so; +} + +int soqremque(struct socket *so, int q) +{ + struct socket *head, *prev, *next; + + head = so->so_head; + prev = head; + for (;;) { + next = q ? prev->so_q : prev->so_q0; + if (next == so) + break; + if (next == NULL) + return 0; + prev = next; + } + if (q == 0) { + prev->so_q0 = next->so_q0; + head->so_q0len--; + } else { + prev->so_q = next->so_q; + head->so_qlen--; + } + next->so_q0 = next->so_q = 0; + next->so_head = 0; + return 1; +} + +void sohasoutofband(struct socket *so) +{ + /* Should we signal the process with SIGURG??? */ + checkevent(so); +} + +void socantsendmore(struct socket *so) +{ + so->so_state |= SS_CANTSENDMORE; + sowwakeup(so); +} + +void socantrcvmore(struct socket *so) +{ + so->so_state |= SS_CANTRCVMORE; + sorwakeup(so); +} + +void soisconnecting(struct socket *so) +{ + so->so_state &= ~(SS_ISCONNECTED|SS_ISDISCONNECTING); + so->so_state |= SS_ISCONNECTING; +} + +void soisconnected(struct socket *so) +{ + struct socket *head = so->so_head; + + so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING); + so->so_state |= SS_ISCONNECTED; + if (head && soqremque(so, 0)) { + soqinsque(head, so, 1); + sorwakeup(head); + wakeup(head->so_timeo); + } else { + wakeup(so->so_timeo); + sorwakeup(so); + sowwakeup(so); + } +} + +void soisdisconnecting(struct socket *so) +{ + so->so_state &= ~SS_ISCONNECTING; + so->so_state |= (SS_ISDISCONNECTING|SS_CANTRCVMORE|SS_CANTSENDMORE); + wakeup(so->so_timeo); + sowwakeup(so); + sorwakeup(so); +} + +void soisdisconnected(struct socket *so) +{ + so->so_state &= ~(SS_ISCONNECTING|SS_ISCONNECTED|SS_ISDISCONNECTING); + so->so_state |= (SS_CANTRCVMORE|SS_CANTSENDMORE|SS_ISDISCONNECTED); + wakeup(so->so_timeo); + sowwakeup(so); + sorwakeup(so); +} + +int nsleep(sem_id chan, char *msg, int timeo) +{ + status_t rv; +// printf("nsleep: %s (%ld)\n", msg, chan); + + if (timeo > 0) + rv = acquire_sem_etc(chan, 1, B_TIMEOUT|B_CAN_INTERRUPT, timeo); + else + rv = acquire_sem_etc(chan, 1, B_CAN_INTERRUPT, 0); + + if (rv == B_TIMED_OUT) { + printf ("nsleep: EWOULDBLOCK\n"); + return EWOULDBLOCK; + } + if (rv == B_INTERRUPTED) + return EINTR; + return 0; +} + +void wakeup(sem_id chan) +{ + /* we should release as many as are waiting... + * the number 100 is just something that shuld be large enough... + */ + release_sem_etc(chan, 100, B_CAN_INTERRUPT | B_DO_NOT_RESCHEDULE); +} + +/* This file is too big - split it up!! */ +int sogetsockname(void *sp, struct sockaddr *sa, int *alen) +{ + struct socket *so = (struct socket*)sp; + struct mbuf *m = m_getclr(MT_SONAME); + int len; + int error; + + if (!m) + return ENOBUFS; + memcpy(&len, alen, sizeof(len)); + error = (*so->so_proto->pr_userreq)(so, PRU_SOCKADDR, NULL, m, NULL); + if (error == 0) { + if (len > m->m_len) + len = m->m_len; + memcpy(sa, mtod(m, caddr_t), len); + memcpy(alen, &len, sizeof(len)); + } + m_freem(m); + return error; +} + +int sogetpeername(void *sp, struct sockaddr *sa, int * alen) +{ + struct socket *so = (struct socket*)sp; + struct mbuf *m = m_getclr(MT_SONAME); + int len; + int error; + + if (!m) + return ENOBUFS; + if ((so->so_state & (SS_ISCONNECTED|SS_ISCONFIRMING)) == 0) + return ENOTCONN; + memcpy(&len, alen, sizeof(len)); + error = (*so->so_proto->pr_userreq)(so, PRU_PEERADDR, NULL, m, NULL); + if (error == 0) { + if (len > m->m_len) + len = m->m_len; + memcpy(sa, mtod(m, caddr_t), len); + memcpy(alen, &len, sizeof(len)); + } + m_freem(m); + return error; +} + +int soaccept(void *sp, void **nsp, void *data, int *alen) +{ + struct socket * so = (struct socket *)sp; + int len; + int error; + struct mbuf *nam; + + if (data) + memcpy(&len, alen, sizeof(len)); + if ((so->so_options & SO_ACCEPTCONN) == 0) + return EINVAL; + if ((so->so_state & SS_NBIO) && so->so_qlen == 0) + return EWOULDBLOCK; + while (so->so_qlen == 0 && so->so_error == 0) { + if (so->so_state & SS_CANTRCVMORE) { + so->so_error = ECONNABORTED; + break; + } + if ((error = nsleep(so->so_timeo, "soaccept", 0))) + return error; + } + if (so->so_error) { + error = so->so_error; + so->so_error = 0; + return error; + } + + { + struct socket *aso = so->so_q; + if (soqremque(aso, 1) == 0) { + printf("PANIC: soaccept!\n"); + return ENOMEM; + } + so = aso; + } + nam = m_get(MT_SONAME); + so->so_state &= ~SS_NOFDREF; + error = (*so->so_proto->pr_userreq)(so, PRU_ACCEPT, NULL, nam, NULL); + if (data) { + if (len > nam->m_len) + len = nam->m_len; + if (memcpy(data, mtod(nam, caddr_t), len) == NULL) + memcpy(alen, (caddr_t)&len, sizeof(len)); + } + m_freem(nam); + + /* assign the socket to the cookie passed in! */ + *nsp = so; + return error; +} diff --git a/src/add-ons/kernel/network/interfaces/Jamfile b/src/add-ons/kernel/network/interfaces/Jamfile new file mode 100644 index 0000000000..39e28b9c09 --- /dev/null +++ b/src/add-ons/kernel/network/interfaces/Jamfile @@ -0,0 +1,5 @@ +SubDir OBOS_TOP src add-ons kernel network interfaces ; + +SubInclude OBOS_TOP src add-ons kernel network interfaces ethernet ; +SubInclude OBOS_TOP src add-ons kernel network interfaces loopback ; + diff --git a/src/add-ons/kernel/network/interfaces/ethernet/Jamfile b/src/add-ons/kernel/network/interfaces/ethernet/Jamfile new file mode 100644 index 0000000000..4c2be8b5f8 --- /dev/null +++ b/src/add-ons/kernel/network/interfaces/ethernet/Jamfile @@ -0,0 +1,2 @@ +SubDir OBOS_TOP src add-ons kernel network interfaces ethernet ; + diff --git a/src/add-ons/kernel/network/interfaces/ethernet/ethernet.c b/src/add-ons/kernel/network/interfaces/ethernet/ethernet.c new file mode 100644 index 0000000000..735695064c --- /dev/null +++ b/src/add-ons/kernel/network/interfaces/ethernet/ethernet.c @@ -0,0 +1,938 @@ +/* ethernet.c + * ethernet encapsulation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "protocols.h" +#include "netinet/in_var.h" +#include "sys/protosw.h" +#include "net/if.h" +#include "net/if_arp.h" +#include "net/if_dl.h" +#include "netinet/if_ether.h" +#include "sys/socket.h" +#include "sys/sockio.h" +#include "net/route.h" + +#include "core_module.h" +#include "core_funcs.h" +#include "net_timer.h" + +#define ETHERNET_MODULE_PATH "network/interfaces/ethernet" + +#ifdef _KERNEL_MODE + #include + #define spawn_thread spawn_kernel_thread + + /* forward prototypes */ + int ether_dev_start(ifnet *dev); + int ether_dev_stop (ifnet *dev); +#endif + +static int32 std_ops(int32 op, ...); + +/* Local variables */ +static struct protosw *proto[IPPROTO_MAX]; +static struct core_module_info *core = NULL; +static net_timer_id arptimer_id; +static struct ether_device *ether_devices = NULL; /* list of ethernet devices */ +static struct ifq *etherq = NULL; +static thread_id ether_rxt = -1; +static int arpt_prune = (5 * 60); /* time interval we prune the arp cache? 5 minutes */ +static int arpt_keep = (20 * 60); /* length of time we keep entries... (20 mins) */ +static int arpt_down = 20; /* seconds between arp flooding */ +static int arp_maxtries = 5; /* max tries before a pause */ +static int32 arp_inuse = 0; /* how many entries do we have? */ +static int32 arp_allocated = 0; /* how many arp entries have we created? */ + +/* Prototypes */ +int32 ether_input(void *data); +int ether_output(struct ifnet *ifp, struct mbuf *buf, struct sockaddr *dst, + struct rtentry *rt0); +static int ether_ioctl(struct ifnet *ifp, int cmd, caddr_t data); +int ether_dev_attach(ifnet *dev); +int ether_dev_stop(ifnet *dev); +void arp_rtrequest(int req, struct rtentry *rt, struct sockaddr *sa); +static void arpinput(struct mbuf *m); + + +#define DRIVER_DIRECTORY "/dev/net" +#define SIN(s) ((struct sockaddr_in*)s) +#define SDL(s) ((struct sockaddr_dl*)s) +#define rt_expire rt_rmx.rmx_expire + +static uint8 ether_bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + +struct llinfo_arp llinfo_arp; +struct in_ifaddr *primary_addr; + +static char digits[] = "0123456789abcdef"; + +static char *ether_sprintf(uint8 *ap) +{ + register int i; + static char etherbuf[18]; + register char *cp = etherbuf; + + for (i = 0; i < 6; i++) { + *cp++ = digits[*ap >> 4]; + *cp++ = digits[*ap++ & 0xf]; + *cp++ = ':'; + } + *--cp = 0; + return (etherbuf); +} + +#if ARP_DEBUG +static void dump_arp(void *buffer) +{ + struct ether_arp *arp = (struct ether_arp *)buffer; + + printf("arp request :\n"); + printf(" : hardware type : %s\n", + ntohs(arp->arp_hrd) == ARPHRD_ETHER ? "ethernet" : "unknown"); + printf(" : protocol type : %s\n", + ntohs(arp->arp_pro) == ETHERTYPE_IP ? "IPv4" : "unknown"); + printf(" : hardware size : %d\n", arp->arp_hln); + printf(" : protocol size : %d\n", arp->arp_pln); + printf(" : op code : "); + switch(ntohs(arp->arp_op)) { + case ARPOP_REPLY: + printf("ARP Reply\n"); + break; + case ARPOP_REQUEST: + printf("ARP Request\n"); + break; + default: + printf("Who knows? %04x\n", ntohs(arp->arp_op)); + } + printf(" : sender : %s", ether_sprintf(&arp->arp_sha)); + printf(" [%08lx]\n", ntohl(*(uint32*)&arp->arp_spa)); + printf(" : target : %s", ether_sprintf(&arp->arp_tha)); + printf(" [%08lx]\n", ntohl(*(uint32*)&arp->arp_tpa)); +} +#endif /* ARP_DEBUG */ + +/* We now actually attach the device to the system... */ +static void attach_device(int devid, char *driver, char *devno) +{ + struct ether_device *ed; + struct ifnet *ifp; + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + status_t status; + int fsz = 0; + + ed = malloc(sizeof(struct ether_device)); + if (!ed) + return; + memset(ed, 0, sizeof(*ed)); + ifp = &ed->sc_if; + + /* get the MAC address... */ + status = ioctl(devid, IF_GETADDR, &ed->sc_addr, 6); + if (status < B_OK) { + printf("%s/%s: ignored: Failed to get a MAC address\n", driver, devno); + close(devid); + free(ed); + return; + } + /* Try to detrmine the MTU to use */ + status = ioctl(devid, IF_GETFRAMESIZE, &fsz, sizeof(fsz)); + if (status < 0) { + printf("%s/%s: IF_GETFRAMESIZE not supported, defaulting to %d\n", + driver, devno, ETHERMTU); + ifp->if_mtu = ETHERMTU; + } else + ifp->if_mtu = fsz; + + ifp->devid = -1; + ifp->if_type = IFT_ETHER; + ifp->name = strdup(driver); + ifp->if_unit = atoi(devno); + ifp->if_hdrlen = 14; + ifp->if_addrlen = 6; + ifp->if_flags |= (IFF_BROADCAST|IFF_SIMPLEX|IFF_MULTICAST); + + ifp->rx_thread = -1; + ifp->tx_thread = -1; + ifp->devq = etherq; + + ifp->input = NULL;//ðer_input; + ifp->output = ðer_output; + ifp->stop = ðer_dev_stop; + ifp->ioctl = ðer_ioctl; + + if_attach(ifp); + + /* Add the MAC address to our list of addresses... */ + for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) { + if ((sdl = (struct sockaddr_dl*)ifa->ifa_addr) && + sdl->sdl_family == AF_LINK) { + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = ifp->if_addrlen; + memcpy(LLADDR(sdl), &ed->sc_addr, ifp->if_addrlen); + break; + } + } + + ed->next = NULL; /* we get added at the end of the list */ + /* we maintain our own list of devices as well as the global list */ + if (!ether_devices) { + ether_devices = ed; + } else { + struct ether_device *dptr = ether_devices; + while (dptr->next) + dptr = dptr->next; + + dptr->next = ed; + } +} + +static void open_device(char *driver, char *devno) +{ + char path[PATH_MAX]; + int dev; + status_t status = -1; + + sprintf(path, "%s/%s/%s", DRIVER_DIRECTORY, driver, devno); + dev = open(path, O_RDWR); + if (dev < B_OK) { + printf("Unable to open %s, %ld [%s]\n", path, + status, strerror(status)); + return; + } + + status = ioctl(dev, IF_INIT, NULL, 0); + if (status == B_OK) + attach_device(dev, driver, devno); + + close(dev); +}; + +static void find_devices(void) +{ + DIR *dir, *driv_dir; + struct dirent *de, *dre; + char path[PATH_MAX]; + + dir = opendir(DRIVER_DIRECTORY); + if (!dir) { + printf("Couldn't open the directory %s\n", DRIVER_DIRECTORY); + return; + } + + while ((de = readdir(dir)) != NULL) { + /* hmm, is it a driver? */ + if (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0 || + strcmp(de->d_name, "socket") == 0 || + strcmp(de->d_name, "stack") == 0) + continue; + + /* OK we assume it's a driver...but skip the ether driver + * as I don't really know what it is! + */ + if (strcmp(de->d_name, "ether") == 0) + continue; + + sprintf(path, "%s/%s", DRIVER_DIRECTORY, de->d_name); + driv_dir = opendir(path); + if (!driv_dir) { + printf("I couldn't find any drivers in the %s driver directory\n", + de->d_name); + } else { + while ((dre = readdir(driv_dir)) != NULL) { + /* skip . and .. */ + if (strcmp(dre->d_name, ".") == 0 || + strcmp(dre->d_name, "..") == 0) + continue; + + open_device(de->d_name, dre->d_name); + } + closedir(driv_dir); + } + } + closedir(dir); + + return; +} + +#if SHOW_DEBUG +static void dump_ether_details(struct mbuf *buf) +{ + struct ether_header *eth = mtod(buf, struct ether_header *); + + printf("Ethernet packet from "); + print_ether_addr(ð->src); + printf(" to "); + print_ether_addr(ð->dest); + + if (buf->m_flags & M_BCAST) + printf(" BCAST"); + + printf(" proto "); + switch (eth->type) { + case ETHER_ARP: + printf("ARP\n"); + break; + case ETHER_RARP: + printf("RARP\n"); + break; + case ETHER_IPV4: + printf("IPv4\n"); + break; + case ETHER_IPV6: + printf("IPv6\n"); + break; + default: + printf("unknown (%04x)\n", eth->type); + } +} +#endif + +int32 ether_input(void *data) +{ + struct mbuf *m; + struct ether_header *eth; + int len; + + while (1) { + len = sizeof(struct ether_header); + acquire_sem_etc(etherq->pop, 1, B_CAN_INTERRUPT, 0); + IFQ_DEQUEUE(etherq, m); + if (!m) + continue; + + eth = mtod(m, struct ether_header *); + eth->ether_type = ntohs(eth->ether_type); + + if (memcmp((void*)ð->ether_dhost, (void*)ðer_bcast, 6) == 0) + m->m_flags |= M_BCAST; + if (eth->ether_dhost[0] & 1) + m->m_flags |= M_MCAST; + +#if SHOW_DEBUG + dump_ether_details(buf); +#endif + m_adj(m, len); + + switch(eth->ether_type) { + case ETHERTYPE_ARP: + arpinput(m); + break; + case ETHERTYPE_IP: + if (proto[IPPROTO_IP] && proto[IPPROTO_IP]->pr_input) + proto[IPPROTO_IP]->pr_input(m, 0); + else + printf("proto[%d] = %p, not called...\n", IPPROTO_IP, + proto[IPPROTO_IP]); + break; + case ETHERTYPE_PPPOEDISC: + case ETHERTYPE_PPPOE: + printf("PPPoE packet detected...not yet implemented :)\n"); + m_freem(m); + break; + default: + printf("Couldn't process unknown protocol %04x\n", eth->ether_type); + m_freem(m); + } + } + + return 0; +} + +#define senderr(e) { error = (e); goto bad; } + +int ether_output(struct ifnet *ifp, struct mbuf *buf, struct sockaddr *dst, + struct rtentry *rt0) +{ + struct ether_header *eh; + struct rtentry *rt; + struct arpcom *ac = (struct arpcom*)ifp; + uint8 edst[6]; + int off; + uint16 type; + struct mbuf *mcopy = NULL; + int error = 0; + + if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) + senderr(ENETDOWN); + + if ((rt = rt0)) { + if ((rt->rt_flags & RTF_UP) == 0) { + if ((rt0 = rt = rtalloc1(dst, 1)) != NULL) + rt->rt_refcnt--; + else + senderr(EHOSTUNREACH); + } + if (rt->rt_flags & RTF_GATEWAY) { + if (!rt->rt_gwroute) + goto lookup; + if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) { + rtfree(rt0); + rt = rt0; +lookup: + rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1); + + if ((rt = rt->rt_gwroute) == NULL) + senderr(EHOSTUNREACH); + } + } + if (rt->rt_flags & RTF_REJECT) + if (rt->rt_expire == 0 || + rt->rt_expire > real_time_clock()) { + /* XXX - add test for expired here... */ + printf("flags & RTF_REJECT\n"); + printf("Error: %s\n", rt == rt0 ? "EHOSTDOWN" : "EHOSTUNREACH"); + senderr(rt == rt0 ? EHOSTDOWN : EHOSTUNREACH); + } + } + + switch (dst->sa_family) { + case AF_INET: + if (!arpresolve(ac, rt, buf, dst, edst)) { + return 0; + } + if ((buf->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) + mcopy = m_copym(buf, 0, (int)M_COPYALL); + off = buf->m_pkthdr.len - buf->m_len; + type = ETHERTYPE_IP; + break; + case AF_UNSPEC: + eh = (struct ether_header*)dst->sa_data; + memcpy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof(edst)); + type = eh->ether_type; + break; + } + +/* Hmmm, can't find a good way of getting this to work without expanding the + * macro for the kernel case, so it's here expanded. I've found that using the + * directly causes segafults. Bear in mind we want ALL allocation/free actions + * to take place in the core to keep as small a memory footprint as possible. + */ +#define M_LEADINGSPACE(m) \ + ((m)->m_flags & M_EXT ? (m)->m_data - (m)->m_ext.ext_buf : \ + (m)->m_flags & M_PKTHDR ? (m)->m_data - (m)->m_pktdat : \ + (m)->m_data - (m)->m_dat) + + if (M_LEADINGSPACE(buf) >= sizeof(struct ether_header)) { + buf->m_data -= sizeof(struct ether_header); + buf->m_len += sizeof(struct ether_header); + } else + buf = m_prepend(buf, sizeof(struct ether_header)); + if (buf && buf->m_flags & M_PKTHDR) + buf->m_pkthdr.len += sizeof(struct ether_header); + + if (buf == NULL) + senderr(ENOMEM); + eh = mtod(buf, struct ether_header*); + type = htons(type); + memcpy(&eh->ether_type, &type, sizeof(eh->ether_type)); + memcpy(&eh->ether_dhost, edst, sizeof(edst)); + memcpy(&eh->ether_shost, ac->ac_enaddr, sizeof(eh->ether_shost)); + + IFQ_ENQUEUE(ifp->txq, buf); + + return error; +bad: + if (buf) + m_free(buf); + printf("ether_output: returning %d\n", error); + return error; +} + +static struct llinfo_arp *arplookup(uint32 addr, int create, int proxy) +{ + struct rtentry *rt; + static struct sockaddr_inarp sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_addr.s_addr = addr; + sin.sin_other = proxy ? SIN_PROXY : 0; + rt = rtalloc1((struct sockaddr*) &sin, create); + if (!rt) + return NULL; + rt->rt_refcnt--; + if ((rt->rt_flags & RTF_GATEWAY) || (rt->rt_flags & RTF_LLINFO) == 0 || + rt->rt_gateway->sa_family != AF_LINK) { + if (create) + printf("arptnew failed on %08lx\n", ntohl(addr)); + return NULL; + } + return ((struct llinfo_arp *)rt->rt_llinfo); +} + +int arpresolve(struct arpcom *ac, struct rtentry *rt, struct mbuf *m, + struct sockaddr *dst, uint8 *desten) +{ + struct llinfo_arp *la; + struct sockaddr_dl *sdl; + + if (m->m_flags & M_BCAST) { + memcpy(desten, ðer_bcast, sizeof(ether_bcast)); + return 1; + } + if (m->m_flags & M_MCAST) { + ETHER_MAP_IP_MULTICAST(&SIN(dst)->sin_addr, desten); + return 1; + } + + if (rt) { + la = (struct llinfo_arp*) rt->rt_llinfo; + } else { + if ((la = arplookup(SIN(dst)->sin_addr.s_addr, 1, 0))) + rt = la->la_rt; + } + if (la == NULL || rt == NULL) { + printf("arpresolve: can't allocate llinfo!\n"); + m_freem(m); + return 0; + } + sdl = SDL(rt->rt_gateway); + + if ((rt->rt_expire == 0 || rt->rt_expire > real_time_clock()) && + sdl->sdl_family == AF_LINK && sdl->sdl_alen != 0) { + memcpy(desten, LLADDR(sdl), sdl->sdl_alen); + return 1; + } + + if (la->la_hold) { + m_freem(la->la_hold); + } + + la->la_hold = m; + + if (rt->rt_expire) { + rt->rt_flags &= ~RTF_REJECT; + if (la->la_asked == 0 || rt->rt_expire != real_time_clock()) { + rt->rt_expire = real_time_clock(); + if (la->la_asked++ < arp_maxtries) { + arpwhohas(ac, &(SIN(dst)->sin_addr)); + } else { + rt->rt_flags |= RTF_REJECT; + rt->rt_expire += arpt_down; + la->la_asked = 0; + } + } + } + return 0; +} + +static void arptfree(struct llinfo_arp *la) +{ + struct rtentry *rt = la->la_rt; + struct sockaddr_dl *sdl; + + if (rt == NULL) + return; + if (rt->rt_refcnt > 0 && (sdl = SDL(rt->rt_gateway)) && + sdl->sdl_family == AF_LINK) { + sdl->sdl_alen = 0; + la->la_asked = 0; + rt->rt_flags &= ~RTF_REJECT; + return; + } + rtrequest(RTM_DELETE, rt_key(rt), NULL, rt_mask(rt), 0, NULL); +} + +static void arptimer(void *data) +{ + struct llinfo_arp *la = llinfo_arp.la_next; + while (la != &llinfo_arp) { + struct rtentry *rt = la->la_rt; + la = la->la_next; + if (rt->rt_expire && rt->rt_expire <= real_time_clock()) + arptfree(la->la_prev); + } + return; +} + +static void arprequest(struct arpcom *ac, uint32 *sip, uint32 *tip, uint8 *enaddr) +{ + struct mbuf *m; + struct ether_header *eh; + struct ether_arp *ea; + struct sockaddr sa; + + if ((m = m_gethdr(MT_DATA)) == NULL) + return; + m->m_len = sizeof(*ea); + m->m_pkthdr.len = sizeof(*ea); + MH_ALIGN(m, sizeof(*ea)); + + ea = mtod(m, struct ether_arp*); + eh = (struct ether_header*) sa.sa_data; + memset(ea, 0, sizeof(*ea)); + + memcpy(eh->ether_dhost, ðer_bcast, sizeof(eh->ether_dhost)); + eh->ether_type = ETHERTYPE_ARP; + + ea->arp_hrd = htons(ARPHRD_ETHER); + ea->arp_pro = htons(ETHERTYPE_IP); + ea->arp_hln = sizeof(ea->arp_sha); + ea->arp_pln = sizeof(ea->arp_spa); + ea->arp_op = htons(ARPOP_REQUEST); + + memcpy((caddr_t)ea->arp_sha, (caddr_t)enaddr, sizeof(ea->arp_sha)); + memcpy(ea->arp_spa, sip, sizeof(ea->arp_spa)); + memcpy(ea->arp_tpa, tip, sizeof(ea->arp_tpa)); + + sa.sa_family = AF_UNSPEC; + sa.sa_len = sizeof(sa); + + (*ac->ac_if.output)(&ac->ac_if, m, &sa, NULL); +} + +void arpwhohas(struct arpcom *ac, struct in_addr *ia) +{ + arprequest(ac, &ac->ac_ipaddr.s_addr, &ia->s_addr, ac->ac_enaddr); +} + + +void arp_rtrequest(int req, struct rtentry *rt, struct sockaddr *sa) +{ + struct sockaddr *gate = rt->rt_gateway; + static struct sockaddr_dl null_sdl = {sizeof(null_sdl), AF_LINK}; + struct llinfo_arp *la = (struct llinfo_arp*)rt->rt_llinfo; + + if (rt->rt_flags & RTF_GATEWAY) + return; + + switch (req) { + case RTM_ADD: + if ((rt->rt_flags & RTF_HOST) == 0 && + SIN(rt_mask(rt))->sin_addr.s_addr != 0xffffffff) + rt->rt_flags |= RTF_CLONING; + if (rt->rt_flags & RTF_CLONING) { + rt_setgate(rt, rt_key(rt), (struct sockaddr*)&null_sdl); + gate = rt->rt_gateway; + SDL(gate)->sdl_type = rt->rt_ifp->if_type; + SDL(gate)->sdl_index = rt->rt_ifp->if_index; + rt->rt_expire = real_time_clock(); + break; + } + if (rt->rt_flags & RTF_ANNOUNCE) + arprequest((struct arpcom*) rt->rt_ifp, + &SIN(rt_key(rt))->sin_addr.s_addr, + &SIN(rt_key(rt))->sin_addr.s_addr, + (uint8*)LLADDR(SDL(gate))); + case RTM_RESOLVE: + if (gate->sa_family != AF_LINK || + gate->sa_len < sizeof(null_sdl)) { + printf("arp_rtrequest: bad gateway value!\n"); + break; + } + SDL(gate)->sdl_type = rt->rt_ifp->if_type; + SDL(gate)->sdl_index = rt->rt_ifp->if_index; + if (la) + break; + + R_Malloc(la, struct llinfo_arp *, sizeof(*la)); + if (!la) { + printf("arp_rtrequest: malloc failed!\n"); + break; + } + rt->rt_llinfo = (caddr_t)la; + arp_inuse++, arp_allocated++; + Bzero(la, sizeof(*la)); + + la->la_rt = rt; + rt->rt_flags |= RTF_LLINFO; + insque(la, &llinfo_arp); + + if (SIN(rt_key(rt))->sin_addr.s_addr == + (IA_SIN(rt->rt_ifa))->sin_addr.s_addr) { + rt->rt_expire = 0; + Bcopy(((struct arpcom*)rt->rt_ifp)->ac_enaddr, + LLADDR(SDL(gate)), SDL(gate)->sdl_alen = 6); +/* +XXX - add function to get the ifnet * for the loopback from the "core" + and ability to find the loopback device when added. + if (useloopback) + rt->rt_ifp = &loif; +*/ + } + break; + case RTM_DELETE: + if (!la) + break; + arp_inuse--; + remque(la); + rt->rt_llinfo = NULL; + rt->rt_flags &= ~ RTF_LLINFO; + if (la->la_hold) + m_freem(la->la_hold); + Free((caddr_t)la); + } +} + +static void arpinput(struct mbuf *m) +{ + struct ether_arp *ea; + struct arpcom *ac = (struct arpcom *)m->m_pkthdr.rcvif; + struct ether_header *eh; + struct llinfo_arp *la = NULL; + struct in_ifaddr *ia = NULL, *maybe_ia = NULL; + struct in_addr isaddr, itaddr, myaddr; + struct rtentry *rt; + int op; + struct sockaddr_dl *sdl; + struct sockaddr sa; + +#if ARP_DEBUG + dump_arp(mtod(m, void*)); +#endif + + if (!primary_addr) + primary_addr = get_primary_addr(); + + ea = mtod(m, struct ether_arp *); + op = ntohs(ea->arp_op); + memcpy(&isaddr, ea->arp_spa, sizeof(isaddr)); + memcpy(&itaddr, ea->arp_tpa, sizeof(isaddr)); + + /* find out if it's for us... */ + for (ia = primary_addr;ia ; ia = ia->ia_next) + if (ia->ia_ifp == &ac->ac_if) { + maybe_ia = ia; + if ((itaddr.s_addr == ia->ia_addr.sin_addr.s_addr) || + (isaddr.s_addr == ia->ia_addr.sin_addr.s_addr)) + break; + } + if (!maybe_ia) + goto out; + + myaddr = ia ? ia->ia_addr.sin_addr : maybe_ia->ia_addr.sin_addr; + + if (!memcmp(ac->ac_enaddr, ea->arp_sha, sizeof(ea->arp_sha))) + goto out; + if (!memcmp(ea->arp_sha, ðer_bcast, sizeof(ea->arp_sha))) { + printf("arp_input: ether address was broadcast for %08lx\n", + htonl(isaddr.s_addr)); + goto out; + } + if (isaddr.s_addr == myaddr.s_addr) { + printf("arp_input: duplicate IP address sent from %s\n", + ether_sprintf(ea->arp_sha)); + itaddr = myaddr; + goto reply; + } + la = arplookup(isaddr.s_addr, itaddr.s_addr == myaddr.s_addr, 0); + if (la && (rt = la->la_rt) && (sdl = SDL(rt->rt_gateway))) { + if (sdl->sdl_alen && memcmp(ea->arp_sha, LLADDR(sdl), sdl->sdl_alen)) + printf("arp info overwritten for %08lx by %s\n", + isaddr.s_addr, ether_sprintf(ea->arp_sha)); + memcpy(LLADDR(sdl), ea->arp_sha, sdl->sdl_alen = sizeof(ea->arp_sha)); + if (rt->rt_expire) { + rt->rt_expire = real_time_clock() + arpt_keep; + } + rt->rt_flags &= ~RTF_REJECT; + la->la_asked = 0; + if (la->la_hold) { + (*ac->ac_if.output)(&ac->ac_if, la->la_hold, rt_key(rt), rt); + la->la_hold = NULL; + } + } + +reply: + if (op != ARPOP_REQUEST) { +out: + return; + } + + if (itaddr.s_addr == myaddr.s_addr) { + memcpy(ea->arp_tha, ea->arp_sha, sizeof(ea->arp_sha)); + memcpy(ea->arp_sha, ac->ac_enaddr, sizeof(ea->arp_sha)); + } else { + la = arplookup(itaddr.s_addr, 0, SIN_PROXY); + if (la == NULL) + goto out; + rt = la->la_rt; + memcpy(ea->arp_tha, ea->arp_sha, sizeof(ea->arp_sha)); + sdl = SDL(rt->rt_gateway); + memcpy(ea->arp_sha, LLADDR(sdl), sizeof(ea->arp_sha)); + } + + memcpy(ea->arp_tpa, ea->arp_spa, sizeof(ea->arp_spa)); + memcpy(ea->arp_spa, &itaddr, sizeof(ea->arp_spa)); + ea->arp_op = htons(ARPOP_REPLY); + ea->arp_pro = htons(ETHERTYPE_IP); + eh = (struct ether_header *)sa.sa_data; + memcpy(eh->ether_dhost, ea->arp_tha, sizeof(eh->ether_dhost)); + eh->ether_type = ETHERTYPE_ARP; + sa.sa_family = AF_UNSPEC; + sa.sa_len = sizeof(sa); + + (*ac->ac_if.output)(&ac->ac_if, m, &sa, NULL); + return; +} + +void arp_init(void) +{ + llinfo_arp.la_next = llinfo_arp.la_prev = &llinfo_arp; + + arptimer_id = net_add_timer(&arptimer, NULL, arpt_prune * 1000000); + +} + +static int ether_ioctl(struct ifnet *ifp, int cmd, caddr_t data) +{ + struct arpcom *ac = (struct arpcom *)ifp; + struct ifaddr *ifa = (struct ifaddr*)data; + + if (ifp->devid == -1) { + char path[PATH_MAX]; + sprintf(path, "%s/%s/%d", DRIVER_DIRECTORY, ifp->name, ifp->if_unit); + ifp->devid = open(path, O_RDWR); + if (ifp->devid < 0) { + ifp->devid = -1; + return -1; + } + } + + if ((ifp->if_flags & IFF_UP) == 0 && + (ifp->rx_thread > 0 || ifp->tx_thread > 0)) { + /* shutdown our threads and remove the IFF_RUNNING flag... */ + if (ifp->rx_thread > 0) + kill_thread(ifp->rx_thread); + if (ifp->tx_thread > 0) + kill_thread(ifp->tx_thread); + ifp->rx_thread = ifp->tx_thread = -1; + ifp->if_flags &= ~IFF_RUNNING; + } + ifa->ifa_rtrequest = &arp_rtrequest; + + switch (cmd) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + ac->ac_ipaddr = ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; +#ifdef ETHER_DEBUG + printf("setting ether_device ip address to %08lx\n", + ntohl(((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr)); +#endif + break; + default: + printf("don't know how to work with address family %d\n", + ifa->ifa_addr->sa_family); + } + break; + case SIOCSIFFLAGS: + printf("ether_ioctl: SIOCSIFFLAGS\n"); + break; + default: + printf("unhandled call to ethernet_ioctl\n"); + } + + if ((ifp->if_flags & IFF_UP) && + (ifp->rx_thread == -1 || ifp->tx_thread == -1)) { + /* start our threads and add the IFF_RUNNING flag... */ + if (ifp->rx_thread < 0) + start_rx_thread(ifp); + if (ifp->tx_thread < 0) + start_tx_thread(ifp); + ifp->if_flags |= IFF_RUNNING; + } + /* close and reset the devid so we don't try to use it again */ + if ((ifp->if_flags & IFF_UP) == 0) { + close(ifp->devid); + ifp->devid = -1; + } + return 0; +} + +int ether_dev_stop(ifnet *dev) +{ + dev->if_flags &= ~IFF_UP; + + /* should find better ways of doing this... */ + kill_thread(dev->rx_thread); + kill_thread(dev->tx_thread); + + return 0; +} + +static int ether_init(void *cpp) +{ + if (cpp) + core = cpp; + + etherq = start_ifq(); + ether_rxt = spawn_thread(ether_input, "ethernet_input", 50, NULL); + if (ether_rxt > 0) + resume_thread(ether_rxt); + + find_devices(); + + + memset(proto, 0, sizeof(struct protosw *) * IPPROTO_MAX); + add_protosw(proto, NET_LAYER2); + arp_init(); + + return 0; +} + +static int ether_stop() +{ + struct ether_device *dptr = ether_devices, *odev; + kill_thread(ether_rxt); + + while (dptr) { + odev = dptr; + dptr = odev->next; + free(odev); + } + + net_remove_timer(arptimer_id); + + return 0; +} + +_EXPORT struct kernel_net_module_info device_info = { + { + ETHERNET_MODULE_PATH, + 0, + std_ops + }, + + ether_init, + ether_stop +}; + + +static int32 std_ops(int32 op, ...) +{ + switch(op) { + case B_MODULE_INIT: + get_module(CORE_MODULE_PATH, (module_info**)&core); + if (!core) + return B_ERROR; +#ifdef _KERNEL_ + load_driver_symbols("ethernet"); +#endif + return B_OK; + case B_MODULE_UNINIT: + return B_OK; + default: + return B_ERROR; + } + return B_OK; +} + +_EXPORT module_info * modules[] = { + (module_info *) &device_info, + NULL +}; diff --git a/src/add-ons/kernel/network/interfaces/loopback/Jamfile b/src/add-ons/kernel/network/interfaces/loopback/Jamfile new file mode 100644 index 0000000000..205c7f7677 --- /dev/null +++ b/src/add-ons/kernel/network/interfaces/loopback/Jamfile @@ -0,0 +1,2 @@ +SubDir OBOS_TOP src add-ons kernel network interfaces loopback ; + diff --git a/src/add-ons/kernel/network/interfaces/loopback/loopback.c b/src/add-ons/kernel/network/interfaces/loopback/loopback.c new file mode 100644 index 0000000000..7309dbfe74 --- /dev/null +++ b/src/add-ons/kernel/network/interfaces/loopback/loopback.c @@ -0,0 +1,178 @@ +/* loopback.c - loopback device + */ + +#include +#include +#include + +#include "sys/socket.h" +#include "protocols.h" +#include "netinet/in.h" +#include "netinet/ip.h" +#include "sys/socketvar.h" +#include "sys/protosw.h" +#include "sys/domain.h" +#include "sys/sockio.h" + +#include "net_malloc.h" +#include "core_module.h" +#include "net_module.h" +#include "core_funcs.h" + +#ifdef _KERNEL_ +#include +#define LOOP_MODULE_PATH "network/interfaces/loopback" +#else +#define LOOP_MODULE_PATH "interfaces/loopback" +#endif + +static status_t std_ops(int32 op, ...); + +static struct core_module_info *core = NULL; + +static struct protosw *proto[IPPROTO_MAX]; +static struct ifnet *me = NULL; + +int loopback_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa, + struct rtentry *rt) +{ + /* turn it straight back... */ + /* This is lame as we should be detecting the protocol, but it gets + * us working. + * XXX - fix me! + */ + struct ip *ip = mtod(m, struct ip *); + + ip->ip_dst = ip->ip_src; + ip->ip_src.s_addr = INADDR_LOOPBACK; + + IFQ_ENQUEUE(ifp->rxq, m); + return 0; +} + +void loopback_input(struct mbuf *buf) +{ + if (!buf) + return; + + buf->m_pkthdr.rcvif = me; + + if (proto[IPPROTO_IP] && proto[IPPROTO_IP]->pr_input) { + proto[IPPROTO_IP]->pr_input(buf, 0); + return; + } else + printf("No input tourtine found for IP\n"); + + m_freem(buf); + return; +} + +static int loopback_dev_stop(struct ifnet *dev) +{ + if (!dev || dev->if_type != IFT_LOOP) + return EINVAL; + + dev->if_flags &= ~IFF_UP; + + if (dev->rx_thread > 0) + kill_thread(dev->rx_thread); + if (dev->tx_thread > 0) + kill_thread(dev->tx_thread); + + dev->if_flags &= ~IFF_RUNNING; + + return 0; +} + +static int loopback_ioctl(struct ifnet *ifp, int cmd, caddr_t data) +{ + int error = 0; + + switch(cmd) { + case SIOCSIFADDR: + ifp->if_flags |= (IFF_UP | IFF_RUNNING); + if (ifp->rx_thread < 0) + start_rx_thread(ifp); + if (ifp->tx_thread < 0) + start_tx_thread(ifp); + break; + default: + error = EINVAL; + } + return error; +} + +static int loopback_init(void) +{ + me = (struct ifnet*)malloc(sizeof(struct ifnet)); + if (!me) + return -1; + + memset(me, 0, sizeof(*me)); + + memset(proto, 0, sizeof(struct protosw *) * IPPROTO_MAX); + + me->devid = -1; + me->name = "loop"; + me->if_unit = 0; + me->if_type = IFT_LOOP; + me->rx_thread = -1; + me->tx_thread = -1; + me->if_addrlen = 0; + me->if_hdrlen = 0; + me->if_flags = IFF_LOOPBACK | IFF_MULTICAST; + me->if_mtu = 16384; /* can be as large as we want */ + me->input = &loopback_input; + me->output = &loopback_output; + me->stop = &loopback_dev_stop; + me->ioctl = &loopback_ioctl; + + add_protosw(proto, NET_LAYER1); + if_attach(me); + + return 0; +} + +static int loopback_module_init(void *cpp) +{ + if (cpp) + core = cpp; + + loopback_init(); + + return 0; +} + +_EXPORT struct kernel_net_module_info device_info = { + { + LOOP_MODULE_PATH, + 0, + std_ops + }, + loopback_module_init, + NULL, +}; + +static status_t std_ops(int32 op, ...) +{ + switch(op) { + case B_MODULE_INIT: + get_module(CORE_MODULE_PATH, (module_info **)&core); + if (!core) + return B_ERROR; + return B_OK; + case B_MODULE_UNINIT: + break; + default: + return B_ERROR; + } + return B_OK; +} + +_EXPORT module_info *modules[] = { + (module_info*) &device_info, + NULL +}; + + + diff --git a/src/add-ons/kernel/network/protocols/Jamfile b/src/add-ons/kernel/network/protocols/Jamfile new file mode 100644 index 0000000000..8460eb12ae --- /dev/null +++ b/src/add-ons/kernel/network/protocols/Jamfile @@ -0,0 +1,8 @@ +SubDir OBOS_TOP src add-ons kernel network protocols ; + +SubInclude OBOS_TOP src add-ons kernel network protocols icmp ; +SubInclude OBOS_TOP src add-ons kernel network protocols ipv4 ; +SubInclude OBOS_TOP src add-ons kernel network protocols raw ; +SubInclude OBOS_TOP src add-ons kernel network protocols tcp ; +SubInclude OBOS_TOP src add-ons kernel network protocols udp ; + diff --git a/src/add-ons/kernel/network/protocols/icmp/Jamfile b/src/add-ons/kernel/network/protocols/icmp/Jamfile new file mode 100644 index 0000000000..5c4ccf6d79 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/icmp/Jamfile @@ -0,0 +1,2 @@ +SubDir OBOS_TOP src add-ons kernel network protocols icmp ; + diff --git a/src/add-ons/kernel/network/protocols/icmp/icmp.c b/src/add-ons/kernel/network/protocols/icmp/icmp.c new file mode 100644 index 0000000000..fba3c30fcd --- /dev/null +++ b/src/add-ons/kernel/network/protocols/icmp/icmp.c @@ -0,0 +1,503 @@ +/* icmp.c + */ + +#ifndef _KERNEL +#include +#include +#endif + +#include "net_misc.h" +#include "sys/socket.h" +#include "netinet/in_systm.h" +#include "netinet/ip.h" +#include "netinet/ip_icmp.h" +#include "protocols.h" +#include "sys/protosw.h" +#include "sys/domain.h" +#include "netinet/icmp_var.h" +#include "netinet/in_var.h" +#include "net/if.h" + +#include "core_module.h" +#include "net_module.h" +#include "core_funcs.h" +#include "raw/raw_module.h" +#include "icmp_module.h" +#include "../ipv4/ipv4_module.h" + +#ifdef _KERNEL_ +#include +static status_t icmp_ops(int32 op, ...); +#else +#define icmp_ops NULL +#endif + +/* private variables */ +static struct core_module_info *core = NULL; +static struct raw_module_info *raw = NULL; +static struct protosw* proto[IPPROTO_MAX]; +static struct in_ifaddr *ic_ifaddr = NULL; +static struct ipv4_module_info *ipm = NULL; +#ifndef _KERNEL_ +static image_id ipid = -1; +#endif + +static struct route icmprt; + +#if SHOW_DEBUG +static void dump_icmp(struct mbuf *buf) +{ + struct ip *ip = mtod(buf, struct ip *); + struct icmp *ic = (struct icmp*)((caddr_t)ip + (ip->hl * 4)); + + printf("ICMP: "); + switch (ic->icmp_type) { + case ICMP_ECHORQST: + printf ("Echo request\n"); + break; + case ICMP_ECHORPLY: + printf("echo reply\n"); + break; + default: + printf("?? type = %d\n", ic->type); + } +} +#endif + +static void icmp_send(struct mbuf *m, struct mbuf *opts) +{ + struct ip *ip = mtod(m, struct ip*); + int hlen; + struct icmp *icp; + + hlen = ip->ip_hl << 2; + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp *); + icp->icmp_cksum = 0; + icp->icmp_cksum = in_cksum(m, ip->ip_len - hlen, 0); + m->m_data -= hlen; + m->m_len += hlen; + proto[IPPROTO_IP]->pr_output(m, opts, NULL, 0, NULL); +} + +static void icmp_reflect(struct mbuf *m) +{ + struct ip *ip = mtod(m, struct ip*); + struct in_ifaddr *ia; + struct in_addr t; + struct mbuf *opts = NULL; + int optlen = (ip->ip_hl << 2) - sizeof(struct ip); + struct sockaddr_in icmpdst; + + if (!ic_ifaddr) { + ic_ifaddr = get_primary_addr(); + if (!ic_ifaddr) { + printf("icmp_reflect: no interfaces available (ic_ifaddr == NULL)\n"); + m_freem(m); + return; + } + } + + if (!in_canforward(ip->ip_src) && + ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) != + (IN_LOOPBACKNET << IN_CLASSA_NSHIFT))) { + printf("icmp_reflect: can't forward packet!\n"); + m_freem(m); + goto done; + } + t = ip->ip_dst; + ip->ip_dst = ip->ip_src; + for (ia = ic_ifaddr; ia; ia = ia->ia_next) { + if (t.s_addr == IA_SIN(ia)->sin_addr.s_addr) + break; + if ((ia->ia_ifp->if_flags & IFF_BROADCAST) && + t.s_addr == satosin(&ia->ia_broadaddr)->sin_addr.s_addr) + break; + } + icmpdst.sin_addr = t; + if (ia == NULL) + ia = (struct in_ifaddr*)ifaof_ifpforaddr((struct sockaddr*)&icmpdst, + m->m_pkthdr.rcvif); + if (ia == NULL) + ia = in_ifaddr; + t = IA_SIN(ia)->sin_addr; + ip->ip_src = t; + ip->ip_ttl = MAXTTL; + if (optlen > 0) { + uint8 *cp; + int opt, cnt; + uint len; + + cp = (uint8*)(ip + 1); + if ((opts = ipm->srcroute()) == 0 && + (opts = m_gethdr(MT_HEADER))) { + opts->m_len = sizeof(struct in_addr); + mtod(opts, struct in_addr*)->s_addr = 0; + } + if (opts) { + for (cnt = optlen; cnt > 0; cnt -= len, cp+= len) { + opt = cp[IPOPT_OPTVAL]; + if (opt == IPOPT_EOL) + break; + if (opt == IPOPT_NOP) + len = 1; + else { + len = cp[IPOPT_OLEN]; + if (len <= 0 || len > cnt) + break; + } + if (opt == IPOPT_RR || opt == IPOPT_TS || + opt == IPOPT_SECURITY) { + memcpy((void*)(mtod(opts, char*) + opts->m_len), + (void*)cp, len); + opts->m_len += len; + } + } + if ((cnt = opts->m_len % 4)) { + for (; cnt < 4; cnt++) { + *(mtod(opts, char*) + opts->m_len) = IPOPT_EOL; + opts->m_len++; + } + } + } + ip->ip_len -= optlen; + ip->ip_hl = sizeof(struct ip) >> 2; + m->m_len -= optlen; + if (m->m_flags & M_PKTHDR) + m->m_pkthdr.len -= optlen; + optlen += sizeof(struct ip); + memcpy((void*)(ip + 1), (void*)(ip + optlen), + (uint)(m->m_len - sizeof(struct ip))); + } + m->m_flags &= ~(M_BCAST | M_MCAST); + icmp_send(m, opts); +done: + if (opts) + m_free(opts); +} + +void icmp_input(struct mbuf *buf, int hdrlen) +{ + struct ip *ip = mtod(buf, struct ip *); + struct icmp *ic; + int icl = ip->ip_len; + int i; + uint16 rv; + int code; + struct sockaddr_in icmpsrc = {sizeof(struct sockaddr_in), AF_INET}; + + if (icl < ICMP_MINLEN) { + icmpstat.icps_tooshort++; + goto freeit; + } +#if SHOW_DEBUG + dump_icmp(buf); +#endif + i = hdrlen + min(icl, ICMP_ADVLENMIN); + if (buf->m_len < i && (buf = m_pullup(buf, i)) == NULL) { + icmpstat.icps_tooshort++; + return; + } + ip = mtod(buf, struct ip*); + buf->m_len -= hdrlen; + buf->m_data += hdrlen; + ic = mtod(buf, struct icmp*); + + if ((rv = in_cksum(buf, icl, 0)) != 0) { + printf("icmp_input: checksum failed over %d bytes (%d)!\n", icl, rv); + icmpstat.icps_checksum++; + goto freeit; + } + buf->m_len += hdrlen; + buf->m_data -= hdrlen; + if (ic->icmp_type > ICMP_MAXTYPE) + goto raw; + + icmpstat.icps_inhist[ic->icmp_type]++; + code = ic->icmp_code; + switch (ic->icmp_type) { + case ICMP_UNREACH: + switch (code) { + case ICMP_UNREACH_NET: + case ICMP_UNREACH_HOST: + case ICMP_UNREACH_PROTOCOL: + case ICMP_UNREACH_PORT: + case ICMP_UNREACH_SRCFAIL: + code += PRC_UNREACH_NET; + break; + case ICMP_UNREACH_NEEDFRAG: + code = PRC_MSGSIZE; + break; + case ICMP_UNREACH_NET_UNKNOWN: + case ICMP_UNREACH_NET_PROHIB: + case ICMP_UNREACH_TOSNET: + code = PRC_UNREACH_NET; + break; + case ICMP_UNREACH_HOST_UNKNOWN: + case ICMP_UNREACH_ISOLATED: + case ICMP_UNREACH_HOST_PROHIB: + case ICMP_UNREACH_TOSHOST: + code = PRC_UNREACH_HOST; + break; + default: + goto badcode; + } + goto deliver; + case ICMP_TIMXCEED: + if (code > 1) + goto badcode; + code += PRC_TIMXCEED_INTRANS; + goto deliver; + case ICMP_PARAMPROB: + if (code > 1) + goto badcode; + code = PRC_PARAMPROB; + goto deliver; + case ICMP_SOURCEQUENCH: + if (code) + goto badcode; + code = PRC_QUENCH; +deliver: + if (icl < ICMP_ADVLENMIN || icl < ICMP_ADVLEN(ic) || + (ic->icmp_ip.ip_hl < sizeof(struct ip) >> 2)) { + icmpstat.icps_badlen++; + goto freeit; + } + ic->icmp_ip.ip_len = htons(ic->icmp_ip.ip_len); + icmpsrc.sin_addr = ic->icmp_ip.ip_dst; + if (proto[ic->icmp_ip.ip_p]->pr_ctlinput) + proto[ic->icmp_ip.ip_p]->pr_ctlinput(code, + (struct sockaddr*)&icmpsrc, (void*)&ic->icmp_ip); + break; +badcode: + icmpstat.icps_badcode++; + break; + case ICMP_ECHO: { + ic->icmp_type = ICMP_ECHOREPLY; + ip->ip_len += hdrlen; + icmpstat.icps_reflect++; + icmpstat.icps_outhist[ic->icmp_type]++; + icmp_reflect(buf); + return; + break; + } + case ICMP_ECHOREPLY: + break; + default: + break; + } + +raw: + if (raw) + raw->input(buf, 0); + + return; + +freeit: + m_freem(buf); + return; +} + + +void icmp_error(struct mbuf *n, int type, int code, n_long dest, + struct ifnet *destifp) +{ + struct ip *oip = mtod(n, struct ip*), *nip; + uint oiplen = oip->ip_hl << 2; + struct icmp *icp; + struct mbuf *m; + uint icmplen; + + if (type != ICMP_REDIRECT) + icmpstat.icps_error++; + if (oip->ip_off & ~(IP_MF | IP_DF)) + goto freeit; + if (oip->ip_p == IPPROTO_ICMP && type != ICMP_REDIRECT && + n->m_len >= oiplen + ICMP_MINLEN && + ICMP_INFOTYPE(((struct icmp*)((void*)(oip + oiplen)))->icmp_type)) { + icmpstat.icps_oldicmp++; + goto freeit; + } + /* don't send icmp errors in response to multi or broad cast */ + if (n->m_flags & (M_BCAST | M_MCAST)) + goto freeit; + + m = m_gethdr(MT_HEADER); + if (!m) + goto freeit; + icmplen = oiplen + min(8, oip->ip_len); + m->m_len = icmplen + ICMP_MINLEN; + MH_ALIGN(m, m->m_len); + icp = mtod(m, struct icmp*); + if ((uint)type > ICMP_MAXTYPE) { + /* PANIC */ + printf("PANIC: icmp_error! type outside of range\n"); + return; + } + icmpstat.icps_outhist[type]++; + icp->icmp_type = type; + if (type == ICMP_REDIRECT) + icp->icmp_gwaddr.s_addr = dest; + else { + icp->icmp_void = 0; + if (type == ICMP_PARAMPROB) { + icp->icmp_pptr = code; + } else if (type == ICMP_UNREACH && + code == ICMP_UNREACH_NEEDFRAG && + destifp) { + icp->icmp_nextmtu = htons(destifp->if_mtu); + } + } + icp->icmp_code = code; + memcpy((void*)&icp->icmp_ip, (void*)oip, icmplen); + nip = &icp->icmp_ip; + nip->ip_len = htons((nip->ip_len + oiplen)); + if (m->m_data - sizeof(struct ip) < m->m_pktdat) { + /* PANIC */ + printf("PANIC: icmp_error: icmp len\n"); + return; + } + m->m_data -= sizeof(struct ip); + m->m_len += sizeof(struct ip); + m->m_pkthdr.len = m->m_len; + m->m_pkthdr.rcvif = n->m_pkthdr.rcvif; + nip = mtod(m, struct ip*); + memcpy((void*)nip, (void*)oip, sizeof(struct ip)); + nip->ip_len = m->m_len; + nip->ip_hl = sizeof(struct ip) >> 2; + nip->ip_p = IPPROTO_ICMP; + nip->ip_tos = 0; + icmp_reflect(m); + +freeit: + m_freem(n); +} + +static void icmp_init(void) +{ + memset(&icmprt, 0, sizeof(icmprt)); + + memset(proto, 0, sizeof(struct protosw *) * IPPROTO_MAX); + add_protosw(proto, NET_LAYER2); +#ifdef _KERNEL + if (!raw) + get_module(RAW_MODULE_PATH, (module_info**)&raw); +#endif + ic_ifaddr = get_primary_addr(); +} + +struct protosw my_proto = { + "ICMP (v4)", + ICMP_MODULE_PATH, + 0, + NULL, + IPPROTO_ICMP, + PR_ATOMIC | PR_ADDR, + NET_LAYER2, + + &icmp_init, + &icmp_input, + NULL, /* pr_output, */ + NULL, + NULL, /* pr_sysctl */ + NULL, + NULL, /* pr_ctloutput */ + + NULL, + NULL +}; + +static int icmp_protocol_init(void *cpp) +{ + /* we will never call this with anything but NULL when in kernel, + * so this should be safe. + */ + if (cpp) + core = (struct core_module_info *)cpp; + add_domain(NULL, AF_INET); + add_protocol(&my_proto, AF_INET); + +#ifndef _KERNEL_ + if (!ipm) { + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + strcat(path, "/" IPV4_MODULE_PATH); + + ipid = load_add_on(path); + if (ipid > 0) { + status_t rv = get_image_symbol(ipid, "protocol_info", + B_SYMBOL_TYPE_DATA, (void**)&ipm); + if (rv < 0) { + printf("Failed to get access to IPv4 information!\n"); + return -1; + } + } else { + printf("Failed to load the IPv4 module...\n"); + return -1; + } + ipm->set_core(cpp); + } +#else + if (!ipm) + get_module(IPV4_MODULE_PATH, (module_info**)&ipm); +#endif + + return 0; +} + +static int icmp_protocol_stop(void) +{ + remove_protocol(&my_proto); + remove_domain(AF_INET); + return 0; +} + +#ifndef _KERNEL_ +void set_core(struct core_module_info *cp) +{ + core = cp; +} +#endif + +_EXPORT struct icmp_module_info protocol_info = { + { + { + ICMP_MODULE_PATH, + 0, + icmp_ops + }, + icmp_protocol_init, + icmp_protocol_stop + }, +#ifndef _KERNEL_ + set_core, +#endif + icmp_error +}; + +#ifdef _KERNEL_ +static status_t icmp_ops(int32 op, ...) +{ + switch(op) { + case B_MODULE_INIT: + if (!core) + get_module(CORE_MODULE_PATH, (module_info**)&core); + if (!core) + return B_ERROR; + load_driver_symbols("icmp"); + return B_OK; + case B_MODULE_UNINIT: + break; + default: + return B_ERROR; + } + return B_OK; +} + +_EXPORT module_info *modules[] = { + (module_info *)&protocol_info, + NULL +}; +#endif + diff --git a/src/add-ons/kernel/network/protocols/ipv4/Jamfile b/src/add-ons/kernel/network/protocols/ipv4/Jamfile new file mode 100644 index 0000000000..80c8d4ff97 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/Jamfile @@ -0,0 +1,2 @@ +SubDir OBOS_TOP src add-ons kernel network protocols ipv4 ; + diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4.c b/src/add-ons/kernel/network/protocols/ipv4/ipv4.c new file mode 100644 index 0000000000..dacdf33cef --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4.c @@ -0,0 +1,1266 @@ +/* ipv4.c + * simple ipv4 implementation + */ + +#ifndef _KERNEL_ +#include +#endif +#include +#include +#include +#include + +#include "netinet/in.h" +#include "netinet/in_var.h" +#include "netinet/in_systm.h" +#include "netinet/ip.h" +#include "netinet/ip_var.h" +#include "netinet/in_pcb.h" +#include "netinet/ip_icmp.h" +#include "protocols.h" +#include "sys/protosw.h" +#include "sys/domain.h" + +#include "core_module.h" +#include "core_funcs.h" +#include "net_module.h" +#include "ipv4_module.h" +#include "../icmp/icmp_module.h" + +#ifdef _KERNEL_ +#include +static status_t ipv4_ops(int32 op, ...); +#else +#define ipv4_ops NULL +#endif /* _KERNEL_ */ + +#define INA struct in_ifaddr * +#define SA struct sockaddr * + +/* private variables */ +static struct core_module_info *core = NULL; +static struct protosw *proto[IPPROTO_MAX]; +static int ipforwarding = 0; +static int ipsendredirects = 1; +static uint16 ip_id = 0; +static sem_id id_lock = -1; +static struct in_ifaddr *ip_ifaddr = NULL; +static struct icmp_module_info *icmp = NULL; +#ifndef _KERNEL_ +static image_id icmpid = -1; +#endif +static struct ipq ipq; +static net_timer_id timerid; + +/* ??? - Globals we need to remove... TLS storage? */ +struct route ipforward_rt; + +/* Forward prototypes */ +int ipv4_output(struct mbuf *, struct mbuf *, struct route *, int, void *); + +#if SHOW_DEBUG +static void dump_ipv4_header(struct mbuf *buf) +{ + struct ip *ip = mtod(buf, struct ip *); + + printf("IPv4 Header :\n"); + printf(" : version : %d\n", ip->ip_v); + printf(" : header length : %d\n", ip->ip_hl * 4); + printf(" : tos : %d\n", ip->ip_tos); + printf(" : total length : %d\n", ntohs(ip->ip_len)); + printf(" : id : %d\n", ntohs(ip->ip_id)); + printf(" : ttl : %d\n", ip->ip_ttl); + dump_ipv4_addr(" : src address :", &ip->ip_src); + dump_ipv4_addr(" : dst address :", &ip->ip_dst); + + printf(" : protocol : "); + + switch(ip->ip_p) { + case IPPROTO_ICMP: + printf("ICMP\n"); + break; + case IPPROTO_UDP: + printf("UDP\n"); + break; + case IPPROTO_TCP: + printf("TCP\n"); + break; + default: + printf("unknown (0x%02x)\n", ip->ip_p); + } +} +#endif + + +/* IP Options variables and structures... */ +int ip_nhops = 0; +static struct ip_srcrt { + struct in_addr dst; + char nop; + char srcopt[IPOPT_OFFSET + 1]; + struct in_addr route[MAX_IPOPTLEN / sizeof(struct in_addr)]; +} ip_srcrt; + +/* + * Strip out IP options, at higher + * level protocol in the kernel. + * Second argument is buffer to which options + * will be moved, and return value is their length. + * XXX should be deleted; last arg currently ignored. + */ +void ip_stripoptions(struct mbuf *m, struct mbuf *mopt) +{ + int i; + struct ip *ip = mtod(m, struct ip *); + caddr_t opts; + int olen; + + olen = (ip->ip_hl<<2) - sizeof (struct ip); + opts = (caddr_t)(ip + 1); + i = m->m_len - (sizeof (struct ip) + olen); + memcpy(opts, opts + olen, (unsigned)i); + m->m_len -= olen; + if (m->m_flags & M_PKTHDR) + m->m_pkthdr.len -= olen; + ip->ip_hl = sizeof(struct ip) >> 2; +} + +static struct mbuf *ip_insertoptions(struct mbuf *m, struct mbuf *opt, int *phlen) +{ + struct ipoption * p = mtod(opt, struct ipoption*); + struct mbuf *n; + struct ip *ip = mtod(m, struct ip*); + uint optlen; + + optlen = opt->m_len - sizeof(p->ipopt_dst); + if (optlen + ip->ip_len > IP_MAXPACKET) + return (m); + if (p->ipopt_dst.s_addr) + ip->ip_dst = p->ipopt_dst; + if (m->m_flags & M_EXT || m->m_data - optlen < m->m_pktdat) { + n = m_get(MT_HEADER); + if (!n) + return (m); + n->m_pkthdr.len = m->m_pkthdr.len + optlen; + m->m_len -= sizeof(struct ip); + m->m_data += sizeof(struct ip); + n->m_next = m; + m = n; + m->m_len = optlen + sizeof(struct ip); + m->m_data += max_linkhdr; + memcpy(mtod(m, void *), ip, sizeof(struct ip)); + } else { + m->m_data -= optlen; + m->m_len += optlen; + m->m_pkthdr.len += optlen; + memmove(mtod(m, void *), ip, sizeof(struct ip)); + } + ip = mtod(m, struct ip*); + memcpy((void*)(ip + 1), p->ipopt_list, optlen); + *phlen = sizeof(struct ip) + optlen; + ip->ip_len += optlen; + return (m); +} + +static struct in_ifaddr * ip_rtaddr(struct in_addr dst) +{ + struct sockaddr_in *sin; + + sin = (struct sockaddr_in*)&ipforward_rt.ro_dst; + + if (ipforward_rt.ro_rt == NULL || dst.s_addr != sin->sin_addr.s_addr) { + if (ipforward_rt.ro_rt) { + RTFREE(ipforward_rt.ro_rt); + ipforward_rt.ro_rt = NULL; + } + memset(&sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_addr = dst; + + rtalloc(&ipforward_rt); + } + if (ipforward_rt.ro_rt == NULL) + return NULL; + return ((struct in_ifaddr*) ipforward_rt.ro_rt->rt_ifa); +} + +static void save_rte(uchar *option, struct in_addr dst) +{ + uint olen; + + olen = option[IPOPT_OLEN]; + if (olen > sizeof(struct ip_srcrt) - (1 + sizeof(dst))) + return; + memcpy((caddr_t) ip_srcrt.srcopt, (caddr_t)option, olen); + ip_nhops = (olen - IPOPT_OFFSET - 1) / sizeof(struct in_addr); + + ip_srcrt.dst = dst; +} + +static void ip_forward(struct mbuf *m, int srcrt) +{ + struct ip *ip = mtod(m, struct ip*); + struct sockaddr_in *sin; + struct rtentry *rt; + struct mbuf *mcopy; + n_long dest; + int code, type, error = 0; + struct ifnet *destifp = NULL; + + memset(sin, 0, sizeof(*sin)); + dest = 0; + if (m->m_flags & M_BCAST || in_canforward(ip->ip_dst) == 0) { + ipstat.ips_cantforward++; + m_freem(m); + return; + } + ip->ip_id = htons(ip->ip_id); + if (ip->ip_ttl <= IPTTLDEC) { + icmp->error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, dest, 0); + return; + } + ip->ip_ttl -= IPTTLDEC; + + sin = (struct sockaddr_in *)&ipforward_rt.ro_dst; + if ((rt = ipforward_rt.ro_rt) == NULL || + ip->ip_dst.s_addr != sin->sin_addr.s_addr) { + if (ipforward_rt.ro_rt) { + RTFREE(ipforward_rt.ro_rt); + ipforward_rt.ro_rt = NULL; + } + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_addr = ip->ip_dst; + + rtalloc(&ipforward_rt); + if (ipforward_rt.ro_rt == NULL) { + icmp->error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, dest, 0); + return; + } + rt = ipforward_rt.ro_rt; + } + mcopy = m_copym(m, 0, (int)min(ip->ip_len, 64)); + + if (rt->rt_ifp == m->m_pkthdr.rcvif && + (rt->rt_flags & (RTF_DYNAMIC | RTF_MODIFIED)) == 0 && + satosin(rt_key(rt))->sin_addr.s_addr != 0 && + ipsendredirects && !srcrt) { +#define RTA(rt) ((struct in_ifaddr *)(rt->rt_ifa)) + uint32 src = ntohl(ip->ip_src.s_addr); + if (RTA(rt) && + (src & RTA(rt)->ia_subnetmask) == RTA(rt)->ia_subnet) { + if (rt->rt_flags & RTF_GATEWAY) + dest = satosin(rt->rt_gateway)->sin_addr.s_addr; + else + dest = ip->ip_dst.s_addr; + type = ICMP_REDIRECT; + code = ICMP_REDIRECT_HOST; + } + } + error = ipv4_output(m, NULL, &ipforward_rt, IP_FORWARDING | IP_ALLOWBROADCAST, 0); + if (error) + ipstat.ips_cantforward++; + else { + ipstat.ips_forward++; + if (type) + ipstat.ips_redirectsent++; + else { + if (mcopy) + m_freem(mcopy); + return; + } + } + if (!mcopy) + return; + destifp = NULL; + + switch(error) { + case 0: + break; + case ENETUNREACH: + case EHOSTUNREACH: + case ENETDOWN: + case EHOSTDOWN: + default: + type = ICMP_UNREACH; + code = ICMP_UNREACH_HOST; + break; + case EMSGSIZE: + type = ICMP_UNREACH; + code = ICMP_UNREACH_NEEDFRAG; + if (ipforward_rt.ro_rt) + destifp = ipforward_rt.ro_rt->rt_ifp; + ipstat.ips_cantfrag++; + break; + case ENOBUFS: + type = ICMP_SOURCEQUENCH; + code = 0; + break; + } + icmp->error(mcopy, type, code, dest, destifp); +} + +int ip_dooptions(struct mbuf *m) +{ + struct ip *ip = mtod(m, struct ip*); + uint8 *cp; + struct in_addr dst, *sin; + struct sockaddr_in ipaddr; + int cnt, optlen, opt, code, off; + int type = ICMP_PARAMPROB; + int forward = 0; + struct in_ifaddr *ia; + struct ip_timestamp*ipt; + n_time ntime; + +printf("ip_dooptions\n"); + + dst = ip->ip_dst; + cp = (uint8*)(ip + 1); + cnt = (ip->ip_hl << 2) - sizeof(struct ip); + for (; cnt > 0; cnt -= optlen, cp+= optlen) { + opt = cp[IPOPT_OPTVAL]; + if (opt == IPOPT_EOL) + break; + if (opt == IPOPT_NOP) + optlen = 1; + else { + optlen = cp[IPOPT_OLEN]; + if (optlen <= 0 || optlen > cnt) { + code = &cp[IPOPT_OLEN] - (uint8 *)ip; + goto bad; + } + } + switch (opt) { + case IPOPT_LSRR: + case IPOPT_SSRR: + if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { + code = &cp[IPOPT_OFFSET] - (uint8 *)ip; + goto bad; + } + ipaddr.sin_addr = ip->ip_dst; + ia = (struct in_ifaddr*)ifa_ifwithaddr((struct sockaddr*)&ipaddr); + if (ia == NULL) { + if (opt == IPOPT_SSRR) { + type = ICMP_UNREACH; + code = ICMP_UNREACH_SRCFAIL; + goto bad; + } + break; + } + off--; + if (off > optlen - sizeof(struct in_addr)) { + save_rte(cp, ip->ip_src); + break; + } + memcpy(&ipaddr.sin_addr, cp+off, sizeof(ipaddr.sin_addr)); + if (opt == IPOPT_SSRR) { + if ((ia = (INA) ifa_ifwithdstaddr((SA)&ipaddr)) == NULL) + ia = (INA) ifa_ifwithnet((SA)&ipaddr); + } else + ia = ip_rtaddr(ipaddr.sin_addr); + if (ia == NULL) { + type = ICMP_UNREACH; + code = ICMP_UNREACH_SRCFAIL; + goto bad; + } + ip->ip_dst = ipaddr.sin_addr; + memcpy(cp+off, &(IA_SIN(ia)->sin_addr), sizeof(struct in_addr)); + cp[IPOPT_OFFSET] += sizeof(struct in_addr); + forward = !IN_MULTICAST(ntohl(ip->ip_dst.s_addr)); + break; + case IPOPT_RR: + if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { + code = &cp[IPOPT_OFFSET] - (uint8*)ip; + goto bad; + } + off--; + if (off > optlen - sizeof(struct in_addr)) + break; + memcpy(&ipaddr.sin_addr, &ip->ip_dst, sizeof(ipaddr.sin_addr)); + if ((ia = (INA)ifa_ifwithaddr((SA)&ipaddr)) == NULL && + (ia = ip_rtaddr(ipaddr.sin_addr)) == NULL) { + type = ICMP_UNREACH; + code = ICMP_UNREACH_HOST; + goto bad; + } + memcpy(cp+off, &(IA_SIN(ia)->sin_addr), sizeof(struct in_addr)); + cp[IPOPT_OFFSET] += sizeof(struct in_addr); + break; + case IPOPT_TS: + code = cp - (uint8*)ip; + ipt = (struct ip_timestamp *)cp; + if (ipt->ipt_len < 5) + goto bad; + if (ipt->ipt_ptr > ipt->ipt_len - sizeof(uint32)) { + if (++ipt->ipt_oflw == 0) + goto bad; + break; + } + sin = (struct in_addr *)(cp + ipt->ipt_ptr - 1); + switch (ipt->ipt_flg) { + case IPOPT_TS_TSONLY: + break; + case IPOPT_TS_TSANDADDR: + if (ipt->ipt_ptr + sizeof(n_time) + + sizeof(struct in_addr) > ipt->ipt_len) + goto bad; + ipaddr.sin_addr = dst; + ia = (INA)ifaof_ifpforaddr((SA) &ipaddr, m->m_pkthdr.rcvif); + if (!ia) + continue; + memcpy(sin, &IA_SIN(ia)->sin_addr, sizeof(struct in_addr)); + ipt->ipt_ptr += sizeof(struct in_addr); + break; + case IPOPT_TS_PRESPEC: + if (ipt->ipt_ptr + sizeof(n_time) + + sizeof(struct in_addr) > ipt->ipt_len) + goto bad; + memcpy(&ipaddr.sin_addr, sin, sizeof(struct in_addr)); + if (ifa_ifwithaddr((SA)&ipaddr) == 0) + continue; + ipt->ipt_ptr += sizeof(struct in_addr); + break; + default: + goto bad; + } + ntime = iptime(); + memcpy(cp+ipt->ipt_ptr - 1, &ntime, sizeof(n_time)); + ipt->ipt_ptr += sizeof(n_time); + default: + break; + } + } + if (forward) { + ip_forward(m, 1); + return 1; + } + return 0; +bad: + ip->ip_len -= ip->ip_hl << 2; + icmp->error(m, type, code, 0, 0); + ipstat.ips_badoptions++; + return 1; +} + + +struct mbuf * ip_srcroute(void) +{ + struct in_addr *p, *q; + struct mbuf *m; + + if (ip_nhops == 0) + return NULL; + + m = m_get(MT_SOOPTS); + if (!m) + return NULL; + +#define OPTSIZ (sizeof(ip_srcrt.nop) + sizeof(ip_srcrt.srcopt)) + + m->m_len = ip_nhops * sizeof(struct in_addr) + sizeof(struct in_addr) + OPTSIZ; + + p = &ip_srcrt.route[ip_nhops-1]; + *(mtod(m, struct in_addr*)) = *p--; + ip_srcrt.nop = IPOPT_NOP; + ip_srcrt.srcopt[IPOPT_OFFSET] = IPOPT_MINOFF; + memcpy(mtod(m, caddr_t) + sizeof(struct in_addr), &ip_srcrt.nop, OPTSIZ); + q = (struct in_addr*)(mtod(m, caddr_t) + sizeof(struct in_addr) + OPTSIZ); + +#undef OPTSIZ + + while (p >= ip_srcrt.route) { + *q++ = *p--; + } + + *q = ip_srcrt.dst; + return m; +} + +/* XXX - don't think these are thread safe somehow! + * look at adding locking or making them thread safe + */ +static void ip_enq(struct ipasfrag *p, struct ipasfrag *prev) +{ + p->ipf_prev = prev; + p->ipf_next = prev->ipf_next; + prev->ipf_next->ipf_prev = p; + prev->ipf_next = p; +} + +static void ip_deq(struct ipasfrag *p) +{ + p->ipf_prev->ipf_next = p->ipf_next; + p->ipf_next->ipf_prev = p->ipf_prev; +} + +static void ip_freef(struct ipq *fp) +{ + struct ipasfrag *q, *p; + + for (q = fp->ipq_next; q!= (struct ipasfrag*)fp; q = p) { + p = q->ipf_next; + ip_deq(q); + m_freem(dtom(q)); + } + remque(fp); + m_free(dtom(fp)); +} + +static struct ip *ip_reass(struct ipasfrag *ip, struct ipq *fp) +{ + struct mbuf *m = dtom(ip); + struct ipasfrag *q; + struct mbuf *t; + int hlen = ip->ip_hl << 2; + int i, next; + + m->m_data += hlen; + m->m_len -= hlen; + + if (!fp) { + if ((t = m_get(MT_FTABLE)) == NULL) + goto dropfrag; + fp = mtod(t, struct ipq*); + insque(fp, &ipq); + fp->ipq_ttl = IPFRAGTTL; + fp->ipq_p = ip->ip_p; + fp->ipq_id = ip->ip_id; + fp->ipq_next = fp->ipq_prev = (struct ipasfrag*)fp; + fp->ipq_src = ((struct ip*)ip)->ip_src; + fp->ipq_dst = ((struct ip*)ip)->ip_dst; + q = (struct ipasfrag*)fp; + goto insert; + } + /* Find a fragment that begins after the one we're trying to insert */ + for (q = fp->ipq_next; q != (struct ipasfrag*)fp; q = q->ipf_next) + if (q->ip_off > ip->ip_off) + break; + /* If we have a preceeding fragment, check for overlaps and discard + * the overlapped data from the new fragment + */ + if (q->ipf_prev != (struct ipasfrag*)fp) { + i = q->ipf_prev->ip_off + q->ipf_prev->ip_len - ip->ip_off; + if (i > 0) { + /* overlapped! */ + if (i >= ip->ip_len) + goto dropfrag; + m_adj(dtom(ip), i); + ip->ip_off += i; + ip->ip_len -= i; + } + } + /* Trim overlapping fragments or if they overlap totally simply drop + * them + */ + while (q != (struct ipasfrag*)fp && ip->ip_off + ip->ip_len > q->ip_off) { + i = (ip->ip_off + ip->ip_len) - q->ip_off; + if (i < q->ip_len) { + q ->ip_len -= i; + q->ip_off += i; + m_adj(dtom(q), i); + break; + } + q = q->ipf_next; + m_freem(dtom(q->ipf_prev)); + ip_deq(q->ipf_prev); + } +insert: + ip_enq(ip, q->ipf_prev); + next = 0; + for (q = fp->ipq_next; q != (struct ipasfrag*)fp; q = q->ipf_next) { + if (q->ip_off != next) + return NULL; + next += q->ip_len; + } + if (q->ipf_prev->ipf_mff & 1) + return NULL; + /* we're the last fragment */ + q = fp->ipq_next; + m = dtom(q); + t = m->m_next; + m->m_next = NULL; + m_cat(m, t); + q = q->ipf_next; + while (q != (struct ipasfrag*)fp) { + t = dtom(q); + q = q->ipf_next; + m_cat(m, t); + } + /* create new header */ + ip = fp->ipq_next; + ip->ip_len = next; + ip->ipf_mff &= ~1; + ((struct ip*)ip)->ip_src = fp->ipq_src; + ((struct ip*)ip)->ip_dst = fp->ipq_dst; + remque(fp); + m_free(dtom(fp)); + m = dtom(ip); + m->m_len += (ip->ip_hl << 2); + m->m_data -= (ip->ip_hl << 2); + if (m->m_flags & M_PKTHDR) { + int plen = 0; + for (t=m;m;m = m->m_next) + plen += m->m_len; + t->m_pkthdr.len = plen; + } + return ((struct ip*)ip); + +dropfrag: + ipstat.ips_fragdropped++; + m_freem(m); + return NULL; +} + +void ipv4_input(struct mbuf *m, int hdrlen) +{ + struct ip *ip; + struct in_ifaddr *ia = NULL; + int hlen; + struct ipq *fp; + +#if SHOW_DEBUG + dump_ipv4_header(buf); +#endif + if (!m) + return; + /* If we don't have a pointer to our IP addresses we can't go on */ + if (!ip_ifaddr) { + ip_ifaddr = get_primary_addr(); + if (!ip_ifaddr) { + printf("ipv4_input: no interfaces available! (ip_ifaddr == NULL)\n"); + goto bad; + } + } + + ipstat.ips_total++; + /* Get the whole header in the first mbuf */ + if (m->m_len < sizeof(struct ip) && + (m = m_pullup(m, sizeof(struct ip))) == NULL) { + ipstat.ips_toosmall++; + return; + } + ip = mtod(m, struct ip *); + + /* Check IP version... */ + if (ip->ip_v != IPVERSION) { + printf("Wrong IP version! %d\n", ip->ip_v); + ipstat.ips_badvers++; + goto bad; + } + /* Figure out of header length */ + hlen = ip->ip_hl << 2; + /* Check we're at least the minimum possible length */ + if (hlen < sizeof(struct ip)) { + ipstat.ips_badhlen++; + goto bad; + } + /* Check again we have the entire header in the first mbuf */ + if (hlen > m->m_len) { + if ((m = m_pullup(m, hlen)) == NULL) { + ipstat.ips_badhlen++; + goto bad; + } + ip = mtod(m, struct ip *); + } + + /* Checksum (should be 0) */ + if ((ip->ip_sum = in_cksum(m, hlen, 0)) != 0) { + printf("ipv4_input: checksum failed\n"); + ipstat.ips_badsum++; + goto bad; + } + + /* we put the length into host order here... */ + ip->ip_len = ntohs(ip->ip_len); + /* sanity check. Datagram MUST be longer than the header! */ + if (ip->ip_len < hlen) { + ipstat.ips_badhlen++; + goto bad; + } + ip->ip_id = ntohs(ip->ip_id); + ip->ip_off = ntohs(ip->ip_off); + + /* the first mbuf should be the packet hdr, so check it's length */ + if (m->m_pkthdr.len < ip->ip_len) { + ipstat.ips_tooshort++; + goto bad; + } + + /* Strip excess data from mbuf */ + if (m->m_pkthdr.len > ip->ip_len) { + if (m->m_len == m->m_pkthdr.len) { + m->m_len = ip->ip_len; + m->m_pkthdr.len = ip->ip_len; + } else + m_adj(m, ip->ip_len - m->m_pkthdr.len); + } + + /* options processing */ + ip_nhops = 0; + if (hlen > sizeof(struct ip) && ip_dooptions(m)) + return; + + for (ia = ip_ifaddr;ia; ia = ia->ia_next) { + if (IA_SIN(ia)->sin_addr.s_addr == ip->ip_dst.s_addr) + goto ours; + + if (ia->ia_ifp == m->m_pkthdr.rcvif && + (ia->ia_ifp->if_flags & IFF_BROADCAST)) { + uint32 t; + + if (satosin(&ia->ia_broadaddr)->sin_addr.s_addr == ip->ip_dst.s_addr) + goto ours; + if (ip->ip_dst.s_addr == ia->ia_netbroadcast.s_addr) + goto ours; + t = ntohl(ip->ip_dst.s_addr); + if (t == ia->ia_subnet) + goto ours; + if (t == ia->ia_net) + goto ours; + } + } + + if (ip->ip_dst.s_addr == (uint32)INADDR_BROADCAST) + goto ours; + if (ip->ip_dst.s_addr == INADDR_ANY) + goto ours; + + if (ipforwarding == 0) { + ipstat.ips_cantforward++; + m_freem(m); + } else + ip_forward(m, 0); + return; +ours: + if (ip->ip_off & ~IP_DF) { + if (m->m_flags & M_EXT) { + if ((m = m_pullup(m, sizeof(struct ip))) == NULL) { + ipstat.ips_toosmall++; + return; + } + ip = mtod(m, struct ip*); + } + for (fp = ipq.next; fp != &ipq; fp = fp->next) { + if (ip->ip_id == fp->ipq_id && + ip->ip_src.s_addr == fp->ipq_src.s_addr && + ip->ip_dst.s_addr == fp->ipq_dst.s_addr && + ip->ip_p == fp->ipq_p) + goto found; + } + fp = NULL; +found: + ip->ip_len -= hlen; + ((struct ipasfrag*)ip)->ipf_mff &= ~1; + if (ip->ip_off & IP_MF) + ((struct ipasfrag*)ip)->ipf_mff |= 1; + ip->ip_off <<= 3; + if (((struct ipasfrag*)ip)->ipf_mff & 1 || ip->ip_off) { + ipstat.ips_fragments++; + ip = ip_reass((struct ipasfrag*)ip, fp); + if (ip == NULL) + return; + ipstat.ips_reassembled++; + m = dtom(ip); + } else if (fp) + ip_freef(fp); + } else + ip->ip_len -= hlen; + +#if SHOW_ROUTE + /* This just shows which interface we're planning on using */ + printf("Accepting packet [%d] to address %08lx via device %s from src addr %08lx\n", + ip->ip_p, ntohl(ip->ip_dst.s_addr), m->m_pkthdr.rcvif->if_name, + ntohl(ip->ip_src.s_addr)); +#endif + + ipstat.ips_delivered++; + if (proto[ip->ip_p] && proto[ip->ip_p]->pr_input) { + proto[ip->ip_p]->pr_input(m, hlen); + return; + } else { + printf("proto[%d] = %p\n", ip->ip_p, proto[ip->ip_p]); + goto bad; + } + + return; +bad: + m_freem(m); + return; +} + +static int ip_optcopy(struct ip *ip, struct ip *jp) +{ + uint8 *cp, *dp; + int opt, optlen, cnt; + + cp =(uint8*)(ip + 1); + dp = (uint8*)(jp + 1); + cnt = (ip->ip_hl << 2) - sizeof(struct ip); + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[0]; + if (opt == IPOPT_EOL) + break; + if (opt == IPOPT_NOP) { + *dp++ = IPOPT_NOP; + optlen = 1; + continue; + } else + optlen = cp[IPOPT_OLEN]; + if (optlen > cnt) + optlen = cnt; + if (IPOPT_COPIED(opt)) { + memcpy(dp, cp, optlen); + dp += optlen; + } + } + for (optlen = dp - (uint8*)(jp + 1); optlen & 0x3; optlen ++) + *dp++ = IPOPT_EOL; + return (optlen); +} + +int ipv4_output(struct mbuf *m0, struct mbuf *opt, struct route *ro, + int flags, void *optp) +{ + struct mbuf *m = m0; + struct ip *ip = mtod(m, struct ip*), *mhip; + struct route iproute; /* temporary route we may need */ + struct sockaddr_in *dst; /* destination address */ + struct in_ifaddr *ia; + int error = 0, hlen = sizeof(struct ip); + struct ifnet *ifp = NULL; + int len, off; + + /* handle options... */ + if (opt) { + m = ip_insertoptions(m, opt, &len); + hlen = len; + } + + ip = mtod(m, struct ip*); + + if ((flags & (IP_FORWARDING | IP_RAWOUTPUT)) == 0) { + ip->ip_v = IPVERSION; + ip->ip_off &= IP_DF; + ip->ip_id = htons(ip_id++); + ip->ip_hl = hlen >> 2; + ipstat.ips_localout++; + } else + hlen = ip->ip_hl << 2; + + /* route the packet! */ + if (!ro) { + ro = &iproute; + memset(ro, 0, sizeof(iproute)); + } + dst = (struct sockaddr_in *)&ro->ro_dst; + + if (ro && ro->ro_rt && + ((ro->ro_rt->rt_flags & RTF_UP) == 0 || /* route isn't available */ + dst->sin_addr.s_addr != ip->ip_dst.s_addr)) { /* not same ip address */ + RTFREE(ro->ro_rt); + ro->ro_rt = NULL; + } + if (ro->ro_rt == NULL) { + memset(&ro->ro_dst, 0, sizeof(ro->ro_dst)); + dst->sin_family = AF_INET; + dst->sin_len = sizeof(*dst); + dst->sin_addr = ip->ip_dst; + } + if (flags & IP_ROUTETOIF) { + /* we're routing to an interface... */ + if (!(ia = ifatoia(ifa_ifwithdstaddr(sintosa(dst)))) && + !(ia = ifatoia(ifa_ifwithnet(sintosa(dst))))) { + ipstat.ips_noroute++; + error = ENETUNREACH; + goto bad; + } + ifp = ia->ia_ifp; + ip->ip_ttl = 1; + } else { + /* normal routing */ + if (ro->ro_rt == NULL) + rtalloc(ro); + if (ro->ro_rt == NULL) { + ipstat.ips_noroute++; + printf("EHOSTUNREACH\n"); + error = EHOSTUNREACH; + goto bad; + } + + ia = ifatoia(ro->ro_rt->rt_ifa); + ifp = ro->ro_rt->rt_ifp; + atomic_add((volatile long *)&ro->ro_rt->rt_use, 1); + if (ro->ro_rt->rt_flags & RTF_GATEWAY) + dst = (struct sockaddr_in *) ro->ro_rt->rt_gateway; + } + /* make sure we have an outgoing address. if not yet specified, use the + * address of the outgoing interface + */ + if (ip->ip_src.s_addr == INADDR_ANY) + ip->ip_src = IA_SIN(ia)->sin_addr; + + if ((in_broadcast(dst->sin_addr, ifp))) { + if ((ifp->if_flags & IFF_BROADCAST) == 0) { + error = EADDRNOTAVAIL; + goto bad; + } + if ((flags & IP_ALLOWBROADCAST) == 0) { + error = EACCES; + goto bad; + } + if (ip->ip_len > ifp->if_mtu) { + error = EMSGSIZE; + goto bad; + } + m->m_flags |= M_BCAST; + } else + m->m_flags &= ~M_BCAST; + +#if SHOW_ROUTE + /* This just shows which interface we're planning on using */ + printf("Sending to address %08lx via device %s using src addr %08lx\n", + ntohl(ip->ip_dst.s_addr), ifp->if_name, ntohl(ip->ip_src.s_addr)); +#endif + + /* if we're small enough, just send the thing! */ + if (ip->ip_len <= ifp->if_mtu) { + ip->ip_len = htons(ip->ip_len); + ip->ip_off = htons(ip->ip_off); + ip->ip_sum = 0; + ip->ip_sum = in_cksum(m, hlen, 0); + /* now send the packet! */ + error = (*ifp->output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); + goto done; + } + + /* datagram is too big for interface! */ + /* IP_DF = do not fragment, so if we're too big we have a problem */ + if ((ip->ip_off & IP_DF)) { + error = EMSGSIZE; + ipstat.ips_cantfrag++; + goto bad; + } + len = (ifp->if_mtu - hlen) & ~7; + /* we need at least 8 bytes per fragment... */ + if (len < 8) { + error = EMSGSIZE; + goto bad; + } + { + int mhlen, firstlen = len; + struct mbuf **mnext = &m->m_nextpkt; + + m0 = m; + mhlen = sizeof(struct ip); + for (off = hlen + len; off < (uint16)ip->ip_len; off += len) { + m = m_gethdr(MT_HEADER); + if (!m) { + error = ENOBUFS; + ipstat.ips_odropped++; + goto sendorfree; + } + m->m_data += max_linkhdr; + mhip = mtod(m, struct ip*); + *mhip = *ip; + if (hlen > sizeof(struct ip)) { + mhlen = ip_optcopy(ip, mhip) + sizeof(struct ip); + mhip->ip_hl = mhlen >> 2; + } + m->m_len = mhlen; + mhip->ip_off = ((off - hlen) >> 3) + (ip->ip_off & ~IP_MF); + if (ip->ip_off & IP_MF) + mhip->ip_off |= IP_MF; + if (off + len >= (uint16)ip->ip_len) + len = (uint16) ip->ip_len - off; + else + mhip->ip_off |= IP_MF; + mhip->ip_len = htons(len + mhlen); + m->m_next = m_copym(m0, off, len); + if (!m->m_next) { + m_freem(m); + error = ENOBUFS; + ipstat.ips_odropped++; + goto sendorfree; + } + m->m_pkthdr.len = mhlen + len; + m->m_pkthdr.rcvif = NULL; + mhip->ip_off = htons(mhip->ip_off); + mhip->ip_sum = 0; + mhip->ip_sum = in_cksum(m, mhlen, 0); + *mnext = m; + mnext = &m->m_nextpkt; + ipstat.ips_ofragments++; + } + m = m0; + m_adj(m, hlen + firstlen - ip->ip_len); + m->m_pkthdr.len = hlen + firstlen; + ip->ip_len = htons(m->m_pkthdr.len); + ip->ip_off = htons((ip->ip_off | IP_MF)); + ip->ip_sum = 0; + ip->ip_sum = in_cksum(m, hlen, 0); +sendorfree: + for (m = m0; m; m = m0) { + m0 = m->m_nextpkt; + m->m_nextpkt = NULL; + if (error == 0) + error = (*ifp->output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); + else + m_freem(m); + } + if (error == 0) + ipstat.ips_fragmented++; + } + +done: + if (ro == &iproute && /* we used our own variable */ + (flags & IP_ROUTETOIF) == 0 && /* we didn't route to an iterface */ + ro->ro_rt) { /* we have an allocated route */ + RTFREE(ro->ro_rt); /* free the route */ + } + + return error; +bad: + m_free(m0); + goto done; +} + +/* ??? - can we just use atomic_add() here? */ +uint16 get_ip_id(void) +{ + uint16 rv = 0; + acquire_sem_etc(id_lock, 1, B_CAN_INTERRUPT, 0); + rv = ip_id++; + release_sem_etc(id_lock, 1, B_CAN_INTERRUPT); + return rv; +} + +static int ipv4_ctloutput(int op, struct socket *so, int level, + int optnum, struct mbuf **mp) +{ + struct inpcb *inp = sotoinpcb(so); + struct mbuf *m = *mp; + int optval; + int error = 0; + + if (level != IPPROTO_IP) { + error = EINVAL; + if (op == PRCO_SETOPT && *mp) + m_free(*mp); + } else { + switch(op) { + case PRCO_SETOPT: + switch(optnum) { + case IP_OPTIONS: + /* process options... */ + break; + case IP_TOS: + case IP_TTL: + case IP_RECVOPTS: + case IP_RECVRETOPTS: + case IP_RECVDSTADDR: + if (m->m_len != sizeof(int)) + error = EINVAL; + else { + optval = *mtod(m, int*); + switch (optnum) { + case IP_TOS: + inp->inp_ip.ip_tos = optval; + break; + case IP_TTL: + inp->inp_ip.ip_ttl = optval; + break; +#define OPTSET(bit) \ + if (optval) \ + inp->inp_flags |= bit; \ + else \ + inp->inp_flags &= ~bit; + + case IP_RECVOPTS: + OPTSET(INP_RECVOPTS); + break; + case IP_RECVRETOPTS: + OPTSET(INP_RECVRETOPTS); + break; + case IP_RECVDSTADDR: + OPTSET(INP_RECVDSTADDR); + break; + } + } + break; +//freeit: + default: + error = EINVAL; + break; + } + if (m) + m_free(m); + break; + case PRCO_GETOPT: + switch(optnum) { + /* XXX - add the code here */ + default: + error = ENOPROTOOPT; + break; + } + break; + } + } + return error; +} + +static void ip_slowtimer(void *data) +{ + struct ipq *fp; + + fp = ipq.next; + if (!fp) + return; + while (fp != &ipq) { + --fp->ipq_ttl; + fp = fp->next; + if (fp->prev->ipq_ttl == 0) { + /* timed out! remove it */ + ipstat.ips_fragtimeout++; + ip_freef(fp->prev); + } + } +} + +static void ipv4_init(void) +{ + if (ip_id == 0) + ip_id = real_time_clock() & 0xffff; + + ip_ifaddr = get_primary_addr(); + + memset(proto, 0, sizeof(struct protosw *) * IPPROTO_MAX); + add_protosw(proto, NET_LAYER2); + + ipq.next = ipq.prev = &ipq; + + timerid = net_add_timer(&ip_slowtimer, NULL, 1000000 / PR_SLOWHZ); +} + +struct protosw my_proto = { + "IPv4", + IPV4_MODULE_PATH, + 0, + NULL, + IPPROTO_IP, + 0, + NET_LAYER2, + + &ipv4_init, + &ipv4_input, + &ipv4_output, + NULL, /* pr_userreq */ + NULL, /* pr_sysctl */ + NULL, + &ipv4_ctloutput, + + NULL, + NULL +}; + + +static int ipv4_module_init(void *cpp) +{ + if (cpp) + core = cpp; + + add_domain(NULL, AF_INET); + add_protocol(&my_proto, AF_INET); + +#ifndef _KERNEL_ + if (!icmp) { + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + strcat(path, "/" ICMP_MODULE_PATH); + + icmpid = load_add_on(path); + if (icmpid > 0) { + status_t rv = get_image_symbol(icmpid, "protocol_info", + B_SYMBOL_TYPE_DATA, (void**)&icmp); + if (rv < 0) { + printf("Failed to get access to IPv4 information!\n"); + return -1; + } + } else { + printf("Failed to load the IPv4 module...\n"); + return -1; + } + icmp->set_core(cpp); + } +#else + if (!icmp) + get_module(ICMP_MODULE_PATH, (module_info**)&icmp); +#endif + + return 0; +} + +static int ipv4_module_stop(void) +{ + remove_protocol(&my_proto); + remove_domain(AF_INET); + + net_remove_timer(timerid); + return 0; +} + +#ifndef _KERNEL_ +void set_core(struct core_module_info *cp) +{ + core = cp; +} +#endif + +_EXPORT struct ipv4_module_info protocol_info = { + { + { + IPV4_MODULE_PATH, + 0, + ipv4_ops + }, + ipv4_module_init, + ipv4_module_stop + }, + +#ifndef _KERNEL_ + set_core, +#endif + + ipv4_output, + get_ip_id, + ipv4_ctloutput, + ip_srcroute, + ip_stripoptions, + ip_srcroute +}; + +#ifdef _KERNEL_ +static status_t ipv4_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + get_module(CORE_MODULE_PATH, (module_info**)&core); + if (!core) + return B_ERROR; + load_driver_symbols("ipv4"); + return B_OK; + case B_MODULE_UNINIT: + return B_OK; + default: + return B_ERROR; + } + return B_OK; +} + + +_EXPORT module_info *modules[] = { + (module_info*) &protocol_info, + NULL +}; +#endif diff --git a/src/add-ons/kernel/network/protocols/raw/raw.c b/src/add-ons/kernel/network/protocols/raw/raw.c new file mode 100644 index 0000000000..86a1848a18 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/raw/raw.c @@ -0,0 +1,378 @@ +/* raw.c */ + +#ifndef _KERNEL_ +#include +#include +#endif + +#include "sys/protosw.h" +#include "sys/domain.h" +#include "sys/socket.h" +#include "netinet/in_pcb.h" +#include "netinet/in.h" +#include "netinet/in_var.h" +#include "netinet/ip_var.h" + +#include "core_module.h" +#include "net_module.h" +#include "core_funcs.h" +#include "raw/raw_module.h" +#include "ipv4/ipv4_module.h" + +#ifdef _KERNEL_ +#include +static status_t raw_ops(int32 op, ...); +#else /* _KERNEL_ */ +#define raw_ops NULL +static image_id ipid; +#endif + +static struct core_module_info *core = NULL; +static struct ipv4_module_info *ipm = NULL; + +static struct inpcb rawinpcb; +static struct sockaddr_in ripsrc; +static int rip_sendspace = 8192; +static int rip_recvspace = 8192; + +void rip_init(void) +{ + rawinpcb.inp_next = rawinpcb.inp_prev = &rawinpcb; + memset(&ripsrc, 0, sizeof(ripsrc)); + ripsrc.sin_family = AF_INET; + ripsrc.sin_len = sizeof(ripsrc); +} + +void rip_input(struct mbuf *m, int hdrlen) +{ + struct ip *ip = mtod(m, struct ip*); + struct inpcb *inp; + struct socket *last = NULL; + + ripsrc.sin_addr = ip->ip_src; + for (inp = rawinpcb.inp_next; inp != &rawinpcb; inp=inp->inp_next) { + if (inp->inp_ip.ip_p && inp->inp_ip.ip_p != ip->ip_p) + continue; + if (inp->laddr.s_addr && inp->laddr.s_addr == ip->ip_dst.s_addr) + continue; + if (inp->faddr.s_addr && inp->faddr.s_addr == ip->ip_src.s_addr) + continue; + if (last) { + struct mbuf *n; + if ((n = m_copym(m, 0, (int)M_COPYALL))) { + if (sbappendaddr(&last->so_rcv, (struct sockaddr*)&ripsrc, + n, NULL) == 0) + m_freem(n); + else + sorwakeup(last); + } + } + last = inp->inp_socket; + } + if (last) { + if (sbappendaddr(&last->so_rcv, (struct sockaddr*)&ripsrc, + m, NULL) == 0) + m_freem(m); + else + sorwakeup(last); + } else { + m_freem(m); + ipstat.ips_noproto++; + ipstat.ips_delivered--; + } + return; +} + +int rip_output(struct mbuf *m, struct socket *so, uint32 dst) +{ + struct ip *ip; + struct inpcb *inp = sotoinpcb(so); + struct mbuf *opts; + int flags = (so->so_options & SO_DONTROUTE) | IP_ALLOWBROADCAST; + + if ((inp->inp_flags & INP_HDRINCL) == 0) { + M_PREPEND(m, sizeof(struct ip)); + ip = mtod(m, struct ip *); + ip->ip_p = inp->inp_ip.ip_p; + ip->ip_len = m->m_pkthdr.len; + ip->ip_src = inp->laddr; + ip->ip_dst.s_addr = dst; + ip->ip_ttl = MAXTTL; + opts = inp->inp_options; + ip->ip_off = 0; + ip->ip_tos = 0; + } else { + ip = mtod(m, struct ip *); + /* ip_output relies on having the ip->ip_len in host + * order...this is lame... */ + ip->ip_len = ntohs(ip->ip_len); + if (ip->ip_id == 0) + if (ipm) + ip->ip_id = htons(ipm->ip_id()); + + opts = NULL; + flags |= IP_RAWOUTPUT; + ipstat.ips_rawout++; + } + + if (ipm) { + return ipm->output(m, opts, &inp->inp_route, flags, NULL); + } + /* XXX - last arg should be inp->inp_moptions when we have multicast */ + + return 0; +} + +int rip_userreq(struct socket *so, int req, struct mbuf *m, struct mbuf *nam, + struct mbuf *control) +{ + int error = 0; + struct inpcb *inp = sotoinpcb(so); + struct ifnet *interfaces = get_interfaces(); + + switch(req) { + case PRU_ATTACH: + if (inp) { + printf("Trying to attach to a socket already attached!\n"); + return EINVAL; + } + if ((error = soreserve(so, rip_sendspace, rip_recvspace)) || + (error = in_pcballoc(so, &rawinpcb))) + break; + inp = (struct inpcb*)so->so_pcb; + inp->inp_ip.ip_p = (int)nam; + break; + case PRU_DISCONNECT: + if ((so->so_state & SS_ISCONNECTED) == 0) { + error = ENOTCONN; + break; + } + case PRU_ABORT: + soisdisconnected(so); + case PRU_DETACH: + if (inp == NULL) { + printf("Can't detach from NULL protocol block!\n"); + error = EINVAL; + break; + } + in_pcbdetach(inp); + break; + case PRU_SEND: { + uint32 dst; + if ((so->so_state & SS_ISCONNECTED)) { + if (nam) { + error = EISCONN; + break; + } + dst = inp->faddr.s_addr; + } else { + if (!nam) { + error = ENOTCONN; + break; + } + dst = mtod(nam, struct sockaddr_in *)->sin_addr.s_addr; + } + error = rip_output(m, so, dst); + m = NULL; + break; + } + case PRU_BIND: { + struct sockaddr_in *addr = mtod(nam, struct sockaddr_in *); + if (nam->m_len != sizeof(*addr)) { + error = EINVAL; + break; + } + if ((interfaces) || + ((addr->sin_family != AF_INET) && + (addr->sin_family != AF_IMPLINK)) || + (addr->sin_addr.s_addr && + ifa_ifwithaddr((struct sockaddr*)addr) == 0)) { + error = EADDRNOTAVAIL; + break; + } + inp->laddr = addr->sin_addr; + break; + } + case PRU_CONNECT: { + struct sockaddr_in *addr = mtod(nam, struct sockaddr_in *); + + if (nam->m_len != sizeof(*addr)) { + error = EINVAL; + break; + } + if ((interfaces == NULL)) { + error = EADDRNOTAVAIL; + break; + } + if ((addr->sin_family != AF_INET) && + (addr->sin_family != AF_IMPLINK)) { + error = EAFNOSUPPORT; + break; + } + inp->faddr = addr->sin_addr; + soisconnected(so); + break; + } + case PRU_CONNECT2: + error = EOPNOTSUPP; + break; + case PRU_SHUTDOWN: + socantsendmore(so); + break; + case PRU_RCVOOB: + case PRU_RCVD: + case PRU_LISTEN: + case PRU_ACCEPT: + case PRU_SENDOOB: + error = EINVAL;//EOPNOTSUPP; + break; + /* add remaining cases */ + } + + return error; +} + +int rip_ctloutput(int op, struct socket *so, int level, + int optnum, struct mbuf **m) +{ + struct inpcb *inp = sotoinpcb(so); + + if (level != IPPROTO_IP) + return EINVAL; + + switch (optnum) { + case IP_HDRINCL: + if (op == PRCO_SETOPT || op == PRCO_GETOPT) { + if (m == NULL || *m == NULL || (*m)->m_len < sizeof(int)) + return EINVAL; + if (op == PRCO_SETOPT) { + if (*mtod(*m, int*)) + inp->inp_flags |= INP_HDRINCL; + else + inp->inp_flags &= ~INP_HDRINCL; + m_free(*m); + } else { + (*m)->m_len = sizeof(int); + *mtod(*m, int*) = inp->inp_flags & INP_HDRINCL; + } + return 0; + } + break; + /* XXX - Add other options here */ + } +#ifdef _KERNEL_ + return ipm->ctloutput(op, so, level, optnum, m); +#else +/* XXX - get this working for app...? */ + return 0; +#endif +} + +static struct protosw my_protocol = { + "Raw IP module", + RAW_MODULE_PATH, + SOCK_RAW, + NULL, + 0, + PR_ATOMIC | PR_ADDR, + NET_LAYER4, + + &rip_init, + &rip_input, + NULL, + &rip_userreq, + NULL, /* pr_sysctl */ + NULL, + &rip_ctloutput, + + NULL, + NULL +}; + +static int raw_module_init(void *cpp) +{ + if (cpp) + core = cpp; + + add_domain(NULL, AF_INET); + add_protocol(&my_protocol, AF_INET); + +#ifndef _KERNEL_ + if (!ipm) { + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + strcat(path, "/" IPV4_MODULE_PATH); + + ipid = load_add_on(path); + if (ipid > 0) { + status_t rv = get_image_symbol(ipid, "protocol_info", + B_SYMBOL_TYPE_DATA, (void**)&ipm); + if (rv < 0) { + printf("Failed to get access to IPv4 information!\n"); + return -1; + } + ipm->set_core(cpp); + } else { + printf("Failed to load the IPv4 module...%ld [%s]\n", + ipid, strerror(ipid)); + return -1; + } + } +#else + if (!ipm) + get_module(IPV4_MODULE_PATH, (module_info**)&ipm); +#endif + + return 0; +} + +static int raw_module_stop(void) +{ +#ifndef _KERNEL_ + unload_add_on(ipid); +#else + put_module(IPV4_MODULE_PATH); +#endif + + remove_protocol(&my_protocol); + remove_domain(AF_INET); + + return 0; +} + +_EXPORT struct raw_module_info protocol_info = { + { + { + RAW_MODULE_PATH, + 0, + raw_ops + }, + raw_module_init, + raw_module_stop + }, + &rip_input +}; + +#ifdef _KERNEL_ +static status_t raw_ops(int32 op, ...) +{ + switch(op) { + case B_MODULE_INIT: + get_module(CORE_MODULE_PATH, (module_info**)&core); + if (!core) + return B_ERROR; + return B_OK; + case B_MODULE_UNINIT: + break; + default: + return B_ERROR; + } + return B_OK; +} + +_EXPORT module_info *modules[] = { + (module_info*)&protocol_info, + NULL +}; + +#endif diff --git a/src/add-ons/kernel/network/protocols/tcp/arc4random.c b/src/add-ons/kernel/network/protocols/tcp/arc4random.c new file mode 100644 index 0000000000..9b3514a5ab --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/arc4random.c @@ -0,0 +1,146 @@ +/* + * Arc4 random number generator for OpenBSD. + * Copyright 1996 David Mazieres . + * + * Modification and redistribution in source and binary forms is + * permitted provided that due credit is given to the author and the + * OpenBSD project by leaving this copyright notice intact. + */ + +/* + * This code is derived from section 17.1 of Applied Cryptography, + * second edition, which describes a stream cipher allegedly + * compatible with RSA Labs "RC4" cipher (the actual description of + * which is a trade secret). The same algorithm is used as a stream + * cipher called "arcfour" in Tatu Ylonen's ssh package. + * + * Here the stream cipher has been modified always to include the time + * when initializing the state. That makes it impossible to + * regenerate the same random sequence twice, so this can't be used + * for encryption, but will generate good random numbers. + * + * RC4 is a registered trademark of RSA Laboratories. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +struct arc4_stream { + uint8 i; + uint8 j; + uint8 s[256]; +}; + +int rs_initialized; +static struct arc4_stream rs; + + +static inline void arc4_init(struct arc4_stream *as) +{ + int n; + + for (n = 0; n < 256; n++) + as->s[n] = n; + as->i = 0; + as->j = 0; +} + + +static inline void arc4_addrandom(struct arc4_stream *as, + u_char *dat, int datlen) +{ + int n; + uint8 si; + + as->i--; + for (n = 0; n < 256; n++) { + as->i = (as->i + 1); + si = as->s[as->i]; + as->j = (as->j + si + dat[n % datlen]); + as->s[as->i] = as->s[as->j]; + as->s[as->j] = si; + } + as->j = as->i; +} + + +static void arc4_stir(struct arc4_stream *as) +{ + int fd; + struct { + struct timeval tv; + u_int rnd[(128 - sizeof(struct timeval)) / sizeof(u_int)]; + } rdat; + + gettimeofday(&rdat.tv, NULL); + fd = open("/dev/arandom", O_RDONLY); + if (fd != -1) { + read(fd, rdat.rnd, sizeof(rdat.rnd)); + close(fd); + } + /* fd < 0 or failed sysctl ? Ah, what the heck. We'll just take + * whatever was on the stack... */ + + arc4_addrandom(as, (void *) &rdat, sizeof(rdat)); +} + + +static inline uint8 arc4_getbyte(struct arc4_stream *as) +{ + uint8 si, sj; + + as->i = (as->i + 1); + si = as->s[as->i]; + as->j = (as->j + si); + sj = as->s[as->j]; + as->s[as->i] = sj; + as->s[as->j] = si; + return (as->s[(si + sj) & 0xff]); +} + + +static inline uint32 arc4_getword(struct arc4_stream *as) +{ + uint32 val; + val = arc4_getbyte(as) << 24; + val |= arc4_getbyte(as) << 16; + val |= arc4_getbyte(as) << 8; + val |= arc4_getbyte(as); + return val; +} + + +void arc4random_stir(void) +{ + if (!rs_initialized) { + arc4_init(&rs); + rs_initialized = 1; + } + arc4_stir(&rs); +} + +void arc4random_addrandom(u_char *dat, int datlen) +{ + if (!rs_initialized) + arc4random_stir(); + arc4_addrandom(&rs, dat, datlen); +} + + +uint32 arc4random() +{ + if (!rs_initialized) + arc4random_stir(); + return arc4_getword(&rs); +} diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.c b/src/add-ons/kernel/network/protocols/tcp/tcp.c new file mode 100644 index 0000000000..592b2802c7 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.c @@ -0,0 +1,600 @@ +/* udp.c + */ + +#ifndef _KERNEL_ +#include +#include +#endif + +#include "pools.h" +#include "net_misc.h" +#include "protocols.h" +#include "netinet/in_systm.h" +#include "netinet/in_var.h" +#include "netinet/in_pcb.h" +#include "netinet/ip.h" +#include "sys/domain.h" +#include "sys/protosw.h" +#include "netinet/ip_var.h" +#include "netinet/tcp.h" +#include "netinet/tcp_timer.h" +#include "netinet/tcp_fsm.h" +#include "netinet/tcp_seq.h" +#include "netinet/tcp_var.h" +#include "netinet/tcpip.h" + +#include "core_module.h" +#include "net_module.h" +#include "core_funcs.h" +#include "ipv4/ipv4_module.h" +#include "net_timer.h" + +#ifdef _KERNEL_ +#include +#define TCP_MODULE_PATH "network/protocol/tcp" +static status_t tcp_ops(int32 op, ...); +#else /* _KERNEL_ */ +#define tcp_ops NULL +#define TCP_MODULE_PATH "modules/protocol/tcp" +static image_id ipid = -1; +#endif + +struct core_module_info *core = NULL; +struct ipv4_module_info *ipm = NULL; + +/* Declaration as we don't have it natively... */ +uint32 arc4random(); + +struct protosw *proto[IPPROTO_MAX]; +struct pool_ctl *tcppool = NULL; + +/* patchable/settable parameters for tcp */ +int tcp_mssdflt = TCP_MSS; +int tcp_rttdflt = TCPTV_SRTTDFLT / PR_SLOWHZ; +static net_timer_id slowtim; +static net_timer_id fasttim; + +struct inpcb *tcp_last_inpcb = NULL; + +static uint32 tcp_sendspace = 8192; /* size of send buffer */ +static uint32 tcp_recvspace = 8192; /* size of recieve buffer */ + +void tcp_init(void) +{ + tcp_now = arc4random() / 2; + tcp_iss = 1; + + tcb.inp_next = tcb.inp_prev = &tcb; + if (max_protohdr < sizeof(struct tcpiphdr)) + max_protohdr = sizeof(struct tcpiphdr); + memset(&tcpstat, 0, sizeof(struct tcpstat)); + + memset(proto, 0, sizeof(struct protosw *) * IPPROTO_MAX); + add_protosw(proto, NET_LAYER4); + + if (!tcppool) + pool_init(&tcppool, sizeof(struct tcpcb)); + + /* Add timers... */ + /* Assuming we're using usecs, then we call PR_SLOWHZ per sec + * which is 1,000,000 / PR_SLOWHZ + */ + slowtim = net_add_timer(&tcp_slowtimer, NULL, 1000000 / PR_SLOWHZ); + fasttim = net_add_timer(&tcp_fasttimer, NULL, 1000000 / PR_FASTHZ); +} + +struct tcpiphdr *tcp_template(struct tcpcb *tp) +{ + struct inpcb *inp = tp->t_inpcb; + struct mbuf *m; + struct tcpiphdr *n = NULL; + + if ((n = tp->t_template) == NULL) { + m = m_get(MT_HEADER); + if (m == NULL) + return NULL; + m->m_len = sizeof(struct tcpiphdr); + n = mtod(m, struct tcpiphdr*); + } + /* ??? maybe we should just memset 0 and then fill in what we need? */ + n->ti_next = n->ti_prev = NULL; + n->ti_x1 = 0; + n->ti_pr = IPPROTO_TCP; + n->ti_len = htons(sizeof(struct tcpiphdr) - sizeof(struct ip)); + n->ti_src = inp->laddr; + n->ti_dst = inp->faddr; + n->ti_sport = inp->lport; + n->ti_dport = inp->fport; + n->ti_seq = 0; + n->ti_ack = 0; + n->ti_x2 = 0; + n->ti_off = 5; + n->ti_flags = 0; + n->ti_win = 0; + n->ti_sum = 0; + n->ti_urp = 0; + return n; +} + +struct tcpcb *tcp_close(struct tcpcb *tp) +{ + struct tcpiphdr *t; + struct inpcb *inp = tp->t_inpcb; + struct socket *so = inp->inp_socket; + struct mbuf *m; + struct rtentry *rt; + + /* did we send enough data to get some meaningful iformation? + * If we did save it in the routing entry. + * We define enough as being the sendpipesize (default 8k) x 16 + * which should give us 16 rtt samples, assuming of course we only + * have one sample per frame. + * 16 samples is enough for the srtt filter to converge to within 5% + * of the correct value. + * + * We don't however update the default route or anything else that the + * user has locked. + */ + if (SEQ_LT(tp->iss + so->so_snd.sb_hiwat * 16, tp->snd_max) && + (rt = inp->inp_route.ro_rt) && + ((struct sockaddr_in*)rt_key(rt))->sin_addr.s_addr != INADDR_ANY) { + uint32 i; + + if ((rt->rt_rmx.rmx_locks & RTV_RTT) == 0) { + i = tp->t_srtt * (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE)); + if (rt->rt_rmx.rmx_rtt && i) + /* + * update this value with half the old and new values, + * converting scale. + */ + rt->rt_rmx.rmx_rtt = (rt->rt_rmx.rmx_rtt + i) / 2; + else + rt->rt_rmx.rmx_rtt = i; + } + if ((rt->rt_rmx.rmx_locks & RTV_RTTVAR) == 0) { + i = tp->t_rttvar * (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE)); + if (rt->rt_rmx.rmx_rttvar && i) + rt->rt_rmx.rmx_rttvar = (rt->rt_rmx.rmx_rttvar + i) / 2; + else + rt->rt_rmx.rmx_rttvar = i; + } + /* update the pipelimit (ssthresh) */ + if ((rt->rt_rmx.rmx_locks & RTV_SSTHRESH) == 0 && + ((i = tp->snd_ssthresh) && (rt->rt_rmx.rmx_ssthresh || + i < (rt->rt_rmx.rmx_sendpipe / 2)))) { + /* convert the limit from user data bytes to + * packets and then to packet data bytes + */ + i = (i + tp->t_maxseg / 2) / tp->t_maxseg; + if (i < 2) + i = 2; + i *= (u_long)(tp->t_maxseg + sizeof(struct tcpiphdr)); + if (rt->rt_rmx.rmx_ssthresh) + rt->rt_rmx.rmx_ssthresh = (rt->rt_rmx.rmx_ssthresh + i) / 2; + else + rt->rt_rmx.rmx_ssthresh = i; + } + } + /* free our reassembly queue */ + t = tp->seg_next; + while (t != (struct tcpiphdr*)tp) { + t = (struct tcpiphdr*)t->ti_next; + m = REASS_MBUF((struct tcpiphdr*)t->ti_prev); + remque(t->ti_prev); + m_freem(m); + } + if (tp->t_template) + (void)m_free(dtom(tp->t_template)); + + pool_put(tcppool, tp); + inp->inp_ppcb = NULL; + soisdisconnected(so); + if (inp == tcp_last_inpcb) + tcp_last_inpcb = &tcb; + in_pcbdetach(inp); + tcpstat.tcps_closed++; + + return NULL; +} + +struct tcpcb *tcp_drop(struct tcpcb *tp, int error) +{ + struct socket *so = tp->t_inpcb->inp_socket; + + if (TCPS_HAVERCVDSYN(tp->t_state)) { + tp->t_state = TCPS_CLOSED; + (void) tcp_output(tp); + tcpstat.tcps_drops++; + } else + tcpstat.tcps_conndrops++; + + if (error == ETIMEDOUT && tp->t_softerror) + error = tp->t_softerror; + so->so_error = error; + return tcp_close(tp); +} + +void tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, + tcp_seq ack, tcp_seq seq, int flags) +{ + int tlen; + int win = 0; + struct route *ro = NULL; + + if (tp) { + win = sbspace(&tp->t_inpcb->inp_socket->so_rcv); + ro = &tp->t_inpcb->inp_route; + } + if (m == NULL) { + m = m_gethdr(MT_HEADER); + if (!m) + return; + tlen = 0; + m->m_data += max_linkhdr; + *mtod(m, struct tcpiphdr*) = *ti; + ti = mtod(m, struct tcpiphdr*); + flags = TH_ACK; + } else { + m_freem(m->m_next); + m->m_next = NULL; + m->m_data = (caddr_t) ti; + m->m_len = sizeof(struct tcpiphdr); + tlen = 0; +#define xchng(a,b,type) { type t; t=a; a=b; b= t; } + xchng(ti->ti_dst.s_addr, ti->ti_src.s_addr, uint32); + xchng(ti->ti_dport, ti->ti_sport, uint16); +#undef xchng + } + ti->ti_len = htons((uint16)(sizeof(struct tcphdr) + tlen)); + tlen += sizeof(struct tcpiphdr); + m->m_len = tlen; + m->m_pkthdr.len = tlen; + m->m_pkthdr.rcvif = NULL; + ti->ti_next = ti->ti_prev = NULL; + ti->ti_x1 = 0; + ti->ti_seq = htonl(seq); + ti->ti_ack = htonl(ack); + ti->ti_x2 = 0; + ti->ti_off = sizeof(struct tcphdr) >> 2; + ti->ti_flags = flags; + if (tp) + ti->ti_win = htons((uint16)(win >> tp->rcv_scale)); + else + ti->ti_win = htons((uint16)win); + ti->ti_urp = 0; + ti->ti_sum = 0; + ti->ti_sum = in_cksum(m, tlen, 0); + ((struct ip*)ti)->ip_len = tlen; + ((struct ip*)ti)->ip_ttl = 64;/* XXX - ip_defttl; */ + ipm->output(m, NULL, ro, 0, NULL); +} + +struct tcpcb *tcp_usrclosed(struct tcpcb *tp) +{ + switch(tp->t_state) { + case TCPS_CLOSED: + case TCPS_LISTEN: + case TCPS_SYN_SENT: + tp->t_state = TCPS_CLOSED; + tp = tcp_close(tp); + break; + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + tp->t_state = TCPS_FIN_WAIT_1; + break; + case TCPS_CLOSE_WAIT: + tp->t_state = TCPS_LAST_ACK; + break; + } + if (tp && tp->t_state >= TCPS_FIN_WAIT_2) + soisdisconnected(tp->t_inpcb->inp_socket); + return tp; +} + +static struct tcpcb *tcp_disconnect(struct tcpcb *tp) +{ + struct socket *so = tp->t_inpcb->inp_socket; + + if (tp->t_state < TCPS_ESTABLISHED) + tp = tcp_close(tp); + else if ((so->so_options & SO_LINGER) && so->so_linger == 0) + tp = tcp_drop(tp, 0); + else { + soisdisconnecting(so); + sbflush(&so->so_rcv); + tp = tcp_usrclosed(tp); + if (tp) + tcp_output(tp); + } + return tp; +} + +static struct tcpcb * tcp_newtcpcb(struct inpcb *inp) +{ + struct tcpcb *tp; + tp = (struct tcpcb*)pool_get(tcppool); + if (!tp) + return NULL; + memset(tp, 0, sizeof(*tp)); + tp->seg_next = tp->seg_prev = (struct tcpiphdr*)tp; + tp->t_maxseg = tcp_mssdflt; + tp->t_flags = tcp_do_rfc1323 ? (TF_REQ_SCALE | TF_REQ_TSTMP) : 0; + tp->t_inpcb = inp; + + tp->t_srtt = TCPTV_SRTTBASE; + tp->t_rttvar = tcp_rttdflt * PR_SLOWHZ << 2; + tp->t_rttmin = TCPTV_MIN; + TCPT_RANGESET(tp->t_rxtcur, ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1, + TCPTV_MIN, TCPTV_REXMTMAX); + tp->snd_cwnd = tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT; + + inp->inp_ip.ip_ttl = 64;/* XXX - ip_defttl; */ + inp->inp_ppcb = (caddr_t)tp; + return tp; +} + +static int tcp_attach(struct socket *so) +{ + struct inpcb *inp; + struct tcpcb *tp; + int error = 0; + + if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { + error = soreserve(so, tcp_sendspace, tcp_recvspace); + if (error) + return error; + } + error = in_pcballoc(so, &tcb); + if (error) + return error; + inp = sotoinpcb(so); + tp = tcp_newtcpcb(inp); + if (tp == NULL) { + /* we don't want to free the socket just yet, so + * record the setting of SS_NOFDREF, then clear the bit, + * detach and then reset the bit. + */ + int nofd = so->so_state & SS_NOFDREF; + so->so_state &= ~SS_NOFDREF; + in_pcbdetach(inp); + so->so_state |= nofd; + return ENOBUFS; + } + tp->t_state = TCPS_CLOSED; + return 0; +} + +int tcp_userreq(struct socket *so, int req, struct mbuf *m, + struct mbuf *nam, struct mbuf *control) +{ + struct inpcb *inp; + struct tcpcb *tp = NULL; + int error = 0; + int ostate; + + if (req == PRU_CONTROL) + return in_control(so, (int)m, (caddr_t)nam, (struct ifnet *)control); + if (control && control->m_len) { + m_freem(control); + if (m) + m_freem(m); + return EINVAL; + } + + inp = sotoinpcb(so); + /* When we're attached, the inpcb points at the socket */ + if (inp == NULL && req != PRU_ATTACH) + return EINVAL; + + if (inp) { + tp = intotcpcb(inp); + ostate = tp->t_state; + } else + ostate = 0; + + switch(req) { + case PRU_ATTACH: + if (inp) { + error = EISCONN; + break; + } + error = tcp_attach(so); + if (error) + break; + if ((so->so_options & SO_LINGER) && so->so_linger == 0) + so->so_linger = TCP_LINGERTIME; + tp = sototcpcb(so); + break; + case PRU_DETACH: + if (tp->t_state > TCPS_LISTEN) + tp = tcp_disconnect(tp); + else + tp = tcp_close(tp); + break; + case PRU_BIND: + error = in_pcbbind(inp, nam); + break; + case PRU_LISTEN: + if (inp->lport == 0) + error = in_pcbbind(inp, NULL); + if (error == 0) + tp->t_state = TCPS_LISTEN; + break; + case PRU_CONNECT: + if (inp->lport == 0) { + error = in_pcbbind(inp, NULL); + if (error) + break; + } + + error = in_pcbconnect(inp, nam); + if (error) { + printf("in_pcbconnect gave error %d\n", error); + break; + } + tp->t_template = tcp_template(tp); + if (tp->t_template == NULL) { + in_pcbdisconnect(inp); + error = ENOBUFS; + break; + } + while (tp->request_r_scale < TCP_MAX_WINSHIFT && + (TCP_MAXWIN << tp->request_r_scale) < so->so_rcv.sb_hiwat) + tp->request_r_scale++; + soisconnecting(so); + tcpstat.tcps_connattempt++; + tp->t_state = TCPS_SYN_SENT; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + + tp->iss = tcp_iss; + tcp_iss += TCP_ISSINCR / 2; + tcp_sendseqinit(tp); + error = tcp_output(tp); + break; + case PRU_CONNECT2: + error = EOPNOTSUPP; + break; + case PRU_DISCONNECT: + tp = tcp_disconnect(tp); + break; + case PRU_ACCEPT: + in_setpeeraddr(inp, nam); + break; + case PRU_SLOWTIMO: + tp = tcp_timers(tp, (int)nam); + req |= (int)nam << 8; + break; + case PRU_SEND: + sbappend(&so->so_snd, m); + error = tcp_output(tp); + break; + case PRU_RCVD: + (void) tcp_output(tp); + break; + case PRU_SHUTDOWN: + socantsendmore(so); + tp = tcp_usrclosed(tp); + if (tp) + error = tcp_output(tp); + break; + case PRU_SOCKADDR: + in_setsockaddr(inp, nam); + break; + case PRU_PEERADDR: + in_setpeeraddr(inp, nam); + break; + } +/* XXX - add tcp_trace! + if (tp && (so->so_options & SO_DEBUG)) +*/ + return error; +} + +static struct protosw my_proto = { + "TCP Module", + TCP_MODULE_PATH, + SOCK_STREAM, + NULL, + IPPROTO_TCP, + PR_CONNREQUIRED | PR_WANTRCVD, + NET_LAYER3, + + &tcp_init, + &tcp_input, /* pr_input */ + NULL, /* pr_output */ + &tcp_userreq, /* pr_userreq */ + NULL, /* pr_sysctl */ + NULL, /* pr_ctlinput */ + NULL, /* pr_ctloutput */ + + NULL, + NULL +}; + + +static int tcp_module_init(void *cpp) +{ + if (cpp) + core = cpp; + + add_domain(NULL, AF_INET); + add_protocol(&my_proto, AF_INET); + +#ifndef _KERNEL_ + if (!ipm) { + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + strcat(path, "/" IPV4_MODULE_PATH); + + ipid = load_add_on(path); + if (ipid > 0) { + status_t rv = get_image_symbol(ipid, "protocol_info", + B_SYMBOL_TYPE_DATA, (void**)&ipm); + if (rv < 0) { + printf("Failed to get access to IPv4 information!\n"); + return -1; + } + } else { + printf("Failed to load the IPv4 module...\n"); + return -1; + } + ipm->set_core(cpp); + } +#else + if (!ipm) + get_module(IPV4_MODULE_PATH, (module_info**)&ipm); +#endif + + return 0; +} + +static int tcp_module_stop(void) +{ + net_remove_timer(slowtim); + net_remove_timer(fasttim); + +#ifndef _KERNEL_ + unload_add_on(ipid); +#else + put_module(IPV4_MODULE_PATH); +#endif + + remove_protocol(&my_proto); + remove_domain(AF_INET); + + return 0; +} + +_EXPORT struct kernel_net_module_info protocol_info = { + { + TCP_MODULE_PATH, + 0, + tcp_ops + }, + tcp_module_init, + tcp_module_stop +}; + +#ifdef _KERNEL_ +static status_t tcp_ops(int32 op, ...) +{ + switch(op) { + case B_MODULE_INIT: + get_module(CORE_MODULE_PATH, (module_info**)&core); + if (!core) + return B_ERROR; + return B_OK; + case B_MODULE_UNINIT: + break; + default: + return B_ERROR; + } + return B_OK; +} + +_EXPORT module_info *modules[] = { + (module_info*)&protocol_info, + NULL +}; + +#endif diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp_debug.c b/src/add-ons/kernel/network/protocols/tcp/tcp_debug.c new file mode 100644 index 0000000000..17f2c71bd4 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/tcp_debug.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)COPYRIGHT 1.1 (NRL) 17 January 1995 + * + * NRL grants permission for redistribution and use in source and binary + * forms, with or without modification, of the software and documentation + * created at NRL provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgements: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * This product includes software developed at the Information + * Technology Division, US Naval Research Laboratory. + * 4. Neither the name of the NRL nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THE SOFTWARE PROVIDED BY NRL IS PROVIDED BY NRL AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NRL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of the US Naval + * Research Laboratory (NRL). + */ + +#ifdef TCPDEBUG +/* load symbolic names */ +#define PRUREQUESTS +#define TCPSTATES +#define TCPTIMERS +#define TANAMES +#endif + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#ifndef INET +#include +#endif +#include +#endif /* INET6 */ + +#ifdef TCPDEBUG +int tcpconsdebug = 0; +#endif +/* + * Tcp debug routines + */ +void tcp_trace(int16 act, int16 ostate, struct tcpcb *tp, void *headers, + int req, int len) +{ +#ifdef TCPDEBUG + tcp_seq seq, ack; + int flags; +#endif + struct tcp_debug *td = &tcp_debug[tcp_debx++]; + struct tcpiphdr *ti = (struct tcpiphdr *)headers; + struct tcphdr *th; +#ifdef INET6 + struct tcpipv6hdr *ti6 = (struct tcpipv6hdr *)ti; +#endif + + if (tcp_debx == TCP_NDEBUG) + tcp_debx = 0; + td->td_time = iptime(); + td->td_act = act; + td->td_ostate = ostate; + td->td_tcb = (caddr_t)tp; + if (tp) + td->td_cb = *tp; + else + memset((caddr_t)&td->td_cb, 0, sizeof (*tp)); +#ifdef INET6 + if (tp->pf == PF_INET6) { + if (ti) { + th = &ti6->ti6_t; + td->td_ti6 = *ti6; + } else { + memset(&td->td_ti6, 0, sizeof(struct tcpipv6hdr)); + } + } else +#endif /* INET6 */ + { + if (ti) { + th = &ti->ti_t; + td->td_ti = *ti; + } else { + memset(&td->td_ti, 0, sizeof(struct tcpiphdr)); + } + } + + td->td_req = req; +#ifdef TCPDEBUG + if (tcpconsdebug == 0) + return; + if (tp) + printf("%x %s:", tp, tcpstates[ostate]); + else + printf("???????? "); + printf("%s ", tanames[act]); + switch (act) { + case TA_INPUT: + case TA_OUTPUT: + case TA_DROP: + if (ti == 0) + break; + seq = th->th_seq; + ack = th->th_ack; + if (act == TA_OUTPUT) { + seq = ntohl(seq); + ack = ntohl(ack); + } + if (len) + printf("[%x..%x)", seq, seq+len); + else + printf("%x", seq); + printf("@%x, urp=%x", ack, th->th_urp); + flags = th->th_flags; + if (flags) { +#ifndef lint + char *cp = "<"; +#define pf(f) { if (th->th_flags&TH_##f) { printf("%s%s", cp, "f"); cp = ","; } } + pf(SYN); pf(ACK); pf(FIN); pf(RST); pf(PUSH); pf(URG); +#endif + printf(">"); + } + break; + + case TA_USER: + printf("%s", prurequests[req&0xff]); + if ((req & 0xff) == PRU_SLOWTIMO) + printf("<%s>", tcptimers[req>>8]); + break; + } + if (tp) + printf(" -> %s", tcpstates[tp->t_state]); + /* print out internal state of tp !?! */ + printf("\n"); + if (tp == 0) + return; + printf("\trcv_(nxt,wnd,up) (%x,%x,%x) snd_(una,nxt,max) (%x,%x,%x)\n", + tp->rcv_nxt, tp->rcv_wnd, tp->rcv_up, tp->snd_una, tp->snd_nxt, + tp->snd_max); + printf("\tsnd_(wl1,wl2,wnd) (%x,%x,%x)\n", + tp->snd_wl1, tp->snd_wl2, tp->snd_wnd); +#endif /* TCPDEBUG */ +} diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp_input.c b/src/add-ons/kernel/network/protocols/tcp/tcp_input.c new file mode 100644 index 0000000000..6bfea49f6f --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/tcp_input.c @@ -0,0 +1,1728 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)COPYRIGHT 1.1 (NRL) 17 January 1995 + * + * NRL grants permission for redistribution and use in source and binary + * forms, with or without modification, of the software and documentation + * created at NRL provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgements: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * This product includes software developed at the Information + * Technology Division, US Naval Research Laboratory. + * 4. Neither the name of the NRL nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THE SOFTWARE PROVIDED BY NRL IS PROVIDED BY NRL AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NRL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of the US Naval + * Research Laboratory (NRL). + */ + +#ifndef _KERNEL_ +#include +#endif + +#include +#include + +#include "sys/protosw.h" +#include "sys/socket.h" +#include "sys/socketvar.h" + +#include "net/if.h" +#include "net/route.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core_module.h" +#include "core_funcs.h" +#include "ipv4/ipv4_module.h" + +#ifdef _KERNEL_ +#include +#endif + +extern struct core_module_info *core; +extern struct ipv4_module_info *ipm; + +int tcprexmtthresh = 3; +struct tcpiphdr tcp_saveti; +int tcptv_keep_init = TCPTV_KEEP_INIT; + +extern struct inpcb *tcp_last_inpcb; +extern uint32 sb_max; + +#define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) +#define TCP_PAWS_IDLE (24 * 24 * 60 * 60 * PR_SLOWHZ) + +/* for modulo comparisons of timestamps */ +#define TSTMP_LT(a,b) ((int)((a)-(b)) < 0) +#define TSTMP_GEQ(a,b) ((int)((a)-(b)) >= 0) + +#define TCP_REASS(tp, ti, m, so, flags) { \ + if ((ti)->ti_seq == (tp)->rcv_nxt && \ + (tp)->seg_next == (struct tcpiphdr*)(tp) && \ + (tp)->t_state == TCPS_ESTABLISHED) { \ + \ + (tp)->t_flags |= TF_DELACK; \ + (tp)->rcv_nxt += (ti)->ti_len; \ + (flags) = (ti)->ti_flags & TH_FIN; \ + tcpstat.tcps_rcvpack++; \ + tcpstat.tcps_rcvbyte += (ti)->ti_len; \ + sbappend(&(so)->so_rcv, (m)); \ + sorwakeup(so); \ + } else { \ + (flags) = tcp_reass((tp), (ti), (m)); \ + (tp)->t_flags |= TF_ACKNOW; \ + }\ +} + +/* + * Insert segment ti into reassembly queue of tcp with + * control block tp. Return TH_FIN if reassembly now includes + * a segment with FIN. The macro form does the common case inline + * (segment is the next to be received on an established connection, + * and the queue is empty), avoiding linkage into and removal + * from the queue and repetition of various conversions. + * Set DELACK for segments received in order, but ack immediately + * when segments are out of order (so fast retransmit can work). + */ + +int tcp_reass(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m) +{ + struct tcpiphdr *q; + struct socket *so = tp->t_inpcb->inp_socket; + int flags; + + /* + * Call with ti==NULL after become established to + * force pre-ESTABLISHED data up to user socket. + */ + if (ti == NULL) + goto present; + + /* + * Find a segment which begins after this one does. + */ + for (q = tp->seg_next; q != (struct tcpiphdr*)tp; q = (struct tcpiphdr*)q->ti_next) + if (SEQ_GT(q->ti_seq, ti->ti_seq)) + break; + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if ((struct tcpiphdr *)q->ti_prev != (struct tcpiphdr*)tp) { + int i; + q = (struct tcpiphdr*)q->ti_prev; + /* conversion to int (in i) handles seq wraparound */ + i = (q->ti_seq + q->ti_len) - ti->ti_seq; + if (i > 0) { + if (i >= ti->ti_len) { + tcpstat.tcps_rcvduppack++; + tcpstat.tcps_rcvdupbyte += ti->ti_len; + //m_freem(m); + return (0); + } + m_adj(m, i); + ti->ti_len -= i; + ti->ti_seq += i; + } + q = (struct tcpiphdr*)(q->ti_next); + } + tcpstat.tcps_rcvoopack++; + tcpstat.tcps_rcvoobyte += ti->ti_len; + REASS_MBUF(ti) = m; + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (q != (struct tcpiphdr*)tp) { + int i = (ti->ti_seq + ti->ti_len) - q->ti_seq; + + if (i <= 0) + break; + if (i < q->ti_len) { + q->ti_seq += i; + q->ti_len -= i; + m_adj(REASS_MBUF(q), i); + break; + } + q = (struct tcpiphdr*)q->ti_next; + m = REASS_MBUF((struct tcpiphdr*)q->ti_prev); + remque(q->ti_prev); + //m_freem(m); + } + + insque(ti, q->ti_prev); + +present: + /* + * Present data to user, advancing rcv_nxt through + * completed sequence space. + */ + if (TCPS_HAVERCVDSYN(tp->t_state) == 0) { + return 0; + } + ti = tp->seg_next; + if (ti == (struct tcpiphdr*)tp || ti->ti_seq != tp->rcv_nxt) { + return 0; + } + if (tp->t_state == TCPS_SYN_RECEIVED && ti->ti_len) { + return 0; + } + do { + tp->rcv_nxt += ti->ti_len; + flags = ti->ti_flags & TH_FIN; + remque(ti); + + m = REASS_MBUF(ti); + ti = (struct tcpiphdr*) ti->ti_next; + /* XXX - Change + * BSD sockets would call m_freem(m) if this was true, but if we + * do that we have trouble... + * So, reverse the logic and only append data if we can. + */ + if ((so->so_state & SS_CANTRCVMORE) == 0) + sbappend(&so->so_rcv, m); + } while (ti != (struct tcpiphdr*)tp && ti->ti_seq == tp->rcv_nxt); + sorwakeup(so); + return (flags); +} + +#ifdef TCP_DEBUG +static void show_ti(struct tcpiphdr *ti) +{ + printf("TCP/IP Header:\n"); + printf(" : src port : %d\n", ntohs(ti->ti_sport)); + printf(" : dst port : %d\n", ntohs(ti->ti_dport)); + printf(" : seq num : %lu\n", ti->ti_seq); + printf(" : ack num : %lu\n", ti->ti_ack); + printf(" : hdr len : %d\n", ti->ti_len); + printf(" : flags : "); + if (ti->ti_flags & TH_SYN) + printf(" SYN "); + if (ti->ti_flags & TH_ACK) + printf(" ACK "); + if (ti->ti_flags & TH_RST) + printf(" RST "); + if (ti->ti_flags & TH_PUSH) + printf(" PSH "); + if (ti->ti_flags & TH_URG) + printf(" URG "); + if (ti->ti_flags & TH_FIN) + printf(" FIN "); + printf("\n"); + printf(" : win : %d\n", ti->ti_win); + +} + +static void show_tp(struct tcpcb *tp) +{ + printf("TCP Control Block:\n"); + printf(" : snd_una : %lu\n", tp->snd_una); + printf(" : snd_nxt : %lu\n", tp->snd_nxt); + printf(" : snd_up : %lu\n", tp->snd_up); + printf(" : snd_wl1 : %lu\n", tp->snd_wl1); + printf(" : snd_wl2 : %lu\n", tp->snd_wl2); + printf(" : iss : %lu\n", tp->iss); + printf(" : rcv_wnd : %lu\n", tp->rcv_wnd); + printf(" : rcv_nxt : %lu\n", tp->rcv_nxt); + printf(" : rcv_up : %lu\n", tp->rcv_up); + printf(" : last_ack_sent : %lu\n", tp->last_ack_sent); + printf(" : flags : "); + if (tp->t_flags & TF_ACKNOW) + printf(" ACKNOW "); + if (tp->t_flags & TF_DELACK) + printf(" DELACK "); + if (tp->t_flags & TF_SENTFIN) + printf(" SENTFIN "); + if (tp->t_flags & TF_NODELAY) + printf(" NODELAY "); + if (tp->t_flags & TF_NOOPT) + printf(" NOOPT "); + printf("\n"); +} +#endif /* TCP_DEBUG */ + +/* + * TCP input routine, follows pages 65-76 of the + * protocol specification dated September, 1981 very closely. + */ +void tcp_input(struct mbuf *m, int iphlen) +{ + struct inpcb *inp; + caddr_t optp = NULL; + int optlen = 0; + int len = 0; + uint16 tlen = 0; + int off = 0; + struct tcpcb *tp = NULL; + int tiflags = 0; + struct socket *so = NULL; + int todrop, acked, ourfinisacked, needoutput = 0; + short ostate = 0; + struct in_addr laddr; + int dropsocket = 0; + int iss = 0; + u_long tiwin; + uint32 ts_val, ts_ecr; + int ts_present = 0; + struct tcpiphdr *ti; + + tcpstat.tcps_rcvtotal++; + /* Get the IP and TCP header together (the tcpiphdr struct) + * together in the first mbuf. + * NB IP layer should leave IP header in first mbuf + */ + ti = mtod(m, struct tcpiphdr*); + + if (iphlen > sizeof(struct ip)) + ipm->ip_stripoptions(m, NULL); + + if (m->m_len < sizeof(struct tcpiphdr)) { + if ((m = m_pullup(m, sizeof(struct tcpiphdr))) == NULL) { + tcpstat.tcps_rcvshort++; + return; + } + ti = mtod(m, struct tcpiphdr*); + } + + /* + * Checksum extended TCP header and data. + */ + tlen = ((struct ip *) ti)->ip_len; + len = sizeof(struct ip) + tlen; + + ti->ti_next = ti->ti_prev = NULL; + ti->ti_x1 = 0; + ti->ti_len = htons(tlen); + + if ((ti->ti_sum = in_cksum(m, len, 0))) { + tcpstat.tcps_rcvbadsum++; + printf("invalid checksum %d over %d bytes!\n", ti->ti_sum, len); + goto drop; + } + + off = ti->ti_off << 2; + if (off < sizeof(struct tcphdr) || off > tlen) { + tcpstat.tcps_rcvbadoff++; + printf("tcp_input: bad len\n"); + goto drop; + } + + tlen -=off; + ti->ti_len = tlen; + + if (off > sizeof(struct tcphdr)) { + if (m->m_len < sizeof(struct ip) + off) { + if ((m = m_pullup(m, sizeof(struct ip) + off)) == NULL) { + tcpstat.tcps_rcvshort++; + return; + } + ti = mtod(m, struct tcpiphdr*); + } + optlen = off - sizeof(struct tcphdr); + optp = mtod(m, caddr_t) + sizeof(struct tcphdr); + + /* + * Do quick retrieval of timestamp options ("options + * prediction?"). If timestamp is the only option and it's + * formatted as recommended in RFC 1323 appendix A, we + * quickly get the values now and not bother calling + * tcp_dooptions(), etc. + */ + if ((optlen == TCPOLEN_TSTAMP_APPA || + (optlen > TCPOLEN_TSTAMP_APPA && + optp[TCPOLEN_TSTAMP_APPA] == TCPOPT_EOL)) && + *(uint32 *)optp == htonl(TCPOPT_TSTAMP_HDR) && + (ti->ti_flags & TH_SYN) == 0) { + ts_present = 1; + ts_val = ntohl(*(uint32 *)(optp + 4)); + ts_ecr = ntohl(*(uint32 *)(optp + 8)); + optp = NULL; /* we've parsed the options */ + } + } + tiflags = ti->ti_flags; + + /* + * Convert TCP protocol specific fields to host format. + */ + ti->ti_seq = ntohl(ti->ti_seq); + ti->ti_ack = ntohl(ti->ti_ack); + ti->ti_win = ntohs(ti->ti_win); + ti->ti_urp = ntohs(ti->ti_urp); + + /* + * Locate pcb for segment. + */ +findpcb: + inp = tcp_last_inpcb; + if (!inp || inp->lport != ti->ti_dport || + inp->fport != ti->ti_sport || + inp->faddr.s_addr != ti->ti_src.s_addr || + inp->laddr.s_addr != ti->ti_dst.s_addr) { + inp = in_pcblookup(&tcb, ti->ti_src, ti->ti_sport, ti->ti_dst, + ti->ti_dport, INPLOOKUP_WILDCARD); + if (inp) + tcp_last_inpcb = inp; + ++tcpstat.tcps_pcbhashmiss; + } + if (inp == NULL) { +/* XXX - This is very common, but only commented out as it's useful at times + for debugging. + printf("tcp_input: dropwithreset: inp is NULL, line %d\n", __LINE__); + */ + goto dropwithreset; + } + + tp = intotcpcb(inp); + if (tp == NULL) { + printf("tcp_input: dropwithreset: tp is NULL, line %d\n", __LINE__); + goto dropwithreset; + } + + if (tp->t_state == TCPS_CLOSED) { + printf("tcp_input: state is closed\n"); + goto drop; + } + + /* Unscale the window into a 32-bit value. */ + if ((tiflags & TH_SYN) == 0) + tiwin = ti->ti_win << tp->snd_scale; + else + tiwin = ti->ti_win; + + so = inp->inp_socket; + if (so->so_options & (SO_DEBUG|SO_ACCEPTCONN)) { + if (so->so_options & SO_DEBUG) { + ostate = tp->t_state; + tcp_saveti = *ti; + } + if (so->so_options & SO_ACCEPTCONN) { + so = sonewconn(so, 0); + if (so == NULL) { + goto drop; + } + /* + * This is ugly, but .... + * + * Mark socket as temporary until we're + * committed to keeping it. The code at + * ``drop'' and ``dropwithreset'' check the + * flag dropsocket to see if the temporary + * socket created here should be discarded. + * We mark the socket as discardable until + * we're committed to it below in TCPS_LISTEN. + */ + dropsocket++; + inp = (struct inpcb *)so->so_pcb; + inp->laddr = ti->ti_dst; + inp->lport = ti->ti_dport; + inp->inp_options = ipm->ip_srcroute(); + tp = intotcpcb(inp); + tp->t_state = TCPS_LISTEN; + + tp->request_r_scale = 0; + /* Compute proper scaling value from buffer space */ + while (tp->request_r_scale < TCP_MAX_WINSHIFT && + TCP_MAXWIN << tp->request_r_scale < so->so_rcv.sb_hiwat) + tp->request_r_scale++; + } + } + + /* + * Segment received on connection. + * Reset idle time and keep-alive timer. + */ + tp->t_idle = 0; + tp->t_timer[TCPT_KEEP] = tcp_keepidle; + + /* + * Process options if not in LISTEN state, + * else do it below (after getting remote address). + */ + if (optp && tp->t_state != TCPS_LISTEN) + tcp_dooptions(tp, (unsigned char *)optp, optlen, ti, &ts_present, &ts_val, &ts_ecr); + + /* + * Header prediction: check for the two common cases + * of a uni-directional data xfer. If the packet has + * no control flags, is in-sequence, the window didn't + * change and we're not retransmitting, it's a + * candidate. If the length is zero and the ack moved + * forward, we're the sender side of the xfer. Just + * free the data acked & wake any higher level process + * that was blocked waiting for space. If the length + * is non-zero and the ack didn't move, we're the + * receiver side. If we're getting packets in-order + * (the reassembly queue is empty), add the data to + * the socket buffer and note that we need a delayed ack. + */ + if (tp->t_state == TCPS_ESTABLISHED && + (tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) == TH_ACK && + (!ts_present || TSTMP_GEQ(ts_val, tp->ts_recent)) && + ti->ti_seq == tp->rcv_nxt && + tiwin && tiwin == tp->snd_wnd && + tp->snd_nxt == tp->snd_max) { + + /* + * If last ACK falls within this segment's sequence numbers, + * record the timestamp. + * Fix from Braden, see Stevens p. 870 + */ + if (ts_present && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent)) { + tp->ts_recent_age = tcp_now; + tp->ts_recent = ts_val; + } + + if (ti->ti_len == 0) { + if (SEQ_GT(ti->ti_ack, tp->snd_una) && + SEQ_LEQ(ti->ti_ack, tp->snd_max) && + tp->snd_cwnd >= tp->snd_wnd) { + /* + * this is a pure ack for outstanding data. + */ + ++tcpstat.tcps_predack; + if (ts_present) + tcp_xmit_timer(tp, tcp_now - ts_ecr + 1); + else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp, tp->t_rtt); + acked = ti->ti_ack - tp->snd_una; + tcpstat.tcps_rcvackpack++; + tcpstat.tcps_rcvackbyte += acked; + sbdrop(&so->so_snd, acked); + tp->snd_una = ti->ti_ack; + m_freem(m); + + /* + * If all outstanding data are acked, stop + * retransmit timer, otherwise restart timer + * using current (possibly backed-off) value. + * If process is waiting for space, + * wakeup/selwakeup/signal. If data + * are ready to send, let tcp_output + * decide between more output or persist. + */ + if (tp->snd_una == tp->snd_max) + tp->t_timer[TCPT_REXMT] = 0; + else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + + if (so->so_snd.sb_flags & SB_NOTIFY) + sowwakeup(so); + if (so->so_snd.sb_cc) { + (void) tcp_output(tp); + } + return; + } + } else if (ti->ti_ack == tp->snd_una && + tp->seg_next == (struct tcpiphdr*)tp && + ti->ti_len <= sbspace(&so->so_rcv)) { + /* + * This is a pure, in-sequence data packet + * with nothing on the reassembly queue and + * we have enough buffer space to take it. + */ + ++tcpstat.tcps_preddat; + tp->rcv_nxt += ti->ti_len; + tcpstat.tcps_rcvpack++; + tcpstat.tcps_rcvbyte += ti->ti_len; + /* + * Drop TCP, IP headers and TCP options then add data + * to socket buffer. + */ + m->m_data += sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + sbappend(&so->so_rcv, m); + sorwakeup(so); + tp->t_flags |= TF_DELACK; + return; + } + } + + m->m_data += sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + + /* + * Calculate amount of space in receive window, + * and then do TCP input processing. + * Receive window is amount of space in rcv queue, + * but not less than advertised window. + */ + { + int win = sbspace(&so->so_rcv); + if (win < 0) + win = 0; + tp->rcv_wnd = max(win, (int)(tp->rcv_adv - tp->rcv_nxt)); + } + + switch (tp->t_state) { + + /* + * If the state is LISTEN then ignore segment if it contains an RST. + * If the segment contains an ACK then it is bad and send a RST. + * If it does not contain a SYN then it is not interesting; drop it. + * If it is from this socket, drop it, it must be forged. + * Don't bother responding if the destination was a broadcast. + * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial + * tp->iss, and send a segment: + * + * Also initialize tp->snd_nxt to tp->iss+1 and tp->snd_una to tp->iss. + * Fill in remote peer address fields if not previously specified. + * Enter SYN_RECEIVED state, and process any other fields of this + * segment in this state. + */ + case TCPS_LISTEN: { + struct mbuf *am; + struct sockaddr_in *sin; + + if (tiflags & TH_RST) { + printf("tcp_input: TH_RST\n"); + goto drop; + } + if (tiflags & TH_ACK) { + printf("tcp_input: dropwithreset: line %d\n", __LINE__); + goto dropwithreset; + } + if ((tiflags & TH_SYN) == 0) { + printf("tcp_input: TH_SYN\n"); + goto drop; + } + /* + * RFC1122 4.2.3.10, p. 104: discard bcast/mcast SYN + * in_broadcast() should never return true on a received + * packet with M_BCAST not set. + */ + if (m->m_flags & (M_BCAST | M_MCAST) || + IN_MULTICAST(ti->ti_dst.s_addr)) { + printf("tcp_input: multicast! %d\n", __LINE__); + goto drop; + } + + am = m_get(MT_SONAME); + if (!am) { + printf("failed to get MT_SONAME\n"); + goto drop; + } + am->m_len = sizeof(struct sockaddr_in); + sin = mtod(am, struct sockaddr_in *); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_addr = ti->ti_src; + sin->sin_port = ti->ti_sport; + memset((caddr_t)sin->sin_zero, 0, sizeof(sin->sin_zero)); + laddr = inp->laddr; + if (inp->laddr.s_addr == INADDR_ANY) + inp->laddr = ti->ti_dst; + + if (in_pcbconnect(inp, am)) { + inp->laddr = laddr; + (void) m_free(am); + printf("tcp_input: in_pcbconnect failed\n"); + goto drop; + } + (void) m_free(am); + tp->t_template = tcp_template(tp); + if (tp->t_template == NULL) { + printf("template = NULL\n"); + tp = tcp_drop(tp, ENOBUFS); + dropsocket = 0; /* socket is already gone */ + goto drop; + } + if (optp) + tcp_dooptions(tp, (unsigned char *)optp, optlen, ti, + &ts_present, &ts_val, &ts_ecr); + + if (iss) + tp->iss = iss; + else + tp->iss = tcp_iss; + tcp_iss += TCP_ISSINCR / 2; + tp->irs = ti->ti_seq; + tcp_sendseqinit(tp); + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + tp->t_state = TCPS_SYN_RECEIVED; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + dropsocket = 0; /* committed to socket */ + tcpstat.tcps_accepts++; + goto trimthenstep6; + } + + /* + * If the state is SYN_RECEIVED: + * if seg contains SYN/ACK, send an RST. + * if seg contains an ACK, but not for our SYN/ACK, send an RST + */ + + case TCPS_SYN_RECEIVED: + if (tiflags & TH_ACK) { + if (tiflags & TH_SYN) { + tcpstat.tcps_badsyn++; + printf("SYN + ACK in a reply\n"); + printf("tcp_input: dropwithreset: line %d\n", __LINE__); + goto dropwithreset; + } + if (SEQ_LEQ(ti->ti_ack, tp->snd_una) || + SEQ_GT(ti->ti_ack, tp->snd_max)) { + printf("SEQ outside of boundaries...\n"); + printf("tcp_input: dropwithreset: line %d\n", __LINE__); + goto dropwithreset; + } + } + break; + + /* + * If the state is SYN_SENT: + * if seg contains an ACK, but not for our SYN, drop the input. + * if seg contains a RST, then drop the connection. + * if seg does not contain SYN, then drop it. + * Otherwise this is an acceptable SYN segment + * initialize tp->rcv_nxt and tp->irs + * if seg contains ack then advance tp->snd_una + * if SYN has been acked change to ESTABLISHED else SYN_RCVD state + * arrange for segment to be acked (eventually) + * continue processing rest of data/controls, beginning with URG + */ + case TCPS_SYN_SENT: + if ((tiflags & TH_ACK) && + (SEQ_LEQ(ti->ti_ack, tp->iss) || + SEQ_GT(ti->ti_ack, tp->snd_max))) { + printf("SYN_SENT but ACK was not for our request!\n"); + printf("tcp_input: dropwithreset: line %d\n", __LINE__); + goto dropwithreset; + } + if (tiflags & TH_RST) { + if (tiflags & TH_ACK) + tp = tcp_drop(tp, ECONNREFUSED); + printf("tcp_input: %d\n", __LINE__); + goto drop; + } + if ((tiflags & TH_SYN) == 0) { + printf("tcp_input: %d\n", __LINE__); + goto drop; + } + if (tiflags & TH_ACK) { + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + } + tp->t_timer[TCPT_REXMT] = 0; + tp->irs = ti->ti_seq; + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + if (tiflags & TH_ACK && SEQ_GT(tp->snd_una, tp->iss)) { + tcpstat.tcps_connects++; + soisconnected(so); + tp->t_state = TCPS_ESTABLISHED; + /* Do window scaling on this connection? */ + if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == + (TF_RCVD_SCALE|TF_REQ_SCALE)) { + tp->snd_scale = tp->requested_s_scale; + tp->rcv_scale = tp->request_r_scale; + } + (void) tcp_reass(tp, NULL, NULL); + /* + * if we didn't have to retransmit the SYN, + * use its rtt as our initial srtt & rtt var. + */ + if (tp->t_rtt) + tcp_xmit_timer(tp, tp->t_rtt); + } else + tp->t_state = TCPS_SYN_RECEIVED; + +trimthenstep6: + /* + * Advance ti->ti_seq to correspond to first data byte. + * If data, trim to stay within window, + * dropping FIN if necessary. + */ + ti->ti_seq++; + if (ti->ti_len > tp->rcv_wnd) { + todrop = ti->ti_len - tp->rcv_wnd; + m_adj(m, -todrop); + ti->ti_len = tp->rcv_wnd; + tiflags &= ~TH_FIN; + tcpstat.tcps_rcvpackafterwin++; + tcpstat.tcps_rcvbyteafterwin += todrop; + } + tp->snd_wl1 = ti->ti_seq - 1; + tp->rcv_up = ti->ti_seq; + goto step6; + } + + /* + * States other than LISTEN or SYN_SENT. + * First check timestamp, if present. + * Then check that at least some bytes of segment are within + * receive window. If segment begins before rcv_nxt, + * drop leading data (and SYN); if nothing left, just ack. + * + * RFC 1323 PAWS: If we have a timestamp reply on this segment + * and it's less than ts_recent, drop it. + * PAWS = Protection Against Wrapped Sequence Numbers + */ + if (ts_present && (tiflags & TH_RST) == 0 && tp->ts_recent && + TSTMP_LT(ts_val, tp->ts_recent)) { + + /* Check to see if ts_recent is over 24 days old. */ + if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) { + /* + * Invalidate ts_recent. If this segment updates + * ts_recent, the age will be reset later and ts_recent + * will get a valid value. If it does not, setting + * ts_recent to zero will at least satisfy the + * requirement that zero be placed in the timestamp + * echo reply when ts_recent isn't valid. The + * age isn't reset until we get a valid ts_recent + * because we don't want out-of-order segments to be + * dropped when ts_recent is old. + */ + tp->ts_recent = 0; + } else { + tcpstat.tcps_rcvduppack++; + tcpstat.tcps_rcvdupbyte += ti->ti_len; + tcpstat.tcps_pawsdrop++; + goto dropafterack; + } + } + + todrop = tp->rcv_nxt - ti->ti_seq; + if (todrop > 0) { + if (tiflags & TH_SYN) { + tiflags &= ~TH_SYN; + ti->ti_seq++; + if (ti->ti_urp > 1) + ti->ti_urp--; + else + tiflags &= ~TH_URG; + todrop--; + } + if ((todrop >= ti->ti_len) || (todrop == ti->ti_len && + (tiflags & TH_FIN) == 0)) { + /* + * Any valid FIN must be to the left of the + * window. At this point, FIN must be a + * duplicate or out-of-sequence, so drop it. + */ + tiflags &= ~TH_FIN; + /* + * Send ACK to resynchronize, and drop any data, + * but keep on processing for RST or ACK. + */ + tp->t_flags |= TF_ACKNOW; + todrop = ti->ti_len; + tcpstat.tcps_rcvduppack++; + tcpstat.tcps_rcvdupbyte += todrop; + } else { + tcpstat.tcps_rcvpartduppack++; + tcpstat.tcps_rcvpartdupbyte += todrop; + } + m_adj(m, todrop); + ti->ti_seq += todrop; + ti->ti_len -= todrop; + if (ti->ti_urp > todrop) + ti->ti_urp -= todrop; + else { + tiflags &= ~TH_URG; + ti->ti_urp = 0; + } + } + + /* + * If new data are received on a connection after the + * user processes are gone, then RST the other end. + */ +/* XXX - how do we check this!!! */ + if ((so->so_state & SS_NOFDREF) && + tp->t_state > TCPS_CLOSE_WAIT && ti->ti_len) { + tp = tcp_close(tp); + tcpstat.tcps_rcvafterclose++; + printf("tcp_input: dropwithreset: line %d\n", __LINE__); + goto dropwithreset; + } + + /* + * If segment ends after window, drop trailing data + * (and PUSH and FIN); if nothing left, just ACK. + */ + todrop = (ti->ti_seq + ti->ti_len) - (tp->rcv_nxt + tp->rcv_wnd); + if (todrop > 0) { + tcpstat.tcps_rcvpackafterwin++; + if (todrop >= ti->ti_len) { + tcpstat.tcps_rcvbyteafterwin += ti->ti_len; + /* + * If a new connection request is received + * while in TIME_WAIT, drop the old connection + * and start over if the sequence numbers + * are above the previous ones. + */ + if (tiflags & TH_SYN && + tp->t_state == TCPS_TIME_WAIT && + SEQ_GT(ti->ti_seq, tp->rcv_nxt)) { + iss = tp->rcv_nxt + TCP_ISSINCR; + tp = tcp_close(tp); + goto findpcb; + } + /* + * If window is closed can only take segments at + * window edge, and have to drop data and PUSH from + * incoming segments. Continue processing, but + * remember to ack. Otherwise, drop segment + * and ack. + */ + if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) { + tp->t_flags |= TF_ACKNOW; + tcpstat.tcps_rcvwinprobe++; + } else + goto dropafterack; + } else + tcpstat.tcps_rcvbyteafterwin += todrop; + m_adj(m, -todrop); + ti->ti_len -= todrop; + tiflags &= ~(TH_PUSH|TH_FIN); + } + + /* + * If last ACK falls within this segment's sequence numbers, + * record its timestamp. + * Fix from Braden, see Stevens p. 870 + */ + if (ts_present && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent) && + SEQ_LT(tp->last_ack_sent, ti->ti_seq + ti->ti_len + ((tiflags & (TH_SYN | TH_FIN)) != 0))) { + tp->ts_recent_age = tcp_now; + tp->ts_recent = ts_val; + } + + /* + * If the RST bit is set examine the state: + * SYN_RECEIVED STATE: + * If passive open, return to LISTEN state. + * If active open, inform user that connection was refused. + * ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES: + * Inform user that connection was reset, and close tcb. + * CLOSING, LAST_ACK, TIME_WAIT STATES + * Close the tcb. + */ + if (tiflags & TH_RST) { + switch (tp->t_state) { + case TCPS_SYN_RECEIVED: + so->so_error = ECONNREFUSED; + goto close; + + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + so->so_error = ECONNRESET; +close: + tp->t_state = TCPS_CLOSED; + tcpstat.tcps_drops++; + tp = tcp_close(tp); + goto drop; + + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + tp = tcp_close(tp); + goto drop; + } + } + + /* + * If a SYN is in the window, then this is an + * error and we send an RST and drop the connection. + */ + if (tiflags & TH_SYN) { + tp = tcp_drop(tp, ECONNRESET); + printf("tcp_input: dropwithreset: line %d\n", __LINE__); + goto dropwithreset; + } + + /* + * If the ACK bit is off we drop the segment and return. + */ + if ((tiflags & TH_ACK) == 0) { + printf("tcp_input: drop: no ack flag...\n"); + goto drop; + } + + /* + * Ack processing. + */ + switch (tp->t_state) { + + /* + * In SYN_RECEIVED state, the ack ACKs our SYN, so enter + * ESTABLISHED state and continue processing. + * The ACK was checked above. + */ + case TCPS_SYN_RECEIVED: + if (SEQ_GT(tp->snd_una, ti->ti_ack) || + SEQ_GT(ti->ti_ack, tp->snd_max)) { + printf("tcp_input: dropwithreset: line %d\n", __LINE__); + goto dropwithreset; + } + tcpstat.tcps_connects++; + soisconnected(so); + tp->t_state = TCPS_ESTABLISHED; + /* Do window scaling? */ + if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == + (TF_RCVD_SCALE|TF_REQ_SCALE)) { + tp->snd_scale = tp->requested_s_scale; + tp->rcv_scale = tp->request_r_scale; + } + (void) tcp_reass(tp, NULL, NULL); + tp->snd_wl1 = ti->ti_seq - 1; + /* fall into ... */ + + /* + * In ESTABLISHED state: drop duplicate ACKs; ACK out of range + * ACKs. If the ack is in the range + * tp->snd_una < th->th_ack <= tp->snd_max + * then advance tp->snd_una to th->th_ack and drop + * data from the retransmission queue. If this ACK reflects + * more up to date window information we update our window information. + */ + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) { + if (ti->ti_len == 0 && tiwin == tp->snd_wnd) { + tcpstat.tcps_rcvdupack++; + /* + * If we have outstanding data (other than + * a window probe), this is a completely + * duplicate ack (ie, window info didn't + * change), the ack is the biggest we've + * seen and we've seen exactly our rexmt + * threshhold of them, assume a packet + * has been dropped and retransmit it. + * Kludge snd_nxt & the congestion + * window so we send only this one + * packet. + * + * We know we're losing at the current + * window size so do congestion avoidance + * (set ssthresh to half the current window + * and pull our congestion window back to + * the new ssthresh). + * + * Dup acks mean that packets have left the + * network (they're now cached at the receiver) + * so bump cwnd by the amount in the receiver + * to keep a constant cwnd packets in the + * network. + */ + if (tp->t_timer[TCPT_REXMT] == 0 || ti->ti_ack != tp->snd_una) + tp->t_dupacks = 0; + else if (++tp->t_dupacks == tcprexmtthresh) { + tcp_seq onxt = tp->snd_nxt; + uint32 win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; + + if (win < 2) + win = 2; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_timer[TCPT_REXMT] = 0; + tp->t_rtt = 0; + tp->snd_nxt = ti->ti_ack; + tp->snd_cwnd = tp->t_maxseg; + (void) tcp_output(tp); + tp->snd_cwnd = tp->snd_ssthresh + tp->t_maxseg * tp->t_dupacks; + if (SEQ_GT(onxt, tp->snd_nxt)) + tp->snd_nxt = onxt; + goto drop; + } else if (tp->t_dupacks > tcprexmtthresh) { + tp->snd_cwnd += tp->t_maxseg; + (void) tcp_output(tp); + goto drop; + } + } else + tp->t_dupacks = 0; + break; + } + /* + * If the congestion window was inflated to account + * for the other side's cached packets, retract it. + */ + if (tp->t_dupacks >= tcprexmtthresh && + tp->snd_cwnd > tp->snd_ssthresh) + tp->snd_cwnd = tp->snd_ssthresh; + tp->t_dupacks = 0; + + if (SEQ_GT(ti->ti_ack, tp->snd_max)) { + tcpstat.tcps_rcvacktoomuch++; + goto dropafterack; + } + acked = ti->ti_ack - tp->snd_una; + tcpstat.tcps_rcvackpack++; + tcpstat.tcps_rcvackbyte += acked; + + /* + * If we have a timestamp reply, update smoothed + * round trip time. If no timestamp is present but + * transmit timer is running and timed sequence + * number was acked, update smoothed round trip time. + * Since we now have an rtt measurement, cancel the + * timer backoff (cf., Phil Karn's retransmit alg.). + * Recompute the initial retransmit timer. + */ + if (ts_present) + tcp_xmit_timer(tp, tcp_now - ts_ecr + 1); + else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp,tp->t_rtt); + + /* + * If all outstanding data is acked, stop retransmit + * timer and remember to restart (more output or persist). + * If there is more data to be acked, restart retransmit + * timer, using current (possibly backed-off) value. + */ + if (ti->ti_ack == tp->snd_max) { + tp->t_timer[TCPT_REXMT] = 0; + needoutput = 1; + } else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + /* + * When new data is acked, open the congestion window. + * If the window gives us less than ssthresh packets + * in flight, open exponentially (maxseg per packet). + * Otherwise open linearly: maxseg per window + * (maxseg^2 / cwnd per packet). + */ + { + uint cw = tp->snd_cwnd; + uint incr = tp->t_maxseg; + + if (cw > tp->snd_ssthresh) + incr = incr * incr / cw + incr / 8; + tp->snd_cwnd = min(cw + incr, TCP_MAXWIN << tp->snd_scale); + } + if (acked > so->so_snd.sb_cc) { + tp->snd_wnd -= so->so_snd.sb_cc; + sbdrop(&so->so_snd, (int)so->so_snd.sb_cc); + ourfinisacked = 1; + } else { + sbdrop(&so->so_snd, acked); + tp->snd_wnd -= acked; + ourfinisacked = 0; + } + if (so->so_snd.sb_flags & SB_NOTIFY) + sowwakeup(so); + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + + switch (tp->t_state) { + + /* + * In FIN_WAIT_1 STATE in addition to the processing + * for the ESTABLISHED state if our FIN is now acknowledged + * then enter FIN_WAIT_2. + */ + case TCPS_FIN_WAIT_1: + if (ourfinisacked) { + /* + * If we can't receive any more + * data, then closing user can proceed. + * Starting the timer is contrary to the + * specification, but if we don't get a FIN + * we'll hang forever. + */ + if (so->so_state & SS_CANTRCVMORE) { + soisdisconnected(so); + tp->t_timer[TCPT_2MSL] = tcp_maxidle; + } + tp->t_state = TCPS_FIN_WAIT_2; + } + break; + + /* + * In CLOSING STATE in addition to the processing for + * the ESTABLISHED state if the ACK acknowledges our FIN + * then enter the TIME-WAIT state, otherwise ignore + * the segment. + */ + case TCPS_CLOSING: + if (ourfinisacked) { + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + soisdisconnected(so); + } + break; + + /* + * In LAST_ACK, we may still be waiting for data to drain + * and/or to be acked, as well as for the ack of our FIN. + * If our FIN is now acknowledged, delete the TCB, + * enter the closed state and return. + */ + case TCPS_LAST_ACK: + if (ourfinisacked) { + tp = tcp_close(tp); + goto drop; + } + break; + + /* + * In TIME_WAIT state the only thing that should arrive + * is a retransmission of the remote FIN. Acknowledge + * it and restart the finack timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + goto dropafterack; + } + } + +step6: + /* + * Update window information. + * Don't look at window if no ACK: TAC's send garbage on first SYN. + */ + if ((tiflags & TH_ACK) && + (SEQ_LT(tp->snd_wl1, ti->ti_seq) || tp->snd_wl1 == ti->ti_seq) && + (SEQ_LT(tp->snd_wl2, ti->ti_ack) || + (tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))) { + /* keep track of pure window updates */ + if (ti->ti_len == 0 && + tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd) + tcpstat.tcps_rcvwinupd++; + tp->snd_wnd = tiwin; + tp->snd_wl1 = ti->ti_seq; + tp->snd_wl2 = ti->ti_ack; + if (tp->snd_wnd > tp->max_sndwnd) + tp->max_sndwnd = tp->snd_wnd; + needoutput = 1; + } + /* + * Process segments with URG. + */ + if ((tiflags & TH_URG) && ti->ti_urp && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + /* + * This is a kludge, but if we receive and accept + * random urgent pointers, we'll crash in + * soreceive. It's hard to imagine someone + * actually wanting to send this much urgent data. + */ + if (ti->ti_urp + so->so_rcv.sb_cc > sb_max) { + ti->ti_urp = 0; /* XXX */ + tiflags &= ~TH_URG; /* XXX */ + goto dodata; /* XXX */ + } + /* + * If this segment advances the known urgent pointer, + * then mark the data stream. This should not happen + * in CLOSE_WAIT, CLOSING, LAST_ACK or TIME_WAIT STATES since + * a FIN has been received from the remote side. + * In these states we ignore the URG. + * + * According to RFC961 (Assigned Protocols), + * the urgent pointer points to the last octet + * of urgent data. We continue, however, + * to consider it to indicate the first octet + * of data past the urgent section as the original + * spec states (in one of two places). + */ + if (SEQ_GT(ti->ti_seq + ti->ti_urp, tp->rcv_up)) { + tp->rcv_up = ti->ti_seq + ti->ti_urp; + so->so_oobmark = so->so_rcv.sb_cc + + (tp->rcv_up - tp->rcv_nxt) - 1; + if (so->so_oobmark == 0) + so->so_state |= SS_RCVATMARK; + sohasoutofband(so); + tp->t_oobflags &= ~(TCPOOB_HAVEDATA | TCPOOB_HADDATA); + } + /* + * Remove out of band data so doesn't get presented to user. + * This can happen independent of advancing the URG pointer, + * but if two URG's are pending at once, some out-of-band + * data may creep in... ick. + */ + if (ti->ti_urp <= ti->ti_len +#ifdef SO_OOBINLINE + && (so->so_options & SO_OOBINLINE) == 0 +#endif + ) + tcp_pulloutofband(so, ti, m); + } else { + /* + * If no out of band data is expected, + * pull receive urgent pointer along + * with the receive window. + */ + if (SEQ_GT(tp->rcv_nxt, tp->rcv_up)) + tp->rcv_up = tp->rcv_nxt; + } +dodata: /* XXX */ + + /* + * Process the segment text, merging it into the TCP sequencing queue, + * and arranging for acknowledgment of receipt if necessary. + * This process logically involves adjusting tp->rcv_wnd as data + * is presented to the user (this happens in tcp_usrreq.c, + * case PRU_RCVD). If a FIN has already been received on this + * connection then we just ignore the text. + */ + + if ((ti->ti_len || (tiflags & TH_FIN)) && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + TCP_REASS(tp, ti, m, so, tiflags); + len = so->so_rcv.sb_hiwat - (tp->rcv_adv - tp->rcv_nxt); + } else { + m_freem(m); + tiflags &= ~TH_FIN; + } + + /* + * If FIN is received ACK the FIN and let the user know + * that the connection is closing. Ignore a FIN received before + * the connection is fully established. + */ + if ((tiflags & TH_FIN)) { + if (TCPS_HAVERCVDFIN(tp->t_state) == 0) { + socantrcvmore(so); + tp->t_flags |= TF_ACKNOW; + tp->rcv_nxt++; + } + switch (tp->t_state) { + /* + * In ESTABLISHED STATE enter the CLOSE_WAIT state. + */ + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + tp->t_state = TCPS_CLOSE_WAIT; + break; + + /* + * If still in FIN_WAIT_1 STATE FIN has not been acked so + * enter the CLOSING state. + */ + case TCPS_FIN_WAIT_1: + tp->t_state = TCPS_CLOSING; + break; + + /* + * In FIN_WAIT_2 state enter the TIME_WAIT state, + * starting the time-wait timer, turning off the other + * standard timers. + */ + case TCPS_FIN_WAIT_2: + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + soisdisconnected(so); + break; + + /* + * In TIME_WAIT state restart the 2 MSL time_wait timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + break; + } + } + + if (so->so_options & SO_DEBUG) { + tcp_trace(TA_INPUT, ostate, tp, (caddr_t) &tcp_saveti, + 0, tlen); + } + + /* + * Return any desired output. + */ + if (needoutput || (tp->t_flags & TF_ACKNOW)) { + (void) tcp_output(tp); + } + return; + +dropafterack: + /* + * Generate an ACK dropping incoming segment if it occupies + * sequence space, where the ACK reflects our state. + */ + if (tiflags & TH_RST) + goto drop; + m_freem(m); + tp->t_flags |= TF_ACKNOW; + (void) tcp_output(tp); + return; + +dropwithreset: + /* + * Generate a RST, dropping incoming segment. + * Make ACK acceptable to originator of segment. + * Don't bother to respond if destination was broadcast/multicast. + */ + if ((tiflags & TH_RST) || m->m_flags & (M_BCAST|M_MCAST) || + IN_MULTICAST(ti->ti_dst.s_addr)) + goto drop; + if (tiflags & TH_ACK) { + tcp_respond(tp, ti, m, 0, ti->ti_ack, TH_RST); + } else { + if (tiflags & TH_SYN) + ti->ti_len++; + tcp_respond(tp, ti, m, ti->ti_seq + ti->ti_len, 0, TH_RST|TH_ACK); + } + /* destroy temporarily created socket */ + if (dropsocket) + (*so->so_proto->pr_userreq)(so, PRU_ABORT, NULL, NULL, NULL); + return; + +drop: + /* + * Drop space held by incoming segment and return. + */ + if (tp && (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) { + tcp_trace(TA_DROP, ostate, tp, (caddr_t) &tcp_saveti, + 0, tlen); + } + + m_freem(m); + /* destroy temporarily created socket */ + + if (dropsocket) + (*so->so_proto->pr_userreq)(so, PRU_ABORT, NULL, NULL, NULL); + return; +} + +void tcp_dooptions(struct tcpcb *tp, u_char *cp, int cnt, struct tcpiphdr *ti, + int *ts_present, uint32 *ts_val, uint32 *ts_ecr) +{ + uint16 mss = 0; + int opt, optlen; + + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[0]; + if (opt == TCPOPT_EOL) + break; + if (opt == TCPOPT_NOP) + optlen = 1; + else { + optlen = cp[1]; + if (optlen <= 0) + break; + } + switch (opt) { + case TCPOPT_MAXSEG: + if (optlen != TCPOLEN_MAXSEG) + continue; + if (!(ti->ti_flags & TH_SYN)) + continue; + memcpy((char *) &mss, (char *) cp + 2,sizeof(mss)); + ntohs(mss); + break; + case TCPOPT_WINDOW: + if (optlen != TCPOLEN_WINDOW) + continue; + if (!(ti->ti_flags & TH_SYN)) + continue; + tp->t_flags |= TF_RCVD_SCALE; + tp->requested_s_scale = min(cp[2], TCP_MAX_WINSHIFT); + break; + case TCPOPT_TIMESTAMP: + if (optlen != TCPOLEN_TIMESTAMP) + continue; + *ts_present = 1; + memcpy((char *) ts_val, (char *)cp + 2, sizeof(*ts_val)); + ntohl(*ts_val); + memcpy((char*)ts_ecr, (char *)cp + 6, sizeof(*ts_ecr)); + ntohl(*ts_ecr); + + /* + * A timestamp received in a SYN makes + * it ok to send timestamp requests and replies. + */ + if (ti->ti_flags & TH_SYN) { + tp->t_flags |= TF_RCVD_TSTMP; + tp->ts_recent = *ts_val; + tp->ts_recent_age = tcp_now; + } + break; + default: + continue; + } + } +} + +/* + * Pull out of band byte out of a segment so + * it doesn't appear in the user's data queue. + * It is still reflected in the segment length for + * sequencing purposes. + */ +void tcp_pulloutofband(struct socket *so, struct tcpiphdr *ti, struct mbuf *m) +{ + int cnt = ti->ti_urp - 1; + + while (cnt >= 0) { + if (m->m_len > cnt) { + char *cp = mtod(m, caddr_t) + cnt; + struct tcpcb *tp = sototcpcb(so); + + tp->t_iobc = *cp; + tp->t_oobflags |= TCPOOB_HAVEDATA; + memcpy(cp, cp+1, (unsigned)(m->m_len - cnt - 1)); + m->m_len--; + return; + } + cnt -= m->m_len; + m = m->m_next; + if (m == NULL) + break; + } +} + +/* + * Collect new round-trip time estimate + * and update averages and current timeout. + */ +void tcp_xmit_timer(struct tcpcb *tp, int16 rtt) +{ + int16 delta; + int16 rttmin; + + tcpstat.tcps_rttupdated++; + --rtt; + if (tp->t_srtt != 0) { + /* + * srtt is stored as fixed point with 3 bits after the + * binary point (i.e., scaled by 8). The following magic + * is equivalent to the smoothing algorithm in rfc793 with + * an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed + * point). Adjust rtt to origin 0. + */ + delta = (rtt << 2) - (tp->t_srtt >> TCP_RTT_SHIFT); + if ((tp->t_srtt += delta) <= 0) + tp->t_srtt = 1; + /* + * We accumulate a smoothed rtt variance (actually, a + * smoothed mean difference), then set the retransmit + * timer to smoothed rtt + 4 times the smoothed variance. + * rttvar is stored as fixed point with 2 bits after the + * binary point (scaled by 4). The following is + * equivalent to rfc793 smoothing with an alpha of .75 + * (rttvar = rttvar*3/4 + |delta| / 4). This replaces + * rfc793's wired-in beta. + */ + if (delta < 0) + delta = -delta; + delta -= (tp->t_rttvar >> TCP_RTTVAR_SHIFT); + if ((tp->t_rttvar += delta) <= 0) + tp->t_rttvar = 1; + } else { + /* + * No rtt measurement yet - use the unsmoothed rtt. + * Set the variance to half the rtt (so our first + * retransmit happens at 3*rtt). + */ + tp->t_srtt = rtt << (TCP_RTT_SHIFT + 2); + tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT + 2 - 1); + } + tp->t_rtt = 0; + tp->t_rxtshift = 0; + + /* + * the retransmit should happen at rtt + 4 * rttvar. + * Because of the way we do the smoothing, srtt and rttvar + * will each average +1/2 tick of bias. When we compute + * the retransmit timer, we want 1/2 tick of rounding and + * 1 extra tick because of +-1/2 tick uncertainty in the + * firing of the timer. The bias will give us exactly the + * 1.5 tick we need. But, because the bias is + * statistical, we have to test that we don't drop below + * the minimum feasible timer (which is 2 ticks). + */ + if (tp->t_rttmin > rtt + 2) + rttmin = tp->t_rttmin; + else + rttmin = rtt + 2; + TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), rttmin, TCPTV_REXMTMAX); + + /* + * We received an ack for a packet that wasn't retransmitted; + * it is probably safe to discard any error indications we've + * received recently. This isn't quite right, but close enough + * for now (a route might have failed after we sent a segment, + * and the return path might not be symmetrical). + */ + tp->t_softerror = 0; +} + +/* + * Set connection variables based on the effective MSS. + * We are passed the TCPCB for the actual connection. If we + * are the server, we are called by the compressed state engine + * when the 3-way handshake is complete. If we are the client, + * we are called when we receive the SYN,ACK from the server. + * + * NOTE: The t_maxseg value must be initialized in the TCPCB + * before this routine is called! + */ +void tcp_mss_update(struct tcpcb *tp) +{ + int mss, rtt; + u_long bufsize; + struct rtentry *rt; + struct socket *so; + + so = tp->t_inpcb->inp_socket; + mss = tp->t_maxseg; + + rt = in_pcbrtentry(tp->t_inpcb); + + if (rt == NULL) + return; + +#ifdef RTV_MTU /* if route characteristics exist ... */ + /* + * While we're here, check if there's an initial rtt + * or rttvar. Convert from the route-table units + * to scaled multiples of the slow timeout timer. + */ + if (tp->t_srtt == 0 && (rtt = rt->rt_rmx.rmx_rtt)) { + /* + * XXX the lock bit for MTU indicates that the value + * is also a minimum value; this is subject to time. + */ + if (rt->rt_rmx.rmx_locks & RTV_RTT) + TCPT_RANGESET(tp->t_rttmin, + rtt / (RTM_RTTUNIT / PR_SLOWHZ), + TCPTV_MIN, TCPTV_REXMTMAX); + tp->t_srtt = rtt / (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE)); + if (rt->rt_rmx.rmx_rttvar) + tp->t_rttvar = rt->rt_rmx.rmx_rttvar / + (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE)); + else + /* default variation is +- 1 rtt */ + tp->t_rttvar = + tp->t_srtt * TCP_RTTVAR_SCALE / TCP_RTT_SCALE; + TCPT_RANGESET(/*(long)*/tp->t_rxtcur, + ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1, + tp->t_rttmin, TCPTV_REXMTMAX); + } +#endif + + /* + * If there's a pipesize, change the socket buffer + * to that size. Make the socket buffers an integral + * number of mss units; if the mss is larger than + * the socket buffer, decrease the mss. + */ +#ifdef RTV_SPIPE + if ((bufsize = rt->rt_rmx.rmx_sendpipe) == 0) +#endif + bufsize = so->so_snd.sb_hiwat; + if (bufsize < mss) { + mss = bufsize; + /* Update t_maxseg and t_maxopd */ + tcp_mss(tp, mss); + } else { + bufsize = roundup(bufsize, mss); + if (bufsize > sb_max) + bufsize = sb_max; + (void)sbreserve(&so->so_snd, bufsize); + } + +#ifdef RTV_RPIPE + if ((bufsize = rt->rt_rmx.rmx_recvpipe) == 0) +#endif + bufsize = so->so_rcv.sb_hiwat; + if (bufsize > mss) { + bufsize = roundup(bufsize, mss); + if (bufsize > sb_max) + bufsize = sb_max; + (void)sbreserve(&so->so_rcv, bufsize); +#ifdef RTV_RPIPE + if (rt->rt_rmx.rmx_recvpipe > 0) { + tp->request_r_scale = 0; + while (tp->request_r_scale < TCP_MAX_WINSHIFT && + TCP_MAXWIN << tp->request_r_scale < + so->so_rcv.sb_hiwat) + tp->request_r_scale++; + } +#endif + } + +#ifdef RTV_SSTHRESH + if (rt->rt_rmx.rmx_ssthresh) { + /* + * There's some sort of gateway or interface + * buffer limit on the path. Use this to set + * the slow start threshhold, but set the + * threshold to no less than 2*mss. + */ + tp->snd_ssthresh = max(2 * mss, rt->rt_rmx.rmx_ssthresh); + } +#endif /* RTV_MTU */ +} diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp_output.c b/src/add-ons/kernel/network/protocols/tcp/tcp_output.c new file mode 100644 index 0000000000..0299f17139 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/tcp_output.c @@ -0,0 +1,454 @@ +/* tcp_output.c */ + +#ifndef _KERNEL_ +#include +#endif + +#include "sys/socketvar.h" +#include "sys/protosw.h" +#include "netinet/in.h" +#include "netinet/in_pcb.h" +#include "netinet/ip_var.h" +#include "netinet/tcp.h" +#include "netinet/tcp_timer.h" +#include "netinet/tcp_var.h" +#include "netinet/tcpip.h" +#include "netinet/tcp_seq.h" +#define TCPOUTFLAGS +#include "netinet/tcp_fsm.h" +#include "netinet/tcp_debug.h" + +#include "core_module.h" +#include "core_funcs.h" +#include "ipv4/ipv4_module.h" + +#ifdef _KERNEL_ +#include + +#endif + +extern struct core_module_info *core; +extern struct ipv4_module_info *ipm; +extern struct pool_ctl *tcppool; + +#define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) +extern uint32 sb_max; /* defined in socketvar.h */ + +void tcp_setpersist(struct tcpcb *tp) +{ + int t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1; + + if (tp->t_timer[TCPT_REXMT]) { + printf("PANIC: tcp_output REXMT\n"); + return; + } + /* Start/reset the persistance timer */ + TCPT_RANGESET(tp->t_timer[TCPT_PERSIST], + t * tcp_backoff[tp->t_rxtshift], + TCPTV_PERSMIN, TCPTV_PERSMAX); + if (tp->t_rxtshift < TCP_MAXRXTSHIFT) + tp->t_rxtshift++; +} + +int tcp_mss(struct tcpcb *tp, uint offer) +{ + struct route *ro; + struct rtentry *rt; + struct ifnet *ifp; + int rtt, mss; + uint32 bufsize; + struct inpcb *inp; + struct socket *so; + + inp = tp->t_inpcb; + ro = &inp->inp_route; + + if ((rt = ro->ro_rt) == NULL) { + /* don't have a route, get one if we can */ + if (inp->faddr.s_addr != INADDR_ANY) { + memset(&ro->ro_dst, 0, sizeof(ro->ro_dst)); + ro->ro_dst.sa_family = AF_INET; + ro->ro_dst.sa_len = sizeof(ro->ro_dst); + ((struct sockaddr_in *)&ro->ro_dst)->sin_addr = inp->faddr; + rtalloc(ro); + } + if ((rt = ro->ro_rt) == NULL) + return tcp_mssdflt; + } + ifp=rt->rt_ifp; + so = inp->inp_socket; + + if (tp->t_srtt == 0 && (rtt = rt->rt_rmx.rmx_rtt)) { + if (rt->rt_rmx.rmx_locks & RTV_RTT) + tp->t_rttmin = rtt / (RTM_RTTUNIT / PR_SLOWHZ); + tp->t_srtt = rtt / (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE)); + + if (rt->rt_rmx.rmx_rttvar) + tp->t_rttvar = rt->rt_rmx.rmx_rttvar / + (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE)); + else + tp->t_rttvar = tp->t_srtt * TCP_RTTVAR_SCALE / TCP_RTT_SCALE; + + TCPT_RANGESET(tp->t_rxtcur, + ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1, + tp->t_rttmin, TCPTV_REXMTMAX); + } + + if (rt->rt_rmx.rmx_mtu) + mss = rt->rt_rmx.rmx_mtu - sizeof(struct tcpiphdr); + else { + mss = ifp->if_mtu - sizeof(struct tcpiphdr); +#if (MCLBYTES & (MCLBYTES - 1)) == 0 + if (mss > MCLBYTES) + mss &=~ (MCLBYTES - 1); +else + if (mss > MCLBYTES) + mss = mss / MCLBYTES * MCLBYTES; +#endif + if (!in_localaddr(inp->faddr)) + mss = min(mss, tcp_mssdflt); + } + + if (offer) + mss = min(mss, offer); + + mss = max(mss, 32); + if (mss < tp->t_maxseg || offer != 0) { + if ((bufsize = rt->rt_rmx.rmx_sendpipe) == 0) + bufsize = so->so_snd.sb_hiwat; + if (bufsize < mss) + mss = bufsize; + else { + bufsize = roundup(bufsize, mss); + if (bufsize > sb_max) + bufsize = sb_max; + sbreserve(&so->so_snd, bufsize); + } + tp->t_maxseg = mss; + + if ((bufsize = rt->rt_rmx.rmx_recvpipe) == 0) + bufsize = so->so_rcv.sb_hiwat; + if (bufsize > mss) { + bufsize = roundup(bufsize, mss); + if (bufsize > sb_max) + bufsize = sb_max; + sbreserve(&so->so_rcv, bufsize); + } + } + tp->snd_cwnd = mss; + if (rt->rt_rmx.rmx_ssthresh) { + tp->snd_ssthresh = max(2 * mss, rt->rt_rmx.rmx_ssthresh); + } + return mss; +} + +void tcp_quench(struct inpcb *inp, int error) +{ + struct tcpcb *tp = intotcpcb(inp); + + if(tp) + tp->snd_cwnd = tp->t_maxseg; +} + +int tcp_output(struct tcpcb *tp) +{ + struct socket *so = tp->t_inpcb->inp_socket; + int32 len, win; + int off, flags, error = 0; + struct mbuf *m; + struct tcpiphdr *ti; + u_char opt[MAX_TCPOPTLEN]; + uint optlen, hdrlen; + int idle, sendalot; + + idle = (tp->snd_max == tp->snd_una); + + if (idle && tp->t_idle >= tp->t_rxtcur) + /* Basically have we been idle for a while? + * If so, slow start to get ack "clock" running again + */ + tp->snd_cwnd = tp->t_maxseg; + +again: + sendalot = 0; + + off = tp->snd_nxt - tp->snd_una; + win = min(tp->snd_wnd, tp->snd_cwnd); + + flags = tcp_outflags[tp->t_state]; + /* if we're in a persist window of 0, send 1 byte + * Otherwise, if we have a small but nonzero window and + * the timer has expired, send what we can and go to + * transmit state + */ + if (tp->t_force) { + if (win == 0) { + /* If we have data to send, clear the FIN bit. + */ + if (off < so->so_snd.sb_cc) + flags &= ~TH_FIN; + win = 1; + } else { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + len = min(so->so_snd.sb_cc, win) - off; + if (len < 0) { + /* if FIN has been sent but not ack'd, + * but we haven't been asked to retransmit, len would be -1 + * Otherwise window shrank after we sent into it. If window + * shrank to 0, cancel pending transmit and pull snd_nxt + * back to (closed) window. + */ + len = 0; + if (win == 0) { + tp->t_timer[TCPT_REXMT] = 0; + tp->snd_nxt = tp->snd_una; + } + } + if (len > tp->t_maxseg) { + len = tp->t_maxseg; + sendalot = 1; + } + if (SEQ_LT(tp->snd_nxt + len, tp->snd_una + so->so_snd.sb_cc)) + flags &= ~TH_FIN; + + win = sbspace(&so->so_rcv); + + /* Do we have a reason to send anything? */ + if (len) { + if (len == tp->t_maxseg) + goto send; + if ((idle || tp->t_flags & TF_NODELAY) && + len + off >= so->so_snd.sb_cc) + goto send; + if (tp->t_force) + goto send; + if (len >= tp->max_sndwnd / 2) + goto send; + if (SEQ_LT(tp->snd_nxt, tp->snd_max)) + goto send; + } + if (win > 0) { + int32 adv = min(win, (int32)TCP_MAXWIN << tp->rcv_scale) - + (tp->rcv_adv - tp->rcv_nxt); + if (adv >= (int32)(2 * tp->t_maxseg)) + goto send; + if (2 * adv >= (int32)so->so_rcv.sb_hiwat) + goto send; + } + if (tp->t_flags & TF_ACKNOW) + goto send; + if (flags & (TH_SYN | TH_RST)) + goto send; + if (SEQ_GT(tp->snd_up, tp->snd_una)) + goto send; + if ((flags & TH_FIN) && ((tp->t_flags & TF_SENTFIN) == 0 || + tp->snd_nxt == tp->snd_una)) + goto send; + if (so->so_snd.sb_cc && tp->t_timer[TCPT_REXMT] == 0 && + (tp->t_timer[TCPT_PERSIST] == 0)) { + tp->t_rxtshift = 0; + tcp_setpersist(tp); + } + + /* We don't have a reason to send anything for this connection, + * so just return. + */ + return 0; +send: + + optlen = 0; + hdrlen = sizeof(struct tcpiphdr); + if (flags & TH_SYN) { + tp->snd_nxt = tp->iss; + if ((tp->t_flags & TF_NOOPT) == 0) { + uint16 mss; + + opt[0] = TCPOPT_MAXSEG; + opt[1] = 4; + mss = htons((uint16)tcp_mss(tp, 0)); + memcpy((caddr_t)opt + 2, &mss, sizeof(mss)); + optlen = 4; + if ((tp->t_flags & TF_REQ_SCALE) && + ((flags & TH_ACK) == 0 || + (tp->t_flags & TF_RCVD_SCALE))) { + *((uint32*)(opt + optlen)) = htonl (TCPOPT_NOP << 24 | + TCPOPT_WINDOW << 16 | + TCPOLEN_WINDOW << 8 | + tp->request_r_scale); + optlen += 4; + } + } + } + if ((tp->t_flags & (TF_REQ_TSTMP | TF_NOOPT)) == TF_REQ_TSTMP && + (flags & TH_RST) == 0 && + ((flags & (TH_SYN | TH_ACK)) == TH_SYN || + (tp->t_flags & TF_RCVD_TSTMP))) { + uint32 *lp = (uint32*)(opt + optlen); + *lp++ = htonl(TCPOPT_TSTAMP_HDR); + *lp++ = htonl(tcp_now); + *lp = htonl(tp->ts_recent); + optlen += TCPOLEN_TSTAMP_APPA; + } + hdrlen += optlen; + + if (len > tp->t_maxseg - optlen) { + len = tp->t_maxseg - optlen; + sendalot = 1; + } + + if (len) { + if (tp->t_force && len == 1) + tcpstat.tcps_sndprobe++; + else if (SEQ_LT(tp->snd_nxt, tp->snd_max)) { + tcpstat.tcps_sndrexmitpack++; + tcpstat.tcps_sndrexmitbyte += len; + } else { + tcpstat.tcps_sndpack++; + tcpstat.tcps_sndbyte += len; + } + m = m_gethdr(MT_HEADER); + if (m == NULL) { + printf("tcp_output: ENOBUFS\n"); + error = ENOBUFS; + goto out; + } + m->m_data += max_linkhdr; + m->m_len = hdrlen; + if (len <= MHLEN - hdrlen - max_linkhdr) { + m_copydata(so->so_snd.sb_mb, off, (int)len, mtod(m, caddr_t) + hdrlen); + m->m_len += len; + } else { + m->m_next = m_copym(so->so_snd.sb_mb, off, (int)len); + if (m->m_next == NULL) + len = 0; + } + if (off + len == so->so_snd.sb_cc) + flags |= TH_PUSH; + } else { + if (tp->t_flags & TF_ACKNOW) + tcpstat.tcps_sndacks++; + else if (flags & (TH_SYN | TH_FIN | TH_RST)) + tcpstat.tcps_sndctrl++; + else if (SEQ_GT(tp->snd_up, tp->snd_una)) + tcpstat.tcps_sndurg++; + else + tcpstat.tcps_sndwinup++; + + m = m_gethdr(MT_HEADER); + if (m == NULL) { + printf("tcp_output: ENOBUFS\n"); + error = ENOBUFS; + goto out; + } + m->m_data += max_linkhdr; + m->m_len = hdrlen; + } + m->m_pkthdr.rcvif = NULL; + ti = mtod(m, struct tcpiphdr*); + if (tp->t_template == NULL) + printf("tcp_output: PANIC t_template == NULL\n"); + memcpy((caddr_t)ti, (caddr_t)tp->t_template, sizeof(struct tcpiphdr)); + + if (flags & TH_FIN && (tp->t_flags & TF_SENTFIN) && + (tp->snd_nxt == tp->snd_max)) + tp->snd_nxt--; + + if (len || (flags & (TH_SYN | TH_FIN)) || tp->t_timer[TCPT_PERSIST]) + ti->ti_seq = htonl(tp->snd_nxt); + else + ti->ti_seq = htonl(tp->snd_max); + + ti->ti_ack = htonl(tp->rcv_nxt); + + if (optlen) { + memcpy((caddr_t)(ti + 1), (caddr_t)opt, optlen); + ti->ti_off = (sizeof(struct tcphdr) + optlen) >> 2; + } + ti->ti_flags = flags; + + if (win < (int32)(so->so_rcv.sb_hiwat / 4) && + win < (int32) tp->t_maxseg) + win = 0; + if (win > (int32) TCP_MAXWIN << tp->rcv_scale) + win = (int32) TCP_MAXWIN << tp->rcv_scale; + if (win < (int32)(tp->rcv_adv - tp->rcv_nxt)) + win = (int32)(tp->rcv_adv - tp->rcv_nxt); + ti->ti_win = htons((uint16)(win >> tp->rcv_scale)); + + if (SEQ_GT(tp->snd_up, tp->snd_nxt)) { + ti->ti_urp = htons((uint16)(tp->snd_up - tp->snd_nxt)); + ti->ti_flags |= TH_URG; + } else + tp->snd_up = tp->snd_una; + + if (len + optlen) + ti->ti_len = htons((uint16)(sizeof(struct tcphdr) + optlen + len)); + ti->ti_sum = in_cksum(m, (int)(hdrlen+len), 0); + + if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) { + tcp_seq startseq = tp->snd_nxt; + + if (flags & (TH_SYN | TH_FIN)) { + tp->snd_nxt++; + if (flags & TH_FIN) + tp->t_flags |= TF_SENTFIN; + } + tp->snd_nxt += len; + if (SEQ_GT(tp->snd_nxt, tp->snd_max)) { + tp->snd_max = tp->snd_nxt; + if (tp->t_rtt == 0) { + tp->t_rtt = 1; + tp->t_rtseq = startseq; + tcpstat.tcps_segstimed++; + } + } + if (tp->t_timer[TCPT_REXMT] == 0 && + tp->snd_nxt != tp->snd_una) { + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + if (tp->t_timer[TCPT_PERSIST]) { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + } else if (SEQ_GT(tp->snd_nxt + len, tp->snd_max)) + tp->snd_max = tp->snd_nxt + len; + + if (so->so_options & SO_DEBUG) + tcp_trace(TA_OUTPUT, tp->t_state, tp, ti, 0, len); + + m->m_pkthdr.len = hdrlen + len; + ((struct ip*)ti)->ip_len = m->m_pkthdr.len; + ((struct ip*)ti)->ip_ttl = tp->t_inpcb->inp_ip.ip_ttl; + ((struct ip*)ti)->ip_tos = tp->t_inpcb->inp_ip.ip_tos; + error = ipm->output(m, tp->t_inpcb->inp_options, &tp->t_inpcb->inp_route, + so->so_options & SO_DONTROUTE, NULL); + + if (error) { +out: + if (error == ENOBUFS) { + tcp_quench(tp->t_inpcb, 0); + return 0; + } + if ((error == EHOSTUNREACH || error == ENETDOWN) && + TCPS_HAVERCVDSYN(tp->t_state)) { + tp->t_softerror = error; + return 0; + } + return error; + } + + tcpstat.tcps_sndtotal++; + + if (win > 0 && SEQ_GT(tp->rcv_nxt + win, tp->rcv_adv)) + tp->rcv_adv = tp->rcv_nxt + win; + tp->last_ack_sent = tp->rcv_nxt; + tp->t_flags &= ~(TF_ACKNOW | TF_DELACK); + + if (sendalot) + goto again; + + return 0; +} diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp_timer.c b/src/add-ons/kernel/network/protocols/tcp/tcp_timer.c new file mode 100644 index 0000000000..924c4f4f53 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/tcp_timer.c @@ -0,0 +1,182 @@ +/* tcp_timer.c */ + +#ifndef _KERNEL_ +#include +#endif + +#include "sys/protosw.h" +#include "netinet/in_pcb.h" +#include "netinet/tcp.h" +#include "netinet/tcp_timer.h" +#include "netinet/tcp_var.h" +#include "netinet/tcp_seq.h" +#include "netinet/tcp_fsm.h" + +#include "core_module.h" +#include "core_funcs.h" + +#ifdef _KERNEL_ +#include +#endif + +extern struct core_module_info *core; + +int tcp_backoff[TCP_MAXRXTSHIFT + 1] = + { 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 }; + +int tcp_totbackoff = 511; /* sum of tcp_backoff[] */ + +int tcp_keepidle = TCPTV_KEEP_IDLE; +int tcp_keepintvl = TCPTV_KEEPINTVL; +int tcp_maxpersistidle = TCPTV_KEEP_IDLE; /* max idle time in persist */ +int tcp_maxidle; + +void tcp_canceltimers(struct tcpcb *tp) +{ + int i; + + for (i=0; i < TCPT_NTIMERS; i++) + tp->t_timer[i] = 0; +} + +struct tcpcb * tcp_timers(struct tcpcb *tp, int timer) +{ + int rexmt; + + switch (timer) { + /* TCPT_2MSL is used for + * FIN_WAIT2 timer + * TIME_WAIT + */ + case TCPT_2MSL: + if (tp->t_state != TCPS_TIME_WAIT && + tp->t_idle <= tcp_maxidle) + tp->t_timer[TCPT_2MSL] = tcp_keepintvl; + else + tp = tcp_close(tp); + break; + /* TCPT_PERSIST is used to wait for being told it can send + * data. The window ahs been set to 0, so no data can be + * sent, but there's data waiting to be sent. when the timer + * expires we'll force a byte to be sent (despite the window + * being 0) and reset the time... + */ + case TCPT_PERSIST: + tcpstat.tcps_persisttimeo++; + tcp_setpersist(tp); + tp->t_force = 1; + tcp_output(tp); + tp->t_force = 0; + break; + /* TCPT_KEEP is used for + * send data + * drop connection if too long idle + */ + case TCPT_KEEP: + tcpstat.tcps_keeptimeo++; + if (tp->t_state < TCPS_ESTABLISHED) + goto dropit; + if (tp->t_inpcb->inp_socket->so_options & SO_KEEPALIVE && + tp->t_state <= TCPS_CLOSE_WAIT) { + if (tp->t_idle >= tcp_keepidle + tcp_maxidle) + goto dropit; + tcpstat.tcps_keepprobe++; + tcp_respond(tp, tp->t_template, NULL, tp->rcv_nxt, + tp->snd_una - 1, 0); + tp->t_timer[TCPT_KEEP] = tcp_keepintvl; + } else + tp->t_timer[TCPT_KEEP] = tcp_keepidle; + break; +dropit: + tcpstat.tcps_keepdrops++; + tp = tcp_drop(tp, ETIMEDOUT); + break; + /* TCPT_REXMT is the transmission timer */ + case TCPT_REXMT: + if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) { + tp->t_rxtshift = TCP_MAXRXTSHIFT; + tcpstat.tcps_timeoutdrop++; + tp = tcp_drop(tp, tp->t_softerror ? tp->t_softerror : ETIMEDOUT); + break; + } + tcpstat.tcps_rexmttimeo++; + rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift]; + TCPT_RANGESET(tp->t_rxtcur, rexmt, tp->t_rttmin, TCPTV_REXMTMAX); + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) { + in_losing(tp->t_inpcb); + tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT); + tp->t_srtt = 0; + } + tp->snd_nxt = tp->snd_una; + tp->t_rtt = 0; + { + uint win = min(tp->snd_wnd, tp->snd_cwnd) / 2/ tp->t_maxseg; + if (win < 2) + win = 2; + tp->snd_cwnd = tp->t_maxseg; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_dupacks = 0; + } + tcp_output(tp); + break; + } + return (tp); +} + +void tcp_slowtimer(void *data) +{ + struct inpcb *ip, *ipnxt; + struct tcpcb *tp; + int i; + + tcp_maxidle = TCPTV_KEEPCNT * tcp_keepintvl; + + ip = tcb.inp_next; + if (!ip) + return; + for (; ip != &tcb; ip = ipnxt) { + ipnxt = ip->inp_next; + tp = intotcpcb(ip); + if (!tp) + continue; + for (i=0;i < TCPT_NTIMERS;i++) { + if (tp->t_timer[i] && --tp->t_timer[i] == 0) { + tcp_userreq(tp->t_inpcb->inp_socket, PRU_SLOWTIMO, NULL, + (struct mbuf *)i, NULL); + if (ipnxt->inp_prev != ip) + goto tpgone; + } + } + tp->t_idle++; + if (tp->t_rtt) + tp->t_rtt++; +tpgone: + ; /* mwcc wants a ; here, so it gets one */ + } + tcp_iss += TCP_ISSINCR / PR_SLOWHZ; + tcp_now++; + + return; +} + +void tcp_fasttimer(void *data) +{ + struct inpcb *inp; + struct tcpcb *tp; + + inp = tcb.inp_next; + if (inp) { + for (; inp != &tcb; inp = inp->inp_next) { + if ((tp = (struct tcpcb*)inp->inp_ppcb) && + (tp->t_flags & TF_DELACK)) { + tp->t_flags &= ~TF_DELACK; + tp->t_flags |= TF_ACKNOW; + tcpstat.tcps_delack++; + tcp_output(tp); + } + } + } + + return; +} diff --git a/src/add-ons/kernel/network/protocols/udp/udp.c b/src/add-ons/kernel/network/protocols/udp/udp.c new file mode 100644 index 0000000000..ece42f98d0 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/udp/udp.c @@ -0,0 +1,449 @@ +/* udp.c + */ + +#ifndef _KERNEL_ +#include +#include +#endif + +#include "net_misc.h" +#include "protocols.h" +#include "netinet/in_systm.h" +#include "netinet/in_var.h" +#include "netinet/in_pcb.h" +#include "netinet/ip.h" +#include "sys/domain.h" +#include "sys/protosw.h" +#include "netinet/ip_var.h" +#include "netinet/udp.h" +#include "netinet/udp_var.h" +#include "netinet/ip_icmp.h" + +#include "core_module.h" +#include "net_module.h" +#include "core_funcs.h" +#include "../icmp/icmp_module.h" + +#ifdef _KERNEL_ +#include +static status_t udp_ops(int32 op, ...); +#define UDP_MODULE_PATH "network/protocol/udp" +#else /* _KERNEL */ +#define udp_ops NULL +#define UDP_MODULE_PATH "modules/protocol/udp" +#endif + +/* Private variables */ +static struct core_module_info *core = NULL; +static struct protosw *proto[IPPROTO_MAX]; +static struct inpcb udb; /* head of the UDP PCB list! */ +static int udpcksum = 1; /* do we calculate the UDP checksum? */ +static struct udpstat udpstat; +static uint32 udp_sendspace; /* size of send buffer */ +static uint32 udp_recvspace; /* size of recieve buffer */ +static struct icmp_module_info *icmp = NULL; +#ifndef _KERNEL_ +static image_id icmpid = -1; +#endif + +static struct in_addr zeroin_addr = {0}; + +/* Private but used globally, need to make thread safe... tls??? */ +static struct inpcb *udp_last_inpcb = NULL; +static struct sockaddr_in udp_in; + +#if SHOW_DEBUG +static void dump_udp(struct mbuf *buf) +{ + struct ip *ip = mtod(buf, struct ip*); + struct udphdr *udp = (struct udphdr*)((caddr_t)ip + (ip->ip_hl * 4)); + + printf("udp_header :\n"); + printf(" : src_port : %d\n", ntohs(udp->src_port)); + printf(" : dst_port : %d\n", ntohs(udp->dst_port)); + printf(" : udp length : %d bytes\n", ntohs(udp->length)); +} +#endif /* SHOW_DEBUG */ + +int udp_output(struct inpcb *inp, struct mbuf *m, struct mbuf *addr,struct mbuf *control) +{ + struct udpiphdr *ui; + uint16 len = m->m_pkthdr.len; + uint16 hdrlen = len + (uint16)sizeof(struct udpiphdr); + struct in_addr laddr; + int error = 0; + + if (control) + m_freem(control); + + if (addr) { + laddr = inp->laddr; + if (inp->faddr.s_addr != INADDR_ANY) { + error = EISCONN; + goto release; + } + error = in_pcbconnect(inp, addr); + if (error) + goto release; + + } else { + if (inp->faddr.s_addr == INADDR_ANY) { + error = ENOTCONN; + goto release; + } + } + + M_PREPEND(m, sizeof(*ui)); + if (!m) { + error = ENOMEM; + goto release; + } + + ui = mtod(m, struct udpiphdr *); + ui->ui_next = ui->ui_prev = NULL; + ui->ui_x1 = 0; + ui->ui_pr = IPPROTO_UDP; + ui->ui_len = htons(len + sizeof(struct udphdr)); + ui->ui_src = inp->laddr; + ui->ui_dst = inp->faddr; + ui->ui_sport = inp->lport; + ui->ui_dport = inp->fport; + ui->ui_ulen = ui->ui_len; + ui->ui_sum = 0; + + if (udpcksum) + ui->ui_sum = in_cksum(m, hdrlen, 0); + + if (ui->ui_sum == 0) + ui->ui_sum = 0xffff; + + ((struct ip*)ui)->ip_len = hdrlen; + ((struct ip*)ui)->ip_ttl = 64; /* XXX - Fix this! */ + ((struct ip*)ui)->ip_tos = 0; /* XXX - Fix this! */ + + + /* XXX - add multicast options when available! */ + error = proto[IPPROTO_IP]->pr_output(m, + inp->inp_options, &inp->inp_route, + inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST), + NULL /* inp->inp_moptions */ ); + + if (addr) { + in_pcbdisconnect(inp); /* remove temporary route */ + inp->laddr = laddr; + } + + return error; + +release: + m_freem(m); + return error; +} + +int udp_userreq(struct socket *so, int req, + struct mbuf *m, struct mbuf *addr, struct mbuf *ctrl) +{ + struct inpcb *inp = sotoinpcb(so); + int error = 0; + + if (req == PRU_CONTROL) + return in_control(so, (int)m, (caddr_t)addr, (struct ifnet *)ctrl); + + if (inp == NULL && req != PRU_ATTACH) { + error = EINVAL; + goto release; + } + + switch (req) { + case PRU_ATTACH: + /* we don't replace an existing inpcb! */ + if (inp != NULL) { + error = EINVAL; + break; + } + error = in_pcballoc(so, &udb); /* udp head */ + if (error) + break; + error = soreserve(so, udp_sendspace, udp_recvspace); + if (error) + break; + /* XXX - this is a hack! This should be the default ip TTL */ + ((struct inpcb*) so->so_pcb)->inp_ip.ip_ttl = 64; + break; + case PRU_DETACH: + /* This should really be protected when in kernel... */ + if (inp == udp_last_inpcb) + udp_last_inpcb = &udb; + in_pcbdetach(inp); + break; + case PRU_BIND: + /* XXX - locking */ + error = in_pcbbind(inp, addr); + break; + case PRU_SEND: + /* we can use this as we're in the same module... */ + return udp_output(inp, m, addr, ctrl); + case PRU_LISTEN: + error = EINVAL;//EOPNOTSUPP; + break; + case PRU_CONNECT: + if (inp->faddr.s_addr != INADDR_ANY) { + error = EISCONN; + break; + } + error = in_pcbconnect(inp, addr); + if (error == 0) + soisconnected(so); + break; + case PRU_CONNECT2: + error = EINVAL;//EOPNOTSUPP; + break; + case PRU_ACCEPT: + error = EINVAL;//EOPNOTSUPP; + break; + case PRU_DISCONNECT: + if (inp->faddr.s_addr == INADDR_ANY) { + error = ENOTCONN; + break; + } + in_pcbdisconnect(inp); + inp->laddr.s_addr = INADDR_ANY; + so->so_state &= ~SS_ISCONNECTED; + break; + case PRU_SOCKADDR: + in_setsockaddr(inp, addr); + break; + case PRU_PEERADDR: + in_setpeeraddr(inp, addr); + break; + case PRU_SENSE: + /* will we ever see one of these???? */ + /* generated by an fstat on bsd... */ + return 0; + default: + printf("Unknown options passed to udp_userreq (%d)\n", req); + } + +release: + if (ctrl) { + printf("UDP control retained!\n"); + m_freem(ctrl); + } + if (m) + m_freem(m); + + return error; +} + +void udp_input(struct mbuf *buf, int hdrlen) +{ + struct ip *ip = mtod(buf, struct ip*); + struct udphdr *udp = (struct udphdr*)((caddr_t)ip + hdrlen); + uint16 ck = 0; + int len; + struct ip saved_ip; + struct mbuf *opts = NULL; + struct inpcb *inp = NULL; + +#if SHOW_DEBUG + dump_udp(buf); +#endif + /* check and adjust sizes as required... */ + len = ntohs(udp->uh_ulen) + hdrlen; + saved_ip = *ip; + + if (udpcksum && udp->uh_sum) { + ((struct ipovly*)ip)->ih_next = ((struct ipovly*)ip)->ih_prev = NULL; + ((struct ipovly*)ip)->ih_x1 = 0; + ((struct ipovly*)ip)->ih_len = udp->uh_ulen; + /* XXX - if we have options we need to be careful when calculating the + * checksum here... + */ + if ((ck = in_cksum(buf, len, 0)) != 0) { + udpstat.udps_badsum++; + m_freem(buf); + printf("udp_input: UDP Checksum check failed. (%d over %ld bytes)\n", ck, len + sizeof(*ip)); + return; + } + } + inp = udp_last_inpcb; + + if (inp == NULL || + inp->lport != udp->uh_dport || + inp->fport != udp->uh_sport || + inp->faddr.s_addr != ip->ip_src.s_addr || + inp->laddr.s_addr != ip->ip_dst.s_addr) { + + inp = in_pcblookup(&udb, ip->ip_src, udp->uh_sport, + ip->ip_dst, udp->uh_dport, INPLOOKUP_WILDCARD); + if (inp) + udp_last_inpcb = inp; + } + if (!inp) { + atomic_add((vint32 *)&udpstat.udps_noport, 1); + if (buf->m_flags & (M_BCAST | M_MCAST)) { + atomic_add((vint32 *)&udpstat.udps_noportbcast, 1); + goto bad; + } + *ip = saved_ip; + ip->ip_len += hdrlen; + icmp->error(buf, ICMP_UNREACH, ICMP_UNREACH_PORT, 0, 0); + return; + } + + udp_in.sin_port = udp->uh_sport; + udp_in.sin_addr = ip->ip_src; + + if (inp->inp_flags & INP_CONTROLOPT) { + printf("INP Control Options to process!\n"); + /* XXX - add code to do this... */ + } + + hdrlen += sizeof(struct udphdr); + buf->m_len -= hdrlen; + buf->m_pkthdr.len -= hdrlen; + buf->m_data += hdrlen; + + if (sbappendaddr(&inp->inp_socket->so_rcv, (struct sockaddr*)&udp_in, + buf, opts) == 0) { + goto bad; + } + sorwakeup(inp->inp_socket); + return; + +bad: + if (opts) + m_freem(opts); + m_freem(buf); + return; +} + +static void udp_notify(struct inpcb *inp, int err) +{ + inp->inp_socket->so_error = err; + sorwakeup(inp->inp_socket); + sowwakeup(inp->inp_socket); +} + +static void udp_ctlinput(int cmd, struct sockaddr *sa, void *ipp) +{ + struct ip *ip = (struct ip*)ipp; + struct udphdr *uh; + + if (!PRC_IS_REDIRECT(cmd) && + ((uint)cmd >= PRC_NCMDS || inetctlerrmap(cmd) == 0)) + return; + if (ip) { + uh = (struct udphdr *)((char *) ip + (ip->ip_hl << 2)); + in_pcbnotify(&udb, sa, uh->uh_dport, ip->ip_src, uh->uh_sport, cmd, udp_notify); + } else + in_pcbnotify(&udb, sa, 0, zeroin_addr, 0, cmd, udp_notify); +} + +void udp_init(void) +{ + udb.inp_prev = udb.inp_next = &udb; + udp_sendspace = 9216; /* default size */ + udp_recvspace = 41600; /* default size */ + udp_in.sin_len = sizeof(udp_in); + memset(&udpstat, 0, sizeof(udpstat)); + + memset(proto, 0, sizeof(struct protosw *) * IPPROTO_MAX); + add_protosw(proto, NET_LAYER2); +} + +static struct protosw my_proto = { + "UDP Module", + UDP_MODULE_PATH, + SOCK_DGRAM, + NULL, + IPPROTO_UDP, + PR_ATOMIC | PR_ADDR, + NET_LAYER3, + + &udp_init, + &udp_input, + NULL, /* pr_output */ + &udp_userreq, + NULL, /* pr_sysctl */ + &udp_ctlinput, + NULL, /* pr_ctloutput */ + + NULL, + NULL +}; + +static int udp_module_init(void *cpp) +{ + if (cpp) + core = cpp; + add_domain(NULL, AF_INET); + add_protocol(&my_proto, AF_INET); + +#ifndef _KERNEL_ + if (!icmp) { + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + strcat(path, "/" ICMP_MODULE_PATH); + + icmpid = load_add_on(path); + if (icmpid > 0) { + status_t rv = get_image_symbol(icmpid, "protocol_info", + B_SYMBOL_TYPE_DATA, (void**)&icmp); + if (rv < 0) { + printf("Failed to get access to IPv4 information!\n"); + return -1; + } + } else { + printf("Failed to load the IPv4 module...\n"); + return -1; + } + icmp->set_core(cpp); + } +#else + if (!icmp) + get_module(ICMP_MODULE_PATH, (module_info**)&icmp); +#endif + + return 0; +} + +static int udp_module_stop(void) +{ + remove_protocol(&my_proto); + remove_domain(AF_INET); + return 0; +} + +_EXPORT struct kernel_net_module_info protocol_info = { + { + UDP_MODULE_PATH, + B_KEEP_LOADED, + udp_ops + }, + udp_module_init, + udp_module_stop +}; + +#ifdef _KERNEL_ +static status_t udp_ops(int32 op, ...) +{ + switch(op) { + case B_MODULE_INIT: + get_module(CORE_MODULE_PATH, (module_info**)&core); + if (!core) + return B_ERROR; + return B_OK; + case B_MODULE_UNINIT: + break; + default: + return B_ERROR; + } + return B_OK; +} + +_EXPORT module_info *modules[] = { + (module_info *)&protocol_info, + NULL +}; +#endif