summaryrefslogtreecommitdiffstats
path: root/freebsd/sys/netinet/in.c
diff options
context:
space:
mode:
Diffstat (limited to 'freebsd/sys/netinet/in.c')
-rw-r--r--freebsd/sys/netinet/in.c189
1 files changed, 135 insertions, 54 deletions
diff --git a/freebsd/sys/netinet/in.c b/freebsd/sys/netinet/in.c
index b9231e13..9d84e82b 100644
--- a/freebsd/sys/netinet/in.c
+++ b/freebsd/sys/netinet/in.c
@@ -51,6 +51,7 @@ __FBSDID("$FreeBSD$");
#include <net/if.h>
#include <net/if_var.h>
+#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_llatbl.h>
#include <net/if_types.h>
@@ -71,7 +72,7 @@ static int in_lifaddr_ioctl(struct socket *, u_long, caddr_t,
struct ifnet *, struct thread *);
static int in_addprefix(struct in_ifaddr *, int);
-static int in_scrubprefix(struct in_ifaddr *);
+static int in_scrubprefix(struct in_ifaddr *, u_int);
static void in_socktrim(struct sockaddr_in *);
static int in_ifinit(struct ifnet *,
struct in_ifaddr *, struct sockaddr_in *, int);
@@ -91,6 +92,9 @@ SYSCTL_VNET_INT(_net_inet_ip, OID_AUTO, same_prefix_carp_only, CTLFLAG_RW,
VNET_DECLARE(struct inpcbinfo, ripcbinfo);
#define V_ripcbinfo VNET(ripcbinfo)
+VNET_DECLARE(struct arpstat, arpstat); /* ARP statistics, see if_arp.h */
+#define V_arpstat VNET(arpstat)
+
/*
* Return 1 if an internet address is for a ``local'' host
* (one to which we have a connection). If subnetsarelocal
@@ -340,7 +344,7 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
ifa_ref(&ia->ia_ifa);
IN_IFADDR_RUNLOCK();
if (ia == NULL) {
- IF_ADDR_LOCK(ifp);
+ IF_ADDR_RLOCK(ifp);
TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
iap = ifatoia(ifa);
if (iap->ia_addr.sin_family == AF_INET) {
@@ -354,7 +358,7 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
}
if (ia != NULL)
ifa_ref(&ia->ia_ifa);
- IF_ADDR_UNLOCK(ifp);
+ IF_ADDR_RUNLOCK(ifp);
}
if (ia == NULL)
iaIsFirst = 1;
@@ -418,9 +422,9 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
ia->ia_ifp = ifp;
ifa_ref(ifa); /* if_addrhead */
- IF_ADDR_LOCK(ifp);
+ IF_ADDR_WLOCK(ifp);
TAILQ_INSERT_TAIL(&ifp->if_addrhead, ifa, ifa_link);
- IF_ADDR_UNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
ifa_ref(ifa); /* in_ifaddrhead */
IN_IFADDR_WLOCK();
TAILQ_INSERT_TAIL(&V_in_ifaddrhead, ia, ia_link);
@@ -546,7 +550,7 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
* is the same as before, then the call is
* un-necessarily executed here.
*/
- in_ifscrub(ifp, ia);
+ in_ifscrub(ifp, ia, LLE_STATIC);
ia->ia_sockmask = ifra->ifra_mask;
ia->ia_sockmask.sin_family = AF_INET;
ia->ia_subnetmask =
@@ -555,7 +559,7 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
}
if ((ifp->if_flags & IFF_POINTOPOINT) &&
(ifra->ifra_dstaddr.sin_family == AF_INET)) {
- in_ifscrub(ifp, ia);
+ in_ifscrub(ifp, ia, LLE_STATIC);
ia->ia_dstaddr = ifra->ifra_dstaddr;
maskIsNew = 1; /* We lie; but the effect's the same */
}
@@ -583,7 +587,7 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
/*
* in_ifscrub kills the interface route.
*/
- in_ifscrub(ifp, ia);
+ in_ifscrub(ifp, ia, LLE_STATIC);
/*
* in_ifadown gets rid of all the rest of
@@ -600,7 +604,7 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
panic("in_control: unsupported ioctl");
}
- IF_ADDR_LOCK(ifp);
+ IF_ADDR_WLOCK(ifp);
/* Re-check that ia is still part of the list. */
TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
if (ifa == &ia->ia_ifa)
@@ -612,12 +616,12 @@ in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp,
* try it again for the next loop as there is no other exit
* path between here and out.
*/
- IF_ADDR_UNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
error = EADDRNOTAVAIL;
goto out;
}
TAILQ_REMOVE(&ifp->if_addrhead, &ia->ia_ifa, ifa_link);
- IF_ADDR_UNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
ifa_free(&ia->ia_ifa); /* if_addrhead */
IN_IFADDR_WLOCK();
@@ -718,7 +722,7 @@ in_lifaddr_ioctl(struct socket *so, u_long cmd, caddr_t data,
if (iflr->flags & IFLR_PREFIX)
return (EINVAL);
- /* copy args to in_aliasreq, perform ioctl(SIOCAIFADDR_IN6). */
+ /* copy args to in_aliasreq, perform ioctl(SIOCAIFADDR). */
bzero(&ifra, sizeof(ifra));
bcopy(iflr->iflr_name, ifra.ifra_name,
sizeof(ifra.ifra_name));
@@ -767,8 +771,9 @@ in_lifaddr_ioctl(struct socket *so, u_long cmd, caddr_t data,
}
}
+ IF_ADDR_RLOCK(ifp);
TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
- if (ifa->ifa_addr->sa_family != AF_INET6)
+ if (ifa->ifa_addr->sa_family != AF_INET)
continue;
if (match.s_addr == 0)
break;
@@ -777,6 +782,9 @@ in_lifaddr_ioctl(struct socket *so, u_long cmd, caddr_t data,
if (candidate.s_addr == match.s_addr)
break;
}
+ if (ifa != NULL)
+ ifa_ref(ifa);
+ IF_ADDR_RUNLOCK(ifp);
if (ifa == NULL)
return (EADDRNOTAVAIL);
ia = (struct in_ifaddr *)ifa;
@@ -795,12 +803,13 @@ in_lifaddr_ioctl(struct socket *so, u_long cmd, caddr_t data,
in_mask2len(&ia->ia_sockmask.sin_addr);
iflr->flags = 0; /*XXX*/
+ ifa_free(ifa);
return (0);
} else {
struct in_aliasreq ifra;
- /* fill in_aliasreq and do ioctl(SIOCDIFADDR_IN6) */
+ /* fill in_aliasreq and do ioctl(SIOCDIFADDR) */
bzero(&ifra, sizeof(ifra));
bcopy(iflr->iflr_name, ifra.ifra_name,
sizeof(ifra.ifra_name));
@@ -813,6 +822,7 @@ in_lifaddr_ioctl(struct socket *so, u_long cmd, caddr_t data,
}
bcopy(&ia->ia_sockmask, &ifra.ifra_dstaddr,
ia->ia_sockmask.sin_len);
+ ifa_free(ifa);
return (in_control(so, SIOCDIFADDR, (caddr_t)&ifra,
ifp, td));
@@ -827,10 +837,10 @@ in_lifaddr_ioctl(struct socket *so, u_long cmd, caddr_t data,
* Delete any existing route for an interface.
*/
void
-in_ifscrub(struct ifnet *ifp, struct in_ifaddr *ia)
+in_ifscrub(struct ifnet *ifp, struct in_ifaddr *ia, u_int flags)
{
- in_scrubprefix(ia);
+ in_scrubprefix(ia, flags);
}
/*
@@ -885,7 +895,7 @@ in_ifinit(struct ifnet *ifp, struct in_ifaddr *ia, struct sockaddr_in *sin,
splx(s);
if (scrub) {
ia->ia_ifa.ifa_addr = (struct sockaddr *)&oldaddr;
- in_ifscrub(ifp, ia);
+ in_ifscrub(ifp, ia, LLE_STATIC);
ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr;
}
if (IN_CLASSA(i))
@@ -949,7 +959,7 @@ in_ifinit(struct ifnet *ifp, struct in_ifaddr *ia, struct sockaddr_in *sin,
bzero(&ia_ro, sizeof(ia_ro));
*((struct sockaddr_in *)(&ia_ro.ro_dst)) = ia->ia_addr;
- rtalloc_ign_fib(&ia_ro, 0, 0);
+ rtalloc_ign_fib(&ia_ro, 0, RT_DEFAULT_FIB);
if ((ia_ro.ro_rt != NULL) && (ia_ro.ro_rt->rt_ifp != NULL) &&
(ia_ro.ro_rt->rt_ifp == V_loif)) {
RT_LOCK(ia_ro.ro_rt);
@@ -1035,7 +1045,7 @@ in_addprefix(struct in_ifaddr *target, int flags)
IN_IFADDR_RLOCK();
TAILQ_FOREACH(ia, &V_in_ifaddrhead, ia_link) {
if (rtinitflags(ia)) {
- p = ia->ia_addr.sin_addr;
+ p = ia->ia_dstaddr.sin_addr;
if (prefix.s_addr != p.s_addr)
continue;
@@ -1093,7 +1103,7 @@ extern void arp_ifscrub(struct ifnet *ifp, uint32_t addr);
* otherwise.
*/
static int
-in_scrubprefix(struct in_ifaddr *target)
+in_scrubprefix(struct in_ifaddr *target, u_int flags)
{
struct in_ifaddr *ia;
struct in_addr prefix, mask, p;
@@ -1124,17 +1134,22 @@ in_scrubprefix(struct in_ifaddr *target)
RT_LOCK(ia_ro.ro_rt);
if (ia_ro.ro_rt->rt_refcnt <= 1)
freeit = 1;
- else
+ else if (flags & LLE_STATIC) {
RT_REMREF(ia_ro.ro_rt);
+ target->ia_flags &= ~IFA_RTSELF;
+ }
RTFREE_LOCKED(ia_ro.ro_rt);
}
- if (freeit)
+ if (freeit && (flags & LLE_STATIC)) {
error = ifa_del_loopback_route((struct ifaddr *)target,
(struct sockaddr *)&target->ia_addr);
- if (error == 0)
- target->ia_flags &= ~IFA_RTSELF;
- /* remove arp cache */
- arp_ifscrub(target->ia_ifp, IA_SIN(target)->sin_addr.s_addr);
+ if (error == 0)
+ target->ia_flags &= ~IFA_RTSELF;
+ }
+ if ((flags & LLE_STATIC) &&
+ !(target->ia_ifp->if_flags & IFF_NOARP))
+ /* remove arp cache */
+ arp_ifscrub(target->ia_ifp, IA_SIN(target)->sin_addr.s_addr);
}
if (rtinitflags(target))
@@ -1159,7 +1174,8 @@ in_scrubprefix(struct in_ifaddr *target)
p.s_addr &= ia->ia_sockmask.sin_addr.s_addr;
}
- if (prefix.s_addr != p.s_addr)
+ if ((prefix.s_addr != p.s_addr) ||
+ !(ia->ia_ifp->if_flags & IFF_UP))
continue;
/*
@@ -1172,17 +1188,24 @@ in_scrubprefix(struct in_ifaddr *target)
* doesn't support such action.
*/
if ((ia->ia_flags & IFA_ROUTE) == 0
- && (ia->ia_ifp->if_type != IFT_CARP)
- ) {
+ && (ia->ia_ifp->if_type != IFT_CARP)) {
+ ifa_ref(&ia->ia_ifa);
IN_IFADDR_RUNLOCK();
- rtinit(&(target->ia_ifa), (int)RTM_DELETE,
+ error = rtinit(&(target->ia_ifa), (int)RTM_DELETE,
rtinitflags(target));
- target->ia_flags &= ~IFA_ROUTE;
-
+ if (error == 0)
+ target->ia_flags &= ~IFA_ROUTE;
+ else
+ log(LOG_INFO, "in_scrubprefix: err=%d, old prefix delete failed\n",
+ error);
error = rtinit(&ia->ia_ifa, (int)RTM_ADD,
rtinitflags(ia) | RTF_UP);
if (error == 0)
ia->ia_flags |= IFA_ROUTE;
+ else
+ log(LOG_INFO, "in_scrubprefix: err=%d, new prefix add failed\n",
+ error);
+ ifa_free(&ia->ia_ifa);
return (error);
}
}
@@ -1199,15 +1222,18 @@ in_scrubprefix(struct in_ifaddr *target)
mask0.sin_len = sizeof(mask0);
mask0.sin_family = AF_INET;
mask0.sin_addr.s_addr = target->ia_subnetmask;
- lltable_prefix_free(AF_INET, (struct sockaddr *)&prefix0,
- (struct sockaddr *)&mask0);
+ lltable_prefix_free(AF_INET, (struct sockaddr *)&prefix0,
+ (struct sockaddr *)&mask0, flags);
/*
* As no-one seem to have this prefix, we can remove the route.
*/
- rtinit(&(target->ia_ifa), (int)RTM_DELETE, rtinitflags(target));
- target->ia_flags &= ~IFA_ROUTE;
- return (0);
+ error = rtinit(&(target->ia_ifa), (int)RTM_DELETE, rtinitflags(target));
+ if (error == 0)
+ target->ia_flags &= ~IFA_ROUTE;
+ else
+ log(LOG_INFO, "in_scrubprefix: err=%d, prefix delete failed\n", error);
+ return (error);
}
#undef rtinitflags
@@ -1286,7 +1312,7 @@ in_purgemaddrs(struct ifnet *ifp)
* We need to do this as IF_ADDR_LOCK() may be re-acquired
* by code further down.
*/
- IF_ADDR_LOCK(ifp);
+ IF_ADDR_RLOCK(ifp);
TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
if (ifma->ifma_addr->sa_family != AF_INET ||
ifma->ifma_protospec == NULL)
@@ -1298,7 +1324,7 @@ in_purgemaddrs(struct ifnet *ifp)
inm = (struct in_multi *)ifma->ifma_protospec;
LIST_INSERT_HEAD(&purgeinms, inm, inm_link);
}
- IF_ADDR_UNLOCK(ifp);
+ IF_ADDR_RUNLOCK(ifp);
LIST_FOREACH_SAFE(inm, &purgeinms, inm_link, tinm) {
LIST_REMOVE(inm, inm_link);
@@ -1359,25 +1385,33 @@ in_lltable_free(struct lltable *llt, struct llentry *lle)
static void
in_lltable_prefix_free(struct lltable *llt,
const struct sockaddr *prefix,
- const struct sockaddr *mask)
+ const struct sockaddr *mask,
+ u_int flags)
{
const struct sockaddr_in *pfx = (const struct sockaddr_in *)prefix;
const struct sockaddr_in *msk = (const struct sockaddr_in *)mask;
struct llentry *lle, *next;
register int i;
+ size_t pkts_dropped;
for (i=0; i < LLTBL_HASHTBL_SIZE; i++) {
LIST_FOREACH_SAFE(lle, &llt->lle_head[i], lle_next, next) {
- if (IN_ARE_MASKED_ADDR_EQUAL((struct sockaddr_in *)L3_ADDR(lle),
- pfx, msk)) {
+ /*
+ * (flags & LLE_STATIC) means deleting all entries
+ * including static ARP entries
+ */
+ if (IN_ARE_MASKED_ADDR_EQUAL((struct sockaddr_in *)L3_ADDR(lle),
+ pfx, msk) &&
+ ((flags & LLE_STATIC) || !(lle->la_flags & LLE_STATIC))) {
int canceled;
canceled = callout_drain(&lle->la_timer);
LLE_WLOCK(lle);
if (canceled)
LLE_REMREF(lle);
- llentry_free(lle);
+ pkts_dropped = llentry_free(lle);
+ ARPSTAT_ADD(dropped, pkts_dropped);
}
}
}
@@ -1394,19 +1428,68 @@ in_lltable_rtcheck(struct ifnet *ifp, u_int flags, const struct sockaddr *l3addr
/* XXX rtalloc1 should take a const param */
rt = rtalloc1(__DECONST(struct sockaddr *, l3addr), 0, 0);
- if (rt == NULL || (!(flags & LLE_PUB) &&
- ((rt->rt_flags & RTF_GATEWAY) ||
- (rt->rt_ifp != ifp)))) {
+
+ if (rt == NULL)
+ return (EINVAL);
+
+ /*
+ * If the gateway for an existing host route matches the target L3
+ * address, which is a special route inserted by some implementation
+ * such as MANET, and the interface is of the correct type, then
+ * allow for ARP to proceed.
+ */
+ if (rt->rt_flags & RTF_GATEWAY) {
+ if (!(rt->rt_flags & RTF_HOST) || !rt->rt_ifp ||
+ rt->rt_ifp->if_type != IFT_ETHER ||
+ (rt->rt_ifp->if_flags &
+ (IFF_NOARP | IFF_STATICARP)) != 0 ||
+ memcmp(rt->rt_gateway->sa_data, l3addr->sa_data,
+ sizeof(in_addr_t)) != 0) {
+ RTFREE_LOCKED(rt);
+ return (EINVAL);
+ }
+ }
+
+ /*
+ * Make sure that at least the destination address is covered
+ * by the route. This is for handling the case where 2 or more
+ * interfaces have the same prefix. An incoming packet arrives
+ * on one interface and the corresponding outgoing packet leaves
+ * another interface.
+ */
+ if (!(rt->rt_flags & RTF_HOST) && rt->rt_ifp != ifp) {
+ const char *sa, *mask, *addr, *lim;
+ int len;
+
+ mask = (const char *)rt_mask(rt);
+ /*
+ * Just being extra cautious to avoid some custom
+ * code getting into trouble.
+ */
+ if (mask == NULL) {
+ RTFREE_LOCKED(rt);
+ return (EINVAL);
+ }
+
+ sa = (const char *)rt_key(rt);
+ addr = (const char *)l3addr;
+ len = ((const struct sockaddr_in *)l3addr)->sin_len;
+ lim = addr + len;
+
+ for ( ; addr < lim; sa++, mask++, addr++) {
+ if ((*sa ^ *addr) & *mask) {
#ifdef DIAGNOSTIC
- log(LOG_INFO, "IPv4 address: \"%s\" is not on the network\n",
- inet_ntoa(((const struct sockaddr_in *)l3addr)->sin_addr));
+ log(LOG_INFO, "IPv4 address: \"%s\" is not on the network\n",
+ inet_ntoa(((const struct sockaddr_in *)l3addr)->sin_addr));
#endif
- if (rt != NULL)
- RTFREE_LOCKED(rt);
- return (EINVAL);
+ RTFREE_LOCKED(rt);
+ return (EINVAL);
+ }
+ }
}
+
RTFREE_LOCKED(rt);
- return 0;
+ return (0);
}
/*
@@ -1576,10 +1659,8 @@ in_domifattach(struct ifnet *ifp)
llt = lltable_init(ifp, AF_INET);
if (llt != NULL) {
- llt->llt_new = in_lltable_new;
llt->llt_free = in_lltable_free;
llt->llt_prefix_free = in_lltable_prefix_free;
- llt->llt_rtcheck = in_lltable_rtcheck;
llt->llt_lookup = in_lltable_lookup;
llt->llt_dump = in_lltable_dump;
}