/* $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 #ifdef HAVE_SYS_SOCKIO_H #include #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 *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 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 *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 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 (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 (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 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 &addrs) { vec lav; if ((dev && !ifaddrs (&lav, dev)) || (!dev && !myipaddrs (&lav))) return false; bhash local; for (in_addr *ap = lav.base (); ap < lav.lim (); ap++) local.insert (*ap); bhash seen; bhash 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 (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 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 s = New refcounted; 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 &av) { pfv.clear (); in_addr inaddr_any; inaddr_any.s_addr = htonl (INADDR_ANY); vec 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 s = New refcounted; 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 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 */