Linux ping source code

Пишем ping на Си

Видите это довольное лицо? Это Майк Муусс — автор, наверное самой часто используемой утилиты ping (недаром он на фотке так радуется).

Некоторое время назад мне самому довелось написать ping, но в виде отдельной функции.

И на мой взгляд, для си-программистов, которые только начинают работать с сетью, это очень полезная утилита для самостоятельной разработки. Почему? Потому, что для разработки этой утилиты нужно научится делать, казалось бы, самые примитивные действия — отправлять и принимать пакет.

ICMP

Давайте сначала вкратце определим требования конкретно для нашей функции ping. Функция нужна нам для проверки целостности и качества соединения между хостами. И для этого нам достаточно отправить ICMP-эхопакет с запросом целевому хосту и получить от нее ответ. Для оценки качества соединения будем использовать время между запросом и получением ответа.

Системные вызовы

Операционная система (в нашем случае Linux) позволяет получать доступ к сетевым устройствам посредством системных вызовов. Для ping мне потребовались следующие вызовы:

  • socket — используется для создания сокета
  • select — в нашем случае используется для проверки состояния сокета
  • sendto — для отправки данных
  • recvfrom — для получения данных
  • inet_aton — для преобразования ip-aдреса в строковом виде в структуру sockaddr_in
  • и другие

Реализация

Функция для получения текущего времени

Для начала напишем вспомогательную функцию для получения текущего времени в миллисекундах.

static ulong get_cur_time_ms()  struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); ulong time_ms = time.tv_sec * 1000 + (time.tv_nsec / 1000000); return time_ms; > 

Данные

Далее нужно определится с тем, какие данные будем отправлять. Объявим структуру, который будет иметь icmp заголовок и поле данных. Эта структура будет служить нашим пакетом.

typedef struct  struct icmp hdr; // заголовок char data[PING_PKT_DATA_SZ]; // данные > ping_pkt_t; 

Напишем функцию для заполнения нашей структуры.

static void prepare_icmp_pkt(ping_pkt_t *ping_pkt)  memset(ping_pkt, 0, sizeof(ping_pkt_t)); srand(time(NULL)); const short random_id = rand(); ping_pkt->hdr.icmp_hun.ih_idseq.icd_id = random_id; // некоторый id-пакета ping_pkt->hdr.icmp_type = ICMP_ECHO; // говорим, что это эхо-пакет ping_pkt->hdr.icmp_hun.ih_idseq.icd_seq = 0; // номер запроса (у нас всегда 0) ping_pkt->hdr.icmp_cksum = checksum(ping_pkt, sizeof(ping_pkt_t)); // контрольная сумма > 

Функция ping

Сигнатура нашей основной функции будет следующим.

int ping(const char* ip, const ulong timeout, ulong* time) 

На входе мы получаем ip-адрес пингуемого хоста и таймаут ожидания. На выходе результат выполнения и третий аргумент time куда запишем время пинга хоста.

Тут же, с помощью inet_aton , преобразуем ip в строковом виде в структуру sockaddr_in .

struct sockaddr_in to_addr; to_addr.sin_family = AF_INET; if (!inet_aton(ip, (struct in_addr*)&to_addr.sin_addr.s_addr))  perror("inet_aton"); > 

Сокет

Далее нам нужно создать сокет путем вызова соответствующей функции.

int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sock  0)  perror("socket"); > 

Директива SOCK_RAW говорит о том, что мы отправляем “сырой” пакет без использования транспортного уровня (UDP/TCP). С помощью директивы IPPROTO_ICMP говорим, что мы будем использовать протокол ICMP. Функция socket() возвращает нам файловый дескриптор, который будем использовать в последующем.

Отправляем пакет

Перед тем как отправить пакет фиксируем время.

const ulong start_time_ms = get_cur_time_ms(); 
int res = sendto(sock, &ping_pkt, sizeof(ping_pkt_t), 0, &to_addr, sizeof(to_addr)); if (res  0)  perror("error"); > 

Получаем ответ

Для получения ответа нужно считать принятые данные из сокета. Данные будут записаны в структуру ip_pkt .

ip_pkt_t ip_pkt; struct sockaddr_in from_addr; socklen_t socklen = sizeof(struct sockaddr_in); int res = recvfrom(sock, &ip_pkt, sizeof(ip_pkt_t), 0, &from_addr, &socklen); if (res  0)  perror("error"); > 

Таймауты

Далее на мой взгляд идет самая сложная часть. Делать вызов recvfrom , то есть принимать данные из сети, нам нужно только при наличии данных. Для этого нам поможет системный вызов select , который используется для отслеживания состояния сокета. А именно:

  • Я взвожу select на требуемый таймаут, после чего происходит блокировка на этой функции до момента пока не появятся данные или не истечет время
  • Если select разблокировался по таймауту, значит время истекло и мы выходим из цикла
  • Если select разблокировался из-за появления данных, то вызываем recvfrom и записываем данные в структуру ip_pkt
  • Если данные адресованы не нам, взводим select заново, ведь у нас еще осталось время
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
struct timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; for (;;)  fd_set rfd; FD_ZERO(&rfd); FD_SET(sock, &rfd); // блокируемся пока не получим данные или не истечет время int n = select(sock + 1, &rfd, 0, 0, &tv); if (n == 0)  break; > // выходим из цикла по таймауту if (n  0)  break; > // произошла ошибка // значит у нас есть данные, фиксируем время const ulong elapsed_time = (get_cur_time_ms() - start_time_ms); if (FD_ISSET(sock, &rfd))  ip_pkt_t ip_pkt; // сюда будут записаны данные struct sockaddr_in from_addr; // а здесь адрес от кого получили данные socklen_t socklen = sizeof(struct sockaddr_in); if (recvfrom(sock, &ip_pkt, sizeof(ip_pkt_t), 0, &from_addr, &socklen)  0)  break; > if (to_addr.sin_addr.s_addr == from_addr.sin_addr.s_addr)  // проверка ip-адреса if (reply_id == ip_pkt.ping_pkt.hdr.icmp_hun.ih_idseq.icd_id)  // если пакет от того кому отправили и ID запроса и ответа совпадают // то пакет наш, фиксируем время и выходим из цикла *reply_time = elapsed_time; break; > > > // Данные не наши, поэтому // задаем новый таймаут по оставшемуся времени и возвращаемся в начало цикла const int new_timeout = timeout - elapsed_time; tv.tv_sec = new_timeout / 1000; tv.tv_usec = (new_timeout % 1000) * 1000; > 

Запуск

Компилируем, запускаем ping и видим, что все работает.

$ sudo ./ping 8.8.8.8 1000 Ping host=8.8.8.8 timeout=1000 time=47 ms $ sudo ./ping 8.8.8.8 1000 Ping host=8.8.8.8 timeout=1000 time=52 ms $ sudo ./ping 5.5.5.5 1000 Timeout.

Стоп! Но почему для запуска требуется sudo? А потому, что мы используем сырые пакеты. Помните директиву SOCK_RAW ?

Тогда почему стандартная утилита ping не требует sudo? Ничего страшного. Пара нехитрых действий и наш ping тоже запускается без sudo.

$ sudo chown root:root ./ping $ sudo chmod u+s ./ping $ ./ping 8.8.8.8 1000 # технически он запускается из под рута Ping host=8.8.8.8 timeout=1000 time=56 ms

Если есть вопросы или замечания — пишите! Я обязательно постараюсь ответить. Спасибо!

Источник

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

self made linux’s ping command implemented by C

Reiddd/linux-ping

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Sign In Required

Please sign in to use Codespaces.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching Xcode

If nothing happens, download Xcode and try again.

Launching Visual Studio Code

Your codespace will open once ready.

There was a problem preparing your codespace, please try again.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

self made linux’s ping command implemented by C. can’t customize ICMP packages. send 5 packages each time.

> gcc -o my_ping ping.c > my_ping 127.0.0.1 ping 127.0.0.1 (127.0.0.1) : 24 bytes of data. 24 bytes from 0.0.0.0 : icmp_seq = 1 ttl = 64 rtt = 0.000000ms 24 bytes from 0.0.0.0 : icmp_seq = 2 ttl = 64 rtt = 0.000000ms 24 bytes from 0.0.0.0 : icmp_seq = 3 ttl = 64 rtt = 0.000000ms 24 bytes from 0.0.0.0 : icmp_seq = 4 ttl = 64 rtt = 0.000000ms 24 bytes from 0.0.0.0 : icmp_seq = 5 ttl = 64 rtt = 0.000000ms --- 127.0.0.1 ping statistics --- 5 packets transmitted, 5 received, 0% packets lost 

About

self made linux’s ping command implemented by C

Источник

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

Simple (cross-platform) implementation of the «ping» command

License

sryze/ping

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Sign In Required

Please sign in to use Codespaces.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching Xcode

If nothing happens, download Xcode and try again.

Launching Visual Studio Code

Your codespace will open once ready.

There was a problem preparing your codespace, please try again.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

This is a rather basic implementation of the ping command in C. It was created for learning more about raw sockets and how ping works (and for fun).

  • Cross-platform: can compile and run on Windows, Linux, macOS, *BSD
  • Supports IPv6
  • Displays time with microsecond precision
$ ./ping google.com PING google.com (142.250.74.206) Received reply from 142.250.74.206: seq=0, time=103.307 ms Received reply from 142.250.74.206: seq=1, time=91.200 ms Received reply from 142.250.74.206: seq=2, time=103.080 ms Received reply from 142.250.74.206: seq=3, time=94.531 ms Received reply from 142.250.74.206: seq=4, time=92.204 ms ^C

ping accepts only one argument — the name of the host to ping.

To build ping you’ll need a C89 compiler and CMake. Supported platforms include Linux, Mac OS X, Windows (MSVC, Cygwin, MinGW), FreeBSD, NetBSD, OpenBSD, Solaris.

After you cloned this repo run the following commands to build an executable:

cd ping mkdir build && cd build cmake ../ -G "Unix Makefiles" make

Use of raw sockets usually requires administrative privileges, therefore you will need to run ping as root:

There is also a way to make it run without typing sudo every time: set the suid bit on the executable and change its owner to root :

sudo chmod +s ./ping sudo chown root ./ping

After starting ping , it will run indefinitely until you interrupt it, e.g. by doing Ctrl-C in the terminal.

The scripts directory contains a couple of scripts to aid debugging:

  • capture.sh — captures ICMP traffic with tcpdump and saves it to ping.pcap (needs to be run as root)
  • dump.sh — prints the contents of ping.pcap in a nice form ( tcpdump may actually display helpful errors there, like a miscalculated checksum)

About

Simple (cross-platform) implementation of the «ping» command

Источник

Читайте также:  Check what is mounted linux
Оцените статью
Adblock
detector