| #include <u.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <asm/types.h> |
| #include <net/if_arp.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <net/if.h> |
| #include <libc.h> |
| #include <ip.h> |
| |
| /* |
| * Use netlink sockets to find interfaces. |
| * Thanks to Erik Quanstrom. |
| */ |
| static int |
| netlinkrequest(int fd, int type, int (*fn)(struct nlmsghdr *h, Ipifc**, int), |
| Ipifc **ifc, int index) |
| { |
| char buf[1024]; |
| int n; |
| struct sockaddr_nl nl; |
| struct { |
| struct nlmsghdr nlh; |
| struct rtgenmsg g; |
| } req; |
| struct nlmsghdr *h; |
| |
| memset(&nl, 0, sizeof nl); |
| nl.nl_family = AF_NETLINK; |
| |
| memset(&req, 0, sizeof req); |
| req.nlh.nlmsg_len = sizeof req; |
| req.nlh.nlmsg_type = type; |
| req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; |
| req.nlh.nlmsg_pid = 0; |
| req.nlh.nlmsg_seq = 1; |
| req.g.rtgen_family = AF_NETLINK; |
| |
| if(sendto(fd, (void*)&req, sizeof req, 0, (struct sockaddr*)&nl, sizeof nl) < 0) |
| return -1; |
| |
| while((n=read(fd, buf, sizeof buf)) > 0){ |
| for(h=(struct nlmsghdr*)buf; NLMSG_OK(h, n); h = NLMSG_NEXT(h, n)){ |
| if(h->nlmsg_type == NLMSG_DONE) |
| return 0; |
| if(h->nlmsg_type == NLMSG_ERROR){ |
| werrstr("netlink error"); |
| return -1; |
| } |
| if(fn(h, ifc, index) < 0) |
| return -1; |
| } |
| } |
| werrstr("netlink error"); |
| return -1; |
| } |
| |
| static int |
| devsocket(void) |
| { |
| /* we couldn't care less which one; just want to talk to the kernel! */ |
| static int dumb[3] = { AF_INET, AF_PACKET, AF_INET6 }; |
| int i, fd; |
| |
| for(i=0; i<nelem(dumb); i++) |
| if((fd = socket(dumb[i], SOCK_DGRAM, 0)) >= 0) |
| return fd; |
| return -1; |
| } |
| |
| static int |
| parsertattr(struct rtattr **dst, int ndst, struct nlmsghdr *h, int type, int skip) |
| { |
| struct rtattr *src; |
| int len; |
| |
| len = h->nlmsg_len - NLMSG_LENGTH(skip); |
| if(len < 0 || h->nlmsg_type != type){ |
| werrstr("attrs too short"); |
| return -1; |
| } |
| src = (struct rtattr*)((char*)NLMSG_DATA(h) + NLMSG_ALIGN(skip)); |
| |
| memset(dst, 0, ndst*sizeof dst[0]); |
| for(; RTA_OK(src, len); src = RTA_NEXT(src, len)) |
| if(src->rta_type < ndst) |
| dst[src->rta_type] = src; |
| return 0; |
| } |
| |
| static void |
| rta2ip(int af, uchar *ip, struct rtattr *rta) |
| { |
| memset(ip, 0, IPaddrlen); |
| |
| switch(af){ |
| case AF_INET: |
| memmove(ip, v4prefix, IPv4off); |
| memmove(ip+IPv4off, RTA_DATA(rta), IPv4addrlen); |
| break; |
| case AF_INET6: |
| memmove(ip, RTA_DATA(rta), IPaddrlen); |
| break; |
| } |
| } |
| |
| static int |
| getlink(struct nlmsghdr *h, Ipifc **ipifclist, int index) |
| { |
| char *p; |
| int fd; |
| struct rtattr *attr[IFLA_MAX+1]; |
| struct ifinfomsg *ifi; |
| Ipifc *ifc; |
| |
| ifi = (struct ifinfomsg*)NLMSG_DATA(h); |
| if(index >= 0 && ifi->ifi_index != index) |
| return 0; |
| |
| ifc = mallocz(sizeof *ifc, 1); |
| if(ifc == nil) |
| return -1; |
| ifc->index = ifi->ifi_index; |
| |
| while(*ipifclist) |
| ipifclist = &(*ipifclist)->next; |
| *ipifclist = ifc; |
| |
| if(parsertattr(attr, nelem(attr), h, RTM_NEWLINK, sizeof(struct ifinfomsg)) < 0) |
| return -1; |
| |
| if(attr[IFLA_IFNAME]) |
| p = (char*)RTA_DATA(attr[IFLA_IFNAME]); |
| else |
| p = "nil"; |
| strecpy(ifc->dev, ifc->dev+sizeof ifc->dev, p); |
| |
| if(attr[IFLA_MTU]) |
| ifc->mtu = *(int*)RTA_DATA(attr[IFLA_MTU]); |
| |
| /* |
| * Does not work on old Linux systems, |
| * and not really necessary for my purposes. |
| * Uncomment if you want it bad. |
| * |
| if(attr[IFLA_STATS]){ |
| struct rtnl_link_stats *s; |
| |
| s = RTA_DATA(attr[IFLA_STATS]); |
| ifc->pktin = s->rx_packets; |
| ifc->pktout = s->tx_packets; |
| ifc->errin = s->rx_errors; |
| ifc->errout = s->tx_errors; |
| } |
| * |
| */ |
| |
| if((fd = devsocket()) > 0){ |
| struct ifreq ifr; |
| |
| memset(&ifr, 0, sizeof ifr); |
| strncpy(ifr.ifr_name, p, IFNAMSIZ); |
| ifr.ifr_mtu = 0; |
| if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) |
| ifc->rp.linkmtu = ifr.ifr_mtu; |
| |
| memset(&ifr, 0, sizeof ifr); |
| strncpy(ifr.ifr_name, p, IFNAMSIZ); |
| if(ioctl(fd, SIOCGIFHWADDR, &ifr) >= 0 |
| && ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER) |
| memmove(ifc->ether, ifr.ifr_hwaddr.sa_data, 6); |
| |
| close(fd); |
| } |
| return 0; |
| } |
| |
| static int |
| getaddr(struct nlmsghdr *h, Ipifc **ipifclist, int index) |
| { |
| int mask; |
| Ipifc *ifc; |
| Iplifc *lifc, **l; |
| struct ifaddrmsg *ifa; |
| struct rtattr *attr[IFA_MAX+1]; |
| |
| USED(index); |
| |
| ifa = (struct ifaddrmsg*)NLMSG_DATA(h); |
| for(ifc=*ipifclist; ifc; ifc=ifc->next) |
| if(ifc->index == ifa->ifa_index) |
| break; |
| if(ifc == nil) |
| return 0; |
| if(parsertattr(attr, nelem(attr), h, RTM_NEWADDR, sizeof(struct ifaddrmsg)) < 0) |
| return -1; |
| |
| lifc = mallocz(sizeof *lifc, 1); |
| if(lifc == nil) |
| return -1; |
| for(l=&ifc->lifc; *l; l=&(*l)->next) |
| ; |
| *l = lifc; |
| |
| if(attr[IFA_ADDRESS] == nil) |
| attr[IFA_ADDRESS] = attr[IFA_LOCAL]; |
| if(attr[IFA_ADDRESS] == nil) |
| return 0; |
| |
| rta2ip(ifa->ifa_family, lifc->ip, attr[IFA_ADDRESS]); |
| |
| mask = ifa->ifa_prefixlen/8; |
| if(ifa->ifa_family == AF_INET) |
| mask += IPv4off; |
| memset(lifc->mask, 0xFF, mask); |
| memmove(lifc->net, lifc->ip, mask); |
| |
| if(attr[IFA_CACHEINFO]){ |
| struct ifa_cacheinfo *ci; |
| |
| ci = RTA_DATA(attr[IFA_CACHEINFO]); |
| lifc->preflt = ci->ifa_prefered; |
| lifc->validlt = ci->ifa_valid; |
| } |
| return 0; |
| } |
| |
| Ipifc* |
| readipifc(char *net, Ipifc *ifc, int index) |
| { |
| int fd; |
| |
| USED(net); |
| freeipifc(ifc); |
| ifc = nil; |
| |
| fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); |
| if(fd < 0) |
| return nil; |
| ifc = nil; |
| if(netlinkrequest(fd, RTM_GETLINK, getlink, &ifc, index) < 0 |
| || netlinkrequest(fd, RTM_GETADDR, getaddr, &ifc, index) < 0){ |
| close(fd); |
| freeipifc(ifc); |
| return nil; |
| } |
| close(fd); |
| return ifc; |
| } |
| |