Skip to content

Instantly share code, notes, and snippets.

@dxdxdt
Last active July 7, 2025 09:32
Show Gist options
  • Save dxdxdt/3b1d6596c15627e53e296cdfe5b2cd90 to your computer and use it in GitHub Desktop.
Save dxdxdt/3b1d6596c15627e53e296cdfe5b2cd90 to your computer and use it in GitHub Desktop.
Ever wondered why `ping 127.1` works?

inet-pnp

Ever wondered why this works?

$ ping 127.1
PING 127.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.047 ms

$ ping 1.1
PING 1.1 (1.0.0.1) 56(84) bytes of data.
64 bytes from 1.0.0.1: icmp_seq=1 ttl=48 time=5.04 ms

What's doing on here?

getaddrinfo(3):

node specifies either a numerical network address (for IPv4, numbers-and-dots notation as supported by inet_aton(3); for IPv6, hexadecimal string format as supported by inet_pton(3))

inet(3):

The address supplied in cp can have one of the following forms:

a.b.c.d Each of the four numeric parts specifies a byte of the address; the bytes are assigned in left-to-right order to produce the binary address.

a.b.c Parts a and b specify the first two bytes of the binary address. Part c is interpreted as a 16-bit value that defines the rightmost two bytes of the binary address. This notation is suitable for specifying (outmoded) Class B network addresses.

a.b Part a specifies the first byte of the binary address. Part b is interpreted as a 24-bit value that defines the rightmost three bytes of the binary address. This notation is suitable for specifying (outmoded) Class A network addresses.

a The valuqe a is interpreted as a 32-bit value that is stored directly into the binary address without any byte rearrangement.

inet_ntop(3):

AF_INET src points to a struct in_addr (in network byte order) which is converted to an IPv4 network address in the dotted-decimal format, "ddd.ddd.ddd.ddd". The buffer dst must be at least INET_ADDRSTRLEN bytes long.

AF_INET6 src points to a struct in6_addr (in network byte order) which is converted to a representation of this address in the most appropriate IPv6 network address format for this address. The buffer dst must be at least INET6_ADDRSTRLEN bytes long.

It's because inet_ntop() only accepts "ddd.ddd.ddd.ddd" format whereas inet_aton() accepts "a", "a.b", ..., "a.b.c.d". Any program that calls getaddrinfo() could potentially accept the inet_aton() style.

The code

Build:

cc -std=c17 inet-pnp -o inet-pnp.c

Run:

# normal data
echo '1.2.3.4' | ./inet-pnp
# inet_aton() style
echo '127.1' | ./inet-pnp

Result

Team "loose"(Windows, Linux, FreeBSD):

pton: 1.2.3.4, gai: 1.2.3.4
pton: Bad message, gai: 127.0.0.1

Team "strict"(OpenBSD):

pton: 1.2.3.4, gai: 1.2.3.4
pton: Bad message, gai: name or service is not known

Takeaway

The inet_aton() style IPv4 address representation is not portable. Always use 'a.b.c.d' format. Use inet_ntop() over inet_aton() where possible.

Some history

https://datatracker.ietf.org/doc/html/draft-main-ipaddr-text-rep-00

#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#else
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#endif
#define ARGV0 "inet-pnp"
static bool do_pn (const char *in, const int af) {
uint8_t buf[16];
char out[INET6_ADDRSTRLEN];
out[0] = 0;
errno = EBADMSG;
return
inet_pton(af, in, buf) > 0 &&
inet_ntop(af, buf, out, sizeof(out)) != NULL &&
printf("%s", out);
}
static bool do_gn (const char *in) {
static const struct addrinfo hints = {
.ai_flags = AI_NUMERICHOST
};
union {
struct sockaddr_storage storage;
struct sockaddr a;
struct sockaddr_in in;
struct sockaddr_in6 in6;
} s;
struct addrinfo *ai = NULL;
char out[INET6_ADDRSTRLEN];
bool ret = false;
const void *addrbuf;
const int fr = getaddrinfo(in, NULL, &hints, &ai);
if (fr != 0) {
const char *errmsg = gai_strerror(fr);
printf("%s", errmsg);
ret = true;
goto END;
}
memcpy(&s, ai->ai_addr, ai->ai_addrlen);
switch (ai->ai_family) {
case AF_INET: addrbuf = &s.in.sin_addr; break;
case AF_INET6: addrbuf = &s.in6.sin6_addr; break;
default:
errno = EAFNOSUPPORT;
goto END;
}
out[0] = 0;
errno = EBADMSG;
if (inet_ntop(ai->ai_family, addrbuf, out, sizeof(out)) == NULL) {
goto END;
}
printf("%s", out);
ret = true;
END:
if (ai != NULL) {
freeaddrinfo(ai);
}
return ret;
}
#ifdef WIN32
static void start_wsa (void) {
int fr;
WSADATA wsaData = {0};
fr = WSAStartup(MAKEWORD(2, 2), &wsaData);
assert(fr == 0);
(void)fr;
}
#endif
int main (const int argc, const char **argv) {
char line[2][256];
bool err = false;
bool proc = false;
#ifdef WIN32
start_wsa();
#endif
while (fgets(line[0], sizeof(line[0]), stdin) != NULL) {
unsigned int cnt = 0;
// trim
line[1][0] = 0;
if (sscanf(line[0], "%255s", line[1]) != 1) {
// empty line. all whitespace string cannot be captured using %s
putc('\n', stdout);
continue;
}
printf("pton: ");
if (do_pn(line[1], AF_INET) || do_pn(line[1], AF_INET6)) {
cnt += 1;
}
else {
printf("%s", strerror(errno));
}
printf(", gai: ");
if (do_gn(line[1])) {
cnt += 1;
}
else {
printf("%s", strerror(errno));
}
if (cnt > 0) {
proc = true;
}
else {
err = true;
}
putc('\n', stdout);
}
if (err) {
if (proc) {
return 3;
}
return 1;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment