2

I want to convert IP addresses to binary in order to store them in MySQL DB. Online search says this is the most efficient way to store addresses, because ipv4 fits in 4 bytes and ipv6 in 16 bytes (What is the ideal datatype to store IP address in a mysql table?).

Now, it seems the common method to do that is using inet_pton() function which says: "inet_pton - convert IPv4 and IPv6 addresses from text to binary form" (http://man7.org/linux/man-pages/man3/inet_pton.3.html)

So my question is where is that binary number stored?

I use "sockaddr_in" struct with inet_pton as most online guides suggest, this is how struct looks like:

struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};

struct in_addr {
    unsigned long s_addr;  // load with inet_ntop()
};

My code is basically:

#include <arpa/inet.h>

int main(int argc, const char *argv[]) {

    if (argc >= 2) {

        char *ip = malloc(strlen(argv[1]) + 1);
        memcpy(ip, argv[1], strlen(argv[1]) + 1);

        struct sockaddr_in sa;
        char str[INET_ADDRSTRLEN];

        // store this IP address in sa:
        inet_pton(AF_INET, ip, &sa.sin_addr);


        /* for verifying purposes, I'm trying to print the result
           to make sure I get a binary number (obviously, "%d" should
           not print a binary yet I do get an int result) */
        printf("%d\n", sa.sin_addr.s_addr);

    }
}

The output I'm getting (using 127.0.0.1 as input) is: 16777343 <- This does not seem like a binary number, nor should printf print a binary if it actually were that. If inet_pton() converts an IP to binary then where is that binary.

If it's possible, I would prefer for a solution to include printf that prints the binary to verify the result (but that's just personal preference).

Edit

My question is not about how to convert int to binary, it is about inet_pton function output. I wanted to include a mechanism to convert int to binary in the answer as a bonus, but that's definitely not the main theme of the question - hence it's not duplicate of: Print an int in binary representation using C as @Andre Kampling suggested in the comments

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • 3
    All numbers are binary. It is just a representation in base 2. You can even have numbers in base 200. – Ed Heal Aug 15 '17 at 08:37
  • 5
    `16777343` decimal is [`0000 0001 0000 0000 0000 0000 0111 1111`](https://www.numberfacts.com/16777343) binary which is: `1 0 0 127` because of little endian representation. How to printf binary: https://stackoverflow.com/questions/1024389/print-an-int-in-binary-representation-using-c. – Andre Kampling Aug 15 '17 at 08:41
  • 1
    Possible duplicate of [Print an int in binary representation using C](https://stackoverflow.com/questions/1024389/print-an-int-in-binary-representation-using-c) – Andre Kampling Aug 15 '17 at 08:42
  • Ok, but the thing is I need to store the binary in MySQL, and if it's base 2 then would it fit in 4 bytes? how do I get the binary form that would fit to MySQL? –  Aug 15 '17 at 08:43
  • 2
    @user3086182 you already **have** it. –  Aug 15 '17 at 08:48
  • 2
    The word "binary" is confusing because it has two meanings: 1. base 2 representation (0s and 1s). 2. Non-textual representation as a series of bytes. Here it means the latter, so you shouldn't expect to see a result in base 2. – interjay Aug 15 '17 at 08:54
  • @user3086182 - how are you interfacing the MySQL database? If you're looking for help writing the code to actually pass the data into the database, we'll need to see the code that's doing that. – wally Aug 15 '17 at 08:57

3 Answers3

2

The output I'm getting (using 127.0.0.1 as input) is: 16777343 <- This does not seem like a binary number

As already commented, everything is stored "binary" in a computer. The %d conversion specifier of printf() just interprets the bit pattern as a number.

If it's possible, I would prefer for a solution to include printf that prints the binary to verify the result (but that's just personal preference).

There's no conversion specifier for a "binary number" in printf(), so you would have to write your own implementation. E.g. for int, it could look like this:

#define IMAX_BITS(m) ((m) /((m)%0x3fffffffL+1) /0x3fffffffL %0x3fffffffL *30 \
                  + (m)%0x3fffffffL /((m)%31+1)/31%31*5 + 4-12/((m)%31+3))

// use unsigned because we don't want to treat a sign bit specially
void print_binary(unsigned int i)
{
    unsigned mask = 1 << (IMAX_BITS((unsigned)-1) - 1);
    while (mask)
    {
        putchar(i & mask ? '1' : '0');
        mask >>= 1;
    }
}

The "strange" macro is for portably determining the number of bits in an unsigned int, see this answer for more info.

1

[...] where is the binary?

inet_pton(AF_INET, ip, &sa.sin_addr);

takes what is stored where ip points to, converts it to a binary representation of the IP-address and then stores the result under the address of sa.sin_addr.

sa.sin_addr is typed struct in_addr, which in turn is defined as being

struct in_addr {
  unsigned long s_addr;  
};

which in fact is nothing more but an unsigned long.

An unsigned long (as well as all other integer/floating point data types) stores its value as binary by concept. You do not need to verify this ... ;-)


So to store the result of the above conversion into an SQL database, you need an SQL column type able to store at least an unsigned long.

For passing the result of this above conversion to any function storing it into a DB, pass either

 sa.sin_addr

or

 sa.sin_addr.s_addr
alk
  • 69,737
  • 10
  • 105
  • 255
  • Unsigned long is less efficient, I need to store it as binary (MySQL data type = VARBINARY), so I gather I would have to transform the %ul to a binary somehow... –  Aug 15 '17 at 08:58
  • @user3086182: No, you don't need to convert it again, as an `unsigned long`'s internal representation *already is binary* ([also see this comment](https://stackoverflow.com/questions/45689306/inet-pton-function-where-is-the-binary/45689505#comment78336679_45689306)). Just define the `VARBINARY` typed column large enough to hold `sizeof (unsigned long)` bytes and store the conversion's result in there. – alk Aug 15 '17 at 09:03
  • @user3086182: "*Unsigned long is less efficient*" from what do you conclude this? *"less efficient*" in term of what? – alk Aug 15 '17 at 09:05
  • MySQL documentation: "VARBINARY - L + 1 bytes if column values require 0 − 255 bytes", "INTEGER - 4 bytes" or "BIGINT - 8 bytes" or "LONGBLOB - L + 4 bytes, where L < 232" https://dev.mysql.com/doc/refman/5.7/en/storage-requirements.html –  Aug 15 '17 at 09:10
  • You were very helpful to help me realize the solution, thank you, that's why I upvoted you –  Aug 15 '17 at 09:13
  • You only need binary (BLOB) because MySQL doesn't have other datatype to fit IPv6 addresses in a size-efficient way. You could use two BIGINTs, but that's messy. If you only care about IPv4, then INTEGER is enough. – Erki Aring Aug 15 '17 at 12:00
0

In order to support both, IPv4 and IPv6 addresses:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <arpa/inet.h>

// Convert IPv4 or IPv6 to 16-bytes binary
int ip_to_binary (const char *ip, void *binary_16_bytes)
{
    struct sockaddr_in sa4;
    struct sockaddr_in6 sa6;

    if (inet_pton (AF_INET, ip, &sa4.sin_addr) == 1) {
        // This is a valid IPv4 address - copy 4 bytes to the end of binary
        memset (binary_16_bytes, 0, 12);
        memcpy (binary_16_bytes + 12, &sa4.sin_addr, 4);
    } else if (inet_pton (AF_INET6, ip, &sa6.sin6_addr) == 1) {
        // This is a valid IPv6 address
        memcpy (binary_16_bytes, &sa6.sin6_addr, 16);
    } else {
        // This is crap...
        return -1;
    }

    return 0;
}

// Convert 16-bytes binary to IPv4 or IPv6 address
int binary_to_ip (const void *binary_16_bytes, char *buf, size_t buflen)
{
    static uint8_t zero[16] = {0};
    short family;
    const void *sin_addr;

    // If first 12 bytes (but not all 16 bytes) are zeroes, then treat it as IPv4 address
    if (!memcmp (binary_16_bytes, zero, 12) && memcmp (binary_16_bytes, zero, 16)) {
        family = AF_INET;
        sin_addr = binary_16_bytes + 12;
    } else {
        family = AF_INET6;
        sin_addr = binary_16_bytes;
    }

    if (inet_ntop (family, sin_addr, buf, buflen) != NULL) {
        return 0;
    } else {
        return -1;
    }
}

int main (int argc, char *argv[])
{
    uint8_t binary[16];

    if (argc >= 2) {

        // IP address to binary
        if (ip_to_binary (argv[1], binary) != 0) {
            printf ("Invalid IP address!\n");
            exit (1);
        }

        // Binary back to IP address
        char buf[INET6_ADDRSTRLEN];
        if (binary_to_ip (binary, buf, sizeof (buf)) == 0) {
            printf ("IP address: %s\n", buf);
        }

    }
}

Then you can bind this buffer to the statement by doing something like this: (not sure, I haven't used MySQL C API)

// ...
MYSQL_BIND bind;
unsigned long len = sizeof (binary);
bind.buffer_type = MYSQL_TYPE_BLOB;
bind.buffer = binary;
bind.buffer_length = len;
bind.is_null = 0;
bind.length = &len;
// ...
Erki Aring
  • 2,032
  • 13
  • 15