198 lines
5.1 KiB
C++
198 lines
5.1 KiB
C++
#include "udp_interface.h"
|
|
|
|
#include <microReticulum/Transport.h>
|
|
#include <microReticulum/Log.h>
|
|
|
|
#ifndef ARDUINO
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#endif
|
|
|
|
using namespace RNS;
|
|
|
|
UDPInterface::UDPInterface(const char* name,
|
|
const std::string& local_host, int local_port,
|
|
const std::string& remote_host, int remote_port)
|
|
: RNS::InterfaceImpl(name) {
|
|
|
|
_IN = true;
|
|
_OUT = true;
|
|
_bitrate = BITRATE_GUESS;
|
|
_HW_MTU = 1064;
|
|
|
|
_local_host = local_host;
|
|
_local_port = local_port;
|
|
if (!remote_host.empty()) {
|
|
_forward_configured = true;
|
|
_remote_host = remote_host;
|
|
_remote_port = remote_port;
|
|
}
|
|
}
|
|
|
|
/*virtual*/ UDPInterface::~UDPInterface() {
|
|
stop();
|
|
}
|
|
|
|
/*virtual*/ bool UDPInterface::start() {
|
|
_online = false;
|
|
|
|
#ifdef ARDUINO
|
|
udp.begin(_local_port);
|
|
#else
|
|
// resolve local host
|
|
struct in_addr local_addr;
|
|
if (inet_aton(_local_host.c_str(), &local_addr) == 0) {
|
|
struct hostent* host_ent = gethostbyname(_local_host.c_str());
|
|
if (host_ent == nullptr || host_ent->h_addr_list[0] == nullptr) {
|
|
ERRORF("Unable to resolve local host %s", _local_host.c_str());
|
|
return false;
|
|
}
|
|
_local_address = *((in_addr_t*)(host_ent->h_addr_list[0]));
|
|
}
|
|
else {
|
|
_local_address = local_addr.s_addr;
|
|
}
|
|
|
|
_remote_address = INADDR_NONE;
|
|
if (_forward_configured) {
|
|
struct in_addr remote_addr;
|
|
if (inet_aton(_remote_host.c_str(), &remote_addr) == 0) {
|
|
struct hostent* host_ent = gethostbyname(_remote_host.c_str());
|
|
if (host_ent == nullptr || host_ent->h_addr_list[0] == nullptr) {
|
|
ERRORF("Unable to resolve remote host %s", _remote_host.c_str());
|
|
return false;
|
|
}
|
|
_remote_address = *((in_addr_t*)(host_ent->h_addr_list[0]));
|
|
}
|
|
else {
|
|
_remote_address = remote_addr.s_addr;
|
|
}
|
|
}
|
|
|
|
_socket = socket( PF_INET, SOCK_DGRAM, 0 );
|
|
if (_socket < 0) {
|
|
ERRORF("Unable to create socket with error %d", errno);
|
|
return false;
|
|
}
|
|
|
|
int broadcast = 1;
|
|
setsockopt(_socket, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
|
|
|
|
int reuse = 1;
|
|
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
|
#ifdef SO_REUSEPORT
|
|
setsockopt(_socket, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
|
|
#endif
|
|
|
|
INFOF("Binding UDP socket %d to %s:%d", _socket, _local_host.c_str(), _local_port);
|
|
sockaddr_in bind_addr;
|
|
memset(&bind_addr, 0, sizeof(bind_addr));
|
|
bind_addr.sin_family = AF_INET;
|
|
bind_addr.sin_addr.s_addr = _local_address;
|
|
bind_addr.sin_port = htons(_local_port);
|
|
if (bind(_socket, (struct sockaddr*)&bind_addr, sizeof(bind_addr)) == -1) {
|
|
ERRORF("Unable to bind socket with error %d", errno);
|
|
close(_socket);
|
|
_socket = -1;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
_online = true;
|
|
return true;
|
|
}
|
|
|
|
/*virtual*/ void UDPInterface::stop() {
|
|
#ifndef ARDUINO
|
|
if (_socket > -1) {
|
|
close(_socket);
|
|
_socket = -1;
|
|
}
|
|
#endif
|
|
_online = false;
|
|
}
|
|
|
|
/*virtual*/ void UDPInterface::loop() {
|
|
if (!_online) {
|
|
return;
|
|
}
|
|
#ifdef ARDUINO
|
|
udp.parsePacket();
|
|
size_t len = udp.read(_buffer.writable(Type::Reticulum::MTU), Type::Reticulum::MTU);
|
|
if (len > 0) {
|
|
_buffer.resize(len);
|
|
on_incoming(_buffer);
|
|
}
|
|
#else
|
|
// One datagram per recvfrom() with MSG_DONTWAIT, looping until the
|
|
// kernel queue is empty — the same drain pattern as the microReticulum
|
|
// example, which avoids stale/zero FIONREAD counts on some platforms.
|
|
while (true) {
|
|
sockaddr_in src_addr{};
|
|
socklen_t src_addr_len = sizeof(src_addr);
|
|
ssize_t len = recvfrom(_socket,
|
|
_buffer.writable(_HW_MTU),
|
|
_HW_MTU,
|
|
MSG_DONTWAIT,
|
|
(struct sockaddr*)&src_addr,
|
|
&src_addr_len);
|
|
if (len <= 0) {
|
|
break;
|
|
}
|
|
_buffer.resize(static_cast<size_t>(len));
|
|
if (!_forward_configured) {
|
|
_last_src_addr = src_addr;
|
|
_have_src = true;
|
|
}
|
|
on_incoming(_buffer);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*virtual*/ bool UDPInterface::send_outgoing(const RNS::Bytes& data) {
|
|
bool success = true;
|
|
try {
|
|
if (_online) {
|
|
#ifdef ARDUINO
|
|
udp.beginPacket(_remote_host.c_str(), _remote_port);
|
|
udp.write(data.data(), data.size());
|
|
udp.endPacket();
|
|
#else
|
|
sockaddr_in sock_addr;
|
|
if (_forward_configured) {
|
|
memset(&sock_addr, 0, sizeof(sock_addr));
|
|
sock_addr.sin_family = AF_INET;
|
|
sock_addr.sin_addr.s_addr = _remote_address;
|
|
sock_addr.sin_port = htons(_remote_port);
|
|
}
|
|
else if (_have_src) {
|
|
// No forward target: reply to whoever spoke to us last.
|
|
sock_addr = _last_src_addr;
|
|
}
|
|
else {
|
|
WARNING("UDPInterface: no forward target and no peer heard from yet, dropping outgoing packet");
|
|
return false;
|
|
}
|
|
ssize_t sent = sendto(_socket, data.data(), data.size(), 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
|
|
if (sent != (ssize_t)data.size()) {
|
|
WARNINGF("Failed sending %d bytes via UDP", (int)data.size());
|
|
success = false;
|
|
}
|
|
#endif
|
|
}
|
|
InterfaceImpl::handle_outgoing(data);
|
|
}
|
|
catch (const std::exception& e) {
|
|
ERRORF("Could not transmit on %s. The contained exception was: %s", toString().c_str(), e.what());
|
|
success = false;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void UDPInterface::on_incoming(const RNS::Bytes& data) {
|
|
InterfaceImpl::handle_incoming(data);
|
|
}
|