mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
feat(tests): Add a ConnectionManager that can replay PCAP network dumps
This commit is contained in:
parent
be47bc57d2
commit
6b64ca3519
2
.github/workflows/build_linux.yml
vendored
2
.github/workflows/build_linux.yml
vendored
@ -220,7 +220,7 @@ jobs:
|
||||
if: ${{ matrix.build.runs_on == '' || matrix.build.runs_on == matrix.os }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -qq python3-sphinx graphviz check
|
||||
sudo apt-get install -y -qq python3-sphinx graphviz check libpcap-dev
|
||||
${{ matrix.build.cmd_deps }}
|
||||
- name: ${{ matrix.build.name }}
|
||||
if: ${{ matrix.build.runs_on == '' || matrix.build.runs_on == matrix.os }}
|
||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -qq python3-sphinx graphviz check
|
||||
sudo apt-get install -y -qq tcc clang-14 clang-tools-14 valgrind mosquitto
|
||||
sudo apt-get install -y -qq libmbedtls-dev openssl
|
||||
sudo apt-get install -y -qq libmbedtls-dev openssl libpcap-dev
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
@ -21,6 +21,7 @@ Building with CMake on Ubuntu or Debian
|
||||
sudo apt-get install cmake-curses-gui # for the ccmake graphical interface
|
||||
sudo apt-get install libmbedtls-dev # for encryption support
|
||||
sudo apt-get install check libsubunit-dev # for unit tests
|
||||
sudo apt-get install libpcap-dev # for network-replay unit tests
|
||||
sudo apt-get install python3-sphinx graphviz # for documentation generation
|
||||
sudo apt-get install python3-sphinx-rtd-theme # documentation style
|
||||
sudo apt-get install libavahi-client-dev libavahi-common-dev # for LDS-ME (multicast discovery)
|
||||
|
@ -77,6 +77,14 @@ set(test_plugin_sources
|
||||
${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_policy.c
|
||||
${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_networklayers.c)
|
||||
|
||||
# Network replay tests currently require a Linux environment.
|
||||
# Also only do it for 64bit builds, as there is a bug in the Debian multi-arch installation.
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND NOT UA_FORCE_32BIT)
|
||||
list(APPEND test_plugin_sources
|
||||
${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_networklayers_pcap.c)
|
||||
list(APPEND LIBS pcap)
|
||||
endif()
|
||||
|
||||
if(UA_ENABLE_PUBSUB)
|
||||
# Add ethernet_config.h to ensure the tests are rebuild when it changes
|
||||
list(APPEND test_plugin_sources ${PROJECT_SOURCE_DIR}/tests/pubsub/ethernet_config.h)
|
||||
|
@ -15,6 +15,11 @@ extern UA_ByteString *testConnectionLastSentBuf;
|
||||
|
||||
extern UA_ConnectionManager testConnectionManagerTCP;
|
||||
|
||||
/* Network Manager which accepts a TCP connection and replays the incoming TCP
|
||||
* packets from a pcap dump file */
|
||||
UA_ConnectionManager *
|
||||
ConnectionManage_replayPCAP(const char *pcap_file, UA_Boolean client);
|
||||
|
||||
_UA_END_DECLS
|
||||
|
||||
#endif /* TESTING_NETWORKLAYERS_H_ */
|
||||
|
321
tests/testing-plugins/testing_networklayers_pcap.c
Normal file
321
tests/testing-plugins/testing_networklayers_pcap.c
Normal file
@ -0,0 +1,321 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "testing_networklayers.h"
|
||||
#include "../../arch/posix/eventloop_posix.h"
|
||||
|
||||
#include <pcap.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <net/ethernet.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
/* This assumes the POSIX EventLoop. It replays TCP packets from a pcap file. It
|
||||
* registers a unix socket for self-triggering in the EventLoop. The TCP
|
||||
* connections are handled internally and not registered in the EventLoop. */
|
||||
|
||||
#define PCAP_MAX_CONNECTIONS 16
|
||||
|
||||
typedef struct {
|
||||
unsigned id;
|
||||
UA_ConnectionState state;
|
||||
|
||||
/* For now it suffices to compare the ports...
|
||||
char serverIP[INET_ADDRSTRLEN];
|
||||
char clientIP[INET_ADDRSTRLEN]; */
|
||||
u_int serverPort;
|
||||
u_int clientPort;
|
||||
|
||||
void *application;
|
||||
void *context;
|
||||
UA_ConnectionManager_connectionCallback connectionCallback;
|
||||
} PCAPConnection;
|
||||
|
||||
typedef struct {
|
||||
UA_ConnectionManager cm;
|
||||
pcap_t *fp;
|
||||
UA_RegisteredFD rfd; /* Self-pipe to trigger activity on connections */
|
||||
int pipe_fd;
|
||||
UA_Boolean client; /* Are we client or server? */
|
||||
PCAPConnection connections[PCAP_MAX_CONNECTIONS];
|
||||
unsigned fdCount;
|
||||
} PCAPConnectionManager;
|
||||
|
||||
static void
|
||||
flushPipe(int fd) {
|
||||
char buf[128];
|
||||
ssize_t i;
|
||||
do {
|
||||
i = read(fd, buf, 128);
|
||||
} while(i > 0);
|
||||
}
|
||||
|
||||
static void
|
||||
pcapCloseConnection(PCAPConnectionManager *pcm, PCAPConnection *c) {
|
||||
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop;
|
||||
c->connectionCallback(&pcm->cm, (uintptr_t)c->clientPort, c->application,
|
||||
&c->context, UA_CONNECTIONSTATE_CLOSING,
|
||||
NULL, UA_BYTESTRING_NULL);
|
||||
c->state = UA_CONNECTIONSTATE_CLOSED;
|
||||
}
|
||||
|
||||
static void
|
||||
processPacket(PCAPConnectionManager *pcm, const u_char *data, size_t dataLen) {
|
||||
const struct ether_header *ethHeader = (const struct ether_header*)data;
|
||||
if(ntohs(ethHeader->ether_type) != ETHERTYPE_IP)
|
||||
return;
|
||||
|
||||
const struct ip *ipHeader = (const struct ip*)(data + sizeof(struct ether_header));
|
||||
if(ipHeader->ip_p != IPPROTO_TCP)
|
||||
return;
|
||||
|
||||
const struct tcphdr *tcpHeader =
|
||||
(const struct tcphdr*)((const u_char*)ipHeader + sizeof(struct ip));
|
||||
u_int sourcePort = ntohs(tcpHeader->source);
|
||||
u_int destPort = ntohs(tcpHeader->dest);
|
||||
|
||||
/* A connection is fully opening. Store IP+port and notify the application.
|
||||
* ATTENTION! This assumes that only one connection is _OPENING at a time. */
|
||||
if(tcpHeader->th_flags & TH_SYN &&
|
||||
!(tcpHeader->th_flags & TH_ACK)) {
|
||||
for(size_t i = 0; i < PCAP_MAX_CONNECTIONS; i++) {
|
||||
PCAPConnection *c = &pcm->connections[i];
|
||||
if(c->state == UA_CONNECTIONSTATE_OPENING) {
|
||||
c->clientPort = sourcePort;
|
||||
c->serverPort = destPort;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Find the connection which matches the ports.
|
||||
* This relies on the client-side ports being randomized (unique). */
|
||||
PCAPConnection *c = NULL;
|
||||
for(size_t i = 0; i < PCAP_MAX_CONNECTIONS; i++) {
|
||||
PCAPConnection *tmp = &pcm->connections[i];
|
||||
if(tmp->state == UA_CONNECTIONSTATE_CLOSED ||
|
||||
tmp->state == UA_CONNECTIONSTATE_CLOSING)
|
||||
continue;
|
||||
if(pcm->client) {
|
||||
if(sourcePort != tmp->serverPort || destPort != tmp->clientPort)
|
||||
continue;
|
||||
} else {
|
||||
if(sourcePort != tmp->clientPort || destPort != tmp->serverPort)
|
||||
continue;
|
||||
}
|
||||
c = tmp;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!c)
|
||||
return;
|
||||
|
||||
c->state = UA_CONNECTIONSTATE_ESTABLISHED;
|
||||
|
||||
/* Use the packet */
|
||||
UA_ByteString payload;
|
||||
payload.data = ((u_char*)(uintptr_t)tcpHeader) + (tcpHeader->doff * 4);
|
||||
payload.length =
|
||||
dataLen - (sizeof(struct ether_header) + sizeof(struct ip) + (tcpHeader->doff * 4));
|
||||
|
||||
/* Process the packet */
|
||||
c->connectionCallback(&pcm->cm, (uintptr_t)c->id, c->application, &c->context,
|
||||
UA_CONNECTIONSTATE_ESTABLISHED, NULL, payload);
|
||||
|
||||
/* Close the connection when FIN is received */
|
||||
if(tcpHeader->th_flags & TH_FIN)
|
||||
c->state = UA_CONNECTIONSTATE_CLOSING;
|
||||
}
|
||||
|
||||
static void
|
||||
pcapActivityCallback(UA_EventSource *es, UA_RegisteredFD *rfd, short event) {
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)es;
|
||||
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop;
|
||||
|
||||
/* Re-arm the self-pipe by reading all waiting data */
|
||||
flushPipe(rfd->fd);
|
||||
|
||||
/* Close connections in the _CLOSING state */
|
||||
for(size_t i = 0; i < PCAP_MAX_CONNECTIONS; i++) {
|
||||
if(pcm->connections[i].state == UA_CONNECTIONSTATE_CLOSING)
|
||||
pcapCloseConnection(pcm, &pcm->connections[i]);
|
||||
}
|
||||
|
||||
/* Get the next packet */
|
||||
const u_char *data;
|
||||
struct pcap_pkthdr *pkthdr;
|
||||
int res = pcap_next_ex(pcm->fp, &pkthdr, &data);
|
||||
if(res != 1 || data == NULL)
|
||||
return;
|
||||
|
||||
/* Process the packet */
|
||||
processPacket(pcm, data, pkthdr->len);
|
||||
|
||||
/* Retrigger the EventLoop to process the next packet */
|
||||
write(pcm->pipe_fd, ".", 1);
|
||||
}
|
||||
|
||||
/* Only allow a single TCP connection */
|
||||
static UA_StatusCode
|
||||
pcapOpenConnection(UA_ConnectionManager *cm, const UA_KeyValueMap *params,
|
||||
void *application, void *context,
|
||||
UA_ConnectionManager_connectionCallback connectionCallback) {
|
||||
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)cm->eventSource.eventLoop;
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)cm;
|
||||
|
||||
/* Find a unused connection slot */
|
||||
PCAPConnection *c = NULL;
|
||||
for(size_t i = 0; i < PCAP_MAX_CONNECTIONS; i++) {
|
||||
if(pcm->connections[i].state == UA_CONNECTIONSTATE_CLOSED) {
|
||||
c = &pcm->connections[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!c)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/* Store the context for the connection */
|
||||
memset(c, 0, sizeof(PCAPConnection));
|
||||
c->id = ++pcm->fdCount;
|
||||
c->application = application;
|
||||
c->context = context;
|
||||
c->connectionCallback = connectionCallback;
|
||||
c->state = UA_CONNECTIONSTATE_OPENING;
|
||||
|
||||
/* Signal that the connection is opening */
|
||||
connectionCallback(cm, (uintptr_t)c->id, application, &c->context,
|
||||
UA_CONNECTIONSTATE_OPENING, NULL, UA_BYTESTRING_NULL);
|
||||
|
||||
/* Trigger the EventLoop to process the next packet */
|
||||
write(pcm->pipe_fd, ".", 1);
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
pcapSendWithConnection(UA_ConnectionManager *cm, uintptr_t connectionId,
|
||||
const UA_KeyValueMap *params,
|
||||
UA_ByteString *buf) {
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)cm;
|
||||
|
||||
UA_ByteString_clear(buf);
|
||||
|
||||
/* Find the connection */
|
||||
PCAPConnection *c = NULL;
|
||||
for(size_t i = 0; i < PCAP_MAX_CONNECTIONS; i++) {
|
||||
if(pcm->connections[i].id == (unsigned)connectionId) {
|
||||
c = &pcm->connections[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!c)
|
||||
return UA_STATUSCODE_BADCONNECTIONCLOSED;
|
||||
|
||||
/* Retrigger the EventLoop to process the next packet */
|
||||
write(pcm->pipe_fd, ".", 1);
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
pcapShutdownConnection(UA_ConnectionManager *cm, uintptr_t connectionId) {
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)cm;
|
||||
for(size_t i = 0; i < PCAP_MAX_CONNECTIONS; i++) {
|
||||
if(pcm->connections[i].id == (unsigned)connectionId)
|
||||
pcm->connections[i].state = UA_CONNECTIONSTATE_CLOSING;
|
||||
}
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
pcapAllocNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
|
||||
UA_ByteString *buf, size_t bufSize) {
|
||||
return UA_ByteString_allocBuffer(buf, bufSize);
|
||||
}
|
||||
|
||||
static void
|
||||
pcapFreeNetworkBuffer(UA_ConnectionManager *cm, uintptr_t connectionId,
|
||||
UA_ByteString *buf) {
|
||||
UA_ByteString_clear(buf);
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
pcapStart(UA_EventSource *es) {
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)es;
|
||||
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop;
|
||||
|
||||
int sd[2];
|
||||
UA_EventLoopPOSIX_pipe(sd);
|
||||
pcm->rfd.fd = sd[0];
|
||||
pcm->pipe_fd= sd[1];
|
||||
|
||||
pcm->rfd.es = &pcm->cm.eventSource;
|
||||
pcm->rfd.eventSourceCB = pcapActivityCallback;
|
||||
pcm->rfd.listenEvents = UA_FDEVENT_IN;
|
||||
UA_EventLoopPOSIX_registerFD(el, &pcm->rfd);
|
||||
|
||||
es->state = UA_EVENTSOURCESTATE_STARTED;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static void
|
||||
pcapStop(UA_EventSource *es) {
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)es;
|
||||
UA_EventLoopPOSIX *el = (UA_EventLoopPOSIX*)pcm->cm.eventSource.eventLoop;
|
||||
|
||||
/* Close all connections that remain open */
|
||||
for(size_t i = 0; i < PCAP_MAX_CONNECTIONS; i++) {
|
||||
if(pcm->connections[i].state != UA_CONNECTIONSTATE_CLOSED)
|
||||
pcapCloseConnection(pcm, &pcm->connections[i]);
|
||||
}
|
||||
|
||||
UA_EventLoopPOSIX_deregisterFD(el, &pcm->rfd);
|
||||
close(pcm->rfd.fd);
|
||||
close(pcm->pipe_fd);
|
||||
es->state = UA_EVENTSOURCESTATE_STOPPING;
|
||||
pcm->cm.eventSource.state = UA_EVENTSOURCESTATE_STOPPED;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
pcapFree(UA_EventSource *es) {
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)es;
|
||||
pcap_close(pcm->fp);
|
||||
UA_free(es);
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
UA_ConnectionManager *
|
||||
ConnectionManage_replayPCAP(const char *pcap_file, UA_Boolean client) {
|
||||
char errbuf[PCAP_ERRBUF_SIZE];
|
||||
pcap_t *fp = pcap_open_offline(pcap_file, errbuf);
|
||||
if(!fp)
|
||||
return NULL;
|
||||
|
||||
int lt = pcap_datalink(fp);
|
||||
if(lt != DLT_EN10MB) {
|
||||
pcap_close(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PCAPConnectionManager *pcm = (PCAPConnectionManager*)
|
||||
UA_calloc(1, sizeof(PCAPConnectionManager));
|
||||
pcm->cm.protocol = UA_STRING("tcp");
|
||||
pcm->cm.openConnection = pcapOpenConnection;
|
||||
pcm->cm.sendWithConnection = pcapSendWithConnection;
|
||||
pcm->cm.closeConnection = pcapShutdownConnection;
|
||||
pcm->cm.allocNetworkBuffer = pcapAllocNetworkBuffer;
|
||||
pcm->cm.freeNetworkBuffer = pcapFreeNetworkBuffer;
|
||||
|
||||
pcm->cm.eventSource.start = pcapStart;
|
||||
pcm->cm.eventSource.stop = pcapStop;
|
||||
pcm->cm.eventSource.free = pcapFree;
|
||||
|
||||
pcm->fp = fp;
|
||||
pcm->client = client;
|
||||
|
||||
return &pcm->cm;
|
||||
}
|
Loading…
Reference in New Issue
Block a user