לדלג לתוכן

11.3 UDP ו raw sockets פתרון

פתרון - UDP ו-raw sockets

פתרון 1 - שרת ולקוח UDP - מילון

שרת:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 9999
#define BUFFER_SIZE 1024
#define MAX_ENTRIES 1024

struct entry {
    char key[64];
    char value[256];
    int used;
};

struct entry dict[MAX_ENTRIES];

int find_key(const char *key)
{
    for (int i = 0; i < MAX_ENTRIES; i++) {
        if (dict[i].used && strcmp(dict[i].key, key) == 0)
            return i;
    }
    return -1;
}

int find_empty(void)
{
    for (int i = 0; i < MAX_ENTRIES; i++) {
        if (!dict[i].used)
            return i;
    }
    return -1;
}

void handle_command(const char *cmd, char *response, int resp_size)
{
    char op[16], key[64], value[256];

    if (sscanf(cmd, "SET %63s %255[^\n]", key, value) == 2) {
        int idx = find_key(key);
        if (idx == -1)
            idx = find_empty();
        if (idx == -1) {
            snprintf(response, resp_size, "ERROR: dict full");
            return;
        }
        strncpy(dict[idx].key, key, sizeof(dict[idx].key) - 1);
        strncpy(dict[idx].value, value, sizeof(dict[idx].value) - 1);
        dict[idx].used = 1;
        snprintf(response, resp_size, "OK");

    } else if (sscanf(cmd, "GET %63s", key) == 1) {
        int idx = find_key(key);
        if (idx == -1)
            snprintf(response, resp_size, "NOT FOUND");
        else
            snprintf(response, resp_size, "%s", dict[idx].value);

    } else if (sscanf(cmd, "DEL %63s", key) == 1) {
        int idx = find_key(key);
        if (idx == -1) {
            snprintf(response, resp_size, "NOT FOUND");
        } else {
            dict[idx].used = 0;
            snprintf(response, resp_size, "DELETED");
        }

    } else {
        snprintf(response, resp_size, "ERROR: unknown command");
    }
}

int main(void)
{
    memset(dict, 0, sizeof(dict));

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }
    printf("dict server on UDP port %d\n", PORT);

    char buffer[BUFFER_SIZE];
    char response[BUFFER_SIZE];

    while (1) {
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);

        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,
                             (struct sockaddr *)&client_addr, &client_len);
        if (n <= 0) continue;
        buffer[n] = '\0';

        handle_command(buffer, response, sizeof(response));

        sendto(sockfd, response, strlen(response), 0,
               (struct sockaddr *)&client_addr, client_len);
    }

    close(sockfd);
    return 0;
}

לקוח:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BUFFER_SIZE 1024

int main(int argc, char *argv[])
{
    const char *ip = (argc > 1) ? argv[1] : "127.0.0.1";
    int port = (argc > 2) ? atoi(argv[2]) : 9999;

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(1);
    }

    /* timeout לקבלה */
    struct timeval tv = {2, 0};
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &server_addr.sin_addr);

    char input[BUFFER_SIZE];
    char buffer[BUFFER_SIZE];

    printf("dict client (SET key value | GET key | DEL key)\n");
    while (1) {
        printf("> ");
        if (fgets(input, sizeof(input), stdin) == NULL)
            break;
        input[strcspn(input, "\n")] = '\0';
        if (strlen(input) == 0)
            continue;
        if (strcmp(input, "quit") == 0)
            break;

        sendto(sockfd, input, strlen(input), 0,
               (struct sockaddr *)&server_addr, sizeof(server_addr));

        struct sockaddr_in from_addr;
        socklen_t from_len = sizeof(from_addr);
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,
                             (struct sockaddr *)&from_addr, &from_len);
        if (n > 0) {
            buffer[n] = '\0';
            printf("%s\n", buffer);
        } else {
            printf("timeout - no response\n");
        }
    }

    close(sockfd);
    return 0;
}

שימו לב: הוספנו timeout לסוקט של הלקוח. בלעדיו, אם השרת לא עובד, recvfrom ייתקע לנצח.


פתרון 2 - העברת קבצים בUDP

לקוח (שולח):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define CHUNK_SIZE 1000

struct file_packet {
    uint32_t seq_num;
    uint32_t data_len;
    char data[CHUNK_SIZE];
};

int main(int argc, char *argv[])
{
    if (argc != 4) {
        fprintf(stderr, "usage: %s <server_ip> <port> <file>\n", argv[0]);
        exit(1);
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);
    const char *filename = argv[3];

    FILE *fp = fopen(filename, "rb");
    if (!fp) {
        perror("fopen");
        exit(1);
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &server_addr.sin_addr);

    struct file_packet pkt;
    uint32_t seq = 0;
    size_t bytes_read;

    while ((bytes_read = fread(pkt.data, 1, CHUNK_SIZE, fp)) > 0) {
        pkt.seq_num = htonl(seq);
        pkt.data_len = htonl(bytes_read);

        sendto(sockfd, &pkt, sizeof(uint32_t) * 2 + bytes_read, 0,
               (struct sockaddr *)&server_addr, sizeof(server_addr));

        printf("sent packet %u (%zu bytes)\n", seq, bytes_read);
        seq++;

        usleep(1000);  /* השהייה קצרה למניעת הצפה */
    }

    /* שליחת חבילת סיום (data_len = 0) */
    pkt.seq_num = htonl(seq);
    pkt.data_len = 0;
    sendto(sockfd, &pkt, sizeof(uint32_t) * 2, 0,
           (struct sockaddr *)&server_addr, sizeof(server_addr));

    printf("done. sent %u packets.\n", seq);

    fclose(fp);
    close(sockfd);
    return 0;
}

שרת (מקבל):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 5555
#define CHUNK_SIZE 1000
#define BUFFER_SIZE (sizeof(uint32_t) * 2 + CHUNK_SIZE)

struct file_packet {
    uint32_t seq_num;
    uint32_t data_len;
    char data[CHUNK_SIZE];
};

int main(int argc, char *argv[])
{
    const char *output = (argc > 1) ? argv[1] : "received_file";

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }
    printf("file receiver on UDP port %d, writing to: %s\n", PORT, output);

    FILE *fp = fopen(output, "wb");
    if (!fp) {
        perror("fopen");
        exit(1);
    }

    char buffer[BUFFER_SIZE];
    uint32_t packets_received = 0;
    uint32_t max_seq = 0;

    while (1) {
        struct sockaddr_in from;
        socklen_t from_len = sizeof(from);
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                             (struct sockaddr *)&from, &from_len);
        if (n < (ssize_t)(sizeof(uint32_t) * 2))
            continue;

        struct file_packet *pkt = (struct file_packet *)buffer;
        uint32_t seq = ntohl(pkt->seq_num);
        uint32_t data_len = ntohl(pkt->data_len);

        if (data_len == 0) {
            printf("received end marker at seq %u\n", seq);
            max_seq = seq;
            break;
        }

        fwrite(pkt->data, 1, data_len, fp);
        packets_received++;

        if (seq + 1 > max_seq)
            max_seq = seq + 1;

        printf("received packet %u (%u bytes)\n", seq, data_len);
    }

    fclose(fp);
    close(sockfd);

    printf("\nresults: %u packets received out of %u expected\n",
           packets_received, max_seq);
    if (max_seq > 0)
        printf("packet loss: %.1f%%\n",
               (1.0 - (double)packets_received / max_seq) * 100);

    return 0;
}

על localhost כמעט לא יהיה אובדן. כדי לראות אובדן, אפשר להוסיף שיבוש מלאכותי עם tc netem:

sudo tc qdisc add dev lo root netem loss 10%

ואחרי הבדיקה להסיר:

sudo tc qdisc del dev lo root


פתרון 3 - כלי ping

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <float.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <errno.h>

#define PACKET_SIZE 64

uint16_t calc_checksum(void *data, int len)
{
    uint16_t *buf = data;
    uint32_t sum = 0;
    while (len > 1) {
        sum += *buf++;
        len -= 2;
    }
    if (len == 1)
        sum += *(uint8_t *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    return (uint16_t)~sum;
}

int main(int argc, char *argv[])
{
    int count = 4;
    const char *dest_ip = NULL;

    /* פענוח ארגומנטים */
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) {
            count = atoi(argv[++i]);
        } else {
            dest_ip = argv[i];
        }
    }

    if (!dest_ip) {
        fprintf(stderr, "usage: %s [-c count] <ip>\n", argv[0]);
        exit(1);
    }

    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd == -1) {
        perror("socket (need root)");
        exit(1);
    }

    struct timeval tv = {1, 0};
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    struct sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    inet_pton(AF_INET, dest_ip, &dest_addr.sin_addr);

    printf("PING %s: %d data bytes\n", dest_ip, PACKET_SIZE);

    pid_t pid = getpid();
    int sent = 0, received = 0;
    double rtt_min = DBL_MAX, rtt_max = 0, rtt_sum = 0;

    for (int seq = 0; seq < count; seq++) {
        /* בניית חבילה */
        char packet[PACKET_SIZE];
        memset(packet, 0, sizeof(packet));

        struct icmphdr *icmp = (struct icmphdr *)packet;
        icmp->type = ICMP_ECHO;
        icmp->code = 0;
        icmp->un.echo.id = htons(pid & 0xFFFF);
        icmp->un.echo.sequence = htons(seq);
        icmp->checksum = 0;
        icmp->checksum = calc_checksum(packet, PACKET_SIZE);

        struct timespec t_send, t_recv;
        clock_gettime(CLOCK_MONOTONIC, &t_send);

        ssize_t s = sendto(sockfd, packet, PACKET_SIZE, 0,
                           (struct sockaddr *)&dest_addr, sizeof(dest_addr));
        if (s == -1) {
            perror("sendto");
            sent++;
            sleep(1);
            continue;
        }
        sent++;

        char recv_buf[1024];
        struct sockaddr_in from;
        socklen_t from_len = sizeof(from);

        ssize_t r = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0,
                             (struct sockaddr *)&from, &from_len);
        if (r == -1) {
            printf("request timeout for icmp_seq %d\n", seq);
            sleep(1);
            continue;
        }

        clock_gettime(CLOCK_MONOTONIC, &t_recv);

        struct iphdr *ip_hdr = (struct iphdr *)recv_buf;
        int ip_len = ip_hdr->ihl * 4;
        struct icmphdr *reply = (struct icmphdr *)(recv_buf + ip_len);

        if (reply->type == ICMP_ECHOREPLY) {
            received++;
            double rtt = (t_recv.tv_sec - t_send.tv_sec) * 1000.0 +
                         (t_recv.tv_nsec - t_send.tv_nsec) / 1e6;

            if (rtt < rtt_min) rtt_min = rtt;
            if (rtt > rtt_max) rtt_max = rtt;
            rtt_sum += rtt;

            char from_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &from.sin_addr, from_ip, sizeof(from_ip));

            printf("%ld bytes from %s: icmp_seq=%d ttl=%d time=%.1f ms\n",
                   r - ip_len, from_ip, seq, ip_hdr->ttl, rtt);
        }

        sleep(1);
    }

    /* סטטיסטיקות */
    printf("\n--- %s ping statistics ---\n", dest_ip);
    printf("%d packets transmitted, %d received, %.0f%% packet loss\n",
           sent, received,
           sent > 0 ? (1.0 - (double)received / sent) * 100 : 0.0);

    if (received > 0) {
        printf("rtt min/avg/max = %.1f/%.1f/%.1f ms\n",
               rtt_min, rtt_sum / received, rtt_max);
    }

    close(sockfd);
    return 0;
}

קומפילציה והרצה:

gcc -o myping myping.c
sudo ./myping -c 10 8.8.8.8


פתרון 4 - sniffer פשוט

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>

#define BUFFER_SIZE 65536

/* סטטיסטיקות גלובליות */
volatile int running = 1;
unsigned long total_packets = 0;
unsigned long tcp_packets = 0;
unsigned long udp_packets = 0;
unsigned long other_packets = 0;
time_t last_stats_time;

void handle_signal(int sig)
{
    (void)sig;
    running = 0;
}

void print_stats(void)
{
    printf("\n--- statistics ---\n");
    printf("total: %lu  TCP: %lu  UDP: %lu  other: %lu\n\n",
           total_packets, tcp_packets, udp_packets, other_packets);
}

int main(int argc, char *argv[])
{
    int filter_port = 0;
    if (argc > 1)
        filter_port = atoi(argv[1]);

    signal(SIGINT, handle_signal);

    int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sockfd == -1) {
        perror("socket (need root)");
        exit(1);
    }

    /* timeout כדי שנוכל לבדוק סטטיסטיקות */
    struct timeval tv = {1, 0};
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    if (filter_port)
        printf("sniffing packets on port %d...\n\n", filter_port);
    else
        printf("sniffing all packets...\n\n");

    last_stats_time = time(NULL);
    char buffer[BUFFER_SIZE];

    while (running) {
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);

        /* בדיקת סטטיסטיקות כל 10 שניות */
        time_t now = time(NULL);
        if (now - last_stats_time >= 10) {
            print_stats();
            last_stats_time = now;
        }

        if (n <= 0) continue;

        /* Ethernet */
        struct ethhdr *eth = (struct ethhdr *)buffer;
        if (ntohs(eth->h_proto) != ETH_P_IP)
            continue;

        /* IP */
        struct iphdr *ip = (struct iphdr *)(buffer + sizeof(struct ethhdr));
        int ip_hdr_len = ip->ihl * 4;
        void *transport = buffer + sizeof(struct ethhdr) + ip_hdr_len;

        struct in_addr src_ip, dst_ip;
        src_ip.s_addr = ip->saddr;
        dst_ip.s_addr = ip->daddr;

        int src_port = 0, dst_port = 0;
        const char *proto_str;

        if (ip->protocol == IPPROTO_TCP) {
            struct tcphdr *tcp = (struct tcphdr *)transport;
            src_port = ntohs(tcp->source);
            dst_port = ntohs(tcp->dest);
            proto_str = "TCP";
            tcp_packets++;
        } else if (ip->protocol == IPPROTO_UDP) {
            struct udphdr *udp = (struct udphdr *)transport;
            src_port = ntohs(udp->source);
            dst_port = ntohs(udp->dest);
            proto_str = "UDP";
            udp_packets++;
        } else if (ip->protocol == IPPROTO_ICMP) {
            proto_str = "ICMP";
            other_packets++;
        } else {
            proto_str = "OTHER";
            other_packets++;
        }
        total_packets++;

        /* סינון לפי פורט */
        if (filter_port > 0) {
            if (src_port != filter_port && dst_port != filter_port)
                continue;
        }

        /* הדפסה */
        printf("%s %s:%d -> %s:%d (%d bytes)\n",
               proto_str, inet_ntoa(src_ip), src_port,
               inet_ntoa(dst_ip), dst_port,
               ntohs(ip->tot_len));
    }

    printf("\n");
    print_stats();

    close(sockfd);
    return 0;
}

קומפילציה והרצה:

gcc -o sniffer sniffer.c
sudo ./sniffer        # כל התעבורה
sudo ./sniffer 80     # רק פורט 80
sudo ./sniffer 443    # רק HTTPS

שימו לב ל-inet_ntoa - הפונקציה הזו מחזירה מצביע לבאפר סטטי, מה שאומר שקריאה שנייה דורסת את התוצאה הראשונה. בקוד שלנו זה בסדר כי אנחנו קוראים לה פעמיים באותו printf, אבל הקומפיילר מבטיח שהערכים מחושבים לפני שהprintf מופעל. אם זה מדאיג, אפשר להשתמש ב-inet_ntop ובבאפרים נפרדים.