Skip to content

Commit

Permalink
Add IPv6 support (#37)
Browse files Browse the repository at this point in the history
* Add IPv6 support

This patch makes bridge accept IPv6 addresses as amqp_url value.

* Committing clang-format changes

* Eliminate implicit-function-declaration in tests

* Correct character class in AMQP_URL_REGEX

---------

Co-authored-by: InfraWatch CI <[email protected]>
Co-authored-by: Chris Sibbitt <[email protected]>
Co-authored-by: Victoria Martinez de la Cruz <[email protected]>
  • Loading branch information
4 people authored May 31, 2024
1 parent 85b865d commit bf54f32
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 50 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Unit tests suite
env:
TEST_IMAGE: quay.io/centos/centos:stream9
PROJECT_ROOT: /src/github.com/infrawatch/sg-bridge
OPSTOOLS_REPO: https://git.centos.org/rpms/centos-release-opstools/raw/c9s-sig-opstools/f/SOURCES/CentOS-OpsTools.repo

on: [push, pull_request]

jobs:
unit-tests:
name: Unit tests
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run sg-core unit test suite
run: |
docker run --name=testsuite -uroot --network host -e OPSTOOLS_REPO \
--volume ${{ github.workspace }}:$PROJECT_ROOT:z --workdir $PROJECT_ROOT \
$TEST_IMAGE bash $PROJECT_ROOT/ci/run_tests.sh
35 changes: 28 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@

MAJOR?=0
MINOR?=1

VERSION=$(MAJOR).$(MINOR)

BIN := bridge
TEST_EXEC = tests

SRCS = $(wildcard *.c)
# Specify the source files, excluding the test and the main source file
MAIN_SRC = bridge.c
SRCS = $(filter-out tests.c $(MAIN_SRC), $(wildcard *.c))
TEST_SRC = tests.c

OBJDIR := obj
TEST_OBJDIR := test_obj

DEPDIR := $(OBJDIR)/.deps

# object files, auto generated from source files
OBJS := $(patsubst %,$(OBJDIR)/%.o,$(basename $(SRCS)))
TEST_OBJS := $(patsubst %,$(TEST_OBJDIR)/%.o,$(basename $(SRCS)))
TEST_OBJS += $(TEST_OBJDIR)/$(basename $(TEST_SRC)).o

# dependency files, auto generated from source files
DEPS := $(patsubst %,$(DEPDIR)/%.d,$(basename $(SRCS)))

# compilers (at least gcc and clang) don't create the subdirectories automatically
$(shell mkdir -p $(dir $(OBJS)) >/dev/null)
$(shell mkdir -p $(dir $(TEST_OBJS)) >/dev/null)
$(shell mkdir -p $(dir $(DEPS)) >/dev/null)

CC=gcc
Expand Down Expand Up @@ -48,7 +56,7 @@ debug: all

.PHONY: clean
clean:
rm -fr $(OBJDIR) $(DEPDIR)
rm -fr $(OBJDIR) $(TEST_OBJDIR) $(DEPDIR)

.PHONY: clean-image
clean-image: version-check
Expand All @@ -61,7 +69,7 @@ image: version-check
@buildah bud -t ${HUB_NAMESPACE}/${BRIDGE_IMAGE_NAME}:latest -f build/Dockerfile .
@echo 'Done.'

$(BIN): $(OBJS)
$(BIN): $(OBJS) $(OBJDIR)/$(basename $(MAIN_SRC)).o
$(CC) -o $@ $^ $(LDFLAGS) $(CFLAGS) $(LDLIBS)

$(OBJDIR)/%.o: %.c %.h
Expand All @@ -76,18 +84,31 @@ $(OBJDIR)/%.o : %.c $(DEPDIR)/%.d %.h | $(DEPDIR)
.PRECIOUS: $(DEPDIR)/%.d
$(DEPDIR)/%.d: ;

# Build unit test executable
$(TEST_EXEC): $(TEST_OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)

# Compile unit test file
$(TEST_OBJDIR)/%.o: %.c
@mkdir -p $(TEST_OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<

# Run unit tests
test: $(TEST_EXEC)
@$(TEST_EXEC)

#################################
# Utilities
#################################

.PHONY: version-check
version-check:
@echo "+ $@"
ifdef VERSION
ifdef VERSION
@echo "VERSION is ${VERSION}"
else
else
@echo "VERSION is not set!"
@false;
endif
endif

-include $(DEPS)
50 changes: 8 additions & 42 deletions bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include <proton/session.h>
#include <proton/transport.h>
#include <pthread.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -136,44 +135,6 @@ static void usage(char *program) {
}
}

static int match_regex(char *regmatch, char *matches[], int n_matches,
const char *to_match) {
/* "M" contains the matches found. */
regmatch_t m[n_matches];
regex_t regex;

if (regcomp(&regex, regmatch, REG_EXTENDED)) {
fprintf(stderr, "Could not compile regex: %s\n", regmatch);

return -1;
}

int nomatch = regexec(&regex, to_match, n_matches, m, 0);
if (nomatch == REG_NOMATCH) {
return 0;
}

int match_count = 0;
for (int i = 0; i < n_matches; i++) {
if (m[i].rm_so == -1) {
continue;
}
match_count++;

int match_len = m[i].rm_eo - m[i].rm_so;

matches[i] = malloc(match_len + 1); // make room for '\0'

int k = 0;
for (int j = m[i].rm_so; j < m[i].rm_eo; j++) {
matches[i][k++] = to_match[j];
}
matches[i][k] = '\0';
}

return match_count;
}

int main(int argc, char **argv) {
app_data_t app = {0};
char cid_buf[100];
Expand Down Expand Up @@ -275,16 +236,21 @@ int main(int argc, char **argv) {

match_regex(AMQP_URL_REGEX, matches, 10, app.amqp_con.url);
if (matches[3] != NULL) {
app.amqp_con.user = strdup(matches[2]);
app.amqp_con.user = strdup(matches[3]);
}
if (matches[5] != NULL) {
app.amqp_con.password = strdup(matches[4]);
app.amqp_con.password = strdup(matches[5]);
}
if (matches[6] == NULL || matches[9] == NULL) {
fprintf(stderr, "Invalid AMQP URL: %s", app.amqp_con.url);
exit(1);
}
app.amqp_con.host = strdup(matches[6]);
if (strchr(matches[6], '[') != NULL && strchr(matches[6], ']') != NULL) {
app.amqp_con.host = strndup(matches[6] + 1, strlen(matches[6]) - 2);
} else {
app.amqp_con.host = strdup(matches[6]);
}

app.amqp_con.address = strdup(matches[9]);
if (matches[8] != NULL) {
app.amqp_con.port = strdup(matches[8]);
Expand Down
4 changes: 3 additions & 1 deletion bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
#define DEFAULT_AMQP_BLOCK "false"

#define AMQP_URL_REGEX \
"^(amqps*)://(([a-z]+)(:([a-z]+))*@)*([a-zA-Z_0-9.-]+)(:([0-9]+))*(.+)$"
"^(amqps*)://" \
"(([a-z]+)(:([a-z]+))*@)*([a-zA-Z_0-9.-]+|\\[[:a-fA-F0-9]+\\])(:([0-9]+))" \
"?(/[^[:space:]]*)?$"

typedef struct {
char *user;
Expand Down
11 changes: 11 additions & 0 deletions ci/run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/env bash
# purpose: runt unit test suite

set -ex

# enable required repo(s)
dnf install -y centos-release-opstools tree
dnf install make gcc qpid-proton-c-devel annobin-annocheck gcc-plugin-annobin rpm-build -y

make tests
./tests
18 changes: 18 additions & 0 deletions minunit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* taken from https://jera.com/techinfo/jtns/jtn002
*/

/* file: minunit.h */
#define mu_assert(message, test) \
do { \
if (!(test)) \
return message; \
} while (0)
#define mu_run_test(test) \
do { \
char *message = test(); \
tests_run++; \
if (message) \
return message; \
} while (0)
extern int tests_run;
168 changes: 168 additions & 0 deletions tests.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

#include "bridge.h"
#include "minunit.h"
#include "utils.h"
#include <stdio.h>

/***************** test suite *****************/

static char *test_match_amqp_url_hostname() {
char *matches[10];
memset(matches, 0, sizeof(matches));
match_regex(AMQP_URL_REGEX, matches, 10,
"amqp://scooby:[email protected]:5666/foo/bar");

mu_assert("Failed to parse full amqp_url in form of hostname: user",
!strcmp(matches[3], "scooby"));
mu_assert("Failed to parse full amqp_url in form of hostname: password",
!strcmp(matches[5], "doo"));
mu_assert("Failed to parse full amqp_url in form of hostname: host",
!strcmp(matches[6], "some.k8s.svc"));
mu_assert("Failed to parse full amqp_url in form of hostname: port",
!strcmp(matches[8], "5666"));
mu_assert("Failed to parse full amqp_url in form of hostname: address",
!strcmp(matches[9], "/foo/bar"));

char *matches2[10];
memset(matches2, 0, sizeof(matches2));
match_regex(AMQP_URL_REGEX, matches2, 10, "amqps://other.k8s.svc:5666/baz");

mu_assert(
"Failed to parse amqp_url w/o user info in form of hostname: user",
matches2[3] == NULL);
mu_assert(
"Failed to parse amqp_url w/o user info in form of hostname: password",
matches2[5] == NULL);
mu_assert(
"Failed to parse amqp_url w/o user info in form of hostname: host",
!strcmp(matches2[6], "other.k8s.svc"));
mu_assert(
"Failed to parse amqp_url w/o user info in form of hostname: port",
!strcmp(matches2[8], "5666"));
mu_assert(
"Failed to parse amqp_url w/o user info in form of hostname: address",
!strcmp(matches2[9], "/baz"));

return 0;
}

static char *test_match_amqp_url_ipv4() {
char *matches[10];
memset(matches, 0, sizeof(matches));
match_regex(AMQP_URL_REGEX, matches, 10,
"amqp://scooby:[email protected]:5666/foo/bar");

mu_assert("Failed to parse amqp_url in form of IPv4: user invalid: %s",
!strcmp(matches[3], "scooby"));
mu_assert("Failed to parse amqp_url in form of IPv4: password",
!strcmp(matches[5], "doo"));
mu_assert("Failed to parse amqp_url in form of IPv4:i host",
!strcmp(matches[6], "127.0.0.1"));
mu_assert("Failed to parse amqp_url in form of IPv4: port",
!strcmp(matches[8], "5666"));
mu_assert("Failed to parse amqp_url in form of IPv4: address",
!strcmp(matches[9], "/foo/bar"));

char *matches2[10];
memset(matches2, 0, sizeof(matches2));
match_regex(AMQP_URL_REGEX, matches2, 10, "amqps://127.0.0.1:5666/baz");

mu_assert("Failed to parse amqp_url w/o user info in form of IPv4: user",
matches2[3] == NULL);
mu_assert(
"Failed to parse amqp_url w/o user info in form of IPv4: password",
matches2[5] == NULL);
mu_assert("Failed to parse amqp_url w/o user info in form of IPv4: host",
!strcmp(matches2[6], "127.0.0.1"));
mu_assert("Failed to parse amqp_url w/o user info in form of IPv4: port",
!strcmp(matches2[8], "5666"));
mu_assert("Failed to parse amqp_url w/o user info in form of IPv4: address",
!strcmp(matches2[9], "/baz"));
return 0;
}

static char *test_match_amqp_url_ipv6() {
char *matches[10];
memset(matches, 0, sizeof(matches));
match_regex(AMQP_URL_REGEX, matches, 10,
"amqp://scooby:doo@[fe80::abcd:fcff:fe07:9999]:5666/foo/bar");

mu_assert("Failed to parse amqp_url in form of IPv6: user",
!strcmp(matches[3], "scooby"));
mu_assert("Failed to parse amqp_url in form of IPv6: password",
!strcmp(matches[5], "doo"));
mu_assert("Failed to parse amqp_url in form of IPv6: host",
!strcmp(matches[6], "[fe80::abcd:fcff:fe07:9999]"));
mu_assert("Failed to parse amqp_url in form of IPv6: port",
!strcmp(matches[8], "5666"));
mu_assert("Failed to parse amqp_url in form of IPv6: address",
!strcmp(matches[9], "/foo/bar"));

char *matches2[10];
memset(matches2, 0, sizeof(matches2));
match_regex(AMQP_URL_REGEX, matches2, 10,
"amqps://[fe80::abcd:fcff:fe07:9999]:5666/bas");

mu_assert("Failed to parse amqp_url w/o user info in form of IPv6: user",
matches2[3] == NULL);
mu_assert(
"Failed to parse amqp_url w/o user info in form of IPv6: password",
matches2[5] == NULL);
mu_assert("Failed to parse amqp_url w/o user info in form of IPv6: host",
!strcmp(matches2[6], "[fe80::abcd:fcff:fe07:9999]"));
mu_assert("Failed to parse amqp_url w/o user info in form of IPv6: port",
!strcmp(matches2[8], "5666"));
mu_assert("Failed to parse amqp_url w/o user info in form of IPv6: address",
!strcmp(matches2[9], "/bas"));
return 0;
}

static char *test_match_amqp_url_fail() {
char *matches[10];
memset(matches, 0, sizeof(matches));
match_regex(AMQP_URL_REGEX, matches, 10,
"amqp://scooby:doo@[XXX.666.000.123/64]:5666/foo/bar");

mu_assert("Failed to fail parsing invalid amqp_url",
matches[6] == NULL && matches[9] == NULL);
return 0;
}

/******************* runner *******************/

int tests_run = 0;

static char *all_tests() {
mu_run_test(test_match_amqp_url_hostname);
mu_run_test(test_match_amqp_url_ipv4);
mu_run_test(test_match_amqp_url_ipv6);
mu_run_test(test_match_amqp_url_fail);
return 0;
}

int main(int argc, char **argv) {
char *result = all_tests();
if (result != 0) {
printf("%s\n", result);
} else {
printf("ALL TESTS PASSED\n");
}
printf("Tests run: %d\n", tests_run);

return result != 0;
}
Loading

0 comments on commit bf54f32

Please sign in to comment.