SatCat5
hal_pcap.cc
1 // Copyright 2021-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include "hal_pcap/hal_pcap.h"
7 #include <cstdio>
8 #include <cstring>
9 #include <iostream>
10 #include <satcat5/log.h>
11 
12 // PCAP must be included last due to name conflicts on some platforms.
13 #include <pcap.h>
14 
15 // Platform-specific includes
16 #ifdef _WIN32
17  #include <tchar.h>
18  #undef ERROR // Deconflict Windows "ERROR" macro
19 #else
20  #include <arpa/inet.h>
21 #endif
22 
23 namespace eth = satcat5::eth;
24 namespace log = satcat5::log;
25 namespace spcap = satcat5::pcap;
26 
27 // Set verbosity level (0/1/2)
28 static const unsigned DEBUG_VERBOSE = 0;
29 
30 // Maximum frame size (TODO: Jumbo frames?)
31 static const unsigned MAX_ETH_FRAME = 1536;
32 
33 // Global PCAP state
34 static pcap_if_t* g_pcap_alldevs = 0;
35 
36 // Define the PCAP data structures required for a Socket.
37 // (Note: Spell out the full name here to help Doxygen.)
39  explicit Device(const char* ifname, u16 filter);
40 
41  const char* name() const {return m_descr->name;}
42  const char* desc() const {return m_descr->description;}
43 
44  bool m_ok;
45  pcap_if_t* m_descr;
46  pcap_t* m_device;
47  struct bpf_program m_filter;
48 };
49 
50 // First-time PCAP initialization.
51 bool pcap_init() {
52 #ifdef _WIN32
53  // Windows only: Load the Npcap DLL.
54  _TCHAR npcap_dir[512];
55  UINT len;
56  len = GetSystemDirectory(npcap_dir, 480);
57  if (!len) {
58  log::Log(log::ERROR, "GetSystemDirectory").write((u32)GetLastError());
59  return false;
60  }
61 
62  _tcscat_s(npcap_dir, 512, _T("\\Npcap"));
63  if (SetDllDirectory(npcap_dir) == 0) {
64  log::Log(log::ERROR, "SetDllDirectory").write((u32)GetLastError());
65  return false;
66  }
67 #endif
68 
69  // Get the list of Ethernet devices.
70  char errbuf[PCAP_ERRBUF_SIZE];
71  if (pcap_findalldevs(&g_pcap_alldevs, errbuf) < 0) {
72  log::Log(log::ERROR, "pcap_findalldevs", errbuf);
73  return false;
74  }
75 
76  // Ready to use PCAP!
77  return true;
78 }
79 
80 // Is the designated device an Ethernet interface?
81 bool is_ethernet_device(const char* ifname) {
82  // Attempt to open designated interface.
83  char errbuf[PCAP_ERRBUF_SIZE];
84  pcap_t* dev = pcap_open_live(ifname, 0, 0, 0, errbuf);
85 
86  // If successful, check type before closing.
87  if (dev) {
88  int type = pcap_datalink(dev);
89  pcap_close(dev);
90  return (type == DLT_EN10MB);
91  } else if (DEBUG_VERBOSE > 0) {
92  log::Log(log::WARNING, ifname, "Can't open, ").write(errbuf);
93  }
94  return false;
95 }
96 
97 spcap::Descriptor::Descriptor(const char* n, const char* d)
98  : name(n), desc(d ? d : n)
99 {
100  // Nothing else to initialize.
101 }
102 
103 spcap::DescriptorList spcap::list_all_devices() {
104  spcap::DescriptorList list;
105 
106  // First-time PCAP initialization.
107  if (!g_pcap_alldevs) pcap_init();
108 
109  // Scan the global list for Ethernet devices (PCAP also handles USB).
110  for (pcap_if_t* dev = g_pcap_alldevs ; dev ; dev = dev->next) {
111  if (is_ethernet_device(dev->name)) {
112  list.push_back(spcap::Descriptor(dev->name, dev->description));
113  }
114  }
115 
116  return list;
117 }
118 
119 bool spcap::is_device(const char* ifname) {
120  // First-time setup for Pcap.
121  if (!g_pcap_alldevs) pcap_init();
122 
123  // Scan list of device-descriptors for a matching name.
124  for (pcap_if_t* dev = g_pcap_alldevs ; dev ; dev = dev->next) {
125  if (strstr(ifname, dev->name)) return true;
126  }
127 
128  return false; // No match
129 }
130 
132  // Sanity check: Only one option? No options at all?
133  spcap::DescriptorList devs = spcap::list_all_devices();
134  if (devs.size() == 1) return devs[0].name;
135  if (devs.size() == 0) {
136  std::cerr << "No valid PCAP devices." << std::endl;
137  return "";
138  }
139 
140  // Otherwise print a menu of options.
141  std::cout << "Please select a device from the list:" << std::endl;
142  for (unsigned a = 0 ; a < devs.size() ; ++a) {
143  std::cout << " " << a << ":\t" << devs[a].desc << std::endl;
144  }
145  std::cout << " (Any other number to cancel)" << std::endl;
146 
147  // Return selected index, if valid.
148  int sel = -1;
149  std::cin >> sel;
150  if (sel < 0 || sel >= (int)devs.size())
151  return "";
152  else
153  return devs[sel].name;
154 }
155 
156 spcap::Device::Device(const char* ifname, u16 filter)
157  : m_ok(false)
158  , m_descr(0)
159  , m_device(0)
160 {
161  // First-time setup for Pcap.
162  if (!g_pcap_alldevs) pcap_init();
163 
164  // Scan list of device-descriptors for a matching name.
165  for (m_descr = g_pcap_alldevs ; m_descr ; m_descr = m_descr->next) {
166  if (strstr(ifname, m_descr->name)) break;
167  }
168 
169  if (!m_descr) { // Success?
170  log::Log(log::ERROR, ifname, "No matching Ethernet device.");
171  return;
172  }
173 
174  // Open matching device.
175  char errbuf[PCAP_ERRBUF_SIZE];
176  m_device = pcap_open_live(
177  m_descr->name, // Device to open
178  MAX_ETH_FRAME, // Maximum capture size
179  1, // Request promiscuous mode
180  1, // Read timeout = 1 msec
181  errbuf); // Buffer for error string
182 
183  if (!m_device) {
184  log::Log(log::ERROR, ifname, "Could not open: ").write(errbuf);
185  return;
186  }
187 
188  // Set non-blocking mode.
189  if (pcap_setnonblock(m_device, 1, errbuf) == PCAP_ERROR) {
190  log::Log(log::ERROR, ifname, "Could not set mode: ").write(errbuf);
191  return;
192  }
193 
194  // Enable a filter for incoming packets?
195  if (filter) {
196  char filter_str[128];
197  snprintf(filter_str, sizeof(filter_str), "ether proto 0x%04X", filter);
198  if (pcap_compile(m_device, &m_filter, filter_str, 1, PCAP_NETMASK_UNKNOWN) < 0) return;
199  if (pcap_setfilter(m_device, &m_filter) < 0) return;
200  }
201 
202  // Success!
203  m_ok = true;
204 }
205 
206 spcap::Socket::Socket(const char* ifname, unsigned bsize, eth::MacType filter)
207  : satcat5::io::BufferedIO(
208  new u8[bsize], bsize, bsize/64,
209  new u8[bsize], bsize, bsize/64)
210  , m_device(new spcap::Device(ifname, filter.value))
211 {
212  // Nothing else to initialize.
213 }
214 
215 spcap::Socket::~Socket() {
216  // Close down the socket.
217  if (m_device) pcap_close(m_device->m_device);
218 
219  // Deallocate child objects.
220  delete[] m_tx.get_buff_dtor();
221  delete[] m_rx.get_buff_dtor();
222  delete m_device;
223 }
224 
225 bool spcap::Socket::ok() const {
226  return (m_device) && (m_device->m_ok);
227 }
228 
229 const char* spcap::Socket::name() const {
230  return m_device ? m_device->name() : "";
231 }
232 
233 const char* spcap::Socket::desc() const {
234  return m_device ? m_device->desc() : "";
235 }
236 
238  // New data ready for transmission?
239  unsigned nread = m_tx.get_read_ready();
240  if (nread > MAX_ETH_FRAME) {
241  log::Log(log::ERROR, m_device->name(), "Tx frame too long.").write(nread);
242  } else if (nread && ok()) {
243  // Copy outgoing data to a working buffer...
244  u8 temp[MAX_ETH_FRAME];
245  m_tx.read_bytes(nread, temp);
246  // ...then write to the PCAP socket.
247  int result = pcap_sendpacket(m_device->m_device, temp, nread);
248  if (result < 0)
249  log::Log(log::WARNING, m_device->name(), "Tx failed:\n")
250  .write(pcap_geterr(m_device->m_device));
251  }
252 
253  // Cleanup for the next packet, if any.
254  m_tx.read_finalize();
255 }
256 
258  struct pcap_pkthdr* pkt_header;
259  const u_char *pkt_data;
260 
261  // Sanity check before polling...
262  if (!ok()) return;
263 
264  // Attempt to read next frame from PCAP socket...
265  int result = pcap_next_ex(m_device->m_device, &pkt_header, &pkt_data);
266  if (result > 0) {
267  m_rx.write_bytes(pkt_header->len, pkt_data);
268  m_rx.write_finalize();
269  } else if (result < 0 && DEBUG_VERBOSE > 0) {
270  log::Log(log::WARNING, m_device->name(), "Rx error:\n")
271  .write(pcap_geterr(m_device->m_device));
272  }
273 }
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
The Log class creates and formats one log message.
Definition: log.h:195
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
const char * name() const
Unique interface ID.
Definition: hal_pcap.cc:229
Socket(const char *ifname, unsigned bsize=65536, satcat5::eth::MacType filter=satcat5::eth::ETYPE_NONE)
Open the specified interface by name.
Definition: hal_pcap.cc:206
void poll_always() override
Child class MUST override this method.
Definition: hal_pcap.cc:257
void data_rcvd(satcat5::io::Readable *src) override
The data_rcvd() callback is polled whenever data is available.
Definition: hal_pcap.cc:237
bool ok() const
Is the socket in a usable state?
Definition: hal_pcap.cc:225
const char * desc() const
Human-readable name.
Definition: hal_pcap.cc:233
Wrapper for PCAP / NPCAP socket library and supporting functions.
bool is_device(const char *ifname)
Check if a given name is on the list from list_all_devices.
Definition: hal_pcap.cc:119
std::string prompt_for_ifname()
Print a list of Ethernet devices, and select by index.
Definition: hal_pcap.cc:131
satcat5::pcap::DescriptorList list_all_devices()
Fetch a list of Ethernet device descriptors.
Definition: hal_pcap.cc:103
Diagnostic logging to UART and/or Ethernet ports.
EtherType field (uint16) is used a protocol-ID [1536..65535].
Definition: eth_header.h:96
Structure for holding a device-name and user-readable description.
Definition: hal_pcap.h:72