Files
oc2r-native-networking/bsd_impl.c
Jika 27ad967f41
Some checks failed
build / build (push) Has been cancelled
Add support for FreeBSD
2025-10-31 16:29:40 +01:00

273 lines
6.6 KiB
C

#include "li_cil_oc2_common_inet_DefaultSessionLayer.h"
#include <arpa/inet.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#define SOCK_PATH_DEFAULT "/var/run/oc2rnet-helper.sock"
#define PROTOCOL_VERSION 1
#define MAX_PAYLOAD 2048
struct PingReq {
uint8_t version;
uint8_t reserved;
uint16_t size;
uint32_t ip_be;
uint32_t timeout_ms;
} __attribute__((packed));
struct PingRes {
int32_t status;
uint16_t size;
uint16_t reserved;
} __attribute__((packed));
static int uds_connect(const char *path) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
return -1;
}
struct sockaddr_un sa;
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
if (strlcpy(sa.sun_path, path, sizeof(sa.sun_path)) >= sizeof(sa.sun_path)) {
close(fd);
errno = ENAMETOOLONG;
return -1;
}
if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
close(fd);
return -1;
}
return fd;
}
JNIEXPORT jbyteArray JNICALL
Java_li_cil_oc2_common_inet_DefaultSessionLayer_sendICMP(JNIEnv *env, jobject instance,
jbyteArray ip, jbyteArray data,
jint size, jint timeout) {
(void)instance;
if (size < 0 || size > MAX_PAYLOAD) {
return NULL;
}
const char *sock_path = getenv("OC2RNET_SOCK");
if (sock_path == NULL) {
sock_path = SOCK_PATH_DEFAULT;
}
jbyte *addr = (*env)->GetByteArrayElements(env, ip, NULL);
if (addr == NULL) {
return NULL;
}
jbyte *payload = (*env)->GetByteArrayElements(env, data, NULL);
if (payload == NULL) {
(*env)->ReleaseByteArrayElements(env, ip, addr, JNI_ABORT);
return NULL;
}
int fd = uds_connect(sock_path);
if (fd < 0) {
(*env)->ReleaseByteArrayElements(env, ip, addr, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, data, payload, JNI_ABORT);
return NULL;
}
struct PingReq req;
memset(&req, 0, sizeof(req));
req.version = PROTOCOL_VERSION;
req.size = (uint16_t)size;
memcpy(&req.ip_be, addr, sizeof(req.ip_be));
req.timeout_ms = (uint32_t)timeout;
ssize_t written = write(fd, &req, sizeof(req));
if (written != (ssize_t)sizeof(req)) {
goto fail;
}
if (size > 0) {
written = write(fd, payload, (size_t)size);
if (written != size) {
goto fail;
}
}
struct PingRes res;
ssize_t received = read(fd, &res, sizeof(res));
if (received != (ssize_t)sizeof(res)) {
goto fail;
}
if (res.status != 0 || res.size > (uint16_t)size) {
goto fail;
}
jbyteArray out = (*env)->NewByteArray(env, res.size);
if (out == NULL) {
goto fail;
}
jbyte *outbuf = malloc(res.size);
if (outbuf == NULL) {
(*env)->DeleteLocalRef(env, out);
goto fail;
}
received = read(fd, outbuf, res.size);
if (received != res.size) {
free(outbuf);
(*env)->DeleteLocalRef(env, out);
goto fail;
}
(*env)->SetByteArrayRegion(env, out, 0, res.size, outbuf);
free(outbuf);
close(fd);
(*env)->ReleaseByteArrayElements(env, ip, addr, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, data, payload, JNI_ABORT);
return out;
fail:
close(fd);
(*env)->ReleaseByteArrayElements(env, ip, addr, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, data, payload, JNI_ABORT);
return NULL;
}
#ifdef CLITEST
static ssize_t write_all(int fd, const void *buffer, size_t length) {
const uint8_t *ptr = buffer;
size_t total = 0;
while (total < length) {
ssize_t written = write(fd, ptr + total, length - total);
if (written < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
total += (size_t)written;
}
return (ssize_t)total;
}
static ssize_t read_all(int fd, void *buffer, size_t length) {
uint8_t *ptr = buffer;
size_t total = 0;
while (total < length) {
ssize_t read_bytes = read(fd, ptr + total, length - total);
if (read_bytes == 0) {
return (ssize_t)total;
}
if (read_bytes < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
total += (size_t)read_bytes;
}
return (ssize_t)total;
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <ipv4> [payload]\n", argv[0]);
return 1;
}
const char *target_ip = argv[1];
const char *payload_str = (argc >= 3) ? argv[2] : "oc2r";
size_t payload_len = strlen(payload_str);
if (payload_len > MAX_PAYLOAD) {
fprintf(stderr, "payload too large (max %d)\n", MAX_PAYLOAD);
return 1;
}
struct in_addr in_addr_target;
if (inet_pton(AF_INET, target_ip, &in_addr_target) != 1) {
perror("inet_pton");
return 1;
}
const char *sock_path = getenv("OC2RNET_SOCK");
if (sock_path == NULL || *sock_path == '\0') {
sock_path = SOCK_PATH_DEFAULT;
}
int fd = uds_connect(sock_path);
if (fd < 0) {
perror("uds_connect");
return 1;
}
struct PingReq req;
memset(&req, 0, sizeof(req));
req.version = PROTOCOL_VERSION;
req.size = (uint16_t)payload_len;
req.ip_be = in_addr_target.s_addr;
req.timeout_ms = 1000;
if (write_all(fd, &req, sizeof(req)) != (ssize_t)sizeof(req)) {
perror("write request");
close(fd);
return 1;
}
if (payload_len > 0) {
if (write_all(fd, payload_str, payload_len) != (ssize_t)payload_len) {
perror("write payload");
close(fd);
return 1;
}
}
struct PingRes res;
if (read_all(fd, &res, sizeof(res)) != (ssize_t)sizeof(res)) {
perror("read response");
close(fd);
return 1;
}
printf("status=%d size=%u\n", res.status, res.size);
if (res.status == 0 && res.size > 0) {
uint8_t *buffer = malloc(res.size);
if (buffer == NULL) {
fprintf(stderr, "malloc failed\n");
close(fd);
return 1;
}
if (read_all(fd, buffer, res.size) != res.size) {
perror("read payload");
free(buffer);
close(fd);
return 1;
}
fwrite(buffer, 1, res.size, stdout);
putchar('\n');
free(buffer);
}
close(fd);
return 0;
}
#endif /* CLITEST */