| /* |
| * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of the |
| * License, or any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| * |
| * You can also choose to distribute this program under the terms of |
| * the Unmodified Binary Distribution Licence (as given in the file |
| * COPYING.UBDL), provided that you have satisfied its requirements. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <byteswap.h> |
| #include <ipxe/refcnt.h> |
| #include <ipxe/list.h> |
| #include <ipxe/iobuf.h> |
| #include <ipxe/tcpip.h> |
| #include <ipxe/icmp.h> |
| #include <ipxe/interface.h> |
| #include <ipxe/xfer.h> |
| #include <ipxe/open.h> |
| #include <ipxe/netdevice.h> |
| #include <ipxe/ping.h> |
| |
| /** @file |
| * |
| * ICMP ping protocol |
| * |
| */ |
| |
| /** |
| * A ping connection |
| * |
| */ |
| struct ping_connection { |
| /** Reference counter */ |
| struct refcnt refcnt; |
| /** List of ping connections */ |
| struct list_head list; |
| |
| /** Remote socket address */ |
| struct sockaddr_tcpip peer; |
| /** Local port number */ |
| uint16_t port; |
| |
| /** Data transfer interface */ |
| struct interface xfer; |
| }; |
| |
| /** List of registered ping connections */ |
| static LIST_HEAD ( ping_conns ); |
| |
| /** |
| * Identify ping connection by local port number |
| * |
| * @v port Local port number |
| * @ret ping Ping connection, or NULL |
| */ |
| static struct ping_connection * ping_demux ( unsigned int port ) { |
| struct ping_connection *ping; |
| |
| list_for_each_entry ( ping, &ping_conns, list ) { |
| if ( ping->port == port ) |
| return ping; |
| } |
| return NULL; |
| } |
| |
| /** |
| * Check if local port number is available |
| * |
| * @v port Local port number |
| * @ret port Local port number, or negative error |
| */ |
| static int ping_port_available ( int port ) { |
| |
| return ( ping_demux ( port ) ? -EADDRINUSE : port ); |
| } |
| |
| /** |
| * Process ICMP ping reply |
| * |
| * @v iobuf I/O buffer |
| * @v st_src Source address |
| * @ret rc Return status code |
| */ |
| int ping_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src ) { |
| struct icmp_echo *echo = iobuf->data; |
| struct ping_connection *ping; |
| struct xfer_metadata meta; |
| int rc; |
| |
| /* Sanity check: should already have been checked by ICMP layer */ |
| assert ( iob_len ( iobuf ) >= sizeof ( *echo ) ); |
| |
| /* Identify connection */ |
| ping = ping_demux ( ntohs ( echo->ident ) ); |
| DBGC ( ping, "PING %p reply id %#04x seq %#04x\n", |
| ping, ntohs ( echo->ident ), ntohs ( echo->sequence ) ); |
| if ( ! ping ) { |
| rc = -ENOTCONN; |
| goto discard; |
| } |
| |
| /* Strip header, construct metadata, and pass data to upper layer */ |
| iob_pull ( iobuf, sizeof ( *echo ) ); |
| memset ( &meta, 0, sizeof ( meta ) ); |
| meta.src = ( ( struct sockaddr * ) st_src ); |
| meta.flags = XFER_FL_ABS_OFFSET; |
| meta.offset = ntohs ( echo->sequence ); |
| return xfer_deliver ( &ping->xfer, iob_disown ( iobuf ), &meta ); |
| |
| discard: |
| free_iob ( iobuf ); |
| return rc; |
| } |
| |
| /** |
| * Allocate I/O buffer for ping |
| * |
| * @v ping Ping connection |
| * @v len Payload size |
| * @ret iobuf I/O buffer, or NULL |
| */ |
| static struct io_buffer * |
| ping_alloc_iob ( struct ping_connection *ping __unused, size_t len ) { |
| size_t header_len; |
| struct io_buffer *iobuf; |
| |
| header_len = ( MAX_LL_NET_HEADER_LEN + sizeof ( struct icmp_echo ) ); |
| iobuf = alloc_iob ( header_len + len ); |
| if ( iobuf ) |
| iob_reserve ( iobuf, header_len ); |
| return iobuf; |
| } |
| |
| /** |
| * Deliver datagram as I/O buffer |
| * |
| * @v ping Ping connection |
| * @v iobuf I/O buffer |
| * @v meta Data transfer metadata |
| * @ret rc Return status code |
| */ |
| static int ping_deliver ( struct ping_connection *ping, struct io_buffer *iobuf, |
| struct xfer_metadata *meta ) { |
| struct icmp_echo *echo = iob_push ( iobuf, sizeof ( *echo ) ); |
| int rc; |
| |
| /* Construct header */ |
| memset ( echo, 0, sizeof ( *echo ) ); |
| echo->ident = htons ( ping->port ); |
| echo->sequence = htons ( meta->offset ); |
| |
| /* Transmit echo request */ |
| if ( ( rc = icmp_tx_echo_request ( iob_disown ( iobuf ), |
| &ping->peer ) ) != 0 ) { |
| DBGC ( ping, "PING %p could not transmit: %s\n", |
| ping, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Close ping connection |
| * |
| * @v ping Ping connection |
| * @v rc Reason for close |
| */ |
| static void ping_close ( struct ping_connection *ping, int rc ) { |
| |
| /* Close data transfer interface */ |
| intf_shutdown ( &ping->xfer, rc ); |
| |
| /* Remove from list of connections and drop list's reference */ |
| list_del ( &ping->list ); |
| ref_put ( &ping->refcnt ); |
| |
| DBGC ( ping, "PING %p closed\n", ping ); |
| } |
| |
| /** Ping data transfer interface operations */ |
| static struct interface_operation ping_xfer_operations[] = { |
| INTF_OP ( xfer_deliver, struct ping_connection *, ping_deliver ), |
| INTF_OP ( xfer_alloc_iob, struct ping_connection *, ping_alloc_iob ), |
| INTF_OP ( intf_close, struct ping_connection *, ping_close ), |
| }; |
| |
| /** Ping data transfer interface descriptor */ |
| static struct interface_descriptor ping_xfer_desc = |
| INTF_DESC ( struct ping_connection, xfer, ping_xfer_operations ); |
| |
| /** |
| * Open a ping connection |
| * |
| * @v xfer Data transfer interface |
| * @v peer Peer socket address |
| * @v local Local socket address, or NULL |
| * @ret rc Return status code |
| */ |
| static int ping_open ( struct interface *xfer, struct sockaddr *peer, |
| struct sockaddr *local ) { |
| struct sockaddr_tcpip *st_peer = ( struct sockaddr_tcpip * ) peer; |
| struct sockaddr_tcpip *st_local = ( struct sockaddr_tcpip * ) local; |
| struct ping_connection *ping; |
| int port; |
| int rc; |
| |
| /* Allocate and initialise structure */ |
| ping = zalloc ( sizeof ( *ping ) ); |
| if ( ! ping ) { |
| rc = -ENOMEM; |
| goto err_alloc; |
| } |
| DBGC ( ping, "PING %p allocated\n", ping ); |
| ref_init ( &ping->refcnt, NULL ); |
| intf_init ( &ping->xfer, &ping_xfer_desc, &ping->refcnt ); |
| memcpy ( &ping->peer, st_peer, sizeof ( ping->peer ) ); |
| |
| /* Bind to local port */ |
| port = tcpip_bind ( st_local, ping_port_available ); |
| if ( port < 0 ) { |
| rc = port; |
| DBGC ( ping, "PING %p could not bind: %s\n", |
| ping, strerror ( rc ) ); |
| goto err_bind; |
| } |
| ping->port = port; |
| DBGC ( ping, "PING %p bound to id %#04x\n", ping, port ); |
| |
| /* Attach parent interface, transfer reference to connection |
| * list, and return |
| */ |
| intf_plug_plug ( &ping->xfer, xfer ); |
| list_add ( &ping->list, &ping_conns ); |
| return 0; |
| |
| err_bind: |
| ref_put ( &ping->refcnt ); |
| err_alloc: |
| return rc; |
| } |
| |
| /** Ping socket opener */ |
| struct socket_opener ping_socket_opener __socket_opener = { |
| .semantics = PING_SOCK_ECHO, |
| .open = ping_open, |
| }; |
| |
| /** Linkage hack */ |
| int ping_sock_echo = PING_SOCK_ECHO; |