mirror of
https://github.com/mfontanini/libtins
synced 2026-01-23 02:35:57 +01:00
547 lines
18 KiB
C++
547 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2017, Matias Fontanini
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 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 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <tins/packet_sender.h>
|
|
#ifndef _WIN32
|
|
#include <sys/socket.h>
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
#include <arpa/inet.h>
|
|
#include <unistd.h>
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <net/if.h>
|
|
#include <net/bpf.h>
|
|
#else
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_packet.h>
|
|
#endif
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <errno.h>
|
|
#else
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#endif
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <sstream>
|
|
#include <tins/pdu.h>
|
|
#include <tins/macros.h>
|
|
// PDUs required by PacketSender::send(PDU&, NetworkInterface)
|
|
#include <tins/ethernetII.h>
|
|
#include <tins/radiotap.h>
|
|
#include <tins/dot11/dot11_base.h>
|
|
#include <tins/radiotap.h>
|
|
#include <tins/ieee802_3.h>
|
|
#include <tins/cxxstd.h>
|
|
#include <tins/detail/pdu_helpers.h>
|
|
#if TINS_IS_CXX11
|
|
#include <chrono>
|
|
#endif // TINS_IS_CXX11
|
|
|
|
using std::string;
|
|
using std::ostringstream;
|
|
using std::make_pair;
|
|
using std::vector;
|
|
using std::runtime_error;
|
|
|
|
namespace Tins {
|
|
|
|
const int PacketSender::INVALID_RAW_SOCKET = -1;
|
|
const uint32_t PacketSender::DEFAULT_TIMEOUT = 2;
|
|
|
|
#ifndef _WIN32
|
|
typedef int socket_type;
|
|
|
|
const char* make_error_string() {
|
|
return strerror(errno);
|
|
}
|
|
#else
|
|
typedef SOCKET socket_type;
|
|
|
|
// fixme
|
|
const char* make_error_string() {
|
|
return "error";
|
|
}
|
|
#endif
|
|
|
|
PacketSender::PacketSender(const NetworkInterface& iface,
|
|
uint32_t recv_timeout,
|
|
uint32_t usec)
|
|
: sockets_(SOCKETS_END, INVALID_RAW_SOCKET),
|
|
#if !defined(BSD) && !defined(_WIN32) && !defined(__FreeBSD_kernel__)
|
|
ether_socket_(INVALID_RAW_SOCKET),
|
|
#endif
|
|
_timeout(recv_timeout), timeout_usec_(usec), default_iface_(iface) {
|
|
types_[IP_TCP_SOCKET] = IPPROTO_TCP;
|
|
types_[IP_UDP_SOCKET] = IPPROTO_UDP;
|
|
types_[IP_RAW_SOCKET] = IPPROTO_RAW;
|
|
types_[IPV6_SOCKET] = IPPROTO_RAW;
|
|
types_[ICMP_SOCKET] = IPPROTO_ICMP;
|
|
types_[ICMPV6_SOCKET] = IPPROTO_ICMPV6;
|
|
}
|
|
|
|
PacketSender::~PacketSender() {
|
|
for (unsigned i(0); i < sockets_.size(); ++i) {
|
|
if (sockets_[i] != INVALID_RAW_SOCKET) {
|
|
#ifndef _WIN32
|
|
::close(sockets_[i]);
|
|
#else
|
|
::closesocket(sockets_[i]);
|
|
#endif
|
|
}
|
|
}
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
for (BSDEtherSockets::iterator it = ether_socket_.begin();
|
|
it != ether_socket_.end(); ++it) {
|
|
::close(it->second);
|
|
}
|
|
#elif !defined(_WIN32)
|
|
if (ether_socket_ != INVALID_RAW_SOCKET) {
|
|
::close(ether_socket_);
|
|
}
|
|
#endif
|
|
|
|
#ifdef TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
for (PcapHandleMap::iterator it = pcap_handles_.begin(); it != pcap_handles_.end(); ++it) {
|
|
pcap_close(it->second);
|
|
}
|
|
pcap_handles_.clear();
|
|
#endif // TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
}
|
|
|
|
void PacketSender::default_interface(const NetworkInterface& iface) {
|
|
default_iface_ = iface;
|
|
}
|
|
|
|
const NetworkInterface& PacketSender::default_interface() const {
|
|
return default_iface_;
|
|
}
|
|
|
|
#if !defined(_WIN32) || defined(TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET)
|
|
|
|
#ifndef _WIN32
|
|
bool PacketSender::ether_socket_initialized(const NetworkInterface& iface) const {
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
return ether_socket_.count(iface.id());
|
|
#else
|
|
Internals::unused(iface);
|
|
return ether_socket_ != INVALID_RAW_SOCKET;
|
|
#endif
|
|
}
|
|
|
|
int PacketSender::get_ether_socket(const NetworkInterface& iface) {
|
|
if (!ether_socket_initialized(iface)) {
|
|
open_l2_socket(iface);
|
|
}
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
return ether_socket_[iface.id()];
|
|
#else
|
|
return ether_socket_;
|
|
#endif
|
|
}
|
|
#endif // _WIN32
|
|
|
|
#ifdef TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
|
|
pcap_t* PacketSender::make_pcap_handle(const NetworkInterface& iface) const {
|
|
// This is an ugly fix to make interface names look like what
|
|
// libpcap expects on Windows
|
|
#ifdef _WIN32
|
|
#define TINS_PREFIX_INTERFACE(x) ("\\Device\\NPF_" + x)
|
|
#else // _WIN32
|
|
#define TINS_PREFIX_INTERFACE(x) (x)
|
|
#endif // _WIN32
|
|
|
|
char error[PCAP_ERRBUF_SIZE];
|
|
pcap_t* handle = pcap_create(TINS_PREFIX_INTERFACE(iface.name()).c_str(), error);
|
|
if (!handle) {
|
|
throw pcap_error("Error opening pcap handle: " + string(error));
|
|
}
|
|
if (pcap_set_promisc(handle, 1) < 0) {
|
|
throw pcap_error("Failed to set pcap handle promisc mode: " + string(pcap_geterr(handle)));
|
|
}
|
|
if (pcap_activate(handle) < 0) {
|
|
throw pcap_error("Failed to activate pcap handle: " + string(pcap_geterr(handle)));
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
#endif // TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
|
|
void PacketSender::open_l2_socket(const NetworkInterface& iface) {
|
|
#ifdef TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
if (pcap_handles_.count(iface) == 0) {
|
|
pcap_handles_.insert(make_pair(iface, make_pcap_handle(iface)));
|
|
}
|
|
#elif defined(BSD) || defined(__FreeBSD_kernel__)
|
|
int sock = -1;
|
|
// At some point, there should be an available device
|
|
for (int i = 0; sock == -1;i++) {
|
|
ostringstream oss;
|
|
oss << "/dev/bpf" << i;
|
|
|
|
sock = open(oss.str().c_str(), O_RDWR);
|
|
}
|
|
if (sock == -1) {
|
|
throw socket_open_error(make_error_string());
|
|
}
|
|
|
|
struct ifreq ifr;
|
|
strncpy(ifr.ifr_name, iface.name().c_str(), sizeof(ifr.ifr_name) - 1);
|
|
if (ioctl(sock, BIOCSETIF, (caddr_t)&ifr) < 0) {
|
|
::close(sock);
|
|
throw socket_open_error(make_error_string());
|
|
}
|
|
// Use immediate mode
|
|
u_int value = 1;
|
|
if (ioctl(sock, BIOCIMMEDIATE, &value) < 0) {
|
|
throw socket_open_error(make_error_string());
|
|
}
|
|
// Get the buffer size
|
|
if (ioctl(sock, BIOCGBLEN, &buffer_size_) < 0) {
|
|
throw socket_open_error(make_error_string());
|
|
}
|
|
ether_socket_[iface.id()] = sock;
|
|
#else
|
|
Internals::unused(iface);
|
|
if (ether_socket_ == INVALID_RAW_SOCKET) {
|
|
ether_socket_ = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
|
|
|
if (ether_socket_ == -1) {
|
|
throw socket_open_error(make_error_string());
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif // !_WIN32 || TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
|
|
void PacketSender::open_l3_socket(SocketType type) {
|
|
int socktype = find_type(type);
|
|
if (socktype == -1) {
|
|
throw invalid_socket_type();
|
|
}
|
|
if (sockets_[type] == INVALID_RAW_SOCKET) {
|
|
const bool is_v6 = (type == IPV6_SOCKET || type == ICMPV6_SOCKET);
|
|
socket_type sockfd;
|
|
sockfd = socket(is_v6 ? AF_INET6 : AF_INET, SOCK_RAW, socktype);
|
|
if (sockfd < 0) {
|
|
throw socket_open_error(make_error_string());
|
|
}
|
|
|
|
const int on = 1;
|
|
#ifndef _WIN32
|
|
typedef const void* option_ptr;
|
|
#else
|
|
typedef const char* option_ptr;
|
|
#endif
|
|
const int level = (is_v6) ? IPPROTO_IPV6 : IPPROTO_IP;
|
|
if (setsockopt(sockfd, level, IP_HDRINCL, (option_ptr)&on, sizeof(on)) != 0) {
|
|
throw socket_open_error(make_error_string());
|
|
}
|
|
|
|
sockets_[type] = static_cast<int>(sockfd);
|
|
}
|
|
}
|
|
|
|
void PacketSender::close_socket(SocketType type, const NetworkInterface& iface) {
|
|
if (type == ETHER_SOCKET) {
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
BSDEtherSockets::iterator it = ether_socket_.find(iface.id());
|
|
if (it == ether_socket_.end()) {
|
|
throw invalid_socket_type();
|
|
}
|
|
if (::close(it->second) == -1) {
|
|
throw socket_close_error(make_error_string());
|
|
}
|
|
ether_socket_.erase(it);
|
|
#elif !defined(_WIN32)
|
|
Internals::unused(iface);
|
|
if (ether_socket_ == INVALID_RAW_SOCKET) {
|
|
throw invalid_socket_type();
|
|
}
|
|
if (::close(ether_socket_) == -1) {
|
|
throw socket_close_error(make_error_string());
|
|
}
|
|
ether_socket_ = INVALID_RAW_SOCKET;
|
|
#endif
|
|
}
|
|
else {
|
|
Internals::unused(iface);
|
|
if (type >= SOCKETS_END || sockets_[type] == INVALID_RAW_SOCKET) {
|
|
throw invalid_socket_type();
|
|
}
|
|
#ifndef _WIN32
|
|
if (close(sockets_[type]) == -1) {
|
|
throw socket_close_error(make_error_string());
|
|
}
|
|
#else
|
|
closesocket(sockets_[type]);
|
|
#endif
|
|
sockets_[type] = INVALID_RAW_SOCKET;
|
|
}
|
|
}
|
|
|
|
void PacketSender::send(PDU& pdu) {
|
|
pdu.send(*this, default_iface_);
|
|
}
|
|
|
|
void PacketSender::send(PDU& pdu, const NetworkInterface& iface) {
|
|
if (pdu.matches_flag(PDU::ETHERNET_II)) {
|
|
send<Tins::EthernetII>(pdu, iface);
|
|
}
|
|
#ifdef TINS_HAVE_DOT11
|
|
else if (pdu.matches_flag(PDU::DOT11)) {
|
|
send<Tins::Dot11>(pdu, iface);
|
|
}
|
|
else if (pdu.matches_flag(PDU::RADIOTAP)) {
|
|
send<Tins::RadioTap>(pdu, iface);
|
|
}
|
|
#endif // TINS_HAVE_DOT11
|
|
else if (pdu.matches_flag(PDU::IEEE802_3)) {
|
|
send<Tins::IEEE802_3>(pdu, iface);
|
|
}
|
|
else {
|
|
send(pdu);
|
|
}
|
|
}
|
|
|
|
PDU* PacketSender::send_recv(PDU& pdu) {
|
|
return send_recv(pdu, default_iface_);
|
|
}
|
|
|
|
PDU* PacketSender::send_recv(PDU& pdu, const NetworkInterface& iface) {
|
|
try {
|
|
pdu.send(*this, iface);
|
|
}
|
|
catch (runtime_error&) {
|
|
return 0;
|
|
}
|
|
return pdu.recv_response(*this, iface);
|
|
}
|
|
|
|
#if !defined(_WIN32) || defined(TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET)
|
|
void PacketSender::send_l2(PDU& pdu,
|
|
struct sockaddr* link_addr,
|
|
uint32_t len_addr,
|
|
const NetworkInterface& iface) {
|
|
PDU::serialization_type buffer = pdu.serialize();
|
|
|
|
#ifdef TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
Internals::unused(len_addr);
|
|
Internals::unused(link_addr);
|
|
open_l2_socket(iface);
|
|
pcap_t* handle = pcap_handles_[iface];
|
|
const int buf_size = static_cast<int>(buffer.size());
|
|
if (pcap_sendpacket(handle, (u_char*)&buffer[0], buf_size) != 0) {
|
|
throw pcap_error("Failed to send packet: " + string(pcap_geterr(handle)));
|
|
}
|
|
#else // TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
int sock = get_ether_socket(iface);
|
|
if (!buffer.empty()) {
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
Internals::unused(len_addr);
|
|
Internals::unused(link_addr);
|
|
if (::write(sock, &buffer[0], buffer.size()) == -1) {
|
|
#else
|
|
if (::sendto(sock, &buffer[0], buffer.size(), 0, link_addr, len_addr) == -1) {
|
|
#endif
|
|
throw socket_write_error(make_error_string());
|
|
}
|
|
}
|
|
#endif // TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
}
|
|
|
|
#endif // !_WIN32 || TINS_HAVE_PACKET_SENDER_PCAP_SENDPACKET
|
|
|
|
#ifndef _WIN32
|
|
PDU* PacketSender::recv_l2(PDU& pdu,
|
|
struct sockaddr* link_addr,
|
|
uint32_t len_addr,
|
|
const NetworkInterface& iface) {
|
|
int sock = get_ether_socket(iface);
|
|
vector<int> sockets(1, sock);
|
|
return recv_match_loop(sockets, pdu, link_addr, len_addr, false);
|
|
}
|
|
#endif // _WIN32
|
|
|
|
PDU* PacketSender::recv_l3(PDU& pdu,
|
|
struct sockaddr* link_addr,
|
|
uint32_t len_addr,
|
|
SocketType type) {
|
|
open_l3_socket(type);
|
|
vector<int> sockets(1, sockets_[type]);
|
|
if (type == IP_TCP_SOCKET || type == IP_UDP_SOCKET) {
|
|
#ifdef BSD
|
|
throw feature_disabled();
|
|
#endif
|
|
open_l3_socket(ICMP_SOCKET);
|
|
sockets.push_back(sockets_[ICMP_SOCKET]);
|
|
}
|
|
return recv_match_loop(sockets, pdu, link_addr, len_addr, true);
|
|
}
|
|
|
|
void PacketSender::send_l3(PDU& pdu,
|
|
struct sockaddr* link_addr,
|
|
uint32_t len_addr,
|
|
SocketType type) {
|
|
open_l3_socket(type);
|
|
int sock = sockets_[type];
|
|
PDU::serialization_type buffer = pdu.serialize();
|
|
const int buf_size = static_cast<int>(buffer.size());
|
|
if (sendto(sock, (const char*)&buffer[0], buf_size, 0, link_addr, len_addr) == -1) {
|
|
throw socket_write_error(make_error_string());
|
|
}
|
|
}
|
|
|
|
PDU* PacketSender::recv_match_loop(const vector<int>& sockets,
|
|
PDU& pdu,
|
|
struct sockaddr* link_addr,
|
|
uint32_t addrlen,
|
|
bool is_layer_3) {
|
|
#ifdef _WIN32
|
|
typedef int socket_len_type;
|
|
typedef int recvfrom_ret_type;
|
|
#else
|
|
typedef socklen_t socket_len_type;
|
|
typedef ssize_t recvfrom_ret_type;
|
|
#endif
|
|
fd_set readfds;
|
|
struct timeval timeout, end_time;
|
|
int read;
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
bool is_bsd = true;
|
|
// On* BSD, we need to allocate a buffer using the given size.
|
|
const int buffer_size = is_layer_3 ? 2048 : buffer_size_;
|
|
vector<uint8_t> actual_buffer(buffer_size);
|
|
uint8_t* buffer = &actual_buffer[0];
|
|
#else
|
|
bool is_bsd = false;
|
|
uint8_t buffer[2048];
|
|
const int buffer_size = 2048;
|
|
#endif
|
|
|
|
timeout.tv_sec = _timeout;
|
|
end_time.tv_sec = static_cast<long>(time(0) + _timeout);
|
|
end_time.tv_usec = timeout.tv_usec = timeout_usec_;
|
|
while (true) {
|
|
FD_ZERO(&readfds);
|
|
int max_fd = 0;
|
|
for (vector<int>::const_iterator it = sockets.begin(); it != sockets.end(); ++it) {
|
|
FD_SET(*it, &readfds);
|
|
max_fd = (max_fd > *it) ? max_fd : *it;
|
|
}
|
|
if ((read = select(max_fd + 1, &readfds, 0, 0, &timeout)) == -1) {
|
|
return 0;
|
|
}
|
|
if (read > 0) {
|
|
for (vector<int>::const_iterator it = sockets.begin(); it != sockets.end(); ++it) {
|
|
if (FD_ISSET(*it, &readfds)) {
|
|
recvfrom_ret_type size;
|
|
// Crappy way of only conditionally running this on BSD + layer2
|
|
if (is_bsd && !is_layer_3) {
|
|
#if defined(BSD) || defined(__FreeBSD_kernel__)
|
|
size = ::read(*it, buffer, buffer_size_);
|
|
const uint8_t* ptr = buffer;
|
|
// We might see more than one packet
|
|
while (ptr < (buffer + size)) {
|
|
const bpf_hdr* bpf_header = reinterpret_cast<const bpf_hdr*>(ptr);
|
|
const uint8_t* pkt_start = ptr + bpf_header->bh_hdrlen;
|
|
if (pdu.matches_response(pkt_start, bpf_header->bh_caplen)) {
|
|
return Internals::pdu_from_flag(pdu.pdu_type(), pkt_start, bpf_header->bh_caplen);
|
|
}
|
|
ptr += BPF_WORDALIGN(bpf_header->bh_hdrlen + bpf_header->bh_caplen);
|
|
}
|
|
#endif // BSD
|
|
}
|
|
else {
|
|
socket_len_type length = addrlen;
|
|
size = ::recvfrom(*it, (char*)buffer, buffer_size, 0, link_addr, &length);
|
|
if (pdu.matches_response(buffer, size)) {
|
|
return Internals::pdu_from_flag(pdu.pdu_type(), buffer, size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if TINS_IS_CXX11
|
|
using namespace std::chrono;
|
|
microseconds end = seconds(end_time.tv_sec) + microseconds(end_time.tv_usec);
|
|
microseconds now = duration_cast<microseconds>(system_clock::now().time_since_epoch());
|
|
if (now > end) {
|
|
return 0;
|
|
}
|
|
// VC complains if we don't statically cast here
|
|
#ifdef _WIN32
|
|
typedef long tv_sec_type;
|
|
typedef long tv_usec_type;
|
|
#else
|
|
typedef time_t tv_sec_type;
|
|
typedef long tv_usec_type;
|
|
#endif
|
|
microseconds diff = end - now;
|
|
timeout.tv_sec = static_cast<tv_sec_type>(duration_cast<seconds>(diff).count());
|
|
timeout.tv_usec = static_cast<tv_usec_type>((diff - seconds(timeout.tv_sec)).count());
|
|
#else
|
|
#ifdef _WIN32
|
|
// Can't do much
|
|
return 0;
|
|
#else
|
|
struct timeval now;
|
|
gettimeofday(&now, 0);
|
|
// If now > end_time
|
|
if (timercmp(&now, &end_time, >)) {
|
|
return 0;
|
|
}
|
|
struct timeval diff;
|
|
timersub(&end_time, &now, &diff);
|
|
timeout.tv_sec = diff.tv_sec;
|
|
timeout.tv_usec = diff.tv_usec;
|
|
#endif // _WIN32
|
|
#endif // TINS_IS_CXX11
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int PacketSender::find_type(SocketType type) {
|
|
SocketTypeMap::iterator it = types_.find(type);
|
|
if (it == types_.end()) {
|
|
return -1;
|
|
}
|
|
else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
} // Tins
|