2
* Copyright (c) 2008 Lukas Mejdrech
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions
9
* - Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* - Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
* - The name of the author may not be used to endorse or promote products
15
* derived from this software without specific prior written permission.
17
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
* ICMP module implementation.
40
#include <fibril_synch.h>
43
#include <ipc/services.h>
48
#include <sys/types.h>
49
#include <byteorder.h>
51
#include <adt/hash_table.h>
53
#include <net/socket_codes.h>
54
#include <net/ip_protocols.h>
56
#include <net/modules.h>
57
#include <net/icmp_api.h>
58
#include <net/icmp_codes.h>
59
#include <net/icmp_common.h>
61
#include <packet_client.h>
62
#include <packet_remote.h>
63
#include <net_checksum.h>
64
#include <icmp_client.h>
65
#include <icmp_remote.h>
66
#include <il_remote.h>
67
#include <ip_client.h>
68
#include <ip_interface.h>
69
#include <net_interface.h>
70
#include <tl_remote.h>
72
#include <icmp_header.h>
74
/** ICMP module name */
77
/** Number of replies hash table keys */
80
/** Number of replies hash table buckets */
81
#define REPLY_BUCKETS 1024
84
* Original datagram length in bytes transfered to the error
85
* notification message.
87
#define ICMP_KEEP_LENGTH 8
89
/** Compute the ICMP datagram checksum.
91
* @param[in,out] header ICMP datagram header.
92
* @param[in] length Total datagram length.
94
* @return Computed checksum.
97
#define ICMP_CHECKSUM(header, length) \
98
htons(ip_checksum((uint8_t *) (header), (length)))
100
/** An echo request datagrams pattern. */
101
#define ICMP_ECHO_TEXT "ICMP hello from HelenOS."
103
/** ICMP reply data. */
105
/** Hash table link */
108
/** Reply identification and sequence */
110
icmp_param_t sequence;
112
/** Reply signaling */
113
fibril_condvar_t condvar;
120
static async_sess_t *net_sess = NULL;
121
static async_sess_t *ip_sess = NULL;
122
static bool error_reporting = true;
123
static bool echo_replying = true;
124
static packet_dimension_t icmp_dimension;
126
/** ICMP client identification counter */
127
static atomic_t icmp_client;
129
/** ICMP identifier and sequence number (client-specific) */
130
static fibril_local icmp_param_t icmp_id;
131
static fibril_local icmp_param_t icmp_seq;
133
/** Reply hash table */
134
static fibril_mutex_t reply_lock;
135
static hash_table_t replies;
137
static hash_index_t replies_hash(unsigned long key[])
140
* ICMP identifier and sequence numbers
143
hash_index_t index = ((key[0] & 0xffff) << 16) | (key[1] & 0xffff);
144
return (index % REPLY_BUCKETS);
147
static int replies_compare(unsigned long key[], hash_count_t keys, link_t *item)
149
icmp_reply_t *reply =
150
hash_table_get_instance(item, icmp_reply_t, link);
153
return (reply->id == key[0]);
155
return ((reply->id == key[0]) && (reply->sequence == key[1]));
158
static void replies_remove_callback(link_t *item)
162
static hash_table_operations_t reply_ops = {
163
.hash = replies_hash,
164
.compare = replies_compare,
165
.remove_callback = replies_remove_callback
168
/** Release the packet and return the result.
170
* @param[in] packet Packet queue to be released.
173
static void icmp_release(packet_t *packet)
175
pq_release_remote(net_sess, packet_get_id(packet));
178
/** Send the ICMP message.
180
* Set the message type and code and compute the checksum.
181
* Error messages are sent only if allowed in the configuration.
182
* Release the packet on errors.
184
* @param[in] type Message type.
185
* @param[in] code Message code.
186
* @param[in] packet Message packet to be sent.
187
* @param[in] header ICMP header.
188
* @param[in] error Error service to be announced. Should be
189
* SERVICE_ICMP or zero.
190
* @param[in] ttl Time to live.
191
* @param[in] tos Type of service.
192
* @param[in] dont_fragment Disable fragmentation.
194
* @return EOK on success.
195
* @return EPERM if the error message is not allowed.
198
static int icmp_send_packet(icmp_type_t type, icmp_code_t code,
199
packet_t *packet, icmp_header_t *header, services_t error, ip_ttl_t ttl,
200
ip_tos_t tos, bool dont_fragment)
202
/* Do not send an error if disabled */
203
if ((error) && (!error_reporting)) {
204
icmp_release(packet);
212
* The checksum needs to be calculated
213
* with a virtual checksum field set to
216
header->checksum = 0;
217
header->checksum = ICMP_CHECKSUM(header,
218
packet_get_data_length(packet));
220
int rc = ip_client_prepare_packet(packet, IPPROTO_ICMP, ttl, tos,
223
icmp_release(packet);
227
return ip_send_msg(ip_sess, -1, packet, SERVICE_ICMP, error);
230
/** Prepare the ICMP error packet.
232
* Truncate the original packet if longer than ICMP_KEEP_LENGTH bytes.
233
* Prefix and return the ICMP header.
235
* @param[in,out] packet Original packet.
237
* @return The prefixed ICMP header.
238
* @return NULL on errors.
241
static icmp_header_t *icmp_prepare_packet(packet_t *packet)
243
size_t total_length = packet_get_data_length(packet);
244
if (total_length <= 0)
247
size_t header_length = ip_client_header_length(packet);
248
if (header_length <= 0)
251
/* Truncate if longer than 64 bits (without the IP header) */
252
if ((total_length > header_length + ICMP_KEEP_LENGTH) &&
253
(packet_trim(packet, 0,
254
total_length - header_length - ICMP_KEEP_LENGTH) != EOK))
257
icmp_header_t *header = PACKET_PREFIX(packet, icmp_header_t);
261
bzero(header, sizeof(*header));
265
/** Request an echo message.
267
* Send a packet with specified parameters to the target host
268
* and wait for the reply upto the given timeout.
269
* Block the caller until the reply or the timeout occurs.
271
* @param[in] id Message identifier.
272
* @param[in] sequence Message sequence parameter.
273
* @param[in] size Message data length in bytes.
274
* @param[in] timeout Timeout in miliseconds.
275
* @param[in] ttl Time to live.
276
* @param[in] tos Type of service.
277
* @param[in] dont_fragment Disable fragmentation.
278
* @param[in] addr Target host address.
279
* @param[in] addrlen Torget host address length.
281
* @return ICMP_ECHO on success.
282
* @return ETIMEOUT if the reply has not arrived before the
284
* @return ICMP type of the received error notification.
285
* @return EINVAL if the addrlen parameter is less or equal to
287
* @return ENOMEM if there is not enough memory left.
290
static int icmp_echo(icmp_param_t id, icmp_param_t sequence, size_t size,
291
mseconds_t timeout, ip_ttl_t ttl, ip_tos_t tos, bool dont_fragment,
292
const struct sockaddr *addr, socklen_t addrlen)
297
size_t length = (size_t) addrlen;
299
packet_t *packet = packet_get_4_remote(net_sess, size,
300
icmp_dimension.addr_len, ICMP_HEADER_SIZE + icmp_dimension.prefix,
301
icmp_dimension.suffix);
305
/* Prepare the requesting packet, set the destination address. */
306
int rc = packet_set_addr(packet, NULL, (const uint8_t *) addr, length);
308
icmp_release(packet);
312
/* Allocate space in the packet */
313
uint8_t *data = (uint8_t *) packet_suffix(packet, size);
315
icmp_release(packet);
321
while (size > length + sizeof(ICMP_ECHO_TEXT)) {
322
memcpy(data + length, ICMP_ECHO_TEXT, sizeof(ICMP_ECHO_TEXT));
323
length += sizeof(ICMP_ECHO_TEXT);
325
memcpy(data + length, ICMP_ECHO_TEXT, size - length);
327
/* Prefix the header */
328
icmp_header_t *header = PACKET_PREFIX(packet, icmp_header_t);
330
icmp_release(packet);
334
bzero(header, sizeof(icmp_header_t));
335
header->un.echo.identifier = id;
336
header->un.echo.sequence_number = sequence;
338
/* Prepare the reply structure */
339
icmp_reply_t *reply = malloc(sizeof(icmp_reply_t));
341
icmp_release(packet);
346
reply->sequence = sequence;
347
fibril_condvar_initialize(&reply->condvar);
349
/* Add the reply to the replies hash table */
350
fibril_mutex_lock(&reply_lock);
352
unsigned long key[REPLY_KEYS] = {id, sequence};
353
hash_table_insert(&replies, key, &reply->link);
355
/* Send the request */
356
icmp_send_packet(ICMP_ECHO, 0, packet, header, 0, ttl, tos,
359
/* Wait for the reply. Timeout in microseconds. */
360
rc = fibril_condvar_wait_timeout(&reply->condvar, &reply_lock,
365
/* Remove the reply from the replies hash table */
366
hash_table_remove(&replies, key, REPLY_KEYS);
368
fibril_mutex_unlock(&reply_lock);
375
static int icmp_destination_unreachable(icmp_code_t code, icmp_param_t mtu,
378
icmp_header_t *header = icmp_prepare_packet(packet);
380
icmp_release(packet);
385
header->un.frag.mtu = mtu;
387
return icmp_send_packet(ICMP_DEST_UNREACH, code, packet, header,
388
SERVICE_ICMP, 0, 0, false);
391
static int icmp_source_quench(packet_t *packet)
393
icmp_header_t *header = icmp_prepare_packet(packet);
395
icmp_release(packet);
399
return icmp_send_packet(ICMP_SOURCE_QUENCH, 0, packet, header,
400
SERVICE_ICMP, 0, 0, false);
403
static int icmp_time_exceeded(icmp_code_t code, packet_t *packet)
405
icmp_header_t *header = icmp_prepare_packet(packet);
407
icmp_release(packet);
411
return icmp_send_packet(ICMP_TIME_EXCEEDED, code, packet, header,
412
SERVICE_ICMP, 0, 0, false);
415
static int icmp_parameter_problem(icmp_code_t code, icmp_param_t pointer,
418
icmp_header_t *header = icmp_prepare_packet(packet);
420
icmp_release(packet);
424
header->un.param.pointer = pointer;
425
return icmp_send_packet(ICMP_PARAMETERPROB, code, packet, header,
426
SERVICE_ICMP, 0, 0, false);
429
/** Try to set the pending reply result as the received message type.
431
* If the reply data is not present, the reply timed out and the other fibril
432
* is already awake. The packet is released.
434
* @param[in] packet The received reply message.
435
* @param[in] header The ICMP message header.
436
* @param[in] type The received reply message type.
437
* @param[in] code The received reply message code.
440
static void icmp_process_echo_reply(packet_t *packet, icmp_header_t *header,
441
icmp_type_t type, icmp_code_t code)
443
unsigned long key[REPLY_KEYS] =
444
{header->un.echo.identifier, header->un.echo.sequence_number};
446
/* The packet is no longer needed */
447
icmp_release(packet);
449
/* Find the pending reply */
450
fibril_mutex_lock(&reply_lock);
452
link_t *link = hash_table_find(&replies, key);
454
icmp_reply_t *reply =
455
hash_table_get_instance(link, icmp_reply_t, link);
457
reply->result = type;
458
fibril_condvar_signal(&reply->condvar);
461
fibril_mutex_unlock(&reply_lock);
464
/** Process the received ICMP packet.
466
* Notify the destination socket application.
468
* @param[in,out] packet Received packet.
469
* @param[in] error Packet error reporting service to prefix
470
* the received packet.
472
* @return EOK on success.
473
* @return EINVAL if the packet is not valid.
474
* @return EINVAL if the stored packet address is not the an_addr_t.
475
* @return EINVAL if the packet does not contain any data.
476
* @return NO_DATA if the packet content is shorter than the user
478
* @return ENOMEM if there is not enough memory left.
479
* @return EADDRNOTAVAIL if the destination socket does not exist.
480
* @return Other error codes as defined for the
481
* ip_client_process_packet() function.
484
static int icmp_process_packet(packet_t *packet, services_t error)
495
rc = icmp_client_process_packet(packet, &type, &code, NULL, NULL);
499
/* Remove the error header */
500
rc = packet_trim(packet, (size_t) rc, 0);
509
/* Get rid of the IP header */
510
size_t length = ip_client_header_length(packet);
511
rc = packet_trim(packet, length, 0);
515
length = packet_get_data_length(packet);
519
if (length < ICMP_HEADER_SIZE)
522
void *data = packet_get_data(packet);
526
/* Get ICMP header */
527
icmp_header_t *header = (icmp_header_t *) data;
529
if (header->checksum) {
530
while (ICMP_CHECKSUM(header, length) != IP_CHECKSUM_ZERO) {
532
* Set the original message type on error notification.
533
* Type swap observed in Qemu.
536
switch (header->type) {
538
header->type = ICMP_ECHO;
547
switch (header->type) {
550
icmp_process_echo_reply(packet, header, type, code);
552
icmp_process_echo_reply(packet, header, ICMP_ECHO, 0);
558
icmp_process_echo_reply(packet, header, type, code);
562
/* Do not send a reply if disabled */
565
int addrlen = packet_get_addr(packet, &src, NULL);
568
* Set both addresses to the source one (avoid the
569
* source address deletion before setting the
572
if ((addrlen > 0) && (packet_set_addr(packet, src, src,
573
(size_t) addrlen) == EOK)) {
575
icmp_send_packet(ICMP_ECHOREPLY, 0, packet,
585
case ICMP_DEST_UNREACH:
586
case ICMP_SOURCE_QUENCH:
588
case ICMP_ALTERNATE_ADDR:
589
case ICMP_ROUTER_ADV:
590
case ICMP_ROUTER_SOL:
591
case ICMP_TIME_EXCEEDED:
592
case ICMP_PARAMETERPROB:
593
case ICMP_CONVERSION_ERROR:
594
case ICMP_REDIRECT_MOBILE:
597
ip_received_error_msg(ip_sess, -1, packet,
598
SERVICE_IP, SERVICE_ICMP);
606
/** Process IPC messages from the IP module
608
* @param[in] iid Message identifier.
609
* @param[in,out] icall Message parameters.
610
* @param[in] arg Local argument.
613
static void icmp_receiver(ipc_callid_t iid, ipc_call_t *icall, void *arg)
619
if (!IPC_GET_IMETHOD(*icall))
622
switch (IPC_GET_IMETHOD(*icall)) {
623
case NET_TL_RECEIVED:
624
rc = packet_translate_remote(net_sess, &packet,
625
IPC_GET_PACKET(*icall));
627
rc = icmp_process_packet(packet, IPC_GET_ERROR(*icall));
629
icmp_release(packet);
632
async_answer_0(iid, (sysarg_t) rc);
635
async_answer_0(iid, (sysarg_t) ENOTSUP);
638
iid = async_get_call(icall);
642
/** Initialize the ICMP module.
644
* @param[in] sess Network module session.
646
* @return EOK on success.
647
* @return ENOMEM if there is not enough memory left.
650
int tl_initialize(async_sess_t *sess)
652
measured_string_t names[] = {
654
(uint8_t *) "ICMP_ERROR_REPORTING",
658
(uint8_t *) "ICMP_ECHO_REPLYING",
662
measured_string_t *configuration;
663
size_t count = sizeof(names) / sizeof(measured_string_t);
666
if (!hash_table_create(&replies, REPLY_BUCKETS, REPLY_KEYS, &reply_ops))
669
fibril_mutex_initialize(&reply_lock);
670
atomic_set(&icmp_client, 0);
673
ip_sess = ip_bind_service(SERVICE_IP, IPPROTO_ICMP, SERVICE_ICMP,
678
int rc = ip_packet_size_req(ip_sess, -1, &icmp_dimension);
682
icmp_dimension.prefix += ICMP_HEADER_SIZE;
683
icmp_dimension.content -= ICMP_HEADER_SIZE;
685
/* Get configuration */
686
configuration = &names[0];
687
rc = net_get_conf_req(net_sess, &configuration, count, &data);
692
if (configuration[0].value)
693
error_reporting = (configuration[0].value[0] == 'y');
695
if (configuration[1].value)
696
echo_replying = (configuration[1].value[0] == 'y');
698
net_free_settings(configuration, data);
704
/** Per-connection initialization
706
* Initialize client-specific global variables.
709
void tl_connection(void)
711
icmp_id = (icmp_param_t) atomic_postinc(&icmp_client);
715
/** Process the ICMP message.
717
* @param[in] callid Message identifier.
718
* @param[in] call Message parameters.
719
* @param[out] answer Answer.
720
* @param[out] count Number of arguments of the answer.
722
* @return EOK on success.
723
* @return ENOTSUP if the message is not known.
724
* @return Other error codes as defined for the packet_translate()
726
* @return Other error codes as defined for the
727
* icmp_destination_unreachable() function.
728
* @return Other error codes as defined for the
729
* icmp_source_quench() function.
730
* @return Other error codes as defined for the
731
* icmp_time_exceeded() function.
732
* @return Other error codes as defined for the
733
* icmp_parameter_problem() function.
736
* @see IS_NET_ICMP_MESSAGE()
739
int tl_message(ipc_callid_t callid, ipc_call_t *call,
740
ipc_call_t *answer, size_t *count)
742
struct sockaddr *addr;
749
switch (IPC_GET_IMETHOD(*call)) {
751
rc = async_data_write_accept((void **) &addr, false, 0, 0, 0, &size);
755
rc = icmp_echo(icmp_id, icmp_seq, ICMP_GET_SIZE(*call),
756
ICMP_GET_TIMEOUT(*call), ICMP_GET_TTL(*call),
757
ICMP_GET_TOS(*call), ICMP_GET_DONT_FRAGMENT(*call),
758
addr, (socklen_t) size);
764
case NET_ICMP_DEST_UNREACH:
765
rc = packet_translate_remote(net_sess, &packet,
766
IPC_GET_PACKET(*call));
770
return icmp_destination_unreachable(ICMP_GET_CODE(*call),
771
ICMP_GET_MTU(*call), packet);
773
case NET_ICMP_SOURCE_QUENCH:
774
rc = packet_translate_remote(net_sess, &packet,
775
IPC_GET_PACKET(*call));
779
return icmp_source_quench(packet);
781
case NET_ICMP_TIME_EXCEEDED:
782
rc = packet_translate_remote(net_sess, &packet,
783
IPC_GET_PACKET(*call));
787
return icmp_time_exceeded(ICMP_GET_CODE(*call), packet);
789
case NET_ICMP_PARAMETERPROB:
790
rc = packet_translate_remote(net_sess, &packet,
791
IPC_GET_PACKET(*call));
795
return icmp_parameter_problem(ICMP_GET_CODE(*call),
796
ICMP_GET_POINTER(*call), packet);
802
int main(int argc, char *argv[])
804
/* Start the module */
805
return tl_module_start(SERVICE_ICMP);