diff --git a/FPEngine.cc b/FPEngine.cc index 050b9e8..9d437d4 100644 --- a/FPEngine.cc +++ b/FPEngine.cc @@ -158,6 +158,7 @@ void FPNetworkControl::init(const char *ifname, devtype iftype) { /* Create a new nsock pool */ if ((this->nsp = nsp_new(NULL)) == NULL) fatal("Unable to obtain an Nsock pool"); + nsp_setdevice(nsp, o.device); /* Set Trace level */ if (o.packetTrace()) { @@ -183,17 +184,9 @@ void FPNetworkControl::init(const char *ifname, devtype iftype) { fatal("dnet: failed to open device %s", ifname); this->rawsd = -1; } else { -#ifdef WIN32 - win32_fatal_raw_sockets(ifname); -#endif if (this->rawsd >= 0) close(this->rawsd); - if ((this->rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) - pfatal("Couldn't obtain raw socket in %s", __func__); - broadcast_socket(this->rawsd); -#ifndef WIN32 - sethdrinclude(this->rawsd); -#endif + rawsd = nmap_raw_socket(ifname); } /* De-register existing callers */ diff --git a/idle_scan.cc b/idle_scan.cc index a85af0a..557a6e8 100644 --- a/idle_scan.cc +++ b/idle_scan.cc @@ -382,16 +382,8 @@ static void initialize_idleproxy(struct idle_proxy_info *proxy, char *proxyName, proxy->rawsd = -1; proxy->ethptr = &proxy->eth; } else { -#ifdef WIN32 - win32_fatal_raw_sockets(proxy->host.deviceName()); -#endif - if ((proxy->rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) - pfatal("socket troubles in %s", __func__); + proxy->rawsd = nmap_raw_socket(proxy->host.deviceName()); unblock_socket(proxy->rawsd); - broadcast_socket(proxy->rawsd); -#ifndef WIN32 - sethdrinclude(proxy->rawsd); -#endif proxy->eth.ethsd = NULL; proxy->ethptr = NULL; } diff --git a/nbase/nbase.h b/nbase/nbase.h index 8988b2c..9e0e848 100644 --- a/nbase/nbase.h +++ b/nbase/nbase.h @@ -459,6 +459,7 @@ int inheritable_socket(int af, int style, int protocol); int dup_socket(int sd); int unblock_socket(int sd); int block_socket(int sd); +int socket_bindtodevice(int sd, const char *device); /* CRC32 Cyclic Redundancy Check */ unsigned long nbase_crc32(unsigned char *buf, int len); diff --git a/nbase/nbase_misc.c b/nbase/nbase_misc.c index 2e410c3..8bd2e13 100644 --- a/nbase/nbase_misc.c +++ b/nbase/nbase_misc.c @@ -281,6 +281,18 @@ int block_socket(int sd) { return 1; } +/* Use the SO_BINDTODEVICE sockopt to bind with a specific interface (Linux + only). Pass NULL or an empty string to remove device binding. */ +int socket_bindtodevice(int sd, const char *device) { +#ifdef SO_BINDTODEVICE + /* Linux-specific sockopt asking to use a specific interface. See socket(7). */ + if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) < 0) + return 0; +#endif + + return 1; +} + /* Convert a time specification into a count of seconds. A time specification is * a non-negative real number, possibly followed by a units suffix. The suffixes * are "ms" for milliseconds, "s" for seconds, "m" for minutes, or "h" for diff --git a/nmap.cc b/nmap.cc index 9fcf032..39bbbad 100644 --- a/nmap.cc +++ b/nmap.cc @@ -2908,6 +2908,7 @@ int ftp_anon_connect(struct ftpinfo *ftp) { gh_perror("Couldn't create %s socket", __func__); return 0; } + socket_bindtodevice(sd, o.device); sock.sin_family = AF_INET; sock.sin_addr.s_addr = ftp->server.s_addr; diff --git a/nmap_dns.cc b/nmap_dns.cc index 8aa9ddf..04f2d34 100644 --- a/nmap_dns.cc +++ b/nmap_dns.cc @@ -1210,6 +1210,7 @@ static void nmap_mass_rdns_core(Target **targets, int num_targets) { if ((dnspool = nsp_new(NULL)) == NULL) fatal("Unable to create nsock pool in %s()", __func__); + nsp_setdevice(dnspool, o.device); if ((lasttrace = o.packetTrace())) nsp_settrace(dnspool, NULL, NSOCK_TRACE_LEVEL, o.getStartTime()); diff --git a/nping/ProbeMode.cc b/nping/ProbeMode.cc index b069ad3..90a1c90 100644 --- a/nping/ProbeMode.cc +++ b/nping/ProbeMode.cc @@ -122,6 +122,7 @@ int ProbeMode::init_nsock(){ /* Create a new nsock pool */ if ((nsp = nsp_new(NULL)) == NULL) outFatal(QT_3, "Failed to create new pool. QUITTING.\n"); + nsp_setdevice(nsp, o.getDevice()); /* Allow broadcast addresses */ nsp_setbroadcast(nsp, 1); diff --git a/nse_dnet.cc b/nse_dnet.cc index 95c2751..eefea2d 100644 --- a/nse_dnet.cc +++ b/nse_dnet.cc @@ -187,14 +187,10 @@ static int ethernet_send (lua_State *L) static int ip_open (lua_State *L) { nse_dnet_udata *udata = (nse_dnet_udata *) nseU_checkudata(L, 1, DNET_METATABLE, "dnet"); - udata->sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + udata->sock = nmap_raw_socket(NULL); if (udata->sock == -1) return luaL_error(L, "failed to open raw socket: %s (errno %d)", socket_strerror(socket_errno()), socket_errno()); - broadcast_socket(udata->sock); -#ifndef WIN32 - sethdrinclude(udata->sock); -#endif return nseU_success(L); } diff --git a/nse_nsock.cc b/nse_nsock.cc index b432b48..acbb699 100644 --- a/nse_nsock.cc +++ b/nse_nsock.cc @@ -80,6 +80,7 @@ static nsock_pool new_pool (lua_State *L) { nsock_pool nsp = nsp_new(NULL); nsock_pool *nspp; + nsp_setdevice(nsp, o.device); nsp_setbroadcast(nsp, true); nspp = (nsock_pool *) lua_newuserdata(L, sizeof(nsock_pool)); *nspp = nsp; diff --git a/nsock/include/nsock.h b/nsock/include/nsock.h index 8f2a7fa..01cf676 100644 --- a/nsock/include/nsock.h +++ b/nsock/include/nsock.h @@ -183,6 +183,9 @@ void nsp_settrace(nsock_pool nsp, FILE *file, int level, const struct timeval *b * sockets (value of optval will be used directly in the setsockopt() call). */ void nsp_setbroadcast(nsock_pool nsp, int optval); +/* Sets the name of the interface for new sockets to bind to. */ +void nsp_setdevice(nsock_pool nsp, const char *device); + /* Initializes an Nsock pool to create SSL connections. This sets an internal * SSL_CTX, which is like a template that sets options for all connections that * are made from it. Returns the SSL_CTX so you can set your own options. */ diff --git a/nsock/src/nsock_connect.c b/nsock/src/nsock_connect.c index e83abac..3683cbc 100644 --- a/nsock/src/nsock_connect.c +++ b/nsock/src/nsock_connect.c @@ -95,6 +95,15 @@ static int nsock_make_socket(mspool *ms, msiod *iod, int family, int proto) { nsock_trace(ms, "Setting of IP options failed (IOD #%li)", iod->id); } } + if (ms->device) { +#ifdef SO_BINDTODEVICE + errno = 0; + if (setsockopt(iod->sd, SOL_SOCKET, SO_BINDTODEVICE, ms->device, strlen(ms->device) + 1) == -1) { + if ((errno != EPERM && ms->tracelevel > 0) || ms->tracelevel > 5) + nsock_trace(ms, "Setting of SO_BROADCAST failed (IOD #%li)", iod->id); + } +#endif + } if (ms->broadcast) { if (setsockopt(iod->sd, SOL_SOCKET, SO_BROADCAST, (const char *)&(ms->broadcast), sizeof(int)) == -1) { if (ms->tracelevel > 0) diff --git a/nsock/src/nsock_internal.h b/nsock/src/nsock_internal.h index 8822cee..47060b9 100644 --- a/nsock/src/nsock_internal.h +++ b/nsock/src/nsock_internal.h @@ -205,6 +205,9 @@ typedef struct { /* If true, new sockets will have SO_BROADCAST set */ int broadcast; + /* Interface to bind to; only supported on Linux with SO_BINDTODEVICE sockopt. */ + const char *device; + /* If true, exit the next iteration of nsock_loop with a status of * NSOCK_LOOP_QUIT. */ int quit; diff --git a/nsock/src/nsock_pool.c b/nsock/src/nsock_pool.c index 55ed084..02c41a9 100644 --- a/nsock/src/nsock_pool.c +++ b/nsock/src/nsock_pool.c @@ -147,6 +147,12 @@ void nsp_setbroadcast(nsock_pool nsp, int optval) { mt->broadcast = optval; } +/* Sets the name of the interface for new sockets to bind to. */ +void nsp_setdevice(nsock_pool nsp, const char *device) { + mspool *mt = (mspool *)nsp; + mt->device = device; +} + /* And here is how you create an nsock_pool. This allocates, initializes, and * returns an nsock_pool event aggregator. In the case of error, NULL will be * returned. If you do not wish to immediately associate any userdata, pass in @@ -192,6 +198,8 @@ nsock_pool nsp_new(void *userdata) { nsp->next_event_serial = 1; + nsp->device = NULL; + #if HAVE_OPENSSL nsp->sslctx = NULL; #endif diff --git a/osscan2.cc b/osscan2.cc index 243b343..b3d94d4 100644 --- a/osscan2.cc +++ b/osscan2.cc @@ -1268,17 +1268,8 @@ HostOsScan::HostOsScan(Target *t) { fatal("%s: Failed to open ethernet device (%s)", __func__, t->deviceName()); rawsd = -1; } else { - /* Init our raw socket */ -#ifdef WIN32 - win32_fatal_raw_sockets(t->deviceName()); -#endif - if ((rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0 ) - pfatal("socket troubles in %s", __func__); + rawsd = nmap_raw_socket(t->deviceName()); unblock_socket(rawsd); - broadcast_socket(rawsd); -#ifndef WIN32 - sethdrinclude(rawsd); -#endif ethsd = NULL; } diff --git a/output.cc b/output.cc index 55f7875..ef77c7f 100644 --- a/output.cc +++ b/output.cc @@ -252,10 +252,16 @@ static void print_xml_service(const struct serviceDeductions *sd) { /* Show a fatal error explaining that an interface is not Ethernet and won't work on Windows. Do nothing if --send-ip (PACKET_SEND_IP_STRONG) was used. */ void win32_fatal_raw_sockets(const char *devname) { - if ((o.sendpref & PACKET_SEND_IP_STRONG) == 0) { + if ((o.sendpref & PACKET_SEND_IP_STRONG) == 0) + return; + + if (devname != NULL) { fatal("Only ethernet devices can be used for raw scans on Windows, and\n" "\"%s\" is not an ethernet device. Use the --unprivileged option\n" "for this scan.", devname); + } else { + fatal("Only ethernet devices can be used for raw scans on Windows. Use\n" + "the --unprivileged option for this scan.", devname); } } diff --git a/scan_engine.cc b/scan_engine.cc index 0c3f0cf..ba99865 100644 --- a/scan_engine.cc +++ b/scan_engine.cc @@ -1662,20 +1662,11 @@ void UltraScanInfo::Init(vector &Targets, struct scan_lists *pts, styp fatal("dnet: Failed to open device %s", Targets[0]->deviceName()); rawsd = -1; } else { - /* Initialize a raw socket */ -#ifdef WIN32 - win32_fatal_raw_sockets(Targets[0]->deviceName()); -#endif - if ((rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0 ) - pfatal("socket troubles in %s", __func__); + rawsd = nmap_raw_socket(Targets[0]->deviceName()); /* We do not wan't to unblock the socket since we want to wait if kernel send buffers fill up rather than get ENOBUF, and we won't be receiving on the socket anyway unblock_socket(rawsd);*/ - broadcast_socket(rawsd); -#ifndef WIN32 - sethdrinclude(rawsd); -#endif ethsd = NULL; } } @@ -2987,7 +2978,8 @@ static void ultrascan_port_probe_update(UltraScanInfo *USI, HostScanStats *hss, /* Set the socket lingering so we will RST connections instead of wasting - bandwidth with the four-step close. Set the source address if needed. */ + bandwidth with the four-step close. Set the source address if needed. Bind to + a specific interface if needed. */ static void init_socket(int sd) { static int bind_failed = 0; struct linger l; @@ -3009,6 +3001,14 @@ static void init_socket(int sd) { bind_failed = 1; } } + errno = 0; + if (!socket_bindtodevice(sd, o.device)) { + /* EPERM is expected when not running as root. */ + if (errno != EPERM) { + error("Problem binding to interface %s, errno: %d", o.device, socket_errno()); + perror("socket_bindtodevice"); + } + } } /* If this is NOT a ping probe, set pingseq to 0. Otherwise it will be the diff --git a/service_scan.cc b/service_scan.cc index 9bb0248..59839ff 100644 --- a/service_scan.cc +++ b/service_scan.cc @@ -2682,6 +2682,7 @@ int service_scan(vector &Targets) { if ((nsp = nsp_new(SG)) == NULL) { fatal("%s() failed to create new nsock pool.", __func__); } + nsp_setdevice(nsp, o.device); if (o.versionTrace()) { nsp_settrace(nsp, NULL, NSOCK_TRACE_LEVEL, o.getStartTime()); diff --git a/tcpip.cc b/tcpip.cc index 11a6531..d7f66f9 100644 --- a/tcpip.cc +++ b/tcpip.cc @@ -138,6 +138,33 @@ static PacketCounter PktCt; +/* Create a raw socket and do things that always apply to raw sockets: + * Emit a fatal error on Windows. + * Set SO_BROADCAST. + * Set IP_HDRINCL. + * Bind to an interface with SO_BINDTODEVICE (if o.device is set). + The socket is created with address family AF_INET, but may be usable for + AF_INET6, depending on the operating system. + + The argument warning_device_name is used *only* in the Windows fatal error + message, and does not affect any socket characteristics. The global o.device + controls which interface to bind to with SO_BINDTODEVICE. */ +int nmap_raw_socket(const char *warning_device_name) { + int rawsd; + +#ifdef WIN32 + win32_fatal_raw_sockets(Targets[0]->deviceName()); +#endif + if ((rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0 ) + pfatal("socket troubles in %s", __func__); + broadcast_socket(rawsd); +#ifndef WIN32 + sethdrinclude(rawsd); +#endif + socket_bindtodevice(rawsd, o.device); + + return rawsd; +} /* Fill buf (up to buflen -- truncate if necessary but always terminate) with a short representation of the packet stats. diff --git a/tcpip.h b/tcpip.h index f40ed17..4d3a7db 100644 --- a/tcpip.h +++ b/tcpip.h @@ -200,6 +200,7 @@ extern "C" { #define INET_ADDRSTRLEN 16 #endif +int nmap_raw_socket(const char *warning_device_name); /* Used for tracing all packets sent or received (eg the --packet-trace option) */ diff --git a/traceroute.cc b/traceroute.cc index 88595df..4bafc6e 100644 --- a/traceroute.cc +++ b/traceroute.cc @@ -851,16 +851,7 @@ TracerouteState::TracerouteState(std::vector &targets) { fatal("dnet: failed to open device %s", targets[0]->deviceName()); rawsd = -1; } else { -#ifdef WIN32 - win32_fatal_raw_sockets(targets[0]->deviceName()); -#endif - rawsd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); - if (rawsd == -1) - pfatal("traceroute: socket troubles"); - broadcast_socket(rawsd); -#ifndef WIN32 - sethdrinclude(rawsd); -#endif + rawsd = nmap_raw_socket(targets[0]->deviceName()); ethsd = NULL; }