Skip to content

Commit 7ff5e27

Browse files
committed
Support devices with multiple IPv4 addresses
Add a get_ipv4_addresses() method to ethtool.etherinfo to support devices with multiple IPv4 addresses (rhbz#759150) Previously, get_etherinfo() made queries to NETLINK with NLQRY_ADDR, and callback_nl_address handled responses of family AF_INET (IPv4) by writing to fields within a struct etherinfo. If multiple AF_INET responses come back, each overwrote the last, and the last one won. This patch generalizes things by moving the relevant fields: char *ipv4_address; /**< Configured IPv4 address */ int ipv4_netmask; /**< Configured IPv4 netmask */ char *ipv4_broadcast; from (struct etherinfo) into a new Python class, currently named PyNetlinkIPv4Address. This object has a sane repr(): >>> ethtool.get_interfaces_info('eth1')[0].get_ipv4_addresses() [ethtool.NetlinkIPv4Address(address='192.168.1.10', netmask=24, broadcast='192.168.1.255')] and attributes: >>> print [iface.address for iface in ethtool.get_interfaces_info('eth1')[0].get_ipv4_addresses()] ['192.168.1.10'] >>> print [iface.netmask for iface in ethtool.get_interfaces_info('eth1')[0].get_ipv4_addresses()] [24] >>> print [iface.broadcast for iface in ethtool.get_interfaces_info('eth1')[0].get_ipv4_addresses()] ['192.168.1.255'] The (struct etherinfo) then gains a new field: PyObject *ipv4_addresses; /**< list of PyNetlinkIPv4Address instances */ which is created before starting the query, and populated by the callback as responses come in. All direct usage of the old fields (which assumed a single IPv4 address) are changed to use the last entry in the list (if any), to mimic the old behavior. dump_etherinfo() and _ethtool_etherinfo_str() are changed to loop over all of the IPv4 addresses when outputting, rather than just outputting one. Caveats: * the exact terminology is probably incorrect: I'm not a networking specialist * the relationship between each of devices, get_interfaces_info() results, and addresses seems both unclear and messy to me: how changable is the API? >>> ethtool.get_interfaces_info('eth1')[0].get_ipv4_addresses() [ethtool.NetlinkIPv4Address(address='192.168.1.10', netmask=24, broadcast='192.168.1.255')] It seems that an etherinfo object relates to a device: perhaps it should be named as such? But it may be too late to make this change. Notes: The _ethtool_etherinfo_members array within python-ethtool/etherinfo_obj.c was broken: it defined 4 attributes of type PyObject*, to be extracted from etherinfo_py->data, which is of a completed different type. If these PyMemberDef fields were ever used, Python would segfault. Thankfully _ethtool_etherinfo_getter() has handlers for these attributes, and gets called first. This is a modified version of the patch applied downstream in RHEL 6.4 within python-ethtool-0.6-3.el6: python-ethtool-0.6-add-get_ipv4_addresses-method.patch ported to take account of 508ffff
1 parent d33ad02 commit 7ff5e27

File tree

6 files changed

+330
-64
lines changed

6 files changed

+330
-64
lines changed

python-ethtool/etherinfo.c

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,8 @@ void free_etherinfo(struct etherinfo *ptr)
9292
if( ptr->hwaddress ) {
9393
free(ptr->hwaddress);
9494
}
95-
if( ptr->ipv4_address ) {
96-
free(ptr->ipv4_address);
97-
}
98-
if( ptr->ipv4_broadcast ) {
99-
free(ptr->ipv4_broadcast);
100-
}
95+
Py_XDECREF(ptr->ipv4_addresses);
96+
10197
if( ptr->ipv6_addresses ) {
10298
free_ipv6addresses(ptr->ipv6_addresses);
10399
}
@@ -171,6 +167,36 @@ static void callback_nl_link(struct nl_object *obj, void *arg)
171167
SET_STR_VALUE(ethi->hwaddress, hwaddr);
172168
}
173169

170+
/**
171+
* For use by callback_nl_address
172+
* Returns 0 for success; -1 for error (though this is currently ignored)
173+
*/
174+
static int
175+
append_object_for_netlink_address(struct etherinfo *ethi,
176+
struct nl_object *obj,
177+
struct rtnl_addr *addr)
178+
{
179+
PyObject *addr_obj;
180+
181+
assert(ethi);
182+
assert(ethi->ipv4_addresses);
183+
assert(addr);
184+
185+
addr_obj = make_python_address_from_rtnl_addr(obj, addr);
186+
if (!addr_obj) {
187+
return -1;
188+
}
189+
190+
if (-1 == PyList_Append(ethi->ipv4_addresses, addr_obj)) {
191+
Py_DECREF(addr_obj);
192+
return -1;
193+
}
194+
195+
Py_DECREF(addr_obj);
196+
197+
/* Success */
198+
return 0;
199+
}
174200

175201
/**
176202
* libnl callback function. Does the real parsing of a record returned by NETLINK. This function
@@ -199,17 +225,7 @@ static void callback_nl_address(struct nl_object *obj, void *arg)
199225
inet_ntop(family, nl_addr_get_binary_addr(addr), (char *)&ip_str, 64);
200226

201227
if( family == AF_INET ) {
202-
struct nl_addr *brdcst = rtnl_addr_get_broadcast((struct rtnl_addr *)obj);
203-
char brdcst_str[66];
204-
205-
SET_STR_VALUE(ethi->ipv4_address, ip_str);
206-
ethi->ipv4_netmask = rtnl_addr_get_prefixlen((struct rtnl_addr*) obj);
207-
208-
if( brdcst ) {
209-
memset(&brdcst_str, 0, 66);
210-
inet_ntop(family, nl_addr_get_binary_addr(brdcst), (char *)&brdcst_str, 64);
211-
SET_STR_VALUE(ethi->ipv4_broadcast, brdcst_str);
212-
}
228+
(void)append_object_for_netlink_address(ethi, obj, (struct rtnl_addr*) addr);
213229
} else {
214230
ethi->ipv6_addresses = etherinfo_add_ipv6(ethi->ipv6_addresses,
215231
ip_str,
@@ -244,13 +260,18 @@ void dump_etherinfo(FILE *fp, struct etherinfo *ptr)
244260
fprintf(fp, "MAC address: %s", ptr->hwaddress);
245261
}
246262
fprintf(fp, "\n");
247-
if( ptr->ipv4_address ) {
248-
fprintf(fp, "\tIPv4 Address: %s/%i",
249-
ptr->ipv4_address, ptr->ipv4_netmask);
250-
if( ptr->ipv4_broadcast ) {
251-
fprintf(fp, " - Broadcast: %s", ptr->ipv4_broadcast);
252-
}
253-
fprintf(fp, "\n");
263+
if( ptr->ipv4_addresses ) {
264+
Py_ssize_t i;
265+
for (i = 0; i < PyList_Size(ptr->ipv4_addresses); i++) {
266+
PyNetlinkIPv4Address *addr = (PyNetlinkIPv4Address *)PyList_GetItem(ptr->ipv4_addresses, i);
267+
fprintf(fp, "\tIPv4 Address: %s/%i",
268+
PyString_AsString(addr->ipv4_address),
269+
addr->ipv4_netmask);
270+
if( addr->ipv4_broadcast ) {
271+
fprintf(fp, " - Broadcast: %s", PyString_AsString(addr->ipv4_broadcast));
272+
}
273+
fprintf(fp, "\n");
274+
}
254275
}
255276
if( ptr->ipv6_addresses ) {
256277
struct ipv6address *ipv6 = ptr->ipv6_addresses;
@@ -338,6 +359,13 @@ int get_etherinfo(struct etherinfo_obj_data *data, nlQuery query)
338359
ethinf->ipv6_addresses = NULL;
339360
}
340361

362+
/* Likewise for IPv4 addresses: */
363+
Py_XDECREF(ethinf->ipv4_addresses);
364+
ethinf->ipv4_addresses = PyList_New(0);
365+
if (!ethinf->ipv4_addresses) {
366+
return 0;
367+
}
368+
341369
/* Retrieve all address information */
342370
nl_cache_foreach_filter(addr_cache, (struct nl_object *)addr, callback_nl_address, ethinf);
343371
rtnl_addr_put(addr);

python-ethtool/etherinfo_obj.c

Lines changed: 95 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,41 @@ int _ethtool_etherinfo_init(etherinfo_py *self, PyObject *args, PyObject *kwds)
9191
return 0;
9292
}
9393

94+
/*
95+
The old approach of having a single IPv4 address per device meant each result
96+
that came in from netlink overwrote the old result.
97+
98+
Mimic it by returning the last entry in the list (if any).
99+
100+
The return value is a *borrowed reference* (or NULL)
101+
*/
102+
static PyNetlinkIPv4Address*
103+
get_last_address(etherinfo_py *self)
104+
{
105+
Py_ssize_t size;
106+
PyObject *list;
107+
108+
assert(self);
109+
list = self->data->ethinfo->ipv4_addresses;
110+
if (!list) {
111+
return NULL;
112+
}
113+
114+
if (!PyList_Check(list)) {
115+
return NULL;
116+
}
117+
118+
size = PyList_Size(list);
119+
if (size > 0) {
120+
PyObject *item = PyList_GetItem(list, size - 1);
121+
if (Py_TYPE(item) == &ethtool_netlink_ipv4_address_Type) {
122+
return (PyNetlinkIPv4Address*)item;
123+
}
124+
}
125+
126+
return NULL;
127+
}
128+
94129
/**
95130
* ethtool.etherinfo function for retrieving data from a Python object.
96131
*
@@ -102,6 +137,7 @@ int _ethtool_etherinfo_init(etherinfo_py *self, PyObject *args, PyObject *kwds)
102137
PyObject *_ethtool_etherinfo_getter(etherinfo_py *self, PyObject *attr_o)
103138
{
104139
char *attr = PyString_AsString(attr_o);
140+
PyNetlinkIPv4Address *py_addr;
105141

106142
if( !self || !self->data ) {
107143
PyErr_SetString(PyExc_AttributeError, "No data available");
@@ -115,13 +151,32 @@ PyObject *_ethtool_etherinfo_getter(etherinfo_py *self, PyObject *attr_o)
115151
return RETURN_STRING(self->data->ethinfo->hwaddress);
116152
} else if( strcmp(attr, "ipv4_address") == 0 ) {
117153
get_etherinfo(self->data, NLQRY_ADDR);
118-
return RETURN_STRING(self->data->ethinfo->ipv4_address);
154+
/* For compatiblity with old approach, return last IPv4 address: */
155+
py_addr = get_last_address(self);
156+
if (py_addr) {
157+
if (py_addr->ipv4_address) {
158+
Py_INCREF(py_addr->ipv4_address);
159+
return py_addr->ipv4_address;
160+
}
161+
}
162+
Py_RETURN_NONE;
119163
} else if( strcmp(attr, "ipv4_netmask") == 0 ) {
120164
get_etherinfo(self->data, NLQRY_ADDR);
121-
return PyInt_FromLong(self->data->ethinfo->ipv4_netmask);
165+
py_addr = get_last_address(self);
166+
if (py_addr) {
167+
return PyInt_FromLong(py_addr->ipv4_netmask);
168+
}
169+
return PyInt_FromLong(0);
122170
} else if( strcmp(attr, "ipv4_broadcast") == 0 ) {
123171
get_etherinfo(self->data, NLQRY_ADDR);
124-
return RETURN_STRING(self->data->ethinfo->ipv4_broadcast);
172+
py_addr = get_last_address(self);
173+
if (py_addr) {
174+
if (py_addr->ipv4_broadcast) {
175+
Py_INCREF(py_addr->ipv4_broadcast);
176+
return py_addr->ipv4_broadcast;
177+
}
178+
}
179+
Py_RETURN_NONE;
125180
} else {
126181
return PyObject_GenericGetAttr((PyObject *)self, attr_o);
127182
}
@@ -170,19 +225,21 @@ PyObject *_ethtool_etherinfo_str(etherinfo_py *self)
170225
Py_DECREF(tmp);
171226
}
172227

173-
if( self->data->ethinfo->ipv4_address ) {
174-
PyObject *tmp = PyString_FromFormat("\tIPv4 address: %s/%i",
175-
self->data->ethinfo->ipv4_address,
176-
self->data->ethinfo->ipv4_netmask);
177-
if( self->data->ethinfo->ipv4_broadcast ) {
178-
PyObject *tmp2 = PyString_FromFormat(" Broadcast: %s",
179-
self->data->ethinfo->ipv4_broadcast);
180-
PyString_Concat(&tmp, tmp2);
181-
Py_DECREF(tmp2);
182-
}
183-
PyString_Concat(&tmp, PyString_FromString("\n"));
184-
PyString_Concat(&ret, tmp);
185-
Py_DECREF(tmp);
228+
if( self->data->ethinfo->ipv4_addresses ) {
229+
Py_ssize_t i;
230+
for (i = 0; i < PyList_Size(self->data->ethinfo->ipv4_addresses); i++) {
231+
PyNetlinkIPv4Address *py_addr = (PyNetlinkIPv4Address *)PyList_GetItem(self->data->ethinfo->ipv4_addresses, i);
232+
PyObject *tmp = PyString_FromFormat("\tIPv4 address: ");
233+
PyString_Concat(&tmp, py_addr->ipv4_address);
234+
PyString_ConcatAndDel(&tmp, PyString_FromFormat("/%d", py_addr->ipv4_netmask));
235+
if (py_addr->ipv4_broadcast ) {
236+
PyString_ConcatAndDel(&tmp,
237+
PyString_FromString(" Broadcast: "));
238+
PyString_Concat(&tmp, py_addr->ipv4_broadcast);
239+
}
240+
PyString_ConcatAndDel(&tmp, PyString_FromString("\n"));
241+
PyString_ConcatAndDel(&ret, tmp);
242+
}
186243
}
187244

188245
if( self->data->ethinfo->ipv6_addresses ) {
@@ -203,6 +260,24 @@ PyObject *_ethtool_etherinfo_str(etherinfo_py *self)
203260
return ret;
204261
}
205262

263+
static PyObject *
264+
_ethtool_etherinfo_get_ipv4_addresses(etherinfo_py *self, PyObject *notused) {
265+
PyObject *ret;
266+
267+
if( !self || !self->data ) {
268+
PyErr_SetString(PyExc_AttributeError, "No data available");
269+
return NULL;
270+
}
271+
272+
get_etherinfo(self->data, NLQRY_ADDR);
273+
274+
/* Transfer ownership of reference: */
275+
ret = self->data->ethinfo->ipv4_addresses;
276+
self->data->ethinfo->ipv4_addresses = NULL;
277+
278+
return ret;
279+
}
280+
206281

207282
/**
208283
* Returns a tuple list of ethertool.etherinfo_ipv6addr objects, containing configured
@@ -276,29 +351,13 @@ PyObject * _ethtool_etherinfo_get_ipv6_addresses(etherinfo_py *self, PyObject *n
276351
*
277352
*/
278353
static PyMethodDef _ethtool_etherinfo_methods[] = {
279-
{"get_ipv6_addresses", _ethtool_etherinfo_get_ipv6_addresses, METH_NOARGS,
354+
{"get_ipv4_addresses", (PyCFunction)_ethtool_etherinfo_get_ipv4_addresses, METH_NOARGS,
355+
"Retrieve configured IPv4 addresses. Returns a list of NetlinkIP4Address objects"},
356+
{"get_ipv6_addresses", (PyCFunction)_ethtool_etherinfo_get_ipv6_addresses, METH_NOARGS,
280357
"Retrieve configured IPv6 addresses. Returns a tuple list of etherinfo_ipv6addr objects"},
281358
{NULL} /**< No methods defined */
282359
};
283360

284-
/**
285-
* Defines all accessible object members
286-
*
287-
*/
288-
static PyMemberDef _ethtool_etherinfo_members[] = {
289-
{"device", T_OBJECT_EX, offsetof(etherinfo_py, data), 0,
290-
"Device name of the interface"},
291-
{"mac_address", T_OBJECT_EX, offsetof(etherinfo_py, data), 0,
292-
"MAC address / hardware address of the interface"},
293-
{"ipv4_address", T_OBJECT_EX, offsetof(etherinfo_py, data), 0,
294-
"IPv4 address"},
295-
{"ipv4_netmask", T_INT, offsetof(etherinfo_py, data), 0,
296-
"IPv4 netmask in bits"},
297-
{"ipv4_broadcast", T_OBJECT_EX, offsetof(etherinfo_py, data), 0,
298-
"IPv4 broadcast address"},
299-
{NULL} /* End of member list */
300-
};
301-
302361
/**
303362
* Definition of the functions a Python class/object requires.
304363
*
@@ -333,7 +392,7 @@ PyTypeObject ethtool_etherinfoType = {
333392
0, /* tp_iter */
334393
0, /* tp_iternext */
335394
_ethtool_etherinfo_methods, /* tp_methods */
336-
_ethtool_etherinfo_members, /* tp_members */
395+
0, /* tp_members */
337396
0, /* tp_getset */
338397
0, /* tp_base */
339398
0, /* tp_dict */

python-ethtool/etherinfo_struct.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* General Public License for more details.
1414
*/
1515

16+
#include <netlink/route/addr.h>
17+
1618
/**
1719
* @file etherinfo_struct.h
1820
* @author David Sommerseth <dsommers@wsdsommers.usersys.redhat.com>
@@ -33,12 +35,18 @@ struct etherinfo {
3335
char *device; /**< Device name */
3436
int index; /**< NETLINK index reference */
3537
char *hwaddress; /**< HW address / MAC address of device */
36-
char *ipv4_address; /**< Configured IPv4 address */
37-
int ipv4_netmask; /**< Configured IPv4 netmask */
38-
char *ipv4_broadcast; /**< Configured IPv4 broadcast address */
38+
PyObject *ipv4_addresses; /**< list of PyNetlinkIPv4Address instances */
3939
struct ipv6address *ipv6_addresses; /**< Configured IPv6 addresses (as a pointer chain) */
4040
};
4141

42+
/* Python object containing data baked from a (struct rtnl_addr) */
43+
typedef struct PyNetlinkIPv4Address {
44+
PyObject_HEAD
45+
PyObject *ipv4_address; /**< string: Configured IPv4 address */
46+
int ipv4_netmask; /**< int: Configured IPv4 netmask */
47+
PyObject *ipv4_broadcast; /**< string: Configured IPv4 broadcast address */
48+
} PyNetlinkIPv4Address;
49+
extern PyTypeObject ethtool_netlink_ipv4_address_Type;
4250

4351
/**
4452
* Pointer chain with IPv6 addresses associated with a ethernet interface. Used
@@ -91,4 +99,9 @@ typedef struct {
9199
*/
92100
#define RETURN_STRING(str) (str ? PyString_FromString(str) : (Py_INCREF(Py_None), Py_None))
93101

102+
PyObject *
103+
make_python_address_from_rtnl_addr(struct nl_object *obj,
104+
struct rtnl_addr *addr);
105+
106+
94107
#endif

python-ethtool/ethtool.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,9 @@ PyMODINIT_FUNC initethtool(void)
999999
Py_INCREF(&ethtool_etherinfoIPv6Type);
10001000
PyModule_AddObject(m, "etherinfo_ipv6addr", (PyObject *)&ethtool_etherinfoIPv6Type);
10011001

1002+
if (PyType_Ready(&ethtool_netlink_ipv4_address_Type))
1003+
return;
1004+
10021005
// Setup constants
10031006
PyModule_AddIntConstant(m, "IFF_UP", IFF_UP); /* Interface is up. */
10041007
PyModule_AddIntConstant(m, "IFF_BROADCAST", IFF_BROADCAST); /* Broadcast address valid. */

0 commit comments

Comments
 (0)