/*
* Copyright (c) 2001-2002 Packet Design, LLC.
* All rights reserved.
*
* Subject to the following obligations and disclaimer of warranty,
* use and redistribution of this software, in source or object code
* forms, with or without modifications are expressly permitted by
* Packet Design; provided, however, that:
*
* (i) Any and all reproductions of the source or object code
* must include the copyright notice above and the following
* disclaimer of warranties; and
* (ii) No rights are granted, in any manner or form, to use
* Packet Design trademarks, including the mark "PACKET DESIGN"
* on advertising, endorsements, or otherwise except as such
* appears in the above copyright notice or in the software.
*
* THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
* TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
* REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
* THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
* OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
* OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
* OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
* RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
* LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
* OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
* DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
* USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Archie Cobbs <archie@freebsd.org>
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/sockio.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include "structs/structs.h"
#include "structs/type/array.h"
#include "net/if_util.h"
#include "util/typed_mem.h"
#define ROUNDUP(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
#define ADDADDR(cp, s) \
do { \
memcpy(cp, &s, sizeof(s)); \
cp += ROUNDUP(sizeof(s)); \
} while (0)
#define TEMP_EXPIRE (20 * 60)
struct rt {
struct rt_msghdr m_rtm;
char m_space[512];
};
/*
* Internal functions
*/
static int arp_set(int sock, struct in_addr ip,
const u_char *ether, int temp, int publish);
static int arp_delete(int sock, struct in_addr ip);
static int arp_rtmsg(int sock, struct rt *rt, struct sockaddr_inarp *sin,
struct sockaddr_dl *sdl, int cmd, int flags,
int export_only, int doing_proxy, u_long expiry);
/*
* Internal variables
*/
static const struct sockaddr_in so_mask = { 8, 0, 0, { 0xffffffff} };
static const struct sockaddr_inarp zero_sin = { sizeof(zero_sin), AF_INET };
static const struct sockaddr_dl zero_sdl = { sizeof(zero_sdl), AF_LINK };
/*
* Get an ARP entry.
*/
int
if_get_arp(struct in_addr ip, u_char *ether)
{
int mib[6];
size_t needed;
char *lim, *buf, *next;
struct rt_msghdr *rtm;
struct sockaddr_inarp *sin;
struct sockaddr_dl *sdl;
/* Get ARP table */
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_FLAGS;
#ifdef RTF_LLINFO
mib[5] = RTF_LLINFO;
#else
mib[5] = 0;
#endif
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
return (-1);
needed += 128;
if ((buf = MALLOC(TYPED_MEM_TEMP, needed)) == NULL)
return (-1);
if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
FREE(TYPED_MEM_TEMP, buf);
return (-1);
}
/* Find desired entry */
lim = buf + needed;
for (next = buf; next < lim; next += rtm->rtm_msglen) {
rtm = (struct rt_msghdr *)(void *)next;
sin = (struct sockaddr_inarp *)(rtm + 1);
sdl = (struct sockaddr_dl *)(void *)
((char *)sin + ROUNDUP(sin->sin_len));
if (sin->sin_addr.s_addr != ip.s_addr)
continue;
if (sdl->sdl_alen == 0)
break;
memcpy(ether, LLADDR(sdl), ETHER_ADDR_LEN);
FREE(TYPED_MEM_TEMP, buf);
return (0);
}
/* Not found */
FREE(TYPED_MEM_TEMP, buf);
errno = ENOENT;
return (-1);
}
/*
* Set or remove an ARP entry.
*/
int
if_set_arp(struct in_addr ip, const u_char *ether, int temp, int publish)
{
int ret = -1;
int sock;
/* Get socket */
if ((sock = socket(PF_ROUTE, SOCK_RAW, 0)) == -1)
return (-1);
/* Delete any existing entries */
while ((ret = arp_delete(sock, ip)) != -1);
if (errno != ENOENT)
goto done;
/* If not setting a new one, done */
if (ether == NULL) {
ret = 0;
goto done;
}
/* Set a new one */
ret = arp_set(sock, ip, ether, temp, publish);
done:
/* Done */
(void)close(sock);
return (ret);
}
/*
* Set an individual arp entry
*/
static int
arp_set(int sock, struct in_addr ip, const u_char *ether, int temp, int publish)
{
struct rt m_rtmsg;
struct sockaddr_inarp sin_m;
struct sockaddr_dl sdl_m;
struct sockaddr_inarp *sin = &sin_m;
struct rt_msghdr *const rtm = &m_rtmsg.m_rtm;
struct sockaddr_dl *sdl;
int doing_proxy;
int export_only;
int expiry;
int flags;
sdl_m = zero_sdl;
sin_m = zero_sin;
sin->sin_addr = ip;
doing_proxy = flags = export_only = expiry = 0;
if (temp)
expiry = time(NULL) + TEMP_EXPIRE;
if (publish) {
flags |= RTF_ANNOUNCE;
doing_proxy = SIN_PROXY;
}
memcpy(LLADDR(&sdl_m), ether, ETHER_ADDR_LEN);
sdl_m.sdl_alen = 6;
tryagain:
if (arp_rtmsg(sock, &m_rtmsg, &sin_m, &sdl_m,
RTM_GET, flags, export_only, doing_proxy, expiry) < 0)
return (-1);
sin = (struct sockaddr_inarp *)(rtm + 1);
sdl = (struct sockaddr_dl *)(void *)
(ROUNDUP(sin->sin_len) + (char *)sin);
if (sin->sin_addr.s_addr == sin_m.sin_addr.s_addr) {
if (sdl->sdl_family == AF_LINK &&
#ifdef RTF_LLINFO
(rtm->rtm_flags & RTF_LLINFO) != 0 &&
#endif
(rtm->rtm_flags & RTF_GATEWAY) == 0) {
switch (sdl->sdl_type) {
case IFT_ETHER:
case IFT_FDDI:
case IFT_ISO88023:
case IFT_ISO88024:
case IFT_ISO88025:
case IFT_L2VLAN:
goto overwrite;
default:
break;
}
}
if (doing_proxy == 0) {
errno = EINVAL;
return (-1);
}
if (sin_m.sin_other & SIN_PROXY) {
errno = EINVAL;
return (-1);
}
sin_m.sin_other = SIN_PROXY;
export_only = 1;
goto tryagain;
}
overwrite:
if (sdl->sdl_family != AF_LINK) {
errno = ENOENT;
return (-1);
}
sdl_m.sdl_type = sdl->sdl_type;
sdl_m.sdl_index = sdl->sdl_index;
return (arp_rtmsg(sock, &m_rtmsg, &sin_m, &sdl_m,
RTM_ADD, flags, export_only, doing_proxy, expiry));
}
/*
* Delete an arp entry
*/
static int
arp_delete(int sock, struct in_addr ip)
{
struct rt m_rtmsg;
struct sockaddr_inarp sin_m;
struct sockaddr_dl sdl_m;
struct sockaddr_inarp *sin = &sin_m;
struct rt_msghdr *const rtm = &m_rtmsg.m_rtm;
struct sockaddr_dl *sdl;
sdl_m = zero_sdl;
sin_m = zero_sin;
sin->sin_addr = ip;
tryagain:
if (arp_rtmsg(sock, &m_rtmsg, &sin_m, &sdl_m, RTM_GET, 0, 0, 0, 0) < 0)
return (-1);
sin = (struct sockaddr_inarp *)(rtm + 1);
sdl = (struct sockaddr_dl *)(void *)
(ROUNDUP(sin->sin_len) + (char *)sin);
if (sdl->sdl_family == AF_LINK &&
#ifdef RTF_LLINFO
(rtm->rtm_flags & RTF_LLINFO) &&
#endif
!(rtm->rtm_flags & RTF_GATEWAY)) switch (sdl->sdl_type) {
case IFT_ETHER: case IFT_FDDI: case IFT_ISO88023:
case IFT_ISO88024: case IFT_ISO88025:
sin->sin_addr.s_addr = sin_m.sin_addr.s_addr;
goto delete;
}
if (sin_m.sin_other & SIN_PROXY) {
errno = ENOENT;
return (-1);
}
sin_m.sin_other = SIN_PROXY;
goto tryagain;
delete:
if (sdl->sdl_family != AF_LINK) {
errno = ENOENT;
return (-1);
}
if (arp_rtmsg(sock, &m_rtmsg, &sin_m, &sdl_m,
RTM_DELETE, 0, 0, 0, 0) == -1)
return (-1);
return (0);
}
static int
arp_rtmsg(int sock, struct rt *rt, struct sockaddr_inarp *sin,
struct sockaddr_dl *sdl, int cmd, int flags,
int export_only, int doing_proxy, u_long expiry)
{
struct rt_msghdr *const rtm = &rt->m_rtm;
const pid_t pid = getpid();
u_char *cp = (u_char *)rt->m_space;
static int seq;
int l;
int rlen;
if (cmd == RTM_DELETE)
goto doit;
memset(rtm, 0, sizeof(*rtm));
rtm->rtm_flags = flags;
rtm->rtm_version = RTM_VERSION;
switch (cmd) {
case RTM_ADD:
rtm->rtm_addrs |= RTA_GATEWAY;
rtm->rtm_rmx.rmx_expire = expiry;
rtm->rtm_inits = RTV_EXPIRE;
rtm->rtm_flags |= (RTF_HOST | RTF_STATIC);
sin->sin_other = 0;
if (doing_proxy) {
if (export_only)
sin->sin_other = SIN_PROXY;
else {
rtm->rtm_addrs |= RTA_NETMASK;
rtm->rtm_flags &= ~RTF_HOST;
}
}
/* FALLTHROUGH */
case RTM_GET:
rtm->rtm_addrs |= RTA_DST;
break;
default:
assert(0);
}
#define NEXTADDR(w, s) \
if (rtm->rtm_addrs & (w)) { \
memcpy(cp, s, sizeof(*s)); cp += ROUNDUP(sizeof(*s));}
NEXTADDR(RTA_DST, sin);
NEXTADDR(RTA_GATEWAY, sdl);
NEXTADDR(RTA_NETMASK, &so_mask);
rtm->rtm_msglen = cp - (u_char *)rtm;
doit:
l = rtm->rtm_msglen;
rtm->rtm_seq = ++seq;
rtm->rtm_type = cmd;
if ((rlen = write(sock, (char *)rt, l)) < 0) {
if (errno != ESRCH || cmd != RTM_DELETE)
return (-1);
}
do {
l = read(sock, (char *)rt, sizeof(*rt));
} while (l > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != pid));
if (l < 0)
return (-1);
return (0);
}
/*
* Flush all ARP entries.
*/
int
if_flush_arp(void)
{
int errno_save = errno;
int mib[6];
size_t needed;
char *lim, *buf, *next;
struct rt_msghdr *rtm;
struct sockaddr_inarp *sin;
struct sockaddr_dl *sdl;
int sock, rtn = -1;
/* Get socket */
if ((sock = socket(PF_ROUTE, SOCK_RAW, 0)) == -1)
return (-1);
/* Get ARP table */
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[4] = NET_RT_FLAGS;
#ifdef RTF_LLINFO
mib[5] = RTF_LLINFO;
#else
mib[5] = 0;
#endif
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
goto done;
needed += 128;
if ((buf = MALLOC(TYPED_MEM_TEMP, needed)) == NULL)
goto done;
if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
goto done2;
}
/* Find desired entry */
lim = buf + needed;
for (next = buf; next < lim; next += rtm->rtm_msglen) {
rtm = (struct rt_msghdr *)(void *)next;
sin = (struct sockaddr_inarp *)(rtm + 1);
sdl = (struct sockaddr_dl *)(void *)
((char *)sin + ROUNDUP(sin->sin_len));
if (sdl->sdl_alen == 0)
break;
arp_delete(sock, sin->sin_addr);
}
rtn = 0;
done2:
FREE(TYPED_MEM_TEMP, buf);
done:
(void)close(sock);
errno = errno_save;
return (rtn);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>