I've been gleaning information about the NETLINK socket which allows us to listen on what is happening in socket land. It seems to very partially work, but I'm wondering whether I missed something.
I'm using C++, although of course all the NETLINK is pure C, so here we have mainly C headers. The following code has three main parts:
Binding
First I create a
socket()
andbind()
it.The
bind()
is kind of magical. When using a bound NETLINK socket, you start receiving events without having to have a polling setup (which is why I'm trying to do this, to avoid polling for when a socket starts listening for connections).I put -1 in the
nl_groups
so that way all events are sent to my socket. But, at this point, I seem to only receive two of them:TCP_ESTABLISHED
andTCP_CLOSE
. The one I really would like to receive is theTCP_LISTEN
and "not listening" (which apparently is not going to be available...)Explicit Request
I tried with an explicit request. I have it in the code below so you can see how I've done it. That request works as expected. I get an explicit reply if the socket exists or an error "No such file or directory" when the socket is closed. Great, except that mechanism means I'd be using a poll (i.e. I need my process to try over and over again on a timer until the socket is visible).
Note: the error when no one is listening is happening because the request is explicit, i.e. it includes the expected IP address and port that I'm interested in.
Response
The next part is a loop, which sits until a response is received. The
recvmsg()
call is blocking in this version, which is why it sits around in this test.If I sent my explicit request (see point 2. above), then, as I mentioned, I get a reply if another process is listening, otherwise I get an error saying it's not listening. The state is clearly set to 10 (
TCP_LISTEN
), so everything works as expected.When listening to all the events (-1 in the bind), the process will go on and receive more data as events happen. However, so far, the only events I've received are 1 and 7 (i.e.
TCP_ESTABLISHED
andTCP_CLOSE
).
I used the following to compile my code:
g++ -Wall -o a test.cpp
Here is my test code with which I can reproduce my current results:
#include <iostream>
#include <linux/netlink.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/sock_diag.h>
#include <linux/inet_diag.h>
int
main(int argc, char ** argv)
{
// socket / bind
int d = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
if(d < 0)
{
std::cerr << "error: could not create RAW socket.\n";
return 1;
}
struct sockaddr_nl addr = {};
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid();
addr.nl_groups = -1;
// You can find these flags in Linux source:
//
// "/usr/src/linux-headers-4.15.0-147/include/net/tcp_states.h
//
// (1 << 7) // TCPF_CLOSE
// | (1 << 8) // TCPF_CLOSE-WAIT
// | (1 << 10) // TCPF_LISTEN
// | (1 << 11) // TCPF_CLOSING
// ;
if(bind(d, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) != 0)
{
perror("bind failure\n");
return 1;
}
// request
struct sockaddr_nl nladdr = {};
nladdr.nl_family = AF_NETLINK;
struct nl_request
{
struct nlmsghdr f_nlh;
struct inet_diag_req_v2 f_inet;
};
nl_request req = {};
req.f_nlh.nlmsg_len = sizeof(req);
req.f_nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
req.f_nlh.nlmsg_flags = NLM_F_REQUEST;
req.f_inet.sdiag_family = AF_INET;
req.f_inet.sdiag_protocol = IPPROTO_TCP;
req.f_inet.idiag_ext = 0;
req.f_inet.pad = 0;
req.f_inet.idiag_states = 0;
req.f_inet.id.idiag_sport = htons(4998);
req.f_inet.id.idiag_dport = 0;
req.f_inet.id.idiag_src[0] = htonl(0x0A00020A);
req.f_inet.id.idiag_dst[0] = 0;
req.f_inet.id.idiag_if = 0;
req.f_inet.id.idiag_cookie[0] = INET_DIAG_NOCOOKIE;
req.f_inet.id.idiag_cookie[1] = INET_DIAG_NOCOOKIE;
struct iovec vector = {};
vector.iov_base = &req;
vector.iov_len = sizeof(req);
struct msghdr msg = {};
msg.msg_name = &nladdr;
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &vector;
msg.msg_iovlen = 1;
int const r(sendmsg(d, &msg, 0));
if(r < 0)
{
perror("sendmsg");
return 1;
}
// response
struct sockaddr_nl r_nladdr = {};
r_nladdr.nl_family = AF_NETLINK;
struct iovec r_vector = {};
long buf[8192 / sizeof(long)];
r_vector.iov_base = buf;
r_vector.iov_len = sizeof(buf);
for(int i(1);; ++i)
{
struct msghdr r_msg = {};
r_msg.msg_name = &r_nladdr;
r_msg.msg_namelen = sizeof(r_nladdr);
r_msg.msg_iov = &r_vector;
r_msg.msg_iovlen = 1;
//std::cout << "wait for message...\n";
ssize_t size(recvmsg(d, &r_msg, 0));
if(size < 0)
{
perror("recvmsg");
return 1;
}
if(size == 0)
{
std::cout << "end of message stream received." << std::endl;
break;
}
//std::cout << "got message #" << i << ": size = " << size << std::endl;
struct nlmsghdr const * h(reinterpret_cast<struct nlmsghdr *>(buf));
if(!NLMSG_OK(h, size))
{
std::cerr << "NLMSG_OK() says there is an error." << std::endl;
return 1;
}
do
{
if(h->nlmsg_type == NLMSG_DONE)
{
std::cout << "explicit end of message stream received (NLMSG_DONE)." << std::endl;
break;
}
if(h->nlmsg_type == NLMSG_ERROR)
{
struct nlmsgerr const * err(reinterpret_cast<struct nlmsgerr const *>(NLMSG_DATA(h)));
if(h->nlmsg_len < NLMSG_LENGTH(sizeof(*err)))
{
std::cerr << "unknown NLMSG_ERROR received." << std::endl;
}
else
{
// here is the location display an error when trying to get an
// event about the LISTEN and no one is listening on that port.
//
errno = -err->error;
perror("NLMSG_ERROR:");
}
return 1;
}
if(h->nlmsg_type != SOCK_DIAG_BY_FAMILY)
{
std::cerr << "unexpected message type (h->nlmsg_type) "
<< h->nlmsg_type
<< std::endl;
return 1;
}
//std::cout << "------- sock_diag info!\n";
struct inet_diag_msg const * k_msg(reinterpret_cast<struct inet_diag_msg const *>(NLMSG_DATA(h)));
if(h->nlmsg_len < NLMSG_LENGTH(sizeof(*k_msg)))
{
std::cerr << "unexpected message length (h->nlmsg_len) "
<< h->nlmsg_type
<< std::endl;
return 1;
}
switch(k_msg->idiag_state)
{
case 1:
case 7:
break;
default:
{
std::uint32_t const src_ip(ntohl(k_msg->id.idiag_src[0]));
std::uint32_t const dst_ip(ntohl(k_msg->id.idiag_dst[0]));
std::cout << "inet_diag_msg->idiag_family = " << static_cast<int>(k_msg->idiag_family) << "\n"
<< "inet_diag_msg->idiag_state = " << static_cast<int>(k_msg->idiag_state) << "\n"
<< "inet_diag_msg->idiag_timer = " << static_cast<int>(k_msg->idiag_timer) << "\n"
<< "inet_diag_msg->idiag_retrans = " << static_cast<int>(k_msg->idiag_retrans) << "\n"
<< "inet_diag_msg->id.idiag_sport = " << ntohs(k_msg->id.idiag_sport) << "\n"
<< "inet_diag_msg->id.idiag_dport = " << ntohs(k_msg->id.idiag_dport) << "\n"
<< "inet_diag_msg->id.idiag_src[0] = " << ((src_ip >> 24) & 255)
<< "." << ((src_ip >> 16) & 255) << "." << ((src_ip >> 8) & 255) << "." << (src_ip & 255) << "\n"
<< "inet_diag_msg->id.idiag_dst[0] = " << ((dst_ip >> 24) & 255)
<< "." << ((dst_ip >> 16) & 255) << "." << ((dst_ip >> 8) & 255) << "." << (dst_ip & 255) << "\n"
<< "inet_diag_msg->id.idiag_if = " << k_msg->id.idiag_if << "\n"
<< "inet_diag_msg->id.idiag_cookie[0] = " << k_msg->id.idiag_cookie[0] << "\n"
<< "inet_diag_msg->id.idiag_cookie[1] = " << k_msg->id.idiag_cookie[1] << "\n"
<< "inet_diag_msg->idiag_expires = " << k_msg->idiag_expires << "\n"
<< "inet_diag_msg->idiag_rqueue = " << k_msg->idiag_rqueue << "\n"
<< "inet_diag_msg->idiag_wqueue = " << k_msg->idiag_wqueue << "\n"
<< "inet_diag_msg->idiag_uid = " << k_msg->idiag_uid << "\n"
<< "inet_diag_msg->idiag_inode = " << k_msg->idiag_inode << "\n"
<< "\n";
}
break;
}
// next message
//
h = NLMSG_NEXT(h, size);
}
while(NLMSG_OK(h, size));
}
return 0;
}
To test that IP:port combo, I simply used the nc
command like so:
nc -l 10.0.2.10 4998
You of course need the 10.0.2.10 IP on one of your interfaces for this to work.
My question is:
Did I do something wrong that I do not receive TCP_LISTEN
events on that socket unless explicitly requested?
P.S. Just in case, I tried to run this test app as root. Same results.
switch(k_msg->idiag_state)
and let all cases go through thedefault: ...
case, then I see the established and close events. So I think the loop generally works. But somehow doesn't want to send messages for the newTCP_LISTEN
in said loop. – Correna