icmp.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2008 Lukas Mejdrech
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  *
00009  * - Redistributions of source code must retain the above copyright
00010  *   notice, this list of conditions and the following disclaimer.
00011  * - Redistributions in binary form must reproduce the above copyright
00012  *   notice, this list of conditions and the following disclaimer in the
00013  *   documentation and/or other materials provided with the distribution.
00014  * - The name of the author may not be used to endorse or promote products
00015  *   derived from this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
00018  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00019  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
00020  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
00021  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
00022  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00023  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00024  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00025  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
00026  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00027  */
00028 
00037 #include <async.h>
00038 #include <atomic.h>
00039 #include <fibril.h>
00040 #include <fibril_synch.h>
00041 #include <stdint.h>
00042 #include <str.h>
00043 #include <ipc/services.h>
00044 #include <ipc/net.h>
00045 #include <ipc/tl.h>
00046 #include <ipc/icmp.h>
00047 #include <sys/time.h>
00048 #include <sys/types.h>
00049 #include <byteorder.h>
00050 #include <errno.h>
00051 #include <adt/hash_table.h>
00052 
00053 #include <net/socket_codes.h>
00054 #include <net/ip_protocols.h>
00055 #include <net/inet.h>
00056 #include <net/modules.h>
00057 #include <net/icmp_api.h>
00058 #include <net/icmp_codes.h>
00059 #include <net/icmp_common.h>
00060 
00061 #include <packet_client.h>
00062 #include <packet_remote.h>
00063 #include <net_checksum.h>
00064 #include <icmp_client.h>
00065 #include <icmp_remote.h>
00066 #include <il_remote.h>
00067 #include <ip_client.h>
00068 #include <ip_interface.h>
00069 #include <net_interface.h>
00070 #include <tl_remote.h>
00071 #include <tl_skel.h>
00072 #include <icmp_header.h>
00073 
00075 #define NAME  "icmp"
00076 
00078 #define REPLY_KEYS  2
00079 
00081 #define REPLY_BUCKETS  1024
00082 
00087 #define ICMP_KEEP_LENGTH  8
00088 
00097 #define ICMP_CHECKSUM(header, length) \
00098         htons(ip_checksum((uint8_t *) (header), (length)))
00099 
00101 #define ICMP_ECHO_TEXT  "ICMP hello from HelenOS."
00102 
00104 typedef struct {
00106         link_t link;
00107         
00109         icmp_param_t id;
00110         icmp_param_t sequence;
00111         
00113         fibril_condvar_t condvar;
00114         
00116         int result;
00117 } icmp_reply_t;
00118 
00120 static int phone_net = -1;
00121 static int phone_ip = -1;
00122 static bool error_reporting = true;
00123 static bool echo_replying = true;
00124 static packet_dimension_t icmp_dimension;
00125 
00127 static atomic_t icmp_client;
00128 
00130 static fibril_local icmp_param_t icmp_id;
00131 static fibril_local icmp_param_t icmp_seq;
00132 
00134 static fibril_mutex_t reply_lock;
00135 static hash_table_t replies;
00136 
00137 static hash_index_t replies_hash(unsigned long key[])
00138 {
00139         /*
00140          * ICMP identifier and sequence numbers
00141          * are 16-bit values.
00142          */
00143         hash_index_t index = ((key[0] & 0xffff) << 16) | (key[1] & 0xffff);
00144         return (index % REPLY_BUCKETS);
00145 }
00146 
00147 static int replies_compare(unsigned long key[], hash_count_t keys, link_t *item)
00148 {
00149         icmp_reply_t *reply =
00150             hash_table_get_instance(item, icmp_reply_t, link);
00151         
00152         if (keys == 1)
00153                 return (reply->id == key[0]);
00154         else
00155                 return ((reply->id == key[0]) && (reply->sequence == key[1]));
00156 }
00157 
00158 static void replies_remove_callback(link_t *item)
00159 {
00160 }
00161 
00162 static hash_table_operations_t reply_ops = {
00163         .hash = replies_hash,
00164         .compare = replies_compare,
00165         .remove_callback = replies_remove_callback
00166 };
00167 
00173 static void icmp_release(packet_t *packet)
00174 {
00175         pq_release_remote(phone_net, packet_get_id(packet));
00176 }
00177 
00198 static int icmp_send_packet(icmp_type_t type, icmp_code_t code,
00199     packet_t *packet, icmp_header_t *header, services_t error, ip_ttl_t ttl,
00200     ip_tos_t tos, bool dont_fragment)
00201 {
00202         /* Do not send an error if disabled */
00203         if ((error) && (!error_reporting)) {
00204                 icmp_release(packet);
00205                 return EPERM;
00206         }
00207         
00208         header->type = type;
00209         header->code = code;
00210         
00211         /*
00212          * The checksum needs to be calculated
00213          * with a virtual checksum field set to
00214          * zero.
00215          */
00216         header->checksum = 0;
00217         header->checksum = ICMP_CHECKSUM(header,
00218             packet_get_data_length(packet));
00219         
00220         int rc = ip_client_prepare_packet(packet, IPPROTO_ICMP, ttl, tos,
00221             dont_fragment, 0);
00222         if (rc != EOK) {
00223                 icmp_release(packet);
00224                 return rc;
00225         }
00226         
00227         return ip_send_msg(phone_ip, -1, packet, SERVICE_ICMP, error);
00228 }
00229 
00241 static icmp_header_t *icmp_prepare_packet(packet_t *packet)
00242 {
00243         size_t total_length = packet_get_data_length(packet);
00244         if (total_length <= 0)
00245                 return NULL;
00246         
00247         size_t header_length = ip_client_header_length(packet);
00248         if (header_length <= 0)
00249                 return NULL;
00250         
00251         /* Truncate if longer than 64 bits (without the IP header) */
00252         if ((total_length > header_length + ICMP_KEEP_LENGTH) &&
00253             (packet_trim(packet, 0,
00254             total_length - header_length - ICMP_KEEP_LENGTH) != EOK))
00255                 return NULL;
00256         
00257         icmp_header_t *header = PACKET_PREFIX(packet, icmp_header_t);
00258         if (!header)
00259                 return NULL;
00260         
00261         bzero(header, sizeof(*header));
00262         return header;
00263 }
00264 
00290 static int icmp_echo(icmp_param_t id, icmp_param_t sequence, size_t size,
00291     mseconds_t timeout, ip_ttl_t ttl, ip_tos_t tos, bool dont_fragment,
00292     const struct sockaddr *addr, socklen_t addrlen)
00293 {
00294         if (addrlen <= 0)
00295                 return EINVAL;
00296         
00297         size_t length = (size_t) addrlen;
00298         
00299         packet_t *packet = packet_get_4_remote(phone_net, size,
00300             icmp_dimension.addr_len, ICMP_HEADER_SIZE + icmp_dimension.prefix,
00301             icmp_dimension.suffix);
00302         if (!packet)
00303                 return ENOMEM;
00304         
00305         /* Prepare the requesting packet, set the destination address. */
00306         int rc = packet_set_addr(packet, NULL, (const uint8_t *) addr, length);
00307         if (rc != EOK) {
00308                 icmp_release(packet);
00309                 return rc;
00310         }
00311         
00312         /* Allocate space in the packet */
00313         uint8_t *data = (uint8_t *) packet_suffix(packet, size);
00314         if (!data) {
00315                 icmp_release(packet);
00316                 return ENOMEM;
00317         }
00318         
00319         /* Fill the data */
00320         length = 0;
00321         while (size > length + sizeof(ICMP_ECHO_TEXT)) {
00322                 memcpy(data + length, ICMP_ECHO_TEXT, sizeof(ICMP_ECHO_TEXT));
00323                 length += sizeof(ICMP_ECHO_TEXT);
00324         }
00325         memcpy(data + length, ICMP_ECHO_TEXT, size - length);
00326         
00327         /* Prefix the header */
00328         icmp_header_t *header = PACKET_PREFIX(packet, icmp_header_t);
00329         if (!header) {
00330                 icmp_release(packet);
00331                 return ENOMEM;
00332         }
00333         
00334         bzero(header, sizeof(icmp_header_t));
00335         header->un.echo.identifier = id;
00336         header->un.echo.sequence_number = sequence;
00337         
00338         /* Prepare the reply structure */
00339         icmp_reply_t *reply = malloc(sizeof(icmp_reply_t));
00340         if (!reply) {
00341                 icmp_release(packet);
00342                 return ENOMEM;
00343         }
00344         
00345         reply->id = id;
00346         reply->sequence = sequence;
00347         fibril_condvar_initialize(&reply->condvar);
00348         
00349         /* Add the reply to the replies hash table */
00350         fibril_mutex_lock(&reply_lock);
00351         
00352         unsigned long key[REPLY_KEYS] = {id, sequence};
00353         hash_table_insert(&replies, key, &reply->link);
00354         
00355         /* Send the request */
00356         icmp_send_packet(ICMP_ECHO, 0, packet, header, 0, ttl, tos,
00357             dont_fragment);
00358         
00359         /* Wait for the reply. Timeout in microseconds. */
00360         rc = fibril_condvar_wait_timeout(&reply->condvar, &reply_lock,
00361             timeout * 1000);
00362         if (rc == EOK)
00363                 rc = reply->result;
00364         
00365         /* Remove the reply from the replies hash table */
00366         hash_table_remove(&replies, key, REPLY_KEYS);
00367         
00368         fibril_mutex_unlock(&reply_lock);
00369         
00370         free(reply);
00371         
00372         return rc;
00373 }
00374 
00375 static int icmp_destination_unreachable(icmp_code_t code, icmp_param_t mtu,
00376     packet_t *packet)
00377 {
00378         icmp_header_t *header = icmp_prepare_packet(packet);
00379         if (!header) {
00380                 icmp_release(packet);
00381                 return ENOMEM;
00382         }
00383         
00384         if (mtu)
00385                 header->un.frag.mtu = mtu;
00386         
00387         return icmp_send_packet(ICMP_DEST_UNREACH, code, packet, header,
00388             SERVICE_ICMP, 0, 0, false);
00389 }
00390 
00391 static int icmp_source_quench(packet_t *packet)
00392 {
00393         icmp_header_t *header = icmp_prepare_packet(packet);
00394         if (!header) {
00395                 icmp_release(packet);
00396                 return ENOMEM;
00397         }
00398         
00399         return icmp_send_packet(ICMP_SOURCE_QUENCH, 0, packet, header,
00400             SERVICE_ICMP, 0, 0, false);
00401 }
00402 
00403 static int icmp_time_exceeded(icmp_code_t code, packet_t *packet)
00404 {
00405         icmp_header_t *header = icmp_prepare_packet(packet);
00406         if (!header) {
00407                 icmp_release(packet);
00408                 return ENOMEM;
00409         }
00410         
00411         return icmp_send_packet(ICMP_TIME_EXCEEDED, code, packet, header,
00412             SERVICE_ICMP, 0, 0, false);
00413 }
00414 
00415 static int icmp_parameter_problem(icmp_code_t code, icmp_param_t pointer,
00416     packet_t *packet)
00417 {
00418         icmp_header_t *header = icmp_prepare_packet(packet);
00419         if (!header) {
00420                 icmp_release(packet);
00421                 return ENOMEM;
00422         }
00423         
00424         header->un.param.pointer = pointer;
00425         return icmp_send_packet(ICMP_PARAMETERPROB, code, packet, header,
00426             SERVICE_ICMP, 0, 0, false);
00427 }
00428 
00440 static void icmp_process_echo_reply(packet_t *packet, icmp_header_t *header,
00441     icmp_type_t type, icmp_code_t code)
00442 {
00443         unsigned long key[REPLY_KEYS] =
00444             {header->un.echo.identifier, header->un.echo.sequence_number};
00445         
00446         /* The packet is no longer needed */
00447         icmp_release(packet);
00448         
00449         /* Find the pending reply */
00450         fibril_mutex_lock(&reply_lock);
00451         
00452         link_t *link = hash_table_find(&replies, key);
00453         if (link != NULL) {
00454                 icmp_reply_t *reply =
00455                    hash_table_get_instance(link, icmp_reply_t, link);
00456                 
00457                 reply->result = type;
00458                 fibril_condvar_signal(&reply->condvar);
00459         }
00460         
00461         fibril_mutex_unlock(&reply_lock);
00462 }
00463 
00484 static int icmp_process_packet(packet_t *packet, services_t error)
00485 {
00486         icmp_type_t type;
00487         icmp_code_t code;
00488         int rc;
00489         
00490         switch (error) {
00491         case SERVICE_NONE:
00492                 break;
00493         case SERVICE_ICMP:
00494                 /* Process error */
00495                 rc = icmp_client_process_packet(packet, &type, &code, NULL, NULL);
00496                 if (rc < 0)
00497                         return rc;
00498                 
00499                 /* Remove the error header */
00500                 rc = packet_trim(packet, (size_t) rc, 0);
00501                 if (rc != EOK)
00502                         return rc;
00503                 
00504                 break;
00505         default:
00506                 return ENOTSUP;
00507         }
00508         
00509         /* Get rid of the IP header */
00510         size_t length = ip_client_header_length(packet);
00511         rc = packet_trim(packet, length, 0);
00512         if (rc != EOK)
00513                 return rc;
00514         
00515         length = packet_get_data_length(packet);
00516         if (length <= 0)
00517                 return EINVAL;
00518         
00519         if (length < ICMP_HEADER_SIZE)
00520                 return EINVAL;
00521         
00522         void *data = packet_get_data(packet);
00523         if (!data)
00524                 return EINVAL;
00525         
00526         /* Get ICMP header */
00527         icmp_header_t *header = (icmp_header_t *) data;
00528         
00529         if (header->checksum) {
00530                 while (ICMP_CHECKSUM(header, length) != IP_CHECKSUM_ZERO) {
00531                         /*
00532                          * Set the original message type on error notification.
00533                          * Type swap observed in Qemu.
00534                          */
00535                         if (error) {
00536                                 switch (header->type) {
00537                                 case ICMP_ECHOREPLY:
00538                                         header->type = ICMP_ECHO;
00539                                         continue;
00540                                 }
00541                         }
00542                         
00543                         return EINVAL;
00544                 }
00545         }
00546         
00547         switch (header->type) {
00548         case ICMP_ECHOREPLY:
00549                 if (error)
00550                         icmp_process_echo_reply(packet, header, type, code);
00551                 else
00552                         icmp_process_echo_reply(packet, header, ICMP_ECHO, 0);
00553                 
00554                 return EOK;
00555         
00556         case ICMP_ECHO:
00557                 if (error) {
00558                         icmp_process_echo_reply(packet, header, type, code);
00559                         return EOK;
00560                 }
00561                 
00562                 /* Do not send a reply if disabled */
00563                 if (echo_replying) {
00564                         uint8_t *src;
00565                         int addrlen = packet_get_addr(packet, &src, NULL);
00566                         
00567                         /*
00568                          * Set both addresses to the source one (avoid the
00569                          * source address deletion before setting the
00570                          * destination one).
00571                          */
00572                         if ((addrlen > 0) && (packet_set_addr(packet, src, src,
00573                             (size_t) addrlen) == EOK)) {
00574                                 /* Send the reply */
00575                                 icmp_send_packet(ICMP_ECHOREPLY, 0, packet,
00576                                     header, 0, 0, 0, 0);
00577                                 return EOK;
00578                         }
00579                         
00580                         return EINVAL;
00581                 }
00582                 
00583                 return EPERM;
00584         
00585         case ICMP_DEST_UNREACH:
00586         case ICMP_SOURCE_QUENCH:
00587         case ICMP_REDIRECT:
00588         case ICMP_ALTERNATE_ADDR:
00589         case ICMP_ROUTER_ADV:
00590         case ICMP_ROUTER_SOL:
00591         case ICMP_TIME_EXCEEDED:
00592         case ICMP_PARAMETERPROB:
00593         case ICMP_CONVERSION_ERROR:
00594         case ICMP_REDIRECT_MOBILE:
00595         case ICMP_SKIP:
00596         case ICMP_PHOTURIS:
00597                 ip_received_error_msg(phone_ip, -1, packet,
00598                     SERVICE_IP, SERVICE_ICMP);
00599                 return EOK;
00600         
00601         default:
00602                 return ENOTSUP;
00603         }
00604 }
00605 
00612 static void icmp_receiver(ipc_callid_t iid, ipc_call_t *icall)
00613 {
00614         bool loop = true;
00615         packet_t *packet;
00616         int rc;
00617         
00618         while (loop) {
00619                 switch (IPC_GET_IMETHOD(*icall)) {
00620                 case NET_TL_RECEIVED:
00621                         rc = packet_translate_remote(phone_net, &packet,
00622                             IPC_GET_PACKET(*icall));
00623                         if (rc == EOK) {
00624                                 rc = icmp_process_packet(packet, IPC_GET_ERROR(*icall));
00625                                 if (rc != EOK)
00626                                         icmp_release(packet);
00627                         }
00628                         
00629                         async_answer_0(iid, (sysarg_t) rc);
00630                         break;
00631                 case IPC_M_PHONE_HUNGUP:
00632                         loop = false;
00633                         continue;
00634                 default:
00635                         async_answer_0(iid, (sysarg_t) ENOTSUP);
00636                 }
00637                 
00638                 iid = async_get_call(icall);
00639         }
00640 }
00641 
00650 int tl_initialize(int net_phone)
00651 {
00652         measured_string_t names[] = {
00653                 {
00654                         (uint8_t *) "ICMP_ERROR_REPORTING",
00655                         20
00656                 },
00657                 {
00658                         (uint8_t *) "ICMP_ECHO_REPLYING",
00659                         18
00660                 }
00661         };
00662         measured_string_t *configuration;
00663         size_t count = sizeof(names) / sizeof(measured_string_t);
00664         uint8_t *data;
00665         
00666         if (!hash_table_create(&replies, REPLY_BUCKETS, REPLY_KEYS, &reply_ops))
00667                 return ENOMEM;
00668         
00669         fibril_mutex_initialize(&reply_lock);
00670         atomic_set(&icmp_client, 0);
00671         
00672         phone_net = net_phone;
00673         phone_ip = ip_bind_service(SERVICE_IP, IPPROTO_ICMP, SERVICE_ICMP,
00674             icmp_receiver);
00675         if (phone_ip < 0)
00676                 return phone_ip;
00677         
00678         int rc = ip_packet_size_req(phone_ip, -1, &icmp_dimension);
00679         if (rc != EOK)
00680                 return rc;
00681         
00682         icmp_dimension.prefix += ICMP_HEADER_SIZE;
00683         icmp_dimension.content -= ICMP_HEADER_SIZE;
00684         
00685         /* Get configuration */
00686         configuration = &names[0];
00687         rc = net_get_conf_req(phone_net, &configuration, count, &data);
00688         if (rc != EOK)
00689                 return rc;
00690         
00691         if (configuration) {
00692                 if (configuration[0].value)
00693                         error_reporting = (configuration[0].value[0] == 'y');
00694                 
00695                 if (configuration[1].value)
00696                         echo_replying = (configuration[1].value[0] == 'y');
00697                 
00698                 net_free_settings(configuration, data);
00699         }
00700         
00701         return EOK;
00702 }
00703 
00709 void tl_connection(void)
00710 {
00711         icmp_id = (icmp_param_t) atomic_postinc(&icmp_client);
00712         icmp_seq = 1;
00713 }
00714 
00739 int tl_message(ipc_callid_t callid, ipc_call_t *call,
00740     ipc_call_t *answer, size_t *count)
00741 {
00742         struct sockaddr *addr;
00743         size_t size;
00744         packet_t *packet;
00745         int rc;
00746         
00747         *count = 0;
00748         
00749         switch (IPC_GET_IMETHOD(*call)) {
00750         case NET_ICMP_ECHO:
00751                 rc = async_data_write_accept((void **) &addr, false, 0, 0, 0, &size);
00752                 if (rc != EOK)
00753                         return rc;
00754                 
00755                 rc = icmp_echo(icmp_id, icmp_seq, ICMP_GET_SIZE(*call), 
00756                     ICMP_GET_TIMEOUT(*call), ICMP_GET_TTL(*call),
00757                     ICMP_GET_TOS(*call), ICMP_GET_DONT_FRAGMENT(*call),
00758                     addr, (socklen_t) size);
00759                 
00760                 free(addr);
00761                 icmp_seq++;
00762                 return rc;
00763         
00764         case NET_ICMP_DEST_UNREACH:
00765                 rc = packet_translate_remote(phone_net, &packet,
00766                     IPC_GET_PACKET(*call));
00767                 if (rc != EOK)
00768                         return rc;
00769                 
00770                 return icmp_destination_unreachable(ICMP_GET_CODE(*call),
00771                     ICMP_GET_MTU(*call), packet);
00772         
00773         case NET_ICMP_SOURCE_QUENCH:
00774                 rc = packet_translate_remote(phone_net, &packet,
00775                     IPC_GET_PACKET(*call));
00776                 if (rc != EOK)
00777                         return rc;
00778                 
00779                 return icmp_source_quench(packet);
00780         
00781         case NET_ICMP_TIME_EXCEEDED:
00782                 rc = packet_translate_remote(phone_net, &packet,
00783                     IPC_GET_PACKET(*call));
00784                 if (rc != EOK)
00785                         return rc;
00786                 
00787                 return icmp_time_exceeded(ICMP_GET_CODE(*call), packet);
00788         
00789         case NET_ICMP_PARAMETERPROB:
00790                 rc = packet_translate_remote(phone_net, &packet,
00791                     IPC_GET_PACKET(*call));
00792                 if (rc != EOK)
00793                         return rc;
00794                 
00795                 return icmp_parameter_problem(ICMP_GET_CODE(*call),
00796                     ICMP_GET_POINTER(*call), packet);
00797         }
00798         
00799         return ENOTSUP;
00800 }
00801 
00802 int main(int argc, char *argv[])
00803 {
00804         /* Start the module */
00805         return tl_module_start(SERVICE_ICMP);
00806 }
00807 

Generated on Thu Jun 2 07:45:50 2011 for HelenOS/USB by  doxygen 1.4.7