---[ Phrack Magazine Volume 8, Issue 52 January 26, 1998, article 07 of 20
-------------------------[ Linux Ping Daemon
--------[ route|daemon9 route@infonexus.com
----[ 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 ICMPECHO and ICMPECHOREPLY 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 ICMPHOSTUNREACH 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.
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).
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`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 ^DInstall 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: icmpseq=0 ttl=64 time=0.8 ms 64 bytes from 192.168.2.34: icmpseq=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: icmpseq=0 ttl=64 time=1.5 ms 64 bytes from 192.168.2.35: icmpseq=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: ICMPECHO 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: ICMPECHO 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: ICMPECHO 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: ICMPECHO 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 route@infonexus.com
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 INSTALLLOC = /usr/sbin PINGD = pingd LIBS = -lnet -lwrap DEFINES += -DBSDSOURCE 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) $(INSTALLLOC)) (echo "" >> /etc/rc.d/rc.local) (echo "echo \"Starting ping daemon\"" >> /etc/rc.d/rc.local) (echo "$(INSTALLLOC)/$(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 (route@infonexus.com) * * * */
ifndef PINGDH
define PINGDH
include
include
include
include
include
include
include
include
include
include
include
include
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 icmppacket { struct ip iph; struct icmphdr icmph; uchar 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 icmpreflect( struct icmppacket , / 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 /* PINGDH */
/* EOF /
<-->
<++> Pingd/pingd.c
/
* $Id$
*
* Linux pingd sourcefile
* ping.c - main sourcefile
* Copyright (c) 1998 by daemon9|route route@infonexus.com
*
*
*
* $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 sockfd, c; struct icmppacket ipack; struct passwd *pwdp;
/*
* 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 icmpreflect(struct icmppacket *pptr, int sockfd) { int c; ulong tmp; struct sockaddrin 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 icmppacket *pptr) { if (!hostsctl("ping", hostlookup(pptr->iph.ipsrc.saddr), hostlookup(pptr->iph.ipsrc.saddr), STRINGUNKNOWN)) 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 SOBINDTODEVICE. * Willy Konynenberg : Transparent proxy adapted to new * socket hash code. - * + * route : 1.10.98: ICMPECHO / ICMPECHOREQUEST + * support into userland. * * RFC1122 (Host Requirements -- Comm. Layer) Status: * (boy, are there a lot of rules for ICMP) @@ -882,28 +883,6 @@ kfreeskb(skb, FREE_READ); }
-/* - * Handle ICMPECHO ("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 ICMPECHO 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; - icmpparam.icmph.type=ICMPECHOREPLY; - icmpparam.dataptr=(icmph+1); - icmpparam.datalen=len; - if (ipoptionsecho(&icmpparam.replyopts, NULL, daddr, saddr, skb)==0) - icmpbuildxmit(&icmpparam, daddr, saddr, skb->iphdr->tos); -#endif - kfreeskb(skb, FREE_READ); -}
/* * Handle ICMP Timestamp requests. @@ -1144,8 +1123,8 @@ */
static struct icmpcontrol icmppointers[19] = { -/* ECHO REPLY (0) / - { &icmp_statistics.IcmpOutEchoReps, &icmp_statistics.IcmpInEchoReps, icmp_discard, 0, NULL }, +/ ECHO REPLY (0) - Disabled, we now do ICMPECHOREQUEST in userland */ + { &dummy, &icmpstatistics.IcmpInErrors, icmpdiscard, 1, NULL }, { &dummy, &icmpstatistics.IcmpInErrors, icmpdiscard, 1, NULL }, { &dummy, &icmpstatistics.IcmpInErrors, icmpdiscard, 1, NULL }, /* DEST UNREACH (3) */ @@ -1156,8 +1135,8 @@ { &icmpstatistics.IcmpOutRedirects, &icmpstatistics.IcmpInRedirects, icmpredirect, 1, &xrlredirect }, { &dummy, &icmpstatistics.IcmpInErrors, icmpdiscard, 1, NULL }, { &dummy, &icmpstatistics.IcmpInErrors, icmpdiscard, 1, NULL }, -/* ECHO (8) */ - { &icmpstatistics.IcmpOutEchos, &icmpstatistics.IcmpInEchos, icmpecho, 0, NULL }, +/* ECHO (8) - Disabled, we now do ICMPECHOREQUEST in userland */ + { &dummy, &icmpstatistics.IcmpInErrors, icmpdiscard, 1, NULL }, { &dummy, &icmpstatistics.IcmpInErrors, icmpdiscard, 1, NULL }, { &dummy, &icmpstatistics.IcmpInErrors, icmp_discard, 1, NULL }, /* TIME EXCEEDED (11) */ <-->
----[ EOF