Files
42_ft_ping/src/main.c
2025-12-16 06:12:02 -06:00

244 lines
6.0 KiB
C

#include "dns.h"
#include "host.h"
#include "packet.h"
#include "parsing.h"
#include "print.h"
#include "setting.h"
#include "statistics.h"
#include <netinet/ip.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ctype.h>
#include <net/ethernet.h>
bool loop = true;
static void signal_handler(int code)
{
(void) code;
loop = false;
}
static int parsing_number(const char *str, size_t min, size_t max, size_t *out)
{
size_t number = 0;
const char *start = str;
while (*start != '\0')
{
if (!isdigit(*start))
goto value_error;
size_t tmp = number * 10 + *start - '0';
if (tmp < number)
goto range_error;
number = tmp;
start++;
}
if (min > number || number > max)
goto range_error;
*out = number;
return 0;
range_error:
print_err("invalid argument: '%s': out of range: %zu <= value <= %zu", str, min, max);
return 1;
value_error:
print_err("invalid argument: in '%s' '%c' is not a digit", str, *start);
return 2;
}
static int get_setting(char * const *av, struct setting *settings)
{
struct param parameters[] = {
{NULL, "?", OPTION, false},
{NULL, "v", OPTION, false},
{NULL, "n", OPTION, false},
{"ttl", NULL, ARGUMENT, "116"},
{NULL, "s", ARGUMENT, "56"},
{NULL, "p", ARGUMENT, "0"},
{NULL, NULL, 0, NULL},
};
char *hostname = parsing(av, parameters);
if (hostname == NULL)
return 1;
settings->dest.hostname = hostname;
settings->help = parameters[0].value;
settings->verbose = parameters[1].value;
settings->numeric_only = parameters[2].value;
size_t ttl;
if (parsing_number(parameters[3].value, 1, 255, &ttl))
return 2;
settings->ttl = ttl;
if (parsing_number(parameters[4].value, 0, 2147483647, &settings->payload_size))
return 3;
if (parsing_number(parameters[5].value, 0, 2147483647, &settings->preload))
return 4;
return 0;
}
static int create_socket(struct setting const *settings)
{
int ret;
(void)settings;
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd == -1)
{
print_err("open socket failed, maybe try in root (:");
return -1;
}
ret = setsockopt(sockfd, IPPROTO_IP, IP_TTL, &settings->ttl, sizeof(settings->ttl));
if (ret != 0)
{
close(sockfd);
print_err("Failed to setsockopt(): ttl");
return -1;
}
struct timeval tv = {2, 0};
ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (ret != 0)
{
close(sockfd);
print_err("Failed to setsockopt(): recv timeout");
return -1;
}
return sockfd;
}
static int preload(struct setting const *settings, int sockfd, char const *packet, size_t packet_size)
{
for (size_t i = 0; i < settings->preload; i++)
{
if (sendto(sockfd, packet, packet_size, 0, (struct sockaddr *) &settings->dest.ip, sizeof(settings->dest.ip)) == -1)
return 1;
}
return 0;
}
static int check_reply(struct sockaddr_in const *me, struct sockaddr_in const *sender, char *sent, char *received, size_t sent_size, size_t recv_size)
{
if (sent_size != recv_size)
return 0;
if (((struct icmphdr *)sent)->type != 0)
return 0;
if (check_packet_conformity(sent, received, sent_size))
return 1;
return memcmp(sender, me, sizeof(struct sockaddr_in));
}
int main(int ac, char **av)
{
(void) ac;
struct statistics stats;
struct setting settings;
if (get_setting(av + 1, &settings))
goto error0;
size_t packet_size = sizeof(struct icmphdr) + settings.payload_size;
size_t recv_packet_size = packet_size + sizeof(struct iphdr);
signal(SIGINT, signal_handler);
if (dns_lookup(&settings.dest))
goto error0;
int sockfd = create_socket(&settings);
if (sockfd == -1)
goto error0;
char *packet = packet_create(settings.payload_size);
if (packet == NULL)
goto error1;
char *buffer;
buffer = malloc((recv_packet_size) * sizeof(char));
if (buffer == NULL)
{
print_err("error: allocation failed.");
goto error2;
}
struct sockaddr_in sender;
socklen_t len = sizeof(sender);
bzero(&stats, sizeof(struct statistics));
print_header(&settings);
if (preload(&settings, sockfd, packet, packet_size))
goto error3;
while (loop) {
struct timeval stop, start;
gettimeofday(&start, NULL);
if (sendto(sockfd, packet, packet_size, 0, (struct sockaddr *) &settings.dest.ip, sizeof(settings.dest.ip)) == -1)
{
print_err("error: send packet failed.");
goto error2;
}
stats.packets_sent++;
ssize_t ret = 0;
do
{
ret = recvfrom(sockfd, buffer, recv_packet_size, 0, (struct sockaddr *) &sender, &len);
gettimeofday(&stop, NULL);
}
while (ret >= 0 && check_reply(&settings.dest.ip, &sender, packet, buffer + sizeof(struct iphdr), packet_size, ret));
if (ret >= (ssize_t) (sizeof(struct icmphdr) + sizeof(struct iphdr)) && ((struct icmphdr *) buffer + sizeof(struct iphdr))->type == 0)
{
stats.packets_received++;
print_recv(&settings, buffer, &start, &stop, &sender);
}
sleep(1);
if (packet_update(packet, settings.payload_size))
goto error3;
}
free(packet);
free(buffer);
close(sockfd);
print_statistics(&stats, &settings);
return 0;
error3:
free(buffer);
error2:
free(packet);
error1:
close(sockfd);
error0:
return 1;
}