Title : The Linux pingd
Author : daemon9
---[ Phrack Magazine Volume 8, Issue 52 January 26, 1998, article 07 of 20
-------------------------[ Linux Ping Daemon
--------[ route|daemon9 <[email protected]>
----[ Introduction and Impetus
I have an idea. How about we rip ICMP_ECHO support from the kernel? How
about we employ a userland daemon that controls ICMP_ECHO reflection via TCP
wrapper access control? (Actually, this idea was originally (c) Asriel, who
did the 44BSD version. http://www.enteract.com/~tqbf/goodies.html. He just
asked me to do the linux version.)
The bastard son of this idea is pingd. A cute userland daemon that
handles all ICMP_ECHO and ICMP_ECHOREPLY traffic. The engine is simple. A
raw ICMP socket under Linux gets a copy of every ICMP datagram delivered to
the IP module (assuming the IP datagram is destined for an interface on that
host). We simply remove support of ICMP_ECHO processing from the kernel and
erect a userland daemon with a raw ICMP socket to handle these packets.
Once we have the packet, we do some basic sanity checks such as packet
type and code, and packet size. Next, we pass the packet to the authentication
mechanism where it is checked against the access control list. If the packet
is allowed, we send a response, otherwise we drop it on the floor.
The rule for this project was primarily security and then efficiency. The
next version will have an option to send ICMP_HOST_UNREACH to an offending
host. I may also at some point add some hooks for some sort of payload
content analysis (read: LOKI detection) but for now, pingd stands as is.
----[ Compilation and Installation
i. You will need libwrap and libnet. Libwrap comes with Wieste Venema's Tcp
wrapper package and is available from ftp://ftp.win.tue.nl/pub/security/.
The libnet networking library is available from:
http://www.infonexus.com/~daemon9/Projects/libnet.tar.gz.
ii. Build and install both libraries according to their respective instructions.
1. Build the program and apply the kernel patch.
`make all` OR (`make pingd` AND `make patch`)
1a. Recompile your kernel. It is NOT necessary to make {config, dep, clean}.
It is only necessary to:
`make; make install`
(or the equivalent).
2. Test the daemon. Ensure that there are no wrapper entries in the
/etc/hosts.{deny, allow} and start the daemon in debug mode.
`./pingd -d1` and then `ping 0`
3. Edit your TCP wrapper access control files. Simply add a new service
(ping) and the IP addresses you want to allow or deny:
`cat >> /etc/hosts.deny`
ping : evil.com
^D
4. Install the program and add it to your /etc/rc.d/rc/local:
`make install`
----[ Empirical Data
This is slower then doing it in the kernel. Especially on localhost. How
about that. Remotely, the RTT's are about .7 - .9 ms longer with a concise
/etc/hosts.{allow,deny}. This is the price you pay for a more secure
implementation. All the hosts are on the same 10MB network, with
approximately the same speed NICs.
The following Linux machine has a normal kernel-based ICMP_ECHO reflector
mechanism:
resentment:~/# ping 192.168.2.34
PING 192.168.2.34 (192.168.2.34): 56 data bytes
64 bytes from 192.168.2.34: icmp_seq=0 ttl=64 time=0.8 ms
64 bytes from 192.168.2.34: icmp_seq=1 ttl=64 time=0.6 ms
64 bytes from 192.168.2.34: icmp_seq=2 ttl=64 time=0.8 ms
--- 192.168.2.34 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.6/0.7/0.8 ms
This machine is running pingd compiled with DLOG (and has no kernel
ICMP_ECHO support):
resentment:~/# ping 192.168.2.35
PING 192.168.2.35 (192.168.2.35): 56 data bytes
64 bytes from 192.168.2.35: icmp_seq=0 ttl=64 time=1.5 ms
64 bytes from 192.168.2.35: icmp_seq=1 ttl=64 time=1.4 ms
64 bytes from 192.168.2.35: icmp_seq=2 ttl=64 time=1.3 ms
--- 192.168.2.35 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.3/1.4/1.5 ms
Stress-test of the same host (not recommended to do with debugging on):
torment# /sbin/ping -f -c 10000 192.168.2.35
PING 192.168.2.35 (192.168.2.35): 56 data bytes
............................................................................
--- 192.168.2.35 ping statistics ---
10088 packets transmitted, 10000 packets received, 0% packet loss
round-trip min/avg/max = 0.985/36.790/86.075 ms
resentment:~# ping -f -c 10000 192.168.2.35
PING 192.168.2.35 (192.168.2.35): 56 data bytes
..
--- 192.168.2.35 ping statistics ---
10001 packets transmitted, 10000 packets received, 0% packet loss
round-trip min/avg/max = 1.0/1.2/17.4 ms
An example of the wrapper log:
Jan 16 18:23:03 shattered pingd: started: 997
Jan 16 18:24:52 shattered pingd: ICMP_ECHO allowed by wrapper
(64 bytes from 192.168.2.38)
Jan 16 18:24:54 shattered last message repeated 2 times
Jan 16 18:26:50 shattered pingd: ICMP_ECHO allowed by wrapper
(64 bytes from 192.168.2.37)
Jan 16 18:26:58 shattered last message repeated 10087 times
Jan 16 18:30:09 shattered pingd: ICMP_ECHO allowed by wrapper
(64 bytes from 192.168.2.38)
Jan 16 18:30:19 shattered last message repeated 10000 times
Jan 16 18:47:30 shattered pingd: ICMP_ECHO denied by wrapper
(64 bytes from 192.168.2.34)
Jan 16 18:47:32 shattered last message repeated 2 times
Jan 16 18:48:16 shattered pingd: packet too large
(10008 bytes from 192.168.2.38)
Jan 16 18:48:17 shattered last message repeated 2 times
----[ The code
<++> Pingd/Makefile
# linux pingd Makefile
# daemon9|route <[email protected]>
# Define this if you want syslog logging of ICMP_ECHO traffic. This slows
# slow down daemon response time a bit.
# default: enabled.
DEFINES = -DLOG
CC = gcc
VER = 0.1
NETSRC = /usr/src/linux/net/ipv4
INSTALL_LOC = /usr/sbin
PINGD = pingd
LIBS = -lnet -lwrap
DEFINES += -D__BSD_SOURCE
CFLAGS = -O3 -funroll-loops -fomit-frame-pointer -pipe -m486 -Wall
OBJECTS = pingd.o
.c.o:
$(CC) $(CFLAGS) $(DEFINES) -c $< -o $@
pingd: $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -o pingd $(LIBS)
strip pingd
all: patch pingd
patch:
@(/usr/bin/patch -d $(NETSRC) < patchfile)
@(echo "Patchfile installed")
@(echo "You must now recompile your kernel")
@(echo "")
install: pingd
(install -m755 $(PINGD) $(INSTALL_LOC))
(echo "" >> /etc/rc.d/rc.local)
(echo "echo \"Starting ping daemon\"" >> /etc/rc.d/rc.local)
(echo "$(INSTALL_LOC)/$(PINGD)" >> /etc/rc.d/rc.local)
dist: clean
@(cd ..; rm pingd-$(VER).tgz; tar cvzf pingd-$(VER).tgz Pingd/)
clean:
rm -f *.o core pingd
# EOF
<-->
<++> Pingd/pingd.h
/*
* $Id$
*
* Linux pingd sourcefile
* pingd.h - function prototypes, global data structures, and macros
* Copyright (c) 1998 by daemon9|route ([email protected])
*
*
*
*/
#ifndef _PINGD_H
#define _PINGD_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <libnet.h>
#define NOBODY "nobody" /* Nobody pwnam */
#define STRING_UNKNOWN "unknown" /* From tcpd.h */
#define HEADER_MATERIAL 28 /* ICMP == 8 bytes, IP == 20 bytes */
#define MAX_PAYLOAD 8096 /* Out of thin air */
struct icmp_packet
{
struct ip iph;
struct icmphdr icmph;
u_char payload[MAX_PAYLOAD];
};
/* F U N C T I O N P R O T O T Y P E S */
void
usage(
char * /* pointer to argv[0] */
);
int /* 1 if the packet is allowed, 0 if denied */
verify(
struct icmp_packet * /* pointer to the ICMP packet in question */
);
void
icmp_reflect(
struct icmp_packet *, /* pointer to the ICMP packet in question */
int /* socket file descriptor */
);
int /* 1 if access is granted, 0 if denied */
hosts_ctl(
char *, /* daemon name */
char *, /* client name (canonical) */
char *, /* client address (dots 'n' decimals) */
char * /* client user (unused) */
);
#endif /* _PINGD_H */
/* EOF */
<-->
<++> Pingd/pingd.c
/*
* $Id$
*
* Linux pingd sourcefile
* ping.c - main sourcefile
* Copyright (c) 1998 by daemon9|route <[email protected]>
*
*
*
* $Log$
*/
#include "pingd.h"
int d = 0; /* Debuging level (defaults off) */
int max_packet = 1024; /* Maximum packet size (default) */
int
main(int argc, char **argv)
{
int sock_fd, c;
struct icmp_packet i_pack;
struct passwd *pwd_p;
/*
* Make sure we have UID 0.
*/
if (geteuid() || getuid())
{
fprintf(stderr, "Inadequate privledges\n");
exit(1);
}
/*
* Open a raw ICMP socket and set IP_HDRINCL.
*/
if ((sock_fd = open_raw_sock(IPPROTO_ICMP)) == -1)
{
perror("socket allocation");
exit(1);
}
/*
* Now that we have the raw socket, we no longer need root privledges
* so we drop our UID to nobody.
*/
if (!(pwd_p = getpwnam(NOBODY)))
{
fprintf(stderr, "Can't get pwnam info on nobody");
exit(1);
}
else if (setuid(pwd_p->pw_uid) == -1)
{
perror("Can't drop privledges");
exit(1);
}
while((c = getopt(argc, argv, "d:s:")) != EOF)
{
switch (c)
{
case 'd':
d = atoi(optarg);
break;
case 's':
max_packet = atoi(optarg);
break;
default:
usage(argv[0]);
}
}
if (!d) daemon();
if (d) fprintf(stderr, "Max packetsize of %d bytes\n", max_packet);
#ifdef LOG
openlog("pingd", 0, 0);
syslog(LOG_DAEMON|LOG_INFO, "started: %d", getpid());
#endif /* LOG */
/*
* We're powered up. From here on out, everything should run swimmingly.
*/
for (;;)
{
bzero(&i_pack, sizeof(i_pack));
c = recv(sock_fd, (struct icmp_packet *)&i_pack, sizeof(i_pack), 0);
if (c == -1)
{
if (d) fprintf(stderr, "truncated read: %s", strerror(errno));
continue;
}
/*
* Make sure packet isn't too small or too big.
*/
if (c < HEADER_MATERIAL || c > max_packet)
{
#ifdef LOG
syslog(
LOG_DAEMON|LOG_INFO,
"bad packet size (%d bytes from %s)",
ntohs(i_pack.iph.ip_len) - sizeof(i_pack.iph),
host_lookup(i_pack.iph.ip_src.s_addr));
#endif /* LOG */
continue;
}
/*
* We only want ICMP_ECHO packets.
*/
if (i_pack.icmph.type != ICMP_ECHO) continue;
else if (d)
fprintf(stderr,
"%d byte ICMP_ECHO from %s\n",
ntohs(i_pack.iph.ip_len) - sizeof(i_pack.iph),
host_lookup(i_pack.iph.ip_src.s_addr));
/*
* Pass packet to the access control mechanism.
*/
if (!verify(&i_pack))
{
#ifdef LOG
syslog(
LOG_DAEMON|LOG_INFO,
"ICMP_ECHO denied by wrapper (%d bytes from %s)",
ntohs(i_pack.iph.ip_len) - sizeof(i_pack.iph),
host_lookup(i_pack.iph.ip_src.s_addr));
#endif /* LOG */
}
else
{
#ifdef LOG
syslog(
LOG_DAEMON|LOG_INFO,
"ICMP_ECHO allowed by wrapper (%d bytes from %s)",
ntohs(i_pack.iph.ip_len) - sizeof(i_pack.iph),
host_lookup(i_pack.iph.ip_src.s_addr));
#endif /* LOG */
icmp_reflect(&i_pack, sock_fd);
}
}
}
void
icmp_reflect(struct icmp_packet *p_ptr, int sock_fd)
{
int c;
u_long tmp;
struct sockaddr_in sin;
bzero((struct sockaddr_in *)&sin, sizeof(sin));
/*
* Formulate ICMP_ECHOREPLY response packet. All we do change the
* packet type and flip the IP addresses. This avoids a copy.
*/
tmp = p_ptr->iph.ip_dst.s_addr;
p_ptr->iph.ip_dst.s_addr = p_ptr->iph.ip_src.s_addr;
p_ptr->iph.ip_src.s_addr = tmp;
p_ptr->icmph.type = ICMP_ECHOREPLY;
p_ptr->icmph.checksum = 0;
p_ptr->icmph.checksum =
ip_check((u_short *)&p_ptr->icmph,
ntohs(p_ptr->iph.ip_len) - sizeof(struct ip));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = p_ptr->iph.ip_dst.s_addr;
c = sendto(sock_fd,
(struct icmp_packet *)p_ptr,
ntohs(p_ptr->iph.ip_len),
0,
(struct sockaddr *) &sin, sizeof(sin));
if (c != ntohs(p_ptr->iph.ip_len))
{
if (d) perror("truncated write");
return;
}
else if (d) fprintf(stderr, "ICMP_ECHOREPLY sent\n");
}
int
verify(struct icmp_packet *p_ptr)
{
if (!hosts_ctl("ping",
host_lookup(p_ptr->iph.ip_src.s_addr),
host_lookup(p_ptr->iph.ip_src.s_addr),
STRING_UNKNOWN))
return (0);
else return (1);
}
void
usage(char *argv0)
{
fprintf(stderr, "usage: %s [-d 1|0 ] [-s maxpacketsize] \n",argv0);
exit(0);
}
/* EOF */
<-->
<++> Pingd/patchfile
--- /usr/src/linux/net/ipv4/icmp.c.original Sat Jan 10 11:10:36 1998
+++ /usr/src/linux/net/ipv4/icmp.c Sat Jan 10 11:19:23 1998
@@ -42,7 +42,8 @@
* Elliot Poger : Added support for SO_BINDTODEVICE.
* Willy Konynenberg : Transparent proxy adapted to new
* socket hash code.
- *
+ * route : 1.10.98: ICMP_ECHO / ICMP_ECHOREQUEST
+ * support into userland.
*
* RFC1122 (Host Requirements -- Comm. Layer) Status:
* (boy, are there a lot of rules for ICMP)
@@ -882,28 +883,6 @@
kfree_skb(skb, FREE_READ);
}
-/*
- * Handle ICMP_ECHO ("ping") requests.
- *
- * RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo requests.
- * RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be included in the reply.
- * RFC 1812: 4.3.3.6 SHOULD have a config option for silently ignoring echo requests, MUST have default=NOT.
- * See also WRT handling of options once they are done and working.
- */
-
-static void icmp_echo(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 saddr, __u32 daddr, int len)
-{
-#ifndef CONFIG_IP_IGNORE_ECHO_REQUESTS
- struct icmp_bxm icmp_param;
- icmp_param.icmph=*icmph;
- icmp_param.icmph.type=ICMP_ECHOREPLY;
- icmp_param.data_ptr=(icmph+1);
- icmp_param.data_len=len;
- if (ip_options_echo(&icmp_param.replyopts, NULL, daddr, saddr, skb)==0)
- icmp_build_xmit(&icmp_param, daddr, saddr, skb->ip_hdr->tos);
-#endif
- kfree_skb(skb, FREE_READ);
-}
/*
* Handle ICMP Timestamp requests.
@@ -1144,8 +1123,8 @@
*/
static struct icmp_control icmp_pointers[19] = {
-/* ECHO REPLY (0) */
- { &icmp_statistics.IcmpOutEchoReps, &icmp_statistics.IcmpInEchoReps, icmp_discard, 0, NULL },
+/* ECHO REPLY (0) - Disabled, we now do ICMP_ECHOREQUEST in userland */
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
{ &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
{ &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
/* DEST UNREACH (3) */
@@ -1156,8 +1135,8 @@
{ &icmp_statistics.IcmpOutRedirects, &icmp_statistics.IcmpInRedirects, icmp_redirect, 1, &xrl_redirect },
{ &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
{ &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
-/* ECHO (8) */
- { &icmp_statistics.IcmpOutEchos, &icmp_statistics.IcmpInEchos, icmp_echo, 0, NULL },
+/* ECHO (8) - Disabled, we now do ICMP_ECHOREQUEST in userland */
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
{ &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
{ &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1, NULL },
/* TIME EXCEEDED (11) */
<-->
----[ EOF