iPXE
icmpv6.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <string.h>
00027 #include <errno.h>
00028 #include <byteswap.h>
00029 #include <ipxe/in.h>
00030 #include <ipxe/iobuf.h>
00031 #include <ipxe/tcpip.h>
00032 #include <ipxe/ping.h>
00033 #include <ipxe/icmpv6.h>
00034 
00035 /** @file
00036  *
00037  * ICMPv6 protocol
00038  *
00039  */
00040 
00041 /* Disambiguate the various error causes */
00042 #define EHOSTUNREACH_ROUTE                                              \
00043         __einfo_error ( EINFO_EHOSTUNREACH_ROUTE )
00044 #define EINFO_EHOSTUNREACH_ROUTE                                        \
00045         __einfo_uniqify ( EINFO_EHOSTUNREACH, 0,                        \
00046                           "No route to destination" )
00047 #define EHOSTUNREACH_PROHIBITED                                         \
00048         __einfo_error ( EINFO_EHOSTUNREACH_PROHIBITED )
00049 #define EINFO_EHOSTUNREACH_PROHIBITED                                   \
00050         __einfo_uniqify ( EINFO_EHOSTUNREACH, 1,                        \
00051                           "Communication administratively prohibited" )
00052 #define EHOSTUNREACH_ADDRESS                                            \
00053         __einfo_error ( EINFO_EHOSTUNREACH_ADDRESS )
00054 #define EINFO_EHOSTUNREACH_ADDRESS                                      \
00055         __einfo_uniqify ( EINFO_EHOSTUNREACH, 3,                        \
00056                           "Address unreachable" )
00057 #define EHOSTUNREACH_PORT                                               \
00058         __einfo_error ( EINFO_EHOSTUNREACH_PORT )
00059 #define EINFO_EHOSTUNREACH_PORT                                         \
00060         __einfo_uniqify ( EINFO_EHOSTUNREACH, 4,                        \
00061                           "Port unreachable" )
00062 #define EHOSTUNREACH_CODE( code )                                       \
00063         EUNIQ ( EINFO_EHOSTUNREACH, ( (code) & 0x1f ),                  \
00064                 EHOSTUNREACH_ROUTE, EHOSTUNREACH_PROHIBITED,            \
00065                 EHOSTUNREACH_ADDRESS, EHOSTUNREACH_PORT )
00066 
00067 #define ETIMEDOUT_HOP                                                   \
00068         __einfo_error ( EINFO_ETIMEDOUT_HOP )
00069 #define EINFO_ETIMEDOUT_HOP                                             \
00070         __einfo_uniqify ( EINFO_ETIMEDOUT, 0,                           \
00071                           "Hop limit exceeded in transit" )
00072 #define ETIMEDOUT_REASSEMBLY                                            \
00073         __einfo_error ( EINFO_ETIMEDOUT_REASSEMBLY )
00074 #define EINFO_ETIMEDOUT_REASSEMBLY                                      \
00075         __einfo_uniqify ( EINFO_ETIMEDOUT, 1,                           \
00076                           "Fragment reassembly time exceeded" )
00077 #define ETIMEDOUT_CODE( code )                                          \
00078         EUNIQ ( EINFO_ETIMEDOUT, ( (code) & 0x1f ),                     \
00079                 ETIMEDOUT_HOP, ETIMEDOUT_REASSEMBLY )
00080 
00081 #define EPROTO_BAD_HEADER                                               \
00082         __einfo_error ( EINFO_EPROTO_BAD_HEADER )
00083 #define EINFO_EPROTO_BAD_HEADER                                         \
00084         __einfo_uniqify ( EINFO_EPROTO, 0,                              \
00085                           "Erroneous header field" )
00086 #define EPROTO_NEXT_HEADER                                              \
00087         __einfo_error ( EINFO_EPROTO_NEXT_HEADER )
00088 #define EINFO_EPROTO_NEXT_HEADER                                        \
00089         __einfo_uniqify ( EINFO_EPROTO, 1,                              \
00090                           "Unrecognised next header type" )
00091 #define EPROTO_OPTION                                                   \
00092         __einfo_error ( EINFO_EPROTO_OPTION )
00093 #define EINFO_EPROTO_OPTION                                             \
00094         __einfo_uniqify ( EINFO_EPROTO, 2,                              \
00095                           "Unrecognised IPv6 option" )
00096 #define EPROTO_CODE( code )                                             \
00097         EUNIQ ( EINFO_EPROTO, ( (code) & 0x1f ),                        \
00098                 EPROTO_BAD_HEADER, EPROTO_NEXT_HEADER, EPROTO_OPTION )
00099 
00100 struct icmp_echo_protocol icmpv6_echo_protocol __icmp_echo_protocol;
00101 
00102 /**
00103  * Process received ICMPv6 echo request packet
00104  *
00105  * @v iobuf             I/O buffer
00106  * @v netdev            Network device
00107  * @v sin6_src          Source socket address
00108  * @v sin6_dest         Destination socket address
00109  * @ret rc              Return status code
00110  */
00111 static int icmpv6_rx_echo_request ( struct io_buffer *iobuf,
00112                                     struct net_device *netdev __unused,
00113                                     struct sockaddr_in6 *sin6_src,
00114                                     struct sockaddr_in6 *sin6_dest __unused ) {
00115         struct sockaddr_tcpip *st_src =
00116                 ( ( struct sockaddr_tcpip * ) sin6_src );
00117 
00118         return icmp_rx_echo_request ( iobuf, st_src, &icmpv6_echo_protocol );
00119 }
00120 
00121 /** ICMPv6 echo request handler */
00122 struct icmpv6_handler icmpv6_echo_request_handler __icmpv6_handler = {
00123         .type = ICMPV6_ECHO_REQUEST,
00124         .rx = icmpv6_rx_echo_request,
00125 };
00126 
00127 /**
00128  * Process received ICMPv6 echo reply packet
00129  *
00130  * @v iobuf             I/O buffer
00131  * @v netdev            Network device
00132  * @v sin6_src          Source socket address
00133  * @v sin6_dest         Destination socket address
00134  * @ret rc              Return status code
00135  */
00136 static int icmpv6_rx_echo_reply ( struct io_buffer *iobuf,
00137                                   struct net_device *netdev __unused,
00138                                   struct sockaddr_in6 *sin6_src,
00139                                   struct sockaddr_in6 *sin6_dest __unused ) {
00140         struct sockaddr_tcpip *st_src =
00141                 ( ( struct sockaddr_tcpip * ) sin6_src );
00142 
00143         return icmp_rx_echo_reply ( iobuf, st_src );
00144 }
00145 
00146 /** ICMPv6 echo reply handler */
00147 struct icmpv6_handler icmpv6_echo_reply_handler __icmpv6_handler = {
00148         .type = ICMPV6_ECHO_REPLY,
00149         .rx = icmpv6_rx_echo_reply,
00150 };
00151 
00152 /**
00153  * Identify ICMPv6 handler
00154  *
00155  * @v type              ICMPv6 type
00156  * @ret handler         ICMPv6 handler, or NULL if not found
00157  */
00158 static struct icmpv6_handler * icmpv6_handler ( unsigned int type ) {
00159         struct icmpv6_handler *handler;
00160 
00161         for_each_table_entry ( handler, ICMPV6_HANDLERS ) {
00162                 if ( handler->type == type )
00163                         return handler;
00164         }
00165         return NULL;
00166 }
00167 
00168 /**
00169  * Process a received packet
00170  *
00171  * @v iobuf             I/O buffer
00172  * @v netdev            Network device
00173  * @v st_src            Partially-filled source address
00174  * @v st_dest           Partially-filled destination address
00175  * @v pshdr_csum        Pseudo-header checksum
00176  * @ret rc              Return status code
00177  */
00178 static int icmpv6_rx ( struct io_buffer *iobuf, struct net_device *netdev,
00179                        struct sockaddr_tcpip *st_src,
00180                        struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum ) {
00181         struct sockaddr_in6 *sin6_src = ( ( struct sockaddr_in6 * ) st_src );
00182         struct sockaddr_in6 *sin6_dest = ( ( struct sockaddr_in6 * ) st_dest );
00183         struct icmp_header *icmp = iobuf->data;
00184         size_t len = iob_len ( iobuf );
00185         struct icmpv6_handler *handler;
00186         unsigned int csum;
00187         int rc;
00188 
00189         /* Sanity check */
00190         if ( len < sizeof ( *icmp ) ) {
00191                 DBGC ( netdev, "ICMPv6 packet too short at %zd bytes (min %zd "
00192                        "bytes)\n", len, sizeof ( *icmp ) );
00193                 rc = -EINVAL;
00194                 goto done;
00195         }
00196 
00197         /* Verify checksum */
00198         csum = tcpip_continue_chksum ( pshdr_csum, icmp, len );
00199         if ( csum != 0 ) {
00200                 DBGC ( netdev, "ICMPv6 checksum incorrect (is %04x, should be "
00201                        "0000)\n", csum );
00202                 DBGC_HDA ( netdev, 0, icmp, len );
00203                 rc = -EINVAL;
00204                 goto done;
00205         }
00206 
00207         /* Identify handler */
00208         handler = icmpv6_handler ( icmp->type );
00209         if ( ! handler ) {
00210                 switch ( icmp->type ) {
00211                 case ICMPV6_DESTINATION_UNREACHABLE:
00212                         rc = -EHOSTUNREACH_CODE ( icmp->code );
00213                         break;
00214                 case ICMPV6_PACKET_TOO_BIG:
00215                         rc = -ERANGE;
00216                         break;
00217                 case ICMPV6_TIME_EXCEEDED:
00218                         rc = -ETIMEDOUT_CODE ( icmp->code );
00219                         break;
00220                 case ICMPV6_PARAMETER_PROBLEM:
00221                         rc = -EPROTO_CODE ( icmp->code );
00222                         break;
00223                 default:
00224                         DBGC ( netdev, "ICMPv6 unrecognised type %d code %d\n",
00225                                icmp->type, icmp->code );
00226                         rc = -ENOTSUP;
00227                         break;
00228                 };
00229                 goto done;
00230         }
00231 
00232         /* Pass to handler */
00233         if ( ( rc = handler->rx ( iob_disown ( iobuf ), netdev, sin6_src,
00234                                   sin6_dest ) ) != 0 ) {
00235                 DBGC ( netdev, "ICMPv6 could not handle type %d: %s\n",
00236                        icmp->type, strerror ( rc ) );
00237                 goto done;
00238         }
00239 
00240  done:
00241         free_iob ( iobuf );
00242         return rc;
00243 }
00244 
00245 /** ICMPv6 TCP/IP protocol */
00246 struct tcpip_protocol icmpv6_protocol __tcpip_protocol = {
00247         .name = "ICMPv6",
00248         .rx = icmpv6_rx,
00249         .tcpip_proto = IP_ICMP6,
00250 };
00251 
00252 /** ICMPv6 echo protocol */
00253 struct icmp_echo_protocol icmpv6_echo_protocol __icmp_echo_protocol = {
00254         .family = AF_INET6,
00255         .request = ICMPV6_ECHO_REQUEST,
00256         .reply = ICMPV6_ECHO_REPLY,
00257         .tcpip_protocol = &icmpv6_protocol,
00258         .net_checksum = 1,
00259 };