The sourcecode for the IP datatypes show this:
typedef struct
{
unsigned char family; /* PGSQL_AF_INET or PGSQL_AF_INET6 */
unsigned char bits; /* number of bits in netmask */
unsigned char ipaddr[16]; /* up to 128 bits of address */
} inet_struct;
This means, that additionally to the "raw" data in ipaddr
(4 bytes for IP4, 16 bytes for IP6) there is one byte for the netmask and and one byte for the address family (basically a switch for IP4/IP6).
Additionally there is the varlena
overhead which is mentioned in the same file:
/*
* Both INET and CIDR addresses are represented within Postgres as varlena
* objects, ie, there is a varlena header in front of the struct type
* depicted above. This struct depicts what we actually have in memory
* in "uncompressed" cases. Note that since the maximum data size is only
* 18 bytes, INET/CIDR will invariably be stored into tuples using the
* 1-byte-header varlena format. However, we have to be prepared to cope
* with the 4-byte-header format too, because various code may helpfully
* try to "decompress" 1-byte-header datums.
*/
typedef struct
{
char vl_len_[4]; /* Do not touch this field directly! */
inet_struct inet_data;
} inet;
So the equation for IP4 is this:
1 byte varlena
1 byte address family
1 byte netmask
4 raw bytes
===========
7 byte total
For IP6 the same formula gives you 19 bytes.
EDIT Older versions of PostgreSQL did only have 4 byte varlena representation. Therefore you can add 3 bytes for each type (IP4: 10, IP6: 22). On top of that there was a padding up to the next 4 byte border. This gives you 2 bytes for each type adding up to 12 or 24 bytes.
This mail sheds some light upon the development of the shorter version.