Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions .github/workflows/compilation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ jobs:
make -j $(getconf _NPROCESSORS_ONLN) clean
make -j $(getconf _NPROCESSORS_ONLN) all

- name: Run portable smoke tests
- name: Run native C regression tests
if: matrix.os[0] != 'windows-latest'
run: make check

- name: Run native Windows C smoke tests
if: matrix.os[0] == 'windows-latest'
run: make -f Makefile.mingw32 check

- name: Get short SHA
id: slug
Expand All @@ -50,7 +54,22 @@ jobs:
name: ps2client-${{ steps.slug.outputs.sha8 }}-${{ matrix.os[0] }}-${{ matrix.os[1] }}
path: |
*tar.gz


integration-linux:
name: Linux PS2Link protocol integration
runs-on: ubuntu-latest
timeout-minutes: 8
steps:
- uses: actions/checkout@v4
- name: Build client and native C fake-PS2Link server
run: make -j2 all bin/fake_ps2link bin/crc32
- name: Run full network-namespace integration suite
run: |
# Keep virtual interfaces and UDP ports off the runner's host network.
# GitHub-hosted Linux runners permit root-owned network namespaces.
sudo unshare --net -- env PS2CLIENT_TEST_NS=1 \
bash tests/run-tests.sh "$GITHUB_WORKSPACE/bin/ps2client"

build-win:
runs-on: ubuntu-latest
container: dockcross/windows-static-x86:latest
Expand All @@ -77,7 +96,7 @@ jobs:
*tar.gz

release:
needs: [build, build-win]
needs: [build, build-win, integration-linux]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master'
steps:
Expand Down
9 changes: 8 additions & 1 deletion Makefile.mingw32
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@

all: bin/fsclient.exe bin/ps2client.exe

check: bin/ps2client.exe obj/smoke_windows.exe
obj/smoke_windows.exe

clean:
rm -f obj/*.o bin/*client*
rm -f obj/*.o obj/smoke_windows.exe bin/*client*

#################
## CLIENT LIBS ##
Expand Down Expand Up @@ -46,3 +49,7 @@
bin/ps2client.exe: $(OFILES) src/ps2client.c
@mkdir -p bin
$(CC) $(INC) $(OFILES) src/ps2client.c -o bin/ps2client.exe $(LIB)

obj/smoke_windows.exe: tests/smoke_windows.c
@mkdir -p obj
$(CC) -Wall -Wextra $< -o $@ -lws2_32
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ hostnames and the `console` command) on macOS and Linux without a PS2.
(`tests/fake_ps2link.c`) in unprivileged Linux network namespaces, so no ps2
is needed.

CI also runs the full Linux protocol suite in isolated network namespaces
with `sudo unshare --net` on the GitHub-hosted runner. The job covers
handshakes, host-file transfers, timeouts, disconnects and console packets
against the native C fake server. On Windows, the native C smoke test runs
through `make -f Makefile.mingw32 check`. Neither test needs Python.

The Linux integration suite can be reproduced on a machine that supports
network namespaces using:

```sh
make all bin/fake_ps2link bin/crc32
sudo unshare --net -- env PS2CLIENT_TEST_NS=1 \
bash tests/run-tests.sh "$(pwd)/bin/ps2client"
```

## Community

Links for discussion and chat are available
Expand Down
123 changes: 123 additions & 0 deletions tests/smoke_windows.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* Native Windows command and UDP tests without a PS2 or Python. */
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CONSOLE_PORT 0x4712

static int failures;

static void check(int passed, const char *name)
{
printf("%s %s\n", passed ? "PASS" : "FAIL", name);
failures += !passed;
}

static int launch(const char *arguments, PROCESS_INFORMATION *process)
{
STARTUPINFOA startup = {0};
char command[1024];
startup.cb = sizeof(startup);
memset(process, 0, sizeof(*process));
snprintf(command, sizeof(command), "\"bin\\ps2client.exe\" %s", arguments);
return CreateProcessA(NULL, command, NULL, NULL, FALSE, CREATE_NO_WINDOW,
NULL, NULL, &startup, process) != 0;
}

static int finish(PROCESS_INFORMATION *process, DWORD timeout)
{
DWORD status = (DWORD)-1;
if (WaitForSingleObject(process->hProcess, timeout) != WAIT_OBJECT_0) {
TerminateProcess(process->hProcess, 1);
WaitForSingleObject(process->hProcess, 5000);
} else {
GetExitCodeProcess(process->hProcess, &status);
}
CloseHandle(process->hThread);
CloseHandle(process->hProcess);
return (int)status;
}

static int run(const char *arguments)
{
PROCESS_INFORMATION process;
if (!launch(arguments, &process)) return -1;
return finish(&process, 7000);
}

static void test_console(void)
{
PROCESS_INFORMATION process;
struct sockaddr_in address = {0};
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
int i;
if (sock == INVALID_SOCKET) { check(0, "UDP console setup"); return; }
if (!launch("-h 1.1.l.1 -t 3 -e smoke-console-line console", &process)) {
closesocket(sock);
check(0, "UDP console launch");
return;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(0x7f000001UL);
address.sin_port = htons(CONSOLE_PORT);
Sleep(150);
for (i = 0; i < 10; i++) {
sendto(sock, "smoke-console-line\n", 19, 0,
(struct sockaddr *)&address, sizeof(address));
Sleep(40);
}
closesocket(sock);
check(finish(&process, 5000) == 0,
"UDP-only console accepts messages without resolving host");
}

static void test_reset(void)
{
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in address = {0};
unsigned char packet[64];
DWORD timeout = 3000;
int bytes, status;
if (sock == INVALID_SOCKET) { check(0, "UDP reset setup"); return; }
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(0x7f000001UL);
address.sin_port = htons(CONSOLE_PORT);
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
(const char *)&timeout, sizeof(timeout));
if (bind(sock, (struct sockaddr *)&address, sizeof(address)) != 0) {
closesocket(sock);
check(0, "UDP reset bind");
return;
}
status = run("-h 127.0.0.1 reset");
bytes = recv(sock, (char *)packet, sizeof(packet), 0);
closesocket(sock);
check(status == 0 && bytes == 6 &&
packet[0] == 0xba && packet[1] == 0xbe &&
packet[2] == 0x02 && packet[3] == 0x01 &&
packet[4] == 0x00 && packet[5] == 0x06,
"reset uses UDP with the expected command packet");
}

int main(void)
{
WSADATA sockets;
if (WSAStartup(MAKEWORD(2, 2), &sockets) != 0) return 1;

check(run("-h 1.1.l.1 ping") == 69, "invalid hostname");
check(run("-h 1.1.l.1 -t 1 listen") == 69,
"legacy listen requires a resolvable host");
check(run("invalid-command") == 64, "unknown command");
check(run("dumpmem 0x100000") == 64, "missing arguments");
check(run("-e") == 64, "missing marker");
SetEnvironmentVariableA("PS2HOSTNAME", "1.1.l.1");
check(run("ping") == 69, "invalid PS2HOSTNAME environment setting");
SetEnvironmentVariableA("PS2HOSTNAME", NULL);
test_console();
test_reset();

WSACleanup();
return failures ? 1 : 0;
}
Loading