make kernel includes standard
[unix-history] / usr / src / sys / netinet / in_pcb.c
index 821d64f..2c3dc3d 100644 (file)
-/* in_pcb.c 4.3 81/11/18 */
+/*
+ * Copyright (c) 1982, 1986, 1991 Regents of the University of California.
+ * All rights reserved.
+ *
+ * %sccs.include.redist.c%
+ *
+ *     @(#)in_pcb.c    7.23 (Berkeley) %G%
+ */
 
 
-#include "../h/param.h"
-#include "../h/mbuf.h"
-#include "../h/socket.h"
-#include "../h/socketvar.h"
-#include "../net/inet.h"
-#include "../net/inet_systm.h"
-#include "../net/inet_host.h"
-#include "../net/inet_pcb.h"
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
 
 
-struct inpcb *
-in_pcballoc()
-{
+#include <net/if.h>
+#include <net/route.h>
 
 
-       struct mbuf *m;
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/in_pcb.h>
+#include <netinet/in_var.h>
 
 
-       m = m_getclr(M_WAIT);
-       m->m_off = MMINOFF;
-       return (mtod(m, struct inpcb *));
-}
+#ifdef MULTICAST
+#include <netinet/ip_var.h>
+#endif
 
 
-in_pcbenter(head, new)
-       struct inpcb *head, *new;
+struct in_addr zeroin_addr;
+
+in_pcballoc(so, head)
+       struct socket *so;
+       struct inpcb *head;
 {
 {
+       struct mbuf *m;
        register struct inpcb *inp;
 
        register struct inpcb *inp;
 
-       for (inp = head->inp_next; inp != head; inp = inp->inp_next)
-               if (inp->inp_fhost->h_addr.s_addr == new->inp_fhost.s_addr &&
-                   inp->inp_fport == new->inp_fport &&
-                   inp->inp_lhost->h_addr.s_addr = new->inp_fhost.s_addr &&
-                   inp->inp_lport == new->inp_lport)
+       MALLOC(inp, struct inpcb *, sizeof(*inp), M_PCB, M_WAITOK);
+       if (inp == NULL)
+               return (ENOBUFS);
+       bzero((caddr_t)inp, sizeof(*inp));
+       inp->inp_head = head;
+       inp->inp_socket = so;
+       insque(inp, head);
+       so->so_pcb = (caddr_t)inp;
+       return (0);
+}
+       
+in_pcbbind(inp, nam)
+       register struct inpcb *inp;
+       struct mbuf *nam;
+{
+       register struct socket *so = inp->inp_socket;
+       register struct inpcb *head = inp->inp_head;
+       register struct sockaddr_in *sin;
+       u_short lport = 0;
+
+       if (in_ifaddr == 0)
+               return (EADDRNOTAVAIL);
+       if (inp->inp_lport || inp->inp_laddr.s_addr != INADDR_ANY)
+               return (EINVAL);
+       if (nam == 0)
+               goto noname;
+       sin = mtod(nam, struct sockaddr_in *);
+       if (nam->m_len != sizeof (*sin))
+               return (EINVAL);
+       if (sin->sin_addr.s_addr != INADDR_ANY) {
+               int tport = sin->sin_port;
+
+               sin->sin_port = 0;              /* yech... */
+               if (ifa_ifwithaddr((struct sockaddr *)sin) == 0)
+                       return (EADDRNOTAVAIL);
+               sin->sin_port = tport;
+       }
+       lport = sin->sin_port;
+       if (lport) {
+               struct inpcb *t;
+               u_short aport = ntohs(lport);
+               int wild = 0;
+
+               /* GROSS */
+               if (aport < IPPORT_RESERVED && (so->so_state & SS_PRIV) == 0)
+                       return (EACCES);
+               /* even GROSSER, but this is the Internet */
+               if ((so->so_options & SO_REUSEADDR) == 0 &&
+                   ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 ||
+                    (so->so_options & SO_ACCEPTCONN) == 0))
+                       wild = INPLOOKUP_WILDCARD;
+               t = in_pcblookup(head, zeroin_addr, 0,
+                               sin->sin_addr, lport, wild);
+               if (t && !((so->so_options & t->inp_socket->so_options) &
+                   SO_REUSEPORT))
                        return (EADDRINUSE);
                        return (EADDRINUSE);
-       insque(new, head);
+       }
+       inp->inp_laddr = sin->sin_addr;
+noname:
+       if (lport == 0)
+               do {
+                       if (head->inp_lport++ < IPPORT_RESERVED ||
+                           head->inp_lport > IPPORT_USERRESERVED)
+                               head->inp_lport = IPPORT_RESERVED;
+                       lport = htons(head->inp_lport);
+               } while (in_pcblookup(head,
+                           zeroin_addr, 0, inp->inp_laddr, lport, 0));
+       inp->inp_lport = lport;
        return (0);
 }
 
        return (0);
 }
 
-in_pcbfree(inp)
+/*
+ * Connect from a socket to a specified address.
+ * Both address and port must be specified in argument sin.
+ * If don't have a local address for this socket yet,
+ * then pick one.
+ */
+in_pcbconnect(inp, nam)
+       register struct inpcb *inp;
+       struct mbuf *nam;
+{
+       struct in_ifaddr *ia;
+       struct sockaddr_in *ifaddr;
+       register struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *);
+
+       if (nam->m_len != sizeof (*sin))
+               return (EINVAL);
+       if (sin->sin_family != AF_INET)
+               return (EAFNOSUPPORT);
+       if (sin->sin_port == 0)
+               return (EADDRNOTAVAIL);
+       if (in_ifaddr) {
+               /*
+                * If the destination address is INADDR_ANY,
+                * use the primary local address.
+                * If the supplied address is INADDR_BROADCAST,
+                * and the primary interface supports broadcast,
+                * choose the broadcast address for that interface.
+                */
+#define        satosin(sa)     ((struct sockaddr_in *)(sa))
+               if (sin->sin_addr.s_addr == INADDR_ANY)
+                   sin->sin_addr = IA_SIN(in_ifaddr)->sin_addr;
+               else if (sin->sin_addr.s_addr == (u_long)INADDR_BROADCAST &&
+                 (in_ifaddr->ia_ifp->if_flags & IFF_BROADCAST))
+                   sin->sin_addr = satosin(&in_ifaddr->ia_broadaddr)->sin_addr;
+       }
+       if (inp->inp_laddr.s_addr == INADDR_ANY) {
+               register struct route *ro;
+               struct ifnet *ifp;
+
+               ia = (struct in_ifaddr *)0;
+               /* 
+                * If route is known or can be allocated now,
+                * our src addr is taken from the i/f, else punt.
+                */
+               ro = &inp->inp_route;
+               if (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 = (struct rtentry *)0;
+               }
+               if ((inp->inp_socket->so_options & SO_DONTROUTE) == 0 && /*XXX*/
+                   (ro->ro_rt == (struct rtentry *)0 ||
+                   ro->ro_rt->rt_ifp == (struct ifnet *)0)) {
+                       /* No route yet, so try to acquire one */
+                       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);
+               }
+               /*
+                * If we found a route, use the address
+                * corresponding to the outgoing interface
+                * unless it is the loopback (in case a route
+                * to our address on another net goes to loopback).
+                */
+               if (ro->ro_rt && (ifp = ro->ro_rt->rt_ifp) &&
+                   (ifp->if_flags & IFF_LOOPBACK) == 0 &&
+                   (ia = (struct in_ifaddr *)ro->ro_rt->rt_ifa) == 0)
+                       for (ia = in_ifaddr; ia; ia = ia->ia_next)
+                               if (ia->ia_ifp == ifp)
+                                       break;
+               if (ia == 0) {
+                       int fport = sin->sin_port;
+
+                       sin->sin_port = 0;
+                       ia = (struct in_ifaddr *)
+                           ifa_ifwithdstaddr((struct sockaddr *)sin);
+                       sin->sin_port = fport;
+                       if (ia == 0)
+                               ia = in_iaonnetof(in_netof(sin->sin_addr));
+                       if (ia == 0)
+                               ia = in_ifaddr;
+                       if (ia == 0)
+                               return (EADDRNOTAVAIL);
+               }
+#ifdef MULTICAST
+               /*
+                * If the destination address is multicast and an outgoing
+                * interface has been set as a multicast option, use the
+                * address of that interface as our source address.
+                */
+               if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr)) &&
+                   inp->inp_moptions != NULL) {
+                       struct ip_moptions *imo;
+
+                       imo = inp->inp_moptions;
+                       if (imo->imo_multicast_ifp != NULL) {
+                               ifp = imo->imo_multicast_ifp;
+                               for (ia = in_ifaddr; ia; ia = ia->ia_next)
+                                       if (ia->ia_ifp == ifp)
+                                               break;
+                               if (ia == 0)
+                                       return (EADDRNOTAVAIL);
+                       }
+               }
+#endif
+               ifaddr = (struct sockaddr_in *)&ia->ia_addr;
+       }
+       if (in_pcblookup(inp->inp_head,
+           sin->sin_addr,
+           sin->sin_port,
+           inp->inp_laddr.s_addr ? inp->inp_laddr : ifaddr->sin_addr,
+           inp->inp_lport,
+           0))
+               return (EADDRINUSE);
+       if (inp->inp_laddr.s_addr == INADDR_ANY) {
+               if (inp->inp_lport == 0)
+                       (void)in_pcbbind(inp, (struct mbuf *)0);
+               inp->inp_laddr = ifaddr->sin_addr;
+       }
+       inp->inp_faddr = sin->sin_addr;
+       inp->inp_fport = sin->sin_port;
+       return (0);
+}
+
+in_pcbdisconnect(inp)
+       struct inpcb *inp;
+{
+
+       inp->inp_faddr.s_addr = INADDR_ANY;
+       inp->inp_fport = 0;
+       if (inp->inp_socket->so_state & SS_NOFDREF)
+               in_pcbdetach(inp);
+}
+
+in_pcbdetach(inp)
        struct inpcb *inp;
 {
        struct socket *so = inp->inp_socket;
 
        struct inpcb *inp;
 {
        struct socket *so = inp->inp_socket;
 
-       if (so->so_isfilerefd == 0)
-               sofree(so);
-       else
-               so->so_pcb = 0;
-       if (inp->inp_lhost)
-               in_hostfree(inp->inp_lhost);
-       if (inp->inp_fhost)
-               in_hostfree(inp->inp_fhost);
-       (void) m_free(dtom(inp));
+       so->so_pcb = 0;
+       sofree(so);
+       if (inp->inp_options)
+               (void)m_free(inp->inp_options);
+       if (inp->inp_route.ro_rt)
+               rtfree(inp->inp_route.ro_rt);
+#ifdef MULTICAST
+       ip_freemoptions(inp->inp_moptions);
+#endif
+       remque(inp);
+       FREE(inp, M_PCB);
 }
 
 }
 
-struct inpcb *
-in_pcblookup(head, fhost, fport, lhost, lport)
+in_setsockaddr(inp, nam)
+       register struct inpcb *inp;
+       struct mbuf *nam;
+{
+       register struct sockaddr_in *sin;
+       
+       nam->m_len = sizeof (*sin);
+       sin = mtod(nam, struct sockaddr_in *);
+       bzero((caddr_t)sin, sizeof (*sin));
+       sin->sin_family = AF_INET;
+       sin->sin_len = sizeof(*sin);
+       sin->sin_port = inp->inp_lport;
+       sin->sin_addr = inp->inp_laddr;
+}
+
+in_setpeeraddr(inp, nam)
+       struct inpcb *inp;
+       struct mbuf *nam;
+{
+       register struct sockaddr_in *sin;
+       
+       nam->m_len = sizeof (*sin);
+       sin = mtod(nam, struct sockaddr_in *);
+       bzero((caddr_t)sin, sizeof (*sin));
+       sin->sin_family = AF_INET;
+       sin->sin_len = sizeof(*sin);
+       sin->sin_port = inp->inp_fport;
+       sin->sin_addr = inp->inp_faddr;
+}
+
+/*
+ * Pass some notification to all connections of a protocol
+ * associated with address dst.  The local address and/or port numbers
+ * may be specified to limit the search.  The "usual action" will be
+ * taken, depending on the ctlinput cmd.  The caller must filter any
+ * cmds that are uninteresting (e.g., no error in the map).
+ * Call the protocol specific routine (if any) to report
+ * any errors for each matching socket.
+ *
+ * Must be called at splnet.
+ */
+in_pcbnotify(head, dst, fport, laddr, lport, cmd, notify)
        struct inpcb *head;
        struct inpcb *head;
-       struct in_addr *fhost, *lhost;
+       struct sockaddr *dst;
        u_short fport, lport;
        u_short fport, lport;
+       struct in_addr laddr;
+       int cmd, (*notify)();
 {
 {
-       register struct inpcb *inp;
+       register struct inpcb *inp, *oinp;
+       struct in_addr faddr;
+       int errno;
+       int in_rtchange();
+       extern u_char inetctlerrmap[];
 
 
-       for (inp = head->inp_next; inp != head; inp = inp->inp_next)
-               if (inp->inp_fhost->h_addr.s_addr == fhost->s_addr &&
-                   inp->inp_fport == fport &&
-                   inp->inp_lhost->h_addr.s_addr == lhost->s_addr &&
-                   inp->inp_lport == lport)
-                       return (inp);
-       for (inp = head->inp_next; inp != head; inp = inp->inp_next)
-               if ((inp->inp_fhost->h_addr.s_addr == fhost->s_addr ||
-                    inp->inp_fhost == 0) &&
-                   (inp->inp_fport == fport || inp->inp_fport == 0) &&
-                    inp->inp_lhost->h_addr.s_addr == lhost->s_addr &&
-                   (inp->inp_lport == lport || inp->inp_lport == 0))
-                       return (inp);
-       return (0);
+       if ((unsigned)cmd > PRC_NCMDS || dst->sa_family != AF_INET)
+               return;
+       faddr = ((struct sockaddr_in *)dst)->sin_addr;
+       if (faddr.s_addr == INADDR_ANY)
+               return;
+
+       /*
+        * Redirects go to all references to the destination,
+        * and use in_rtchange to invalidate the route cache.
+        * Dead host indications: notify all references to the destination.
+        * Otherwise, if we have knowledge of the local port and address,
+        * deliver only to that socket.
+        */
+       if (PRC_IS_REDIRECT(cmd) || cmd == PRC_HOSTDEAD) {
+               fport = 0;
+               lport = 0;
+               laddr.s_addr = 0;
+               if (cmd != PRC_HOSTDEAD)
+                       notify = in_rtchange;
+       }
+       errno = inetctlerrmap[cmd];
+       for (inp = head->inp_next; inp != head;) {
+               if (inp->inp_faddr.s_addr != faddr.s_addr ||
+                   inp->inp_socket == 0 ||
+                   (lport && inp->inp_lport != lport) ||
+                   (laddr.s_addr && inp->inp_laddr.s_addr != laddr.s_addr) ||
+                   (fport && inp->inp_fport != fport)) {
+                       inp = inp->inp_next;
+                       continue;
+               }
+               oinp = inp;
+               inp = inp->inp_next;
+               if (notify)
+                       (*notify)(oinp, errno);
+       }
 }
 
 }
 
-in_pcbgenport(head)
-       struct inpcb *head;
+/*
+ * Check for alternatives when higher level complains
+ * about service problems.  For now, invalidate cached
+ * routing information.  If the route was created dynamically
+ * (by a redirect), time to try a default gateway again.
+ */
+in_losing(inp)
+       struct inpcb *inp;
 {
 {
+       register struct rtentry *rt;
+       struct rt_addrinfo info;
+
+       if ((rt = inp->inp_route.ro_rt)) {
+               inp->inp_route.ro_rt = 0;
+               bzero((caddr_t)&info, 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(RTM_LOSING, &info, rt->rt_flags, 0);
+               if (rt->rt_flags & RTF_DYNAMIC)
+                       (void) rtrequest(RTM_DELETE, rt_key(rt),
+                               rt->rt_gateway, rt_mask(rt), rt->rt_flags, 
+                               (struct rtentry **)0);
+               else 
+               /*
+                * A new route can be allocated
+                * the next time output is attempted.
+                */
+                       rtfree(rt);
+       }
+}
+
+/*
+ * After a routing change, flush old routing
+ * and allocate a (hopefully) better one.
+ */
+in_rtchange(inp)
        register struct inpcb *inp;
        register struct inpcb *inp;
+{
+       if (inp->inp_route.ro_rt) {
+               rtfree(inp->inp_route.ro_rt);
+               inp->inp_route.ro_rt = 0;
+               /*
+                * A new route can be allocated the next time
+                * output is attempted.
+                */
+       }
+}
+
+struct inpcb *
+in_pcblookup(head, faddr, fport, laddr, lport, flags)
+       struct inpcb *head;
+       struct in_addr faddr, laddr;
+       u_short fport, lport;
+       int flags;
+{
+       register struct inpcb *inp, *match = 0;
+       int matchwild = 3, wildcard;
 
 
-again:
-       if (head->inp_lport++ < 1024)
-               head->inp_lport = 1024;
-       for (inp = head->inp_next; inp != head; inp = inp->inp_next)
-               if (inp->inp_lport == head->inp_lport)
-                       goto again;
-       return (head->inp_lport);
+       for (inp = head->inp_next; inp != head; inp = inp->inp_next) {
+               if (inp->inp_lport != lport)
+                       continue;
+               wildcard = 0;
+               if (inp->inp_laddr.s_addr != INADDR_ANY) {
+                       if (laddr.s_addr == INADDR_ANY)
+                               wildcard++;
+                       else if (inp->inp_laddr.s_addr != laddr.s_addr)
+                               continue;
+               } else {
+                       if (laddr.s_addr != INADDR_ANY)
+                               wildcard++;
+               }
+               if (inp->inp_faddr.s_addr != INADDR_ANY) {
+                       if (faddr.s_addr == INADDR_ANY)
+                               wildcard++;
+                       else if (inp->inp_faddr.s_addr != faddr.s_addr ||
+                           inp->inp_fport != fport)
+                               continue;
+               } else {
+                       if (faddr.s_addr != INADDR_ANY)
+                               wildcard++;
+               }
+               if (wildcard && (flags & INPLOOKUP_WILDCARD) == 0)
+                       continue;
+               if (wildcard < matchwild) {
+                       match = inp;
+                       matchwild = wildcard;
+                       if (matchwild == 0)
+                               break;
+               }
+       }
+       return (match);
 }
 }