/* $Id: synfp.C,v 1.24 2005/10/19 23:52:26 dm Exp $ */
/*
*
* Copyright (C) 2003 David Mazieres (dm@uun.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*/
#include "async.h"
#include "serial.h"
#include "parseopt.h"
#include "rawnet.h"
#if USE_SYNFP
#include <net/if.h>
#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif /* HAVE_SYS_SOCKIO_H */
#ifndef HAVE_BPF_U_INT32
typedef u_int32_t bpf_u_int32;
#endif /* !HAVE_BPF_U_INT32 */
#ifndef HAVE_PCAP_FREECODE
#define pcap_freecode(x)
#endif /* !HAVE_PCAP_FREECODE */
bool
synfp::pktok (const u_char *ip, const u_char *e)
{
if (ip + 20 > e || ip[0]>>4 != 4 || ip[9] != IPPROTO_TCP)
return false;
u_int len = (ip[0] & 0xf) << 2;
if (len < 20 || ip + len > e)
return false;
return cksum (ip, len) == 0xffff;
}
bool
synfp::ifnames (vec<str> *ifs, in_addr targ)
{
int s;
if ((s = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
warn ("socket: %m\n");
return false;
}
char buf[0x10000];
ifconf ifc;
ifc.ifc_buf = buf;
ifc.ifc_len = sizeof (buf);
if (ioctl (s, SIOCGIFCONF, &ifc) < 0) {
warn ("SIOCGIFCONF: %m\n");
::close (s);
return false;
}
/* The +64 is for large addresses (e.g., IPv6), in which sa_len
* may be greater than the struct sockaddr inside ifreq. */
if (ifc.ifc_len + sizeof (struct ifreq) + 64 > sizeof (buf)) {
warn ("SIOCGIFCONF: result too large\n");
::close (s);
return false;
}
bhash<str> seen;
for (str *sp = ifs->base (); sp < ifs->lim (); sp++)
seen.insert (*sp);
char *p = ifc.ifc_buf, *e = p + ifc.ifc_len;
while (p < e) {
struct ifreq *ifrp = (struct ifreq *) p;
struct ifreq ifr = *ifrp;
#ifndef HAVE_SA_LEN
p += sizeof (ifr);
#else /* !HAVE_SA_LEN */
p += sizeof (ifrp->ifr_name)
+ max (sizeof (ifrp->ifr_addr), (size_t) ifrp->ifr_addr.sa_len);
#endif /* !HAVE_SA_LEN */
if (ifrp->ifr_addr.sa_family != AF_INET)
continue;
in_addr a = ((struct sockaddr_in *) &ifrp->ifr_addr)->sin_addr;
if (targ.s_addr != htonl (INADDR_ANY)) {
if (targ.s_addr != a.s_addr)
continue;
}
else if (a.s_addr == htonl (INADDR_LOOPBACK))
continue;
str ifn (strbuf ("%.*s", (int) sizeof (ifr.ifr_name), ifr.ifr_name));
if (ioctl (s, SIOCGIFFLAGS, &ifr) < 0) {
warn ("SIOCGIFFLAGS (%s): %m\n", ifn.cstr ());
continue;
}
if ((ifr.ifr_flags & IFF_UP) && seen.insert (ifn))
ifs->push_back (ifn);
}
::close (s);
return true;
}
bool
synfp::ifaddrs (vec<in_addr> *addrs, str ifname)
{
int s;
if ((s = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
warn ("socket: %m\n");
return false;
}
char buf[0x10000];
ifconf ifc;
ifc.ifc_buf = buf;
ifc.ifc_len = sizeof (buf);
if (ioctl (s, SIOCGIFCONF, &ifc) < 0) {
warn ("SIOCGIFCONF: %m\n");
::close (s);
return false;
}
/* The +64 is for large addresses (e.g., IPv6), in which sa_len
* may be greater than the struct sockaddr inside ifreq. */
if (ifc.ifc_len + sizeof (struct ifreq) + 64 > sizeof (buf)) {
warn ("SIOCGIFCONF: result too large\n");
::close (s);
return false;
}
bhash<in_addr> seen;
for (in_addr *ap = addrs->base (); ap < addrs->lim (); ap++)
seen.insert (*ap);
char *p = ifc.ifc_buf, *e = p + ifc.ifc_len;
while (p < e) {
struct ifreq *ifrp = (struct ifreq *) p;
struct ifreq ifr = *ifrp;
#ifndef HAVE_SA_LEN
p += sizeof (ifr);
#else /* !HAVE_SA_LEN */
p += sizeof (ifrp->ifr_name)
+ max (sizeof (ifrp->ifr_addr), (size_t) ifrp->ifr_addr.sa_len);
#endif /* !HAVE_SA_LEN */
if (ifrp->ifr_addr.sa_family != AF_INET)
continue;
str ifn (strbuf ("%.*s", (int) sizeof (ifr.ifr_name), ifr.ifr_name));
if (ifname && ifn != ifname)
continue;
in_addr a = ((struct sockaddr_in *) &ifrp->ifr_addr)->sin_addr;
if (ioctl (s, SIOCGIFFLAGS, &ifr) < 0) {
warn ("SIOCGIFFLAGS (%s): %m\n", ifn.cstr ());
continue;
}
if ((ifr.ifr_flags & IFF_UP) && seen.insert (a))
addrs->push_back (a);
}
::close (s);
return true;
}
synfp::synfp ()
: pch (NULL), hdrlen (-1), fd (-1)
{
}
synfp::~synfp ()
{
close ();
}
void
synfp::close ()
{
if (fd >= 0) {
fdcb (fd, selread, NULL);
fd = -1;
}
if (pch) {
pcap_close (pch);
pch = NULL;
}
hdrlen = -1;
}
bool
synfp::setfilter (str dev, str filstr)
{
close ();
char *cdev;
char errbuf[PCAP_ERRBUF_SIZE];
if (dev)
cdev = const_cast<char *> (dev.cstr ());
else if (!(cdev = pcap_lookupdev (errbuf))) {
warn ("pcap_lookupdev: %s\n", errbuf);
return false;
}
bpf_u_int32 net, mask;
if (pcap_lookupnet (cdev, &net, &mask, errbuf) < 0) {
warn ("pcap_lookupnet: %s\n", errbuf);
return false;
}
// XXX - this kind of sucks, but 1ms is smallest timeout
pch = pcap_open_live (cdev, 150, 0, 1, errbuf);
if (!pch) {
warn ("pcap_open_live: %s\n", errbuf);
return false;
}
#if SYNFP_DEBUG
warn ("filter: %s\n", filstr.cstr ());
#endif /* SYNFP_DEBUG */
bpf_program filter;
if (pcap_compile (pch, &filter, const_cast<char *> (filstr.cstr ()),
1, mask) < 0) {
warn ("failed to compile PCAP filter: %s\n", filstr.cstr ());
pcap_close (pch);
pch = NULL;
return false;
}
if (pcap_setfilter (pch, &filter)) {
warn ("failed to set PCAP filter\n");
pcap_freecode (&filter);
pcap_close (pch);
pch = NULL;
return false;
}
pcap_freecode (&filter);
fd = pcap_fileno (pch);
return true;
}
bool
synfp::init (str dev, in_addr addr, int port)
{
strbuf sb;
sb << "tcp[13] & 0x12 == 0x2";
if (port)
sb << " and dst port " << port;
if (addr.s_addr != htonl (INADDR_ANY))
sb << " and dst host " << inet_ntoa (addr);
else {
vec<in_addr> av;
if (myipaddrs (&av) && !av.empty ()) {
sb << " and (dst host " << inet_ntoa (av.pop_front ());
while (!av.empty ())
sb << " or dst host " << inet_ntoa (av.pop_front ());
sb << ")";
}
}
return setfilter (dev, sb);
}
bool
synfp::init (str dev, const vec<sockaddr_in> &addrs)
{
vec<in_addr> lav;
if ((dev && !ifaddrs (&lav, dev))
|| (!dev && !myipaddrs (&lav)))
return false;
bhash<in_addr> local;
for (in_addr *ap = lav.base (); ap < lav.lim (); ap++)
local.insert (*ap);
bhash<sockaddr_in> seen;
bhash<u_int16_t> seen_port;
bool any = false;
strbuf sb;
sb << "tcp[13] & 0x12 == 0x2 and (";
for (const sockaddr_in *ap = addrs.base (); ap < addrs.lim (); ap++) {
if (ap->sin_addr.s_addr != htonl (INADDR_ANY))
continue;
if (!seen.insert (*ap))
continue;
seen_port.insert (ntohs (ap->sin_port));
if (any)
sb << " or ";
else
any = true;
sb.fmt ("dst port %d", ntohs (ap->sin_port));
}
for (const sockaddr_in *ap = addrs.base (); ap < addrs.lim (); ap++) {
if (seen_port[ntohs (ap->sin_port)] || !seen.insert (*ap)
|| !local[ap->sin_addr])
continue;
if (any)
sb << " or ";
else
any = true;
sb.fmt ("(dst port %d and dst host ", ntohs (ap->sin_port));
sb << inet_ntoa (ap->sin_addr);
sb << ")";
}
sb << ")";
if (!any)
return false;
return setfilter (dev, sb);
}
int
synfp::getfp (str *fp, sockaddr_in *sinp, sockaddr_in *dsinp)
{
pcap_pkthdr hdr;
const u_char *buf = pcap_next (pch, &hdr);
if (!buf)
return -1;
const u_char *e = buf + hdr.caplen;
if (!sethdrlen (buf, e))
return false;
const u_char *ip = buf + hdrlen;
const u_char *tcp = ip + ((ip[0] & 0xf) << 2);
if (tcp + 20 > e)
return 0;
else {
const u_char *tcpe = tcp + ((tcp[12] & 0xf0) >> 2);
if (tcpe > e)
return 0;
e = tcpe;
}
sinp->sin_family = AF_INET;
memcpy (&sinp->sin_addr, ip + 12, 4);
memcpy (&sinp->sin_port, tcp, 2);
if (dsinp) {
dsinp->sin_family = AF_INET;
memcpy (&dsinp->sin_addr, ip + 16, 4);
memcpy (&dsinp->sin_port, tcp + 2, 2);
}
strbuf sb ("%d:%d:%d:%d:", getshort (tcp + 14), ip[8],
!!(ip[6] & 0x40), getshort (ip + 2));
const u_char *op = tcp + 20;
const char *sep = "";
while (op < e) {
if (*op > 1 && (op + 2 > e || op + op[1] > e || op[1] < 2))
goto done;
switch (*op) {
case 0:
goto done;
case 1:
sb << sep << "N";
sep = ",";
op++;
continue;
case 2:
if (op[1] != 4)
goto done;
sb.fmt ("%sM%d", sep, getshort (op + 2));
sep = ",";
break;
case 3:
if (op[1] != 3)
goto done;
sb.fmt ("%sW%d", sep, op[2]);
sep = ",";
break;
case 4:
if (op[1] != 2)
goto done;
sb << sep << "S";
sep = ",";
break;
case 8:
if (op[1] != 10)
goto done;
sb << sep << "T";
if (!getint (op + 2))
sb << "0";
sep = ",";
break;
}
op += op[1];
}
done:
*fp = sb;
return true;
}
bool
synfp::sethdrlen (const u_char *pkt, const u_char *e)
{
if (hdrlen >= 0 && pktok (pkt + hdrlen, e))
return true;
int newlen = -1;
int dlt = pcap_datalink (pch);
switch (pcap_datalink (pch)) {
case DLT_EN10MB:
newlen = 14;
break;
}
if (newlen >= 0) {
if (newlen == hdrlen)
return false;
hdrlen = newlen;
return pktok (pkt + hdrlen, e);
}
for (const u_char *ip = pkt; ip + 40 <= e; ip++)
if (pktok (ip, e)) {
hdrlen = ip - pkt;
warn ("guessing %d-byte header for unknown datalink type %d\n",
hdrlen, dlt);
return true;
}
return false;
}
synfp_collect::synfp_collect (u_int msec, u_int bs)
: tmo (NULL), bufsize (bs)
{
extern int fd_set_bytes;
delay.tv_sec = msec / 1000;
delay.tv_nsec = (msec % 1000) * 1000000;
fds = static_cast<fd_set *> (xmalloc (fd_set_bytes));
bzero (fds, fd_set_bytes);
}
synfp_collect::~synfp_collect ()
{
for (cbentry_t *cbe = cbs.tab.first (); cbe; cbe = cbs.tab.next (cbe))
(*cbe->val.cb) (NULL);
if (tmo)
timecb_remove (tmo);
xfree (fds);
}
bool
synfp_collect::init (const sockaddr_in &sin)
{
pfv.clear ();
vec<str> ifnames;
synfp::ifnames (&ifnames, sin.sin_addr);
if (ifnames.empty ())
ifnames.push_back (NULL);
for (str *sp = ifnames.base (); sp < ifnames.lim (); sp++) {
if (*sp)
warn << "listening for SYN fingerprints on " << *sp << "\n";
else
warn << "listening for SYN fingerprints on default pcap interface\n";
ref<synfp> s = New refcounted<synfp>;
if (!s->init (*sp, sin.sin_addr, ntohs (sin.sin_port)))
continue;
pfv.push_back (s);
fdcb (s->fd, selread, wrap (this, &synfp_collect::input, pfv.size () - 1));
}
return !pfv.empty ();
}
bool
synfp_collect::init (const vec<sockaddr_in> &av)
{
pfv.clear ();
in_addr inaddr_any;
inaddr_any.s_addr = htonl (INADDR_ANY);
vec<str> ifnames;
synfp::ifnames (&ifnames, inaddr_any);
if (ifnames.empty ())
ifnames.push_back (NULL);
for (str *sp = ifnames.base (); sp < ifnames.lim (); sp++) {
if (*sp)
warn << "listening for SYN fingerprints on " << *sp << "\n";
else
warn << "listening for SYN fingerprints on default pcap interface\n";
ref<synfp> s = New refcounted<synfp>;
if (!s->init (*sp, av))
continue;
pfv.push_back (s);
fdcb (s->fd, selread, wrap (this, &synfp_collect::input, pfv.size () - 1));
}
return !pfv.empty ();
}
void
synfp_collect::input (int i)
{
for (int j = 0; j < 5; j++) {
str fp;
sockaddr_in sin;
int res = pfv[i]->getfp (&fp, &sin);
if (res < 0)
return;
else if (!res)
continue;
#if SYNFP_DEBUG
warn ("synfp-input %s:%d %s\n", inet_ntoa (sin.sin_addr),
ntohs (sin.sin_port), fp.cstr ());
#endif
bool found = false;
for (cbentry_t *cbe = cbs.tab[sin], *ncbe; cbe; cbe = ncbe) {
ncbe = cbs.tab.nextkeq (cbe);
found = true;
::cbs cb = cbe->val.cb;
cbs.dealloc (cbe);
(*cb) (fp);
}
fps.insert (sin, fp);
while (fps.size > bufsize) {
fpentry_t *fpe = fps.lru.first;
#if SYNFP_DEBUG
warn ("synfp-delete %s:%d %s (#%d)\n", inet_ntoa (fpe->sin.sin_addr),
ntohs (fpe->sin.sin_port), fpe->val.cstr (), fps.size);
#endif
fps.dealloc (fpe);
}
/* Check for readability, because (at least on OpenBSD) pcap can
* wait longer than than the to_ms arg of pcap_open_live. */
static timeval ztv;
FD_SET (pfv[i]->fd, fds);
res = select (pfv[i]->fd + 1, fds, NULL, NULL, &ztv);
FD_CLR (pfv[i]->fd, fds);
if (res < 1)
break;
}
}
void
synfp_collect::service ()
{
timespec old = tsnow - delay;
cbentry_t *cbe;
while ((cbe = cbs.lru.first) && cbe->val.ts < old) {
(*cbe->val.cb) (NULL);
cbs.dealloc (cbe);
}
if (cbe && !tmo)
tmo = delaycb (delay.tv_sec, delay.tv_nsec,
wrap (this, &synfp_collect::timeout));
}
void
synfp_collect::timeout ()
{
tmo = NULL;
service ();
}
void
synfp_collect::lookup (const sockaddr_in &sin, ::cbs cb)
{
if (fpentry_t *fpe = fps.tab[sin]) {
#if SYNFP_DEBUG
warn ("synfp-lookup %s:%d %s\n", inet_ntoa (sin.sin_addr),
ntohs (sin.sin_port), fpe->val.cstr ());
#endif
(*cb) (fpe->val);
//fps.dealloc (fpe);
}
else {
#if SYNFP_DEBUG
warn ("synfp-lookup %s:%d queued\n", inet_ntoa (sin.sin_addr),
ntohs (sin.sin_port));
#endif
cbs.insert (sin, cb);
service ();
}
}
str
synfp_collect::lookup (const sockaddr_in &sin)
{
if (fpentry_t *fpe = fps.tab[sin])
return fpe->val;
return NULL;
}
static void
test (synfp *s)
{
int i = 5;
do {
str fp;
sockaddr_in sin;
sockaddr_in dsin;
int res = s->getfp (&fp, &sin, &dsin);
if (res == 1) {
const char *os = synos_guess (fp);
warnx ("%s:%d", inet_ntoa (sin.sin_addr), ntohs (sin.sin_port));
warnx (" %s:%d %s%s%s\n",
inet_ntoa (dsin.sin_addr), ntohs (dsin.sin_port),
fp.cstr (), os ? " " : "", os ? os : "");
}
else if (res == -1)
return;
} while (--i);
}
static void synfp_usage () __attribute__ ((noreturn));
static void
synfp_usage ()
{
warnx << "usage: " << progname << " --synfp"
<< " [port [ip-addr [if-name ...]]]\n";
exit (1);
}
void
synfp_test (int argc, char **argv)
{
argc++;
argv--;
int port = 0;
in_addr addr;
vec<str> ifnames;
if (argc >= 2 && !convertint (argv[1], &port))
synfp_usage ();
addr.s_addr = htonl (INADDR_ANY);
if (argc >= 3)
if (inet_aton (argv[2], &addr) < 1)
synfp_usage ();
for (int i = 3; i < argc; i++)
ifnames.push_back (argv[i]);
if (ifnames.empty ())
if (!synfp::ifnames (&ifnames, addr))
exit (1);
if (ifnames.empty ())
ifnames.push_back (NULL);
for (str *sp = ifnames.base (); sp < ifnames.lim (); sp++) {
warn << "listening on " << *sp << "\n";
synfp *s = New synfp;
if (!s->init (*sp, addr, port))
exit (1);
fdcb (s->fd, selread, wrap (test, s));
}
}
#endif /* USE_SYNFP */
syntax highlighted by Code2HTML, v. 0.9.1