18constexpr std::size_t kEthernetHeaderSize = 14U;
19constexpr int kCaptureTimeoutMilliseconds = 1;
21[[noreturn]]
void ThrowPcapError(
const std::string& operation, pcap_t* capture,
int errorCode)
23 const char* detail = capture !=
nullptr ? pcap_geterr(capture) : nullptr;
25 std::string message = operation;
27 if (detail !=
nullptr && *detail !=
'\0')
34 message +=
" (libpcap error " + std::to_string(errorCode) +
")";
37 if (errorCode == PCAP_ERROR_PERM_DENIED || errorCode == PCAP_ERROR_PROMISC_PERM_DENIED)
39 throw PermissionDeniedError(message);
42 throw SocketError(message);
49std::string ResolveCaptureDevice(
const std::string& requested)
51 char errorBuffer[PCAP_ERRBUF_SIZE]{};
53 pcap_if_t* devices =
nullptr;
55 const int result = pcap_findalldevs(&devices, errorBuffer);
59 throw SocketError(
"Failed to enumerate packet-capture devices: " + std::string(errorBuffer));
62 std::string resolved = requested;
64 for (
const pcap_if_t* device = devices; device !=
nullptr; device = device->next)
66 if (device->name !=
nullptr && requested == device->name)
68 resolved = device->name;
72 if (device->description !=
nullptr && requested == device->description)
74 resolved = device->name;
79 pcap_freealldevs(devices);
102 asio::error_code
error;
104 packetWait.cancel(
error);
105 packetWait.close(
error);
107 std::lock_guard captureLock(captureMutex);
109 if (capture !=
nullptr)
118 if (interface.empty())
120 throw SocketError(
"Ethernet interface cannot be empty");
123 const std::string deviceName = ResolveCaptureDevice(interface);
125 char errorBuffer[PCAP_ERRBUF_SIZE]{};
127 capture = pcap_create(deviceName.c_str(), errorBuffer);
129 if (capture ==
nullptr)
131 throw SocketError(
"Failed to create packet capture on '" + interface +
"': " + errorBuffer);
134 auto closeCapture = [
this]()
136 if (capture !=
nullptr)
145 const std::string message = pcap_geterr(capture);
147 throw SocketError(
"Failed to configure packet capture snap length: " + message);
150 if (pcap_set_promisc(capture, 1) != 0)
152 const std::string message = pcap_geterr(capture);
154 throw SocketError(
"Failed to configure packet capture promiscuous mode: " + message);
157 if (pcap_set_timeout(capture, kCaptureTimeoutMilliseconds) != 0)
159 const std::string message = pcap_geterr(capture);
161 throw SocketError(
"Failed to configure packet capture timeout: " + message);
164 if (pcap_set_immediate_mode(capture, 1) != 0)
166 const std::string message = pcap_geterr(capture);
168 throw SocketError(
"Failed to enable immediate packet delivery: " + message);
171 const int activateResult = pcap_activate(capture);
173 if (activateResult < 0)
177 ThrowPcapError(
"Failed to activate packet capture on '" + interface +
"'", capture, activateResult);
186 if (pcap_datalink(capture) != DLT_EN10MB)
188 const int dataLink = pcap_datalink(capture);
189 const std::string name = pcap_datalink_val_to_name(dataLink) !=
nullptr
190 ? pcap_datalink_val_to_name(dataLink)
195 throw SocketError(
"Interface '" + interface +
"' does not expose Ethernet frames; libpcap data link type is " + name);
198 if (pcap_setnonblock(capture, 1, errorBuffer) != 0)
200 const std::string message = errorBuffer;
202 throw SocketError(
"Failed to configure non-blocking packet capture: " + message);
208 if (
frame.size() < kEthernetHeaderSize)
210 throw std::invalid_argument(
"Ethernet frame is shorter than the Ethernet header");
213 if (
frame.size() >
static_cast<std::size_t
>(std::numeric_limits<int>::max()))
215 throw std::invalid_argument(
"Ethernet frame is too large");
218 std::lock_guard captureLock(captureMutex);
220 if (capture ==
nullptr)
222 throw SocketError(
"Ethernet packet backend is not open");
225 const int result = pcap_sendpacket(capture,
frame.data(),
static_cast<int>(
frame.size()));
229 ThrowPcapError(
"Failed to send Ethernet frame", capture, result);
235 std::lock_guard lock(timeoutMutex);
237 this->timeout = newTimeout > std::chrono::milliseconds::zero()
239 : std::chrono::milliseconds::zero();
242std::vector<std::uint8_t>
245 std::unique_lock receiveLock(receiveMutex);
247 std::chrono::milliseconds receiveTimeout;
250 std::lock_guard timeoutLock(timeoutMutex);
251 receiveTimeout = timeout;
254 const bool hasTimeout = receiveTimeout > std::chrono::milliseconds::zero();
256 const auto operation = std::make_shared<ReceiveOperation>(
ioContext);
258 operation->pendingHandlers.store(hasTimeout ? 2U : 1U, std::memory_order_release);
260 std::future<std::vector<std::uint8_t>>
completion = operation->completion.get_future();
262 AsyncWaitForPacket(operation);
266 operation->timeoutTimer.expires_after(receiveTimeout);
268 operation->timeoutTimer.async_wait([
this, operation](
const asio::error_code&
error)
270 HandleTimeout(operation,
error);
281 if (
error != asio::error::operation_aborted)
283 FailOperation(operation, std::make_exception_ptr(
SocketError(
"Ethernet packet wait failed: " +
error.message())));
286 CompleteHandler(operation);
290 if (operation->state.load(std::memory_order_acquire) != ReceiveOperation::State::Pending)
292 CompleteHandler(operation);
296 std::vector<std::uint8_t>
frame;
298 const int result = CaptureNextFrame(
frame);
302 if (MatchesEtherType(
frame))
306 if (operation->state.compare_exchange_strong(expected, ReceiveOperation::State::Received, std::memory_order_acq_rel))
309 std::lock_guard lock(operation->resultMutex);
310 operation->frame = std::move(
frame);
313 asio::error_code cancelError;
314 operation->timeoutTimer.cancel(cancelError);
315 CancelPacketWait(operation, cancelError);
320 RearmPacketWait(operation);
323 else if (result == 0)
325 RearmPacketWait(operation);
327 else if (result == -2)
329 FailOperation(operation, std::make_exception_ptr(
SocketError(
"Ethernet packet capture was terminated")));
333 std::string message =
"Failed to receive Ethernet frame";
336 std::lock_guard captureLock(captureMutex);
338 if (capture !=
nullptr)
340 const char* detail = pcap_geterr(capture);
342 if (detail !=
nullptr && *detail !=
'\0')
350 FailOperation(operation, std::make_exception_ptr(
SocketError(message)));
353 CompleteHandler(operation);
362 if (operation->state.compare_exchange_strong(expected, ReceiveOperation::State::TimedOut, std::memory_order_acq_rel))
364 asio::error_code cancelError;
365 CancelPacketWait(operation, cancelError);
369 CompleteHandler(operation);
374 if (operation->state.load(std::memory_order_acquire) != ReceiveOperation::State::Pending)
379 operation->pendingHandlers.fetch_add(1U, std::memory_order_acq_rel);
381 if (operation->state.load(std::memory_order_acquire) != ReceiveOperation::State::Pending)
383 operation->pendingHandlers.fetch_sub(1U, std::memory_order_acq_rel);
390 AsyncWaitForPacket(operation);
394 operation->pendingHandlers.fetch_sub(1U, std::memory_order_acq_rel);
396 FailOperation(operation, std::current_exception());
404 if (!operation->state.compare_exchange_strong(expected, ReceiveOperation::State::Failed, std::memory_order_acq_rel))
410 std::lock_guard lock(operation->resultMutex);
411 operation->error = std::move(
error);
414 asio::error_code cancelError;
416 CancelPacketWait(operation, cancelError);
417 operation->timeoutTimer.cancel(cancelError);
422 if (operation->pendingHandlers.fetch_sub(1U, std::memory_order_acq_rel) != 1U)
429 std::vector<std::uint8_t>
frame;
430 std::exception_ptr
error;
433 std::lock_guard lock(operation->resultMutex);
435 frame = std::move(operation->frame);
436 error = operation->error;
439 if (state == ReceiveOperation::State::Failed)
441 operation->completion.set_exception(
error);
443 else if (state == ReceiveOperation::State::TimedOut)
445 operation->completion.set_value({});
449 operation->completion.set_value(std::move(
frame));
455 std::lock_guard captureLock(captureMutex);
457 if (capture ==
nullptr)
459 throw SocketError(
"Ethernet packet backend is not open");
462 pcap_pkthdr* header =
nullptr;
463 const u_char* data =
nullptr;
465 const int result = pcap_next_ex(capture, &header, &data);
467 if (result == 1 && header !=
nullptr && data !=
nullptr)
469 frame.assign(data, data + header->caplen);
482 if (
frame.size() < kEthernetHeaderSize)
489 if (etherTypeOffset +
sizeof(std::uint16_t) >
frame.size())
494 const auto frameType =
static_cast<std::uint16_t
>((
static_cast<std::uint16_t
>(
frame[etherTypeOffset]) << 8U) |
495 static_cast<std::uint16_t
>(
frame[etherTypeOffset + 1U]));
497 return frameType == ethertype;
void HandleTimeout(const std::shared_ptr< ReceiveOperation > &operation, const asio::error_code &error)
Handle application receive timeout.
void SetTimeout(std::chrono::milliseconds newTimeout)
Configure the receive timeout.
~EthernetPacketBackend()
Destroy the packet backend.
void RearmPacketWait(const std::shared_ptr< ReceiveOperation > &operation)
Rearm packet readiness for another asynchronous operation.
EthernetPacketBackend(asio::io_context &ioContext, const std::string &interface, std::uint16_t ethertype)
Construct the packet backend.
void FailOperation(const std::shared_ptr< ReceiveOperation > &operation, std::exception_ptr error)
Mark a receive operation as failed.
std::vector< std::uint8_t > Recv()
Receive a complete Ethernet frame.
bool MatchesEtherType(const std::vector< std::uint8_t > &frame) const
Check whether an Ethernet frame matches the configured EtherType.
void OpenCapture(const std::string &interface)
Initialize the libpcap packet capture handle.
void Send(const std::vector< std::uint8_t > &frame)
Send one complete Ethernet frame.
int CaptureNextFrame(std::vector< std::uint8_t > &frame)
Read one packet from libpcap without blocking.
void CompleteHandler(const std::shared_ptr< ReceiveOperation > &operation)
Complete one asynchronous handler.
void HandlePacketReady(const std::shared_ptr< ReceiveOperation > &operation, const asio::error_code &error)
Handle packet-source readiness.
void InitializePacketWait()
Connect the packet source to the Asio readiness mechanism.
asio::io_context & ioContext
Asio execution context supplied by the caller.
Private Ethernet packet backend used by AsioEthernetSocket.
Declares exceptions and error types used by the PROFINET IO controller stack.
constexpr std::size_t MAX_ETHERNET_FRAME
Maximum PROFINET Ethernet frame size in bytes (including VLAN tag headroom).
std::size_t SkipVlanTags(std::span< const std::uint8_t > frame) noexcept
Return the byte offset of the real EtherType in a raw Ethernet frame.
std::promise< void > completion
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.
Declares Linux Ethernet, addressing, timing, and utility helpers used by the PROFINET stack.