Skip to content

Commit 2beeae4

Browse files
committed
Update extension to allow ipaddress objects
1 parent 8dc8866 commit 2beeae4

File tree

1 file changed

+123
-18
lines changed

1 file changed

+123
-18
lines changed

extension/maxminddb.c

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include <Python.h>
22
#include <maxminddb.h>
3+
#include <arpa/inet.h>
4+
#include <netinet/in.h>
5+
#include <sys/socket.h>
36
#include "structmember.h"
47

58
#define __STDC_FORMAT_MACROS
@@ -28,10 +31,12 @@ typedef struct {
2831
PyObject *record_size;
2932
} Metadata_obj;
3033

34+
static char *format_sockaddr(struct sockaddr *addr);
3135
static PyObject *from_entry_data_list(MMDB_entry_data_list_s **entry_data_list);
3236
static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list);
3337
static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list);
3438
static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list);
39+
static int ip_converter(PyObject *obj, struct sockaddr **ip_address);
3540

3641
#if PY_MAJOR_VERSION >= 3
3742
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_ ## name(void)
@@ -106,33 +111,28 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds)
106111

107112
static PyObject *Reader_get(PyObject *self, PyObject *args)
108113
{
109-
char *ip_address = NULL;
114+
MMDB_s *mmdb = ((Reader_obj *)self)->mmdb;
110115

111-
Reader_obj *mmdb_obj = (Reader_obj *)self;
112-
if (!PyArg_ParseTuple(args, "s", &ip_address)) {
116+
if (NULL == mmdb) {
117+
PyErr_SetString(PyExc_ValueError,
118+
"Attempt to read from a closed MaxMind DB.");
113119
return NULL;
114120
}
115121

116-
MMDB_s *mmdb = mmdb_obj->mmdb;
122+
struct sockaddr *ip_address = NULL;
123+
if (!PyArg_ParseTuple(args, "O&", ip_converter, &ip_address)) {
124+
return NULL;
125+
}
117126

118-
if (NULL == mmdb) {
127+
if (ip_address == NULL) {
119128
PyErr_SetString(PyExc_ValueError,
120-
"Attempt to read from a closed MaxMind DB.");
129+
"Error parsing argument");
121130
return NULL;
122131
}
123132

124-
int gai_error = 0;
125133
int mmdb_error = MMDB_SUCCESS;
126134
MMDB_lookup_result_s result =
127-
MMDB_lookup_string(mmdb, ip_address, &gai_error,
128-
&mmdb_error);
129-
130-
if (0 != gai_error) {
131-
PyErr_Format(PyExc_ValueError,
132-
"'%s' does not appear to be an IPv4 or IPv6 address.",
133-
ip_address);
134-
return NULL;
135-
}
135+
MMDB_lookup_sockaddr(mmdb, ip_address, &mmdb_error);
136136

137137
if (MMDB_SUCCESS != mmdb_error) {
138138
PyObject *exception;
@@ -141,31 +141,136 @@ static PyObject *Reader_get(PyObject *self, PyObject *args)
141141
} else {
142142
exception = MaxMindDB_error;
143143
}
144+
char *ipstr = format_sockaddr(ip_address);
144145
PyErr_Format(exception, "Error looking up %s. %s",
145-
ip_address, MMDB_strerror(mmdb_error));
146+
ipstr, MMDB_strerror(mmdb_error));
147+
free(ipstr);
148+
free(ip_address);
146149
return NULL;
147150
}
148151

149152
if (!result.found_entry) {
153+
free(ip_address);
150154
Py_RETURN_NONE;
151155
}
152156

153157
MMDB_entry_data_list_s *entry_data_list = NULL;
154158
int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
155159
if (MMDB_SUCCESS != status) {
160+
char *ipstr = format_sockaddr(ip_address);
156161
PyErr_Format(MaxMindDB_error,
157162
"Error while looking up data for %s. %s",
158-
ip_address, MMDB_strerror(status));
163+
ipstr, MMDB_strerror(status));
164+
free(ipstr);
165+
free(ip_address);
159166
MMDB_free_entry_data_list(entry_data_list);
160167
return NULL;
161168
}
162169

163170
MMDB_entry_data_list_s *original_entry_data_list = entry_data_list;
164171
PyObject *py_obj = from_entry_data_list(&entry_data_list);
165172
MMDB_free_entry_data_list(original_entry_data_list);
173+
free(ip_address);
166174
return py_obj;
167175
}
168176

177+
static int ip_converter(PyObject *obj, struct sockaddr **ip_address)
178+
{
179+
#if PY_MAJOR_VERSION >= 3
180+
if (PyUnicode_Check(obj)) {
181+
Py_ssize_t len;
182+
const char *ipstr = PyUnicode_AsUTF8AndSize(obj, &len);
183+
# else
184+
if (PyUnicode_Check(obj) || PyString_Check(obj)) {
185+
// Although this should work on Python 3, we will hopefully delete
186+
// this soon and the Python 3 version is cleaner.
187+
const char *ipstr = PyString_AsString(obj);
188+
Py_ssize_t len = strlen(ipstr);
189+
#endif
190+
if (!ipstr) {
191+
PyErr_SetString(PyExc_ValueError, "invalid string");
192+
return 0;
193+
}
194+
if (strlen(ipstr) != (size_t)len) {
195+
PyErr_SetString(PyExc_ValueError, "embedded null character");
196+
return 0;
197+
}
198+
199+
struct addrinfo hints = {
200+
.ai_family = AF_UNSPEC,
201+
.ai_flags = AI_NUMERICHOST,
202+
// We set ai_socktype so that we only get one result back
203+
.ai_socktype = SOCK_STREAM
204+
};
205+
206+
struct addrinfo *addresses = NULL;
207+
int gai_status = getaddrinfo(ipstr, NULL, &hints, &addresses);
208+
if (gai_status) {
209+
PyErr_Format(PyExc_ValueError,
210+
"'%s' does not appear to be an IPv4 or IPv6 address.",
211+
ipstr);
212+
return 0;
213+
}
214+
*ip_address = calloc(1, sizeof(struct sockaddr_storage));
215+
memcpy(*ip_address, addresses->ai_addr, addresses->ai_addrlen);
216+
freeaddrinfo(addresses);
217+
return 1;
218+
}
219+
PyObject *packed = PyObject_GetAttrString(obj, "packed");
220+
if (!packed) {
221+
PyErr_SetString(PyExc_ValueError, "error about object type");
222+
}
223+
Py_ssize_t len;
224+
char *bytes;
225+
int status = PyBytes_AsStringAndSize(packed, &bytes, &len);
226+
if (status == -1) {
227+
PyErr_SetString(PyExc_ValueError, "cannot get bytes");
228+
Py_DECREF(packed);
229+
return 0;
230+
}
231+
232+
*ip_address = calloc(1, sizeof(struct sockaddr_storage));
233+
234+
switch (len) {
235+
case 16: {
236+
(*ip_address)->sa_family = AF_INET6;
237+
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)*ip_address;
238+
memcpy(sin->sin6_addr.s6_addr, bytes, len);
239+
Py_DECREF(packed);
240+
return 1;
241+
}
242+
case 4: {
243+
(*ip_address)->sa_family = AF_INET;
244+
struct sockaddr_in *sin = (struct sockaddr_in *)*ip_address;
245+
memcpy(&(sin->sin_addr.s_addr), bytes, len);
246+
Py_DECREF(packed);
247+
return 1;
248+
}
249+
default:
250+
PyErr_SetString(PyExc_ValueError, "unexpected packed length");
251+
Py_DECREF(packed);
252+
return 0;
253+
}
254+
}
255+
256+
static char *format_sockaddr(struct sockaddr *sa)
257+
{
258+
char *ip = calloc(INET6_ADDRSTRLEN, sizeof(char));
259+
260+
char *addr;
261+
if (sa->sa_family == AF_INET) {
262+
struct sockaddr_in *sin = (struct sockaddr_in *)sa;
263+
addr = (char *)&sin->sin_addr;
264+
} else {
265+
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa;
266+
addr = (char *)&sin->sin6_addr;
267+
}
268+
269+
inet_ntop(sa->sa_family, addr, ip, INET6_ADDRSTRLEN);
270+
return ip;
271+
}
272+
273+
169274
static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args))
170275
{
171276
Reader_obj *mmdb_obj = (Reader_obj *)self;

0 commit comments

Comments
 (0)