C语言 在 ANSI C 中获取用于给定 IP 的网关

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3288065/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 05:56:56  来源:igfitidea点击:

Getting gateway to use for a given ip in ANSI C

cnetwork-programmingroutessockets

提问by inquam

I have looked around like crazy but don't get a real answer. I got one example, but that depended on the individuals own library so not much good.

我疯狂地环顾四周,但没有得到真正的答案。我有一个例子,但这取决于个人自己的图书馆,所以不太好。

At first I wanted to get the default gateway of an interface, but since different IP's could be routed differently I quickly understood that what I want it get the gateway to use for a given destination IP by using an AF_ROUTEsocket and the rtm_type RTM_GET. Does anyone have an example where I actually end up with a string containing the gateways IP (or mac address)? The gateway entry seem to be in hex but also encoded in /proc/net/route, where I guess the AF_ROUTEsocket get's it info from (but via the kernel I guess).

起初我想获得一个接口的默认网关,但由于不同的 IP 可以以不同的方式路由,我很快就明白我希望它通过使用AF_ROUTE套接字和rtm_type RTM_GET. 有没有人有一个例子,我实际上最终得到了一个包含网关 IP(或 mac 地址)的字符串?网关条目似乎是十六进制的,但也在 /proc/net/route 中编码,我猜AF_ROUTE套接字从那里获取它的信息(但我猜是通过内核)。

Thanx in advance

提前谢谢

and p.s. I just started using stack overflow and I must say, all of you guys are great! Fast replies and good ones! You are my new best friends ;)

和 ps 我刚开始使用堆栈溢出,我必须说,你们所有人都很棒!快速回复和好评!你是我最好的新朋友 ;)

回答by nos

This is OS specific, there's no unified(or ANSI C) API for this.

这是特定于操作系统的,没有统一的(或 ANSI C)API。

Assuming Linux, the best way is to just parse /proc/net/route , look for the entry where Destination is 00000000 , the default gateway is in the Gateway column , where you can read the hex representation of the gateway IP address (in big endian , I believe)

假设Linux,最好的方法是解析 /proc/net/route ,查找 Destination 为 00000000 的条目,默认网关在 Gateway 列中,您可以在其中读取网关 IP 地址的十六进制表示(在大端序,我相信)

If you want to do this via more specific API calls, you'll have to go through quite some hoops, here's an example program:

如果你想通过更具体的 API 调用来做到这一点,你将不得不经历一些麻烦,这是一个示例程序:

#include <netinet/in.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>


#define BUFSIZE 8192
char gateway[255];

struct route_info {
    struct in_addr dstAddr;
    struct in_addr srcAddr;
    struct in_addr gateWay;
    char ifName[IF_NAMESIZE];
};

int readNlSock(int sockFd, char *bufPtr, int seqNum, int pId)
{
    struct nlmsghdr *nlHdr;
    int readLen = 0, msgLen = 0;

 do {
    /* Recieve response from the kernel */
        if ((readLen = recv(sockFd, bufPtr, BUFSIZE - msgLen, 0)) < 0) {
            perror("SOCK READ: ");
            return -1;
        }

        nlHdr = (struct nlmsghdr *) bufPtr;

    /* Check if the header is valid */
        if ((NLMSG_OK(nlHdr, readLen) == 0)
            || (nlHdr->nlmsg_type == NLMSG_ERROR)) {
            perror("Error in recieved packet");
            return -1;
        }

    /* Check if the its the last message */
        if (nlHdr->nlmsg_type == NLMSG_DONE) {
            break;
        } else {
    /* Else move the pointer to buffer appropriately */
            bufPtr += readLen;
            msgLen += readLen;
        }

    /* Check if its a multi part message */
        if ((nlHdr->nlmsg_flags & NLM_F_MULTI) == 0) {
           /* return if its not */
            break;
        }
    } while ((nlHdr->nlmsg_seq != seqNum) || (nlHdr->nlmsg_pid != pId));
    return msgLen;
}
/* For printing the routes. */
void printRoute(struct route_info *rtInfo)
{
    char tempBuf[512];

/* Print Destination address */
    if (rtInfo->dstAddr.s_addr != 0)
        strcpy(tempBuf,  inet_ntoa(rtInfo->dstAddr));
    else
        sprintf(tempBuf, "*.*.*.*\t");
    fprintf(stdout, "%s\t", tempBuf);

/* Print Gateway address */
    if (rtInfo->gateWay.s_addr != 0)
        strcpy(tempBuf, (char *) inet_ntoa(rtInfo->gateWay));
    else
        sprintf(tempBuf, "*.*.*.*\t");
    fprintf(stdout, "%s\t", tempBuf);

    /* Print Interface Name*/
    fprintf(stdout, "%s\t", rtInfo->ifName);

    /* Print Source address */
    if (rtInfo->srcAddr.s_addr != 0)
        strcpy(tempBuf, inet_ntoa(rtInfo->srcAddr));
    else
        sprintf(tempBuf, "*.*.*.*\t");
    fprintf(stdout, "%s\n", tempBuf);
}

void printGateway()
{
    printf("%s\n", gateway);
}
/* For parsing the route info returned */
void parseRoutes(struct nlmsghdr *nlHdr, struct route_info *rtInfo)
{
    struct rtmsg *rtMsg;
    struct rtattr *rtAttr;
    int rtLen;

    rtMsg = (struct rtmsg *) NLMSG_DATA(nlHdr);

/* If the route is not for AF_INET or does not belong to main routing table
then return. */
    if ((rtMsg->rtm_family != AF_INET) || (rtMsg->rtm_table != RT_TABLE_MAIN))
        return;

/* get the rtattr field */
    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.s_addr= *(u_int *) RTA_DATA(rtAttr);
            break;
        case RTA_PREFSRC:
            rtInfo->srcAddr.s_addr= *(u_int *) RTA_DATA(rtAttr);
            break;
        case RTA_DST:
            rtInfo->dstAddr .s_addr= *(u_int *) RTA_DATA(rtAttr);
            break;
        }
    }
    //printf("%s\n", inet_ntoa(rtInfo->dstAddr));

    if (rtInfo->dstAddr.s_addr == 0)
        sprintf(gateway, (char *) inet_ntoa(rtInfo->gateWay));
    //printRoute(rtInfo);

    return;
}


int main()
{
    struct nlmsghdr *nlMsg;
    struct rtmsg *rtMsg;
    struct route_info *rtInfo;
    char msgBuf[BUFSIZE];

    int sock, len, msgSeq = 0;

/* Create Socket */
    if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
        perror("Socket Creation: ");

    memset(msgBuf, 0, BUFSIZE);

/* point the header and the msg structure pointers into the buffer */
    nlMsg = (struct nlmsghdr *) msgBuf;
    rtMsg = (struct rtmsg *) NLMSG_DATA(nlMsg);

/* Fill in the nlmsg header*/
    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.

/* Send the request */
    if (send(sock, nlMsg, nlMsg->nlmsg_len, 0) < 0) {
        printf("Write To Socket Failed...\n");
        return -1;
    }

/* Read the response */
    if ((len = readNlSock(sock, msgBuf, msgSeq, getpid())) < 0) {
        printf("Read From Socket Failed...\n");
    return -1;
    }
/* Parse and print the response */
    rtInfo = (struct route_info *) malloc(sizeof(struct route_info));
//fprintf(stdout, "Destination\tGateway\tInterface\tSource\n");
    for (; NLMSG_OK(nlMsg, len); nlMsg = NLMSG_NEXT(nlMsg, len)) {
        memset(rtInfo, 0, sizeof(struct route_info));
        parseRoutes(nlMsg, rtInfo);
    }
    free(rtInfo);
    close(sock);

    printGateway();
    return 0;
}

回答by Reza

Maybe this is very old question but I had same problem and I can't find better result. Finally I solved my problem with these code that it has a few changes. So I decide to share it.

也许这是一个很老的问题,但我遇到了同样的问题,我找不到更好的结果。最后我用这些代码解决了我的问题,它有一些变化。所以我决定分享它。

char* GetGatewayForInterface(const char* interface) 
{
    char* gateway = NULL;

    char cmd [1000] = {0x0};
    sprintf(cmd,"route -n | grep %s  | grep 'UG[ \t]' | awk '{print }'", interface);
    FILE* fp = popen(cmd, "r");
    char line[256]={0x0};

    if(fgets(line, sizeof(line), fp) != NULL)
        gateway = string(line);


    pclose(fp);
}

回答by inquam

I decided to go the "quick-and-dirty" way to start with and read out the ip from /proc/net/routeusing netstat -rm.

我决定采用“快速而肮脏”的方式开始,并从/proc/net/route使用netstat -rm.

I thought I'd share my function... Note however that there is some error in it and prehaps you could help me find it and I'll edit this to be without faults. The function take a iface name like eth0and returns the ip of the gateway used by that iface.

我想我会分享我的功能......但是请注意,其中存在一些错误,并且您可以帮助我找到它,我将对其进行编辑以使其没有错误。该函数采用 iface 名称,eth0并返回该 iface 使用的网关的 ip。

char* GetGatewayForInterface(const char* interface) {
  char* gateway = NULL;

  FILE* fp = popen("netstat -rn", "r");
  char line[256]={0x0};

  while(fgets(line, sizeof(line), fp) != NULL)
  {    
    /*
     * Get destination.
     */
    char* destination;
    destination = strndup(line, 15);

    /*
     * Extract iface to compare with the requested one
     * todo: fix for iface names longer than eth0, eth1 etc
     */
    char* iface;
    iface = strndup(line + 73, 4);


    // Find line with the gateway
    if(strcmp("0.0.0.0        ", destination) == 0 && strcmp(iface, interface) == 0) {
        // Extract gateway
        gateway = strndup(line + 16, 15);
    }

    free(destination);
    free(iface);
  }

  pclose(fp);
  return gateway;
}

The problem with this function is that when I leave pclose in there it causes a memory corruption chrash. But it works if I remove the pclose call (but that would not be a good solution beacuse the stream would remain open.. hehe). So if anyone can spot the error I'll edit the function with the correct version. I'm no C guru and gets a bit confused about all the memory fiddling ;)

此函数的问题在于,当我将 pclose 留在那里时,它会导致内存损坏崩溃。但是如果我删除 pclose 调用它会起作用(但这不是一个好的解决方案,因为流将保持打开状态......呵呵)。因此,如果有人能发现错误,我将使用正确的版本编辑该函数。我不是 C 大师,对所有的内存摆弄有点困惑;)