Special (and AFAICT) slightly under-documented behaviour in iputils ping: you ping yourself.
If you ping 0 this is what happens (heavily edited and commented for clarity):
if (inet_aton(target, &whereto.sin_addr)) == 1) { // convert string to binary in_addr } // inet_aton returns 1 (success) and leaves the `in_addr` contents all zero. if (source.sin_addr.s_addr == 0) { // determine IP address of src interface, via UDP connect(), getsockname() } // special case for 0 dst address if (whereto.sin_addr.s_addr == 0) whereto.sin_addr.s_addr = source.sin_addr.s_addr;
inet_aton() isn't POSIX, but I'm assuming it copies the behaviour of inet_addr() when less than 4 dotted-decimals are being converted. In the case of a dot-less single number, it's simply stored into the binary network address, and 0x00000000 is equivalent to the dotted form 0.0.0.0.
You can see this if you strace (as root):
# strace -e trace=network ping 0 socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = 3 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 4 connect(4, {sa_family=AF_INET, sin_port=htons(1025), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 getsockname(4, {sa_family=AF_INET, sin_port=htons(58056), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0 ... PING 0 (127.0.0.1) 56(84) bytes of data.
You can also see the change if you bind to a specific interface instead:
# strace -e trace=network ping -I eth0 0 socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = 3 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 4 setsockopt(4, SOL_SOCKET, SO_BINDTODEVICE, "eth0\0", 5) = 0 connect(4, {sa_family=AF_INET, sin_port=htons(1025), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 getsockname(4, {sa_family=AF_INET, sin_port=htons(58408), sin_addr=inet_addr("192.168.0.123")}, [16]) = 0 setsockopt(3, SOL_RAW, ICMP_FILTER, ...) [...] PING 0 (192.168.0.123) from 192.168.0.123 eth0: 56(84) bytes of data.
While 0 may be treated as 0.0.0.0 and a broadcast address in many cases that's clearly not what ping is doing. It special-cases this to mean "the primary IP of the interface in question" (with some extra handling for multicast/broadcast cases).
RFC 1122 §3.2.1.3 explains the behaviour: both 0.0.0.0 and the IP address with the network masked off (the "host number", e.g. 0.0.0.1 in the case of loopback) mean "this host on this network".
(a) { 0, 0 } This host on this network. MUST NOT be sent, except as a source address as part of an initialization procedure by which the host learns its own IP address. See also Section 3.3.6 for a non-standard use of {0,0}. (b) { 0, <Host-number> } Specified host on this network. It MUST NOT be sent, except as a source address as part of an initialization procedure by which the host learns its full IP address.
At least in the case of 0 or 0.0.0.0 that is how iputils ping behaves, other pings and other OSs may behave differently. For example FreeBSD pings 0.0.0.0 via the default route (which I don't think is "correct" behaviour).
ping 1 or 0.0.0.1 don't quite work as hoped though (not for me anyway, iputils-sss20101006).