Sleds/gluster_status/UdpIpc.cpp

263 lines
7.7 KiB
C++

/*
Copyright (c) 2013, 2014 IOnU Security Inc. All rights reserved.
Created February 2014 by Kendrick Webster, based on code borrowed
from the K2Client and K2Daemon projects.
UdpIpc.cpp - implementation for UdpIpc.h
*/
#include <stdlib.h>
#include <string.h>
#include "Hash.h"
#include "Random.h"
#include "UdpIpc.h"
/*
Packets consist of the following fields:
octets description
--------------------------------------------------------------------------
7 nonce (random initialization vector)
<n> body, NUL-terminated string
6 message integrity code (MIC)
All fields after the nonce are encrypted using an auto-hashing stream
cipher with a hard-coded key. The decrypted MIC will match only for
properly encrypted packets. Spurious packets fail the MIC check. This
'encryption' is used only for spurious packet rejection (not security).
Multi-byte fields are in network byte order (big endian).
*/
namespace ipc_constants
{
constexpr size_t NONCE_SIZE = 7;
constexpr size_t MIC_SIZE = 6;
constexpr size_t KEY_MIX_CYCLES = 32;
constexpr size_t CRYPT_SIZE = MIC_SIZE + 1; // +1 for NUL (string terminator)
constexpr size_t OVERHEAD_SIZE = CRYPT_SIZE + NONCE_SIZE;
constexpr size_t MAX_PACKET_SIZE = 1440;
constexpr size_t MAX_STRING_LENGTH = MAX_PACKET_SIZE - OVERHEAD_SIZE;
static const uint8_t KEY [] =
{
82, 144, 16, 86, 168, 144, 74, 102, 226, 166, 242, 99, 166, 54, 199, 118,
218, 71, 180, 240, 9, 234, 119, 252, 35, 35, 145, 79, 220, 4, 113, 63
};
static const uint8_t MIC [MIC_SIZE] =
{
236, 114, 80, 76, 10, 124
};
}
namespace this_module // local data
{
constexpr int INVALID_SOCKET = -1;
int sockfd = INVALID_SOCKET;
uint8_t tx_buf[ipc_constants::MAX_PACKET_SIZE];
uint8_t rx_buf[ipc_constants::MAX_PACKET_SIZE];
struct sockaddr_in recvaddr;
struct sockaddr_in servaddr;
}
success_t ionu::udp_ipc::InitSocket(uint16_t port, uint32_t timeout_milliseconds,
Log::severity_t trace_loglevel, Log::severity_t error_loglevel)
{
using this_module::INVALID_SOCKET;
using this_module::sockfd;
using this_module::servaddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == sockfd)
{
logprintf(error_loglevel, "socket: %s", strerror(errno));
return FAILURE;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (0 != port)
{
logprintf(trace_loglevel, "listening on UDP port %d", port);
}
if (0 != bind(sockfd, reinterpret_cast<struct sockaddr *>(&servaddr), sizeof(servaddr)))
{
logprintf(error_loglevel, "bind: %s", strerror(errno));
return FAILURE;
}
if (0 == port)
{
socklen_t len = sizeof(servaddr);
if (0 == getsockname(sockfd, reinterpret_cast<struct sockaddr *>(&servaddr), &len))
{
logprintf(trace_loglevel, "IPC socket bound to UDP port %d", ntohs(servaddr.sin_port));
}
else
{
logprintf(error_loglevel, "getsockname: %s", strerror(errno));
}
}
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout_milliseconds * 1000;
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
{
logprintf(error_loglevel, "setsockopt: %s", strerror(errno));
return FAILURE;
}
return SUCCESS;
}
// Encode a packet, returns length in octets or 0 if error
static unsigned int encodePacket(const std::string& body)
{
using namespace ipc_constants;
using this_module::tx_buf;
sc_hash_state_t h;
uint8_t* p = tx_buf;
unsigned int len = body.length();
if (len > MAX_STRING_LENGTH)
{
logprintf(Log::error, "string length (%u) too large for packet (%u max)", len, MAX_STRING_LENGTH);
return 0;
}
/* generate nonce and use it to key the stream cipher */
memset(&h, 0, sizeof(h));
ionu::random::GetRandom(p, NONCE_SIZE);
sc_hash_vector(&h, p, NONCE_SIZE);
sc_hash_vector(&h, KEY, sizeof(KEY));
sc_hash_mix(&h, KEY_MIX_CYCLES);
/* add body, encrypt packet, return length */
p += NONCE_SIZE;
memcpy(p, body.c_str(), len + 1);
memcpy(p + len + 1, MIC, MIC_SIZE);
sc_hash_encrypt(&h, tx_buf + NONCE_SIZE, len + CRYPT_SIZE);
return len + OVERHEAD_SIZE;
}
success_t ionu::udp_ipc::SendTo(ionu::network::CAddress a, const std::string& s)
{
using namespace this_module;
if (INVALID_SOCKET == sockfd)
{
logprintf(Log::critical, "IPC SendTo() was called before InitSocket");
exit(EXIT_FAILURE);
}
ionu::random::SeedRandom();
unsigned int len = encodePacket(s);
if (0 == len)
{
return FAILURE;
}
else
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = a.addr;
addr.sin_port = (PORT_LOOPBACK == a.port) ? servaddr.sin_port : a.port;
ssize_t n = sendto(sockfd, tx_buf, len, 0, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
if (n < 0)
{
logprintf(Log::error, "sendto: %s", strerror(errno));
return FAILURE;
}
else if (static_cast<unsigned int>(n) != len)
{
logprintf(Log::error, "sendto: sent %d of %u bytes", n, len);
return FAILURE;
}
else
{
return SUCCESS;
}
}
}
// Decode a packet, returns <true> and sets <body> if packet is good
static bool decodePacket(unsigned int len, std::string& body)
{
using namespace ipc_constants;
using this_module::rx_buf;
using this_module::recvaddr;
using ionu::network::IP4AddressString;
sc_hash_state_t h;
uint8_t* p = rx_buf;
/* reject short packets */
if (len < OVERHEAD_SIZE)
{
logprintf(Log::debug3, "spurious (short) packet from %s:%d", IP4AddressString(recvaddr).c_str(), ntohs(recvaddr.sin_port));
return false;
}
/* decrypt packet */
memset(&h, 0, sizeof(h));
sc_hash_vector(&h, p, NONCE_SIZE);
sc_hash_vector(&h, KEY, sizeof(KEY));
sc_hash_mix(&h, KEY_MIX_CYCLES);
p += NONCE_SIZE;
sc_hash_decrypt(&h, p, len - NONCE_SIZE);
/* reject packet if MIC is wrong */
if (0 != memcmp(rx_buf + (len - MIC_SIZE), MIC, MIC_SIZE))
{
logprintf(Log::debug3, "spurious (bad MIC) packet from %s:%d", IP4AddressString(recvaddr).c_str(), ntohs(recvaddr.sin_port));
return false;
}
/* reject packet if NUL terminator for body (string) is missing */
if (0 != rx_buf[len - (MIC_SIZE + 1)])
{
logprintf(Log::warning, "malformed (missing NUL) packet from %s:%d", IP4AddressString(recvaddr).c_str(), ntohs(recvaddr.sin_port));
return false;
}
body = reinterpret_cast<char*>(p);
return true;
}
bool ionu::udp_ipc::ReceiveFrom(ionu::network::CAddress& a, std::string& s)
{
using namespace this_module;
using ionu::random::SeedRandom;
if (INVALID_SOCKET == sockfd)
{
logprintf(Log::critical, "IPC ReceiveFrom() was called before InitSocket");
exit(EXIT_FAILURE);
}
SeedRandom();
socklen_t recvaddr_len = sizeof(recvaddr);
int n = recvfrom(sockfd, rx_buf, sizeof(rx_buf), 0, reinterpret_cast<struct sockaddr *>(&recvaddr), &recvaddr_len);
if (n > 0)
{
if (decodePacket(n, s))
{
a.addr = recvaddr.sin_addr.s_addr;
a.port = recvaddr.sin_port;
SeedRandom();
return true;
}
}
else if (EAGAIN != errno)
{
logprintf(Log::critical, "recvfrom() returned %d", n);
logprintf(Log::critical, "recvfrom: %s", strerror(errno));
exit(EXIT_FAILURE);
}
SeedRandom();
return false;
}