Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
687
tools/EVerest-main/modules/EVSE/IsoMux/connection/connection.cpp
Normal file
687
tools/EVerest-main/modules/EVSE/IsoMux/connection/connection.cpp
Normal file
@@ -0,0 +1,687 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (C) 2022-2023 chargebyte GmbH
|
||||
// Copyright (C) 2022-2023 Contributors to EVerest
|
||||
|
||||
#include "connection.hpp"
|
||||
#include "log.hpp"
|
||||
#include "tls_connection.hpp"
|
||||
#include "tools.hpp"
|
||||
#include "v2g_server.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fstream>
|
||||
#include <inttypes.h>
|
||||
#include <iostream>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "proxy.hpp"
|
||||
|
||||
#define DEFAULT_SOCKET_BACKLOG 3
|
||||
#define DEFAULT_TCP_PORT 61342
|
||||
#define DEFAULT_TLS_PORT 64110
|
||||
#define ERROR_SESSION_ALREADY_STARTED 2
|
||||
#define CLIENT_FIN_TIMEOUT 3000
|
||||
|
||||
/*!
|
||||
* \brief connection_create_socket This function creates a tcp/tls socket
|
||||
* \param sockaddr to bind the socket to an interface
|
||||
* \return Returns \c 0 on success, otherwise \c -1
|
||||
*/
|
||||
static int connection_create_socket(struct sockaddr_in6* sockaddr) {
|
||||
socklen_t addrlen = sizeof(*sockaddr);
|
||||
int s, enable = 1;
|
||||
static bool error_once = false;
|
||||
|
||||
/* create socket */
|
||||
s = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (s == -1) {
|
||||
if (!error_once) {
|
||||
dlog(DLOG_LEVEL_ERROR, "socket() failed: %s", strerror(errno));
|
||||
error_once = true;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1) {
|
||||
if (!error_once) {
|
||||
dlog(DLOG_LEVEL_ERROR, "setsockopt(SO_REUSEPORT) failed: %s", strerror(errno));
|
||||
error_once = true;
|
||||
}
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* bind it to interface */
|
||||
if (bind(s, reinterpret_cast<struct sockaddr*>(sockaddr), addrlen) == -1) {
|
||||
if (!error_once) {
|
||||
dlog(DLOG_LEVEL_WARNING, "bind() failed: %s", strerror(errno));
|
||||
error_once = true;
|
||||
}
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* listen on this socket */
|
||||
if (listen(s, DEFAULT_SOCKET_BACKLOG) == -1) {
|
||||
if (!error_once) {
|
||||
dlog(DLOG_LEVEL_ERROR, "listen() failed: %s", strerror(errno));
|
||||
error_once = true;
|
||||
}
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* retrieve the actual port number we are listening on */
|
||||
if (getsockname(s, reinterpret_cast<struct sockaddr*>(sockaddr), &addrlen) == -1) {
|
||||
if (!error_once) {
|
||||
dlog(DLOG_LEVEL_ERROR, "getsockname() failed: %s", strerror(errno));
|
||||
error_once = true;
|
||||
}
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief check_interface This function checks the interface name. The interface name is
|
||||
* configured automatically in case it is pre-initialized to “auto.
|
||||
* \param sockaddr to bind the socket to an interface
|
||||
* \return Returns \c 0 on success, otherwise \c -1
|
||||
*/
|
||||
int check_interface(struct v2g_context* v2g_ctx) {
|
||||
if (v2g_ctx == nullptr || v2g_ctx->if_name == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct ipv6_mreq mreq = {};
|
||||
std::memset(&mreq, 0, sizeof(mreq));
|
||||
|
||||
if (strcmp(v2g_ctx->if_name, "auto") == 0) {
|
||||
v2g_ctx->if_name = choose_first_ipv6_interface();
|
||||
}
|
||||
|
||||
if (v2g_ctx->if_name == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
mreq.ipv6mr_interface = if_nametoindex(v2g_ctx->if_name);
|
||||
if (!mreq.ipv6mr_interface) {
|
||||
dlog(DLOG_LEVEL_ERROR, "No such interface: %s", v2g_ctx->if_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (v2g_ctx->if_name == nullptr) ? -1 : 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief connection_init This function initilizes the tcp and tls interface.
|
||||
* \param v2g_context is the V2G context.
|
||||
* \return Returns \c 0 on success, otherwise \c -1
|
||||
*/
|
||||
int connection_init(struct v2g_context* v2g_ctx) {
|
||||
if (check_interface(v2g_ctx) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (v2g_ctx->tls_security != TLS_SECURITY_FORCE) {
|
||||
v2g_ctx->local_tcp_addr = static_cast<sockaddr_in6*>(calloc(1, sizeof(*v2g_ctx->local_tcp_addr)));
|
||||
if (v2g_ctx->local_tcp_addr == nullptr) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Failed to allocate memory for TCP address");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (v2g_ctx->tls_security != TLS_SECURITY_PROHIBIT) {
|
||||
v2g_ctx->local_tls_addr = static_cast<sockaddr_in6*>(calloc(1, sizeof(*v2g_ctx->local_tls_addr)));
|
||||
if (!v2g_ctx->local_tls_addr) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Failed to allocate memory for TLS address");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (v2g_ctx->local_tcp_addr) {
|
||||
get_interface_ipv6_address(v2g_ctx->if_name, ADDR6_TYPE_LINKLOCAL, v2g_ctx->local_tcp_addr);
|
||||
if (v2g_ctx->local_tls_addr) {
|
||||
// Handle allowing TCP with TLS (TLS_SECURITY_ALLOW)
|
||||
memcpy(v2g_ctx->local_tls_addr, v2g_ctx->local_tcp_addr, sizeof(*v2g_ctx->local_tls_addr));
|
||||
}
|
||||
} else {
|
||||
// Handle forcing TLS security (TLS_SECURITY_FORCE)
|
||||
get_interface_ipv6_address(v2g_ctx->if_name, ADDR6_TYPE_LINKLOCAL, v2g_ctx->local_tls_addr);
|
||||
}
|
||||
|
||||
if (v2g_ctx->local_tcp_addr) {
|
||||
char buffer[INET6_ADDRSTRLEN];
|
||||
|
||||
/*
|
||||
* When we bind with port = 0, the kernel assigns a dynamic port from the range configured
|
||||
* in /proc/sys/net/ipv4/ip_local_port_range. This is on a recent Ubuntu Linux e.g.
|
||||
* $ cat /proc/sys/net/ipv4/ip_local_port_range
|
||||
* 32768 60999
|
||||
* However, in ISO15118 spec the IANA range with 49152 to 65535 is referenced. So we have the
|
||||
* problem that the kernel (without further configuration - and we want to avoid this) could
|
||||
* hand out a port which is not "range compatible".
|
||||
* To fulfill the ISO15118 standard, we simply try to bind to static port numbers.
|
||||
*/
|
||||
v2g_ctx->local_tcp_addr->sin6_port = htons(DEFAULT_TCP_PORT);
|
||||
v2g_ctx->tcp_socket = connection_create_socket(v2g_ctx->local_tcp_addr);
|
||||
if (v2g_ctx->tcp_socket < 0) {
|
||||
/* retry until interface is ready */
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
if (inet_ntop(AF_INET6, &v2g_ctx->local_tcp_addr->sin6_addr, buffer, sizeof(buffer)) != nullptr) {
|
||||
dlog(DLOG_LEVEL_INFO, "TCP server on %s is listening on port [%s%%%" PRIu32 "]:%" PRIu16,
|
||||
v2g_ctx->if_name, buffer, v2g_ctx->local_tcp_addr->sin6_scope_id,
|
||||
ntohs(v2g_ctx->local_tcp_addr->sin6_port));
|
||||
} else {
|
||||
dlog(DLOG_LEVEL_ERROR, "TCP server on %s is listening, but inet_ntop failed: %s", v2g_ctx->if_name,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (v2g_ctx->local_tls_addr) {
|
||||
char buffer[INET6_ADDRSTRLEN];
|
||||
|
||||
/* see comment above for reason */
|
||||
v2g_ctx->local_tls_addr->sin6_port = htons(DEFAULT_TLS_PORT);
|
||||
|
||||
v2g_ctx->tls_socket.fd = connection_create_socket(v2g_ctx->local_tls_addr);
|
||||
if (v2g_ctx->tls_socket.fd < 0) {
|
||||
if (v2g_ctx->tcp_socket != -1) {
|
||||
/* free the TCP socket */
|
||||
close(v2g_ctx->tcp_socket);
|
||||
}
|
||||
/* retry until interface is ready */
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inet_ntop(AF_INET6, &v2g_ctx->local_tls_addr->sin6_addr, buffer, sizeof(buffer)) != nullptr) {
|
||||
dlog(DLOG_LEVEL_INFO, "TLS server on %s is listening on port [%s%%%" PRIu32 "]:%" PRIu16,
|
||||
v2g_ctx->if_name, buffer, v2g_ctx->local_tls_addr->sin6_scope_id,
|
||||
ntohs(v2g_ctx->local_tls_addr->sin6_port));
|
||||
} else {
|
||||
dlog(DLOG_LEVEL_INFO, "TLS server on %s is listening, but inet_ntop failed: %s", v2g_ctx->if_name,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
/* Sockets should be ready, leave the loop */
|
||||
break;
|
||||
}
|
||||
|
||||
if (v2g_ctx->local_tls_addr) {
|
||||
return tls::connection_init(v2g_ctx);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief is_sequence_timeout This function checks if a sequence timeout has occurred.
|
||||
* \param ts_start Is the time after waiting of the next request message.
|
||||
* \param ctx is the V2G context.
|
||||
* \return Returns \c true if a timeout has occurred, otherwise \c false
|
||||
*/
|
||||
bool is_sequence_timeout(struct timespec ts_start, struct v2g_context* ctx) {
|
||||
struct timespec ts_current;
|
||||
int sequence_timeout = V2G_SEQUENCE_TIMEOUT_60S;
|
||||
|
||||
if (((clock_gettime(CLOCK_MONOTONIC, &ts_current)) != 0) ||
|
||||
(timespec_to_ms(timespec_sub(ts_current, ts_start)) > sequence_timeout)) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Sequence timeout has occurred (message: %s)", v2g_msg_type[ctx->current_v2g_msg]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief connection_read This function reads from socket until requested bytes are received or sequence
|
||||
* timeout is reached
|
||||
* \param conn is the v2g connection context
|
||||
* \param buf is the buffer to store the v2g message
|
||||
* \param count is the number of bytes to read
|
||||
* \return Returns \c true if a timeout has occurred, otherwise \c false
|
||||
*/
|
||||
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, size_t count, bool read_complete) {
|
||||
struct timespec ts_start;
|
||||
int bytes_read = 0;
|
||||
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts_start) == -1) {
|
||||
dlog(DLOG_LEVEL_ERROR, "clock_gettime(ts_start) failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* loop until we got all requested bytes or sequence timeout DIN [V2G-DC-432]*/
|
||||
if (read_complete) {
|
||||
while ((bytes_read < count) && (is_sequence_timeout(ts_start, conn->ctx) == false) &&
|
||||
(conn->ctx->is_connection_terminated == false)) { // [V2G2-536]
|
||||
|
||||
int num_of_bytes;
|
||||
|
||||
/* use select for timeout handling */
|
||||
struct timeval tv;
|
||||
fd_set read_fds;
|
||||
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(conn->conn.socket_fd, &read_fds);
|
||||
|
||||
tv.tv_sec = conn->ctx->network_read_timeout / 1000;
|
||||
tv.tv_usec = (conn->ctx->network_read_timeout % 1000) * 1000;
|
||||
|
||||
num_of_bytes = select(conn->conn.socket_fd + 1, &read_fds, nullptr, nullptr, &tv);
|
||||
|
||||
if (num_of_bytes == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Zero fds ready means we timed out, so let upper loop check our sequence timeout */
|
||||
if (num_of_bytes == 0) {
|
||||
continue;
|
||||
}
|
||||
num_of_bytes = (int)read(conn->conn.socket_fd, &buf[bytes_read], count - bytes_read);
|
||||
|
||||
if (num_of_bytes == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* return when peer closed connection */
|
||||
if (num_of_bytes == 0)
|
||||
return bytes_read;
|
||||
|
||||
bytes_read += num_of_bytes;
|
||||
}
|
||||
} else {
|
||||
bytes_read = (int)read(conn->conn.socket_fd, buf, count);
|
||||
}
|
||||
|
||||
if (conn->ctx->is_connection_terminated == true) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Reading from tcp-socket aborted");
|
||||
return -2;
|
||||
}
|
||||
|
||||
return (ssize_t)bytes_read; // [V2G2-537] read bytes are currupted if reading from socket was interrupted
|
||||
// (V2G_SECC_Sequence_Timeout)
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief connection_read This function writes to socket until bytes are written to the socket
|
||||
* \param conn is the v2g connection context
|
||||
* \param buf is the buffer where the v2g message is stored
|
||||
* \param count is the number of bytes to write
|
||||
* \return Returns \c true if a timeout has occurred, otherwise \c false
|
||||
*/
|
||||
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, size_t count) {
|
||||
int bytes_written = 0;
|
||||
|
||||
/* loop until we got all requested bytes out */
|
||||
while (bytes_written < count) {
|
||||
int num_of_bytes;
|
||||
|
||||
num_of_bytes = (int)write(conn->conn.socket_fd, &buf[bytes_written], count - bytes_written);
|
||||
|
||||
if (num_of_bytes == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* return when peer closed connection */
|
||||
if (num_of_bytes == 0)
|
||||
return bytes_written;
|
||||
|
||||
bytes_written += num_of_bytes;
|
||||
}
|
||||
|
||||
return (ssize_t)bytes_written;
|
||||
}
|
||||
|
||||
static void wait_for_peer_close(int fd, int timeout_ms) {
|
||||
struct pollfd pfd = {};
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLIN | POLLHUP;
|
||||
|
||||
int rc = poll(&pfd, 1, timeout_ms);
|
||||
if (rc <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pfd.revents & (POLLIN | POLLHUP)) {
|
||||
char buf[64];
|
||||
while (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) > 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the 'main' function of a thread, which handles a TCP connection.
|
||||
*/
|
||||
void* connection_handle_tcp(void* data) {
|
||||
struct v2g_connection* conn = static_cast<struct v2g_connection*>(data);
|
||||
bool error_occurred{false};
|
||||
connection_handle(data);
|
||||
/* tear down connection gracefully */
|
||||
dlog(DLOG_LEVEL_INFO, "Multiplexer: Closing TCP connection");
|
||||
|
||||
/* some EV's did not like the immediate shutdown. Therefore we sleep for 2 seconds */
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
if (shutdown(conn->conn.socket_fd, SHUT_WR) == -1) {
|
||||
dlog(DLOG_LEVEL_ERROR, "shutdown() failed: %s", strerror(errno));
|
||||
error_occurred = true;
|
||||
}
|
||||
|
||||
/* wait briefly for peer FIN or timeout */
|
||||
wait_for_peer_close(conn->conn.socket_fd, CLIENT_FIN_TIMEOUT);
|
||||
|
||||
if (close(conn->conn.socket_fd) == -1) {
|
||||
dlog(DLOG_LEVEL_ERROR, "close() failed: %s", strerror(errno));
|
||||
error_occurred = true;
|
||||
}
|
||||
if (not error_occurred) {
|
||||
dlog(DLOG_LEVEL_INFO, "Multiplexer: TCP connection closed gracefully");
|
||||
}
|
||||
|
||||
free(conn);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the 'main' function of a thread, which handles a TCP connection.
|
||||
*/
|
||||
void* connection_handle(void* data) {
|
||||
struct v2g_connection* conn = static_cast<struct v2g_connection*>(data);
|
||||
int rv = 0;
|
||||
|
||||
bool iso20{false};
|
||||
|
||||
conn->buffer = static_cast<uint8_t*>(malloc(DEFAULT_BUFFER_SIZE));
|
||||
if (not conn->buffer) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* check if the v2g-session is already running in another thread, if not, handle v2g-connection */
|
||||
if (conn->ctx->state == 0) {
|
||||
iso20 = v2g_detect_iso20_support(conn);
|
||||
} else {
|
||||
rv = ERROR_SESSION_ALREADY_STARTED;
|
||||
dlog(DLOG_LEVEL_WARNING, "%s", "Closing tcp-connection. v2g-session is already running");
|
||||
}
|
||||
|
||||
uint16_t port = conn->ctx->proxy_port_iso2;
|
||||
conn->ctx->selected_iso20 = false;
|
||||
const bool iso20_proxy_enabled = conn->ctx->iso20_proxy_enabled;
|
||||
// Open TCP connection to the proxied module
|
||||
if (iso20 && iso20_proxy_enabled) {
|
||||
// Notify the proxy layer about the protocol decision
|
||||
conn->ctx->selected_iso20 = true;
|
||||
port = conn->ctx->proxy_port_iso20;
|
||||
} else if (iso20 && !iso20_proxy_enabled) {
|
||||
dlog(DLOG_LEVEL_INFO, "ISO-20 requested by EV, but ISO-20 is disabled in supported app protocols. "
|
||||
"Routing to ISO-2/DIN proxy.");
|
||||
}
|
||||
|
||||
int proxy_fd = proxy_connect(port, conn->ctx->proxy_if_name);
|
||||
|
||||
if (proxy_fd > 0) {
|
||||
EVLOG_info << "Connected to proxy module for " << (conn->ctx->selected_iso20 ? "ISO-20" : "ISO-2/DIN");
|
||||
conn->proxy(conn, proxy_fd);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int connection_proxy(struct v2g_connection* conn, int proxy_fd) {
|
||||
|
||||
dlog(DLOG_LEVEL_INFO, "Multiplexer: Proxy TCP->TCP");
|
||||
|
||||
int ev_fd = conn->conn.socket_fd;
|
||||
|
||||
// SupportedAppProtocolReq message is still in buffer, we need to forward it to the external stack
|
||||
write(proxy_fd, conn->buffer, conn->payload_len + 8);
|
||||
|
||||
struct pollfd poll_list[2];
|
||||
poll_list[0].fd = proxy_fd;
|
||||
poll_list[1].fd = ev_fd;
|
||||
poll_list[0].events = POLLIN;
|
||||
poll_list[1].events = POLLIN;
|
||||
|
||||
unsigned char buf[2048];
|
||||
|
||||
while (true) {
|
||||
|
||||
int ret = poll(poll_list, 2, -1);
|
||||
|
||||
if (ret == -1) {
|
||||
return -1; // poll error
|
||||
}
|
||||
|
||||
// Timed out, but we blocked forever. This could be a spurious wakeup, so just try again.
|
||||
if (ret == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (poll_list[0].revents & POLLIN) {
|
||||
// we can read from proxy (connection to local ISO module)
|
||||
int nrbytes = read(proxy_fd, buf, sizeof(buf));
|
||||
|
||||
if (nrbytes == 0) {
|
||||
break;
|
||||
}
|
||||
// write data to EV
|
||||
nrbytes = conn->write(conn, buf, nrbytes);
|
||||
}
|
||||
|
||||
if (poll_list[0].revents & POLLERR or poll_list[0].revents & POLLHUP or poll_list[0].revents & POLLNVAL) {
|
||||
// something is wrong with the TCP connection to the ISO module
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (poll_list[1].revents & POLLIN) {
|
||||
// we can read from EV
|
||||
int nrbytes = conn->read(conn, buf, sizeof(buf), false);
|
||||
if (nrbytes == 0) {
|
||||
break;
|
||||
}
|
||||
// write data to proxy
|
||||
nrbytes = write(proxy_fd, buf, nrbytes);
|
||||
}
|
||||
|
||||
if (poll_list[1].revents & POLLERR or poll_list[1].revents & POLLHUP or poll_list[1].revents & POLLNVAL) {
|
||||
// something is wrong with the TCP connection to the EV
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
close(proxy_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* connection_server(void* data) {
|
||||
struct v2g_context* ctx = static_cast<v2g_context*>(data);
|
||||
struct v2g_connection* conn = NULL;
|
||||
pthread_attr_t attr;
|
||||
|
||||
/* create the thread in detached state so we don't need to join every single one */
|
||||
if (pthread_attr_init(&attr) != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "pthread_attr_init failed: %s", strerror(errno));
|
||||
goto thread_exit;
|
||||
}
|
||||
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "pthread_attr_setdetachstate failed: %s", strerror(errno));
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
char client_addr[INET6_ADDRSTRLEN];
|
||||
struct sockaddr_in6 addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
/* cleanup old one and create new connection context */
|
||||
free(conn);
|
||||
conn = static_cast<v2g_connection*>(calloc(1, sizeof(*conn)));
|
||||
if (!conn) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Calloc failed: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
/* setup common stuff */
|
||||
conn->ctx = ctx;
|
||||
conn->read = &connection_read;
|
||||
conn->write = &connection_write;
|
||||
conn->proxy = &connection_proxy;
|
||||
|
||||
/* if this thread is the TLS thread, then connections are TLS secured;
|
||||
* return code is non-zero if equal so align it
|
||||
*/
|
||||
conn->is_tls_connection = false;
|
||||
|
||||
/* wait for an incoming connection */
|
||||
conn->conn.socket_fd = accept(ctx->tcp_socket, (struct sockaddr*)&addr, &addrlen);
|
||||
if (conn->conn.socket_fd == -1) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Accept(tcp) failed: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inet_ntop(AF_INET6, &addr, client_addr, sizeof(client_addr)) != NULL) {
|
||||
dlog(DLOG_LEVEL_INFO, "Incoming connection on %s from [%s]:%" PRIu16, ctx->if_name, client_addr,
|
||||
ntohs(addr.sin6_port));
|
||||
} else {
|
||||
dlog(DLOG_LEVEL_ERROR, "Incoming connection on %s, but inet_ntop failed: %s", ctx->if_name,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
// store the port to create a udp socket
|
||||
conn->ctx->udp_port = ntohs(addr.sin6_port);
|
||||
|
||||
if (pthread_create(&conn->thread_id, &attr, connection_handle_tcp, conn) != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "pthread_create() failed: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is up to the thread to cleanup conn */
|
||||
conn = NULL;
|
||||
}
|
||||
|
||||
thread_exit:
|
||||
if (pthread_attr_destroy(&attr) != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "pthread_attr_destroy failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* clean up if dangling */
|
||||
free(conn);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int connection_start_servers(struct v2g_context* ctx) {
|
||||
int rv, tcp_started = 0;
|
||||
|
||||
if (ctx->tcp_socket != -1) {
|
||||
rv = pthread_create(&ctx->tcp_thread, NULL, connection_server, ctx);
|
||||
if (rv != 0) {
|
||||
dlog(DLOG_LEVEL_ERROR, "pthread_create(tcp) failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
tcp_started = 1;
|
||||
}
|
||||
|
||||
if (ctx->tls_socket.fd != -1) {
|
||||
rv = tls::connection_start_server(ctx);
|
||||
if (rv != 0) {
|
||||
if (tcp_started) {
|
||||
pthread_cancel(ctx->tcp_thread);
|
||||
pthread_join(ctx->tcp_thread, NULL);
|
||||
}
|
||||
dlog(DLOG_LEVEL_ERROR, "pthread_create(tls) failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_udp_socket(const uint16_t udp_port, const char* interface_name) {
|
||||
constexpr auto LINK_LOCAL_MULTICAST = "ff02::1";
|
||||
|
||||
int udp_socket = socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
if (udp_socket < 0) {
|
||||
EVLOG_error << "Could not create socket: " << strerror(errno);
|
||||
return udp_socket;
|
||||
}
|
||||
|
||||
// source setup
|
||||
|
||||
// find port between 49152-65535
|
||||
auto could_bind = false;
|
||||
auto source_port = 49152;
|
||||
for (; source_port < 65535; source_port++) {
|
||||
sockaddr_in6 source_address = {AF_INET6, htons(source_port)};
|
||||
if (bind(udp_socket, reinterpret_cast<sockaddr*>(&source_address), sizeof(sockaddr_in6)) == 0) {
|
||||
could_bind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!could_bind) {
|
||||
EVLOG_error << "Could not bind: " << strerror(errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVLOG_info << "UDP socket bound to source port: " << source_port;
|
||||
|
||||
const auto index = if_nametoindex(interface_name);
|
||||
auto mreq = ipv6_mreq{};
|
||||
mreq.ipv6mr_interface = index;
|
||||
if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &mreq.ipv6mr_multiaddr) <= 0) {
|
||||
EVLOG_error << "Failed to setup multicast address" << strerror(errno);
|
||||
return -1;
|
||||
}
|
||||
if (setsockopt(udp_socket, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
||||
EVLOG_error << "Could not add multicast group membership: " << strerror(errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (setsockopt(udp_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)) < 0) {
|
||||
EVLOG_error << "Could not set interface name: " << interface_name << "with error: " << strerror(errno);
|
||||
}
|
||||
|
||||
// destination setup
|
||||
sockaddr_in6 destination_address = {AF_INET6, htons(udp_port)};
|
||||
if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &destination_address.sin6_addr) <= 0) {
|
||||
EVLOG_error << "Failed to setup server address" << strerror(errno);
|
||||
}
|
||||
const auto connected =
|
||||
connect(udp_socket, reinterpret_cast<sockaddr*>(&destination_address), sizeof(sockaddr_in6)) == 0;
|
||||
if (!connected) {
|
||||
EVLOG_error << "Could not connect: " << strerror(errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return udp_socket;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (C) 2022 chargebyte GmbH
|
||||
// Copyright (C) 2022 Contributors to EVerest
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "v2g_ctx.hpp"
|
||||
|
||||
/*!
|
||||
* \brief initialise TCP/TLS connections
|
||||
* \param ctx the V2G context
|
||||
* \return 0 on success
|
||||
*/
|
||||
int connection_init(struct v2g_context* ctx);
|
||||
|
||||
/*!
|
||||
* \brief start TCP/TLS servers
|
||||
* \param ctx the V2G context
|
||||
* \return 0 on success
|
||||
*/
|
||||
int connection_start_servers(struct v2g_context* ctx);
|
||||
int create_udp_socket(const uint16_t udp_port, const char* interface_name);
|
||||
|
||||
/*!
|
||||
* \brief check for V2G message sequence timeout
|
||||
* \param ts_start start time
|
||||
* \param ctx the V2G context
|
||||
* \return true on timeout
|
||||
*/
|
||||
bool is_sequence_timeout(struct timespec ts_start, struct v2g_context* ctx);
|
||||
|
||||
/*!
|
||||
* \brief connection_read This abstracts a read from the connection socket, so that higher level functions
|
||||
* are not required to distinguish between TCP and TLS connections.
|
||||
* \param conn v2g connection context
|
||||
* \param buf buffer to store received message sequence.
|
||||
* \param count number of read bytes.
|
||||
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
|
||||
* -2 for closed connection */
|
||||
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, std::size_t count, bool read_complete);
|
||||
|
||||
/*!
|
||||
* \brief connection_write This abstracts a write to the connection socket, so that higher level functions
|
||||
* are not required to distinguish between TCP and TLS connections.
|
||||
* \param conn v2g connection context
|
||||
* \param buf buffer to store received message sequence.
|
||||
* \param count size of the buffer
|
||||
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
|
||||
* -2 for closed connection */
|
||||
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, std::size_t count);
|
||||
|
||||
void* connection_handle_tcp(void* data);
|
||||
void* connection_handle(void* data);
|
||||
int connection_proxy(struct v2g_connection* conn, int proxy_fd);
|
||||
|
||||
#endif /* CONNECTION_H */
|
||||
61
tools/EVerest-main/modules/EVSE/IsoMux/connection/proxy.hpp
Normal file
61
tools/EVerest-main/modules/EVSE/IsoMux/connection/proxy.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (C) 2022 chargebyte GmbH
|
||||
// Copyright (C) 2022 Contributors to EVerest
|
||||
|
||||
#ifndef ISOMUX_PROXY_H
|
||||
#define ISOMUX_PROXY_H
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <cstddef>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../tools.hpp"
|
||||
|
||||
/*!
|
||||
* \brief connect to a local V2G server
|
||||
* \param port port to connect to
|
||||
* \param proxy_if_name network interface whose IPv6 link-local address is used
|
||||
* as the connection target. Pass nullptr (default) to fall back to ::1.
|
||||
* \return 0 on failure, otherwise the socket
|
||||
*/
|
||||
inline int proxy_connect(uint16_t port, const char* proxy_if_name = nullptr) {
|
||||
|
||||
int sock_fd = -1;
|
||||
struct sockaddr_in6 server_addr {};
|
||||
|
||||
/* Create socket for communication with server */
|
||||
sock_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock_fd == -1) {
|
||||
perror("socket()");
|
||||
return -1;
|
||||
}
|
||||
|
||||
server_addr.sin6_family = AF_INET6;
|
||||
|
||||
if (proxy_if_name != nullptr && proxy_if_name[0] != '\0') {
|
||||
/* Connect to the link-local address of the dedicated proxy interface */
|
||||
if (get_interface_ipv6_address(proxy_if_name, ADDR6_TYPE_LINKLOCAL, &server_addr) != 0) {
|
||||
perror("proxy_connect: get_interface_ipv6_address()");
|
||||
close(sock_fd);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
/* Fall back to loopback */
|
||||
inet_pton(AF_INET6, "::1", &server_addr.sin6_addr);
|
||||
}
|
||||
|
||||
server_addr.sin6_port = htons(port);
|
||||
|
||||
/* Try to do TCP handshake with server */
|
||||
int ret = connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
|
||||
if (ret == -1) {
|
||||
perror("connect()");
|
||||
close(sock_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sock_fd;
|
||||
}
|
||||
|
||||
#endif /* ISOMUX_PROXY_H */
|
||||
@@ -0,0 +1,415 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "tls_connection.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "log.hpp"
|
||||
#include "v2g.hpp"
|
||||
#include "v2g_server.hpp"
|
||||
#include <everest/tls/tls.hpp>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
// used when ctx->network_read_timeout_tls is 0
|
||||
constexpr int default_timeout_ms = 1000;
|
||||
|
||||
void process_connection_thread(std::shared_ptr<tls::ServerConnection> con, struct v2g_context* ctx) {
|
||||
assert(con != nullptr);
|
||||
assert(ctx != nullptr);
|
||||
|
||||
openssl::pkey_ptr contract_public_key{nullptr, nullptr};
|
||||
auto connection = std::make_unique<v2g_connection>();
|
||||
connection->ctx = ctx;
|
||||
connection->is_tls_connection = true;
|
||||
connection->read = &tls::connection_read;
|
||||
connection->write = &tls::connection_write;
|
||||
connection->proxy = &tls::connection_proxy;
|
||||
connection->tls_connection = con.get();
|
||||
connection->pubkey = &contract_public_key;
|
||||
|
||||
dlog(DLOG_LEVEL_INFO, "Incoming TLS connection");
|
||||
|
||||
bool loop{true};
|
||||
while (loop) {
|
||||
loop = false;
|
||||
const auto result = con->accept();
|
||||
switch (result) {
|
||||
case tls::Connection::result_t::success:
|
||||
|
||||
// TODO(james-ctc) v2g_ctx->tls_key_logging
|
||||
|
||||
if (ctx->state == 0) {
|
||||
const auto rv = ::connection_handle(connection.get());
|
||||
dlog(DLOG_LEVEL_INFO, "connection_handle exited with %d", rv);
|
||||
} else {
|
||||
dlog(DLOG_LEVEL_INFO, "%s", "Closing tls-connection. v2g-session is already running");
|
||||
}
|
||||
|
||||
con->shutdown();
|
||||
break;
|
||||
case tls::Connection::result_t::want_read:
|
||||
case tls::Connection::result_t::want_write:
|
||||
loop = con->wait_for(result, default_timeout_ms) == tls::Connection::result_t::success;
|
||||
break;
|
||||
case tls::Connection::result_t::closed:
|
||||
case tls::Connection::result_t::timeout:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_new_connection_cb(tls::Server::ConnectionPtr&& con, struct v2g_context* ctx) {
|
||||
assert(con != nullptr);
|
||||
assert(ctx != nullptr);
|
||||
// create a thread to process this connection
|
||||
try {
|
||||
// passing unique pointers through thread parameters is problematic
|
||||
std::shared_ptr<tls::ServerConnection> connection(con.release());
|
||||
std::thread connection_loop(process_connection_thread, connection, ctx);
|
||||
connection_loop.detach();
|
||||
} catch (const std::system_error&) {
|
||||
// unable to start thread
|
||||
dlog(DLOG_LEVEL_ERROR, "pthread_create() failed: %s", strerror(errno));
|
||||
con->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void server_loop_thread(struct v2g_context* ctx) {
|
||||
assert(ctx != nullptr);
|
||||
assert(ctx->tls_server != nullptr);
|
||||
const auto res = ctx->tls_server->serve([ctx](auto con) { handle_new_connection_cb(std::move(con), ctx); });
|
||||
if (res != tls::Server::state_t::stopped) {
|
||||
dlog(DLOG_LEVEL_ERROR, "tls::Server failed to serve");
|
||||
}
|
||||
}
|
||||
|
||||
tls::Server::OptionalConfig configure_ssl(struct v2g_context* ctx) {
|
||||
try {
|
||||
dlog(DLOG_LEVEL_WARNING, "configure_ssl");
|
||||
auto config = std::make_unique<tls::Server::config_t>();
|
||||
|
||||
// The config of interest is from Evse Security, no point in updating
|
||||
// config when there is a problem
|
||||
|
||||
if (build_config(*config, ctx)) {
|
||||
return {{std::move(config)}};
|
||||
}
|
||||
} catch (const std::bad_alloc&) {
|
||||
dlog(DLOG_LEVEL_ERROR, "unable to create TLS config");
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace tls {
|
||||
|
||||
int connection_init(struct v2g_context* ctx) {
|
||||
using state_t = tls::Server::state_t;
|
||||
|
||||
assert(ctx != nullptr);
|
||||
assert(ctx->tls_server != nullptr);
|
||||
assert(ctx->r_security != nullptr);
|
||||
|
||||
int res{-1};
|
||||
tls::Server::config_t config;
|
||||
|
||||
// build_config can fail due to issues with Evse Security,
|
||||
// this can be retried later. Not treated as an error.
|
||||
(void)build_config(config, ctx);
|
||||
|
||||
// apply config
|
||||
ctx->tls_server->stop();
|
||||
ctx->tls_server->wait_stopped();
|
||||
const auto result = ctx->tls_server->init(config, [ctx]() { return configure_ssl(ctx); });
|
||||
if ((result == state_t::init_complete) || (result == state_t::init_socket)) {
|
||||
res = 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int connection_start_server(struct v2g_context* ctx) {
|
||||
assert(ctx != nullptr);
|
||||
assert(ctx->tls_server != nullptr);
|
||||
|
||||
// only starts the TLS server
|
||||
|
||||
int res = 0;
|
||||
try {
|
||||
ctx->tls_server->stop();
|
||||
ctx->tls_server->wait_stopped();
|
||||
if (ctx->tls_server->state() == tls::Server::state_t::stopped) {
|
||||
// need to re-initialise
|
||||
tls::connection_init(ctx);
|
||||
}
|
||||
std::thread serve_loop(server_loop_thread, ctx);
|
||||
serve_loop.detach();
|
||||
ctx->tls_server->wait_running();
|
||||
} catch (const std::system_error&) {
|
||||
// unable to start thread (caller logs failure)
|
||||
res = -1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, const std::size_t count, bool read_complete) {
|
||||
assert(conn != nullptr);
|
||||
assert(conn->tls_connection != nullptr);
|
||||
|
||||
ssize_t result{0};
|
||||
std::size_t bytes_read{0};
|
||||
timespec ts_start{};
|
||||
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts_start) == -1) {
|
||||
dlog(DLOG_LEVEL_ERROR, "clock_gettime(ts_start) failed: %s", strerror(errno));
|
||||
result = -1;
|
||||
}
|
||||
|
||||
while ((bytes_read < count) && (result >= 0)) {
|
||||
const std::size_t remaining = count - bytes_read;
|
||||
std::size_t bytes_in{0};
|
||||
auto* ptr = reinterpret_cast<std::byte*>(&buf[bytes_read]);
|
||||
|
||||
const auto read_res = conn->tls_connection->read(ptr, remaining, bytes_in);
|
||||
switch (read_res) {
|
||||
case tls::Connection::result_t::success:
|
||||
bytes_read += bytes_in;
|
||||
break;
|
||||
case tls::Connection::result_t::want_read:
|
||||
case tls::Connection::result_t::want_write:
|
||||
conn->tls_connection->wait_for(read_res, default_timeout_ms);
|
||||
break;
|
||||
case tls::Connection::result_t::timeout:
|
||||
// the MBedTLS code loops on timeout, is_sequence_timeout() is used instead
|
||||
break;
|
||||
case tls::Connection::result_t::closed:
|
||||
default:
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (conn->ctx->is_connection_terminated) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Reading from tcp-socket aborted");
|
||||
conn->tls_connection->shutdown();
|
||||
result = -2;
|
||||
}
|
||||
|
||||
if (::is_sequence_timeout(ts_start, conn->ctx)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (not read_complete) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (result < 0) ? result : static_cast<ssize_t>(bytes_read);
|
||||
}
|
||||
|
||||
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, std::size_t count) {
|
||||
assert(conn != nullptr);
|
||||
assert(conn->tls_connection != nullptr);
|
||||
|
||||
ssize_t result{0};
|
||||
std::size_t bytes_written{0};
|
||||
|
||||
while ((bytes_written < count) && (result >= 0)) {
|
||||
const std::size_t remaining = count - bytes_written;
|
||||
std::size_t bytes_out{0};
|
||||
const auto* ptr = reinterpret_cast<std::byte*>(&buf[bytes_written]);
|
||||
|
||||
const auto write_res = conn->tls_connection->write(ptr, remaining, bytes_out);
|
||||
switch (write_res) {
|
||||
case tls::Connection::result_t::success:
|
||||
bytes_written += bytes_out;
|
||||
break;
|
||||
case tls::Connection::result_t::want_read:
|
||||
case tls::Connection::result_t::want_write:
|
||||
conn->tls_connection->wait_for(write_res, default_timeout_ms);
|
||||
break;
|
||||
case tls::Connection::result_t::timeout:
|
||||
// the MBedTLS code loops on timeout
|
||||
break;
|
||||
case tls::Connection::result_t::closed:
|
||||
default:
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((result == -1) && (conn->tls_connection->state() == tls::Connection::state_t::closed)) {
|
||||
// if the connection has closed - return the number of bytes sent
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return (result < 0) ? result : static_cast<ssize_t>(bytes_written);
|
||||
}
|
||||
|
||||
bool build_config(tls::Server::config_t& config, struct v2g_context* ctx) {
|
||||
assert(ctx != nullptr);
|
||||
assert(ctx->r_security != nullptr);
|
||||
|
||||
using types::evse_security::CaCertificateType;
|
||||
using types::evse_security::EncodingFormat;
|
||||
using types::evse_security::GetCertificateInfoStatus;
|
||||
using types::evse_security::LeafCertificateType;
|
||||
|
||||
/*
|
||||
* libevse-security checks for an optional password and when one
|
||||
* isn't set is uses an empty string as the password rather than nullptr.
|
||||
* hence private keys are always encrypted.
|
||||
*/
|
||||
|
||||
bool bResult{false};
|
||||
|
||||
config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
|
||||
config.ciphersuites = ""; // disable TLS 1.3
|
||||
config.verify_client = false; // contract certificate managed in-band in 15118-2
|
||||
|
||||
// use the existing configured socket
|
||||
// TODO(james-ctc): switch to server socket init code otherwise there
|
||||
// may be issues with reinitialisation
|
||||
config.socket = ctx->tls_socket.fd;
|
||||
config.io_timeout_ms = static_cast<std::int32_t>(ctx->network_read_timeout_tls);
|
||||
|
||||
config.tls_key_logging = ctx->tls_key_logging;
|
||||
|
||||
// information from libevse-security
|
||||
const auto cert_info =
|
||||
ctx->r_security->call_get_leaf_certificate_info(LeafCertificateType::V2G, EncodingFormat::PEM, false);
|
||||
if (cert_info.status != GetCertificateInfoStatus::Accepted) {
|
||||
dlog(DLOG_LEVEL_ERROR, "Failed to read cert_info! Not Accepted");
|
||||
} else {
|
||||
if (cert_info.info) {
|
||||
const auto& info = cert_info.info.value();
|
||||
const auto cert_path = info.certificate.value_or("");
|
||||
const auto key_path = info.key;
|
||||
|
||||
// workaround (see above libevse-security comment)
|
||||
const auto key_password = info.password.value_or("");
|
||||
|
||||
auto& ref = config.chains.emplace_back();
|
||||
ref.certificate_chain_file = cert_path.c_str();
|
||||
ref.private_key_file = key_path.c_str();
|
||||
ref.private_key_password = key_password.c_str();
|
||||
|
||||
if (info.ocsp) {
|
||||
for (const auto& ocsp : info.ocsp.value()) {
|
||||
const char* file{nullptr};
|
||||
if (ocsp.ocsp_path) {
|
||||
file = ocsp.ocsp_path.value().c_str();
|
||||
}
|
||||
ref.ocsp_response_files.push_back(file);
|
||||
}
|
||||
}
|
||||
|
||||
bResult = true;
|
||||
} else {
|
||||
dlog(DLOG_LEVEL_ERROR, "Failed to read cert_info! Empty response");
|
||||
}
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
int connection_proxy(struct v2g_connection* conn, int proxy_fd) {
|
||||
|
||||
dlog(DLOG_LEVEL_INFO, "Multiplexer: Proxy TLS->TCP");
|
||||
int ev_fd = conn->tls_connection->socket(); // underlying socket of TLS connection
|
||||
|
||||
// SupportedAppProtocolReq message is still in buffer, we need to forward it to the external stack
|
||||
write(proxy_fd, conn->buffer, conn->payload_len + 8);
|
||||
|
||||
struct pollfd poll_list[2];
|
||||
poll_list[0].fd = proxy_fd;
|
||||
poll_list[1].fd = ev_fd;
|
||||
poll_list[0].events = POLLIN;
|
||||
poll_list[1].events = POLLIN;
|
||||
|
||||
unsigned char buf[2048];
|
||||
|
||||
// Set reading to (more or less) non-blocking
|
||||
conn->tls_connection->set_read_timeout(10);
|
||||
|
||||
while (true) {
|
||||
// Note we cannot simply poll on the underlying system socket for TLS connection
|
||||
// as it does not guarantee that SSL_read/write will not block after the poll
|
||||
// (an SSL_read my trigger an actual write or multiple reads on the system socket)
|
||||
// So we have to try a non-blocking SSL_read first, openssl will then tell us
|
||||
// what to wait for on the socket before we try again (read, write or both)
|
||||
|
||||
auto r = conn->read(conn, buf, sizeof(buf), false);
|
||||
|
||||
if (r < 0) {
|
||||
// something is wrong with the connection, exiting...
|
||||
break;
|
||||
} else if (r > 0) {
|
||||
// successfully read bytes, forward to proxy module
|
||||
write(proxy_fd, buf, r);
|
||||
}
|
||||
|
||||
// check if SSL was actually waiting on write
|
||||
int e = SSL_get_error(conn->tls_connection->ssl_context(), r);
|
||||
if (e == SSL_ERROR_WANT_WRITE) {
|
||||
poll_list[1].events = POLLIN | POLLOUT;
|
||||
} else {
|
||||
poll_list[1].events = POLLIN;
|
||||
}
|
||||
|
||||
int ret = poll(poll_list, 2, -1);
|
||||
|
||||
if (ret == -1) {
|
||||
return -1; // poll error
|
||||
}
|
||||
|
||||
// Timed out, but we blocked forever. This could be a spurious wakeup, so just try again.
|
||||
if (ret == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (poll_list[0].revents & POLLIN) {
|
||||
// we can read from proxy (connection to local ISO module)
|
||||
int nrbytes = read(proxy_fd, buf, sizeof(buf));
|
||||
|
||||
if (nrbytes == 0) {
|
||||
break;
|
||||
}
|
||||
// write data to EV
|
||||
nrbytes = conn->write(conn, buf, nrbytes);
|
||||
}
|
||||
|
||||
if (poll_list[0].revents & POLLERR or poll_list[0].revents & POLLHUP or poll_list[0].revents & POLLNVAL) {
|
||||
// something is wrong with the TCP connection to the ISO module
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (poll_list[1].revents & POLLIN or poll_list[1].revents & POLLOUT) {
|
||||
// we can read from / write to the EV raw socket, just continue here.
|
||||
// The actual SSL_read() will happen at the beginning of the loop
|
||||
continue;
|
||||
}
|
||||
|
||||
if (poll_list[1].revents & POLLERR or poll_list[1].revents & POLLHUP or poll_list[1].revents & POLLNVAL) {
|
||||
// something is wrong with the TCP connection to the EV
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
close(proxy_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace tls
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef TLS_CONNECTION_HPP_
|
||||
#define TLS_CONNECTION_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <everest/tls/tls.hpp>
|
||||
#include <unistd.h>
|
||||
|
||||
struct v2g_context;
|
||||
struct v2g_connection;
|
||||
|
||||
namespace tls {
|
||||
|
||||
/*!
|
||||
* \param ctx v2g connection context
|
||||
* \return returns 0 on succss and -1 on error
|
||||
*/
|
||||
int connection_init(struct v2g_context* ctx);
|
||||
|
||||
/*!
|
||||
* \param ctx v2g connection context
|
||||
* \return returns 0 on succss and -1 on error
|
||||
*/
|
||||
int connection_start_server(struct v2g_context* ctx);
|
||||
|
||||
/*!
|
||||
* \brief connection_read This abstracts a read from the connection socket, so that higher level functions
|
||||
* are not required to distinguish between TCP and TLS connections.
|
||||
* \param conn v2g connection context
|
||||
* \param buf buffer to store received message sequence.
|
||||
* \param count number of read bytes.
|
||||
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
|
||||
* -2 for closed connection */
|
||||
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, std::size_t count, bool read_complete);
|
||||
|
||||
/*!
|
||||
* \brief connection_write This abstracts a write to the connection socket, so that higher level functions
|
||||
* are not required to distinguish between TCP and TLS connections.
|
||||
* \param conn v2g connection context
|
||||
* \param buf buffer to store received message sequence.
|
||||
* \param count size of the buffer
|
||||
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
|
||||
* -2 for closed connection */
|
||||
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, std::size_t count);
|
||||
|
||||
/*!
|
||||
* \brief build_config This builds the TLS server configuration based on the v2g context.
|
||||
* \param config TLS server configuration to be filled
|
||||
* \param ctx v2g connection context
|
||||
* \return Returns true if the configuration was built successfully, otherwise false.
|
||||
*/
|
||||
bool build_config(tls::Server::config_t& config, struct v2g_context* ctx);
|
||||
|
||||
int connection_proxy(struct v2g_connection* conn, int proxy_fd);
|
||||
|
||||
} // namespace tls
|
||||
|
||||
#endif // TLS_CONNECTION_HPP_
|
||||
Reference in New Issue
Block a user