szaydel
3/25/2020 - 4:19 AM

Portable interface addresses in JSON form

#include <errno.h>
#include <ifaddrs.h>
#include <jansson.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>

#define fatal(msg)     \
  fprintf(stderr, "%s\n", msg); \
  abort();

// must effectively crashes the program if allocation of memory in jansson
// library fails.
static json_t* must(json_t* p) {
  if (!p) {
    fatal("failed to allocate json object");
  }
  return p;
}

static void usage(const char* prog) {
  fprintf(stderr, "usage: %s [interface name]\n", prog);
  exit(2);
}

int main(int argc, char** argv) {
  if ((argc > 2) || ((argc > 1) && (strcasecmp(argv[1], "-h") == 0))) {
    usage(argv[0]);
  }

  struct ifaddrs* addresses;

  bool supplied_name = false;
  char* find_iface_name = NULL;

  if (argc > 1) {
    supplied_name = true;
    find_iface_name = argv[1];
  }

  if (getifaddrs(&addresses) == -1) {
    const char* gai_err = gai_strerror(errno);
    fprintf(stderr, "{\"error\": \"%s\"}", gai_err);
    return EXIT_FAILURE;
  }

  json_t* top_level_obj = must(json_object());
  struct ifaddrs* address = addresses;

  while (address) {
    if (address->ifa_addr == NULL) {
      address = address->ifa_next;
      continue;
    }
    // Skip to next address if there is not a match
    if ((supplied_name) && (strcasecmp(find_iface_name, address->ifa_name))) {
      address = address->ifa_next;
      continue;
    };

    int family = address->ifa_addr->sa_family;
    char ap[NI_MAXHOST];
    char nmp[NI_MAXHOST];
    if (family == AF_INET || family == AF_INET6) {
      const int family_size = family == AF_INET ? sizeof(struct sockaddr_in)
                                                : sizeof(struct sockaddr_in6);
      getnameinfo(address->ifa_addr, family_size, ap, NI_MAXHOST, NULL, 0,
                  NI_NUMERICHOST);
      getnameinfo(address->ifa_netmask, family_size, nmp, NI_MAXHOST, NULL, 0,
                  NI_NUMERICHOST);
      json_t* j = must(json_object());
      json_t* ifa_name = must(json_string(address->ifa_name));
      json_object_set_new(j, "name", ifa_name);
      json_t* ifa_family = must(json_string(family == AF_INET ? "v4" : "v6"));
      json_object_set_new(j, "family", ifa_family);
      json_t* ifa_address = must(json_string(ap));
      json_object_set_new(j, "address", ifa_address);
      json_t* ifa_netmask = must(json_string(nmp));
      json_object_set_new(j, "netmask", ifa_netmask);

      json_t* this_interface =
          json_object_get(top_level_obj, address->ifa_name);
      if (!this_interface) {
        this_interface = must(json_object());
        json_t* packed =
            must(json_pack("{s{ssss}}", family == AF_INET ? "v4" : "v6",
                           "address", ap, "netmask", nmp));
        int res = json_object_set(top_level_obj, address->ifa_name, packed);
        if (res == -1) {
          json_t* err =
              must(json_pack("{ss}", "error", "json_object_set() failed"));
          fprintf(stderr, "%s\n", json_dumps(err, JSON_COMPACT));
          exit(EXIT_FAILURE);
        }
      } else {
        json_t* packed =
            must(json_pack("{s{ssss}}", family == AF_INET ? "v4" : "v6",
                           "address", ap, "netmask", nmp));
        int res = json_object_update_missing(this_interface, packed);
        if (res == -1) {
          json_t* err = must(json_pack("{ss}", "error",
                                       "json_object_update_missing() failed"));
          fprintf(stderr, "%s\n", json_dumps(err, JSON_COMPACT));
          exit(EXIT_FAILURE);
        }
      }
    }
    address = address->ifa_next;
  }
  char* buf = json_dumps(top_level_obj, JSON_COMPACT);
  if (!buf) {
    json_t* err = must(json_pack("{ss}", "error", "json_dumps() failed"));
    fprintf(stderr, "%s\n", json_dumps(err, JSON_COMPACT));
    exit(EXIT_FAILURE);
  }
  printf("%s\n", buf);

  free(buf);
  freeifaddrs(addresses);

  return EXIT_SUCCESS;
}
CC := clang 
CFLAGS := -I/usr/local/Cellar/jansson/2.12/include 
LDFLAGS := -L/usr/local/Cellar/jansson/2.12/lib -ljansson

ifaddrs: ./ifaddrs.c
        $(CC) $(CFLAGS) $(LDFLAGS) -o ifaddrs ifaddrs.c

.PHONY: format
format:
        clang-format -i -style=google *.c