about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGES47
-rw-r--r--LICENSE23
-rw-r--r--Makefile16
-rw-r--r--README38
-rw-r--r--fjorker.854
-rw-r--r--main.c183
-rwxr-xr-xrelease.sh28
7 files changed, 389 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..e17b491
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,47 @@
+fjorker 0.1.3 (August 2, 2025)
+
+* Update Makefile
+
+Known issues:
+ * None at this time
+
+Security:
+ * No additional security features at this time
+
+fjorker 0.1.2 (July 31, 2025)
+
+* Add TTL attack detection
+
+Known issues:
+ * None at this time
+
+Security:
+ * No additional security features at this time
+
+fjorker 0.1.1 (July 31, 2025)
+
+* BSD-style man page
+
+Known issues:
+ * None at this time
+
+Security:
+ * No additional security features at this time
+
+fjorker 0.1.0 (July 30, 2025)
+
+ * Initial release
+ * Basic ICMP echo request/reply functionality
+ * Random identifiers for enhanced security
+ * Sequence number validation
+ * Minimal information disclosure
+ * Simple release script with GPG signature support
+
+Security:
+ * Uses arc4random() for identifier generation
+ * Validates sequence numbers in responses
+ * Minimal code base for better auditability
+ * No unnecessary system information disclosure
+
+Known issues:
+ * None at this time
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fd7d69a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2025 Lain Iwakura. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e252f55
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+CC=cc
+CFLAGS=-Wall -Wextra -Werror -O2 -s
+TARGET=fjorker
+
+all: $(TARGET)
+
+$(TARGET): main.c
+	$(CC) $(CFLAGS) -o $(TARGET) main.c
+
+install: $(TARGET)
+	install -m 755 $(TARGET) /usr/local/bin/
+
+clean:
+	rm -f $(TARGET)
+
+.PHONY: all clean install
diff --git a/README b/README
new file mode 100644
index 0000000..0584a56
--- /dev/null
+++ b/README
@@ -0,0 +1,38 @@
+fjorker(8)                                                           fjorker(8)
+
+NAME
+     fjorker - minimalistic ICMP echo request/reply implementation
+
+SYNOPSIS
+     fjorker host
+     fjorker --version
+
+DESCRIPTION
+     fjorker is a secure-by-default ICMP echo request/reply implementation
+     that follows OpenBSD's philosophy of minimalism and security.
+
+     Unlike traditional ping implementations, fjorker:
+     * Minimizes information disclosure about the source host
+     * Uses random identifiers instead of process IDs
+     * Validates sequence numbers in responses
+     * Has minimal code base for better security
+
+EXAMPLES
+     fjorker 1.1.1.1
+             Ping 1.1.1.1 using ICMP echo requests
+
+     fjorker --version
+             Display version information
+
+EXIT STATUS
+     The fjorker utility exits 0 on success, and >0 if an error occurs.
+
+SEE ALSO
+     ping(8)
+
+STANDARDS
+     The fjorker utility implements ICMP echo request/reply as described in
+     RFC 792.
+
+HISTORY
+     The fjorker utility first appeared in 2025.
\ No newline at end of file
diff --git a/fjorker.8 b/fjorker.8
new file mode 100644
index 0000000..43b5856
--- /dev/null
+++ b/fjorker.8
@@ -0,0 +1,54 @@
+.Dd July 31, 2025
+.Dt FJORKER 8
+.Os
+.Sh NAME
+.Nm fjorker
+.Nd minimalistic ICMP echo request/reply implementation
+.Sh SYNOPSIS
+.Nm
+.Ar host
+.Nm
+.Fl -version
+.Sh DESCRIPTION
+The
+.Nm
+utility is a secure-by-default ICMP echo request/reply implementation
+that follows OpenBSD's philosophy of minimalism and security.
+.Pp
+Unlike traditional ping implementations,
+.Nm
+minimizes information disclosure about the source host, uses random
+identifiers instead of process IDs, validates sequence numbers in responses,
+and has minimal code base for better security.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl -version
+Display version information and exit.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+.Bl -tag -width Ds
+.It Nm Ar 1.1.1.1
+Ping 1.1.1.1 using ICMP echo requests
+.It Nm Fl -version
+Display version information
+.El
+.Sh SECURITY CONSIDERATIONS
+.Nm
+requires root privileges to create raw sockets for ICMP communication.
+However, the --version option can be used without elevated privileges.
+.Pp
+The program uses random identifiers instead of process IDs to avoid
+leaking system information. It also validates sequence numbers in
+responses to detect potential ICMP packet manipulation.
+.Sh SEE ALSO
+.Xr ping 8
+.Sh STANDARDS
+The
+.Nm
+utility implements ICMP echo request/reply as described in RFC 792.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in 2025.
\ No newline at end of file
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..444bf4f
--- /dev/null
+++ b/main.c
@@ -0,0 +1,183 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/time.h>
+#include <time.h>
+#include <errno.h>
+
+#define PACKET_SIZE 64
+#define ICMP_HEADER_LEN 8
+#define VERSION "0.1.3"
+static volatile sig_atomic_t keep_running = 1;
+static int sockfd;
+static uint16_t seq = 0;
+static int packets_sent = 0;
+static int packets_received = 0;
+static int packets_mismatched = 0;
+static int packets_ttl_attack = 0;
+static double min_time = -1;
+static double max_time = -1;
+static double total_time = 0;
+static uint16_t identifier;
+void cleanup(void) {
+    if (sockfd >= 0) {
+        close(sockfd);
+    }
+}
+void signal_handler(int signo __attribute__((unused))) {
+    keep_running = 0;
+}
+unsigned short checksum(unsigned short *addr, int len) {
+    int nleft = len;
+    int sum = 0;
+    unsigned short *w = addr;
+    unsigned short answer = 0;
+
+    while (nleft > 1) {
+        sum += *w++;
+        nleft -= 2;
+    }
+
+    if (nleft == 1) {
+        *(unsigned char *)(&answer) = *(unsigned char *)w;
+        sum += answer;
+    }
+
+    sum = (sum >> 16) + (sum & 0xFFFF);
+    sum += (sum >> 16);
+    answer = ~sum;
+    return answer;
+}
+int main(int argc, char *argv[]) {
+    if (argc == 2 && strcmp(argv[1], "--version") == 0) {
+        printf("fjorker version %s\n", VERSION);
+        exit(0);
+    }
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s host\n       %s --version\n", argv[0], argv[0]);
+        exit(1);
+    }
+    identifier = arc4random() & 0xFFFF;
+    struct hostent *host;
+    struct sockaddr_in addr;
+    struct icmp *icmp;
+    char packet[PACKET_SIZE];
+    char recv_packet[PACKET_SIZE];
+    struct timeval tv_out;
+    tv_out.tv_sec = 1;
+    tv_out.tv_usec = 0;
+
+    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
+        if (errno == EPERM) {
+            fprintf(stderr, "Permission denied. Run as root.\n");
+        } else {
+            perror("socket");
+        }
+        exit(1);
+    }
+
+    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out)) < 0) {
+        perror("setsockopt");
+        cleanup();
+        exit(1);
+    }
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+
+    if (inet_aton(argv[1], &addr.sin_addr) == 0) {
+        if ((host = gethostbyname(argv[1])) == NULL) {
+            fprintf(stderr, "Unknown host %s\n", argv[1]);
+            cleanup();
+            exit(1);
+        }
+        addr.sin_addr = *(struct in_addr *)host->h_addr;
+    }
+    printf("PING %s (%s): %d data bytes\n", argv[1], 
+           inet_ntoa(addr.sin_addr), PACKET_SIZE - ICMP_HEADER_LEN);
+    signal(SIGINT, signal_handler);
+    while (keep_running) {
+        memset(packet, 0, sizeof(packet));
+        icmp = (struct icmp *)packet;
+        icmp->icmp_type = ICMP_ECHO;
+        icmp->icmp_code = 0;
+        icmp->icmp_id = identifier;
+        icmp->icmp_seq = seq++;
+        packets_sent++;
+        icmp->icmp_cksum = 0;
+        icmp->icmp_cksum = checksum((unsigned short *)icmp, PACKET_SIZE);
+        struct timeval send_time;
+        gettimeofday(&send_time, NULL);
+        if (sendto(sockfd, packet, PACKET_SIZE, 0, 
+                   (struct sockaddr *)&addr, sizeof(addr)) <= 0) {
+            perror("sendto");
+            continue;
+        }
+        struct sockaddr_in from;
+        socklen_t fromlen = sizeof(from);
+        int bytes;
+
+        if ((bytes = recvfrom(sockfd, recv_packet, sizeof(recv_packet), 0,
+                             (struct sockaddr *)&from, &fromlen)) > 0) {
+            struct timeval recv_time;
+            gettimeofday(&recv_time, NULL);
+            
+            struct ip *ip = (struct ip *)recv_packet;
+            int ip_header_len = ip->ip_hl << 2;
+            icmp = (struct icmp *)(recv_packet + ip_header_len);
+
+            if (icmp->icmp_type == ICMP_ECHOREPLY && 
+                icmp->icmp_id == identifier) {
+                if (icmp->icmp_seq != seq - 1) {
+                    packets_mismatched++;
+                    printf("[!] Server returned wrong sequence: got %d, expected %d\n",
+                           icmp->icmp_seq, seq - 1);
+                }
+                packets_received++;
+                double elapsed = ((recv_time.tv_sec - send_time.tv_sec) * 1000.0) +
+                               ((recv_time.tv_usec - send_time.tv_usec) / 1000.0);
+
+                if (min_time < 0 || elapsed < min_time) min_time = elapsed;
+                if (max_time < 0 || elapsed > max_time) max_time = elapsed;
+                total_time += elapsed;
+                int ttl = ip->ip_ttl;
+                const char* ttl_warning = "";
+                if (icmp->icmp_type == ICMP_ECHOREPLY && (ttl < 30 || ttl > 255)) {
+                    ttl_warning = " [!] Suspicious TTL value";
+                    packets_ttl_attack++;
+                }
+                printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.3f ms%s\n",
+                       bytes - ip_header_len, inet_ntoa(from.sin_addr),
+                       icmp->icmp_seq, ttl, elapsed, ttl_warning);
+            }
+        }
+
+        sleep(1);
+    }
+    printf("\n--- %s ping statistics ---\n", argv[1]);
+    printf("%d packets transmitted, %d packets received, %.1f%% packet loss\n",
+           packets_sent, packets_received,
+           ((packets_sent - packets_received) * 100.0) / packets_sent);
+    if (packets_mismatched > 0) {
+        printf("%d packets had incorrect sequence numbers\n", packets_mismatched);
+    }
+    if (packets_ttl_attack > 0) {
+        printf("%d packets had suspicious TTL values\n", packets_ttl_attack);
+    }
+    if (packets_received > 0) {
+        double avg = total_time / packets_received;
+
+        printf("round-trip min/avg/max = %.3f/%.3f/%.3f ms\n",
+               min_time, avg, max_time);
+    }
+    cleanup();
+    return 0;
+}
\ No newline at end of file
diff --git a/release.sh b/release.sh
new file mode 100755
index 0000000..6888f6b
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+set -e
+
+VERSION=$(grep '#define VERSION' main.c | cut -d'"' -f2)
+if [ -z "$VERSION" ]; then
+    echo "Error: Could not extract version from main.c"
+    exit 1
+fi
+
+DISTNAME="fjorker-${VERSION}"
+DISTFILE="${DISTNAME}.tar.gz"
+
+rm -rf "$DISTNAME"
+mkdir "$DISTNAME"
+
+cp main.c Makefile README LICENSE CHANGES fjorker.8 "$DISTNAME/"
+
+tar czf "$DISTFILE" "$DISTNAME"
+rm -rf "$DISTNAME"
+
+if command -v gpg >/dev/null 2>&1; then
+    gpg --detach-sign --armor "$DISTFILE"
+    echo "Created: $DISTFILE and $DISTFILE.asc"
+else
+    echo "Warning: gpg not found, skipping signature"
+    echo "Created: $DISTFILE"
+fi
\ No newline at end of file