static int fill_if_addr(struct if_entry *dest, struct nlmsg_entry *ainfo) { struct if_addr *entry; struct nlmsghdr *n; struct ifaddrmsg *ifa; struct rtattr *rta_tb[IFA_MAX + 1]; int len, err; for (; ainfo; ainfo = ainfo->next) { n = &ainfo->h; ifa = NLMSG_DATA(n); if (ifa->ifa_index != dest->if_index) continue; if (n->nlmsg_type != RTM_NEWADDR) continue; len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)); if (len < 0) continue; if (ifa->ifa_family != AF_INET && ifa->ifa_family != AF_INET6) /* only IP addresses supported (at least for now) */ continue; rtnl_parse(rta_tb, IFA_MAX, IFA_RTA(ifa), len); if (!rta_tb[IFA_LOCAL] && !rta_tb[IFA_ADDRESS]) /* don't care about broadcast and anycast adresses */ continue; entry = calloc(sizeof(struct if_addr), 1); if (!entry) return ENOMEM; if (!rta_tb[IFA_LOCAL]) { rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS]; rta_tb[IFA_ADDRESS] = NULL; } if ((err = addr_init_netlink(&entry->addr, ifa, rta_tb[IFA_LOCAL]))) return err; if (rta_tb[IFA_ADDRESS] && memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), ifa->ifa_family == AF_INET ? 4 : 16)) { if ((err = addr_init_netlink(&entry->peer, ifa, rta_tb[IFA_ADDRESS]))) return err; } list_append(&dest->addr, node(entry)); } return 0; }
int nl_send(struct nl_handle *hnd, struct iovec *iov, int iovlen) { struct sockaddr_nl sa = { .nl_family = AF_NETLINK, }; struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = iov, .msg_iovlen = iovlen, }; struct nlmsghdr *src = iov->iov_base; src->nlmsg_seq = ++hnd->seq; if (sendmsg(hnd->fd, &msg, 0) < 0) return errno; return 0; } int nl_recv(struct nl_handle *hnd, struct nlmsg_entry **dest, int is_dump) { struct sockaddr_nl sa = { .nl_family = AF_NETLINK, }; struct iovec iov; struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &iov, .msg_iovlen = 1, }; char buf[16384]; int len, err; struct nlmsghdr *n; struct nlmsg_entry *ptr = NULL; /* GCC false positive */ struct nlmsg_entry *entry; *dest = NULL; while (1) { iov.iov_base = buf; iov.iov_len = sizeof(buf); len = recvmsg(hnd->fd, &msg, 0); if (len < 0) return errno; if (!len) return EPIPE; if (sa.nl_pid) { /* not from the kernel */ continue; } for (n = (struct nlmsghdr *)buf; NLMSG_OK(n, len); n = NLMSG_NEXT(n, len)) { if (n->nlmsg_pid != hnd->pid || n->nlmsg_seq != hnd->seq) continue; if (is_dump && n->nlmsg_type == NLMSG_DONE) return 0; if (n->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *nlerr = (struct nlmsgerr *)NLMSG_DATA(n); err = -nlerr->error; goto err_out; } entry = malloc(n->nlmsg_len + sizeof(void *)); if (!entry) { err = ENOMEM; goto err_out; } entry->next = NULL; memcpy(&entry->h, n, n->nlmsg_len); if (!*dest) *dest = entry; else ptr->next = entry; ptr = entry; if (!is_dump) return 0; } } err_out: nlmsg_free(*dest); *dest = NULL; return err; } int nl_exchange(struct nl_handle *hnd, struct nlmsghdr *src, struct nlmsg_entry **dest) { struct iovec iov = { .iov_base = src, .iov_len = src->nlmsg_len, }; int is_dump; int err; is_dump = !!(src->nlmsg_flags & NLM_F_DUMP); err = nl_send(hnd, &iov, 1); if (err) return err; return nl_recv(hnd, dest, is_dump); } /* The original payload is not freed. Returns 0 in case of error, length * of *dest otherwise. *dest is newly allocated. */ int nla_add_str(void *orig, int orig_len, int nla_type, const char *str, void **dest) { struct nlattr *nla; int len = strlen(str) + 1; int size; size = NLA_ALIGN(orig_len) + NLA_HDRLEN + NLA_ALIGN(len); *dest = calloc(size, 1); if (!*dest) return 0; if (orig_len) memcpy(*dest, orig, orig_len); nla = *dest + NLA_ALIGN(orig_len); nla->nla_len = NLA_HDRLEN + len; nla->nla_type = nla_type; memcpy(nla + 1, str, len); return size; } int rtnl_open(struct nl_handle *hnd) { return nl_open(hnd, NETLINK_ROUTE); } int rtnl_dump(struct nl_handle *hnd, int family, int type, struct nlmsg_entry **dest) { struct { struct nlmsghdr n; struct ifinfomsg i; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = sizeof(req); req.n.nlmsg_type = type; req.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; req.i.ifi_family = family; return nl_exchange(hnd, &req.n, dest); } void rtnl_parse(struct rtattr *tb[], int max, struct rtattr *rta, int len) { memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); while (RTA_OK(rta, len)) { if (rta->rta_type <= max) tb[rta->rta_type] = rta; rta = RTA_NEXT(rta, len); } } void rtnl_parse_nested(struct rtattr *tb[], int max, struct rtattr *rta) { rtnl_parse(tb, max, RTA_DATA(rta), RTA_PAYLOAD(rta)); } int genl_open(struct nl_handle *hnd) { return nl_open(hnd, NETLINK_GENERIC); } int genl_request(struct nl_handle *hnd, int type, int cmd, void *payload, int payload_len, struct nlmsg_entry **dest) { struct { struct nlmsghdr n; struct genlmsghdr g; } req; struct iovec iov[2]; int err; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = sizeof(req) + payload_len; req.n.nlmsg_type = type; req.n.nlmsg_flags = NLM_F_REQUEST; req.g.cmd = cmd; req.g.version = 1; iov[0].iov_base = &req; iov[0].iov_len = sizeof(req); iov[1].iov_base = payload; iov[1].iov_len = payload_len; err = nl_send(hnd, iov, 2); if (err) return err; return nl_recv(hnd, dest, 0); } unsigned int genl_family_id(struct nl_handle *hnd, const char *name) { unsigned int res = 0; struct nlattr *nla; int len; struct nlmsg_entry *dest; void *ptr; len = nla_add_str(NULL, 0, CTRL_ATTR_FAMILY_NAME, name, &ptr); if (!len) return 0; if (genl_request(hnd, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, ptr, len, &dest)) { free(ptr); return 0; } free(ptr); len = dest->h.nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; ptr = (void *)&dest->h + NLMSG_HDRLEN + GENL_HDRLEN; while (len > NLA_HDRLEN) { nla = ptr; if (nla->nla_type == CTRL_ATTR_FAMILY_ID && nla->nla_len >= NLA_HDRLEN + 2) { res = *(uint16_t *)(nla + 1); break; } ptr += NLMSG_ALIGN(nla->nla_len); len -= NLMSG_ALIGN(nla->nla_len); } nlmsg_free(dest); return res; }
static int fill_if_link(struct if_entry *dest, struct nlmsghdr *n) { struct ifinfomsg *ifi = NLMSG_DATA(n); struct rtattr *tb[IFLA_MAX + 1]; struct rtattr *linkinfo[IFLA_INFO_MAX + 1]; int len = n->nlmsg_len; int err; if (n->nlmsg_type != RTM_NEWLINK) return ENOENT; len -= NLMSG_LENGTH(sizeof(*ifi)); if (len < 0) return ENOENT; rtnl_parse(tb, IFLA_MAX, IFLA_RTA(ifi), len); if (tb[IFLA_IFNAME] == NULL) return ENOENT; dest->if_index = ifi->ifi_index; dest->if_name = strdup(RTA_DATA(tb[IFLA_IFNAME])); if (!dest->if_name) return ENOMEM; if (ifi->ifi_flags & IFF_UP) { dest->flags |= IF_UP; if (ifi->ifi_flags & IFF_RUNNING) dest->flags |= IF_HAS_LINK; } if (tb[IFLA_MASTER]) dest->master_index = NLA_GET_U32(tb[IFLA_MASTER]); if (tb[IFLA_LINK]) { dest->link_index = NLA_GET_U32(tb[IFLA_LINK]); if (tb[IFLA_LINK_NETNSID]) dest->link_netnsid = NLA_GET_S32(tb[IFLA_LINK_NETNSID]); } if (tb[IFLA_MTU]) dest->mtu = NLA_GET_U32(tb[IFLA_MTU]); if (tb[IFLA_LINKINFO]) rtnl_parse_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]); if(tb[IFLA_ADDRESS]) if ((err = mac_addr_fill_netlink(&dest->mac_addr, RTA_DATA(tb[IFLA_ADDRESS]), RTA_PAYLOAD(tb[IFLA_ADDRESS])))) return err; if (ifi->ifi_flags & IFF_LOOPBACK) { dest->driver = strdup("loopback"); dest->flags |= IF_LOOPBACK; } else dest->driver = ethtool_driver(dest->if_name); if (!dest->driver) { /* No ethtool ops available, try IFLA_INFO_KIND */ if (tb[IFLA_LINKINFO] && linkinfo[IFLA_INFO_KIND]) dest->driver = strdup(RTA_DATA(linkinfo[IFLA_INFO_KIND])); } if (!dest->driver) { /* Allow the program to continue at least with generic stuff * as there may be interfaces that do not implement any of * the mechanisms for driver detection that we use */ dest->driver = strdup("unknown driver, please report a bug"); } if ((err = if_handler_init(dest))) goto err_driver; if ((err = if_handler_netlink(dest, tb[IFLA_LINKINFO] ? linkinfo : NULL))) if (err != ENOENT) goto err_driver; return 0; err_driver: free(dest->driver); dest->driver = NULL; free(dest->if_name); dest->if_name = NULL; return err; }