Skip to content

Commit 59f78ff

Browse files
committed
Sketch of cleaner ipaddress extension support
1 parent 529dfb0 commit 59f78ff

File tree

1 file changed

+101
-78
lines changed

1 file changed

+101
-78
lines changed

extension/maxminddb.c

Lines changed: 101 additions & 78 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,65 +111,17 @@ 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;
110-
PyObject *object = NULL;
111-
#if PY_MAJOR_VERSION >= 3
112-
PyObject *bytes = NULL;
113-
#endif
114+
struct sockaddr *ip_address = NULL;
114115

115116
Reader_obj *mmdb_obj = (Reader_obj *)self;
116117

117-
if (PyArg_ParseTuple(args, "s", &ip_address)) {
118-
// pass
119-
} else if (PyErr_Clear(), PyArg_ParseTuple(args, "O", &object)) {
120-
PyObject* module = PyImport_ImportModule("ipaddress");
121-
if (module == NULL) {
122-
return NULL;
123-
}
124-
125-
PyObject* module_dict = PyModule_GetDict(module);
126-
if (module_dict == NULL) {
127-
return NULL;
128-
}
129-
130-
PyObject* ipaddress_IPv4Address = PyDict_GetItemString(
131-
module_dict, "IPv4Address");
132-
int is_ipaddress_object = 0;
133-
134-
if (ipaddress_IPv4Address != NULL) {
135-
is_ipaddress_object = PyObject_IsInstance(ipaddress_IPv4Address,
136-
object);
137-
}
138-
139-
PyObject* ipaddress_IPv6Address;
140-
if (!is_ipaddress_object &&
141-
(ipaddress_IPv6Address = PyDict_GetItemString(module_dict,
142-
"IPv6Address")) != NULL) {
143-
is_ipaddress_object = PyObject_IsInstance(ipaddress_IPv6Address,
144-
object);
145-
}
146-
147-
PyErr_Clear();
148-
PyObject *str;
149-
150-
if (!is_ipaddress_object) {
151-
PyErr_SetString(PyExc_TypeError, "IP address must be a string,"
152-
" ipaddress.IPv4Address or ipaddress.IPv6Address object");
153-
} else if ((str = PyObject_Str(object)) != NULL) {
154-
#if PY_MAJOR_VERSION >= 3
155-
bytes = PyUnicode_AsEncodedString(str, "UTF-8", "strict");
156-
ip_address = PyBytes_AS_STRING(bytes);
157-
#else
158-
ip_address = PyString_AsString(str);
159-
#endif
160-
}
161-
162-
Py_DECREF(module);
163-
} else {
118+
if (!PyArg_ParseTuple(args, "O&", ip_converter, &ip_address)) {
164119
return NULL;
165120
}
166121

167122
if (ip_address == NULL) {
123+
PyErr_SetString(PyExc_ValueError,
124+
"Error parsing argument");
168125
return NULL;
169126
}
170127

@@ -176,22 +133,9 @@ static PyObject *Reader_get(PyObject *self, PyObject *args)
176133
return NULL;
177134
}
178135

179-
int gai_error = 0;
180136
int mmdb_error = MMDB_SUCCESS;
181137
MMDB_lookup_result_s result =
182-
MMDB_lookup_string(mmdb, ip_address, &gai_error,
183-
&mmdb_error);
184-
185-
if (0 != gai_error) {
186-
PyErr_Format(PyExc_ValueError,
187-
"'%s' does not appear to be an IPv4 or IPv6 address.",
188-
ip_address);
189-
190-
#if PY_MAJOR_VERSION >= 3
191-
Py_XDECREF(bytes);
192-
#endif
193-
return NULL;
194-
}
138+
MMDB_lookup_sockaddr(mmdb, ip_address, &mmdb_error);
195139

196140
if (MMDB_SUCCESS != mmdb_error) {
197141
PyObject *exception;
@@ -200,11 +144,10 @@ static PyObject *Reader_get(PyObject *self, PyObject *args)
200144
} else {
201145
exception = MaxMindDB_error;
202146
}
147+
char *ipstr = format_sockaddr(ip_address);
203148
PyErr_Format(exception, "Error looking up %s. %s",
204-
ip_address, MMDB_strerror(mmdb_error));
205-
#if PY_MAJOR_VERSION >= 3
206-
Py_XDECREF(bytes);
207-
#endif
149+
ipstr, MMDB_strerror(mmdb_error));
150+
free(ipstr);
208151
return NULL;
209152
}
210153

@@ -215,26 +158,106 @@ static PyObject *Reader_get(PyObject *self, PyObject *args)
215158
MMDB_entry_data_list_s *entry_data_list = NULL;
216159
int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
217160
if (MMDB_SUCCESS != status) {
161+
char *ipstr = format_sockaddr(ip_address);
218162
PyErr_Format(MaxMindDB_error,
219163
"Error while looking up data for %s. %s",
220-
ip_address, MMDB_strerror(status));
164+
ipstr, MMDB_strerror(status));
165+
free(ipstr);
221166
MMDB_free_entry_data_list(entry_data_list);
222-
#if PY_MAJOR_VERSION >= 3
223-
Py_XDECREF(bytes);
224-
#endif
225167
return NULL;
226168
}
227169

228-
#if PY_MAJOR_VERSION >= 3
229-
Py_XDECREF(bytes);
230-
#endif
231-
232170
MMDB_entry_data_list_s *original_entry_data_list = entry_data_list;
233171
PyObject *py_obj = from_entry_data_list(&entry_data_list);
234172
MMDB_free_entry_data_list(original_entry_data_list);
235173
return py_obj;
236174
}
237175

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

0 commit comments

Comments
 (0)