aboutsummaryrefslogtreecommitdiffstats
path: root/src/ipcalc.c
blob: bec14eb597161382dc4d570491d7cb626b3f07ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
/*
 * Copyright (c) 1997-2009 Red Hat, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Authors:
 *   Erik Troan <ewt@redhat.com>
 *   Preston Brown <pbrown@redhat.com>
 *   David Cantrell <dcantrell@redhat.com>
 */

#include <ctype.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

/*!
  \file ipcalc.c
  \brief provides utilities for manipulating IP addresses.

  ipcalc provides utilities and a front-end command line interface for
  manipulating IP addresses, and calculating various aspects of an ip
  address/netmask/network address/prefix/etc.

  Functionality can be accessed from other languages from the library
  interface, documented here.  To use ipcalc from the shell, read the
  ipcalc(1) manual page.

  When passing parameters to the various functions, take note of whether they
  take host byte order or network byte order.  Most take host byte order, and
  return host byte order, but there are some exceptions.
*/

/*!
  \fn struct in_addr prefix2mask(int bits)
  \brief creates a netmask from a specified number of bits

  This function converts a prefix length to a netmask.  As CIDR (classless
  internet domain internet domain routing) has taken off, more an more IP
  addresses are being specified in the format address/prefix
  (i.e. 192.168.2.3/24, with a corresponding netmask 255.255.255.0).  If you
  need to see what netmask corresponds to the prefix part of the address, this
  is the function.  See also \ref mask2prefix.

  \param prefix is the number of bits to create a mask for.
  \return a network mask, in network byte order.
*/
struct in_addr prefix2mask(int prefix) {
    struct in_addr mask;
    memset(&mask, 0, sizeof(mask));
    if (prefix) {
        mask.s_addr = htonl(~((1 << (32 - prefix)) - 1));
    } else {
        mask.s_addr = htonl(0);
    }
    return mask;
}

/*!
  \fn int mask2prefix(struct in_addr mask)
  \brief calculates the number of bits masked off by a netmask.

  This function calculates the significant bits in an IP address as specified by
  a netmask.  See also \ref prefix2mask.

  \param mask is the netmask, specified as an struct in_addr in network byte order.
  \return the number of significant bits.  */
int mask2prefix(struct in_addr mask)
{
    int count;
    uint32_t saddr = ntohl(mask.s_addr);

    for (count=0; saddr > 0; count++) {
        saddr=saddr << 1;
    }

    return count;
}

/*!
  \fn struct in_addr default_netmask(struct in_addr addr)

  \brief returns the default (canonical) netmask associated with specified IP
  address.

  When the Internet was originally set up, various ranges of IP addresses were
  segmented into three network classes: A, B, and C.  This function will return
  a netmask that is associated with the IP address specified defining where it
  falls in the predefined classes.

  \param addr an IP address in network byte order.
  \return a netmask in network byte order.  */
struct in_addr default_netmask(struct in_addr addr)
{
    uint32_t saddr = addr.s_addr;
    struct in_addr mask;

    memset(&mask, 0, sizeof(mask));

    if (((ntohl(saddr) & 0xFF000000) >> 24) <= 127)
        mask.s_addr = htonl(0xFF000000);
    else if (((ntohl(saddr) & 0xFF000000) >> 24) <= 191)
        mask.s_addr = htonl(0xFFFF0000);
    else
        mask.s_addr = htonl(0xFFFFFF00);

    return mask;
}

/*!
  \fn struct in_addr calc_broadcast(struct in_addr addr, int prefix)

  \brief calculate broadcast address given an IP address and a prefix length.

  \param addr an IP address in network byte order.
  \param prefix a prefix length.

  \return the calculated broadcast address for the network, in network byte
  order.
*/
struct in_addr calc_broadcast(struct in_addr addr, int prefix)
{
    struct in_addr mask = prefix2mask(prefix);
    struct in_addr broadcast;

    memset(&broadcast, 0, sizeof(broadcast));

/* if prefix is set to 31 return 255.255.255.255 (RFC3021) */
    if (mask.s_addr ==  htonl(0xFFFFFFFE))
        broadcast.s_addr = htonl(0xFFFFFFFF);
    else
        broadcast.s_addr = (addr.s_addr & mask.s_addr) | ~mask.s_addr;
    return broadcast;
}

/*!
  \fn struct in_addr calc_network(struct in_addr addr, int prefix)
  \brief calculates the network address for a specified address and prefix.

  \param addr an IP address, in network byte order
  \param prefix the network prefix
  \return the base address of the network that addr is associated with, in
  network byte order.
*/
struct in_addr calc_network(struct in_addr addr, int prefix)
{
    struct in_addr mask = prefix2mask(prefix);
    struct in_addr network;

    memset(&network, 0, sizeof(network));
    network.s_addr = addr.s_addr & mask.s_addr;
    return network;
}

/*!
  \fn const char *get_hostname(int family, void *addr)
  \brief returns the hostname associated with the specified IP address

  \param family the address family, either AF_INET or AF_INET6.
  \param addr an IP address to find a hostname for, in network byte order,
  should either be a pointer to a struct in_addr or a struct in6_addr.

  \return a hostname, or NULL if one cannot be determined.  Hostname is stored
  in a static buffer that may disappear at any time, the caller should copy the
  data if it needs permanent storage.
*/
char *get_hostname(int family, void *addr)
{
    struct hostent * hostinfo = NULL;
    int x;
    struct in_addr addr4;
    struct in6_addr addr6;

    if (family == AF_INET) {
        memset(&addr4, 0, sizeof(addr4));
        memcpy(&addr4, addr, sizeof(addr4));
        hostinfo = gethostbyaddr((const void *) &addr4,
                                 sizeof(addr4), family);
    } else if (family == AF_INET6) {
        memset(&addr6, 0, sizeof(addr6));
        memcpy(&addr6, addr, sizeof(addr6));
        hostinfo = gethostbyaddr((const void *) &addr6,
                                 sizeof(addr6), family);
    }

    if (!hostinfo)
        return NULL;

    for (x=0; hostinfo->h_name[x]; x++) {
        hostinfo->h_name[x] = tolower(hostinfo->h_name[x]);
    }
    return hostinfo->h_name;
}

/*!
  \fn main(int argc, const char **argv)
  \brief wrapper program for ipcalc functions.

  This is a wrapper program for the functions that the ipcalc library provides.
  It can be used from shell scripts or directly from the command line.

  For more information, please see the ipcalc(1) man page.
*/
int main(int argc, const char **argv) {
    int showBroadcast = 0, showPrefix = 0, showNetwork = 0;
    int showHostname = 0, showNetmask = 0;
    int beSilent = 0;
    int doCheck = 0, familyIPv4 = 0, familyIPv6 = 0;
    int rc;
    poptContext optCon;
    char *ipStr, *prefixStr, *netmaskStr, *chptr;
    char *hostName = NULL;
    char namebuf[INET6_ADDRSTRLEN+1];
    struct in_addr ip, netmask, network, broadcast;
    struct in6_addr ip6;
    int prefix = -1;
    char errBuf[250];
    struct poptOption optionsTable[] = {
        { "check", 'c', 0, &doCheck, 0,
          "Validate IP address for specified address family", },
        { "ipv4", '4', 0, &familyIPv4, 0,
          "IPv4 address family (default)", },
        { "ipv6", '6', 0, &familyIPv6, 0,
          "IPv6 address family", },
        { "broadcast", 'b', 0, &showBroadcast, 0,
          "Display calculated broadcast address", },
        { "hostname", 'h', 0, &showHostname, 0,
          "Show hostname determined via DNS" },
        { "netmask", 'm', 0, &showNetmask, 0,
          "Display default netmask for IP (class A, B, or C)" },
        { "network", 'n', 0, &showNetwork, 0,
          "Display network address", },
        { "prefix", 'p', 0, &showPrefix, 0,
          "Display network prefix", },
        { "silent", 's', 0, &beSilent, 0,
          "Don't ever display error messages" },
        POPT_AUTOHELP
        { NULL, '\0', 0, 0, 0, NULL, NULL }
    };

    optCon = poptGetContext("ipcalc", argc, argv, optionsTable, 0);
    poptReadDefaultConfig(optCon, 1);

    if ((rc = poptGetNextOpt(optCon)) < -1) {
        if (!beSilent) {
            fprintf(stderr, "ipcalc: bad argument %s: %s\n",
                    poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
                    poptStrerror(rc));
            poptPrintHelp(optCon, stderr, 0);
        }
        return 1;
    }

    if (!(ipStr = (char *) poptGetArg(optCon))) {
        if (!beSilent) {
            fprintf(stderr, "ipcalc: ip address expected\n");
            poptPrintHelp(optCon, stderr, 0);
        }
        return 1;
    }

    /* if there is a : in the address, it is an IPv6 address */
    if (strchr(ipStr,':') != NULL) {
        familyIPv6=1;
    }

    if (strchr(ipStr,'/') != NULL) {
        prefixStr = strchr(ipStr, '/') + 1;
        prefixStr--;
        *prefixStr = '\0';  /* fix up ipStr */
        prefixStr++;
    } else {
        prefixStr = NULL;
    }

    if (prefixStr != NULL) {
        prefix = atoi(prefixStr);
        if (prefix < 0 || ((familyIPv6 && prefix > 128) || (!familyIPv6 && prefix > 32))) {
            if (!beSilent)
                fprintf(stderr, "ipcalc: bad prefix: %s\n", prefixStr);
            return 1;
        }
    }

    if (showBroadcast || showNetwork || showPrefix) {
        if (!(netmaskStr = (char *) poptGetArg(optCon)) && (prefix < 0)) {
            if (!beSilent) {
                fprintf(stderr, "ipcalc: netmask or prefix expected\n");
                poptPrintHelp(optCon, stderr, 0);
            }
            return 1;
        } else if (netmaskStr && prefix >= 0) {
            if (!beSilent) {
                fprintf(stderr, "ipcalc: both netmask and prefix specified\n");
                poptPrintHelp(optCon, stderr, 0);
            }
            return 1;
        } else if (netmaskStr) {
            if (inet_pton(AF_INET, netmaskStr, &netmask) <= 0) {
                if (!beSilent)
                    fprintf(stderr, "ipcalc: bad netmask: %s\n", netmaskStr);
                return 1;
            }
            prefix = mask2prefix(netmask);
        }
    }

    if ((chptr = (char *) poptGetArg(optCon))) {
        if (!beSilent) {
            fprintf(stderr, "ipcalc: unexpected argument: %s\n", chptr);
            poptPrintHelp(optCon, stderr, 0);
        }
        return 1;
    }

    if (!familyIPv4 && !familyIPv6)
        familyIPv4 = 1;

    if (familyIPv4 && familyIPv6) {
        if (!beSilent) {
            fprintf(stderr, "ipcalc: cannot specify both address families\n");
        }
        return 1;
    }

    /* Handle CIDR entries such as 172/8 */
    if (prefix >= 0 && familyIPv4) {
        char *tmp = ipStr;
        int i;

        for (i=3; i> 0; i--) {
            tmp = strchr(tmp,'.');
            if (!tmp)
                break;
            else
                tmp++;
        }

        tmp = NULL;
        for (; i>0; i--) {
            if (asprintf(&tmp, "%s.0", ipStr) == -1) {
                fprintf(stderr, "Memory allocation failure line %d\n", __LINE__);
                abort();
            }
            ipStr = tmp;
        }
    }

    if (familyIPv4) {
        if (inet_pton(AF_INET, ipStr, &ip) <= 0) {
            if (!beSilent)
                fprintf(stderr, "ipcalc: bad IPv4 address: %s\n", ipStr);
            return 1;
        } else if (prefix > 32) {
            if (!beSilent)
                fprintf(stderr, "ipcalc: bad IPv4 prefix %d\n", prefix);
            return 1;
        } else {
            if (doCheck)
                return 0;
        }
    }

    if (familyIPv6) {
        if (inet_pton(AF_INET6, ipStr, &ip6) <= 0) {
            if (!beSilent)
                fprintf(stderr, "ipcalc: bad IPv6 address: %s\n", ipStr);
            return 1;
        } else if (prefix > 128) {
            if (!beSilent)
                fprintf(stderr, "ipcalc: bad IPv6 prefix %d\n", prefix);
            return 1;
        } else {
            if (doCheck)
                return 0;
        }
    }

    if (familyIPv6 &&
        (showBroadcast || showNetmask || showNetwork || showPrefix)) {
        if (!beSilent) {
            fprintf(stderr, "ipcalc: unable to show setting for IPv6\n");
        }
        return 1;
    }

    if (familyIPv4 &&
        !(showNetmask|showPrefix|showBroadcast|showNetwork|showHostname)) {
        poptPrintHelp(optCon, stderr, 0);
        return 1;
    }

    poptFreeContext(optCon);

    /* we know what we want to display now, so display it. */

    if (showNetmask) {
        if (prefix >= 0) {
            netmask = prefix2mask(prefix);
        } else {
            netmask = default_netmask(ip);
            prefix = mask2prefix(netmask);
        }

        memset(&namebuf, '\0', sizeof(namebuf));

        if (inet_ntop(AF_INET, &netmask, namebuf, INET_ADDRSTRLEN) == NULL) {
            fprintf(stderr, "Memory allocation failure line %d\n", __LINE__);
            abort();
        }

        printf("NETMASK=%s\n", namebuf);
    }

    if (showPrefix) {
        if (prefix == -1)
            prefix = mask2prefix(ip);
        printf("PREFIX=%d\n", prefix);
    }

    if (showBroadcast) {
        broadcast = calc_broadcast(ip, prefix);
        memset(&namebuf, '\0', sizeof(namebuf));

        if (inet_ntop(AF_INET, &broadcast, namebuf, INET_ADDRSTRLEN) == NULL) {
            fprintf(stderr, "Memory allocation failure line %d\n", __LINE__);
            abort();
        }

        printf("BROADCAST=%s\n", namebuf);
    }

    if (showNetwork) {
        network = calc_network(ip, prefix);
        memset(&namebuf, '\0', sizeof(namebuf));

        if (inet_ntop(AF_INET, &network, namebuf, INET_ADDRSTRLEN) == NULL) {
            fprintf(stderr, "Memory allocation failure line %d\n", __LINE__);
            abort();
        }

        printf("NETWORK=%s\n", namebuf);
    }

    if (showHostname) {
        if (familyIPv4) {
            hostName = get_hostname(AF_INET, &ip);
        } else if (familyIPv6) {
            hostName = get_hostname(AF_INET6, &ip6);
        }

        if (hostName == NULL) {
            if (!beSilent) {
                sprintf(errBuf, "ipcalc: cannot find hostname for %s", ipStr);
                herror(errBuf);
            }
            return 1;
        }

        printf("HOSTNAME=%s\n", hostName);
    }

    return 0;
}