-
Notifications
You must be signed in to change notification settings - Fork 639
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
base: dev
Are you sure you want to change the base?
Improve the way to get default gateway on macOS #1379
Conversation
There was a problem hiding this 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?
@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]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@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. |
@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. |
9800dfa
to
6d90884
Compare
I think the test |
6d90884
to
0c9db45
Compare
@seladb I think the PR is ready. Could you take a look? |
There was a problem hiding this 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.
This PR is quite complex, I need to go over it carefully... |
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;
} |
This is great, thank you @zhengfeihe ! Let me try to run this code on my Mac to better understand how it works |
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); |
There was a problem hiding this comment.
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
andso_mask
are all zeros, why do we need to copy them tospacePtr
? - We should probably use
memcpy
instead ofbcopy
I think we can make this code much simpler by just calculating the length:
routingMessage.header.rtm_msglen += (sizeof(sockaddr_in) + sizeof(sockaddr_dl));
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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;
}
case RTA_IFP: | ||
if (sa->sa_family == AF_LINK && | ||
reinterpret_cast<sockaddr_dl*>(sa)->sdl_nlen) | ||
ifp = reinterpret_cast<sockaddr_dl *>(sa); | ||
break; |
There was a problem hiding this comment.
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 🙂
There was a problem hiding this comment.
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?
spacePtr += sa->sa_len > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t); | ||
} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@zhengfeihe could you modify the remaining parts? |
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.