Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the way to get default gateway on macOS #1379

Open
wants to merge 16 commits into
base: dev
Choose a base branch
from

Conversation

zhengfeihe
Copy link

@zhengfeihe zhengfeihe commented May 2, 2024

Implement thoughts from #1355

This PR follows the source code of route on a Mac to use a raw socket for retrieving default gateway information from the routing table, thus eliminating the need for system calls.

Although it might be desirable to reuse code logic behind route -n on both Mac and FreeBSD to get the gateway info of the device, the implementation is not the same on two different OS systems. Therefore, we might want to separate macOS and FreeBSD into two PRs, where the FreeBSD would be the next one.

@zhengfeihe zhengfeihe requested a review from seladb as a code owner May 2, 2024 00:03
@zhengfeihe zhengfeihe marked this pull request as draft May 2, 2024 00:04
Copy link
Collaborator

@tigercosmos tigercosmos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @zhengfeihe, it's a good start.
Could you address all comments on the PR, add a few remarks for the code, and add some testing for the PR?

Pcap++/header/PcapLiveDevice.h Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
@tigercosmos
Copy link
Collaborator

@zhengfeihe next time don't resolve the issue yourself, so other people can easily track the issues. Also, could you let the tests pass?

@zhengfeihe zhengfeihe closed this May 5, 2024
@zhengfeihe zhengfeihe reopened this May 5, 2024
@zhengfeihe
Copy link
Author

@zhengfeihe next time don't resolve the issue yourself, so other people can easily track the issues. Also, could you let the tests pass?

Thanks for the reminder. I will watch out next time.

//route message struct for communication in BSD / APPLE device
struct BSDRoutingMessage{
struct rt_msghdr header;
char messageSpace[512];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if 512 is a proper number?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number used is consistent with the one in the macOS source code. I believe it would be safer to retain it as is.

@tigercosmos
Copy link
Collaborator

@zhengfeihe As we previously discussed, it appears that FreeBSD cannot easily share its implementation with macOS. Therefore, let's separate the FreeBSD implementation from macOS, allowing the macOS implementation to be merged first.

@egecetin egecetin added this to the Augest 2024 Release milestone Jun 5, 2024
@zhengfeihe
Copy link
Author

zhengfeihe commented Jun 5, 2024

@zhengfeihe As we previously discussed, it appears that FreeBSD cannot easily share its implementation with macOS. Therefore, let's separate the FreeBSD implementation from macOS, allowing the macOS implementation to be merged first.

Sorry for the delay. I have changed based on the request.

@tigercosmos
Copy link
Collaborator

@zhengfeihe could you check if the current test already covers your change? if not please add the corresponding tests. Also, please modify the description of the PR to explain why you separated the implementation for macOS/FreeBSD.

Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
@zhengfeihe zhengfeihe force-pushed the feature/improve_getting_default_gateway_method_in_macos branch from 9800dfa to 6d90884 Compare June 8, 2024 02:41
@zhengfeihe
Copy link
Author

@zhengfeihe could you check if the current test already covers your change? if not please add the corresponding tests. Also, please modify the description of the PR to explain why you separated the implementation for macOS/FreeBSD.

I think the test TestPcapLiveDeviceList already check the getDefaultGateway across different platforms, we don't need to add new tests.

@zhengfeihe zhengfeihe force-pushed the feature/improve_getting_default_gateway_method_in_macos branch from 6d90884 to 0c9db45 Compare June 8, 2024 04:32
@tigercosmos tigercosmos marked this pull request as ready for review June 8, 2024 06:28
@tigercosmos
Copy link
Collaborator

tigercosmos commented Jun 8, 2024

@seladb I think the PR is ready. Could you take a look?

Copy link
Collaborator

@Dimi1010 Dimi1010 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the file indentation to use tabs. Other then that, LGTM.

Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
Pcap++/src/PcapLiveDevice.cpp Outdated Show resolved Hide resolved
@seladb
Copy link
Owner

seladb commented Jun 11, 2024

This PR is quite complex, I need to go over it carefully...
@zhengfeihe can you share a link to the source code of route and some references on how its code works?

@zhengfeihe
Copy link
Author

zhengfeihe commented Jun 11, 2024

This PR is quite complex, I need to go over it carefully... @zhengfeihe can you share a link to the source code of route and some references on how its code works?

Here's the source code link : https://github.com/apple-oss-distributions/network_cmds/blob/main/route.tproj/route.c.

I cannot find too much valuable info when I search online, so I implement the pr mainly based on running the source code on my own Mac. This is the test code that can work on my machine, maybe it will help

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if_dl.h>



#define ROUNDUP(a) \
	((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t))
#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
#define C(x) ((unsigned char)((x) & 0xff))

int main() {
    int sockfd;

    struct bsd_route_message{
        struct	rt_msghdr m_rtm;
        char	m_space[512];
    } m_rtmsg;

    // Create the socket
    sockfd = socket(PF_ROUTE, SOCK_RAW, 0);
    if (sockfd < 0) {
        perror("socket");
        return EXIT_FAILURE;
    }


    bzero((char *)&m_rtmsg, sizeof(m_rtmsg));

    char* spacePtr = m_rtmsg.m_space;

    bzero((char *)&m_rtmsg, sizeof(m_rtmsg));
    m_rtmsg.m_rtm.rtm_msglen = sizeof(struct rt_msghdr);
    m_rtmsg.m_rtm.rtm_version = RTM_VERSION;
    m_rtmsg.m_rtm.rtm_type = RTM_GET;
    m_rtmsg.m_rtm.rtm_addrs = RTA_DST | RTA_NETMASK | RTA_IFP;
    m_rtmsg.m_rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;

    struct	sockaddr_in so_dst = {0} , so_mask = {0};
    struct	sockaddr_dl so_ifp = {0};

    size_t len = sizeof(sockaddr_in);
    bcopy(reinterpret_cast<char*>(&so_dst), spacePtr, len);
    spacePtr += len;
    bcopy(reinterpret_cast<char*>(&so_mask), spacePtr, len);
    spacePtr += len;
    m_rtmsg.m_rtm.rtm_msglen += 2*len ;
    len = sizeof(sockaddr_dl);
    spacePtr += len;
    bcopy(reinterpret_cast<char*>(&so_ifp), spacePtr, len);
    m_rtmsg.m_rtm.rtm_msglen += len ;

    if (write(sockfd, reinterpret_cast<char*>(&m_rtmsg), m_rtmsg.m_rtm.rtm_msglen) < 0) {
    //    PCPP_LOG_ERROR("Error retrieving default gateway address: couldn't write into the routing socket");
        return 1;
    }

    // Read the response from the route socket
    if (read(sockfd,reinterpret_cast<char*>(&m_rtmsg), sizeof(m_rtmsg)) < 0) {
     //   PCPP_LOG_ERROR("Error retrieving default gateway address: couldn't read from the routing socket");
        return 1;
    }

    struct sockaddr_in  *gate = nullptr;
    struct sockaddr_dl *ifp = nullptr;
    struct sockaddr *sa = nullptr;
    spacePtr = (reinterpret_cast<char*>(&m_rtmsg.m_rtm+1));
    for (int i = 1; i > 0; i <<= 1)
    {
        if (i & m_rtmsg.m_rtm.rtm_addrs)
        {
            sa =  reinterpret_cast<sockaddr *>(spacePtr);
            switch (i)
            {
                case RTA_GATEWAY:
                    gate = (sockaddr_in* )sa;
                    break;
                case RTA_IFP:
                    if (sa->sa_family == AF_LINK &&
                        ((sockaddr_dl *)sa)->sdl_nlen)
                        ifp = (sockaddr_dl *)sa;
                    break;
            }
            // Make sure the increment is the nearest multiple of the size of uint32_t
            spacePtr += sa->sa_len > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t);
        }
    }

    printf("%d", gate->sin_addr.s_addr);
    gate->sin_addr.s_addr = ntohl(gate->sin_addr.s_addr);
    printf("%d", gate->sin_addr.s_addr);
    printf("\n%u.%u.%u.%u",C(gate->sin_addr.s_addr>> 24), C(gate->sin_addr.s_addr>> 16), C(gate->sin_addr.s_addr>> 8), C(gate->sin_addr.s_addr));
    // Read the response from the route socket

    close(sockfd);
    
    return EXIT_SUCCESS;
}

@seladb
Copy link
Owner

seladb commented Jun 12, 2024

I cannot find too much valuable info when I search online, so I implement the pr mainly based on running the source code on my own Mac. This is the test code that can work on my machine, maybe it will help

This is great, thank you @zhengfeihe ! Let me try to run this code on my Mac to better understand how it works

Comment on lines +1092 to +1099
size_t len = sizeof(sockaddr_in);
bcopy(reinterpret_cast<char*>(&so_dst), spacePtr, len);
spacePtr += len;
bcopy(reinterpret_cast<char*>(&so_mask), spacePtr, len);
spacePtr += len;
routingMessage.header.rtm_msglen += 2*len ;
len = sizeof(sockaddr_dl);
bcopy(reinterpret_cast<char*>(&so_ifp), spacePtr, len);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Both so_dst, so_ifp and so_mask are all zeros, why do we need to copy them to spacePtr?
  • We should probably use memcpy instead of bcopy

I think we can make this code much simpler by just calculating the length:

routingMessage.header.rtm_msglen += (sizeof(sockaddr_in) + sizeof(sockaddr_dl));

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly follow the code in the route.c source code.I think they want to ensure that in the routing message data space, these structures are laid out in order, so that they can be read when we receive the returning routing message.

I will try the new code see if it works well.

struct sockaddr_dl *ifp = nullptr;
struct sockaddr *sa = nullptr;
spacePtr = (reinterpret_cast<char*>(&routingMessage.header+1));
for (int i = 1; i != 0; i <<= 1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of going over all integers, maybe we can check which bits are set in routingMessage.header.rtm_addrs, something like this:

auto rtmAddrs = routingMessage.header.rtm_addrs;
int index = 0;

while (rtmAddrs)
{ 
    if (rtmAddrs  & 1) {
        // check if index is RTA_GATEWAY or RTA_IFP
    }
    index++;
    rtmAddrs >>= 1; 
}

Comment on lines +1128 to +1132
case RTA_IFP:
if (sa->sa_family == AF_LINK &&
reinterpret_cast<sockaddr_dl*>(sa)->sdl_nlen)
ifp = reinterpret_cast<sockaddr_dl *>(sa);
break;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need RTA_IFP in addition to RTA_GATEWAY?

I'm not even sure what is IFP 🙂

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RTA_IFP is used in route messages to specify the interface through which packets should be sent. By print ifp, we can get the interface info, eg add this line

(void)printf("  interface: %.*s\n",  ifp->sdl_nlen, ifp->sdl_data);

we can get

  interface: en0

We can still get the default gateway info if we remove RTA_IFP, but I think maybe it's ok to remain the check of the interface?

Comment on lines +1135 to +1136
spacePtr += sa->sa_len > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can extract this to a separate function in this .cpp file to make the code more readable?

int closestMultipleOf(int multiple, int num)
{
    return ((num + multiple - 1) / multiple) * multiple;
}

...

spacePtr += sa->sa_len > 0 ? closestMultipleOf(sizeof(uint32_t), sa->sa_len) : sizeof(uint32_t);

spacePtr += sa->sa_len > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t);
}
}
if(gateAddr == nullptr || ifp == nullptr)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why ifp == nullptr means that the gateway couldn't be extracted?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when ifp == nullptr, it means there's no valid interface info to be extracted through which we send/read packets, so this routing of getting default gateway maybe invalid as well.

@tigercosmos
Copy link
Collaborator

@zhengfeihe could you modify the remaining parts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants