Files
nt-lite/code/ping/ping.c
T

377 lines
11 KiB
C

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <syscall.h>
#define PING_DEFAULT_TARGET "10.0.2.2"
#define PING_DEFAULT_COUNT 4u
#define PING_TIMEOUT_MS 1000u
#define US_PER_MS 1000ull
#define PING_PAYLOAD_BYTES 32u
#define PING_PACKET_BYTES (8u + PING_PAYLOAD_BYTES)
static void print(const char* s) {
if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); }
}
static void print_char(char c) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
static void print_u64(uint64_t v) {
char buf[32];
int n = 0;
if (v == 0) { print("0"); return; }
while (v && n < (int)sizeof(buf)) {
buf[n++] = (char)('0' + (v % 10));
v /= 10;
}
while (n > 0) print_char(buf[--n]);
}
static void print_ipv4(const uint8_t ip[4]) {
print_u64(ip[0]); print("."); print_u64(ip[1]); print(".");
print_u64(ip[2]); print("."); print_u64(ip[3]);
}
static void print_rtt_us(uint64_t us) {
uint64_t whole = us / US_PER_MS;
uint64_t frac = us % US_PER_MS;
print_u64(whole);
print(".");
if (frac < 100) print("0");
if (frac < 10) print("0");
print_u64(frac);
}
static void format_ipv4(char* out, size_t outSize, const uint8_t ip[4]) {
if (!out || outSize == 0) return;
size_t off = 0;
for (int i = 0; i < 4; i++) {
char tmp[4];
uint32_t v = ip[i];
int n = 0;
if (v >= 100) tmp[n++] = (char)('0' + (v / 100));
if (v >= 10) tmp[n++] = (char)('0' + ((v / 10) % 10));
tmp[n++] = (char)('0' + (v % 10));
for (int j = 0; j < n && off + 1 < outSize; j++) out[off++] = tmp[j];
if (i != 3 && off + 1 < outSize) out[off++] = '.';
}
out[off < outSize ? off : outSize - 1] = 0;
}
static const char* next_token(const char* s, char* out, size_t outSize) {
if (!s || !out || outSize == 0) return NULL;
while (*s == ' ' || *s == '\t') s++;
if (*s == 0) { out[0] = 0; return NULL; }
size_t n = 0;
while (*s && *s != ' ' && *s != '\t') {
if (n + 1 < outSize) out[n++] = *s;
s++;
}
out[n] = 0;
return s;
}
static bool parse_u8(const char** p, uint8_t* out) {
const char* s = *p;
uint32_t v = 0;
int digits = 0;
while (*s >= '0' && *s <= '9') {
v = v * 10u + (uint32_t)(*s - '0');
if (v > 255u) return false;
s++;
digits++;
}
if (digits == 0) return false;
*out = (uint8_t)v;
*p = s;
return true;
}
static bool parse_ipv4(const char* s, uint8_t ip[4]) {
if (!s || !ip) return false;
for (int i = 0; i < 4; i++) {
if (!parse_u8(&s, &ip[i])) return false;
if (i != 3) {
if (*s != '.') return false;
s++;
}
}
return *s == 0;
}
static bool parse_u32(const char* s, uint32_t* out) {
if (!s || !*s || !out) return false;
uint32_t v = 0;
while (*s >= '0' && *s <= '9') {
uint32_t d = (uint32_t)(*s - '0');
if (v > (0xFFFFFFFFu - d) / 10u) return false;
v = v * 10u + d;
s++;
}
if (*s != 0) return false;
*out = v;
return true;
}
static uint32_t resolve_host(const char* host, uint8_t ip[4]) {
if (parse_ipv4(host, ip))
return SYS_STATUS_SUCCESS;
SYS_SOCKET_ADDRESS_IPV4 resolved;
size_t resolvedSize = sizeof(resolved);
SYS_RESOLVE_HOST_REQUEST req;
memset(&resolved, 0, sizeof(resolved));
memset(&req, 0, sizeof(req));
req.Name = SysSvFromCstr(host);
req.Family = SYS_AF_INET;
req.AddressOut = &resolved;
req.AddressSizeInOut = &resolvedSize;
uint32_t st = SysResolveHost(&req);
if (st != SYS_STATUS_SUCCESS)
return st;
if (resolved.Family != SYS_AF_INET)
return SYS_STATUS_NOT_FOUND;
ip[0] = resolved.Address[0];
ip[1] = resolved.Address[1];
ip[2] = resolved.Address[2];
ip[3] = resolved.Address[3];
return SYS_STATUS_SUCCESS;
}
static uint16_t icmp_checksum(const uint8_t* data, size_t length) {
uint32_t sum = 0;
while (length >= 2) {
sum += ((uint32_t)data[0] << 8) | data[1];
data += 2;
length -= 2;
}
if (length)
sum += ((uint32_t)data[0] << 8);
while (sum >> 16)
sum = (sum & 0xFFFFu) + (sum >> 16);
return (uint16_t)(~sum & 0xFFFFu);
}
static void make_echo_request(uint8_t packet[PING_PACKET_BYTES], uint16_t ident, uint16_t seq) {
memset(packet, 0, PING_PACKET_BYTES);
packet[0] = 8; // echo request
packet[1] = 0;
packet[4] = (uint8_t)(ident >> 8);
packet[5] = (uint8_t)(ident & 0xFFu);
packet[6] = (uint8_t)(seq >> 8);
packet[7] = (uint8_t)(seq & 0xFFu);
for (uint32_t i = 0; i < PING_PAYLOAD_BYTES; i++)
packet[8 + i] = (uint8_t)('a' + (i % 26u));
uint16_t sum = icmp_checksum(packet, PING_PACKET_BYTES);
packet[2] = (uint8_t)(sum >> 8);
packet[3] = (uint8_t)(sum & 0xFFu);
}
static bool parse_echo_reply(const uint8_t* buf, size_t len, uint16_t ident, uint16_t seq,
size_t* payloadLenOut, uint8_t* typeOut, uint8_t* codeOut) {
size_t off = 0;
if (len >= 20 && (buf[0] >> 4) == 4) {
size_t ihl = (size_t)(buf[0] & 0x0Fu) * 4u;
if (ihl >= 20 && ihl <= len && buf[9] == SYS_IPPROTO_ICMP)
off = ihl;
}
if (len < off + 8)
return false;
uint8_t type = buf[off + 0];
uint8_t code = buf[off + 1];
if (typeOut) *typeOut = type;
if (codeOut) *codeOut = code;
uint16_t rid = ((uint16_t)buf[off + 4] << 8) | buf[off + 5];
uint16_t rseq = ((uint16_t)buf[off + 6] << 8) | buf[off + 7];
if (type == 0 && code == 0 && rid == ident && rseq == seq) {
if (payloadLenOut)
*payloadLenOut = len - off;
return true;
}
return false;
}
static void usage(void) {
print("usage: apps/ping.exe [host-or-ipv4] [count]\n");
print("default: apps/ping.exe 10.0.2.2 4\n");
}
int main(void) {
char host[256];
char tok[256];
uint32_t count = PING_DEFAULT_COUNT;
strcpy(host, PING_DEFAULT_TARGET);
const char* cmd = SysGetPeb()->CommandLine.Data;
cmd = next_token(cmd, tok, sizeof(tok)); // program path
if (cmd) {
const char* rest = next_token(cmd, tok, sizeof(tok));
if (tok[0]) {
if (strlen(tok) >= sizeof(host)) {
print("ping: host too long\n");
SysExitProcess(1);
}
strcpy(host, tok);
cmd = rest;
}
if (cmd) {
(void)next_token(cmd, tok, sizeof(tok));
if (tok[0] && (!parse_u32(tok, &count) || count == 0 || count > 1000)) {
usage();
SysExitProcess(1);
}
}
}
uint8_t ip[4];
uint32_t st = resolve_host(host, ip);
if (st != SYS_STATUS_SUCCESS) {
print("ping: resolve failed "); print_u64(st); print(" for "); print(host); print("\n");
SysExitProcess(1);
}
char ipText[16];
format_ipv4(ipText, sizeof(ipText), ip);
uint32_t sock = 0;
st = SysSocket(SYS_AF_INET, SYS_SOCK_RAW, SYS_IPPROTO_ICMP, &sock);
if (st != SYS_STATUS_SUCCESS) {
print("ping: SysSocket(raw icmp) failed "); print_u64(st); print("\n");
SysExitProcess(1);
}
uint32_t timeout = PING_TIMEOUT_MS;
SYS_SOCKET_OPTION_REQUEST opt;
memset(&opt, 0, sizeof(opt));
opt.Option = SYS_SOCKET_OPT_RECV_TIMEOUT_MS;
opt.Value = &timeout;
opt.ValueSize = sizeof(timeout);
(void)SysSetSocketOption(sock, &opt);
SYS_SOCKET_ADDRESS_IPV4 remote;
memset(&remote, 0, sizeof(remote));
remote.Family = SYS_AF_INET;
remote.Port = 0;
remote.Address[0] = ip[0];
remote.Address[1] = ip[1];
remote.Address[2] = ip[2];
remote.Address[3] = ip[3];
print("PING "); print(host); print(" ("); print(ipText); print(") ");
print_u64(PING_PAYLOAD_BYTES); print(" data bytes\n");
uint16_t ident = (uint16_t)(SysGetCurrentPid() & 0xFFFFu);
uint32_t sentPackets = 0;
uint32_t receivedPackets = 0;
uint64_t minUs = 0, maxUs = 0, sumUs = 0;
for (uint32_t i = 0; i < count; i++) {
uint16_t seq = (uint16_t)(i + 1u);
uint8_t packet[PING_PACKET_BYTES];
make_echo_request(packet, ident, seq);
size_t sent = 0;
SYS_SOCKET_SEND_TO_REQUEST sendReq;
memset(&sendReq, 0, sizeof(sendReq));
sendReq.Buffer = packet;
sendReq.Length = sizeof(packet);
sendReq.RemoteAddress = &remote;
sendReq.RemoteAddressSize = sizeof(remote);
sendReq.SentOut = &sent;
uint64_t start = SysGetTick();
st = SysSendTo(sock, &sendReq);
if (st != SYS_STATUS_SUCCESS) {
print("ping: send failed seq="); print_u64(seq); print(" status="); print_u64(st); print("\n");
if (i + 1 < count) SysSleep(1000);
continue;
}
sentPackets++;
bool matched = false;
uint8_t lastType = 0, lastCode = 0;
for (;;) {
uint8_t recvBuf[512];
SYS_SOCKET_ADDRESS_IPV4 from;
size_t fromSize = sizeof(from);
size_t got = 0;
SYS_SOCKET_RECV_FROM_REQUEST recvReq;
memset(&recvReq, 0, sizeof(recvReq));
recvReq.Buffer = recvBuf;
recvReq.Length = sizeof(recvBuf);
recvReq.RemoteAddressOut = &from;
recvReq.RemoteAddressSizeInOut = &fromSize;
recvReq.ReceivedOut = &got;
st = SysRecvFrom(sock, &recvReq);
if (st == SYS_STATUS_TIMEOUT)
break;
if (st != SYS_STATUS_SUCCESS) {
print("ping: recv failed status="); print_u64(st); print("\n");
break;
}
size_t icmpLen = 0;
if (parse_echo_reply(recvBuf, got, ident, seq, &icmpLen, &lastType, &lastCode)) {
uint64_t end = SysGetTick();
uint64_t deltaUs = end >= start ? end - start : 0;
receivedPackets++;
sumUs += deltaUs;
if (receivedPackets == 1 || deltaUs < minUs) minUs = deltaUs;
if (deltaUs > maxUs) maxUs = deltaUs;
print_u64(icmpLen); print(" bytes from "); print_ipv4(from.Address);
print(": icmp_seq="); print_u64(seq);
print(" time="); print_rtt_us(deltaUs); print(" ms\n");
matched = true;
break;
}
uint64_t now = SysGetTick();
if (now - start >= ((uint64_t)PING_TIMEOUT_MS * US_PER_MS))
break;
}
if (!matched) {
print("Request timeout for icmp_seq "); print_u64(seq);
if (lastType || lastCode) {
print(" last_type="); print_u64(lastType); print(" code="); print_u64(lastCode);
}
print("\n");
}
if (i + 1 < count)
SysSleep(1000);
}
print("--- "); print(host); print(" ping statistics ---\n");
print_u64(sentPackets); print(" packets transmitted, ");
print_u64(receivedPackets); print(" packets received, ");
uint32_t loss = sentPackets ? (uint32_t)(((uint64_t)(sentPackets - receivedPackets) * 100u) / sentPackets) : 100u;
print_u64(loss); print("% packet loss\n");
if (receivedPackets) {
print("round-trip min/avg/max = ");
print_rtt_us(minUs); print("/"); print_rtt_us(sumUs / receivedPackets); print("/"); print_rtt_us(maxUs); print(" ms\n");
}
SysCloseHandle(sock);
SysExitProcess(receivedPackets ? 0 : 1);
return receivedPackets ? 0 : 1;
}