https://github.com/ska-sa/spead2
Tip revision: 953d63ff013cb1cf7beab747fd1fab9ce112788c authored by Bruce Merry on 08 September 2023, 12:52:18 UTC
Fix dependency on numpy and spelling of test-numba
Fix dependency on numpy and spelling of test-numba
Tip revision: 953d63f
common_raw_packet.cpp
/* Copyright 2016, 2020, 2023 National Research Foundation (SARAO)
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file
*
* Utilities for encoding and decoding ethernet, IP and UDP headers. These
* are portable definitions that do not depend on OS-specific header files
* or compiler-specific magic to specify mis-aligned storage.
*/
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <array>
#include <string>
#include <memory>
#include <stdexcept>
#include <system_error>
#include <boost/asio/ip/address_v4.hpp>
#include <boost/asio/ip/address.hpp>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <ifaddrs.h>
#include <spead2/common_raw_packet.h>
#include <spead2/common_endian.h>
// TODO: use autoconf to figure out which headers to use
#ifdef __linux__
# include <netpacket/packet.h>
#else
# include <net/if_dl.h>
#endif
using namespace std::literals;
namespace spead2
{
mac_address multicast_mac(const boost::asio::ip::address_v4 &address)
{
mac_address ans;
auto bytes = address.to_bytes();
std::memcpy(&ans[2], &bytes, 4);
ans[0] = 0x01;
ans[1] = 0x00;
ans[2] = 0x5e;
ans[3] &= 0x7f;
return ans;
}
mac_address multicast_mac(const boost::asio::ip::address &address)
{
return multicast_mac(address.to_v4());
}
namespace
{
struct freeifaddrs_deleter
{
void operator()(ifaddrs *ifa) const { freeifaddrs(ifa); }
};
} // anonymous namespace
mac_address interface_mac(const boost::asio::ip::address &address)
{
ifaddrs *ifap;
if (getifaddrs(&ifap) < 0)
throw std::system_error(errno, std::system_category(), "getifaddrs failed");
std::unique_ptr<ifaddrs, freeifaddrs_deleter> ifap_owner(ifap);
// Map address to an interface name
char *if_name = nullptr;
for (ifaddrs *cur = ifap; cur; cur = cur->ifa_next)
{
if (cur->ifa_addr && *(sa_family_t *) cur->ifa_addr == AF_INET && address.is_v4())
{
const sockaddr_in *cur_address = (const sockaddr_in *) cur->ifa_addr;
const auto expected = address.to_v4().to_bytes();
if (memcmp(&cur_address->sin_addr, &expected, sizeof(expected)) == 0)
{
if_name = cur->ifa_name;
break;
}
}
else if (cur->ifa_addr && *(sa_family_t *) cur->ifa_addr == AF_INET6 && address.is_v6())
{
const sockaddr_in6 *cur_address = (const sockaddr_in6 *) cur->ifa_addr;
const auto expected = address.to_v6().to_bytes();
if (memcmp(&cur_address->sin6_addr, &expected, sizeof(expected)) == 0)
{
if_name = cur->ifa_name;
break;
}
}
}
if (!if_name)
{
throw std::runtime_error("no interface found with the address " + address.to_string());
}
// Now find the MAC address for this interface
for (ifaddrs *cur = ifap; cur; cur = cur->ifa_next)
{
#ifdef __linux__
if (std::strcmp(cur->ifa_name, if_name) == 0
&& cur->ifa_addr && *(sa_family_t *) cur->ifa_addr == AF_PACKET)
{
const sockaddr_ll *ll = (sockaddr_ll *) cur->ifa_addr;
if (ll->sll_hatype == ARPHRD_ETHER && ll->sll_halen == 6)
{
mac_address mac;
std::memcpy(&mac, ll->sll_addr, 6);
return mac;
}
}
#else
if (std::strcmp(cur->ifa_name, if_name) == 0
&& cur->ifa_addr && *(sa_family_t *) cur->ifa_addr == AF_LINK)
{
const sockaddr_dl *dl = (sockaddr_dl *) cur->ifa_addr;
if (dl->sdl_alen == 6)
{
mac_address mac;
std::memcpy(&mac, LLADDR(dl), 6);
return mac;
}
}
#endif
}
throw std::runtime_error("no MAC address found for interface "s + if_name);
}
/////////////////////////////////////////////////////////////////////////////
packet_buffer::packet_buffer() : ptr(nullptr), length(0) {}
packet_buffer::packet_buffer(void *ptr, std::size_t size)
: ptr(reinterpret_cast<std::uint8_t *>(ptr)), length(size)
{
}
packet_buffer::operator boost::asio::mutable_buffer() const
{
return boost::asio::mutable_buffer(ptr, length);
}
/////////////////////////////////////////////////////////////////////////////
udp_packet::udp_packet(void *ptr, std::size_t size)
: packet_buffer(ptr, size)
{
if (size < min_size)
throw std::length_error("packet is too small to be a UDP packet");
}
packet_buffer udp_packet::payload() const
{
std::size_t len = length();
if (len > size() || len < min_size)
throw std::length_error("UDP length header is invalid");
return packet_buffer(data() + min_size, len - min_size);
}
/////////////////////////////////////////////////////////////////////////////
ipv4_packet::ipv4_packet(void *ptr, std::size_t size)
: packet_buffer(ptr, size)
{
if (size < min_size)
throw std::length_error("packet is too small to be an IPv4 packet");
}
void ipv4_packet::update_checksum()
{
int h = header_length();
std::uint32_t sum = 0;
checksum(0); // avoid including the old checksum in the new
for (int i = 0; i < h; i += 2)
sum += get_be<std::uint16_t>(i);
while (sum > 0xffff)
sum = (sum & 0xffff) + (sum >> 16);
checksum(~std::uint16_t(sum));
}
bool ipv4_packet::is_fragment() const
{
// If either the more fragments flag is set, or we have a non-zero offset
return flags_frag_off_be() & htobe(std::uint16_t(flag_more_fragments | 0x1fff));
}
std::size_t ipv4_packet::header_length() const
{
return 4 * (version_ihl() & 0xf);
}
int ipv4_packet::version() const
{
return version_ihl() >> 4;
}
udp_packet ipv4_packet::payload_udp() const
{
std::size_t h = header_length();
std::size_t len = total_length();
if (h < min_size)
throw std::length_error("IPv4 ihl header is invalid");
if (len > size() || len < h)
throw std::length_error("IPv4 length header is invalid");
return udp_packet(data() + h, len - h);
}
/////////////////////////////////////////////////////////////////////////////
ethernet_frame::ethernet_frame(void *ptr, std::size_t size)
: packet_buffer(ptr, size)
{
if (size < min_size)
throw std::length_error("packet is too small to be an ethernet frame");
}
ipv4_packet ethernet_frame::payload_ipv4() const
{
// TODO: handle VLAN tags
return ipv4_packet(data() + min_size, size() - min_size);
}
/////////////////////////////////////////////////////////////////////////////
linux_sll_frame::linux_sll_frame(void *ptr, std::size_t size)
: packet_buffer(ptr, size)
{
if (size < min_size)
throw std::length_error("packet is to small to be a Linux sll frame");
}
ipv4_packet linux_sll_frame::payload_ipv4() const
{
// TODO: handle VLAN tags
return ipv4_packet(data() + min_size, size() - min_size);
}
/////////////////////////////////////////////////////////////////////////////
static packet_buffer udp_from_ipv4(std::uint16_t ethertype_be, const ipv4_packet &ipv4)
{
if (ethertype_be != htobe(ipv4_packet::ethertype))
throw packet_type_error("Frame has wrong ethernet type (VLAN tagging?), discarding");
else
{
if (ipv4.version() != 4)
throw packet_type_error("Frame is not IPv4, discarding");
else if (ipv4.is_fragment())
throw packet_type_error("IP datagram is fragmented, discarding");
else if (ipv4.protocol() != udp_packet::protocol)
throw packet_type_error("Packet is not UDP, discarding");
else
return ipv4.payload_udp().payload();
}
}
packet_buffer udp_from_ethernet(void *ptr, size_t size)
{
ethernet_frame eth(ptr, size);
return udp_from_ipv4(eth.ethertype_be(), eth.payload_ipv4());
}
packet_buffer udp_from_linux_sll(void *ptr, size_t size)
{
linux_sll_frame frame(ptr, size);
return udp_from_ipv4(frame.protocol_type_be(), frame.payload_ipv4());
}
} // namespace spead2