mirror of https://github.com/ipxe/ipxe.git
[ping] Add concept of a ping socket
Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/14/merge
parent
5c2ffc26cc
commit
46873eda44
|
@ -217,6 +217,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||
#define ERRFILE_neighbour ( ERRFILE_NET | 0x00370000 )
|
||||
#define ERRFILE_socket ( ERRFILE_NET | 0x00380000 )
|
||||
#define ERRFILE_icmp ( ERRFILE_NET | 0x00390000 )
|
||||
#define ERRFILE_ping ( ERRFILE_NET | 0x003a0000 )
|
||||
|
||||
#define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 )
|
||||
#define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 )
|
||||
|
|
|
@ -28,6 +28,11 @@ extern int udp_sock_dgram;
|
|||
#define UDP_SOCK_DGRAM 0x2
|
||||
#define SOCK_DGRAM udp_sock_dgram
|
||||
|
||||
/** Echo testing streams */
|
||||
extern int ping_sock_echo;
|
||||
#define PING_SOCK_ECHO 0x3
|
||||
#define SOCK_ECHO ping_sock_echo
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -43,6 +48,8 @@ socket_semantics_name ( int semantics ) {
|
|||
return "SOCK_STREAM";
|
||||
} else if ( semantics == SOCK_DGRAM ) {
|
||||
return "SOCK_DGRAM";
|
||||
} else if ( semantics == SOCK_ECHO ) {
|
||||
return "SOCK_ECHO";
|
||||
} else {
|
||||
return "SOCK_UNKNOWN";
|
||||
}
|
||||
|
@ -69,6 +76,7 @@ socket_family_name ( int family ) {
|
|||
switch ( family ) {
|
||||
case AF_INET: return "AF_INET";
|
||||
case AF_INET6: return "AF_INET6";
|
||||
case AF_FC: return "AF_FC";
|
||||
default: return "AF_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
#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 IPv4 socket opener */
|
||||
struct socket_opener ping_ipv4_socket_opener __socket_opener = {
|
||||
.semantics = PING_SOCK_ECHO,
|
||||
.family = AF_INET,
|
||||
.open = ping_open,
|
||||
};
|
||||
|
||||
/** Ping IPv6 socket opener */
|
||||
struct socket_opener ping_ipv6_socket_opener __socket_opener = {
|
||||
.semantics = PING_SOCK_ECHO,
|
||||
.family = AF_INET6,
|
||||
.open = ping_open,
|
||||
};
|
||||
|
||||
/** Linkage hack */
|
||||
int ping_sock_echo = PING_SOCK_ECHO;
|
Loading…
Reference in New Issue