From 20da5c8986702831a135b5e3e81d3d4a9c048feb Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Fri, 1 Mar 2013 23:27:56 +0200 Subject: [PATCH] wpaspy: Add Python bindings for wpa_ctrl This allows Python to be used to control wpa_supplicant/hostapd through the control interface. Signed-hostap: Jouni Malinen --- wpaspy/Makefile | 14 ++++ wpaspy/setup.py | 22 +++++ wpaspy/test.py | 70 ++++++++++++++++ wpaspy/wpaspy.c | 214 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 wpaspy/Makefile create mode 100644 wpaspy/setup.py create mode 100755 wpaspy/test.py create mode 100644 wpaspy/wpaspy.c diff --git a/wpaspy/Makefile b/wpaspy/Makefile new file mode 100644 index 000000000..bc920e0cc --- /dev/null +++ b/wpaspy/Makefile @@ -0,0 +1,14 @@ +all: build + +SRC=wpaspy.c + +build: $(SRC) setup.py + python setup.py build + +install: + python setup.py install + +clean: + python setup.py clean + rm -f *~ + rm -rf build diff --git a/wpaspy/setup.py b/wpaspy/setup.py new file mode 100644 index 000000000..4dbf76540 --- /dev/null +++ b/wpaspy/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +# +# Python bindings for wpa_ctrl (wpa_supplicant/hostapd control interface) +# Copyright (c) 2013, Jouni Malinen +# +# This software may be distributed under the terms of the BSD license. +# See README for more details. + +from distutils.core import setup, Extension + +ext = Extension(name = 'wpaspy', + sources = ['../src/common/wpa_ctrl.c', + '../src/utils/os_unix.c', + 'wpaspy.c'], + extra_compile_args = ["-I../src/common", + "-I../src/utils", + "-DCONFIG_CTRL_IFACE", + "-DCONFIG_CTRL_IFACE_UNIX"]) + +setup(name = 'wpaspy', + ext_modules = [ext], + description = 'Python bindings for wpa_ctrl (wpa_supplicant/hostapd)') diff --git a/wpaspy/test.py b/wpaspy/test.py new file mode 100755 index 000000000..2a5e409d9 --- /dev/null +++ b/wpaspy/test.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# +# Test script for wpaspy +# Copyright (c) 2013, Jouni Malinen +# +# This software may be distributed under the terms of the BSD license. +# See README for more details. + +import os +import time +import wpaspy + +wpas_ctrl = '/var/run/wpa_supplicant' + +def wpas_connect(): + ifaces = [] + if os.path.isdir(wpas_ctrl): + try: + ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)] + except OSError, error: + print "Could not find wpa_supplicant: ", error + return None + + if len(ifaces) < 1: + print "No wpa_supplicant control interface found" + return None + + for ctrl in ifaces: + try: + wpas = wpaspy.Ctrl(ctrl) + return wpas + except wpactrl.error, error: + print "Error: ", error + pass + return None + + +def main(): + print "Testing wpa_supplicant control interface connection" + wpas = wpas_connect() + if wpas is None: + return + print "Connected to wpa_supplicant" + print wpas.request('PING') + + mon = wpas_connect() + if mon is None: + print "Could not open event monitor connection" + return + + mon.attach() + print "Scan" + print wpas.request('SCAN') + + count = 0 + while count < 10: + count += 1 + time.sleep(1) + while mon.pending(): + ev = mon.recv() + print ev + if 'CTRL-EVENT-SCAN-RESULTS' in ev: + print 'Scan completed' + print wpas.request('SCAN_RESULTS') + count = 10 + pass + + +if __name__ == "__main__": + main() diff --git a/wpaspy/wpaspy.c b/wpaspy/wpaspy.c new file mode 100644 index 000000000..278089b48 --- /dev/null +++ b/wpaspy/wpaspy.c @@ -0,0 +1,214 @@ +/* + * Python bindings for wpa_ctrl (wpa_supplicant/hostapd control interface) + * Copyright (c) 2013, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include +#include + +#include "wpa_ctrl.h" + + +struct wpaspy_obj { + PyObject_HEAD + struct wpa_ctrl *ctrl; + int attached; +}; + +static PyObject *wpaspy_error; + + +static int wpaspy_open(struct wpaspy_obj *self, PyObject *args) +{ + const char *path; + + if (!PyArg_ParseTuple(args, "s", &path)) + return -1; + self->ctrl = wpa_ctrl_open(path); + if (self->ctrl == NULL) + return -1; + self->attached = 0; + return 0; +} + + +static void wpaspy_close(struct wpaspy_obj *self) +{ + if (self->ctrl) { + if (self->attached) + wpa_ctrl_detach(self->ctrl); + wpa_ctrl_close(self->ctrl); + self->ctrl = NULL; + } + + if (self->ob_type) + self->ob_type->tp_free((PyObject *) self); +} + + +static PyObject * wpaspy_request(struct wpaspy_obj *self, PyObject *args) +{ + const char *cmd; + char buf[4096]; + size_t buflen; + int ret; + + if (!PyArg_ParseTuple(args, "s", &cmd)) + return NULL; + + buflen = sizeof(buf) - 1; + ret = wpa_ctrl_request(self->ctrl, cmd, strlen(cmd), buf, &buflen, + NULL); + if (ret == -2) { + PyErr_SetString(wpaspy_error, "Request timed out"); + return NULL; + } + if (ret) { + PyErr_SetString(wpaspy_error, "Request failed"); + return NULL; + } + + buf[buflen] = '\0'; + return Py_BuildValue("s", buf); +} + + +static PyObject * wpaspy_attach(struct wpaspy_obj *self) +{ + int ret; + + if (self->attached) + Py_RETURN_NONE; + + ret = wpa_ctrl_attach(self->ctrl); + if (ret) { + PyErr_SetString(wpaspy_error, "Attach failed"); + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject * wpaspy_detach(struct wpaspy_obj *self) +{ + int ret; + + if (!self->attached) + Py_RETURN_NONE; + + ret = wpa_ctrl_detach(self->ctrl); + if (ret) { + PyErr_SetString(wpaspy_error, "Detach failed"); + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject * wpaspy_pending(struct wpaspy_obj *self) +{ + switch (wpa_ctrl_pending(self->ctrl)) { + case 1: + Py_RETURN_TRUE; + case 0: + Py_RETURN_FALSE; + default: + PyErr_SetString(wpaspy_error, "wpa_ctrl_pending failed"); + break; + } + + return NULL; +} + + +static PyObject * wpaspy_recv(struct wpaspy_obj *self) +{ + int ret; + char buf[4096]; + size_t buflen; + + buflen = sizeof(buf) - 1; + Py_BEGIN_ALLOW_THREADS + ret = wpa_ctrl_recv(self->ctrl, buf, &buflen); + Py_END_ALLOW_THREADS + + if (ret) { + PyErr_SetString(wpaspy_error, "wpa_ctrl_recv failed"); + return NULL; + } + + buf[buflen] = '\0'; + return Py_BuildValue("s", buf); +} + + +static PyMethodDef wpaspy_methods[] = { + { + "request", (PyCFunction) wpaspy_request, METH_VARARGS, + "Send a control interface command and return response" + }, + { + "attach", (PyCFunction) wpaspy_attach, METH_NOARGS, + "Attach as an event monitor" + }, + { + "detach", (PyCFunction) wpaspy_detach, METH_NOARGS, + "Detach an event monitor" + }, + { + "pending", (PyCFunction) wpaspy_pending, METH_NOARGS, + "Check whether any events are pending" + }, + { + "recv", (PyCFunction) wpaspy_recv, METH_NOARGS, + "Received pending event" + }, + { NULL, NULL, 0, NULL } +}; + +static PyMemberDef wpaspy_members[] = { + { + "attached", T_INT, offsetof(struct wpaspy_obj, attached), + READONLY, + "Whether instance is attached as event monitor" + }, + { NULL } +}; + +static PyTypeObject wpaspy_ctrl = { + PyObject_HEAD_INIT(NULL) + .tp_name = "wpaspy.Ctrl", + .tp_basicsize = sizeof(struct wpaspy_obj), + .tp_getattro = PyObject_GenericGetAttr, + .tp_setattro = PyObject_GenericSetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_methods = wpaspy_methods, + .tp_members = wpaspy_members, + .tp_init = (initproc) wpaspy_open, + .tp_dealloc = (destructor) wpaspy_close, + .tp_new = PyType_GenericNew, +}; + + +static PyMethodDef module_methods[] = { + { NULL, NULL, 0, NULL } +}; + + +PyMODINIT_FUNC initwpaspy(void) +{ + PyObject *mod; + + PyType_Ready(&wpaspy_ctrl); + mod = Py_InitModule("wpaspy", module_methods); + wpaspy_error = PyErr_NewException("wpaspy.error", NULL, NULL); + + Py_INCREF(&wpaspy_ctrl); + Py_INCREF(wpaspy_error); + + PyModule_AddObject(mod, "Ctrl", (PyObject *) &wpaspy_ctrl); + PyModule_AddObject(mod, "error", wpaspy_error); +}