3

I have two strings ip1 = "192.168.145.123" and ip2 = "172.167.234.120".

I can compare these two strings for equality:

strncmp(ip1,ip2) == 0

However how can I find out

if (ip1 > ip2) {
    ...
}

What I have tried

I can use sscanf:

sscanf(ip1,"%d.%d.%d.%d",&s1,&s2,&s3,&s4) 

and store the numbers and compare. However in 32 bit I can't store the numbers as integers due to the upper limit.

Thus I have no choice but to compare the integers as strings.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
KAKAK
  • 879
  • 7
  • 16
  • 32
  • 4
    To what "upper limit" are you referring? (and if it is the sign-ness of `int`, any reason you're not using `unsigned int` and `"%u"` for reading them )? – WhozCraig Aug 19 '13 at 06:25
  • 2
    An IPV4 address can be stored perfectly in a 4byte wide unsigned integer. As also shown in this answer http://stackoverflow.com/a/18291062/694576 to one of **your** questions. – alk Aug 19 '13 at 06:26
  • 1
    Of course you can store the numbers in an integer. An ip-address is 32 bits, and fits just fine in an `unsigned int` on most systems. – Some programmer dude Aug 19 '13 at 06:26
  • 1
    You might also want to check e.g. [`inet_pton`](http://man7.org/linux/man-pages/man3/inet_pton.3.html). – Some programmer dude Aug 19 '13 at 06:27
  • 2
    @JoachimPileborg pretty sure he wants 192.168.1.1 to be *greater* than 10.0.0.1. Storing them in an 32bit `int` won't do that, but an `unsigned int` will. – WhozCraig Aug 19 '13 at 06:29
  • What is wrong with the sscanf method you used in your method? You can just compare the numbers returned until you find that the first ip is larger or the opposite – smac89 Aug 19 '13 at 06:40
  • 3
    Why not just compare them lexicographically as strings? There is no meaningful ordering to IP addresses; 172.167.234.120 is not earlier in time than 192.168.145.123, nor farther east or faster. One use for ordering things is to manage them in a data structure. For that purpose, any ordering suffices, so it might as well be whatever you can compute the fastest. String comparison is fine for that, as long as you know there are no leading zeroes. If this is not the purpose, then why do the IP addresses need an ordering? – Eric Postpischil Aug 19 '13 at 18:28
  • what about inet_aton ? – Jaffa Aug 19 '13 at 19:13
  • 1
    @JoachimIsaksson: `strcmp("19.12.10.2", "109.122.199.237")` will say that the second address comes before the first, whereas most people would say that the first comes before the second. If the address uses 3-digit triples throughout, comparison is valid (but leading zeroes are non-standard in IPv4 dotted-decimal notation); if there are any 2-digit or 1-digit numbers, then comparisons can give the wrong answer. – Jonathan Leffler Aug 19 '13 at 19:35
  • @JonathanLeffler You're right, of course. I blame lack of coffee :) – Joachim Isaksson Aug 19 '13 at 20:10
  • 2
    @EricPostpischil Of course there is an order in IP adresses, e.g. by network part in routing tables. And you might want to qsort() a list of IP adresses to use bsearch() to see if one is already in a table. Don't jump to conclusions, Watson :-) – Jens Aug 19 '13 at 21:19
  • @Jens: Regarding searching a table: My comment addressed that. All you need to organize and search a table is an ordering. Lexicographic ordering of the string satisfies that. Regarding routing: Routing tables establish some organization but not a total order. And none of the answers here about comparing by number give any regard to routing. – Eric Postpischil Aug 19 '13 at 22:57

4 Answers4

14

Is it worth mentionning that there is also inet_aton ?

You can find the man page here, below is a short description and a short synopsis.

This solution will work on most POSIX systems, but I'm sure there is some equivalent in the Windows APIs, and even some abstraction wrapper.

inet_ntoa() is specified in POSIX.1-2001. inet_aton() is not specified in POSIX.1-2001, but is available on most systems.


Linux Programmer's Manual

inet_aton() converts the Internet host address cp from the IPv4 numbers-and-dots notation into binary form (in network byte order) and stores it in the structure that inp points to.

SYNOPSIS

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);

EXAMPLE

An example of the use of inet_aton() and inet_ntoa() is shown below. Here are some example runs:

       $ ./a.out 226.000.000.037      # Last byte is in octal
       226.0.0.31
       $ ./a.out 0x7f.1               # First byte is in hex
       127.0.0.1

Program source

   #define _BSD_SOURCE
   #include <arpa/inet.h>
   #include <stdio.h>
   #include <stdlib.h>

   int
   main(int argc, char *argv[])
   {
       struct in_addr addr;

       if (argc != 2) {
           fprintf(stderr, "%s <dotted-address>\n", argv[0]);
           exit(EXIT_FAILURE);
       }

       if (inet_aton(argv[1], &addr) == 0) {
           fprintf(stderr, "Invalid address\n");
           exit(EXIT_FAILURE);
       }

       printf("%s\n", inet_ntoa(addr));
       exit(EXIT_SUCCESS);
   }

Further informations

  • Byte ordering (@Jonathan Leffler)

    The inet_ntoa() function converts the Internet host address in, given in network byte order, to a string in IPv4 dotted-decimal notation. inet_aton() converts the Internet host address cp from the IPv4 numbers-and-dots notation into binary form (in network byte order) and stores it in the structure that inp points to.

  • Structure of in_addr (@POW)

    The structure in_addr as used in inet_ntoa(), inet_makeaddr(), inet_lnaof() and inet_netof() is defined in as:

       typedef uint32_t in_addr_t;
    
       struct in_addr {
           in_addr_t s_addr;
       };
    
  • Compare to address independently of computer-endianness Addresses in in_addr are in network byte order (big-endian), so as pointed by @glglgl, you have to use ntohl, whose man page is available here.

    The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.

    uint32_t ntohl(uint32_t netlong);
    
Jaffa
  • 12,442
  • 4
  • 49
  • 101
  • 1
    Does this give the correct ordering on both big-endian and little-endian machines? The network ordering need not be the native ordering, so I suspect it could get confused. If it works correctly, then this is a good answer. – Jonathan Leffler Aug 19 '13 at 19:24
  • @JonathanLeffler You'll get a "memory dump" with `inet_aton()` which is in the network byte order. A `ntohl()` will give you the correct ordering, then. – glglgl Aug 19 '13 at 21:00
  • This is a good answer, but not sure does this answer _"how can I find out `if (ip1 > ip2) { ...}`"_ ? – P0W Aug 19 '13 at 21:26
  • @JonathanLeffler, edited my post with an additional quote, which should answer your question. – Jaffa Aug 19 '13 at 21:27
  • @POW, I edit with the structure of the `struct in_addr`, so you can get the numerical value :) – Jaffa Aug 19 '13 at 21:30
  • 1
    Thanks for the update. My concern is that `inet_aton()` converts the string into a network order address, but network order (big-endian) won't compare accurately on Intel (little-endian) machines. I think @glglgl more or less agrees with me. To get the result comparable on the machine, you have to convert the network order value to host order, using `ntohl()`, for example. Then you can do the comparison with the native values. – Jonathan Leffler Aug 19 '13 at 21:31
4

You can try the sexy way, store all values in one unsigned integer and compare it.

  const char* ip1 = "192.168.145.123";
  const char* ip2 = "172.167.234.120";

  unsigned char s1, s2, s3, s4;
  unsigned int uip1, uip2;

  sscanf(ip1,"%hhu.%hhu.%hhu.%hhu",&s1,&s2,&s3,&s4);
  uip1 = (s1<<24) | (s2<<16) | (s3<<8) | s4; //store all values in 32bits unsigned int

  sscanf(ip2,"%hhu.%hhu.%hhu.%hhu",&s1,&s2,&s3,&s4);
  uip2 = (s1<<24) | (s2<<16) | (s3<<8) | s4;

  if (uip1 > uip2)
  {
    printf("ip1 greater !");   
  }
  else
  {
    printf("ip2 greater or equal !");     
  }
Michael M.
  • 2,556
  • 1
  • 16
  • 26
  • 1
    Shouldn't the calls to `sscanf()` use `%hhu`? – alk Aug 19 '13 at 16:45
  • 3
    Please **do not `sscanf()`.** Use `strtoul()` instead. –  Aug 19 '13 at 17:26
  • 4
    @H2CO3: Why not use `sscanf()`? What concern is addressed by using `strtoul()` compared with `sscanf()`? Testing the results of `strtoul()` is a surprisingly delicate business, though since the values should be in the range 0..255 for this data, it should be a whole heap simpler to validate than the general 'any value is valid' case. – Jonathan Leffler Aug 19 '13 at 19:21
  • 4
    @H2CO3 Why? Using sscanf is perfectly ok (but one should test the return value). It's much easier to sscanf four numbers than using strtok or whatever to look for the dots. – Jens Aug 19 '13 at 19:21
  • @JonathanLeffler and Jens: `sscanf()` may work *in this case,* not in others. It's not that it doesn't work. It works, if used properly. But it's hard to get right the usage of `scanf()` and variants thereof. –  Aug 19 '13 at 19:24
  • 1
    @H2CO3: OK. One issue is "to what extent is it legitimate to assume that the IPv4 address string being parsed is valid (and just needs interpreting) versus the IPv4 address string could be garbage". If you're allowed to assume 'it is valid; please parse', then not checking the return from `sscanf()` etc is OK (it won't fail if the string is valid). If it is not guaranteed to be valid, there is more work to be done. – Jonathan Leffler Aug 19 '13 at 19:30
  • @JonathanLeffler Yes, sure. My point is merely that if one isn't able to figure out how to convert a string to an integer, he will most likely get `sscanf()` wrong. –  Aug 19 '13 at 19:31
  • 5
    So that is a reason to educate on the correct use of `sscanf()`; it is not a reason to demand the use of `strtoul()` which has to be handled very carefully (much more carefully than `sscanf()`) to detect all error conditions correctly. Granted, `strtoul()` will detect errors (like overflow) that `sscanf()` doesn't — but the blanket "do not use" is too sweeping; it needs a more nuanced response. – Jonathan Leffler Aug 19 '13 at 19:37
  • @JonathanLeffler Perhaps one of you could explain what error conditions / issues could arise in the usage of either sscanf() or strtoul() ?? – NickHalden Aug 20 '13 at 00:06
  • @NickHalden: In 600-characters? Kinda tough! Using `strtoul()`, you have to set `errno` to 0 before calling it, and (in general) test for ULONG_MIN and ULONG_MAX return values with ERANGE, and a return value of 0 with the end pointer set to the start (no conversion possible), plus decide what to do if the character after the converted number isn't what you expected, and so on. With `sscanf()`, there's no guarantee that overflow will be detected at all (and it isn't reported); you have to check that you got the correct number of conversions, maybe using `%n` to find where the conversions ended. – Jonathan Leffler Aug 20 '13 at 00:18
  • Note that `%u` interprets an 'optionally signed decimal integer', so `sscanf()` — and `strtoul()` — will not report an error given `-123` as one of the dotted-decimal components of the IP address, and won't object to `+123` either. This is where the validation vs conversion issue arises; if you simply need to convert a known-to-be-valid number, you can stop worrying about most of the error conditions. If you have to check whether it is valid, you have to do a lot more work — I would be sorely tempted to apply a regex to the string to check its form, and only then parse it. – Jonathan Leffler Aug 20 '13 at 00:23
  • thanks, i had the same problem and your solution was helpful ! – Mohamed Ali Benmansour Apr 06 '17 at 10:15
1

How about this :-

#include<stdio.h>
#include<conio.h>

unsigned int convIP(const char ip[]) {
    unsigned char s1, s2, s3, s4;

    if (sscanf(ip, "%hhu.%hhu.%hhu.%hhu", &s1, &s2, &s3, &s4) != 4)
        return 0;

    /* Create a 32 bit Integer using left shift & bitwise OR
            MSB                                            LSB
            +-----8----+-----8------+-----8-----+----8-----+
            |    s1    |     s2     |    s3     |    s4    |   
            +----------+------------+-----------+----------+
     */
    return  (s1 << 24) | (s2 << 16) | (s3 << 8) | (s4 << 0);

}

int ipComp(const char ip1[], const char ip2[]) {
    unsigned int ip_addr1 = convIP(ip1);
    unsigned int ip_addr2 = convIP(ip2);

    return (ip_addr1 >= ip_addr2);

}


int main()
{

    printf("%d\n",ipComp("192.168.145.123","172.167.234.120") ); //1

    printf("%d\n", ipComp("10.0.0.1","192.168.1.1") );  //0

    printf("%d\n",ipComp("192.168.145.123","192.168.145.123")); //1
}

Edit: As suggested by H2CO3:

You should generally avoid using sscanf, instead you can use strtol(), like this:

unsigned long ip2int(const char *ip)
{
    const char *end = ip + strlen(ip);
    unsigned long n = 0;
    while (ip < end) {
        n <<= 8;
        n |= strtoul(ip, (char **)&ip, 10);
        ip++;
    }

    return n;
}
P0W
  • 46,614
  • 9
  • 72
  • 119
  • Thanks for the answers! however i cant seem to understand what is the function of this part `(s1 << 24) | (s2 << 16) | (s3 << 8) | s4` – KAKAK Aug 19 '13 at 16:30
  • 1
    Shouldn't the call to `sscanf()` use `%hhu`? – alk Aug 19 '13 at 16:46
  • 1
    @alk Agreed, I'm using MINGW and `%hhu` throws a warning with `-Wall`. Same as per [this](http://stackoverflow.com/a/10680635/1870232). Still I updated – P0W Aug 19 '13 at 17:17
  • 1
    @DeepakTivari: Regarding `<<`: read about the "left-shift" operator; regarding `|` read about the "bit-wise OR" operator. – alk Aug 19 '13 at 17:20
  • 1
    @DeepakTivari - That operation is packing four separate ints into a single unsigned int. Three of the values have to be shifted to higher order bits, and that's what the shift operator (<<) does. – Carey Gregory Aug 19 '13 at 17:24
  • @P0W No `sscanf()` please! –  Aug 19 '13 at 17:26
  • Thanks for the explaination, however your prev implimentation worked like a charm, just wanted to be more clear on the bit wise thingy – KAKAK Aug 19 '13 at 17:27
  • @H2CO3 Updated, sorry, I can't think of anything simpler for a valid IP address :( – P0W Aug 19 '13 at 18:20
  • @P0W No problem. In fact, the `sscanf()` code looks better than your hand-rolled version :P Mind you if I mess around a bit with your code? –  Aug 19 '13 at 18:21
  • @H2CO3 Awesome !, but just one concern it will convert these `123.123.12.11.1.1, 123.123.12,11.1.1, 123.123.12,11.` as ints too:P But I guess, it okay, calling function should take care of these. – P0W Aug 19 '13 at 18:45
  • @P0W Yes, I haven't made sure to validate the format, indeed. –  Aug 19 '13 at 18:47
  • @Geoffroy You mean, it should be 0? –  Aug 19 '13 at 19:22
  • 2
    `sscanf(ip, "%hhu.%hhu.%hhu.%hhu"` is _incorrect_ for `s1` is type `int`, not type `unsigned char`. Recommend instead to use `sscanf(ip, "%hhu.%hhu.%hhu.%hhu"` and type `unsigned char` for `s1` or use `sscanf(ip, "%u.%u.%u.%u"` and type `unsigned` for `s1`. – chux - Reinstate Monica Aug 19 '13 at 20:10
  • Is there any virtue in using `"%3u.%3u.%3u.%3u"` so that if someone tries to slip a 4-digit or more number past the system, the `sscanf()` fails? Of course, if you're validating the address, you need to ensure that each component is in the range 0..255 afterwards; you can't specify that with `sscanf()` and it will admit 999 even with the `%3u` notation. – Jonathan Leffler Aug 19 '13 at 20:15
  • @H2CO3 no, just that you used it in a nice way using `(char **)&ip` :) maybe strtoul second argument should be of type `const char**` as this works, as the characters are not modified, just the pointer's value – Jaffa Aug 19 '13 at 21:21
  • @Jonathan Leffler "%3u.%3u..." does have some value, if for nothing else, constraining, a bit, the length of the scan. It could be a problem for the last byte as "123.123.123.1234" would likely scan incorrectly with "%3u.%3u..." and sscanf() returning 4. – chux - Reinstate Monica Aug 19 '13 at 21:38
  • 1
    @chux You could use `sscanf(ip, "%3u.%3u.%3u.%3u%c", &c1, &c2, &c3, &c4, &dummy)` and check if the result is 5 (then it is an error) or 4 (then it is ok). – glglgl Aug 19 '13 at 21:40
  • @Geoffroy No, it's `(char **)` intentionally, it's declared like so in `strtoul()`'s prototype, and omitting the cast causes a warning, [because it's wrong](http://c-faq.com/ansi/constmismatch.html). –  Aug 20 '13 at 06:05
1

A pedantic "after the accepted answer" answer. Error checking employed.

#include <inttypes.h>
int IPstringToUns32(const char *IPString, uint32_t *IPNumber) {
  uint8_t c[4];  // LSByte in c[0]
  char ch;
  const char * format = "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8 "%c";
  if (4 != sscanf(IPString, format, &c[3], &c[2], &c[1], &c[0], &ch)) {
    return 1; // parse error
  }
  *IPNumber = (((uint32_t) c[3]) << 24) | (((uint32_t) c[2]) << 16)
      | (((uint32_t) c[1]) << 8) | ((uint32_t) c[0]);
  return 0;
}

Suppose one could instead use uint_fast32_t. This solution allows for leading white space before digits.

[Edit] Added classic %c at the end of the format. Thanks to @glglgl.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Using an array here has no point. Of course, it is ok, but it has no benefits over e. g. `uint8_t c1, c2, c3, c4`. – glglgl Aug 19 '13 at 21:02
  • glglgl The scan count != 4 is not shown. An indexed array allows looped handing of unset values there. Beginning variable ID from 1 rather than idiomatic 0 is interesting. I find starting at 0 than 1 more consistent in the C language. I see the "((uint32_t) c[3]) << 24" as ""((uint32_t) c[3]) << 8*3 as a reduction in magic numbers. Seems more of a style issue. Suggest you pose it as SO question should you find it compelling. – chux - Reinstate Monica Aug 19 '13 at 21:29
  • Was just meant to be a suggestion. At the end, it is a matter of personal style, I think. – glglgl Aug 19 '13 at 21:33
  • @glglgl Concur about style. BTW: I've been bit too often when asserting the certainty of non-existence "Using ... has no point". ;-) – chux - Reinstate Monica Aug 19 '13 at 21:41