I came across something strange today when writing some code to gather information about network interfaces on Linux. I'm using the standard functionality like ifaddrs and ioctl to pull what I need to pull from the kernel. I'm new to the most of ifaddrs and ioctl functions used to accomplish this, so I'm writing lines, compiling, checking output, commenting things out, and just making a general mess of things. Eventually I stumbled upon this error:
NixNet.h:36:15: error: expected ‘;’ at end of member declaration
36 | struct ifreq ifr_addr;
| ^~~~~~~~
NixNet.h:36:15: error: expected unqualified-id before ‘.’ token
36 | struct ifreq ifr_addr;
| ^~~~~~~~
RawSock.cpp: In constructor ‘RawSock::RawSock()’:
RawSock.cpp:7:16: error: ‘struct ifreq’ has no member named ‘ifru_addr’
7 | memset(&this->ifr_addr, 0, sizeof(struct ifreq));
| ^~~~~~~~
RawSock.cpp:33:15: error: ‘struct ifreq’ has no member named ‘ifru_addr’
33 | strcpy(this->ifr_addr.ifr_name, this->ifname);
| ^~~~~~~~
RawSock.cpp:37:28: error: ‘struct ifreq’ has no member named ‘ifru_addr’
37 | ioctl(sock, SIOCGIFADDR, &ifr_addr);
| ^~~~~~~~
RawSock.cpp:44:50: error: ‘struct ifreq’ has no member named ‘ifru_addr’
44 | printf("%s\n", inet_ntoa(((struct sockaddr_in*)&ifr_addr.ifr_addr)->sin_addr));
|
Huh...?
At first I figured it was my rat's nest of a header managing to somehow trip up the compiler in a strange way:
#include <iostream>
#include <cstdlib>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <pcap/pcap.h>
#include <regex>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
//#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netdb.h>
__attribute__((constructor)) void RawSock_init();
__attribute__((destructor)) void RawSock_clnp();
void pcap_listalldevs(void);
char* iptos(u_long);
char* ip6tos(struct sockaddr*,char*,int);
void ifprint(pcap_if_t*);
uint32_t getMyIP();
bool getMyMAC(unsigned char*);
extern struct ifaddrs* ifaddr_g;
class RawSock {
private:
uint32_t ifaddr;
uint8_t hwaddr[6];
char ifname[17];
struct ifreq ifr_idx;
struct ifreq ifr_mac;
struct ifreq ifr_addr;
struct sockaddr_ll sock_ll;
int sock;
public:
RawSock();
~RawSock();
int send(void* buf, size_t len);
};
So I threw problematic declaration into a dummy program:
#include <iostream>
#include <cstring>
#include <linux/if.h>
struct ifreq ifr_addr;
int main(){
memset(&ifr_addr, 0 , sizeof(struct ifreq));
return 0;
}
g++ dummy.cpp -g -o dummy --std=c++2a
Test.cpp:4:14: error: expected initializer before ‘.’ token
4 | struct ifreq ifr_addr;
| ^~~~~~~~
Test.cpp: In function ‘int main()’:
Test.cpp:6:9: error: ‘ifr_ifru’ was not declared in this scope
6 | memset(&ifr_addr, 0, sizeof(struct ifreq));
| ^~~~~~~~
I threw it in gcc, same result. At this point the only thing left to do is choose a different name for the variable. This resulted in a clean compilation so I applied the name change across my repository. Problem solved!
Maybe it's nothing but I thought this was really weird and I'd be fascinated to know the reason behind this behavior.
ifr_addr
is a macro defined in glibc https://github.com/lattera/glibc/blob/master/sysdeps/gnu/net/if.h#L153
# define ifr_addr ifr_ifru.ifru_addr /* address */
So your code becomes:
class RawSock {
private:
//struct ifreq ifr_addr;
struct ifreq ifr_ifru.ifru_addr;
};
Which is invalid.
You may ask "Why?". Why is ifr_addr
a macro?
The man page https://man7.org/linux/man-pages/man7/netdevice.7.html describes the following structure:
struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
// ... other fields
};
// ^^ anonymous union - no name
};
This structure uses an anonymous union, but that feature is available from C11. So to make the code work under compilers before C11 that do not support anonymous unions, you give the union member a name and do macro magic:
struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
// ... other fields
} some_hidden_name;
};
#define ifr_addr some_hidden_name.ifr_addr
#define ifr_dstaddr some_hidden_name.ifr_dstaddr
int main() {
struct ifreq variable;
variable.ifr_addr = stuff;
// becomes:
// variable.some_hidden_name.ifr_addr = stuff;
}
This trick is used - http://www.qnx.com/developers/docs/6.5.0SP1/neutrino/lib_ref/s/sigevent.html https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/bits/types/sigevent_t.h.html#_M/sigev_notify_function . There are many macros in standard library. Mostly structure members have a prefix - ifr_*
, sigev_*
for sigevent
, si_*
for siginfo
, tv_*
for timeval
. That is because old C compilers used same namespace for all structure member names and variables, but also because in extreme situations the standard library may want to use such tricks.
Overall, pick unique names, generally silently kind-of assume that structure member names from standard library can be macros.