受jørgensen 回答的启发,下面的 C++17 代码使用 Netlink 获取每个接口的 MAC 地址和名称。这是这个 gist的修改版本,它帮助我克服了使用 Netlink 的害羞。它实际上并不像起初看起来那么复杂。
#include <linux/rtnetlink.h>
#include <net/if_arp.h>
#include <unistd.h>
#include <cstddef>
#include <cstring>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
using std::byte;
using std::string;
using std::vector;
struct RtReq {
nlmsghdr header;
ifinfomsg msg;
};
class fd_wrapper
{
public:
fd_wrapper( int const fd_arg ) : fd_( fd_arg ) {}
~fd_wrapper() { close( fd_ ); }
int fd() const noexcept { return fd_; }
private:
int fd_;
};
void print_all_macs()
{
fd_wrapper fd = ( []() -> fd_wrapper {
RtReq req = ( []() {
RtReq req;
memset( &req, 0, sizeof( req ) );
req.header.nlmsg_len = NLMSG_LENGTH( sizeof( struct ifinfomsg ) );
req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
req.header.nlmsg_type = RTM_GETLINK;
req.header.nlmsg_seq = 1; // We're only sending one message
req.msg.ifi_family = AF_UNSPEC;
req.msg.ifi_change = 0xffffffff;
return std::move( req );
} )();
sockaddr_nl sa;
memset( &sa, 0, sizeof( sa ) );
sa.nl_family = AF_NETLINK;
iovec iov = {&req, req.header.nlmsg_len};
msghdr msg = {&sa, sizeof( sa ), &iov, 1, nullptr, 0, 0};
int fd = socket( AF_NETLINK, SOCK_RAW, NETLINK_ROUTE );
sendmsg( fd, &msg, 0 );
return fd_wrapper( fd );
} )();
enum class Result { not_done, done };
auto read_message = []( fd_wrapper const& fd ) -> Result {
char buf[16 * 1024];
iovec iov = {buf, sizeof( buf )};
sockaddr_nl sa;
msghdr msg = {&sa, sizeof( sa ), &iov, 1, nullptr, 0, 0};
int len = recvmsg( fd.fd(), &msg, 0 );
for( nlmsghdr* nh = (nlmsghdr*)buf; NLMSG_OK( nh, len ); nh = NLMSG_NEXT( nh, len ) ) {
if( nh->nlmsg_type == NLMSG_DONE ) {
return Result::done;
}
if( nh->nlmsg_type != RTM_BASE )
continue;
ifinfomsg* msg = (ifinfomsg*)NLMSG_DATA( nh );
if( msg->ifi_type != ARPHRD_ETHER )
continue;
rtattr* rta = IFLA_RTA( msg );
int alen = nh->nlmsg_len - NLMSG_LENGTH( sizeof( *msg ) );
string name;
vector<byte> addr;
for( ; RTA_OK( rta, alen ); rta = RTA_NEXT( rta, alen ) ) {
if( rta->rta_type == IFLA_ADDRESS ) {
auto const p = reinterpret_cast<byte const*>( RTA_DATA( rta ) );
addr.insert( addr.begin(), p, p + 6 );
}
if( rta->rta_type == IFLA_IFNAME ) {
name = string{(char*)RTA_DATA( rta )};
}
}
auto const c = reinterpret_cast<uint8_t const*>( addr.data() );
printf( "%02x:%02x:%02x:%02x:%02x:%02x %s\n", c[0], c[1], c[2], c[3], c[4], c[5],
name.c_str() );
}
};
Result result;
while( ( result = read_message( fd ) ) == Result::not_done )
;
}
int main( int /*argc*/, char** /*argv*/ )
{
print_all_macs();
return 0;
}