Getting routing information from linux/macos

Posted by buluk21 on Wed, 20 Oct 2021 09:39:54 +0200

netlink

What is netlink?

Netlink is a communication mode provided by linux for the kernel and user processes. Although netlink is mainly used for communication between user space and kernel space, it can also be used for communication between two processes in user space.

Generally speaking, there are three ways for user processes to communicate with kernel space: / proc, ioctl and netlink; The first two are unidirectional, and netlink can realize duplex communication.

Each netlink protocol is usually associated with one or a group of kernel services / components, such as NETLINK_ROUTE is used to obtain and set routing and link information and NETLINK_KOBJECT_UEVENT is used by the kernel to send notifications to udev processes in user space; Netlink has the following characteristics:

① Support full duplex and asynchronous communication (of course, synchronous communication is also supported)
② The user space can use the standard BSD socket interface (but netlink does not mask the construction and parsing process of the protocol package. It is recommended to use third-party libraries such as libnl)
③ Use a dedicated kernel API interface in kernel space
④ Support multicast (therefore, support "bus" communication and realize message subscription)
⑤ On the kernel side, it can be used for process context and interrupt context

netlink socket

Create a netlink socket:
socket ( AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) ;
AF_NETLINK represents a protocol family, NETLINK_ROUTE indicates the specific protocols in the protocol family (there are many protocols in the netlink protocol family. If the type of protocol in a protocol family is single, this parameter can be 0);
Generally, we use AF_NETLINK needs to specify a protocol. You can use netlink reserved by the kernel_ Generic (defined in linux/netlink.h) can also be used (that is, define a number that has not been occupied in the kernel. Note: the user-defined protocol does not have to be added to linux/netlink.h, as long as the user state and kernel state codes can find the definition);
When using netlink for data communication, we need to define the packet header ourselves (different from TCP protocol, TCP protocol will automatically fill in the communication header information). The function of packet header will be explained later.

Important data structures

The relationships of several important data structures used in communication are as follows:

struct msghdr

socket message sending and receiving functions generally have these pairs: recv / send, readv / writev, recvfrom / sendto, recvmsg / sendmsg. The first three pairs of functions have their own characteristics and functions, and recvmsg / sendmsg is to include all the functions of the first three pairs, and of course, it has its own special purposes.
The first two members of msghdr are designed to meet the functions of recvfrom / sendto;
Middle two members msg_iov and msg_iovlen is designed to meet the functions of readv / writev;
And the last msg_flags is to meet the function of flag in recv / send;
The remaining msg_control and msg_control len meets the unique functions of recvmsg / sendmsg.

struct sockaddr_nl

sockaddr_nl is the structure used to save the address in netlink. Its function is the same as sockaddr_in is similar, and the comparison is as follows:

struct sockaddr_nl
{
    sa_family_t nl_family; /*This field is always AF_NETLINK */
    unsigned short nl_pad; /* Not used at present, filled with 0*/
    __u32 nl_pid; /* process pid */
    __u32 nl_groups; /* Set this value if the user process wants to join a multicast group */
};
struct nlmsghdr

The message of netlink consists of a message header and a message body. nlmsghdr is the structure for saving the message header:

struct nlmsghdr
{
    __u32 nlmsg_len; /* Length of message including header */
    __u16 nlmsg_type; /* Message content */
    __u16 nlmsg_flags; /* Additional flags */
    __u32 nlmsg_seq; /* Sequence number */
    __u32 nlmsg_pid; /* Sending process PID */
};
/*
* nlmsg_type The following four types of messages are exemplified:
* NLMSG_NOOP Empty message
* NLMSG_ERROR The message contains an error
* NLMSG_DONE Multiple messages are returned through netlink, and the last message is NLMSG_DONE
* NLMSG_OVERRUN Extended use
*/ 

Obtain kernel routing table information through Netlink under linux system

Via NETLINK_ROUTE protocol requests routing table information from the kernel. The business of this code is through get_ The gateway function inputs the network card name and returns the gateway address. In the parseRoutes function, the complete routing table information of the system can be parsed.

#define RECVMSGSIZE 8192 /* recv buf size */
struct route_info
{
        u_int dstAddr;
        u_int srcAddr;
        u_int gateWay;
        char ifName[IF_NAMESIZE];
};
int readNlSock(int sockFd, char *bufPtr, int seqNum, int pId)
{
        struct nlmsghdr *nlHdr;
        int readLen = 0, msgLen = 0;
        do
        {
                if((readLen = recv(sockFd, bufPtr, RECVMSGSIZE - msgLen, 0)) < 0)
                {
                        perror("SOCK READ: ");
                        return -1;
                }

                nlHdr = (struct nlmsghdr *)bufPtr;
                if((NLMSG_OK(nlHdr, readLen) == 0) || (nlHdr->nlmsg_type == NLMSG_ERROR))
                {
                        perror("Error in recieved packet");
                        return -1;
                }
                if(nlHdr->nlmsg_type == NLMSG_DONE)
                {
                        break;
                }
                else
                {
                        bufPtr += readLen;
                        msgLen += readLen;
                }

                if((nlHdr->nlmsg_flags & NLM_F_MULTI) == 0)
                {
                        break;
                }

        }while((nlHdr->nlmsg_seq != seqNum) || (nlHdr->nlmsg_pid != pId));
        return msgLen;
}
char* parseRoutes(struct nlmsghdr *nlHdr, struct route_info *rtInfo, const char *inter_name , const char * ip_addr)
{
        struct rtmsg *rtMsg;
        struct rtattr *rtAttr;
        int rtLen;
        char *tempBuf = NULL;
        struct in_addr gate;
        int is_find = 0;
        rtMsg = (struct rtmsg *)NLMSG_DATA(nlHdr);
        if((rtMsg->rtm_family != AF_INET) || (rtMsg->rtm_table != RT_TABLE_MAIN))
        {
                return NULL;
        }

        rtAttr = (struct rtattr *)RTM_RTA(rtMsg);
        rtLen = RTM_PAYLOAD(nlHdr);
        for(;RTA_OK(rtAttr,rtLen);rtAttr = RTA_NEXT(rtAttr,rtLen))
        {
                switch(rtAttr->rta_type)
                {
                        case RTA_OIF:
                                if_indextoname(*(int *)RTA_DATA(rtAttr), rtInfo->ifName);
                                break;
                        case RTA_GATEWAY:
                                rtInfo->gateWay = *(u_int *)RTA_DATA(rtAttr);
                                break;
                        case RTA_PREFSRC:
                                rtInfo->srcAddr = *(u_int *)RTA_DATA(rtAttr);
                                break;
                        case RTA_DST:
                                rtInfo->dstAddr = *(u_int *)RTA_DATA(rtAttr);
                                break;
                }
        //      printf("inter_name : %s\n", rtInfo->ifName);
                if(memcmp(rtInfo->ifName, inter_name, strlen(inter_name)) == 0)
                {
                        is_find = 1;
                        break;
                }
        }
        if(is_find)
        {
                char *gateway = NULL; /* here malloc return must be free */
                gate.s_addr = rtInfo->gateWay;
                char *gateway_tmp = (char*)inet_ntoa(gate);
                int len = strlen(gateway_tmp);
                gateway = (char*)malloc(len + 1);
                memcpy(gateway, gateway_tmp, len);
                gateway[len] = 0;
                /*
                printf("oif:%s\n",rtInfo->ifName);
                gate.s_addr = rtInfo->gateWay;
                sprintf(gateway, (char *)inet_ntoa(gate));
                printf("gw%s\n",gateway);
                gate.s_addr = rtInfo->srcAddr;
                printf("src:%s\n",(char *)inet_ntoa(gate));
                gate.s_addr = rtInfo->dstAddr;
                printf("dst:%s\n",(char *)inet_ntoa(gate));
                */
                return gateway;
        }
        return NULL;
}
//if the return value is not null and need free
char *get_gateway(const char *net_interface_name, const char * ip_addr)
{
        struct nlmsghdr *nlMsg;
        struct rtmsg *rtMsg;
        struct route_info *rtInfo;
        char msgBuf[RECVMSGSIZE];
        int sock, len, msgSeq = 0;

        if((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
        {
                perror("Socket Creation: ");
                return NULL;
        }
        memset(msgBuf, 0, RECVMSGSIZE);
        nlMsg = (struct nlmsghdr *)msgBuf;
        rtMsg = (struct rtmsg *)NLMSG_DATA(nlMsg);
        nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); // Length of message.
        nlMsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
        nlMsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
        nlMsg->nlmsg_seq = msgSeq++; // Sequence of the message packet.
        nlMsg->nlmsg_pid = getpid(); // PID of process sending the request.
        if(send(sock, nlMsg, nlMsg->nlmsg_len, 0) < 0)
        {
                //printf("Write To Socket Failed...\n");
                return NULL;
        }
        if((len = readNlSock(sock, msgBuf, msgSeq, getpid())) < 0)
        {
                //printf("Read From Socket Failed...\n");
                return NULL;
        }
        rtInfo = (struct route_info *)malloc(sizeof(struct route_info));
        char *gateway = NULL;
        for(;NLMSG_OK(nlMsg,len);nlMsg = NLMSG_NEXT(nlMsg,len))
        {
                memset(rtInfo, 0, sizeof(struct route_info));
                if( (gateway = parseRoutes(nlMsg, rtInfo,net_interface_name, ip_addr)) != NULL)
                        break;
        }
        free(rtInfo);
        close(sock);
        return gateway;
}

Obtaining routing table information by macos

Note: macos related source code can be obtained from www.opensource.apple.com
The following code obtains the routing table through the routepr (void) function in route.c by modifying the route.c part in the source code of macos netstat, and through NP_ Rtentry (struct rt_msghdr2 * RTM) prints relevant information;
The business of this code is through get_ The gateway function inputs the network card name and returns the gateway address:

#ifndef ROUNDUP
#define ROUNDUP(a) \
       ((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t))
#endif 
typedef union 
{
	uint32_t dummy;		/* Helps align structure. */
	struct	sockaddr u_sa;
	u_short	u_data[128];
} sa_u;
void get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
        int i;
        for (i = 0; i < RTAX_MAX; i++) 
		{
            if (addrs & (1 << i)) 
			{
                rti_info[i] = sa;
				sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa);
			} 
			else 
			{
                 rti_info[i] = NULL;
			}
		}
}
char *np_rtentry(struct rt_msghdr2 *rtm, const char* inte_name)
{
	struct sockaddr *sa = (struct sockaddr *)(rtm + 1);
	struct sockaddr *rti_info[RTAX_MAX];
	u_short lastindex = 0xffff;
	static char ifname[IFNAMSIZ + 1];
	get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
	//RTA_DST destination addr
	//get name 
	if (rtm->rtm_index != lastindex) 
	{
		if_indextoname(rtm->rtm_index, ifname);
		//printf("host name : %s\n", ifname);
		lastindex = rtm->rtm_index;
	}
	if( memcmp(ifname, inte_name, strlen(inte_name)) == 0)
	{
		//get gateway
		struct sockaddr_in *sin = (struct sockaddr_in *)rti_info[RTAX_GATEWAY];
		char * gateway = (char* ) malloc(MAXHOSTNAMELEN);  // must be free without
		inet_ntop(AF_INET, &sin->sin_addr.s_addr, gateway, MAXHOSTNAMELEN - 1);
		//printf("gateway : %s\n", gateway);	
		return gateway;
	}
	return NULL;
}
//return value must be free 
char *get_gateway(const char* inter_name, const char* ip_addr)
{
	size_t needed;
	int mib[6];
	char *buf, *next, *lim;
	struct rt_msghdr2 *rtm;
	/,printf("Routing tables\n");
	mib[0] = CTL_NET;
	mib[1] = PF_ROUTE;
	mib[2] = 0;
	mib[3] = 0;
	mib[4] = NET_RT_DUMP2;
	mib[5] = 0;
	if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) 
	{
		err(1, "sysctl: net.route.0.0.dump estimate");
	}
	if ((buf = (char*)malloc(needed)) == 0) 
	{
		err(2, "malloc(%lu)", (unsigned long)needed);
	}
	if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) 
	{
		err(1, "sysctl: net.route.0.0.dump");
	}
	char * getway = NULL;
	lim  = buf + needed;
	for (next = buf; next < lim; next += rtm->rtm_msglen) 
	{
		rtm = (struct rt_msghdr2 *)next;
		if((getway = np_rtentry(rtm, inter_name)) != NULL)
		{
			break;
		}
	}
	free(buf);
	return getway;
}

Topics: Operating System