├── examples ├── clean-log.sed ├── capture-packets │ ├── keahook.py │ ├── capture.py │ └── kea.conf ├── host-cmds │ ├── README.md │ ├── kea.conf │ ├── sendcmd.py │ └── keahook.py ├── cb-cmds │ ├── README.md │ ├── kea.conf │ └── sendcmd.py ├── stress-test │ ├── kea.conf │ ├── README.md │ ├── keahook.py │ └── stress_test.py ├── facebook-trick │ ├── README.md │ ├── kea.conf │ └── keahook.py └── lease-cmds │ ├── kea-reference.conf │ ├── kea.conf │ └── README.md ├── keahook ├── version.cc ├── messages.mes ├── messages.h ├── Makefile ├── messages.cc └── load_unload.cc ├── .gitignore ├── keamodule ├── tests │ ├── test_callout_manager.py │ ├── test_lease_mgr.py │ ├── test_client_class_dictionary.py │ ├── test_host_mgr.py │ ├── test_cfg_mgr.py │ ├── test_subnet4_config_parser.py │ ├── test_library_handle.py │ ├── test_client_class_def_parser.py │ ├── test_config_backend_dhcpv4_mgr.py │ ├── test_server.py │ ├── test_client_class_def.py │ ├── test_srv_config.py │ ├── utils.py │ ├── test_cfg_subnets4.py │ ├── test_subnet4.py │ ├── test_option.py │ └── test_lease4.py ├── keacapsule.h ├── kea.cc ├── logger_manager.cc ├── callout_manager.cc ├── setup.py ├── subnet4_config_parser.cc ├── host_reservation_parser4.cc ├── cfg_mgr.cc ├── client_class_def_parser.cc ├── config_backend_dhcp4_mgr.cc ├── srv_config.cc ├── errors.cc ├── callout_closure.cc ├── client_class_dictionary.cc ├── callouts.cc ├── client_class_def.cc ├── server.cc ├── library_handle.cc ├── host.cc ├── constants.cc ├── utils.cc ├── host_mgr.cc └── cfg_subnets4.cc ├── dhtest └── Dockerfile ├── Dockerfile ├── DockerfileDev ├── settings.py └── Makefile /examples/clean-log.sed: -------------------------------------------------------------------------------- 1 | s/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3} // 2 | s#/[0-9]*\]#\]# 3 | s/tid=0x[0-9a-f]*/tid=0x01020304/ 4 | -------------------------------------------------------------------------------- /keahook/version.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" { 4 | 5 | int version() { 6 | return (KEA_HOOKS_VERSION); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | kea-*.tar.gz 2 | kea-*.tar.xz 3 | libkea_python.so 4 | __pycache__ 5 | build 6 | kea.egg-info 7 | *.log 8 | *.log.lock 9 | .vscode 10 | settings.mk 11 | dhcpdb_create-*.mysql.sql 12 | -------------------------------------------------------------------------------- /keahook/messages.mes: -------------------------------------------------------------------------------- 1 | $NAMESPACE isc::log 2 | 3 | % LOG_PYTHON_HOOK %1 4 | There was a problem loading kea_python.so 5 | 6 | % LOG_PYTHON %1 7 | Log message from the Python module loaded by kea_python.so 8 | -------------------------------------------------------------------------------- /keamodule/tests/test_callout_manager.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestCalloutManager_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_no_arguments(kea.CalloutManager) 9 | 10 | def test_ok(self): 11 | m = kea.CalloutManager() 12 | self.assertEqual(1, m.use_count) 13 | -------------------------------------------------------------------------------- /keahook/messages.h: -------------------------------------------------------------------------------- 1 | // File created from messages.mes on Thu Jan 02 2020 00:21 2 | 3 | #ifndef MESSAGES_H 4 | #define MESSAGES_H 5 | 6 | #include 7 | 8 | namespace isc { 9 | namespace log { 10 | 11 | extern const isc::log::MessageID LOG_PYTHON; 12 | extern const isc::log::MessageID LOG_PYTHON_HOOK; 13 | 14 | } // namespace log 15 | } // namespace isc 16 | 17 | #endif // MESSAGES_H 18 | -------------------------------------------------------------------------------- /examples/capture-packets/keahook.py: -------------------------------------------------------------------------------- 1 | from kea import * 2 | from capture import Capture 3 | 4 | 5 | def load(handle): 6 | global log 7 | log = Capture() 8 | return 0 9 | 10 | 11 | def unload(): 12 | log.unload() 13 | return 0 14 | 15 | 16 | def pkt4_receive(handle): 17 | log.pkt4_receive(handle) 18 | return 0 19 | 20 | 21 | def pkt4_send(handle): 22 | log.pkt4_send(handle) 23 | return 0 24 | -------------------------------------------------------------------------------- /keamodule/tests/test_lease_mgr.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestLeaseMgr_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_no_arguments(kea.LeaseMgr) 9 | 10 | def test_ok(self): 11 | with self.assertRaises(TypeError) as cm: 12 | kea.LeaseMgr() 13 | self.assertEqual(("no current lease manager is available",), cm.exception.args) 14 | -------------------------------------------------------------------------------- /dhtest/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch-slim AS build 2 | ENV DEBIAN_FRONTEND noninteractive 3 | 4 | WORKDIR /source 5 | 6 | RUN apt-get update -y \ 7 | && apt-get -y install \ 8 | wget \ 9 | unzip \ 10 | make \ 11 | gcc \ 12 | && wget https://github.com/saravana815/dhtest/archive/master.zip \ 13 | && unzip master.zip \ 14 | && cd dhtest-master \ 15 | && make 16 | 17 | FROM debian:stretch-slim 18 | 19 | RUN apt-get update -y \ 20 | && apt-get -y install \ 21 | python3 \ 22 | procps 23 | 24 | COPY --from=build /source/dhtest-master/dhtest /usr/local/bin 25 | -------------------------------------------------------------------------------- /keahook/Makefile: -------------------------------------------------------------------------------- 1 | include ../settings.mk 2 | 3 | ifeq ($(wildcard $(KEA_LIBS)/libkea-dhcp.so),) 4 | KEA_DHCP=kea-dhcp++ 5 | else 6 | KEA_DHCP=kea-dhcp 7 | endif 8 | 9 | libkea_python.so: load_unload.cc messages.cc version.cc 10 | g++ -std=c++14 -I $(KEA_INC) -I $(PYTHON_INC) -L /usr/local/lib -fpic -shared -o libkea_python.so \ 11 | load_unload.cc messages.cc version.cc \ 12 | -lkea-dhcpsrv -l$(KEA_DHCP) -lkea-hooks -lkea-log -lkea-util -lkea-exceptions 13 | 14 | messages.cc: messages.mes 15 | kea-msg-compiler messages.mes 16 | 17 | clean: 18 | rm -f libkea_python.so 19 | 20 | install: libkea_python.so 21 | install libkea_python.so $(KEA_HOOKS) 22 | -------------------------------------------------------------------------------- /keahook/messages.cc: -------------------------------------------------------------------------------- 1 | // File created from messages.mes on Thu Jan 02 2020 00:21 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace isc { 8 | namespace log { 9 | 10 | extern const isc::log::MessageID LOG_PYTHON = "LOG_PYTHON"; 11 | extern const isc::log::MessageID LOG_PYTHON_HOOK = "LOG_PYTHON_HOOK"; 12 | 13 | } // namespace log 14 | } // namespace isc 15 | 16 | namespace { 17 | 18 | const char* values[] = { 19 | "LOG_PYTHON", "%1", 20 | "LOG_PYTHON_HOOK", "%1", 21 | NULL 22 | }; 23 | 24 | const isc::log::MessageInitializer initializer(values); 25 | 26 | } // Anonymous namespace 27 | 28 | -------------------------------------------------------------------------------- /keamodule/tests/test_client_class_dictionary.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestClientClassDictionary_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_no_arguments(kea.ClientClassDictionary) 9 | 10 | 11 | class TestClientClassDictionary_getClasses(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | cc_dict = kea.ClientClassDictionary() 15 | self.assert_method_no_arguments(cc_dict.getClasses) 16 | 17 | def test_ok(self): 18 | cc_dict = kea.ClientClassDictionary() 19 | classes = cc_dict.getClasses() 20 | self.assertEqual([], classes) 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VER=latest 2 | FROM kea-dev:${VER} AS build 3 | 4 | WORKDIR /source 5 | COPY . . 6 | 7 | RUN make clean install \ 8 | && mkdir /dist \ 9 | && cd /usr/local \ 10 | && find . -name \*.so\* | tar cf - -T - | (cd /dist; tar xf -) \ 11 | && tar cf - etc share/man bin sbin var | (cd /dist; tar xf -) 12 | 13 | FROM ubuntu:24.04 14 | ENV DEBIAN_FRONTEND=noninteractive 15 | 16 | RUN apt-get update -y \ 17 | && apt-get -y install \ 18 | procps \ 19 | socat \ 20 | python3 \ 21 | libpython3.12 \ 22 | liblog4cplus-2.0.5t64 \ 23 | libboost-system1.83.0 \ 24 | libffi8ubuntu1 \ 25 | libpq5 \ 26 | libmariadb3 27 | 28 | COPY --from=build /dist /usr/local 29 | 30 | RUN ldconfig 31 | -------------------------------------------------------------------------------- /examples/capture-packets/capture.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class Capture: 5 | 6 | def __init__(self, fname=None): 7 | if fname is None: 8 | dirname = os.path.dirname(__file__) 9 | fname = os.path.join(dirname, 'packets.log') 10 | self.fp = open(fname, 'w') 11 | 12 | def unload(self): 13 | self.fp.close() 14 | return 0 15 | 16 | def pkt4_receive(self, handle): 17 | self.fp.write('>> query4\n') 18 | self.fp.write(handle.getArgument('query4').toText()) 19 | self.fp.write('\n\n') 20 | return 0 21 | 22 | def pkt4_send(self, handle): 23 | self.fp.write('<< response4\n') 24 | self.fp.write(handle.getArgument('response4').toText()) 25 | self.fp.write('\n\n') 26 | return 0 27 | -------------------------------------------------------------------------------- /keamodule/tests/test_host_mgr.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestHostMgr_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_cannot_construct(kea.HostMgr) 9 | 10 | 11 | class TestHostMgr_instance(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | self.assert_method_no_arguments(kea.HostMgr.instance) 15 | 16 | def test_ok(self): 17 | m = kea.HostMgr.instance() 18 | self.assertIsInstance(m, kea.HostMgr) 19 | 20 | 21 | class TestHostMgr_add(utils.BaseTestCase): 22 | 23 | def test_badarg_count(self): 24 | m = kea.HostMgr.instance() 25 | self.assert_method_one_arg_no_keywords(m.add) 26 | 27 | def test_badarg_type(self): 28 | m = kea.HostMgr.instance() 29 | with self.assertRaises(TypeError) as cm: 30 | m.add('foo') 31 | self.assertEqual(("argument 1 must be kea.Host, not str",), cm.exception.args) 32 | -------------------------------------------------------------------------------- /examples/host-cmds/README.md: -------------------------------------------------------------------------------- 1 | # host-cmds 2 | This example was implemented by following the REST interface described in the Kea 3 | documentation at https://kea.readthedocs.io/en/kea-1.8.2/arm/hooks.html#host-cmds-host-commands. 4 | 5 | Run a MySQL instance with the host reservations schema. Kea will not start if it cannot connect 6 | to the database. 7 | ``` 8 | djc@laptop:~/play/kea_python$ make run-mysql 9 | ``` 10 | 11 | Start the kea image and run the Python implementation of the `host-cmds` hook.: 12 | ``` 13 | djc@laptop:~/play/kea_python$ make run-kea 14 | root@ae43fd3e78da:/source# cd /workdir 15 | root@ae43fd3e78da:/workdir# /usr/local/sbin/kea-dhcp4 -c examples/host-cmds/kea.conf 16 | ``` 17 | 18 | Then exec a bash shell in the kea container and run the `sendcmd.py` program: 19 | ``` 20 | djc@laptop:~/play/kea_python$ docker exec -it kea bash 21 | root@4cebcb709530:/# cd /workdir/examples/host-cmds/ 22 | root@4cebcb709530:/workdir/examples/host-cmds# python3 -B sendcmd.py 23 | ``` 24 | -------------------------------------------------------------------------------- /keamodule/keacapsule.h: -------------------------------------------------------------------------------- 1 | #ifndef KEA_CAPSULE_H 2 | #define KEA_CAPSULE_H 3 | 4 | extern "C" { 5 | 6 | #define Kea_SetLogger_NUM 0 7 | #define Kea_SetLogger_RETURN void 8 | #define Kea_SetLogger_PROTO (isc::log::Logger &logger, const isc::log::MessageID& ident) 9 | 10 | #define Kea_Load_NUM 1 11 | #define Kea_Load_RETURN int 12 | #define Kea_Load_PROTO (isc::hooks::LibraryHandle *handle, const char *module_name) 13 | 14 | #define Kea_Unload_NUM 2 15 | #define Kea_Unload_RETURN int 16 | #define Kea_Unload_PROTO () 17 | 18 | #define Kea_API_pointers 3 19 | 20 | #ifdef KEA_MODULE 21 | static Kea_Load_RETURN Kea_Load Kea_Load_PROTO; 22 | static Kea_Unload_RETURN Kea_Unload Kea_Unload_PROTO; 23 | #else 24 | static void **kea_capsule; 25 | 26 | #define Kea_SetLogger (*(Kea_SetLogger_RETURN (*)Kea_SetLogger_PROTO) kea_capsule[Kea_SetLogger_NUM]) 27 | #define Kea_Load (*(Kea_Load_RETURN (*)Kea_Load_PROTO) kea_capsule[Kea_Load_NUM]) 28 | #define Kea_Unload (*(Kea_Unload_RETURN (*)Kea_Unload_PROTO) kea_capsule[Kea_Unload_NUM]) 29 | #endif 30 | 31 | } 32 | 33 | #endif /* !defined(KEA_CAPSULE_H) */ 34 | -------------------------------------------------------------------------------- /examples/cb-cmds/README.md: -------------------------------------------------------------------------------- 1 | # host-cmds 2 | This example was implemented by following the REST interface described in the Kea 3 | documentation at https://kea.readthedocs.io/en/kea-2.2.0/arm/hooks.html#cb-cmds-configuration-backend-commands. 4 | 5 | Run a MySQL instance with the host reservations schema. Kea will not start if it cannot connect 6 | to the database. 7 | ``` 8 | djc@laptop:~/play/kea_python$ make run-mysql 9 | ``` 10 | 11 | Start the kea image and run the Python implementation of the `cb-cmds` hook.: 12 | ``` 13 | djc@laptop:~/play/kea_python$ make run-kea 14 | root@ae43fd3e78da:/workdir# /usr/local/sbin/kea-dhcp4 -c examples/cb-cmds/kea.conf 15 | ``` 16 | 17 | Then exec a bash shell in the kea container and run the `sendcmd.py` program: 18 | ``` 19 | djc@laptop:~/play/kea_python$ docker exec -it kea bash 20 | root@4cebcb709530:/# cd /workdir/examples/cb-cmds/ 21 | root@4cebcb709530:/workdir/examples/host-cmds# python3 -B sendcmd.py 22 | ``` 23 | 24 | Try a DHCP request 25 | ``` 26 | djc@laptop:~/play/kea_python$ make run-dhtest 27 | root@9bf4e713d72c:/# dhtest -i eth0 -m 02:42:ac:1c:05:02 28 | ``` 29 | -------------------------------------------------------------------------------- /examples/stress-test/kea.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "interfaces-config": { 4 | "interfaces": [ "eth0" ], 5 | "dhcp-socket-type": "raw" 6 | }, 7 | "valid-lifetime": 4000, 8 | "renew-timer": 1000, 9 | "rebind-timer": 2000, 10 | "subnet4": [{ 11 | "subnet": "0.0.0.0/0", 12 | "pools": [ { "pool": "0.0.0.0 - 255.255.255.255" } ] 13 | }], 14 | "hooks-libraries": [{ 15 | "library": "/usr/local/lib/kea/hooks/libkea_python.so", 16 | "parameters": { 17 | "libpython": "libpython3.10.so.1", 18 | "module": "/workdir/examples/stress-test/keahook.py" 19 | } 20 | }], 21 | "lease-database": { 22 | "persist": false, 23 | "type": "memfile" 24 | }, 25 | "loggers": [{ 26 | "name": "kea-dhcp4", 27 | "output_options": [{ 28 | "output": "stdout", 29 | "flush": true, 30 | "pattern": "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 31 | }], 32 | "severity": "INFO" 33 | }] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /keamodule/tests/test_cfg_mgr.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestCfgMgr_new(utils.BaseTestCase): 6 | 7 | def test_ok(self): 8 | self.assert_cannot_construct(kea.CfgMgr) 9 | 10 | 11 | class TestCfgMgr_instance(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | self.assert_method_no_arguments(kea.CfgMgr.instance) 15 | 16 | def test_ok(self): 17 | m = kea.CfgMgr.instance() 18 | self.assertIsInstance(m, kea.CfgMgr) 19 | 20 | 21 | class TestCfgMgr_getCurrentCfg(utils.BaseTestCase): 22 | 23 | def test_badarg_count(self): 24 | m = kea.CfgMgr.instance() 25 | self.assert_method_no_arguments(m.getCurrentCfg) 26 | 27 | def test_ok(self): 28 | m = kea.CfgMgr.instance() 29 | c = m.getCurrentCfg() 30 | self.assertIsInstance(c, kea.SrvConfig) 31 | 32 | 33 | class TestCfgMgr_getStagingCfg(utils.BaseTestCase): 34 | 35 | def test_badarg_count(self): 36 | m = kea.CfgMgr.instance() 37 | self.assert_method_no_arguments(m.getStagingCfg) 38 | 39 | def test_ok(self): 40 | m = kea.CfgMgr.instance() 41 | c = m.getStagingCfg() 42 | self.assertIsInstance(c, kea.SrvConfig) 43 | -------------------------------------------------------------------------------- /examples/facebook-trick/README.md: -------------------------------------------------------------------------------- 1 | # facebook-trick 2 | Implement the function described by facebook in this post: 3 | https://code.facebook.com/posts/845909058837784/using-isc-kea-dhcp-in-our-data-centers/ 4 | 5 | Unfortunately the behaviour they describe is for a pre 1.0 release of Kea. Their trick 6 | no longer works without changes. When the following code runs in `lease4_select()`: 7 | ```python 8 | if query.getType() == DHCPREQUEST: 9 | handle.setStatus(NEXT_STEP_SKIP) 10 | ``` 11 | Kea decides to respond with a NAK packet because you are telling it not to save the lease. 12 | The `pkt4_send()` callout handles this by replacing the NAK with an ACK. 13 | 14 | Start the kea image: 15 | ``` 16 | djc@laptop:~/play/kea_python$ make run-kea 17 | root@9b06fba64b09:/# cd /workdir/ 18 | root@9b06fba64b09:/workdir# /usr/local/sbin/kea-dhcp4 -c examples/facebook-trick/kea.conf 19 | ``` 20 | 21 | Run the dhtest image: 22 | ``` 23 | djc@laptop:~/play/kea_python$ make run-dhtest 24 | root@95d5aa56a590:/# dhtest -i eth0 25 | DHCP discover sent - Client MAC : 02:42:ac:1c:05:02 26 | DHCP offer received - Offered IP : 172.28.5.42 27 | DHCP request sent - Client MAC : 02:42:ac:1c:05:02 28 | DHCP ack received - Acquired IP: 172.28.5.42 29 | ``` 30 | -------------------------------------------------------------------------------- /keamodule/tests/test_subnet4_config_parser.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestSubnet4ConfigParser_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_no_arguments(kea.Subnet4ConfigParser) 9 | 10 | 11 | class TestSubnet4ConfigParser_parse(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | p = kea.Subnet4ConfigParser() 15 | self.assert_method_one_arg_no_keywords(p.parse) 16 | 17 | def test_badarg_type(self): 18 | p = kea.Subnet4ConfigParser() 19 | with self.assertRaises(TypeError) as cm: 20 | p.parse(None) 21 | self.assertEqual(("mapValue() called on non-map Element",), cm.exception.args) 22 | 23 | def test_ok(self): 24 | p = kea.Subnet4ConfigParser() 25 | s = p.parse({'subnet': '192.168.1.0/24', 26 | 'id': 1}) 27 | self.assertEqual({'4o6-interface': '', 28 | '4o6-interface-id': '', 29 | '4o6-subnet': '', 30 | 'id': 1, 31 | 'option-data': [], 32 | 'pools': [], 33 | 'relay': {'ip-addresses': []}, 34 | 'subnet': '192.168.1.0/24'}, s.toElement()) 35 | -------------------------------------------------------------------------------- /examples/facebook-trick/kea.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "interfaces-config": { 4 | "interfaces": [ "eth0" ], 5 | "dhcp-socket-type": "raw" 6 | }, 7 | "multi-threading": { 8 | "enable-multi-threading": false 9 | }, 10 | "valid-lifetime": 4000, 11 | "renew-timer": 1000, 12 | "rebind-timer": 2000, 13 | "subnet4": [{ 14 | "subnet": "0.0.0.0/0", 15 | "pools": [ { "pool": "0.0.0.0 - 255.255.255.255" } ] 16 | }], 17 | "hooks-libraries": [{ 18 | "library": "/usr/local/lib/kea/hooks/libkea_python.so", 19 | "parameters": { 20 | "libpython": "libpython3.10.so.1", 21 | "module": "/workdir/examples/facebook-trick/keahook.py" 22 | } 23 | }], 24 | "lease-database": { 25 | "persist": false, 26 | "type": "memfile" 27 | }, 28 | "loggers": [{ 29 | "name": "kea-dhcp4", 30 | "output_options": [{ 31 | "output": "stdout", 32 | "flush": true, 33 | "pattern": "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 34 | }], 35 | "severity": "DEBUG", 36 | "debuglevel": 50 37 | }] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/capture-packets/kea.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "interfaces-config": { 4 | "interfaces": [ "eth0" ], 5 | "dhcp-socket-type": "raw" 6 | }, 7 | "multi-threading": { 8 | "enable-multi-threading": false 9 | }, 10 | "valid-lifetime": 4000, 11 | "renew-timer": 1000, 12 | "rebind-timer": 2000, 13 | "subnet4": [{ 14 | "subnet": "172.28.5.0/24", 15 | "pools": [ { "pool": "172.28.5.10 - 172.28.5.200" } ] 16 | }], 17 | "hooks-libraries": [{ 18 | "library": "/usr/local/lib/kea/hooks/libkea_python.so", 19 | "parameters": { 20 | "libpython": "libpython3.9.so.1", 21 | "module": "/workdir/examples/capture-packets/keahook.py" 22 | } 23 | }], 24 | "lease-database": { 25 | "persist": false, 26 | "type": "memfile" 27 | }, 28 | "loggers": [{ 29 | "name": "kea-dhcp4", 30 | "output_options": [{ 31 | "output": "stdout", 32 | "flush": true, 33 | "pattern": "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 34 | }], 35 | "severity": "DEBUG", 36 | "debuglevel": 50 37 | }] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/lease-cmds/kea-reference.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "control-socket": { 4 | "socket-type": "unix", 5 | "socket-name": "/usr/local/var/run/kea/kea4.sock" 6 | }, 7 | "interfaces-config": { 8 | "interfaces": [ "eth0" ], 9 | "dhcp-socket-type": "raw" 10 | }, 11 | "valid-lifetime": 4000, 12 | "renew-timer": 1000, 13 | "rebind-timer": 2000, 14 | "subnet4": [{ 15 | "subnet": "172.28.5.0/24", 16 | "id": 5, 17 | "pools": [ { "pool": "172.28.5.10 - 172.28.5.200" } ] 18 | }, { 19 | "subnet": "172.28.6.0/24", 20 | "id": 6, 21 | "pools": [ { "pool": "172.28.6.10 - 172.28.6.200" } ] 22 | }], 23 | "hooks-libraries": [{ 24 | "library": "/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so" 25 | }], 26 | "lease-database": { 27 | "persist": false, 28 | "type": "memfile" 29 | }, 30 | "loggers": [{ 31 | "name": "kea-dhcp4", 32 | "output_options": [{ 33 | "output": "stdout", 34 | "flush": true, 35 | "pattern": "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 36 | }], 37 | "severity": "DEBUG", 38 | "debuglevel": 50 39 | }] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DockerfileDev: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | ARG VER 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | WORKDIR /source 6 | COPY kea-$VER.tar.* . 7 | 8 | RUN apt-get update -y \ 9 | && apt-get -y install --no-install-recommends \ 10 | procps \ 11 | make \ 12 | socat \ 13 | xz-utils \ 14 | pkg-config \ 15 | g++ \ 16 | g++14 \ 17 | gdb \ 18 | flex \ 19 | bison \ 20 | libffi-dev \ 21 | libboost-dev \ 22 | libboost-system-dev \ 23 | libssl-dev \ 24 | libkrb5-dev \ 25 | libreadline-dev \ 26 | libpam-dev \ 27 | libzstd-dev \ 28 | liblz4-dev \ 29 | libselinux-dev \ 30 | libxslt-dev \ 31 | libgtest-dev \ 32 | liblog4cplus-dev \ 33 | libpython3-dev \ 34 | python3 \ 35 | python3-pip \ 36 | python3-setuptools \ 37 | meson-1.5 \ 38 | libpq-dev \ 39 | postgresql-server-dev-all \ 40 | libmariadb-dev \ 41 | libmariadb-dev-compat \ 42 | && tar xf kea-$VER.tar.* \ 43 | && cd kea-$VER \ 44 | && meson setup build \ 45 | && meson compile -C build \ 46 | && meson install -C build \ 47 | && if [ ! -f /usr/local/include/kea/eval/location.hh ]; then \ 48 | cp src/lib/eval/location.hh /usr/local/include/kea/eval/; \ 49 | fi \ 50 | && echo /usr/local/lib64 > /etc/ld.so.conf.d/kea.conf \ 51 | && ldconfig 52 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def find_pyinc(): 6 | path = os.path.join(sys.prefix, 'include/python%d.%d' % sys.version_info[:2]) 7 | if os.path.isdir(path): 8 | return path 9 | path = os.path.join(sys.prefix, 'include/python%d.%dm' % sys.version_info[:2]) 10 | if os.path.isdir(path): 11 | return path 12 | raise RuntimeError('cannot find python include') 13 | 14 | 15 | def find_keainc(): 16 | for path in ['/usr/local/include/kea', 17 | '/usr/include/kea']: 18 | if os.path.isdir(path): 19 | return path 20 | raise RuntimeError('cannot find kea include') 21 | 22 | 23 | def find_keahooks(): 24 | for path in ['/usr/local/lib/kea/hooks', 25 | '/usr/local/lib64/kea/hooks', 26 | '/usr/lib64/kea/hooks']: 27 | if os.path.isdir(path): 28 | return path 29 | raise RuntimeError('cannot find kea hooks') 30 | 31 | 32 | def find_kealibs(): 33 | for path in ['/usr/local/lib', 34 | '/usr/local/lib64', 35 | '/usr/lib64']: 36 | if os.path.exists(os.path.join(path, 'libkea-hooks.so')): 37 | return path 38 | raise RuntimeError('cannot locate kea library directory') 39 | 40 | 41 | with open('settings.mk', 'w') as fp: 42 | fp.write('PYTHON_INC = %s\n' % find_pyinc()) 43 | fp.write('KEA_INC = %s\n' % find_keainc()) 44 | fp.write('KEA_HOOKS = %s\n' % find_keahooks()) 45 | fp.write('KEA_LIBS = %s\n' % find_kealibs()) 46 | -------------------------------------------------------------------------------- /examples/lease-cmds/kea.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "control-socket": { 4 | "socket-type": "unix", 5 | "socket-name": "/usr/local/var/run/kea/kea4.sock" 6 | }, 7 | "interfaces-config": { 8 | "interfaces": [ "eth0" ], 9 | "dhcp-socket-type": "raw" 10 | }, 11 | "valid-lifetime": 4000, 12 | "renew-timer": 1000, 13 | "rebind-timer": 2000, 14 | "subnet4": [{ 15 | "subnet": "172.28.5.0/24", 16 | "id": 5, 17 | "pools": [ { "pool": "172.28.5.10 - 172.28.5.200" } ] 18 | }, { 19 | "subnet": "172.28.6.0/24", 20 | "id": 6, 21 | "pools": [ { "pool": "172.28.6.10 - 172.28.6.200" } ] 22 | }], 23 | "hooks-libraries": [{ 24 | "library": "/usr/local/lib/kea/hooks/libkea_python.so", 25 | "parameters": { 26 | "libpython": "libpython3.9.so.1", 27 | "module": "/workdir/examples/lease-cmds/keahook.py" 28 | } 29 | }], 30 | "lease-database": { 31 | "persist": false, 32 | "type": "memfile" 33 | }, 34 | "loggers": [{ 35 | "name": "kea-dhcp4", 36 | "output_options": [{ 37 | "output": "stdout", 38 | "flush": true, 39 | "pattern": "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 40 | }], 41 | "severity": "DEBUG", 42 | "debuglevel": 50 43 | }] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /keamodule/tests/test_library_handle.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestLibraryHandle_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_one_arg_no_keywords(kea.LibraryHandle) 9 | 10 | def test_ok(self): 11 | m = kea.CalloutManager() 12 | h = kea.LibraryHandle(m) 13 | # LibraryHandle has pointer to CalloutManager, not shared_ptr 14 | self.assertEqual(1, m.use_count) 15 | self.assertIsInstance(h, kea.LibraryHandle) 16 | 17 | 18 | class TestLibraryHandle_registerCommandCallout(utils.BaseTestCase): 19 | 20 | def test_badarg_count(self): 21 | h = kea.LibraryHandle(kea.CalloutManager()) 22 | self.assert_method_two_args_no_keywords(h.registerCommandCallout) 23 | 24 | def test_nadarg_type(self): 25 | h = kea.LibraryHandle(kea.CalloutManager()) 26 | with self.assertRaises(TypeError) as cm: 27 | h.registerCommandCallout(1, 42) 28 | self.assertEqual(("argument 1 must be str, not int",), cm.exception.args) 29 | with self.assertRaises(TypeError) as cm: 30 | h.registerCommandCallout('foo', 42) 31 | self.assertEqual(("callout must be callable",), cm.exception.args) 32 | 33 | def test_ok(self): 34 | def foo(): 35 | pass 36 | h = kea.LibraryHandle(kea.CalloutManager()) 37 | with self.assertRaises(RuntimeError) as cm: 38 | h.registerCommandCallout('foo', foo) 39 | self.assertEqual(("only supported in embedded mode",), cm.exception.args) 40 | -------------------------------------------------------------------------------- /keamodule/kea.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | 5 | extern "C" { 6 | 7 | PyObject *kea_module; 8 | 9 | static PyMethodDef kea_methods[] = { 10 | {0, 0, 0, 0} // sentinel 11 | }; 12 | 13 | static PyModuleDef kea_module_def = { 14 | PyModuleDef_HEAD_INIT, 15 | "kea", 16 | 0, 17 | -1, 18 | kea_methods 19 | }; 20 | 21 | PyMODINIT_FUNC 22 | PyInit_kea(void) { 23 | // REFCOUNT: PyModule_Create - returns new reference 24 | kea_module = PyModule_Create(&kea_module_def); 25 | if (!kea_module) { 26 | return (0); 27 | } 28 | 29 | if (PyModule_AddStringConstant(kea_module, "__version__", VERSION) < 0 30 | || CalloutClosure_define() 31 | || CalloutHandle_define() 32 | || CalloutManager_define() 33 | || Capsule_define() 34 | || CfgMgr_define() 35 | || CfgSubnets4_define() 36 | || ConfigBackendDHCPv4Mgr_define() 37 | || ConfigBackendPoolDHCPv4_define() 38 | || Constants_define() 39 | || Host_define() 40 | || HostMgr_define() 41 | || HostReservationParser4_define() 42 | || Lease4_define() 43 | || LeaseMgr_define() 44 | || LibraryHandle_define() 45 | || LoggerManager_define() 46 | || Option_define() 47 | || Pkt4_define() 48 | || Server_define() 49 | || SrvConfig_define() 50 | || Subnet4_define() 51 | || Subnet4ConfigParser_define() 52 | || ClientClassDefParser_define() 53 | || ClientClassDef_define() 54 | || ClientClassDictionary_define()) { 55 | Py_DECREF(kea_module); 56 | return (0); 57 | } 58 | 59 | return (kea_module); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /keamodule/logger_manager.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::log; 5 | 6 | extern "C" { 7 | 8 | static PyObject * 9 | LoggerManager_static_init(LoggerManagerObject *self, PyObject *args) { 10 | const char *root; 11 | 12 | if (!PyArg_ParseTuple(args, "s", &root)) { 13 | return (0); 14 | } 15 | 16 | try { 17 | LoggerManager::init(root, Severity::DEFAULT, 0, "/dev/null"); 18 | Py_RETURN_NONE; 19 | } 20 | catch (const exception &e) { 21 | PyErr_SetString(PyExc_TypeError, e.what()); 22 | return (0); 23 | } 24 | } 25 | 26 | static PyMethodDef LoggerManager_methods[] = { 27 | {"init", (PyCFunction) LoggerManager_static_init, METH_VARARGS|METH_STATIC, 28 | "Run-Time Initialization Performs run-time initialization of the logger system."}, 29 | {0} // Sentinel 30 | }; 31 | 32 | PyTypeObject LoggerManagerType = { 33 | .ob_base = PyObject_HEAD_INIT(0) 34 | .tp_name = "kea.LoggerManager", 35 | .tp_basicsize = sizeof(LoggerManagerObject), 36 | .tp_flags = Py_TPFLAGS_DEFAULT, 37 | .tp_doc = PyDoc_STR("Kea server LoggerManager"), 38 | .tp_methods = LoggerManager_methods, 39 | }; 40 | 41 | int 42 | LoggerManager_define() { 43 | // PyType_Ready - finish type initialisation 44 | if (PyType_Ready(&LoggerManagerType) < 0) { 45 | return (1); 46 | } 47 | Py_INCREF(&LoggerManagerType); 48 | // REFCOUNT: PyModule_AddObject steals reference on success 49 | if (PyModule_AddObject(kea_module, "LoggerManager", (PyObject *) &LoggerManagerType) < 0) { 50 | Py_DECREF(&LoggerManagerType); 51 | return (1); 52 | } 53 | 54 | return (0); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /keamodule/tests/test_client_class_def_parser.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestClientClassDefParser_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_no_arguments(kea.ClientClassDefParser) 9 | 10 | 11 | class TestClientClassDefParser_parse(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | p = kea.ClientClassDefParser() 15 | self.assert_method_two_args_no_keywords(p.parse) 16 | 17 | def test_badarg_type(self): 18 | p = kea.ClientClassDefParser() 19 | cc_dict = kea.ClientClassDictionary() 20 | with self.assertRaises(TypeError) as cm: 21 | p.parse(None, {}) 22 | self.assertEqual(("argument 1 must be kea.ClientClassDictionary, not NoneType",), cm.exception.args) 23 | with self.assertRaises(TypeError) as cm: 24 | p.parse(cc_dict, None) 25 | self.assertEqual(("mapValue() called on non-map Element",), cm.exception.args) 26 | 27 | def test_ok(self): 28 | p = kea.ClientClassDefParser() 29 | cc_dict = kea.ClientClassDictionary() 30 | result = p.parse(cc_dict, {'name': 'foo', 31 | 'option-def': [{ 32 | 'name': 'configfile', 33 | 'code': 224, 34 | 'type': 'string' 35 | }], 36 | 'option-data': [{ 37 | 'name': 'configfile', 38 | 'data': '1APC' 39 | }] 40 | }) 41 | self.assertIsNone(result) -------------------------------------------------------------------------------- /examples/host-cmds/kea.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "control-socket": { 4 | "socket-type": "unix", 5 | "socket-name": "/usr/local/var/run/kea/kea4.sock" 6 | }, 7 | "interfaces-config": { 8 | "interfaces": [ "eth0" ], 9 | "dhcp-socket-type": "raw" 10 | }, 11 | "multi-threading": { 12 | "enable-multi-threading": false 13 | }, 14 | "valid-lifetime": 4000, 15 | "renew-timer": 1000, 16 | "rebind-timer": 2000, 17 | "subnet4": [{ 18 | "subnet": "172.28.5.0/24", 19 | "id": 5, 20 | "pools": [ { "pool": "172.28.5.10 - 172.28.5.200" } ] 21 | }, { 22 | "subnet": "172.28.6.0/24", 23 | "id": 6, 24 | "pools": [ { "pool": "172.28.6.10 - 172.28.6.200" } ] 25 | }], 26 | "hooks-libraries": [{ 27 | "library": "/usr/local/lib/kea/hooks/libkea_python.so", 28 | "parameters": { 29 | "libpython": "libpython3.10.so.1", 30 | "module": "/workdir/examples/host-cmds/keahook.py" 31 | } 32 | }], 33 | "lease-database": { 34 | "persist": false, 35 | "type": "memfile" 36 | }, 37 | "hosts-database": { 38 | "type": "mysql", 39 | "name": "kea", 40 | "host": "mysql", 41 | "user": "kea", 42 | "password": "kea" 43 | }, 44 | "loggers": [{ 45 | "name": "kea-dhcp4", 46 | "output_options": [{ 47 | "output": "stdout", 48 | "flush": true, 49 | "pattern": "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 50 | }], 51 | "severity": "DEBUG", 52 | "debuglevel": 50 53 | }] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /keamodule/tests/test_config_backend_dhcpv4_mgr.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestConfigBackendDHCPv4Mgr_new(utils.BaseTestCase): 6 | 7 | def test_ok(self): 8 | self.assert_cannot_construct(kea.ConfigBackendDHCPv4Mgr) 9 | 10 | 11 | class TestConfigBackendDHCPv4Mgr_instance(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | self.assert_method_no_arguments(kea.ConfigBackendDHCPv4Mgr.instance) 15 | 16 | def test_ok(self): 17 | m = kea.ConfigBackendDHCPv4Mgr.instance() 18 | self.assertIsInstance(m, kea.ConfigBackendDHCPv4Mgr) 19 | 20 | 21 | class TestConfigBackendDHCPv4Mgr_addBackend(utils.BaseTestCase): 22 | 23 | def test_badarg_count(self): 24 | m = kea.ConfigBackendDHCPv4Mgr.instance() 25 | self.assert_method_one_arg_no_keywords(m.addBackend) 26 | 27 | def test_ok(self): 28 | m = kea.ConfigBackendDHCPv4Mgr.instance() 29 | # have not added the mysql hook, so this will fail 30 | with self.assertRaises(TypeError) as cm: 31 | m.addBackend('type=mysql') 32 | if kea.__version__ < '3.0.0': 33 | self.assertEqual(("The type of the configuration backend: 'mysql' is not supported",), cm.exception.args) 34 | else: 35 | self.assertEqual(('The Kea server has not been compiled with support for configuration database ' 36 | 'type: mysql. Did you forget to use -D mysql=enabled during setup or to load ' 37 | 'libdhcp_mysql hook library?',), cm.exception.args) 38 | 39 | 40 | class TestConfigBackendDHCPv4Mgr_getPool(utils.BaseTestCase): 41 | 42 | def test_badarg_count(self): 43 | m = kea.ConfigBackendDHCPv4Mgr.instance() 44 | self.assert_method_no_arguments(m.getPool) 45 | 46 | def test_ok(self): 47 | m = kea.ConfigBackendDHCPv4Mgr.instance() 48 | self.assertIsInstance(m.getPool(), kea.ConfigBackendPoolDHCPv4) 49 | -------------------------------------------------------------------------------- /keamodule/tests/test_server.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestServer_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_two_args_no_keywords(kea.Server) 9 | 10 | def test_badarg_type(self): 11 | with self.assertRaises(TypeError) as cm: 12 | kea.Server(1, 1) 13 | self.assertEqual(("argument 1 must be str, not int",), cm.exception.args) 14 | with self.assertRaises(TypeError) as cm: 15 | kea.Server('foo', 1) 16 | self.assertEqual(("argument 2 must be str, not int",), cm.exception.args) 17 | 18 | def test_ok(self): 19 | s = kea.Server('kea', 'kea server') 20 | self.assertEqual('kea', s.getServerTagAsText()) 21 | self.assertEqual('kea server', s.getDescription()) 22 | 23 | 24 | class TestServer_getServerTagAsText(utils.BaseTestCase): 25 | 26 | def test_badarg_count(self): 27 | s = kea.Server('kea', 'kea server') 28 | self.assert_method_no_arguments(s.getServerTagAsText) 29 | 30 | def test_ok(self): 31 | s = kea.Server('kea', 'kea server') 32 | self.assertEqual('kea', s.getServerTagAsText()) 33 | 34 | 35 | class TestServer_getDescription(utils.BaseTestCase): 36 | 37 | def test_badarg_count(self): 38 | s = kea.Server('kea', 'kea server') 39 | self.assert_method_no_arguments(s.getDescription) 40 | 41 | def test_ok(self): 42 | s = kea.Server('kea', 'kea server') 43 | self.assertEqual('kea server', s.getDescription()) 44 | 45 | 46 | class TestServer_toElement(utils.BaseTestCase): 47 | 48 | def test_badarg_count(self): 49 | s = kea.Server('kea', 'kea server') 50 | self.assert_method_no_arguments(s.toElement) 51 | 52 | def test_ok(self): 53 | s = kea.Server('kea', 'kea server') 54 | self.assertEqual({'server-tag': 'kea', 55 | 'description': 'kea server'}, s.toElement()) 56 | -------------------------------------------------------------------------------- /examples/cb-cmds/kea.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "control-socket": { 4 | "socket-type": "unix", 5 | "socket-name": "/usr/local/var/run/kea/kea4.sock" 6 | }, 7 | "interfaces-config": { 8 | "interfaces": [ "eth0" ], 9 | "dhcp-socket-type": "raw" 10 | }, 11 | "multi-threading": { 12 | "enable-multi-threading": false 13 | }, 14 | "server-tag": "kea", 15 | "config-control": { 16 | "config-databases": [{ 17 | "type": "mysql", 18 | "name": "kea", 19 | "user": "kea", 20 | "password": "kea", 21 | "host": "mysql", 22 | "port": 3306, 23 | "max-reconnect-tries": 5, 24 | "reconnect-wait-time": 1000 25 | }], 26 | "config-fetch-wait-time": 20 27 | }, 28 | "hooks-libraries": [{ 29 | "library": "/usr/local/lib64/kea/hooks/libdhcp_mysql.so" 30 | }, 31 | { 32 | "library": "/usr/local/lib64/kea/hooks/libkea_python.so", 33 | "parameters": { 34 | "libpython": "libpython3.12.so.1", 35 | "module": "/workdir/examples/cb-cmds/keahook.py" 36 | } 37 | }], 38 | "valid-lifetime": 4000, 39 | "renew-timer": 1000, 40 | "rebind-timer": 2000, 41 | "lease-database": { 42 | "persist": false, 43 | "type": "memfile" 44 | }, 45 | "hosts-database": { 46 | "type": "mysql", 47 | "name": "kea", 48 | "host": "mysql", 49 | "user": "kea", 50 | "password": "kea" 51 | }, 52 | "loggers": [{ 53 | "name": "kea-dhcp4", 54 | "output_options": [{ 55 | "output": "stdout", 56 | "flush": true, 57 | "pattern": "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 58 | }], 59 | "severity": "DEBUG", 60 | "debuglevel": 50 61 | }] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/facebook-trick/keahook.py: -------------------------------------------------------------------------------- 1 | from kea import * 2 | from ipaddress import IPv4Address, IPv4Network 3 | 4 | 5 | class Config: 6 | 7 | def __init__(self, conf): 8 | dhcp4 = conf['Dhcp4'] 9 | self.options = [Option(DHO_DHCP_LEASE_TIME).setUint32(dhcp4.get('valid-lifetime', 7200)), 10 | Option(DHO_DHCP_RENEWAL_TIME).setUint32(dhcp4.get('renew-timer', 1800)), 11 | Option(DHO_DHCP_REBINDING_TIME).setUint32(dhcp4.get('rebind-timer', 3600))] 12 | self.subnet = IPv4Network('10.0.0.0/20') 13 | self.options.append(Option(DHO_SUBNET_MASK).setBytes(self.subnet.netmask.packed)) 14 | # hard code nameservers 15 | servers = [IPv4Address('192.168.3.1'), IPv4Address('192.168.3.2')] 16 | packed_servers = b''.join([addr.packed 17 | for addr in servers]) 18 | self.options.append(Option(DHO_DOMAIN_NAME_SERVERS).setBytes(packed_servers)) 19 | # hard code routers and domainname 20 | self.options.append(Option(DHO_ROUTERS).setBytes(self.subnet[1].packed)) 21 | self.options.append(Option(DHO_DOMAIN_NAME).setString('facebook.com')) 22 | 23 | 24 | class HostInfo: 25 | def __init__(self, hwaddr): 26 | self.hwaddr = hwaddr 27 | self.addr = config.subnet[42] 28 | 29 | 30 | def load(handle): 31 | global config 32 | config = Config(CfgMgr.instance().getStagingCfg().toElement()) 33 | return 0 34 | 35 | 36 | def pkt4_receive(handle): 37 | query = handle.getArgument('query4') 38 | handle.setContext('hostinfo', HostInfo(query.getHWAddr())) 39 | return 0 40 | 41 | 42 | def lease4_select(handle): 43 | hostinfo = handle.getContext('hostinfo') 44 | lease = handle.getArgument('lease4') 45 | lease.addr = str(hostinfo.addr) 46 | query = handle.getArgument('query4') 47 | if query.getType() == DHCPREQUEST: 48 | handle.setStatus(NEXT_STEP_SKIP) 49 | return 0 50 | 51 | 52 | def pkt4_send(handle): 53 | response = handle.getArgument('response4') 54 | if response.getType() == DHCPNAK: 55 | response.setType(DHCPACK) 56 | hostinfo = handle.getContext('hostinfo') 57 | response.setYiaddr(str(hostinfo.addr)) 58 | for option in config.options: 59 | response.delOption(option.getType()) 60 | response.addOption(option) 61 | return 0 62 | -------------------------------------------------------------------------------- /keamodule/tests/test_client_class_def.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestClientClassDef_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_no_arguments(kea.ClientClassDef) 9 | 10 | 11 | class TestClientClassDef_toElement(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | cc = kea.ClientClassDef() 15 | self.assert_method_no_arguments(cc.toElement) 16 | 17 | def test_uninitialized(self): 18 | cc = kea.ClientClassDef() 19 | with self.assertRaises(RuntimeError) as cm: 20 | cc.toElement() 21 | self.assertEqual(("ClientClassDef object is not initialized",), cm.exception.args) 22 | 23 | def test_ok(self): 24 | p = kea.ClientClassDefParser() 25 | cc_dict = kea.ClientClassDictionary() 26 | p.parse(cc_dict, {'name': 'foo', 27 | 'option-def': [{'name': 'configfile', 28 | 'code': 224, 29 | 'type': 'string'}], 30 | 'option-data': [{'name': 'configfile', 31 | 'data': '1APC'}]}) 32 | cc = cc_dict.getClasses()[0] 33 | self.assertEqual({'boot-file-name': '', 34 | 'name': 'foo', 35 | 'next-server': '0.0.0.0', 36 | 'option-data': [{'always-send': False, 37 | 'code': 224, 38 | 'csv-format': True, 39 | 'data': '1APC', 40 | 'name': 'configfile', 41 | 'never-send': False, 42 | 'space': 'dhcp4'}], 43 | 'option-def': [{'array': False, 44 | 'code': 224, 45 | 'encapsulate': '', 46 | 'name': 'configfile', 47 | 'record-types': '', 48 | 'space': 'dhcp4', 49 | 'type': 'string'}], 50 | 'server-hostname': ''}, cc.toElement()) 51 | -------------------------------------------------------------------------------- /keamodule/tests/test_srv_config.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestSrvConfig_new(utils.BaseTestCase): 6 | 7 | def test_cannot_construct(self): 8 | self.assert_cannot_construct(kea.CfgMgr) 9 | 10 | 11 | class TestGetCurrentConfig(utils.BaseTestCase): 12 | 13 | def test_badarg_count(self): 14 | m = kea.CfgMgr.instance() 15 | self.assert_method_no_arguments(m.getCurrentCfg) 16 | 17 | def test_ok(self): 18 | m = kea.CfgMgr.instance() 19 | c = m.getCurrentCfg() 20 | if kea.__version__ < '3.0.0': 21 | self.assertEqual(3, c.use_count) 22 | d = m.getCurrentCfg() 23 | self.assertEqual(4, c.use_count) 24 | self.assertEqual(4, d.use_count) 25 | d = None 26 | self.assertEqual(3, c.use_count) 27 | else: 28 | self.assertEqual(2, c.use_count) 29 | d = m.getCurrentCfg() 30 | self.assertEqual(3, c.use_count) 31 | self.assertEqual(3, d.use_count) 32 | d = None 33 | self.assertEqual(2, c.use_count) 34 | 35 | 36 | class TestGetStagingConfig(utils.BaseTestCase): 37 | 38 | def test_badarg_count(self): 39 | m = kea.CfgMgr.instance() 40 | self.assert_method_no_arguments(m.getStagingCfg) 41 | 42 | def test_ok(self): 43 | m = kea.CfgMgr.instance() 44 | c = m.getStagingCfg() 45 | self.assertEqual(2, c.use_count) 46 | d = m.getStagingCfg() 47 | self.assertEqual(3, c.use_count) 48 | self.assertEqual(3, d.use_count) 49 | d = None 50 | self.assertEqual(2, c.use_count) 51 | 52 | 53 | class TestSrvConfig_getCfgSubnets4(utils.BaseTestCase): 54 | 55 | def test_badarg_count(self): 56 | c = kea.CfgMgr.instance().getStagingCfg() 57 | self.assert_method_no_arguments(c.getCfgSubnets4) 58 | 59 | def test_ok(self): 60 | c = kea.CfgMgr.instance().getStagingCfg() 61 | n = c.getCfgSubnets4() 62 | self.assertEqual(2, n.use_count) 63 | 64 | 65 | class TestSrvConfig_toElement(utils.BaseTestCase): 66 | 67 | def test_badarg_count(self): 68 | c = kea.CfgMgr.instance().getStagingCfg() 69 | self.assert_method_no_arguments(c.toElement) 70 | 71 | def test_ok(self): 72 | c = kea.CfgMgr.instance().getStagingCfg() 73 | e = c.toElement() 74 | self.assertIsInstance(e, dict) 75 | -------------------------------------------------------------------------------- /examples/stress-test/README.md: -------------------------------------------------------------------------------- 1 | # stress-test 2 | Demonstrate multiple threads in Python code with a performance test thrown in. 3 | 4 | To run this hook you will need to install `curl` and `python3-prometheus-client`. 5 | 6 | Start the kea image: 7 | ``` 8 | djc@laptop:~/play/kea_python$ make run-kea 9 | root@e55a47190d9c:/# cd /workdir/ 10 | root@e55a47190d9c:/workdir# apt-get install curl python3-prometheus-client 11 | root@9b06fba64b09:/workdir# /usr/local/sbin/kea-dhcp4 -c examples/stress-test/kea.conf 12 | ``` 13 | 14 | In another shell start bash in the kea container: 15 | ``` 16 | djc@laptop:~/play/kea_python$ docker exec -it kea bash 17 | root@e55a47190d9c:/# while true; do curl -s http://localhost:9100/ | grep ^dhcp && echo; sleep 0.2; done 18 | ``` 19 | This will force the Python hook code to execute the prometheus client background thread every 0.2 20 | seconds. Doing this at the same time as performing a DHCP stress test is a nice way to see that you 21 | can execute Python threads in Kea. 22 | 23 | Now the dhtest image: 24 | ``` 25 | djc@laptop:~/play/kea_python$ make run-dhtest 26 | root@e01939ffd0f4:/# cd /workdir 27 | root@e01939ffd0f4:/workdir# python3 -B examples/stress-test/stress_test.py --help 28 | usage: stress_test.py [-h] [--total TOTAL] [--parallel PARALLEL] 29 | 30 | optional arguments: 31 | -h, --help show this help message and exit 32 | --total TOTAL total number of requests to perform 33 | --parallel PARALLEL number of parallel requests to perform 34 | root@e01939ffd0f4:/workdir# python3 -B examples/stress-test/stress_test.py --parallel 50 --total 10000 35 | 00:01: 634 at 634/sec 36 | 00:02: 1392 at 696/sec 37 | 00:03: 1971 at 669/sec 38 | 00:04: 2660 at 676/sec 39 | 00:05: 3378 at 662/sec 40 | 00:06: 4031 at 687/sec 41 | 00:07: 4795 at 709/sec 42 | 00:08: 5501 at 704/sec 43 | 00:09: 6227 at 729/sec 44 | 00:10: 7009 at 737/sec 45 | 00:11: 7719 at 740/sec 46 | 00:12: 8468 at 747/sec 47 | 00:13: 9274 at 755/sec 48 | total 10000 at 715/sec with 0 errors 49 | ``` 50 | 51 | As soon as you start the stress test you should start to see output from the `curl` commands. You do not 52 | see any output initially because the metrics defined in the Python hook have labels. The time-series for 53 | each metric cannot be created until a label value is defined. 54 | 55 | The output from the `curl | grep` should look something like: 56 | ``` 57 | dhcp4_pkt_send_total{type="offer"} 21182.0 58 | dhcp4_pkt_send_total{type="ack"} 21182.0 59 | dhcp4_pkt_receive_total{type="request"} 21182.0 60 | dhcp4_pkt_receive_total{type="discover"} 21182.0 61 | ``` 62 | -------------------------------------------------------------------------------- /examples/lease-cmds/README.md: -------------------------------------------------------------------------------- 1 | # lease-cmds 2 | This example is a re-implementation IPv4 functionality of the `lease-cmds` hook that 3 | comes with Kea. 4 | 5 | Start the kea image and run the Python implementation of the `lease-cmds` hook.: 6 | ``` 7 | djc@laptop:~/play/kea_python$ make run-kea 8 | root@ae43fd3e78da:/source# cd /workdir 9 | root@ae43fd3e78da:/workdir# /usr/local/sbin/kea-dhcp4 -c examples/lease-cmds/kea.conf 10 | ``` 11 | 12 | Then in another shell start a command shell in the kea container in order to run the test 13 | harness that exercises all of the implemented commands: 14 | ``` 15 | djc@laptop:~/play/kea_python$ docker exec -it kea bash 16 | root@ae43fd3e78da:/source# cd /workdir/examples/lease-cmds/ 17 | root@ae43fd3e78da:/workdir/examples/lease-cmds# python3 -B -m unittest discover 18 | ............................. 19 | ---------------------------------------------------------------------- 20 | Ran 29 tests in 0.045s 21 | 22 | OK 23 | ``` 24 | 25 | Now in the first shell, hit ^C to kill Kea and restart it using the official `lease-cmds` hook: 26 | ``` 27 | root@ae43fd3e78da:/workdir# /usr/local/sbin/kea-dhcp4 -c examples/lease-cmds/kea-reference.conf 28 | ``` 29 | 30 | Now run the tests again: 31 | ``` 32 | djc@laptop:~/play/kea_python$ docker exec -it kea bash 33 | root@ae43fd3e78da:/source# cd /workdir/examples/lease-cmds/ 34 | root@ae43fd3e78da:/workdir/examples/lease-cmds# python3 -B -m unittest discover 35 | ............................. 36 | ---------------------------------------------------------------------- 37 | Ran 29 tests in 0.035s 38 | 39 | OK 40 | ``` 41 | 42 | The Python implementation of `lease-cmds` is not identical to the C++ library. There are some 43 | differences between the text returned in server responses. In the `assertResponse` method the 44 | response is considered correct if the text matches one of two possible values. 45 | 46 | For example: 47 | ``` 48 | def test_bad_addr_value(self): 49 | response = send_command('lease4-add', 50 | addr='oops') 51 | self.assertResponse({ 52 | "text": ("Failed to convert 'oops' to address: Failed to convert string to " 53 | "address 'oops': Invalid argument(:0:29)"), 54 | "result": 1 55 | }, 56 | response, 57 | "Failed to convert string to address 'oops': Invalid argument") 58 | ``` 59 | This test sends a bad address and then checks for either the C++ error text: 60 | ``` 61 | Failed to convert 'oops' to address: Failed to convert string to address 'oops': Invalid argument(:0:29) 62 | ``` 63 | 64 | or the error text from the Python implementation: 65 | ``` 66 | Failed to convert string to address 'oops': Invalid argument 67 | ``` -------------------------------------------------------------------------------- /examples/host-cmds/sendcmd.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import socket 4 | 5 | 6 | def send_command(name, args=None): 7 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 8 | s.connect('/usr/local/var/run/kea/kea4.sock') 9 | cmd = {'command': name} 10 | if args: 11 | cmd['arguments'] = args 12 | request = json.dumps(cmd, sort_keys=True) 13 | print(request) 14 | s.send(request.encode('utf-8')) 15 | bits = [] 16 | while True: 17 | buff = s.recv(4096) 18 | if not buff: 19 | break 20 | bits.append(buff) 21 | response = b''.join(bits).decode('utf-8') 22 | print(response) 23 | return json.loads(response) 24 | 25 | 26 | num_resv = 10 27 | 28 | 29 | def add_reservations(): 30 | for i in range(num_resv): 31 | send_command('reservation-add', 32 | {'reservation': { 33 | 'subnet-id': 5, 34 | 'hw-address': '1a:1b:1c:1d:1e:%02x' % i, 35 | 'ip-address': '172.28.5.%d' % (i + 1)} 36 | }) 37 | 38 | 39 | def get_reservations(): 40 | for i in range(num_resv): 41 | send_command('reservation-get', {'subnet-id': 5, 'ip-address': '172.28.5.%d' % (i + 1)}) 42 | 43 | 44 | def list_reservations(): 45 | res = send_command('reservation-get-page', {'subnet-id': 5, 'limit': 5}) 46 | while res['result'] == 0: 47 | next = res['arguments']['next'] 48 | lower_host_id = next['from'] 49 | source_index = next['source-index'] 50 | res = send_command('reservation-get-page', {'subnet-id': 5, 51 | 'limit': 5, 52 | 'from': lower_host_id, 53 | 'source-index': source_index}) 54 | 55 | 56 | def delete_reservations(): 57 | for i in range(num_resv): 58 | send_command('reservation-del', 59 | {'subnet-id': 5, 60 | 'ip-address': '172.28.5.%d' % (i + 1)}) 61 | 62 | 63 | def main(): 64 | parser = argparse.ArgumentParser() 65 | parser.add_argument('--add', action='store_true') 66 | parser.add_argument('--get', action='store_true') 67 | parser.add_argument('--list', action='store_true') 68 | parser.add_argument('--delete', action='store_true') 69 | 70 | args = parser.parse_args() 71 | if args.add: 72 | add_reservations() 73 | if args.get: 74 | get_reservations() 75 | if args.list: 76 | list_reservations() 77 | if args.delete: 78 | delete_reservations() 79 | if not args.add and not args.get and not args.list and not args.delete: 80 | add_reservations() 81 | get_reservations() 82 | list_reservations() 83 | delete_reservations() 84 | 85 | 86 | 87 | if __name__ == '__main__': 88 | main() 89 | -------------------------------------------------------------------------------- /keamodule/callout_manager.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::hooks; 5 | 6 | extern "C" { 7 | 8 | static PyMethodDef CalloutManager_methods[] = { 9 | {0} // Sentinel 10 | }; 11 | 12 | static PyObject * 13 | CalloutManager_use_count(CalloutManagerObject *self, void *closure) { 14 | // REFCOUNT: PyLong_FromLong - returns new reference 15 | return (PyLong_FromLong(self->manager.use_count())); 16 | } 17 | 18 | static PyGetSetDef CalloutManager_getsetters[] = { 19 | {(char *)"use_count", (getter) CalloutManager_use_count, (setter) 0, (char *)"shared_ptr use count", 0}, 20 | {0} // Sentinel 21 | }; 22 | 23 | // tp_dealloc - called when refcount is zero 24 | static void 25 | CalloutManager_dealloc(CalloutManagerObject *self) { 26 | self->manager.~CalloutManagerPtr(); 27 | Py_TYPE(self)->tp_free((PyObject *) self); 28 | } 29 | 30 | // tp_init - called after tp_new has returned an instance 31 | static int 32 | CalloutManager_init(CalloutManagerObject *self, PyObject *args, PyObject *kwds) { 33 | new(&self->manager) CalloutManagerPtr; 34 | 35 | if (kwds != 0) { 36 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 37 | return (-1); 38 | } 39 | if (!PyArg_ParseTuple(args, "")) { 40 | return (-1); 41 | } 42 | 43 | try { 44 | self->manager.reset(new CalloutManager()); 45 | } 46 | catch (const exception &e) { 47 | PyErr_SetString(PyExc_TypeError, e.what()); 48 | return (-1); 49 | } 50 | 51 | return (0); 52 | } 53 | 54 | // tp_new - allocate space and initialisation that can be repeated 55 | static PyObject * 56 | CalloutManager_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 57 | CalloutManagerObject *self; 58 | self = (CalloutManagerObject *) type->tp_alloc(type, 0); 59 | if (self) { 60 | new(&self->manager) CalloutManagerPtr; 61 | } 62 | return ((PyObject *) self); 63 | } 64 | 65 | PyTypeObject CalloutManagerType = { 66 | .ob_base = PyObject_HEAD_INIT(0) 67 | .tp_name = "kea.CalloutManager", 68 | .tp_basicsize = sizeof(CalloutManagerObject), 69 | .tp_dealloc = (destructor) CalloutManager_dealloc, 70 | .tp_flags = Py_TPFLAGS_DEFAULT, 71 | .tp_doc = PyDoc_STR("Kea server CalloutManager"), 72 | .tp_methods = CalloutManager_methods, 73 | .tp_getset = CalloutManager_getsetters, 74 | .tp_init = (initproc) CalloutManager_init, 75 | .tp_alloc = PyType_GenericAlloc, 76 | .tp_new = CalloutManager_new, 77 | }; 78 | 79 | int 80 | CalloutManager_define() { 81 | // PyType_Ready - finish type initialisation 82 | if (PyType_Ready(&CalloutManagerType) < 0) { 83 | return (1); 84 | } 85 | Py_INCREF(&CalloutManagerType); 86 | // REFCOUNT: PyModule_AddObject - steals reference on success 87 | if (PyModule_AddObject(kea_module, "CalloutManager", (PyObject *) &CalloutManagerType) < 0) { 88 | Py_DECREF(&CalloutManagerType); 89 | return (1); 90 | } 91 | 92 | return (0); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /examples/stress-test/keahook.py: -------------------------------------------------------------------------------- 1 | from kea import * 2 | from ipaddress import IPv4Address, IPv4Network 3 | from prometheus_client import start_http_server, Counter 4 | 5 | PKT_RECEIVE = Counter('dhcp4_pkt_receive_total', 'Packets received', ['type']) 6 | PKT_SEND = Counter('dhcp4_pkt_send_total', 'Packets sent', ['type']) 7 | 8 | class Config: 9 | def __init__(self, conf): 10 | dhcp4 = conf['Dhcp4'] 11 | self.options = [Option(DHO_DHCP_LEASE_TIME).setUint32(dhcp4.get('valid-lifetime', 7200)), 12 | Option(DHO_DHCP_RENEWAL_TIME).setUint32(dhcp4.get('renew-timer', 1800)), 13 | Option(DHO_DHCP_REBINDING_TIME).setUint32(dhcp4.get('rebind-timer', 3600))] 14 | self.subnet = IPv4Network('10.0.0.0/8') 15 | self.options.append(Option(DHO_SUBNET_MASK).setBytes(self.subnet.netmask.packed)) 16 | # hard code nameservers 17 | servers = [IPv4Address('192.168.3.1'), IPv4Address('192.168.3.2')] 18 | packed_servers = b''.join([addr.packed 19 | for addr in servers]) 20 | self.options.append(Option(DHO_DOMAIN_NAME_SERVERS).setBytes(packed_servers)) 21 | # hard code routers and domainname 22 | self.options.append(Option(DHO_ROUTERS).setBytes(self.subnet[1].packed)) 23 | self.options.append(Option(DHO_DOMAIN_NAME).setString('facebook.com')) 24 | 25 | def load(handle): 26 | global config, type_to_label 27 | config = Config(CfgMgr.instance().getStagingCfg().toElement()) 28 | type_to_label = dict([(v, k[4:].lower()) 29 | for k, v in globals().items() 30 | if k.startswith('DHCP')]) 31 | start_http_server(9100) 32 | return 0 33 | 34 | def pkt4_receive(handle): 35 | query = handle.getArgument('query4') 36 | PKT_RECEIVE.labels(type=type_to_label.get(query.getType(), 'unknown')).inc() 37 | # client must request address in Option 82, suboption 1. 38 | o = query.getOption(DHO_DHCP_AGENT_OPTIONS) 39 | if not o: 40 | raise RuntimeError('client must send option %s' % DHO_DHCP_AGENT_OPTIONS) 41 | o = o.getOption(1) 42 | if not o: 43 | raise RuntimeError('missing suboption 1 in option %s' % DHO_DHCP_AGENT_OPTIONS) 44 | handle.setContext('requested-addr', o.getString()) 45 | return 0 46 | 47 | def lease4_select(handle): 48 | lease = handle.getArgument('lease4') 49 | lease.addr = handle.getContext('requested-addr') 50 | query = handle.getArgument('query4') 51 | if query.getType() == DHCPREQUEST: 52 | handle.setStatus(NEXT_STEP_SKIP) 53 | return 0 54 | 55 | def pkt4_send(handle): 56 | response = handle.getArgument('response4') 57 | if response.getType() == DHCPNAK: 58 | response.setType(DHCPACK) 59 | PKT_SEND.labels(type=type_to_label.get(response.getType(), 'unknown')).inc() 60 | addr = handle.getContext('requested-addr') 61 | response.setYiaddr(addr) 62 | for option in config.options: 63 | response.delOption(option.getType()) 64 | response.addOption(option) 65 | return 0 66 | -------------------------------------------------------------------------------- /keamodule/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from setuptools import setup, Extension 4 | 5 | 6 | settings = {} 7 | for line in open('../settings.mk'): 8 | if '=' in line: 9 | name, value = line.split('=') 10 | settings[name.strip()] = value.strip() 11 | 12 | 13 | def calc_macros(): 14 | config_h = open(os.path.join(settings['KEA_INC'], 'config.h')).read() 15 | m = re.search(r'^#define VERSION "([^"]*)"\n', config_h, re.M) 16 | if not m: 17 | raise RuntimeError('could not determine kea version') 18 | ver = re.sub('-git', '', m.group(1)) 19 | version = tuple(int(v) for v in ver.split('.')) 20 | macros = [] 21 | if version < (1, 7, 1): 22 | macros.append(('MISSING_GETLEASES4_HOSTNAME', None)) 23 | if version < (1, 7, 4): 24 | macros.append(('HAVE_DELETELEASE_ADDR', None)) 25 | if version < (1, 7, 5): 26 | macros.append(('HAVE_LIBRARYHANDLE_MANAGER_PTR', None)) 27 | if version < (1, 9, 4): 28 | macros.append(('HAVE_GETLEASE4_METHOD', None)) 29 | if version < (2, 2, 0): 30 | macros.append(('HAVE_BACKEND_PGSQL', None)) 31 | if version < (3, 0, 0): 32 | macros.append(('HAVE_NONCONST_CFGSUBNETS4_SELECTSUBNET', None)) 33 | return macros 34 | 35 | 36 | kea = Extension('kea', 37 | define_macros=calc_macros(), 38 | sources=['callout_closure.cc', 39 | 'callout_handle.cc', 40 | 'callout_manager.cc', 41 | 'callouts.cc', 42 | 'capsule.cc', 43 | 'cfg_mgr.cc', 44 | 'cfg_subnets4.cc', 45 | 'client_class_def.cc', 46 | 'client_class_def_parser.cc', 47 | 'client_class_dictionary.cc', 48 | 'config_backend_dhcp4_mgr.cc', 49 | 'config_backend_pool_dhcp4.cc', 50 | 'constants.cc', 51 | 'errors.cc', 52 | 'host.cc', 53 | 'host_mgr.cc', 54 | 'host_reservation_parser4.cc', 55 | 'kea.cc', 56 | 'lease4.cc', 57 | 'lease_mgr.cc', 58 | 'library_handle.cc', 59 | 'logger_manager.cc', 60 | 'option.cc', 61 | 'pkt4.cc', 62 | 'server.cc', 63 | 'srv_config.cc', 64 | 'subnet4.cc', 65 | 'subnet4_config_parser.cc', 66 | 'utils.cc'], 67 | include_dirs=[settings['KEA_INC']], 68 | libraries=['kea-exceptions', 69 | 'kea-log', 70 | 'kea-hooks', 71 | 'kea-dhcpsrv', 72 | 'ffi'], 73 | library_dirs=[settings['KEA_LIBS']], 74 | extra_compile_args=['-std=c++14']) 75 | 76 | setup(name='kea', 77 | version='1.0', 78 | description='Extension module for Kea', 79 | ext_modules=[kea]) 80 | -------------------------------------------------------------------------------- /keamodule/subnet4_config_parser.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | using namespace isc::data; 6 | 7 | extern "C" { 8 | 9 | static PyObject * 10 | Subnet4ConfigParser_parse(Subnet4ConfigParserObject *self, PyObject *args) { 11 | PyObject *config; 12 | 13 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 14 | if (!PyArg_ParseTuple(args, "O", &config)) { 15 | return (0); 16 | } 17 | try { 18 | ElementPtr data = object_to_element(config); 19 | Subnet4Ptr subnet = self->parser->parse(data); 20 | // REFCOUNT: Subnet4_from_ptr - returns new reference 21 | return Subnet4_from_ptr(subnet); 22 | } 23 | catch (const exception &e) { 24 | PyErr_SetString(PyExc_TypeError, e.what()); 25 | return (0); 26 | } 27 | } 28 | 29 | 30 | static PyMethodDef Subnet4ConfigParser_methods[] = { 31 | {"parse", (PyCFunction) Subnet4ConfigParser_parse, METH_VARARGS, 32 | "Parses a single IPv4 subnet configuration and adds to the Configuration Manager."}, 33 | {0} // Sentinel 34 | }; 35 | 36 | // tp_dealloc - called when refcount is zero 37 | static void 38 | Subnet4ConfigParser_dealloc(Subnet4ConfigParserObject *self) { 39 | delete self->parser; 40 | Py_TYPE(self)->tp_free((PyObject *) self); 41 | } 42 | 43 | // tp_init - called after tp_new has returned an instance 44 | static int 45 | Subnet4ConfigParser_init(Subnet4ConfigParserObject *self, PyObject *args, PyObject *kwds) { 46 | if (kwds != 0) { 47 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 48 | return (-1); 49 | } 50 | if (!PyArg_ParseTuple(args, "")) { 51 | return (-1); 52 | } 53 | 54 | return (0); 55 | } 56 | 57 | // tp_new - allocate space and initialisation that can be repeated 58 | static PyObject * 59 | Subnet4ConfigParser_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 60 | Subnet4ConfigParserObject *self; 61 | self = (Subnet4ConfigParserObject *) type->tp_alloc(type, 0); 62 | self->parser = new Subnet4ConfigParser(); 63 | return ((PyObject *) self); 64 | } 65 | 66 | PyTypeObject Subnet4ConfigParserType = { 67 | .ob_base = PyObject_HEAD_INIT(0) 68 | .tp_name = "kea.Subnet4ConfigParser", 69 | .tp_basicsize = sizeof(Subnet4ConfigParserObject), 70 | .tp_dealloc = (destructor) Subnet4ConfigParser_dealloc, 71 | .tp_flags = Py_TPFLAGS_DEFAULT, 72 | .tp_doc = PyDoc_STR("Kea server Subnet4ConfigParser"), 73 | .tp_methods = Subnet4ConfigParser_methods, 74 | .tp_init = (initproc) Subnet4ConfigParser_init, 75 | .tp_alloc = PyType_GenericAlloc, 76 | .tp_new = Subnet4ConfigParser_new, 77 | }; 78 | 79 | int 80 | Subnet4ConfigParser_define() { 81 | // PyType_Ready - finish type initialisation 82 | if (PyType_Ready(&Subnet4ConfigParserType) < 0) { 83 | return (1); 84 | } 85 | Py_INCREF(&Subnet4ConfigParserType); 86 | // REFCOUNT: PyModule_AddObject steals reference on success 87 | if (PyModule_AddObject(kea_module, "Subnet4ConfigParser", (PyObject *) &Subnet4ConfigParserType) < 0) { 88 | Py_DECREF(&Subnet4ConfigParserType); 89 | return (1); 90 | } 91 | 92 | return (0); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /keamodule/host_reservation_parser4.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | using namespace isc::data; 6 | 7 | extern "C" { 8 | 9 | static PyObject * 10 | HostReservationParser4_parse(HostReservationParser4Object *self, PyObject *args) { 11 | unsigned long subnet_id; 12 | PyObject *config; 13 | 14 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 15 | if (!PyArg_ParseTuple(args, "kO", &subnet_id, &config)) { 16 | return (0); 17 | } 18 | try { 19 | ElementPtr data = object_to_element(config); 20 | HostPtr host = self->parser->parse(subnet_id, data); 21 | // REFCOUNT: Host_from_ptr - returns new reference 22 | return Host_from_ptr(host); 23 | } 24 | catch (const exception &e) { 25 | PyErr_SetString(PyExc_TypeError, e.what()); 26 | return (0); 27 | } 28 | } 29 | 30 | 31 | static PyMethodDef HostReservationParser4_methods[] = { 32 | {"parse", (PyCFunction) HostReservationParser4_parse, METH_VARARGS, 33 | "Parses a single entry for host reservation."}, 34 | {0} // Sentinel 35 | }; 36 | 37 | // tp_dealloc - called when refcount is zero 38 | static void 39 | HostReservationParser4_dealloc(HostReservationParser4Object *self) { 40 | delete self->parser; 41 | Py_TYPE(self)->tp_free((PyObject *) self); 42 | } 43 | 44 | // tp_init - called after tp_new has returned an instance 45 | static int 46 | HostReservationParser4_init(HostReservationParser4Object *self, PyObject *args, PyObject *kwds) { 47 | if (kwds != 0) { 48 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 49 | return (-1); 50 | } 51 | if (!PyArg_ParseTuple(args, "")) { 52 | return (-1); 53 | } 54 | 55 | return (0); 56 | } 57 | 58 | // tp_new - allocate space and initialisation that can be repeated 59 | static PyObject * 60 | HostReservationParser4_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 61 | HostReservationParser4Object *self; 62 | self = (HostReservationParser4Object *) type->tp_alloc(type, 0); 63 | self->parser = new HostReservationParser4(); 64 | return ((PyObject *) self); 65 | } 66 | 67 | PyTypeObject HostReservationParser4Type = { 68 | .ob_base = PyObject_HEAD_INIT(0) 69 | .tp_name = "kea.HostReservationParser4", 70 | .tp_basicsize = sizeof(HostReservationParser4Object), 71 | .tp_dealloc = (destructor) HostReservationParser4_dealloc, 72 | .tp_flags = Py_TPFLAGS_DEFAULT, 73 | .tp_doc = PyDoc_STR("Kea server HostReservationParser4"), 74 | .tp_methods = HostReservationParser4_methods, 75 | .tp_init = (initproc) HostReservationParser4_init, 76 | .tp_alloc = PyType_GenericAlloc, 77 | .tp_new = HostReservationParser4_new, 78 | }; 79 | 80 | int 81 | HostReservationParser4_define() { 82 | // PyType_Ready - finish type initialisation 83 | if (PyType_Ready(&HostReservationParser4Type) < 0) { 84 | return (1); 85 | } 86 | Py_INCREF(&HostReservationParser4Type); 87 | // REFCOUNT: PyModule_AddObject steals reference on success 88 | if (PyModule_AddObject(kea_module, "HostReservationParser4", (PyObject *) &HostReservationParser4Type) < 0) { 89 | Py_DECREF(&HostReservationParser4Type); 90 | return (1); 91 | } 92 | 93 | return (0); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq "$(VER)" "" 2 | VER=3.0.0 3 | endif 4 | 5 | help: 6 | @echo "run on host" 7 | @echo " build-kea-dev - build kea-dev:$(VER) image" 8 | @echo " build-kea - build kea:$(VER) image" 9 | @echo " build-dhtest - build dhtest image" 10 | @echo " run-kea-dev - run kea-dev:$(VER) shell" 11 | @echo " run-kea - run kea:$(VER) shell" 12 | @echo " run-mysql - run mariadb for kea with schema for $(VER)" 13 | @echo " run-dhtest - run dhtest shell" 14 | @echo "run on host or inside kea-dev shell" 15 | @echo " build - build-hook and build-module" 16 | @echo " install - install-hook and install-module" 17 | @echo " clean - remove all generated files" 18 | @echo " build-hook - build libkea_python" 19 | @echo " build-module - build kea extension module" 20 | @echo " clean-module - delete kea extension module build files" 21 | @echo " install-hook - install libkea_python" 22 | @echo " install-module - install kea extension module" 23 | @echo " test-module - run unit tests for kea extension module" 24 | 25 | build-kea-dev: 26 | docker build --build-arg VER=$(VER) -f DockerfileDev --tag kea-dev:$(VER) . 27 | 28 | build-kea: 29 | docker build --build-arg VER=$(VER) --tag kea:$(VER) . 30 | 31 | build-dhtest: 32 | cd dhtest && docker build --tag dhtest . 33 | 34 | run-kea-dev: kea-network 35 | docker run --rm -it --network kea -e LANG=C.UTF-8 --privileged -v`pwd`:/workdir -w /workdir --name kea-dev --hostname kea kea-dev:$(VER) bash 36 | 37 | run-kea: kea-network 38 | docker run --rm -it --network kea -e LANG=C.UTF-8 --privileged -v`pwd`:/workdir -w /workdir --name kea --hostname kea kea:$(VER) bash 39 | 40 | run-mysql: kea-network dhcpdb_create.mysql.sql 41 | docker run --rm --network kea \ 42 | -e MYSQL_ROOT_PASSWORD=admin -e MYSQL_DATABASE=kea -e MYSQL_USER=kea -eMYSQL_PASSWORD=kea \ 43 | --name mysql --hostname mysql \ 44 | -v `pwd`/dhcpdb_create-$(VER).mysql.sql:/docker-entrypoint-initdb.d/dhcpdb_create.mysql.sql \ 45 | mariadb --general_log 46 | 47 | run-dhtest: kea-network 48 | docker run --rm -it --network kea --privileged -v`pwd`:/workdir --name dhtest dhtest bash 49 | 50 | kea-network: 51 | docker network ls | grep -q kea || docker network create --subnet=172.28.5.0/24 --ip-range=172.28.5.0/24 kea 52 | 53 | build: build-hook build-module 54 | 55 | build-hook: settings.mk 56 | cd keahook && make libkea_python.so 57 | 58 | build-module: settings.mk 59 | cd keamodule && python3 setup.py build 60 | 61 | clean-module: 62 | cd keamodule && rm -rf build 63 | 64 | install: install-hook install-module 65 | 66 | install-hook: build-hook 67 | cd keahook && make install 68 | 69 | install-module: build-module 70 | cd keamodule && python3 setup.py install --single-version-externally-managed --root=/ 71 | 72 | clean: 73 | touch settings.mk 74 | cd keahook && make clean 75 | cd keamodule && rm -rf build 76 | rm -f settings.mk dhcpdb_create.mysql.sql 77 | 78 | settings.mk: 79 | python3 settings.py 80 | 81 | dhcpdb_create.mysql.sql: 82 | tar x --strip-components 6 -f kea-$(VER).tar.* kea-$(VER)/src/share/database/scripts/mysql/dhcpdb_create.mysql 83 | mv dhcpdb_create.mysql dhcpdb_create-$(VER).mysql.sql 84 | 85 | test-module: 86 | PYTHONPATH=$(wildcard keamodule/build/lib.*) python3 -m unittest discover -s keamodule/tests 87 | 88 | .PHONY: help \ 89 | build-kea-dev build-kea build-dhtest run-kea-dev run-kea run-mysql run-dhtest kea-network \ 90 | build-hook build-module test-module 91 | -------------------------------------------------------------------------------- /keamodule/cfg_mgr.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | 6 | extern "C" { 7 | 8 | static PyObject * 9 | CfgMgr_getCurrentCfg(CfgMgrObject *self, PyObject *args) { 10 | try { 11 | SrvConfigPtr ptr = CfgMgr::instance().getCurrentCfg(); 12 | // REFCOUNT: SrvConfig_from_ptr - returns new reference 13 | return (SrvConfig_from_ptr(ptr)); 14 | } 15 | catch (const exception &e) { 16 | PyErr_SetString(PyExc_TypeError, e.what()); 17 | return (0); 18 | } 19 | } 20 | 21 | static PyObject * 22 | CfgMgr_getStagingCfg(CfgMgrObject *self, PyObject *args) { 23 | try { 24 | SrvConfigPtr ptr = CfgMgr::instance().getStagingCfg(); 25 | // REFCOUNT: SrvConfig_from_ptr - returns new reference 26 | return (SrvConfig_from_ptr(ptr)); 27 | } 28 | catch (const exception &e) { 29 | PyErr_SetString(PyExc_TypeError, e.what()); 30 | return (0); 31 | } 32 | } 33 | 34 | PyObject * 35 | CfgMgr_from_ptr(CfgMgr *mgr) { 36 | // REFCOUNT: PyObject_New - returns new reference 37 | CfgMgrObject *self = PyObject_New(CfgMgrObject, &CfgMgrType); 38 | if (self) { 39 | self->mgr = mgr; 40 | } 41 | return ((PyObject *)self); 42 | } 43 | 44 | static PyObject * 45 | CfgMgr_instance(CfgMgrObject *self, PyObject *args) { 46 | try { 47 | CfgMgr& mgr = CfgMgr::instance(); 48 | // REFCOUNT: CfgMgr_from_ptr - returns new reference 49 | return (CfgMgr_from_ptr(&mgr)); 50 | } 51 | catch (const exception &e) { 52 | PyErr_SetString(PyExc_TypeError, e.what()); 53 | return (0); 54 | } 55 | } 56 | 57 | static PyMethodDef CfgMgr_methods[] = { 58 | {"instance", (PyCFunction) CfgMgr_instance, METH_NOARGS|METH_STATIC, 59 | "Returns a single instance of Configuration Manager." 60 | " CfgMgr is a singleton and this method is the only way of accessing it."}, 61 | {"getCurrentCfg", (PyCFunction) CfgMgr_getCurrentCfg, METH_NOARGS, 62 | "Returns the current configuration."}, 63 | {"getStagingCfg", (PyCFunction) CfgMgr_getStagingCfg, METH_NOARGS, 64 | "Returns the staging configuration."}, 65 | {0} // Sentinel 66 | }; 67 | 68 | // tp_dealloc - called when refcount is zero 69 | static void 70 | CfgMgr_dealloc(CfgMgrObject *self) { 71 | Py_TYPE(self)->tp_free((PyObject *) self); 72 | } 73 | 74 | // tp_init - called after tp_new has returned an instance 75 | static int 76 | CfgMgr_init(CfgMgrObject *self, PyObject *args, PyObject *kwds) { 77 | PyErr_SetString(PyExc_TypeError, "cannot create 'kea.CfgMgr' instances"); 78 | return (-1); 79 | } 80 | 81 | PyTypeObject CfgMgrType = { 82 | .ob_base = PyObject_HEAD_INIT(0) 83 | .tp_name = "kea.CfgMgr", 84 | .tp_basicsize = sizeof(CfgMgrObject), 85 | .tp_dealloc = (destructor) CfgMgr_dealloc, 86 | .tp_flags = Py_TPFLAGS_DEFAULT, 87 | .tp_doc = PyDoc_STR("Kea server CfgMgr"), 88 | .tp_methods = CfgMgr_methods, 89 | .tp_init = (initproc) CfgMgr_init, 90 | .tp_alloc = PyType_GenericAlloc, 91 | .tp_new = PyType_GenericNew, 92 | }; 93 | 94 | int 95 | CfgMgr_define() { 96 | // PyType_Ready - finish type initialisation 97 | if (PyType_Ready(&CfgMgrType) < 0) { 98 | return (1); 99 | } 100 | Py_INCREF(&CfgMgrType); 101 | // REFCOUNT: PyModule_AddObject steals reference on success 102 | if (PyModule_AddObject(kea_module, "CfgMgr", (PyObject *) &CfgMgrType) < 0) { 103 | Py_DECREF(&CfgMgrType); 104 | return (1); 105 | } 106 | 107 | return (0); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /keamodule/client_class_def_parser.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | #include 3 | 4 | using namespace std; 5 | using namespace isc::dhcp; 6 | using namespace isc::data; 7 | 8 | extern "C" { 9 | 10 | static PyObject * 11 | ClientClassDefParser_parse(ClientClassDefParserObject *self, PyObject *args) { 12 | ClientClassDictionaryObject *class_dictionary_obj; 13 | PyObject *config; 14 | int family = AF_INET; 15 | 16 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 17 | if (!PyArg_ParseTuple(args, "O!O|i", &ClientClassDictionaryType, &class_dictionary_obj, &config, &family)) { 18 | return (0); 19 | } 20 | 21 | try { 22 | ElementPtr data = object_to_element(config); 23 | 24 | self->ptr->parse(class_dictionary_obj->ptr, data, family); 25 | 26 | // Return None - the class is added to the dictionary 27 | Py_RETURN_NONE; 28 | } 29 | catch (const exception &e) { 30 | // REFCOUNT: PyErr_SetString - does not affect reference counts 31 | PyErr_SetString(PyExc_TypeError, e.what()); 32 | return (0); 33 | } 34 | } 35 | 36 | static PyMethodDef ClientClassDefParser_methods[] = { 37 | {"parse", (PyCFunction) ClientClassDefParser_parse, METH_VARARGS, 38 | "Parses a single client class definition and adds to the class dictionary."}, 39 | {0} // Sentinel 40 | }; 41 | 42 | // tp_dealloc - called when refcount is zero 43 | static void 44 | ClientClassDefParser_dealloc(ClientClassDefParserObject *self) { 45 | self->ptr.~ClientClassDefParserPtr(); 46 | Py_TYPE(self)->tp_free((PyObject *) self); 47 | } 48 | 49 | // tp_init - called after tp_new has returned an instance 50 | static int 51 | ClientClassDefParser_init(ClientClassDefParserObject *self, PyObject *args, PyObject *kwds) { 52 | if (kwds != 0) { 53 | // REFCOUNT: PyErr_SetString - does not affect reference counts 54 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 55 | return (-1); 56 | } 57 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 58 | if (!PyArg_ParseTuple(args, "")) { 59 | return (-1); 60 | } 61 | 62 | return (0); 63 | } 64 | 65 | // tp_new - allocate space and initialisation that can be repeated 66 | static PyObject * 67 | ClientClassDefParser_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 68 | ClientClassDefParserObject *self; 69 | // REFCOUNT: tp_alloc - returns new reference 70 | self = (ClientClassDefParserObject *) type->tp_alloc(type, 0); 71 | new (&self->ptr) ClientClassDefParserPtr(boost::make_shared()); 72 | return ((PyObject *) self); 73 | } 74 | 75 | PyTypeObject ClientClassDefParserType = { 76 | .ob_base = PyObject_HEAD_INIT(0) 77 | .tp_name = "kea.ClientClassDefParser", 78 | .tp_basicsize = sizeof(ClientClassDefParserObject), 79 | .tp_dealloc = (destructor) ClientClassDefParser_dealloc, 80 | .tp_flags = Py_TPFLAGS_DEFAULT, 81 | .tp_doc = PyDoc_STR("Kea server ClientClassDefParser"), 82 | .tp_methods = ClientClassDefParser_methods, 83 | .tp_init = (initproc) ClientClassDefParser_init, 84 | .tp_alloc = PyType_GenericAlloc, 85 | .tp_new = ClientClassDefParser_new, 86 | }; 87 | 88 | int 89 | ClientClassDefParser_define() { 90 | // PyType_Ready - finish type initialisation 91 | if (PyType_Ready(&ClientClassDefParserType) < 0) { 92 | return (1); 93 | } 94 | Py_INCREF(&ClientClassDefParserType); 95 | // REFCOUNT: PyModule_AddObject steals reference on success 96 | if (PyModule_AddObject(kea_module, "ClientClassDefParser", (PyObject *) &ClientClassDefParserType) < 0) { 97 | Py_DECREF(&ClientClassDefParserType); 98 | return (1); 99 | } 100 | 101 | return (0); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /keamodule/config_backend_dhcp4_mgr.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | using namespace isc::asiolink; 6 | using namespace isc::util; 7 | 8 | extern "C" { 9 | 10 | static PyObject * 11 | ConfigBackendDHCPv4Mgr_addBackend(ConfigBackendDHCPv4MgrObject *self, PyObject *args) { 12 | const char *dbaccess; 13 | 14 | if (!PyArg_ParseTuple(args, "s", &dbaccess)) { 15 | return (0); 16 | } 17 | 18 | try { 19 | self->mgr->addBackend(dbaccess); 20 | Py_RETURN_NONE; 21 | } 22 | catch (const exception &e) { 23 | PyErr_SetString(PyExc_TypeError, e.what()); 24 | return (0); 25 | } 26 | } 27 | 28 | static PyObject * 29 | ConfigBackendDHCPv4Mgr_getPool(ConfigBackendDHCPv4MgrObject *self, PyObject *args) { 30 | try { 31 | boost::shared_ptr pool = self->mgr->getPool(); 32 | // REFCOUNT: ConfigBackendPoolDHCPv4_from_ptr - returns new reference 33 | return (ConfigBackendPoolDHCPv4_from_ptr(pool)); 34 | } 35 | catch (const exception &e) { 36 | PyErr_SetString(PyExc_TypeError, e.what()); 37 | return (0); 38 | } 39 | } 40 | 41 | PyObject * 42 | ConfigBackendDHCPv4Mgr_from_ptr(ConfigBackendDHCPv4Mgr *mgr) { 43 | // REFCOUNT: PyObject_New - returns new reference 44 | ConfigBackendDHCPv4MgrObject *self = PyObject_New(ConfigBackendDHCPv4MgrObject, &ConfigBackendDHCPv4MgrType); 45 | if (self) { 46 | self->mgr = mgr; 47 | } 48 | return ((PyObject *)self); 49 | } 50 | 51 | static PyObject * 52 | ConfigBackendDHCPv4Mgr_instance(ConfigBackendDHCPv4MgrObject *self, PyObject *args) { 53 | try { 54 | ConfigBackendDHCPv4Mgr& mgr = ConfigBackendDHCPv4Mgr::instance(); 55 | // REFCOUNT: ConfigBackendDHCPv4Mgr_from_ptr - returns new reference 56 | return (ConfigBackendDHCPv4Mgr_from_ptr(&mgr)); 57 | } 58 | catch (const exception &e) { 59 | PyErr_SetString(PyExc_TypeError, e.what()); 60 | return (0); 61 | } 62 | } 63 | 64 | static PyMethodDef ConfigBackendDHCPv4Mgr_methods[] = { 65 | {"instance", (PyCFunction) ConfigBackendDHCPv4Mgr_instance, METH_NOARGS|METH_STATIC, 66 | "Returns a sole instance of the ConfigBackendDHCPv4Mgr."}, 67 | {"addBackend", (PyCFunction) ConfigBackendDHCPv4Mgr_addBackend, METH_VARARGS, 68 | "Create an instance of a configuration backend."}, 69 | {"getPool", (PyCFunction) ConfigBackendDHCPv4Mgr_getPool, METH_NOARGS, 70 | "Attempts to delete a host by (subnet4-id, identifier, identifier-type)."}, 71 | {0} // Sentinel 72 | }; 73 | 74 | // tp_init - called after tp_new has returned an instance 75 | static int 76 | ConfigBackendDHCPv4Mgr_init(ConfigBackendDHCPv4MgrObject *self, PyObject *args, PyObject *kwds) { 77 | PyErr_SetString(PyExc_TypeError, "cannot create 'kea.ConfigBackendDHCPv4Mgr' instances"); 78 | return (-1); 79 | } 80 | 81 | PyTypeObject ConfigBackendDHCPv4MgrType = { 82 | .ob_base = PyObject_HEAD_INIT(0) 83 | .tp_name = "kea.ConfigBackendDHCPv4Mgr", 84 | .tp_basicsize = sizeof(ConfigBackendDHCPv4MgrObject), 85 | .tp_flags = Py_TPFLAGS_DEFAULT, 86 | .tp_doc = PyDoc_STR("Kea server ConfigBackendDHCPv4Mgr"), 87 | .tp_methods = ConfigBackendDHCPv4Mgr_methods, 88 | .tp_init = (initproc) ConfigBackendDHCPv4Mgr_init, 89 | .tp_alloc = PyType_GenericAlloc, 90 | .tp_new = PyType_GenericNew, 91 | }; 92 | 93 | int 94 | ConfigBackendDHCPv4Mgr_define() { 95 | // PyType_Ready - finish type initialisation 96 | if (PyType_Ready(&ConfigBackendDHCPv4MgrType) < 0) { 97 | return (1); 98 | } 99 | Py_INCREF(&ConfigBackendDHCPv4MgrType); 100 | // REFCOUNT: PyModule_AddObject steals reference on success 101 | if (PyModule_AddObject(kea_module, "ConfigBackendDHCPv4Mgr", (PyObject *) &ConfigBackendDHCPv4MgrType) < 0) { 102 | Py_DECREF(&ConfigBackendDHCPv4MgrType); 103 | return (1); 104 | } 105 | 106 | return (0); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /keamodule/srv_config.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::hooks; 5 | using namespace isc::dhcp; 6 | using namespace isc::data; 7 | 8 | extern "C" { 9 | 10 | static PyObject * 11 | SrvConfig_toElement(SrvConfigObject *self, PyObject *args) { 12 | try { 13 | ElementPtr ptr = self->ptr->toElement(); 14 | // REFCOUNT: element_to_object - returns new reference 15 | return (element_to_object(ptr)); 16 | } 17 | catch (const exception &e) { 18 | PyErr_SetString(PyExc_TypeError, e.what()); 19 | return (0); 20 | } 21 | } 22 | 23 | static PyObject * 24 | SrvConfig_getCfgSubnets4(SrvConfigObject *self, PyObject *args) { 25 | try { 26 | CfgSubnets4Ptr ptr = self->ptr->getCfgSubnets4(); 27 | // REFCOUNT: CfgSubnets4_from_ptr - returns new reference 28 | return (CfgSubnets4_from_ptr(ptr)); 29 | } 30 | catch (const exception &e) { 31 | PyErr_SetString(PyExc_TypeError, e.what()); 32 | return (0); 33 | } 34 | } 35 | 36 | static PyMethodDef SrvConfig_methods[] = { 37 | {"toElement", (PyCFunction) SrvConfig_toElement, METH_NOARGS, 38 | "Unparse configuration object."}, 39 | {"getCfgSubnets4", (PyCFunction) SrvConfig_getCfgSubnets4, METH_NOARGS, 40 | "Returns object holding subnets configuration for DHCPv4."}, 41 | {0} // Sentinel 42 | }; 43 | 44 | static PyObject * 45 | SrvConfig_use_count(OptionObject *self, void *closure) { 46 | // REFCOUNT: PyLong_FromLong - returns new reference 47 | return (PyLong_FromLong(self->ptr.use_count())); 48 | } 49 | 50 | static PyGetSetDef SrvConfig_getsetters[] = { 51 | {(char *)"use_count", (getter) SrvConfig_use_count, (setter) 0, (char *)"shared_ptr use count", 0}, 52 | {0} // Sentinel 53 | }; 54 | 55 | // tp_dealloc - called when refcount is zero 56 | static void 57 | SrvConfig_dealloc(SrvConfigObject *self) { 58 | self->ptr.~SrvConfigPtr(); 59 | Py_TYPE(self)->tp_free((PyObject *) self); 60 | } 61 | 62 | // tp_init - called after tp_new has returned an instance 63 | static int 64 | SrvConfig_init(SrvConfigObject *self, PyObject *args, PyObject *kwds) { 65 | new(&self->ptr) SrvConfigPtr; 66 | 67 | PyErr_SetString(PyExc_TypeError, "cannot create 'kea.SrvConfig' instances"); 68 | return (-1); 69 | } 70 | 71 | // tp_new - allocate space and initialisation that can be repeated 72 | static PyObject * 73 | SrvConfig_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 74 | SrvConfigObject *self; 75 | self = (SrvConfigObject *) type->tp_alloc(type, 0); 76 | if (self) { 77 | new(&self->ptr) SrvConfigPtr; 78 | } 79 | return ((PyObject *) self); 80 | } 81 | 82 | PyTypeObject SrvConfigType = { 83 | .ob_base = PyObject_HEAD_INIT(0) 84 | .tp_name = "kea.SrvConfig", 85 | .tp_basicsize = sizeof(SrvConfigObject), 86 | .tp_dealloc = (destructor) SrvConfig_dealloc, 87 | .tp_flags = Py_TPFLAGS_DEFAULT, 88 | .tp_doc = PyDoc_STR("Kea server SrvConfig"), 89 | .tp_methods = SrvConfig_methods, 90 | .tp_getset = SrvConfig_getsetters, 91 | .tp_init = (initproc) SrvConfig_init, 92 | .tp_alloc = PyType_GenericAlloc, 93 | .tp_new = SrvConfig_new, 94 | }; 95 | 96 | PyObject * 97 | SrvConfig_from_ptr(SrvConfigPtr &ptr) { 98 | // REFCOUNT: PyObject_New - returns new reference 99 | SrvConfigObject *self = PyObject_New(SrvConfigObject, &SrvConfigType); 100 | if (self) { 101 | new(&self->ptr) SrvConfigPtr; 102 | self->ptr = ptr; 103 | } 104 | return (PyObject *)self; 105 | } 106 | 107 | int 108 | SrvConfig_define() { 109 | // PyType_Ready - finish type initialisation 110 | if (PyType_Ready(&SrvConfigType) < 0) { 111 | return (1); 112 | } 113 | Py_INCREF(&SrvConfigType); 114 | // REFCOUNT: PyModule_AddObject steals reference on success 115 | if (PyModule_AddObject(kea_module, "SrvConfig", (PyObject *) &SrvConfigType) < 0) { 116 | Py_DECREF(&SrvConfigType); 117 | return (1); 118 | } 119 | 120 | return (0); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /keamodule/errors.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | 5 | extern "C" { 6 | static PyObject *traceback_module; 7 | 8 | int 9 | Errors_initialize() { 10 | // REFCOUNT: PyImport_ImportModule - returns new reference 11 | traceback_module = PyImport_ImportModule("traceback"); 12 | if (!traceback_module) { 13 | log_error("import traceback failed"); 14 | return (1); 15 | } 16 | 17 | return (0); 18 | } 19 | 20 | int 21 | Errors_finalize() { 22 | if (traceback_module) { 23 | Py_DECREF(traceback_module); 24 | traceback_module = 0; 25 | } 26 | 27 | return (0); 28 | } 29 | 30 | static string 31 | to_string(PyObject *object) { 32 | if (!object) { 33 | return ("NULL"); 34 | } 35 | // REFCOUNT: PyObject_Str - returns new reference 36 | PyObject *str = PyObject_Str(object); 37 | if (!str) { 38 | return ("NULL"); 39 | } 40 | // REFCOUNT: PyUnicode_AsUTF8 - returns UTF-8 encoding of str - buffer cached in str 41 | string ret(PyUnicode_AsUTF8(str)); 42 | Py_DECREF(str); 43 | 44 | return (ret); 45 | } 46 | 47 | int 48 | format_python_traceback(PyObject *exc_type, PyObject *exc_value, PyObject *exc_traceback, string &traceback) { 49 | // import traceback 50 | // exc_type, exc_value, exc_traceback = sys.exc_info() 51 | // return ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) 52 | PyObject *format_exception = 0; 53 | PyObject *line_list = 0; 54 | PyObject *empty_string = 0; 55 | PyObject *formatted_error = 0; 56 | int res = 1; 57 | 58 | // REFCOUNT: PyErr_NormalizeException - reference neutral 59 | PyErr_NormalizeException(&exc_type, &exc_value, &exc_traceback); 60 | 61 | if (!exc_traceback || !traceback_module) { 62 | // PyExceptionClass_Name - returns C name of exception 63 | traceback = string(PyExceptionClass_Name(exc_type)) + ": " + to_string(exc_value); 64 | res = 0; 65 | goto error; 66 | } 67 | else { 68 | // PyObject_GetAttrString - returns new reference 69 | format_exception = PyObject_GetAttrString(traceback_module, "format_exception"); 70 | if (!format_exception) { 71 | log_error("traceback.format_exception not found"); 72 | goto error; 73 | } 74 | // REFCOUNT: PyObject_CallFunction - returns new reference 75 | line_list = PyObject_CallFunction(format_exception, "OOO", exc_type, exc_value, exc_traceback); 76 | if (!line_list) { 77 | log_error("traceback.format_exception(" + to_string(exc_type) + ", " + to_string(exc_value) + ", " + to_string(exc_traceback) + ") failed"); 78 | goto error; 79 | } 80 | } 81 | // REFCOUNT: PyUnicode_FromString - returns new reference 82 | empty_string = PyUnicode_FromString(""); 83 | if (!empty_string) { 84 | goto error; 85 | } 86 | // REFCOUNT: PyUnicode_Join - returns new reference 87 | formatted_error = PyUnicode_Join(empty_string, line_list); 88 | if (!formatted_error) { 89 | log_error("''.join(line_list) failed"); 90 | goto error; 91 | } 92 | // REFCOUNT: PyUnicode_AsUTF8 - returns UTF-8 encoding of str - buffer cached in str 93 | traceback = string(PyUnicode_AsUTF8(formatted_error)); 94 | res = 0; 95 | 96 | error: 97 | PyErr_Clear(); 98 | 99 | Py_XDECREF(formatted_error); 100 | Py_XDECREF(empty_string); 101 | Py_XDECREF(line_list); 102 | Py_XDECREF(format_exception); 103 | Py_XDECREF(exc_type); 104 | Py_XDECREF(exc_value); 105 | Py_XDECREF(exc_traceback); 106 | 107 | return (res); 108 | } 109 | 110 | int 111 | log_python_traceback() { 112 | PyObject *exc_type, *exc_value, *exc_traceback; 113 | string traceback; 114 | 115 | // REFCOUNT: PyErr_Fetch - creates new references 116 | PyErr_Fetch(&exc_type, &exc_value, &exc_traceback); 117 | // REFCOUNT: steals reference to exc_* objects 118 | if (!format_python_traceback(exc_type, exc_value, exc_traceback, traceback)) { 119 | log_error(traceback); 120 | return (0); 121 | } 122 | 123 | return (1); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /keahook/load_unload.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "messages.h" 6 | 7 | using namespace std; 8 | using namespace isc::hooks; 9 | using namespace isc::data; 10 | using namespace isc::log; 11 | 12 | extern "C" { 13 | #include 14 | #include 15 | #include 16 | #include "../keamodule/keacapsule.h" 17 | 18 | static Logger logger("python"); 19 | 20 | static void *libpython; 21 | 22 | void (*dl_Py_Initialize)(void); 23 | void (*dl_PyEval_InitThreads)(void); 24 | void (*dl_Py_Finalize)(void); 25 | void* (*dl_PyCapsule_Import)(const char *name, int no_block); 26 | 27 | static int 28 | find_symbol(void **sym, const char *name) { 29 | *sym = dlsym(libpython, name); 30 | if (*sym == 0) { 31 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg(string("symbol ").append(name).append(" not found")); 32 | } 33 | return (*sym == 0); 34 | } 35 | 36 | #define load_symbol(name) find_symbol((void **)&dl_ ## name, #name) 37 | 38 | static int 39 | load_libpython(string name) { 40 | libpython = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL); 41 | if (!libpython) { 42 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg(string("dlopen ").append(name).append(" failed")); 43 | return (1); 44 | } 45 | if (load_symbol(Py_Initialize) 46 | || load_symbol(PyEval_InitThreads) 47 | || load_symbol(Py_Finalize) 48 | || load_symbol(PyCapsule_Import)) { 49 | return (1); 50 | } 51 | return (0); 52 | } 53 | 54 | static int 55 | unload_libpython() { 56 | if (libpython) { 57 | dlclose(libpython); 58 | libpython = 0; 59 | } 60 | return (0); 61 | } 62 | 63 | static int 64 | python_initialize() { 65 | dl_Py_Initialize(); 66 | dl_PyEval_InitThreads(); 67 | return (0); 68 | } 69 | 70 | static int 71 | python_finalize() { 72 | if (dl_Py_Finalize) { 73 | dl_Py_Finalize(); 74 | } 75 | return (0); 76 | } 77 | 78 | static int 79 | load_kea_capsule(LibraryHandle &handle, string module) { 80 | kea_capsule = (void **)dl_PyCapsule_Import("kea._C_API", 0); 81 | if (!kea_capsule) { 82 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg("PyCapsule_Import(\"kea._C_API\") failed"); 83 | return (1); 84 | } 85 | Kea_SetLogger(logger, LOG_PYTHON); 86 | if (Kea_Load(&handle, module.c_str())) { 87 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg("Kea_Load failed"); 88 | return (1); 89 | } 90 | return (0); 91 | } 92 | 93 | static int 94 | unload_kea_capsule() { 95 | if (kea_capsule) { 96 | Kea_Unload(); 97 | } 98 | return (0); 99 | } 100 | 101 | int 102 | load(LibraryHandle &handle) { 103 | ConstElementPtr libpython = handle.getParameter("libpython"); 104 | ConstElementPtr module = handle.getParameter("module"); 105 | 106 | // check libpython parameter 107 | if (!libpython) { 108 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg("missing \"libpython\" parameter"); 109 | return (1); 110 | } 111 | if (libpython->getType() != Element::string) { 112 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg("\"libpython\" parameter must be a string"); 113 | return (1); 114 | } 115 | // check module parameter 116 | if (!module) { 117 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg("missing \"module\" parameter"); 118 | return (1); 119 | } 120 | if (module->getType() != Element::string) { 121 | LOG_ERROR(logger, LOG_PYTHON_HOOK).arg("\"module\" parameter must be a string"); 122 | return (1); 123 | } 124 | 125 | if (load_libpython(libpython->stringValue())) { 126 | return (1); 127 | } 128 | python_initialize(); 129 | if (load_kea_capsule(handle, module->stringValue())) { 130 | return (1); 131 | } 132 | 133 | LOG_INFO(logger, LOG_PYTHON_HOOK).arg("kea_python loaded"); 134 | 135 | return (0); 136 | } 137 | 138 | int 139 | unload() { 140 | unload_kea_capsule(); 141 | python_finalize(); 142 | unload_libpython(); 143 | LOG_INFO(logger, LOG_PYTHON_HOOK).arg("kea_python unloaded"); 144 | return (0); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /keamodule/callout_closure.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::hooks; 5 | 6 | extern "C" { 7 | 8 | // Called when Kea invokes the bound callout. 9 | // typedef int (*isc::hooks::CalloutPtr)(CalloutHandle &) 10 | static void 11 | CalloutClosure_binding(ffi_cif *cif, void *ret, void* args[], void *userdata) { 12 | PyObject *handle = 0; 13 | PyObject *result = 0; 14 | CalloutClosureObject *obj = (CalloutClosureObject *)userdata; 15 | int res = 1; 16 | 17 | // Kea calling us again - get the GIL. 18 | end_allow_threads(); 19 | 20 | // REFCOUNT: CalloutHandle_from_handle - returns new reference 21 | handle = CalloutHandle_from_handle(*(CalloutHandle **)args[0]); 22 | if (!handle) { 23 | log_error("could not create CalloutHandle"); 24 | goto error; 25 | } 26 | // REFCOUNT: PyObject_CallFunction - returns new reference 27 | result = PyObject_CallFunction(obj->callout, "O", handle); 28 | if (!result) { 29 | log_python_traceback(); 30 | goto error; 31 | } 32 | if (!PyLong_CheckExact(result)) { 33 | // REFCOUNT: PyUnicode_AsUTF8 - returns UTF-8 encoding of str - buffer cached in str 34 | log_error(string(PyUnicode_AsUTF8(obj->name)) + " must return integer"); 35 | goto error; 36 | } 37 | 38 | res = PyLong_AsLong(result); 39 | 40 | error: 41 | *(ffi_arg *)ret = res; 42 | Py_XDECREF(result); 43 | Py_XDECREF(handle); 44 | 45 | // Going back into Kea - give up the GIL. 46 | begin_allow_threads(); 47 | } 48 | 49 | // tp_dealloc - called when refcount is zero 50 | static void 51 | CalloutClosure_dealloc(CalloutClosureObject *self) { 52 | if (self->closure) { 53 | ffi_closure_free(self->closure); 54 | } 55 | Py_XDECREF(self->name); 56 | Py_XDECREF(self->callout); 57 | Py_TYPE(self)->tp_free((PyObject *) self); 58 | } 59 | 60 | PyTypeObject CalloutClosureType = { 61 | .ob_base = PyObject_HEAD_INIT(0) 62 | .tp_name = "kea.CalloutClosure", 63 | .tp_basicsize = sizeof(CalloutClosureObject), 64 | .tp_dealloc = (destructor) CalloutClosure_dealloc, 65 | .tp_flags = Py_TPFLAGS_DEFAULT, 66 | .tp_doc = PyDoc_STR("Kea server callout closure"), 67 | .tp_alloc = PyType_GenericAlloc, 68 | .tp_new = PyType_GenericNew, 69 | }; 70 | 71 | PyObject * 72 | CalloutClosure_from_object(PyObject *name, PyObject *callout) { 73 | // REFCOUNT: PyObject_New - returns new reference 74 | CalloutClosureObject *obj = PyObject_New(CalloutClosureObject, &CalloutClosureType); 75 | if (!obj) { 76 | return (0); 77 | } 78 | obj->closure = 0; 79 | obj->bound_callout = 0; 80 | obj->name = 0; 81 | obj->callout = 0; 82 | 83 | // see https://www.chiark.greenend.org.uk/doc/libffi-dev/html/Closure-Example.html 84 | // allocate memory for the closure in closure and save code address in bound_callout 85 | obj->closure = (ffi_closure *)ffi_closure_alloc(sizeof(ffi_closure), &obj->bound_callout); 86 | if (!obj->closure) { 87 | Py_DECREF(obj); 88 | return (PyErr_NoMemory()); 89 | } 90 | // callout used one argument of type pointer 91 | obj->args[0] = &ffi_type_pointer; 92 | // prepare cif structure for use in ffi call 93 | int res = ffi_prep_cif(&obj->cif, FFI_DEFAULT_ABI, 1, &ffi_type_sint, obj->args); 94 | if (res != FFI_OK) { 95 | PyErr_Format(PyExc_RuntimeError, "ffi_prep_cif failed with result %d", res); 96 | Py_DECREF(obj); 97 | return (0); 98 | } 99 | // prepare the closure function to invoke CalloutClosure_binding with obj as the userdata 100 | res = ffi_prep_closure_loc(obj->closure, &obj->cif, CalloutClosure_binding, obj, obj->bound_callout); 101 | if (res != FFI_OK) { 102 | PyErr_Format(PyExc_RuntimeError, "ffi_prep_closure_loc failed with result %d", res); 103 | Py_DECREF(obj); 104 | return (0); 105 | } 106 | Py_INCREF(name); 107 | Py_INCREF(callout); 108 | obj->name = name; 109 | obj->callout = callout; 110 | 111 | return ((PyObject *) obj); 112 | } 113 | 114 | int 115 | CalloutClosure_define() { 116 | // PyType_Ready - finish type initialisation 117 | if (PyType_Ready(&CalloutClosureType) < 0) { 118 | return (1); 119 | } 120 | 121 | return (0); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /keamodule/client_class_dictionary.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | 6 | extern "C" { 7 | 8 | static PyObject * 9 | ClientClassDictionary_getClasses(ClientClassDictionaryObject *self, PyObject *Py_UNUSED(ignored)) { 10 | try { 11 | const ClientClassDefListPtr& classes = self->ptr->getClasses(); 12 | // REFCOUNT: PyList_New - returns new reference 13 | PyObject *list = PyList_New(0); 14 | if (!list) { 15 | return (0); 16 | } 17 | 18 | for (const auto& class_def : *classes) { 19 | // REFCOUNT: ClientClassDef_from_ptr - returns new reference 20 | PyObject *obj = ClientClassDef_from_ptr(const_cast(class_def)); 21 | if (!obj) { 22 | // REFCOUNT: Py_DECREF - decrements reference count 23 | Py_DECREF(list); 24 | return (0); 25 | } 26 | // REFCOUNT: PyList_Append - does not steal reference 27 | if (PyList_Append(list, obj) < 0) { 28 | // REFCOUNT: Py_DECREF - decrements reference count 29 | Py_DECREF(obj); 30 | Py_DECREF(list); 31 | return (0); 32 | } 33 | // REFCOUNT: Py_DECREF - decrements reference count (PyList_Append doesn't steal) 34 | Py_DECREF(obj); 35 | } 36 | 37 | return list; 38 | } 39 | catch (const exception &e) { 40 | // REFCOUNT: PyErr_SetString - does not affect reference counts 41 | PyErr_SetString(PyExc_TypeError, e.what()); 42 | return (0); 43 | } 44 | } 45 | 46 | static PyMethodDef ClientClassDictionary_methods[] = { 47 | {"getClasses", (PyCFunction) ClientClassDictionary_getClasses, METH_NOARGS, 48 | "Returns a list of ClientClassDef objects."}, 49 | {0} // Sentinel 50 | }; 51 | 52 | // tp_dealloc - called when refcount is zero 53 | static void 54 | ClientClassDictionary_dealloc(ClientClassDictionaryObject *self) { 55 | self->ptr.~ClientClassDictionaryPtr(); 56 | // REFCOUNT: tp_free - frees object 57 | Py_TYPE(self)->tp_free((PyObject *) self); 58 | } 59 | 60 | // tp_init - called after tp_new has returned an instance 61 | static int 62 | ClientClassDictionary_init(ClientClassDictionaryObject *self, PyObject *args, PyObject *kwds) { 63 | if (kwds != 0) { 64 | // REFCOUNT: PyErr_SetString - does not affect reference counts 65 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 66 | return (-1); 67 | } 68 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 69 | if (!PyArg_ParseTuple(args, "")) { 70 | return (-1); 71 | } 72 | 73 | return (0); 74 | } 75 | 76 | // tp_new - allocate space and initialisation that can be repeated 77 | static PyObject * 78 | ClientClassDictionary_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 79 | ClientClassDictionaryObject *self; 80 | // REFCOUNT: tp_alloc - returns new reference 81 | self = (ClientClassDictionaryObject *) type->tp_alloc(type, 0); 82 | new (&self->ptr) ClientClassDictionaryPtr(boost::make_shared()); 83 | return ((PyObject *) self); 84 | } 85 | 86 | PyTypeObject ClientClassDictionaryType = { 87 | .ob_base = PyObject_HEAD_INIT(0) 88 | .tp_name = "kea.ClientClassDictionary", 89 | .tp_basicsize = sizeof(ClientClassDictionaryObject), 90 | .tp_dealloc = (destructor) ClientClassDictionary_dealloc, 91 | .tp_flags = Py_TPFLAGS_DEFAULT, 92 | .tp_doc = PyDoc_STR("Kea server ClientClassDictionary"), 93 | .tp_methods = ClientClassDictionary_methods, 94 | .tp_init = (initproc) ClientClassDictionary_init, 95 | .tp_alloc = PyType_GenericAlloc, 96 | .tp_new = ClientClassDictionary_new, 97 | }; 98 | 99 | int 100 | ClientClassDictionary_define() { 101 | // PyType_Ready - finish type initialisation 102 | if (PyType_Ready(&ClientClassDictionaryType) < 0) { 103 | return (1); 104 | } 105 | Py_INCREF(&ClientClassDictionaryType); 106 | // REFCOUNT: PyModule_AddObject steals reference on success 107 | if (PyModule_AddObject(kea_module, "ClientClassDictionary", (PyObject *) &ClientClassDictionaryType) < 0) { 108 | Py_DECREF(&ClientClassDictionaryType); 109 | return (1); 110 | } 111 | 112 | return (0); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /keamodule/callouts.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::hooks; 5 | 6 | extern "C" { 7 | 8 | static const char *hooks[] = { 9 | "dhcp4_srv_configured", 10 | "cb4_updated", 11 | "buffer4_receive", 12 | "pkt4_receive", 13 | "subnet4_select", 14 | "host4_identifier", 15 | "lease4_select", 16 | "lease4_renew", 17 | "lease4_release", 18 | "lease4_decline", 19 | "leases4_committed", 20 | "pkt4_send", 21 | "buffer4_send", 22 | "lease4_expire", 23 | "lease4_recover", 24 | "command_processed" 25 | }; 26 | #define num_hooks (sizeof(hooks) / sizeof(hooks[0])) 27 | 28 | // dict of CalloutClosureObject objects by name 29 | static PyObject *callout_closures; 30 | 31 | int 32 | Callouts_add_closure(CalloutClosureObject *obj) { 33 | // REFCOUNT: PyDict_SetItem - reference neutral 34 | return (PyDict_SetItem(callout_closures, obj->name, (PyObject *)obj)); 35 | } 36 | 37 | static int 38 | register_callouts(LibraryHandle *handle) { 39 | for (unsigned int i = 0; i < num_hooks; i++) { 40 | PyObject *callout; 41 | 42 | // REFCOUNT: PyObject_GetAttrString - returns new reference 43 | callout = PyObject_GetAttrString(hook_module, hooks[i]); 44 | if (!callout) { 45 | PyErr_Clear(); 46 | continue; 47 | } 48 | if (!PyCallable_Check(callout)) { 49 | log_error(string(hooks[i]) + " must be callable"); 50 | Py_DECREF(callout); 51 | return (1); 52 | } 53 | // REFCOUNT: PyUnicode_FromString - returns new reference 54 | PyObject *name = PyUnicode_FromString(hooks[i]); 55 | if (!name) { 56 | Py_DECREF(callout); 57 | return (1); 58 | } 59 | // REFCOUNT: CalloutClosure_from_object - returns new reference 60 | CalloutClosureObject *obj = (CalloutClosureObject *) CalloutClosure_from_object(name, callout); 61 | if (!obj) { 62 | Py_DECREF(callout); 63 | Py_DECREF(name); 64 | return (1); 65 | } 66 | Py_DECREF(callout); 67 | Py_DECREF(name); 68 | // REFCOUNT: Callouts_add_closure - manage obj in dict 69 | if (Callouts_add_closure(obj)) { 70 | Py_DECREF(obj); 71 | return (1); 72 | } 73 | Py_DECREF(obj); 74 | try { 75 | handle->registerCallout(hooks[i], (CalloutPtr)obj->bound_callout); 76 | } 77 | catch (const exception &e) { 78 | PyErr_SetString(PyExc_TypeError, e.what()); 79 | return (0); 80 | } 81 | } 82 | return (0); 83 | } 84 | 85 | static int 86 | unregister_callouts() { 87 | Py_XDECREF(callout_closures); 88 | callout_closures = 0; 89 | return (0); 90 | } 91 | 92 | static int 93 | call_load_callout(LibraryHandle *handle) { 94 | PyObject *load; 95 | PyObject *py_handle = 0; 96 | PyObject *py_result = 0; 97 | int res = 1; 98 | 99 | // REFCOUNT: PyObject_GetAttrString - returns new reference 100 | load = PyObject_GetAttrString(hook_module, "load"); 101 | if (!load) { 102 | PyErr_Clear(); 103 | return (0); 104 | } 105 | if (!PyCallable_Check(load)) { 106 | log_error("load must be callable"); 107 | goto error; 108 | } 109 | // REFCOUNT: LibraryHandle_from_handle - returns new reference 110 | py_handle = LibraryHandle_from_handle(handle); 111 | if (!py_handle) { 112 | log_error("could not create LibraryHandle"); 113 | goto error; 114 | } 115 | // REFCOUNT: PyObject_CallFunction - returns new reference 116 | py_result = PyObject_CallFunction(load, "O", py_handle); 117 | if (!py_result) { 118 | log_python_traceback(); 119 | goto error; 120 | } 121 | if (!PyLong_CheckExact(py_result)) { 122 | log_error("load must return integer"); 123 | goto error; 124 | } 125 | 126 | res = PyLong_AsLong(py_result); 127 | 128 | error: 129 | Py_XDECREF(py_result); 130 | Py_XDECREF(py_handle); 131 | Py_XDECREF(load); 132 | return (res); 133 | } 134 | 135 | int 136 | Callouts_register(LibraryHandle *handle) { 137 | int res; 138 | 139 | // REFCOUNT: PyDict_New - returns new reference 140 | callout_closures = PyDict_New(); 141 | if (!callout_closures) { 142 | return (1); 143 | } 144 | res = call_load_callout(handle); 145 | if (!res) { 146 | res = register_callouts(handle); 147 | } 148 | 149 | return (res); 150 | } 151 | 152 | int 153 | Callouts_unregister() { 154 | return (unregister_callouts()); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /keamodule/client_class_def.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | 6 | extern "C" { 7 | 8 | static bool 9 | ClientClassDef_is_initialized(ClientClassDefObject *self) { 10 | if (!self->ptr) { 11 | // REFCOUNT: PyErr_SetString - does not affect reference counts 12 | PyErr_SetString(PyExc_RuntimeError, "ClientClassDef object is not initialized"); 13 | return false; 14 | } 15 | return true; 16 | } 17 | 18 | static PyObject * 19 | ClientClassDef_getName(ClientClassDefObject *self, PyObject *Py_UNUSED(ignored)) { 20 | try { 21 | if (!ClientClassDef_is_initialized(self)) { 22 | return (0); 23 | } 24 | const string &name = self->ptr->getName(); 25 | // REFCOUNT: PyUnicode_FromString - returns new reference 26 | return PyUnicode_FromString(name.c_str()); 27 | } 28 | catch (const exception &e) { 29 | // REFCOUNT: PyErr_SetString - does not affect reference counts 30 | PyErr_SetString(PyExc_TypeError, e.what()); 31 | return (0); 32 | } 33 | } 34 | 35 | static PyObject * 36 | ClientClassDef_toElement(ClientClassDefObject *self, PyObject *Py_UNUSED(ignored)) { 37 | try { 38 | if (!ClientClassDef_is_initialized(self)) { 39 | return (0); 40 | } 41 | isc::data::ElementPtr element = self->ptr->toElement(); 42 | // REFCOUNT: element_to_object - returns new reference 43 | return element_to_object(element); 44 | } 45 | catch (const exception &e) { 46 | // REFCOUNT: PyErr_SetString - does not affect reference counts 47 | PyErr_SetString(PyExc_TypeError, e.what()); 48 | return (0); 49 | } 50 | } 51 | 52 | static PyMethodDef ClientClassDef_methods[] = { 53 | {"getName", (PyCFunction) ClientClassDef_getName, METH_NOARGS, 54 | "Returns the name of the client class."}, 55 | {"toElement", (PyCFunction) ClientClassDef_toElement, METH_NOARGS, 56 | "Returns the client class definition as a Python dictionary."}, 57 | {0} // Sentinel 58 | }; 59 | 60 | // tp_dealloc - called when refcount is zero 61 | static void 62 | ClientClassDef_dealloc(ClientClassDefObject *self) { 63 | self->ptr.~ClientClassDefPtr(); 64 | // REFCOUNT: tp_free - frees object 65 | Py_TYPE(self)->tp_free((PyObject *) self); 66 | } 67 | 68 | // tp_init - called after tp_new has returned an instance 69 | static int 70 | ClientClassDef_init(ClientClassDefObject *self, PyObject *args, PyObject *kwds) { 71 | if (kwds != 0) { 72 | // REFCOUNT: PyErr_SetString - does not affect reference counts 73 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 74 | return (-1); 75 | } 76 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 77 | if (!PyArg_ParseTuple(args, "")) { 78 | return (-1); 79 | } 80 | 81 | return (0); 82 | } 83 | 84 | // tp_new - allocate space and initialisation that can be repeated 85 | static PyObject * 86 | ClientClassDef_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 87 | ClientClassDefObject *self; 88 | // REFCOUNT: tp_alloc - returns new reference 89 | self = (ClientClassDefObject *) type->tp_alloc(type, 0); 90 | if (self) { 91 | new (&self->ptr) ClientClassDefPtr(); 92 | } 93 | return ((PyObject *) self); 94 | } 95 | 96 | PyTypeObject ClientClassDefType = { 97 | .ob_base = PyObject_HEAD_INIT(0) 98 | .tp_name = "kea.ClientClassDef", 99 | .tp_basicsize = sizeof(ClientClassDefObject), 100 | .tp_dealloc = (destructor) ClientClassDef_dealloc, 101 | .tp_flags = Py_TPFLAGS_DEFAULT, 102 | .tp_doc = PyDoc_STR("Kea server ClientClassDef"), 103 | .tp_methods = ClientClassDef_methods, 104 | .tp_init = (initproc) ClientClassDef_init, 105 | .tp_alloc = PyType_GenericAlloc, 106 | .tp_new = ClientClassDef_new, 107 | }; 108 | 109 | PyObject * 110 | ClientClassDef_from_ptr(ClientClassDefPtr &ptr) { 111 | // REFCOUNT: tp_alloc - returns new reference 112 | ClientClassDefObject *obj = (ClientClassDefObject *) ClientClassDefType.tp_alloc(&ClientClassDefType, 0); 113 | new (&obj->ptr) ClientClassDefPtr(ptr); 114 | return (PyObject *) obj; 115 | } 116 | 117 | int 118 | ClientClassDef_define() { 119 | // PyType_Ready - finish type initialisation 120 | if (PyType_Ready(&ClientClassDefType) < 0) { 121 | return (1); 122 | } 123 | Py_INCREF(&ClientClassDefType); 124 | // REFCOUNT: PyModule_AddObject steals reference on success 125 | if (PyModule_AddObject(kea_module, "ClientClassDef", (PyObject *) &ClientClassDefType) < 0) { 126 | Py_DECREF(&ClientClassDefType); 127 | return (1); 128 | } 129 | 130 | return (0); 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /keamodule/server.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::db; 5 | using namespace isc::data; 6 | 7 | extern "C" { 8 | 9 | static PyObject * 10 | Server_getServerTagAsText(ServerObject *self, PyObject *args) { 11 | try { 12 | string tag = self->ptr->getServerTagAsText(); 13 | // REFCOUNT: PyUnicode_FromString - returns new reference 14 | return (PyUnicode_FromString(tag.c_str())); 15 | } 16 | catch (const exception &e) { 17 | PyErr_SetString(PyExc_TypeError, e.what()); 18 | return (0); 19 | } 20 | } 21 | 22 | static PyObject * 23 | Server_getDescription(ServerObject *self, PyObject *args) { 24 | try { 25 | string description = self->ptr->getDescription(); 26 | // REFCOUNT: PyUnicode_FromString - returns new reference 27 | return (PyUnicode_FromString(description.c_str())); 28 | } 29 | catch (const exception &e) { 30 | PyErr_SetString(PyExc_TypeError, e.what()); 31 | return (0); 32 | } 33 | } 34 | 35 | static PyObject * 36 | Server_toElement(ServerObject *self, PyObject *args) { 37 | try { 38 | ElementPtr ptr = self->ptr->toElement(); 39 | // REFCOUNT: element_to_object - returns new reference 40 | return (element_to_object(ptr)); 41 | } 42 | catch (const exception &e) { 43 | PyErr_SetString(PyExc_TypeError, e.what()); 44 | return (0); 45 | } 46 | } 47 | 48 | static PyMethodDef Server_methods[] = { 49 | {"getServerTagAsText", (PyCFunction) Server_getServerTagAsText, METH_NOARGS, 50 | "Returns server tag as text."}, 51 | {"getDescription", (PyCFunction) Server_getDescription, METH_NOARGS, 52 | "Returns the description of the server."}, 53 | {"toElement", (PyCFunction) Server_toElement, METH_NOARGS, 54 | "Unparses server object."}, 55 | {0} // Sentinel 56 | }; 57 | 58 | static PyObject * 59 | Server_use_count(ServerObject *self, void *closure) { 60 | // REFCOUNT: PyLong_FromLong - returns new reference 61 | return (PyLong_FromLong(self->ptr.use_count())); 62 | } 63 | 64 | static PyGetSetDef Server_getsetters[] = { 65 | {(char *)"use_count", (getter) Server_use_count, (setter) 0, (char *)"shared_ptr use count", 0}, 66 | {0} // Sentinel 67 | }; 68 | 69 | // tp_dealloc - called when refcount is zero 70 | static void 71 | Server_dealloc(ServerObject *self) { 72 | self->ptr.~ServerPtr(); 73 | Py_TYPE(self)->tp_free((PyObject *) self); 74 | } 75 | 76 | // tp_init - called after tp_new has returned an instance 77 | static int 78 | Server_init(ServerObject *self, PyObject *args, PyObject *kwds) { 79 | const char *tag; 80 | const char *description; 81 | 82 | new(&self->ptr) ServerPtr; 83 | 84 | if (kwds != 0) { 85 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 86 | return (-1); 87 | } 88 | 89 | if (!PyArg_ParseTuple(args, "ss", &tag, &description)) { 90 | return (-1); 91 | } 92 | self->ptr.reset(new Server(ServerTag(tag), description)); 93 | 94 | return (0); 95 | } 96 | 97 | // tp_new - allocate space and initialisation that can be repeated 98 | static PyObject * 99 | Server_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 100 | ServerObject *self; 101 | self = (ServerObject *) type->tp_alloc(type, 0); 102 | if (self) { 103 | new(&self->ptr) ServerPtr; 104 | } 105 | return ((PyObject *) self); 106 | } 107 | 108 | PyTypeObject ServerType = { 109 | .ob_base = PyObject_HEAD_INIT(0) 110 | .tp_name = "kea.Server", 111 | .tp_basicsize = sizeof(ServerObject), 112 | .tp_dealloc = (destructor) Server_dealloc, 113 | .tp_flags = Py_TPFLAGS_DEFAULT, 114 | .tp_doc = PyDoc_STR("Kea server Server"), 115 | .tp_methods = Server_methods, 116 | .tp_getset = Server_getsetters, 117 | .tp_init = (initproc) Server_init, 118 | .tp_alloc = PyType_GenericAlloc, 119 | .tp_new = Server_new, 120 | }; 121 | 122 | PyObject * 123 | Server_from_ptr(ServerPtr &ptr) { 124 | // REFCOUNT: PyObject_New - returns new reference 125 | ServerObject *self = PyObject_New(ServerObject, &ServerType); 126 | if (self) { 127 | new(&self->ptr) ServerPtr; 128 | self->ptr = ptr; 129 | } 130 | return (PyObject *)self; 131 | } 132 | 133 | int 134 | Server_define() { 135 | // PyType_Ready - finish type initialisation 136 | if (PyType_Ready(&ServerType) < 0) { 137 | return (1); 138 | } 139 | Py_INCREF(&ServerType); 140 | // REFCOUNT: PyModule_AddObject steals reference on success 141 | if (PyModule_AddObject(kea_module, "Server", (PyObject *) &ServerType) < 0) { 142 | Py_DECREF(&ServerType); 143 | return (1); 144 | } 145 | 146 | return (0); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /examples/stress-test/stress_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import re 4 | import codecs 5 | import argparse 6 | from ipaddress import IPv4Network 7 | 8 | 9 | class Child: 10 | 11 | def __init__(self, mac, addr): 12 | self.mac = mac 13 | self.addr = addr 14 | self.pid = os.fork() 15 | if self.pid: 16 | return 17 | devnull = os.open('/dev/null', os.O_WRONLY) 18 | os.dup2(devnull, 1) 19 | os.dup2(devnull, 2) 20 | os.close(devnull) 21 | opt_val = '01%02x%s' % (len(self.addr), codecs.encode(bytes(self.addr, 'utf-8'), 'hex').decode('utf-8')) 22 | cmd = 'dhtest -i eth0 -m %s --custom-dhcp-option 82,hex,%s' % (self.mac, opt_val) 23 | os.execv('/usr/local/bin/dhtest', cmd.split()) 24 | 25 | def get_result(self): 26 | # dhclient writes lease details in file with name == self.mac 27 | # Client_mac: 02:42:ac:1c:05:02 28 | # Acquired_ip: 192.168.0.1 29 | # Server_id: 172.28.5.3 30 | # Host_mac: 02:42:AC:1C:05:03 31 | # IP_listen: False. Pid: 0 32 | try: 33 | s = open(self.mac).read() 34 | os.remove(self.mac) 35 | except OSError: 36 | return None 37 | m = re.search(r'Acquired_ip: (.*)', s, re.M) 38 | if m: 39 | return m.group(1) 40 | return None 41 | 42 | 43 | class RateReport: 44 | 45 | def __init__(self, freq, smooth): 46 | self.freq = freq 47 | self.smooth = smooth 48 | self.start = self.last_report = time.time() 49 | self.times = [self.last_report] 50 | self.total = 0 51 | 52 | def add_event(self): 53 | self.total += 1 54 | now = time.time() 55 | if now - self.last_report >= self.freq: 56 | # remove all times older than smooth 57 | pos = 0 58 | cutoff = now - self.smooth 59 | while pos < len(self.times) and self.times[pos] < cutoff: 60 | pos += 1 61 | self.times = self.times[pos:] 62 | if len(self.times) >= 2: 63 | durn = self.times[-1] - self.times[0] 64 | if durn: 65 | elapsed = now - self.start 66 | if elapsed >= 3600: 67 | elapsed = '%02d:%02d:%02d' % (elapsed // 3600, (int(elapsed) % 3600) // 60, int(elapsed) % 60) 68 | else: 69 | elapsed = '%02d:%02d' % (int(elapsed) // 60, int(elapsed) % 60) 70 | print('%s: %s at %.0f/sec' % (elapsed, self.total, len(self.times) / durn)) 71 | self.last_report = now 72 | self.times.append(now) 73 | 74 | 75 | class Runner: 76 | 77 | def __init__(self, parallel, total): 78 | self.parallel = parallel 79 | self.total = total 80 | self.children = {} 81 | self.network = IPv4Network('10.0.0.0/8') 82 | self.num_children = 0 83 | self.num_errors = 0 84 | self.rate = None 85 | 86 | def run(self): 87 | self.rate = RateReport(1, 3) 88 | while self.num_children < self.total: 89 | if len(self.children) < self.parallel: 90 | self.start_child() 91 | else: 92 | self.wait_child() 93 | while self.children: 94 | self.wait_child() 95 | 96 | def start_child(self): 97 | self.num_children += 1 98 | mac = '%012d' % self.num_children 99 | bits = [] 100 | for pos in range(0, len(mac), 2): 101 | bits.append(mac[pos:pos+2]) 102 | mac = ':'.join(bits) 103 | addr = str(self.network[self.num_children]) 104 | child = Child(mac, addr) 105 | self.children[child.pid] = child 106 | 107 | def wait_child(self): 108 | pid, status = os.waitpid(-1, 0) 109 | self.rate.add_event() 110 | child = self.children[pid] 111 | addr = child.get_result() 112 | if addr != child.addr: 113 | self.num_errors += 1 114 | del self.children[pid] 115 | 116 | 117 | def main(): 118 | parser = argparse.ArgumentParser() 119 | parser.add_argument('--total', type=int, default=10000, 120 | help='total number of requests to perform') 121 | parser.add_argument('--parallel', type=int, default=50, 122 | help='number of parallel requests to perform') 123 | args = parser.parse_args() 124 | 125 | os.chdir('/tmp') 126 | runner = Runner(args.parallel, args.total) 127 | start = time.time() 128 | runner.run() 129 | finish = time.time() 130 | rate = args.total / (finish - start) 131 | print('total %s at %.0f/sec with %s errors' % (args.total, rate, runner.num_errors)) 132 | 133 | 134 | if __name__ == '__main__': 135 | main() 136 | -------------------------------------------------------------------------------- /keamodule/library_handle.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "keamodule.h" 3 | 4 | using namespace std; 5 | using namespace isc::hooks; 6 | 7 | extern "C" { 8 | 9 | static PyObject * 10 | LibraryHandle_registerCommandCallout(LibraryHandleObject *self, PyObject *args) { 11 | PyObject *name; 12 | PyObject *callout; 13 | 14 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 15 | if (!PyArg_ParseTuple(args, "O!O", &PyUnicode_Type, &name, &callout)) { 16 | return (0); 17 | } 18 | if (!PyCallable_Check(callout)) { 19 | PyErr_SetString(PyExc_TypeError, "callout must be callable"); 20 | return (0); 21 | } 22 | if (self->is_owner) { 23 | PyErr_SetString(PyExc_RuntimeError, "only supported in embedded mode"); 24 | return (0); 25 | } 26 | 27 | // REFCOUNT: CalloutClosure_from_object - returns new reference 28 | CalloutClosureObject *obj = (CalloutClosureObject *) CalloutClosure_from_object(name, callout); 29 | if (!obj) { 30 | return (0); 31 | } 32 | // REFCOUNT: transfer ownership of obj to dict in callouts.cc 33 | if (Callouts_add_closure(obj)) { 34 | Py_DECREF(obj); 35 | return (0); 36 | } 37 | Py_DECREF(obj); 38 | try { 39 | // REFCOUNT: PyUnicode_AsUTF8 - returns UTF-8 encoding of str - buffer cached in str 40 | self->handle->registerCommandCallout(PyUnicode_AsUTF8(name), (CalloutPtr)obj->bound_callout); 41 | } 42 | catch (const exception &e) { 43 | PyErr_SetString(PyExc_TypeError, e.what()); 44 | return (0); 45 | } 46 | 47 | Py_RETURN_NONE; 48 | } 49 | 50 | static PyMethodDef LibraryHandle_methods[] = { 51 | {"registerCommandCallout", (PyCFunction) LibraryHandle_registerCommandCallout, METH_VARARGS, 52 | "Register control command handler."}, 53 | {0} // Sentinel 54 | }; 55 | 56 | // tp_dealloc - called when refcount is zero 57 | static void 58 | LibraryHandle_dealloc(LibraryHandleObject *self) { 59 | if (self->is_owner) { 60 | delete self->handle; 61 | } 62 | Py_TYPE(self)->tp_free((PyObject *) self); 63 | } 64 | 65 | // tp_init - called after tp_new has returned an instance 66 | static int 67 | LibraryHandle_init(LibraryHandleObject *self, PyObject *args, PyObject *kwds) { 68 | CalloutManagerObject *manager = 0; 69 | 70 | if (kwds != 0) { 71 | PyErr_SetString(PyExc_TypeError, "keyword arguments are not supported"); 72 | return (-1); 73 | } 74 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 75 | if (!PyArg_ParseTuple(args, "O!", &CalloutManagerType, &manager)) { 76 | return (-1); 77 | } 78 | 79 | try { 80 | #if HAVE_LIBRARYHANDLE_MANAGER_PTR 81 | self->handle = new LibraryHandle(manager->manager.get()); 82 | #else 83 | self->handle = new LibraryHandle(*manager->manager); 84 | #endif 85 | self->is_owner = true; 86 | } 87 | catch (const exception &e) { 88 | PyErr_SetString(PyExc_TypeError, e.what()); 89 | return (-1); 90 | } 91 | 92 | return (0); 93 | } 94 | 95 | // tp_new - allocate space and initialisation that can be repeated 96 | static PyObject * 97 | LibraryHandle_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 98 | LibraryHandleObject *self; 99 | self = (LibraryHandleObject *) type->tp_alloc(type, 0); 100 | if (self) { 101 | self->handle = 0; 102 | self->is_owner = false; 103 | } 104 | return ((PyObject *) self); 105 | } 106 | 107 | PyTypeObject LibraryHandleType = { 108 | .ob_base = PyObject_HEAD_INIT(0) 109 | .tp_name = "kea.LibraryHandle", 110 | .tp_basicsize = sizeof(LibraryHandleObject), 111 | .tp_dealloc = (destructor) LibraryHandle_dealloc, 112 | .tp_flags = Py_TPFLAGS_DEFAULT, 113 | .tp_doc = PyDoc_STR("Kea server LibraryHandle"), 114 | .tp_methods = LibraryHandle_methods, 115 | .tp_init = (initproc) LibraryHandle_init, 116 | .tp_alloc = PyType_GenericAlloc, 117 | .tp_new = LibraryHandle_new, 118 | }; 119 | 120 | PyObject * 121 | LibraryHandle_from_handle(LibraryHandle *handle) { 122 | // REFCOUNT: PyObject_New - returns new reference 123 | LibraryHandleObject *obj = PyObject_New(LibraryHandleObject, &LibraryHandleType); 124 | if (obj) { 125 | obj->handle = handle; 126 | obj->is_owner = false; 127 | } 128 | return (PyObject *)obj; 129 | } 130 | 131 | int 132 | LibraryHandle_define() { 133 | // PyType_Ready - finish type initialisation 134 | if (PyType_Ready(&LibraryHandleType) < 0) { 135 | return (1); 136 | } 137 | Py_INCREF(&LibraryHandleType); 138 | // REFCOUNT: PyModule_AddObject steals reference on success 139 | if (PyModule_AddObject(kea_module, "LibraryHandle", (PyObject *) &LibraryHandleType) < 0) { 140 | Py_DECREF(&LibraryHandleType); 141 | return (1); 142 | } 143 | 144 | return (0); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /keamodule/host.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | using namespace isc::asiolink; 6 | using namespace isc::data; 7 | 8 | extern "C" { 9 | 10 | static PyObject * 11 | Host_getHostId(HostObject *self, PyObject *args) { 12 | try { 13 | HostID host_id = (self->is_const ? self->const_ptr : self->ptr)->getHostId(); 14 | // REFCOUNT: PyLong_FromUnsignedLongLong - returns new reference 15 | return (PyLong_FromUnsignedLongLong(host_id)); 16 | } 17 | catch (const exception &e) { 18 | PyErr_SetString(PyExc_TypeError, e.what()); 19 | return (0); 20 | } 21 | } 22 | 23 | static PyObject * 24 | Host_toElement(HostObject *self, PyObject *args) { 25 | try { 26 | ElementPtr ptr = (self->is_const ? self->const_ptr : self->ptr)->toElement4(); 27 | // element_to_object - returns new reference 28 | return (element_to_object(ptr)); 29 | } 30 | catch (const exception &e) { 31 | PyErr_SetString(PyExc_TypeError, e.what()); 32 | return (0); 33 | } 34 | } 35 | 36 | static PyMethodDef Host_methods[] = { 37 | {"getHostId", (PyCFunction) Host_getHostId, METH_NOARGS, 38 | "Returns Host ID (primary key in MySQL, PostgreSQL and Cassandra backends)."}, 39 | {"toElement", (PyCFunction) Host_toElement, METH_NOARGS, 40 | "Element representation of the host."}, 41 | {0} // Sentinel 42 | }; 43 | 44 | static PyObject * 45 | Host_use_count(HostObject *self, void *closure) { 46 | // REFCOUNT: PyLong_FromLong - returns new reference 47 | return (PyLong_FromLong(self->is_const ? self->const_ptr.use_count() : self->ptr.use_count())); 48 | } 49 | 50 | static PyGetSetDef Host_getsetters[] = { 51 | {(char *)"use_count", (getter) Host_use_count, (setter) 0, (char *)"shared_ptr use count", 0}, 52 | {0} // Sentinel 53 | }; 54 | 55 | // tp_dealloc - called when refcount is zero 56 | static void 57 | Host_dealloc(HostObject *self) { 58 | self->ptr.~HostPtr(); 59 | self->const_ptr.~ConstHostPtr(); 60 | Py_TYPE(self)->tp_free((PyObject *) self); 61 | } 62 | 63 | // tp_init - called after tp_new has returned an instance 64 | static int 65 | Host_init(HostObject *self, PyObject *args, PyObject *kwds) { 66 | static const char *kwlist[] = {"identifier", "identifier_type", "subnet_id", "ipv4_reservation", NULL}; 67 | const char *identifier; 68 | const char *identifier_type; 69 | unsigned long subnet_id; 70 | const char *ipv4_reservation; 71 | 72 | new(&self->ptr) HostPtr; 73 | new(&self->const_ptr) ConstHostPtr; 74 | 75 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssks", (char **)kwlist, 76 | &identifier, &identifier_type, &subnet_id, &ipv4_reservation)) { 77 | return (-1); 78 | } 79 | 80 | try { 81 | self->ptr.reset(new Host(identifier, identifier_type, subnet_id, 0, IOAddress(string(ipv4_reservation)))); 82 | self->const_ptr.reset(); 83 | self->is_const = false; 84 | } 85 | catch (const exception &e) { 86 | PyErr_SetString(PyExc_TypeError, e.what()); 87 | return (-1); 88 | } 89 | 90 | return (0); 91 | } 92 | 93 | // tp_new - allocate space and initialisation that can be repeated 94 | static PyObject * 95 | Host_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 96 | HostObject *self; 97 | self = (HostObject *) type->tp_alloc(type, 0); 98 | if (self) { 99 | new(&self->ptr) HostPtr; 100 | new(&self->const_ptr) ConstHostPtr; 101 | } 102 | return ((PyObject *) self); 103 | } 104 | 105 | PyTypeObject HostType = { 106 | .ob_base = PyObject_HEAD_INIT(0) 107 | .tp_name = "kea.Host", 108 | .tp_basicsize = sizeof(HostObject), 109 | .tp_dealloc = (destructor) Host_dealloc, 110 | .tp_flags = Py_TPFLAGS_DEFAULT, 111 | .tp_doc = PyDoc_STR("Kea server Host"), 112 | .tp_methods = Host_methods, 113 | .tp_getset = Host_getsetters, 114 | .tp_init = (initproc) Host_init, 115 | .tp_alloc = PyType_GenericAlloc, 116 | .tp_new = Host_new, 117 | }; 118 | 119 | PyObject * 120 | Host_from_ptr(HostPtr host) { 121 | // REFCOUNT: PyObject_New - returns new reference 122 | HostObject *self = PyObject_New(HostObject, &HostType); 123 | if (self) { 124 | new(&self->ptr) HostPtr; 125 | new(&self->const_ptr) ConstHostPtr; 126 | self->is_const = false; 127 | self->ptr = host; 128 | } 129 | return (PyObject *)self; 130 | } 131 | 132 | PyObject * 133 | Host_from_constptr(ConstHostPtr host) { 134 | // REFCOUNT: PyObject_New - returns new reference 135 | HostObject *self = PyObject_New(HostObject, &HostType); 136 | if (self) { 137 | new(&self->ptr) HostPtr; 138 | new(&self->const_ptr) ConstHostPtr; 139 | self->is_const = true; 140 | self->const_ptr = host; 141 | } 142 | return (PyObject *)self; 143 | } 144 | 145 | int 146 | Host_define() { 147 | // PyType_Ready - finish type initialisation 148 | if (PyType_Ready(&HostType) < 0) { 149 | return (1); 150 | } 151 | Py_INCREF(&HostType); 152 | // REFCOUNT: PyModule_AddObject steals reference on success 153 | if (PyModule_AddObject(kea_module, "Host", (PyObject *) &HostType) < 0) { 154 | Py_DECREF(&HostType); 155 | return (1); 156 | } 157 | 158 | return (0); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /examples/cb-cmds/sendcmd.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import socket 4 | 5 | 6 | def send_command(name, args=None): 7 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 8 | s.connect('/usr/local/var/run/kea/kea4.sock') 9 | cmd = {'command': name} 10 | if args: 11 | cmd['arguments'] = args 12 | request = json.dumps(cmd, sort_keys=True) 13 | print(request) 14 | s.send(request.encode('utf-8')) 15 | bits = [] 16 | while True: 17 | buff = s.recv(4096) 18 | if not buff: 19 | break 20 | bits.append(buff) 21 | response = b''.join(bits).decode('utf-8') 22 | print(response) 23 | return json.loads(response) 24 | 25 | 26 | def server_tag_get(): 27 | send_command('server-tag-get') 28 | 29 | 30 | def remote_server4_get_all(): 31 | send_command('remote-server4-get-all', 32 | {'remote': {'type': 'mysql'}}) 33 | 34 | 35 | def remote_server4_set(): 36 | send_command('remote-server4-set', 37 | {'servers': [{'server-tag': 'kea', 38 | 'description': 'Default server.'}], 39 | 'remote': {'type': 'mysql'}}) 40 | 41 | 42 | def remote_subnet4_list(): 43 | send_command('remote-subnet4-list', 44 | {'remote': {'type': 'mysql'}, 45 | 'server-tags': ['kea'] 46 | }) 47 | 48 | 49 | def remote_subnet4_del_by_id(): 50 | send_command('remote-subnet4-del-by-id', 51 | {'subnets': [{ 52 | 'id': 5, 53 | }], 54 | 'remote': {'type': 'mysql'} 55 | }) 56 | 57 | 58 | def remote_subnet4_get_by_id(): 59 | send_command('remote-subnet4-get-by-id', 60 | {'subnets': [{ 61 | 'id': 5, 62 | }], 63 | 'remote': {'type': 'mysql'} 64 | }) 65 | 66 | 67 | def remote_subnet4_set(): 68 | send_command('remote-subnet4-set', 69 | {'subnets': [{ 70 | 'subnet': '172.28.5.0/24', 71 | 'id': 5, 72 | 'shared-network-name': None, 73 | 'pools': [{'pool': '172.28.5.100 - 172.28.5.200'}] 74 | }], 75 | 'remote': {'type': 'mysql'}, 76 | 'server-tags': ['kea'] 77 | }) 78 | 79 | 80 | def remote_class4_del(): 81 | send_command('remote-class4-del', 82 | {'client-classes': [{ 83 | 'name': 'foo' 84 | }], 85 | 'remote': {'type': 'mysql'} 86 | }) 87 | 88 | 89 | def remote_class4_get(): 90 | send_command('remote-class4-get', 91 | {'client-classes': [{ 92 | 'name': 'foo' 93 | }], 94 | 'remote': {'type': 'mysql'} 95 | }) 96 | 97 | 98 | def remote_class4_get_all(): 99 | send_command('remote-class4-get-all', 100 | {'remote': {'type': 'mysql'}, 101 | 'server-tags': ['kea'] 102 | }) 103 | 104 | 105 | def remote_class4_set(): 106 | send_command('remote-class4-set', 107 | {'client-classes': [{ 108 | 'name': 'foo', 109 | 'test': 'option[93].hex == 0x0009' 110 | }], 111 | 'remote': {'type': 'mysql'}, 112 | 'server-tags': ['kea'] 113 | }) 114 | 115 | 116 | def main(): 117 | parser = argparse.ArgumentParser() 118 | parser.add_argument('--server-tag-get', action='store_true') 119 | parser.add_argument('--server-set', action='store_true') 120 | parser.add_argument('--server-get-all', action='store_true') 121 | parser.add_argument('--subnet-set', action='store_true') 122 | parser.add_argument('--subnet-del-by-id', action='store_true') 123 | parser.add_argument('--subnet-get-by-id', action='store_true') 124 | parser.add_argument('--subnet-list', action='store_true') 125 | parser.add_argument('--class-del', action='store_true') 126 | parser.add_argument('--class-get', action='store_true') 127 | parser.add_argument('--class-get-all', action='store_true') 128 | parser.add_argument('--class-set', action='store_true') 129 | 130 | args = parser.parse_args() 131 | do_all = True 132 | if args.server_tag_get: 133 | server_tag_get() 134 | do_all = False 135 | if args.subnet_set: 136 | remote_subnet4_set() 137 | do_all = False 138 | if args.subnet_del_by_id: 139 | remote_subnet4_del_by_id() 140 | do_all = False 141 | if args.subnet_get_by_id: 142 | remote_subnet4_get_by_id() 143 | do_all = False 144 | if args.subnet_list: 145 | remote_subnet4_list() 146 | do_all = False 147 | if args.server_set: 148 | remote_server4_set() 149 | do_all = False 150 | if args.server_get_all: 151 | remote_server4_get_all() 152 | do_all = False 153 | if args.class_del: 154 | remote_class4_del() 155 | do_all = False 156 | if args.class_get: 157 | remote_class4_get() 158 | do_all = False 159 | if args.class_get_all: 160 | remote_class4_get_all() 161 | do_all = False 162 | if args.class_set: 163 | remote_class4_set() 164 | do_all = False 165 | if do_all: 166 | remote_server4_set() 167 | remote_server4_get_all() 168 | remote_subnet4_set() 169 | remote_subnet4_list() 170 | remote_subnet4_get_by_id() 171 | remote_subnet4_del_by_id() 172 | remote_subnet4_list() 173 | remote_class4_set() 174 | remote_class4_get_all() 175 | remote_class4_get() 176 | remote_class4_del() 177 | remote_class4_get_all() 178 | 179 | 180 | if __name__ == '__main__': 181 | main() 182 | -------------------------------------------------------------------------------- /keamodule/tests/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | 4 | import kea 5 | 6 | 7 | if sys.version_info < (3, 10): 8 | EXPECT_INT_GOT_STR = "an integer is required (got type str)" 9 | else: 10 | EXPECT_INT_GOT_STR = "'str' object cannot be interpreted as an integer" 11 | 12 | 13 | class Logger: 14 | 15 | def error(self, msg): 16 | sys.stderr.write(msg + '\n') 17 | 18 | 19 | class BaseTestCase(unittest.TestCase): 20 | 21 | def setUp(self): 22 | self.maxDiff = None 23 | # initialise logger in kea to prevent errors when calling kea code outside of kea 24 | kea.LoggerManager.init('python') 25 | kea.logger = Logger() 26 | 27 | def assert_cannot_construct(self, cls): 28 | with self.assertRaises(TypeError) as cm: 29 | cls() 30 | self.assertEqual((f"cannot create 'kea.{cls.__name__}' instances",), cm.exception.args) 31 | 32 | def assert_constructor_no_arguments(self, cls): 33 | with self.assertRaises(TypeError) as cm: 34 | cls(1) 35 | self.assertEqual(('function takes exactly 0 arguments (1 given)',), cm.exception.args) 36 | with self.assertRaises(TypeError) as cm: 37 | cls(x=1) 38 | self.assertEqual(('keyword arguments are not supported',), cm.exception.args) 39 | 40 | def assert_constructor_one_arg_no_keywords(self, cls): 41 | with self.assertRaises(TypeError) as cm: 42 | cls() 43 | self.assertEqual(('function takes exactly 1 argument (0 given)',), cm.exception.args) 44 | with self.assertRaises(TypeError) as cm: 45 | cls(1, 2) 46 | self.assertEqual(('function takes exactly 1 argument (2 given)',), cm.exception.args) 47 | with self.assertRaises(TypeError) as cm: 48 | cls(x=1) 49 | self.assertEqual(('keyword arguments are not supported',), cm.exception.args) 50 | 51 | def assert_constructor_two_args_no_keywords(self, cls): 52 | with self.assertRaises(TypeError) as cm: 53 | cls() 54 | self.assertEqual(('function takes exactly 2 arguments (0 given)',), cm.exception.args) 55 | with self.assertRaises(TypeError) as cm: 56 | cls(1) 57 | self.assertEqual(('function takes exactly 2 arguments (1 given)',), cm.exception.args) 58 | with self.assertRaises(TypeError) as cm: 59 | cls(1, 2, 3) 60 | self.assertEqual(('function takes exactly 2 arguments (3 given)',), cm.exception.args) 61 | with self.assertRaises(TypeError) as cm: 62 | cls(x=1) 63 | self.assertEqual(('keyword arguments are not supported',), cm.exception.args) 64 | 65 | def assert_method_no_arguments(self, method): 66 | with self.assertRaises(TypeError) as cm: 67 | method(1) 68 | msg = '%s() takes no arguments (1 given)' % method.__qualname__ 69 | self.assertEqual((msg,), cm.exception.args) 70 | with self.assertRaises(TypeError) as cm: 71 | method(x=1) 72 | msg = '%s() takes no keyword arguments' % method.__qualname__ 73 | self.assertEqual((msg,), cm.exception.args) 74 | 75 | def assert_method_one_arg_no_keywords(self, method): 76 | with self.assertRaises(TypeError) as cm: 77 | method() 78 | self.assertEqual(('function takes exactly 1 argument (0 given)',), cm.exception.args) 79 | with self.assertRaises(TypeError) as cm: 80 | method(1, 2) 81 | self.assertEqual(('function takes exactly 1 argument (2 given)',), cm.exception.args) 82 | with self.assertRaises(TypeError) as cm: 83 | method(x=1) 84 | msg = '%s() takes no keyword arguments' % method.__name__ 85 | self.assertEqual((msg,), cm.exception.args) 86 | 87 | def assert_method_two_args_no_keywords(self, method): 88 | with self.assertRaises(TypeError) as cm: 89 | method() 90 | self.assertEqual(('function takes exactly 2 arguments (0 given)',), cm.exception.args) 91 | with self.assertRaises(TypeError) as cm: 92 | method(1) 93 | self.assertEqual(('function takes exactly 2 arguments (1 given)',), cm.exception.args) 94 | with self.assertRaises(TypeError) as cm: 95 | method(1, 2, 3) 96 | self.assertEqual(('function takes exactly 2 arguments (3 given)',), cm.exception.args) 97 | with self.assertRaises(TypeError) as cm: 98 | method(x=1) 99 | msg = '%s() takes no keyword arguments' % method.__name__ 100 | self.assertEqual((msg,), cm.exception.args) 101 | 102 | def assert_method_three_args_no_keywords(self, method): 103 | with self.assertRaises(TypeError) as cm: 104 | method() 105 | self.assertEqual(('function takes exactly 3 arguments (0 given)',), cm.exception.args) 106 | with self.assertRaises(TypeError) as cm: 107 | method(1) 108 | self.assertEqual(('function takes exactly 3 arguments (1 given)',), cm.exception.args) 109 | with self.assertRaises(TypeError) as cm: 110 | method(1, 2) 111 | self.assertEqual(('function takes exactly 3 arguments (2 given)',), cm.exception.args) 112 | with self.assertRaises(TypeError) as cm: 113 | method(1, 2, 3, 4) 114 | self.assertEqual(('function takes exactly 3 arguments (4 given)',), cm.exception.args) 115 | with self.assertRaises(TypeError) as cm: 116 | method(x=1) 117 | msg = '%s() takes no keyword arguments' % method.__name__ 118 | self.assertEqual((msg,), cm.exception.args) 119 | 120 | def assert_method_four_args_no_keywords(self, method): 121 | with self.assertRaises(TypeError) as cm: 122 | method() 123 | self.assertEqual(('function takes exactly 4 arguments (0 given)',), cm.exception.args) 124 | with self.assertRaises(TypeError) as cm: 125 | method(1) 126 | self.assertEqual(('function takes exactly 4 arguments (1 given)',), cm.exception.args) 127 | with self.assertRaises(TypeError) as cm: 128 | method(1, 2) 129 | self.assertEqual(('function takes exactly 4 arguments (2 given)',), cm.exception.args) 130 | with self.assertRaises(TypeError) as cm: 131 | method(1, 2, 3) 132 | self.assertEqual(('function takes exactly 4 arguments (3 given)',), cm.exception.args) 133 | with self.assertRaises(TypeError) as cm: 134 | method(1, 2, 3, 4, 5) 135 | self.assertEqual(('function takes exactly 4 arguments (5 given)',), cm.exception.args) 136 | with self.assertRaises(TypeError) as cm: 137 | method(x=1) 138 | msg = '%s() takes no keyword arguments' % method.__name__ 139 | self.assertEqual((msg,), cm.exception.args) 140 | -------------------------------------------------------------------------------- /keamodule/tests/test_cfg_subnets4.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestCfgSubnets4_usecount(utils.BaseTestCase): 6 | 7 | def test_usecount(self): 8 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 9 | self.assertEqual(2, n.use_count) 10 | o = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 11 | self.assertEqual(3, o.use_count) 12 | o = None 13 | self.assertEqual(2, n.use_count) 14 | 15 | 16 | class TestCfgSubnets4_new(utils.BaseTestCase): 17 | 18 | def test_cannot_construct(self): 19 | self.assert_cannot_construct(kea.CfgSubnets4) 20 | 21 | 22 | class TestCfgSubnets4_add(utils.BaseTestCase): 23 | 24 | def test_badarg_count(self): 25 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 26 | self.assert_method_one_arg_no_keywords(n.add) 27 | 28 | def test_badarg_type(self): 29 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 30 | with self.assertRaises(TypeError) as cm: 31 | n.add('foo') 32 | self.assertEqual(("argument 1 must be kea.Subnet4, not str",), cm.exception.args) 33 | 34 | def test_ok(self): 35 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 36 | n.add(kea.Subnet4('192.168.0.0', 24, None, None, None, 1)) 37 | try: 38 | subnets = n.getAll() 39 | self.assertEqual(1, len(subnets)) 40 | self.assertEqual('192.168.0.0/24', subnets[0].toText()) 41 | finally: 42 | n.clear() 43 | 44 | 45 | class TestCfgSubnets4_clear(utils.BaseTestCase): 46 | 47 | def test_badarg_count(self): 48 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 49 | self.assert_method_no_arguments(n.clear) 50 | 51 | def test_ok(self): 52 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 53 | n.add(kea.Subnet4('192.168.0.0', 24, None, None, None, 1)) 54 | n.clear() 55 | self.assertEqual([], n.getAll()) 56 | 57 | 58 | class TestCfgSubnets4_delSubnetID(utils.BaseTestCase): 59 | 60 | def test_badarg_count(self): 61 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 62 | self.assert_method_one_arg_no_keywords(n.delSubnetID) 63 | 64 | def test_badarg_type(self): 65 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 66 | with self.assertRaises(TypeError) as cm: 67 | n.delSubnetID('foo') 68 | self.assertEqual((utils.EXPECT_INT_GOT_STR,), cm.exception.args) 69 | 70 | def test_ok(self): 71 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 72 | n.add(kea.Subnet4('192.168.0.0', 24, None, None, None, 1)) 73 | subnets = n.getAll() 74 | self.assertEqual(1, len(subnets)) 75 | self.assertIsNone(n.delSubnetID(1)) 76 | self.assertEqual([], n.getAll()) 77 | 78 | 79 | class TestCfgSubnets4_getAll(utils.BaseTestCase): 80 | 81 | def test_badarg_count(self): 82 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 83 | self.assert_method_no_arguments(n.getAll) 84 | 85 | def test_ok(self): 86 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 87 | self.assertEqual([], n.getAll()) 88 | 89 | 90 | class TestCfgSubnets4_getSubnet(utils.BaseTestCase): 91 | 92 | def test_badarg_count(self): 93 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 94 | self.assert_method_one_arg_no_keywords(n.getSubnet) 95 | 96 | def test_badarg_type(self): 97 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 98 | with self.assertRaises(TypeError) as cm: 99 | n.getSubnet('foo') 100 | self.assertEqual(("argument 1 must be int, not str",), cm.exception.args) 101 | 102 | def test_ok(self): 103 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 104 | self.assertIsNone(n.getSubnet(1)) 105 | n.add(kea.Subnet4('192.168.0.0', 24, None, None, None, 1)) 106 | try: 107 | self.assertEqual('192.168.0.0/24', n.getSubnet(1).toText()) 108 | finally: 109 | n.clear() 110 | 111 | 112 | class TestCfgSubnets4_replace(utils.BaseTestCase): 113 | 114 | def test_badarg_count(self): 115 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 116 | self.assert_method_one_arg_no_keywords(n.replace) 117 | 118 | def test_badarg_type(self): 119 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 120 | with self.assertRaises(TypeError) as cm: 121 | n.replace(1) 122 | self.assertEqual(("argument 1 must be kea.Subnet4, not int",), cm.exception.args) 123 | 124 | def test_ok(self): 125 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 126 | with self.assertRaises(TypeError) as cm: 127 | n.replace(kea.Subnet4('192.168.1.0', 24, None, None, None, 1)) 128 | self.assertEqual(('There is no IPv4 subnet with ID 1',), cm.exception.args) 129 | n.add(kea.Subnet4('192.168.0.0', 24, None, None, None, 1)) 130 | try: 131 | n.replace(kea.Subnet4('192.168.1.0', 24, None, None, None, 1)) 132 | self.assertEqual('192.168.1.0/24', n.getSubnet(1).toText()) 133 | finally: 134 | n.clear() 135 | 136 | 137 | class TestCfgSubnets4_selectSubnet(utils.BaseTestCase): 138 | 139 | def test_badarg_count(self): 140 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 141 | self.assert_method_one_arg_no_keywords(n.selectSubnet) 142 | 143 | def test_badarg_type(self): 144 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 145 | with self.assertRaises(TypeError) as cm: 146 | n.selectSubnet(1) 147 | self.assertEqual(("argument 1 must be str, not int",), cm.exception.args) 148 | 149 | def test_ok(self): 150 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 151 | self.assertIsNone(n.selectSubnet('192.168.0.1')) 152 | 153 | 154 | class TestCfgSubnets4_toElement(utils.BaseTestCase): 155 | 156 | def test_badarg_type(self): 157 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 158 | self.assert_method_no_arguments(n.toElement) 159 | 160 | def test_ok(self): 161 | n = kea.CfgMgr.instance().getCurrentCfg().getCfgSubnets4() 162 | self.assertEqual([], n.toElement()) 163 | n.add(kea.Subnet4('192.168.0.0', 24, None, None, None, 1)) 164 | try: 165 | self.assertEqual([{'4o6-interface': '', 166 | '4o6-interface-id': '', 167 | '4o6-subnet': '', 168 | 'id': 1,'option-data': [], 169 | 'pools': [], 170 | 'relay': {'ip-addresses': []}, 171 | 'subnet': '192.168.0.0/24'}], n.toElement()) 172 | finally: 173 | n.clear() 174 | -------------------------------------------------------------------------------- /keamodule/tests/test_subnet4.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestSubnet4_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | with self.assertRaises(TypeError) as cm: 9 | kea.Subnet4() 10 | self.assertEqual(("function missing required argument 'prefix' (pos 1)",), cm.exception.args) 11 | with self.assertRaises(TypeError) as cm: 12 | kea.Subnet4('foo') 13 | self.assertEqual(("function missing required argument 'length' (pos 2)",), cm.exception.args) 14 | with self.assertRaises(TypeError) as cm: 15 | kea.Subnet4('foo', 3) 16 | self.assertEqual(("function missing required argument 't1' (pos 3)",), cm.exception.args) 17 | with self.assertRaises(TypeError) as cm: 18 | kea.Subnet4('foo', 3, 1) 19 | self.assertEqual(("function missing required argument 't2' (pos 4)",), cm.exception.args) 20 | with self.assertRaises(TypeError) as cm: 21 | kea.Subnet4('foo', 3, 1, 2) 22 | self.assertEqual(("function missing required argument 'valid_lifetime' (pos 5)",), cm.exception.args) 23 | with self.assertRaises(TypeError) as cm: 24 | kea.Subnet4('foo', 3, 1, 2, 3) 25 | self.assertEqual(("function missing required argument 'id' (pos 6)",), cm.exception.args) 26 | 27 | def test_badarg_type(self): 28 | with self.assertRaises(TypeError) as cm: 29 | kea.Subnet4(1, 1, 1, 1, 1, 1) 30 | self.assertEqual(("argument 1 must be str, not int",), cm.exception.args) 31 | with self.assertRaises(TypeError) as cm: 32 | kea.Subnet4('foo', 'foo', 1, 1, 1, 1) 33 | self.assertEqual((utils.EXPECT_INT_GOT_STR,), cm.exception.args) 34 | with self.assertRaises(TypeError) as cm: 35 | kea.Subnet4('foo', 1, 'foo', 1, 1, 1) 36 | self.assertEqual(("'t1' must be int or None",), cm.exception.args) 37 | with self.assertRaises(TypeError) as cm: 38 | kea.Subnet4('foo', 1, None, 'foo', 1, 1) 39 | self.assertEqual(("'t2' must be int or None",), cm.exception.args) 40 | with self.assertRaises(TypeError) as cm: 41 | kea.Subnet4('foo', 1, None, None, 'foo', 1) 42 | self.assertEqual(("'valid_lifetime' must be int or None",), cm.exception.args) 43 | with self.assertRaises(TypeError) as cm: 44 | kea.Subnet4('foo', 1, None, None, None, 'foo') 45 | self.assertEqual((utils.EXPECT_INT_GOT_STR,), cm.exception.args) 46 | 47 | def test_ok(self): 48 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 49 | self.assertEqual({'id': 5, 50 | '4o6-interface': '', 51 | '4o6-interface-id': '', 52 | '4o6-subnet': '', 53 | 'max-valid-lifetime': 3600, 54 | 'min-valid-lifetime': 3600, 55 | 'option-data': [], 56 | 'pools': [], 57 | 'relay': {'ip-addresses': []}, 58 | 'renew-timer': 900, 59 | 'rebind-timer': 1800, 60 | 'valid-lifetime': 3600, 61 | 'subnet': '192.168.1.0/24'}, s.toElement()) 62 | 63 | def test_ok_unspecified(self): 64 | s = kea.Subnet4('192.168.1.0', 24, None, None, None, 5) 65 | self.assertEqual({'id': 5, 66 | '4o6-interface': '', 67 | '4o6-interface-id': '', 68 | '4o6-subnet': '', 69 | 'option-data': [], 70 | 'pools': [], 71 | 'relay': {'ip-addresses': []}, 72 | 'subnet': '192.168.1.0/24'}, s.toElement()) 73 | 74 | 75 | class TestSubnet4_delServerTag(utils.BaseTestCase): 76 | 77 | def test_badarg_count(self): 78 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 79 | self.assert_method_one_arg_no_keywords(s.delServerTag) 80 | 81 | def test_ok(self): 82 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 83 | s.setServerTag('level3') 84 | s.delServerTag('level3') 85 | self.assertEqual(set(), s.getServerTags()) 86 | 87 | 88 | class TestSubnet4_getServerTags(utils.BaseTestCase): 89 | 90 | def test_badarg_count(self): 91 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 92 | self.assert_method_no_arguments(s.getServerTags) 93 | 94 | def test_ok(self): 95 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 96 | self.assertEqual(set(), s.getServerTags()) 97 | s.setServerTag('level3') 98 | self.assertEqual(set(['level3']), s.getServerTags()) 99 | 100 | 101 | class TestSubnet4_getMetadata(utils.BaseTestCase): 102 | 103 | def test_badarg_count(self): 104 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 105 | self.assert_method_no_arguments(s.getMetadata) 106 | 107 | def test_ok(self): 108 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 109 | self.assertEqual({'server-tags': []}, s.getMetadata()) 110 | s.setServerTag('level3') 111 | self.assertEqual({'server-tags': ['level3']}, s.getMetadata()) 112 | 113 | 114 | class TestSubnet4_getSharedNetworkName(utils.BaseTestCase): 115 | 116 | def test_badarg_count(self): 117 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 118 | self.assert_method_no_arguments(s.getSharedNetworkName) 119 | 120 | def test_ok(self): 121 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 122 | self.assertEqual('', s.getSharedNetworkName()) 123 | s.setSharedNetworkName('level3') 124 | self.assertEqual('level3', s.getSharedNetworkName()) 125 | 126 | 127 | class TestSubnet4_setServerTag(utils.BaseTestCase): 128 | 129 | def test_badarg_count(self): 130 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 131 | self.assert_method_one_arg_no_keywords(s.setServerTag) 132 | 133 | def test_ok(self): 134 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 135 | s.setServerTag('one') 136 | self.assertEqual(set(['one']), s.getServerTags()) 137 | s.setServerTag('two') 138 | self.assertEqual(set(['one', 'two']), s.getServerTags()) 139 | 140 | 141 | class TestSubnet4_setSharedNetworkName(utils.BaseTestCase): 142 | 143 | def test_badarg_count(self): 144 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 145 | self.assert_method_one_arg_no_keywords(s.setSharedNetworkName) 146 | 147 | def test_badarg_type(self): 148 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 149 | with self.assertRaises(TypeError) as cm: 150 | s.setSharedNetworkName(1) 151 | self.assertEqual(("argument 1 must be str, not int",), cm.exception.args) 152 | 153 | def test_ok(self): 154 | s = kea.Subnet4('192.168.1.0', 24, 900, 1800, 3600, 5) 155 | s.setSharedNetworkName('level3') 156 | self.assertEqual('level3', s.getSharedNetworkName()) 157 | -------------------------------------------------------------------------------- /keamodule/constants.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace isc::data; 4 | using namespace isc::dhcp; 5 | using namespace isc::hooks; 6 | 7 | extern "C" { 8 | 9 | typedef struct { 10 | const char *name; 11 | int value; 12 | } KeaConstant; 13 | 14 | #define constant(name) {#name, name} 15 | 16 | static KeaConstant constants[] = { 17 | constant(BOOTREQUEST), 18 | constant(BOOTREPLY), 19 | 20 | {"NEXT_STEP_CONTINUE", CalloutHandle::NEXT_STEP_CONTINUE}, 21 | {"NEXT_STEP_SKIP", CalloutHandle::NEXT_STEP_SKIP}, 22 | {"NEXT_STEP_DROP", CalloutHandle::NEXT_STEP_DROP}, 23 | {"NEXT_STEP_PARK", CalloutHandle::NEXT_STEP_PARK}, 24 | 25 | {"STATE_DEFAULT", 0x0}, 26 | {"STATE_DECLINED", 0x1}, 27 | {"STATE_EXPIRED_RECLAIMED", 0x2}, 28 | 29 | constant(DHO_PAD), 30 | constant(DHO_SUBNET_MASK), 31 | constant(DHO_TIME_OFFSET), 32 | constant(DHO_ROUTERS), 33 | constant(DHO_TIME_SERVERS), 34 | constant(DHO_NAME_SERVERS), 35 | constant(DHO_DOMAIN_NAME_SERVERS), 36 | constant(DHO_LOG_SERVERS), 37 | constant(DHO_COOKIE_SERVERS), 38 | constant(DHO_LPR_SERVERS), 39 | constant(DHO_IMPRESS_SERVERS), 40 | constant(DHO_RESOURCE_LOCATION_SERVERS), 41 | constant(DHO_HOST_NAME), 42 | constant(DHO_BOOT_SIZE), 43 | constant(DHO_MERIT_DUMP), 44 | constant(DHO_DOMAIN_NAME), 45 | constant(DHO_SWAP_SERVER), 46 | constant(DHO_ROOT_PATH), 47 | constant(DHO_EXTENSIONS_PATH), 48 | constant(DHO_IP_FORWARDING), 49 | constant(DHO_NON_LOCAL_SOURCE_ROUTING), 50 | constant(DHO_POLICY_FILTER), 51 | constant(DHO_MAX_DGRAM_REASSEMBLY), 52 | constant(DHO_DEFAULT_IP_TTL), 53 | constant(DHO_PATH_MTU_AGING_TIMEOUT), 54 | constant(DHO_PATH_MTU_PLATEAU_TABLE), 55 | constant(DHO_INTERFACE_MTU), 56 | constant(DHO_ALL_SUBNETS_LOCAL), 57 | constant(DHO_BROADCAST_ADDRESS), 58 | constant(DHO_PERFORM_MASK_DISCOVERY), 59 | constant(DHO_MASK_SUPPLIER), 60 | constant(DHO_ROUTER_DISCOVERY), 61 | constant(DHO_ROUTER_SOLICITATION_ADDRESS), 62 | constant(DHO_STATIC_ROUTES), 63 | constant(DHO_TRAILER_ENCAPSULATION), 64 | constant(DHO_ARP_CACHE_TIMEOUT), 65 | constant(DHO_IEEE802_3_ENCAPSULATION), 66 | constant(DHO_DEFAULT_TCP_TTL), 67 | constant(DHO_TCP_KEEPALIVE_INTERVAL), 68 | constant(DHO_TCP_KEEPALIVE_GARBAGE), 69 | constant(DHO_NIS_DOMAIN), 70 | constant(DHO_NIS_SERVERS), 71 | constant(DHO_NTP_SERVERS), 72 | constant(DHO_VENDOR_ENCAPSULATED_OPTIONS), 73 | constant(DHO_NETBIOS_NAME_SERVERS), 74 | constant(DHO_NETBIOS_DD_SERVER), 75 | constant(DHO_NETBIOS_NODE_TYPE), 76 | constant(DHO_NETBIOS_SCOPE), 77 | constant(DHO_FONT_SERVERS), 78 | constant(DHO_X_DISPLAY_MANAGER), 79 | constant(DHO_DHCP_REQUESTED_ADDRESS), 80 | constant(DHO_DHCP_LEASE_TIME), 81 | constant(DHO_DHCP_OPTION_OVERLOAD), 82 | constant(DHO_DHCP_MESSAGE_TYPE), 83 | constant(DHO_DHCP_SERVER_IDENTIFIER), 84 | constant(DHO_DHCP_PARAMETER_REQUEST_LIST), 85 | constant(DHO_DHCP_MESSAGE), 86 | constant(DHO_DHCP_MAX_MESSAGE_SIZE), 87 | constant(DHO_DHCP_RENEWAL_TIME), 88 | constant(DHO_DHCP_REBINDING_TIME), 89 | constant(DHO_VENDOR_CLASS_IDENTIFIER), 90 | constant(DHO_DHCP_CLIENT_IDENTIFIER), 91 | constant(DHO_NWIP_DOMAIN_NAME), 92 | constant(DHO_NWIP_SUBOPTIONS), 93 | constant(DHO_NISP_DOMAIN_NAME), 94 | constant(DHO_NISP_SERVER_ADDR), 95 | constant(DHO_TFTP_SERVER_NAME), 96 | constant(DHO_BOOT_FILE_NAME), 97 | constant(DHO_HOME_AGENT_ADDRS), 98 | constant(DHO_SMTP_SERVER), 99 | constant(DHO_POP3_SERVER), 100 | constant(DHO_NNTP_SERVER), 101 | constant(DHO_WWW_SERVER), 102 | constant(DHO_FINGER_SERVER), 103 | constant(DHO_IRC_SERVER), 104 | constant(DHO_STREETTALK_SERVER), 105 | constant(DHO_STDASERVER), 106 | constant(DHO_USER_CLASS), 107 | constant(DHO_DIRECTORY_AGENT), 108 | constant(DHO_SERVICE_SCOPE), 109 | constant(DHO_FQDN), 110 | constant(DHO_DHCP_AGENT_OPTIONS), 111 | constant(DHO_NDS_SERVERS), 112 | constant(DHO_NDS_TREE_NAME), 113 | constant(DHO_NDS_CONTEXT), 114 | constant(DHO_BCMCS_DOMAIN_NAME_LIST), 115 | constant(DHO_BCMCS_IPV4_ADDR), 116 | constant(DHO_AUTHENTICATE), 117 | constant(DHO_CLIENT_LAST_TRANSACTION_TIME), 118 | constant(DHO_ASSOCIATED_IP), 119 | constant(DHO_SYSTEM), 120 | constant(DHO_NDI), 121 | constant(DHO_UUID_GUID), 122 | constant(DHO_USER_AUTH), 123 | constant(DHO_GEOCONF_CIVIC), 124 | constant(DHO_PCODE), 125 | constant(DHO_TCODE), 126 | constant(DHO_NETINFO_ADDR), 127 | constant(DHO_NETINFO_TAG), 128 | constant(DHO_V4_CAPTIVE_PORTAL), 129 | constant(DHO_AUTO_CONFIG), 130 | constant(DHO_NAME_SERVICE_SEARCH), 131 | constant(DHO_SUBNET_SELECTION), 132 | constant(DHO_DOMAIN_SEARCH), 133 | constant(DHO_VIVCO_SUBOPTIONS), 134 | constant(DHO_VIVSO_SUBOPTIONS), 135 | constant(DHO_PANA_AGENT), 136 | constant(DHO_V4_LOST), 137 | constant(DHO_CAPWAP_AC_V4), 138 | constant(DHO_SIP_UA_CONF_SERVICE_DOMAINS), 139 | constant(DHO_RDNSS_SELECT), 140 | constant(DHO_V4_PORTPARAMS), 141 | constant(DHO_V4_CAPTIVE_PORTAL), 142 | constant(DHO_6RD), 143 | constant(DHO_V4_ACCESS_DOMAIN), 144 | constant(DHO_END), 145 | 146 | constant(DHCP_NOTYPE), 147 | constant(DHCPDISCOVER), 148 | constant(DHCPOFFER), 149 | constant(DHCPREQUEST), 150 | constant(DHCPDECLINE), 151 | constant(DHCPACK), 152 | constant(DHCPNAK), 153 | constant(DHCPRELEASE), 154 | constant(DHCPINFORM), 155 | constant(DHCPLEASEQUERY), 156 | constant(DHCPLEASEUNASSIGNED), 157 | constant(DHCPLEASEUNKNOWN), 158 | constant(DHCPLEASEACTIVE), 159 | constant(DHCPBULKLEASEQUERY), 160 | constant(DHCPLEASEQUERYDONE), 161 | constant(DHCPLEASEQUERYSTATUS), 162 | constant(DHCPTLS), 163 | constant(DHCP_TYPES_EOF), 164 | 165 | constant(DHCP4_CLIENT_PORT), 166 | constant(DHCP4_SERVER_PORT), 167 | constant(DHCP_OPTIONS_COOKIE), 168 | constant(RAI_OPTION_AGENT_CIRCUIT_ID), 169 | constant(RAI_OPTION_REMOTE_ID), 170 | constant(RAI_OPTION_DOCSIS_DEVICE_CLASS), 171 | constant(RAI_OPTION_LINK_SELECTION), 172 | constant(RAI_OPTION_SUBSCRIBER_ID), 173 | constant(RAI_OPTION_RADIUS), 174 | constant(RAI_OPTION_AUTH), 175 | constant(RAI_OPTION_VSI), 176 | constant(RAI_OPTION_RELAY_FLAGS), 177 | constant(RAI_OPTION_SERVER_ID_OVERRIDE), 178 | constant(RAI_OPTION_RELAY_ID), 179 | constant(RAI_OPTION_ACCESS_TECHNO_TYPE), 180 | constant(RAI_OPTION_ACCESS_NETWORK_NAME), 181 | constant(RAI_OPTION_ACCESS_POINT_NAME), 182 | constant(RAI_OPTION_ACCESS_POINT_BSSID), 183 | constant(RAI_OPTION_OPERATOR_ID), 184 | constant(RAI_OPTION_OPERATOR_REALM), 185 | constant(RAI_OPTION_RELAY_PORT), 186 | constant(RAI_OPTION_VIRTUAL_SUBNET_SELECT), 187 | constant(RAI_OPTION_VIRTUAL_SUBNET_SELECT_CTRL), 188 | 189 | {"IDENT_HWADDR", Host::IdentifierType::IDENT_HWADDR}, 190 | {"IDENT_DUID", Host::IdentifierType::IDENT_DUID}, 191 | {"IDENT_CIRCUIT_ID", Host::IdentifierType::IDENT_CIRCUIT_ID}, 192 | {"IDENT_CLIENT_ID", Host::IdentifierType::IDENT_CLIENT_ID}, 193 | {"IDENT_FLEX", Host::IdentifierType::IDENT_FLEX}, 194 | }; 195 | #define num_constants (sizeof(constants) / sizeof(constants[0])) 196 | 197 | int 198 | Constants_define() { 199 | for (unsigned int i = 0; i < num_constants; i++) { 200 | if (PyModule_AddIntConstant(kea_module, constants[i].name, constants[i].value) < 0) { 201 | return (1); 202 | } 203 | } 204 | return (0); 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /examples/host-cmds/keahook.py: -------------------------------------------------------------------------------- 1 | import kea 2 | 3 | 4 | class UNSPECIFIED: 5 | pass 6 | 7 | 8 | class CommandError(Exception): 9 | 10 | def __init__(self, reason): 11 | self.reason = reason 12 | 13 | 14 | def get_arg(args, name, default=UNSPECIFIED, error_msg=None): 15 | if args is None or name not in args: 16 | if default is not UNSPECIFIED: 17 | return default 18 | if error_msg: 19 | raise CommandError(error_msg) 20 | raise CommandError("'%s' parameter not specified" % name) 21 | return args[name] 22 | 23 | 24 | def get_string_arg(args, name, default=UNSPECIFIED, error_msg=None): 25 | value = get_arg(args, name, default, error_msg) 26 | if value != default and not isinstance(value, str): 27 | if error_msg: 28 | raise CommandError(error_msg) 29 | raise CommandError("'%s' is not a string" % name) 30 | return value 31 | 32 | 33 | def get_int_arg(args, name, default=UNSPECIFIED, error_msg=None): 34 | value = get_arg(args, name, default) 35 | if value != default and not isinstance(value, int): 36 | if error_msg: 37 | raise CommandError(error_msg) 38 | raise CommandError("'%s' is not an integer" % name) 39 | return value 40 | 41 | 42 | def get_map_arg(args, name, default=UNSPECIFIED, error_msg=None): 43 | value = get_arg(args, name, default) 44 | if value != default and not isinstance(value, dict): 45 | if error_msg: 46 | raise CommandError(error_msg) 47 | raise CommandError("'%s' is not a map" % name) 48 | return value 49 | 50 | 51 | def wrap_handler(handle, get_response): 52 | try: 53 | cmd = handle.getArgument('command') 54 | args = cmd.get('arguments') 55 | if args is not None and not isinstance(args, dict): 56 | raise CommandError('parameters missing or is not a map') 57 | handle.setArgument('response', get_response(args)) 58 | except CommandError as e: 59 | handle.setArgument('response', {'result': 1, 60 | 'text': e.reason}) 61 | return 1 62 | except Exception as e: 63 | kea.logger.exception('') 64 | handle.setArgument('response', {'result': 1, 65 | 'text': str(e)}) 66 | return 1 67 | return 0 68 | 69 | 70 | # {"command": "reservation-add", 71 | # "arguments": {"reservation": {"subnet-id": 1, 72 | # reservation attribs}}} 73 | def reservation_add(handle): 74 | def get_response(args): 75 | resv = get_map_arg(args, 'reservation') 76 | subnet_id = get_int_arg(resv, 'subnet-id') 77 | del resv['subnet-id'] 78 | host = kea.HostReservationParser4().parse(subnet_id, resv) 79 | kea.HostMgr.instance().add(host) 80 | return {'result': 0, 81 | 'text': 'Host added.'} 82 | 83 | return wrap_handler(handle, get_response) 84 | 85 | 86 | # {"command": "reservation-get", 87 | # "arguments": {"subnet-id": 1, 88 | # "ip-address": "192.0.2.202"}} 89 | # {"command": "reservation-get", 90 | # "arguments": {"subnet-id": 4, 91 | # "identifier-type": "hw-address", 92 | # "identifier": "01:02:03:04:05:06"}} 93 | def reservation_get(handle): 94 | def get_response(args): 95 | host_mgr = kea.HostMgr.instance() 96 | subnet_id = get_int_arg(args, 'subnet-id') 97 | if 'ip-address' in args: 98 | ip_address = get_string_arg(args, 'ip-address') 99 | host = host_mgr.get(subnet_id, ip_address) 100 | else: 101 | identifier_type = get_string_arg(args, 'identifier-type') 102 | identifier = get_string_arg(args, 'identifier') 103 | host = host_mgr.get(subnet_id, identifier_type, identifier) 104 | if host is None: 105 | return {'result': 0, 'text': 'Host not found.'} 106 | else: 107 | return {'result': 0, 108 | 'text': 'Host found.', 109 | 'arguments': host.toElement()} 110 | 111 | return wrap_handler(handle, get_response) 112 | 113 | 114 | # {"command": "reservation-get-all", 115 | # "arguments": {"subnet-id": 1}} 116 | def reservation_get_all(handle): 117 | def get_response(args): 118 | subnet_id = get_int_arg(args, 'subnet-id') 119 | hosts = kea.HostMgr.instance().getAll4(subnet_id) 120 | return {'result': 0, 121 | 'text': '%s IPv4 host(s) found.' % len(hosts), 122 | 'arguments': {'hosts': [h.toElement() for h in hosts]}} 123 | 124 | return wrap_handler(handle, get_response) 125 | 126 | 127 | # {"command": "reservation-get-page", 128 | # "arguments": {"subnet-id": 1, 129 | # "limit": 10}} 130 | # { "command": "reservation-get-page", 131 | # "arguments": {"subnet-id": 1, 132 | # "source-index": 1, 133 | # "from": 1234567, 134 | # "limit": 10}} 135 | def reservation_get_page(handle): 136 | def get_response(args): 137 | host_mgr = kea.HostMgr.instance() 138 | subnet_id = get_int_arg(args, 'subnet-id') 139 | source_index = get_int_arg(args, 'source-index', 0) 140 | lower_host_id = get_int_arg(args, 'from', 0) 141 | page_size = get_int_arg(args, 'limit') 142 | hosts, source_index = host_mgr.getPage4(subnet_id, source_index, lower_host_id, page_size) 143 | if hosts: 144 | return {'result': 0, 145 | 'text': '%s IPv4 host(s) found.' % len(hosts), 146 | 'arguments': {'count': len(hosts), 147 | 'hosts': [h.toElement() for h in hosts], 148 | 'next': {'from': hosts[-1].getHostId(), 149 | 'source-index': source_index}}} 150 | return {'result': 3, 151 | 'text': '0 IPv4 host(s) found.', 152 | 'arguments': {'count': 0, 153 | 'hosts': []}} 154 | 155 | return wrap_handler(handle, get_response) 156 | 157 | 158 | # {"command": "reservation-del", 159 | # "arguments": {"subnet-id": 1, 160 | # "ip-address": "192.0.2.202"}} 161 | # {"command": "reservation-del", 162 | # "arguments": {"subnet-id": 4, 163 | # "identifier-type": "hw-address", 164 | # "identifier": "01:02:03:04:05:06"}} 165 | def reservation_del(handle): 166 | def get_response(args): 167 | host_mgr = kea.HostMgr.instance() 168 | subnet_id = get_int_arg(args, 'subnet-id') 169 | if 'ip-address' in args: 170 | ip_address = get_string_arg(args, 'ip-address') 171 | was_deleted = host_mgr.del_(subnet_id, ip_address) 172 | else: 173 | identifier_type = get_string_arg(args, 'identifier-type') 174 | identifier = get_string_arg(args, 'identifier') 175 | was_deleted = host_mgr.del4(subnet_id, identifier_type, identifier) 176 | if was_deleted: 177 | return {'result': 0, 'text': 'Host deleted.'} 178 | return {'result': 1, 'text': 'Host not deleted (not found).'} 179 | 180 | return wrap_handler(handle, get_response) 181 | 182 | 183 | def load(handle): 184 | handle.registerCommandCallout('reservation-add', reservation_add) 185 | handle.registerCommandCallout('reservation-get', reservation_get) 186 | handle.registerCommandCallout('reservation-get-all', reservation_get_all) 187 | handle.registerCommandCallout('reservation-get-page', reservation_get_page) 188 | handle.registerCommandCallout('reservation-del', reservation_del) 189 | return 0 190 | -------------------------------------------------------------------------------- /keamodule/utils.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::data; 5 | 6 | extern "C" { 7 | 8 | int 9 | assert_long_value(PyObject *value, const char *name) { 10 | if (value == NULL) { 11 | PyErr_Format(PyExc_TypeError, "cannot delete the %s attribute", name); 12 | return (-1); 13 | } 14 | if (!PyLong_Check(value)) { 15 | PyErr_Format(PyExc_TypeError, "the %s attribute value must be an int", name); 16 | return (-1); 17 | } 18 | return (0); 19 | } 20 | 21 | int 22 | assert_bool_value(PyObject *value, const char *name) { 23 | if (value == NULL) { 24 | PyErr_Format(PyExc_TypeError, "cannot delete the %s attribute", name); 25 | return (-1); 26 | } 27 | if (!PyBool_Check(value)) { 28 | PyErr_Format(PyExc_TypeError, "the %s attribute value must be a bool", name); 29 | return (-1); 30 | } 31 | return (0); 32 | } 33 | 34 | int 35 | assert_string_value(PyObject *value, const char *name, bool allow_none) { 36 | if (value == NULL) { 37 | PyErr_Format(PyExc_TypeError, "cannot delete the %s attribute", name); 38 | return (-1); 39 | } 40 | if (allow_none && value == Py_None) { 41 | return (0); 42 | } 43 | if (!PyUnicode_Check(value)) { 44 | PyErr_Format(PyExc_TypeError, "the %s attribute value must be a str", name); 45 | return (-1); 46 | } 47 | return (0); 48 | } 49 | 50 | PyObject * 51 | element_to_object(ConstElementPtr ptr) { 52 | if (!ptr) { 53 | Py_RETURN_NONE; 54 | } 55 | switch (ptr->getType()) { 56 | case Element::integer: 57 | try { 58 | // REFCOUNT: PyLong_FromLong - returns new reference 59 | return (PyLong_FromLong(ptr->intValue())); 60 | } 61 | catch (const exception &e) { 62 | PyErr_SetString(PyExc_TypeError, e.what()); 63 | return (0); 64 | } 65 | 66 | case Element::real: 67 | try { 68 | // REFCOUNT: PyFloat_FromDouble - returns new reference 69 | return (PyFloat_FromDouble(ptr->doubleValue())); 70 | } 71 | catch (const exception &e) { 72 | PyErr_SetString(PyExc_TypeError, e.what()); 73 | return (0); 74 | } 75 | 76 | case Element::boolean: 77 | try { 78 | // REFCOUNT: PyBool_FromLong - returns new reference 79 | return (PyBool_FromLong(ptr->boolValue())); 80 | } 81 | catch (const exception &e) { 82 | PyErr_SetString(PyExc_TypeError, e.what()); 83 | return (0); 84 | } 85 | 86 | case Element::null: 87 | Py_RETURN_NONE; 88 | 89 | case Element::string: 90 | try { 91 | // REFCOUNT: PyUnicode_FromString - returns new reference 92 | return (PyUnicode_FromString(ptr->stringValue().c_str())); 93 | } 94 | catch (const exception &e) { 95 | PyErr_SetString(PyExc_TypeError, e.what()); 96 | return (0); 97 | } 98 | 99 | case Element::list: { 100 | auto list_ptr = ptr->listValue(); 101 | // REFCOUNT: PyList_New - returns new reference 102 | PyObject *list = PyList_New(list_ptr.size()); 103 | if (!list) { 104 | return (0); 105 | } 106 | try { 107 | for (std::vector::const_iterator it = list_ptr.begin(); it != list_ptr.cend(); ++it) { 108 | auto pos = it - list_ptr.begin(); 109 | // REFCOUNT: element_to_object - returns new reference 110 | PyObject *elem = element_to_object(*it); 111 | // REFCOUNT: PyList_SetItem - steals reference 112 | if (!elem || PyList_SetItem(list, pos, elem) < 0) { 113 | Py_DECREF(list); 114 | return (0); 115 | } 116 | } 117 | } 118 | catch (const exception &e) { 119 | PyErr_SetString(PyExc_TypeError, e.what()); 120 | Py_DECREF(list); 121 | return (0); 122 | } 123 | return (list); 124 | } 125 | 126 | case Element::map: { 127 | auto map_ptr = ptr->mapValue(); 128 | // REFCOUNT: PyDict_New - returns new reference 129 | PyObject *dict = PyDict_New(); 130 | if (!dict) { 131 | return (0); 132 | } 133 | try { 134 | for (std::map::const_iterator it = map_ptr.begin(); it != map_ptr.end(); ++it) { 135 | // REFCOUNT: element_to_object - returns new reference 136 | PyObject *elem = element_to_object(it->second); 137 | // REFCOUNT: PyDict_SetItemString - reference neutral 138 | if (!elem || PyDict_SetItemString(dict, it->first.c_str(), elem) < 0) { 139 | Py_DECREF(dict); 140 | Py_XDECREF(elem); 141 | return (0); 142 | } 143 | Py_DECREF(elem); 144 | } 145 | } 146 | catch (const exception &e) { 147 | PyErr_SetString(PyExc_TypeError, e.what()); 148 | Py_DECREF(dict); 149 | return (0); 150 | } 151 | return (dict); 152 | } 153 | 154 | default: 155 | Py_RETURN_NONE; 156 | } 157 | } 158 | 159 | ElementPtr 160 | object_to_element(PyObject *obj) { 161 | if (obj == Py_None) { 162 | return (Element::create()); 163 | } 164 | if (PyBool_Check(obj)) { 165 | return (Element::create((bool) (obj == Py_True))); 166 | } 167 | if (PyLong_Check(obj)) { 168 | return (Element::create(PyLong_AsLong(obj))); 169 | } 170 | if (PyFloat_Check(obj)) { 171 | return (Element::create(PyFloat_AsDouble(obj))); 172 | } 173 | if (PyUnicode_Check(obj)) { 174 | // REFCOUNT: PyUnicode_AsUTF8 - returns UTF-8 encoding of str - buffer cached in str 175 | return (Element::create(string(PyUnicode_AsUTF8(obj)))); 176 | } 177 | if (PyList_Check(obj)) { 178 | ElementPtr ptr = Element::createList(); 179 | for (Py_ssize_t i = 0; i < PyList_Size(obj); i++) { 180 | // REFCOUNT: PyList_GetItem - returns borrowed reference 181 | ElementPtr item = object_to_element(PyList_GetItem(obj, i)); 182 | if (!item) { 183 | return (0); 184 | } 185 | ptr->add(item); 186 | } 187 | return (ptr); 188 | } 189 | if (PyDict_Check(obj)) { 190 | ElementPtr ptr = Element::createMap(); 191 | PyObject *key; 192 | PyObject *value; 193 | Py_ssize_t pos = 0; 194 | 195 | // REFCOUNT: PyDict_Next - returns borrowed references 196 | while (PyDict_Next(obj, &pos, &key, &value)) { 197 | if (!PyUnicode_Check(key)) { 198 | PyErr_SetString(PyExc_TypeError, "keys must be string"); 199 | return (0); 200 | } 201 | ElementPtr item = object_to_element(value); 202 | if (!item) { 203 | return (0); 204 | } 205 | // REFCOUNT: PyUnicode_AsUTF8 - returns UTF-8 encoding of str - buffer cached in str 206 | ptr->set(PyUnicode_AsUTF8(key), item); 207 | } 208 | return (ptr); 209 | } 210 | 211 | PyErr_Format(PyExc_TypeError, "unhandled type %s", Py_TYPE(obj)->tp_name); 212 | return (0); 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /keamodule/tests/test_option.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import textwrap 3 | 4 | import kea 5 | import utils 6 | 7 | 8 | class TestOption_new(utils.BaseTestCase): 9 | 10 | def test_badarg_count(self): 11 | self.assert_constructor_one_arg_no_keywords(kea.Option) 12 | 13 | def test_badarg_type(self): 14 | with self.assertRaises(TypeError) as cm: 15 | kea.Option('foo') 16 | self.assertEqual((utils.EXPECT_INT_GOT_STR,), cm.exception.args) 17 | 18 | def test_ok(self): 19 | o = kea.Option(42) 20 | self.assertEqual(1, o.use_count) 21 | 22 | 23 | class TestOption_getType(utils.BaseTestCase): 24 | 25 | def test_badarg_count(self): 26 | o = kea.Option(42) 27 | self.assert_method_no_arguments(o.getType) 28 | 29 | def test_ok(self): 30 | o = kea.Option(42) 31 | self.assertIs(42, o.getType()) 32 | 33 | 34 | class TestOption_getBytes(utils.BaseTestCase): 35 | 36 | def test_badarg_count(self): 37 | o = kea.Option(42) 38 | self.assert_method_no_arguments(o.getBytes) 39 | 40 | def test_ok(self): 41 | o = kea.Option(42) 42 | o.setBytes(b'123') 43 | self.assertEqual(b'123', o.getBytes()) 44 | 45 | 46 | class TestOption_setBytes(utils.BaseTestCase): 47 | 48 | def test_badarg_count(self): 49 | o = kea.Option(42) 50 | self.assert_method_one_arg_no_keywords(o.setBytes) 51 | 52 | def test_badarg_type(self): 53 | o = kea.Option(42) 54 | with self.assertRaises(TypeError) as cm: 55 | o.setBytes('foo') 56 | self.assertEqual(("argument 1 must be bytes, not str",), cm.exception.args) 57 | 58 | def test_ok(self): 59 | o = kea.Option(42) 60 | self.assertIs(o, o.setBytes(b'123')) 61 | 62 | 63 | class TestOption_getString(utils.BaseTestCase): 64 | 65 | def test_badarg_count(self): 66 | o = kea.Option(42) 67 | self.assert_method_no_arguments(o.getString) 68 | 69 | def test_ok(self): 70 | o = kea.Option(42) 71 | o.setString('Pokémon') 72 | self.assertEqual('Pokémon', o.getString()) 73 | 74 | 75 | class TestOption_setString(utils.BaseTestCase): 76 | 77 | def test_badarg_count(self): 78 | o = kea.Option(42) 79 | self.assert_method_one_arg_no_keywords(o.setString) 80 | 81 | def test_badarg_type(self): 82 | o = kea.Option(42) 83 | with self.assertRaises(TypeError) as cm: 84 | o.setString(1) 85 | self.assertEqual(("a bytes-like object is required, not 'int'",), cm.exception.args) 86 | 87 | def test_ok(self): 88 | o = kea.Option(42) 89 | self.assertIs(o, o.setString('Pokémon')) 90 | 91 | 92 | class TestOption_getUint8(utils.BaseTestCase): 93 | 94 | def test_badarg_count(self): 95 | o = kea.Option(42) 96 | self.assert_method_no_arguments(o.getUint8) 97 | 98 | def test_ok(self): 99 | o = kea.Option(42) 100 | o.setUint8(0xfe) 101 | self.assertEqual(0xfe, o.getUint8()) 102 | self.assertEqual(b'\xfe', o.getBytes()) 103 | 104 | 105 | class TestOption_setUint8(utils.BaseTestCase): 106 | 107 | def test_badarg_count(self): 108 | o = kea.Option(42) 109 | self.assert_method_one_arg_no_keywords(o.setUint8) 110 | 111 | def test_badarg_type(self): 112 | o = kea.Option(42) 113 | with self.assertRaises(TypeError) as cm: 114 | o.setUint8('foo') 115 | self.assertEqual((utils.EXPECT_INT_GOT_STR,), cm.exception.args) 116 | 117 | def test_ok(self): 118 | o = kea.Option(42) 119 | self.assertIs(o, o.setUint8(0xfe)) 120 | 121 | 122 | class TestOption_getUint16(utils.BaseTestCase): 123 | 124 | def test_badarg_count(self): 125 | o = kea.Option(42) 126 | self.assert_method_no_arguments(o.getUint16) 127 | 128 | def test_ok(self): 129 | o = kea.Option(42) 130 | o.setUint16(0xfeed) 131 | self.assertEqual(0xfeed, o.getUint16()) 132 | self.assertEqual(b'\xfe\xed', o.getBytes()) 133 | 134 | 135 | class TestOption_setUint16(utils.BaseTestCase): 136 | 137 | def test_badarg_count(self): 138 | o = kea.Option(42) 139 | self.assert_method_one_arg_no_keywords(o.setUint16) 140 | 141 | def test_badarg_type(self): 142 | o = kea.Option(42) 143 | with self.assertRaises(TypeError) as cm: 144 | o.setUint16('foo') 145 | self.assertEqual((utils.EXPECT_INT_GOT_STR,), cm.exception.args) 146 | 147 | def test_ok(self): 148 | o = kea.Option(42) 149 | self.assertIs(o, o.setUint16(0xfeed)) 150 | 151 | 152 | class TestOption_getUint32(utils.BaseTestCase): 153 | 154 | def test_badarg_count(self): 155 | o = kea.Option(42) 156 | self.assert_method_no_arguments(o.getUint32) 157 | 158 | def test_ok(self): 159 | o = kea.Option(42) 160 | o.setUint32(0x01020304) 161 | self.assertEqual(0x01020304, o.getUint32()) 162 | self.assertEqual(b'\x01\x02\x03\x04', o.getBytes()) 163 | 164 | 165 | class TestOption_setUint32(utils.BaseTestCase): 166 | 167 | def test_badarg_count(self): 168 | o = kea.Option(42) 169 | self.assert_method_one_arg_no_keywords(o.setUint32) 170 | 171 | def test_badarg_type(self): 172 | o = kea.Option(42) 173 | with self.assertRaises(TypeError) as cm: 174 | o.setUint32('foo') 175 | self.assertEqual(("argument 1 must be int, not str",), cm.exception.args) 176 | 177 | def test_ok(self): 178 | o = kea.Option(42) 179 | self.assertIs(o, o.setUint32(0x01020304)) 180 | 181 | 182 | class TestOption_addOption(utils.BaseTestCase): 183 | 184 | def test_badarg_count(self): 185 | o = kea.Option(42) 186 | self.assert_method_one_arg_no_keywords(o.addOption) 187 | 188 | def test_badarg_type(self): 189 | o = kea.Option(42) 190 | with self.assertRaises(TypeError) as cm: 191 | o.addOption('foo') 192 | self.assertEqual(("argument 1 must be kea.Option, not str",), cm.exception.args) 193 | 194 | def test_ok(self): 195 | o = kea.Option(42) 196 | p = kea.Option(2).setUint8(0xef) 197 | self.assertIs(o, o.addOption(p)) 198 | 199 | 200 | class TestOption_getOption(utils.BaseTestCase): 201 | 202 | def test_badarg_count(self): 203 | o = kea.Option(42) 204 | self.assert_method_one_arg_no_keywords(o.getOption) 205 | 206 | def test_badarg_type(self): 207 | o = kea.Option(42) 208 | with self.assertRaises(TypeError) as cm: 209 | o.getOption('foo') 210 | self.assertEqual((utils.EXPECT_INT_GOT_STR,), cm.exception.args) 211 | 212 | def test_ok(self): 213 | o = kea.Option(42).addOption(kea.Option(2).setUint8(0xef)) 214 | p = o.getOption(2) 215 | self.assertIsInstance(p, kea.Option) 216 | self.assertEqual(2, p.getType()) 217 | self.assertEqual(0xef, p.getUint8()) 218 | 219 | 220 | class TestOption_pack(utils.BaseTestCase): 221 | 222 | def test_badarg_count(self): 223 | o = kea.Option(42) 224 | self.assert_method_no_arguments(o.pack) 225 | 226 | def test_ok(self): 227 | o = kea.Option(42).addOption(kea.Option(2).setUint8(0xef)) 228 | wire = o.pack() 229 | self.assertIsInstance(wire, bytes) 230 | self.assertEqual(b'2a030201ef', codecs.encode(wire, 'hex')) 231 | 232 | 233 | class TestOption_toText(utils.BaseTestCase): 234 | 235 | def test_badarg_count(self): 236 | o = kea.Option(42) 237 | self.assert_method_no_arguments(o.toText) 238 | 239 | def test_empty(self): 240 | o = kea.Option(42) 241 | if kea.__version__ < '3.0.0': 242 | self.assertEqual('type=042, len=000: ', o.toText()) 243 | else: 244 | self.assertEqual("type=042, len=000: ''", o.toText()) 245 | 246 | def test_uint8(self): 247 | o = kea.Option(42).setUint8(5) 248 | self.assertEqual('type=042, len=001: 05', o.toText()) 249 | 250 | def test_nested(self): 251 | o = kea.Option(42).addOption(kea.Option(4) 252 | .setUint16(5)).addOption(kea.Option(6) 253 | .setString('hello')) 254 | if kea.__version__ < '3.0.0': 255 | self.assertEqual(textwrap.dedent("""\ 256 | type=042, len=011: , 257 | options: 258 | type=004, len=002: 00:05 259 | type=006, len=005: 68:65:6c:6c:6f"""), o.toText()) 260 | else: 261 | self.assertEqual(textwrap.dedent("""\ 262 | type=042, len=011: '', 263 | options: 264 | type=004, len=002: 00:05 265 | type=006, len=005: 68:65:6c:6c:6f 'hello'"""), o.toText()) 266 | -------------------------------------------------------------------------------- /keamodule/host_mgr.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::dhcp; 5 | using namespace isc::asiolink; 6 | using namespace isc::util; 7 | 8 | extern "C" { 9 | 10 | // need BaseHostDataSource 11 | 12 | static PyObject * 13 | HostMgr_add(HostMgrObject *self, PyObject *args) { 14 | HostObject *host; 15 | 16 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 17 | if (!PyArg_ParseTuple(args, "O!", &HostType, &host)) { 18 | return (0); 19 | } 20 | 21 | try { 22 | self->mgr->add(host->ptr); 23 | Py_RETURN_NONE; 24 | } 25 | catch (const exception &e) { 26 | PyErr_SetString(PyExc_TypeError, e.what()); 27 | return (0); 28 | } 29 | } 30 | 31 | static PyObject * 32 | HostMgr_get(HostMgrObject *self, PyObject *args) { 33 | unsigned long subnet_id; 34 | const char *ip_address = 0; 35 | const char *identifier_type = 0; 36 | const char *identifier = 0; 37 | 38 | if (PyTuple_GET_SIZE(args) == 2) { 39 | if (!PyArg_ParseTuple(args, "ks", &subnet_id, &ip_address)) { 40 | return (0); 41 | } 42 | } else { 43 | if (!PyArg_ParseTuple(args, "kss", &subnet_id, &identifier_type, &identifier)) { 44 | return (0); 45 | } 46 | } 47 | 48 | try { 49 | ConstHostPtr host; 50 | if (ip_address != 0) { 51 | host = self->mgr->get4(subnet_id, IOAddress(ip_address)); 52 | } else { 53 | std::vector binary = str::quotedStringToBinary(identifier); 54 | if (binary.empty()) { 55 | str::decodeFormattedHexString(identifier, binary); 56 | } 57 | host = self->mgr->get4(subnet_id, Host::getIdentifierType(identifier_type), &binary.front(), binary.size()); 58 | } 59 | if (!host.get()) { 60 | Py_RETURN_NONE; 61 | } 62 | // REFCOUNT: Host_from_constptr - returns new reference 63 | return Host_from_constptr(host); 64 | } 65 | catch (const exception &e) { 66 | PyErr_SetString(PyExc_TypeError, e.what()); 67 | return (0); 68 | } 69 | } 70 | 71 | static PyObject * 72 | collection_to_list(ConstHostCollection& hosts) { 73 | // REFCOUNT: PyList_New - returns new reference 74 | PyObject *result = PyList_New(hosts.size()); 75 | if (result == 0) { 76 | return 0; 77 | } 78 | int pos = 0; 79 | for (auto host : hosts) { 80 | // REFCOUNT: Host_from_constptr - returns new reference 81 | PyObject *pyhost = Host_from_constptr(host); 82 | if (pyhost == 0) { 83 | Py_DECREF(result); 84 | return (0); 85 | } 86 | // REFCOUNT: PyList_SetItem - steals reference 87 | if (PyList_SetItem(result, pos++, pyhost) < 0) { 88 | Py_DECREF(result); 89 | return (0); 90 | } 91 | } 92 | return (result); 93 | } 94 | 95 | static PyObject * 96 | HostMgr_getAll4(HostMgrObject *self, PyObject *args) { 97 | unsigned long subnet_id; 98 | 99 | if (!PyArg_ParseTuple(args, "k", &subnet_id)) { 100 | return (0); 101 | } 102 | 103 | try { 104 | ConstHostCollection hosts = self->mgr->getAll4(subnet_id); 105 | // REFCOUNT: collection_to_list - returns new reference 106 | return (collection_to_list(hosts)); 107 | } 108 | catch (const exception &e) { 109 | PyErr_SetString(PyExc_TypeError, e.what()); 110 | return (0); 111 | } 112 | } 113 | 114 | static PyObject * 115 | HostMgr_getPage4(HostMgrObject *self, PyObject *args) { 116 | unsigned long subnet_id; 117 | unsigned long source_index = 0; 118 | uint64_t lower_host_id = 0; 119 | unsigned long page_size = 0; 120 | 121 | if (!PyArg_ParseTuple(args, "kkKk", &subnet_id, &source_index, &lower_host_id, &page_size)) { 122 | return (0); 123 | } 124 | 125 | try { 126 | HostPageSize host_page_size(page_size); 127 | ConstHostCollection hosts = self->mgr->getPage4(subnet_id, source_index, lower_host_id, host_page_size); 128 | // REFCOUNT: collection_to_list - returns new reference 129 | PyObject *host_list = collection_to_list(hosts); 130 | if (host_list == 0) { 131 | return (0); 132 | } 133 | // REFCOUNT: Py_BuildValue - returns new reference - reference neutral 134 | PyObject *result = Py_BuildValue("Ok", host_list, source_index); 135 | Py_DECREF(host_list); 136 | return (result); 137 | } 138 | catch (const exception &e) { 139 | PyErr_SetString(PyExc_TypeError, e.what()); 140 | return (0); 141 | } 142 | } 143 | 144 | static PyObject * 145 | HostMgr_del_(HostMgrObject *self, PyObject *args) { 146 | unsigned long subnet_id; 147 | const char *ip_address; 148 | 149 | if (!PyArg_ParseTuple(args, "ks", &subnet_id, &ip_address)) { 150 | return (0); 151 | } 152 | 153 | try { 154 | if (self->mgr->del(subnet_id, IOAddress(ip_address))) { 155 | Py_RETURN_TRUE; 156 | } else { 157 | Py_RETURN_FALSE; 158 | } 159 | } 160 | catch (const exception &e) { 161 | PyErr_SetString(PyExc_TypeError, e.what()); 162 | return (0); 163 | } 164 | } 165 | 166 | static PyObject * 167 | HostMgr_del4(HostMgrObject *self, PyObject *args) { 168 | unsigned long subnet_id; 169 | const char *identifier_type; 170 | const char *identifier; 171 | 172 | if (!PyArg_ParseTuple(args, "kss", &subnet_id, &identifier_type, &identifier)) { 173 | return (0); 174 | } 175 | 176 | try { 177 | std::vector binary = str::quotedStringToBinary(identifier); 178 | if (binary.empty()) { 179 | str::decodeFormattedHexString(identifier, binary); 180 | } 181 | if (self->mgr->del4(subnet_id, Host::getIdentifierType(identifier_type), &binary.front(), binary.size())) { 182 | Py_RETURN_TRUE; 183 | } else { 184 | Py_RETURN_FALSE; 185 | } 186 | } 187 | catch (const exception &e) { 188 | PyErr_SetString(PyExc_TypeError, e.what()); 189 | return (0); 190 | } 191 | } 192 | 193 | PyObject * 194 | HostMgr_from_ptr(HostMgr *mgr) { 195 | // REFCOUNT: PyObject_New - returns new reference 196 | HostMgrObject *self = PyObject_New(HostMgrObject, &HostMgrType); 197 | if (self) { 198 | self->mgr = mgr; 199 | } 200 | return (PyObject *)self; 201 | } 202 | 203 | static PyObject * 204 | HostMgr_instance(HostMgrObject *self, PyObject *args) { 205 | try { 206 | HostMgr &mgr = HostMgr::instance(); 207 | // REFCOUNT: HostMgr_from_ptr - returns new reference 208 | return (HostMgr_from_ptr(&mgr)); 209 | } 210 | catch (const exception &e) { 211 | PyErr_SetString(PyExc_TypeError, e.what()); 212 | return (0); 213 | } 214 | } 215 | 216 | 217 | static PyMethodDef HostMgr_methods[] = { 218 | {"instance", (PyCFunction) HostMgr_instance, METH_NOARGS|METH_STATIC, 219 | "Returns a sole instance of the HostMgr."}, 220 | {"add", (PyCFunction) HostMgr_add, METH_VARARGS, 221 | "Adds a new host to the alternate data source."}, 222 | {"get", (PyCFunction) HostMgr_get, METH_VARARGS, 223 | "Returns a host connected to the IPv4 subnet."}, 224 | {"getAll4", (PyCFunction) HostMgr_getAll4, METH_VARARGS, 225 | "Return all hosts in a DHCPv4 subnet."}, 226 | {"getPage4", (PyCFunction) HostMgr_getPage4, METH_VARARGS, 227 | "Returns range of hosts in a DHCPv4 subnet."}, 228 | {"del_", (PyCFunction) HostMgr_del_, METH_VARARGS, 229 | "Attempts to delete a host by address."}, 230 | {"del4", (PyCFunction) HostMgr_del4, METH_VARARGS, 231 | "Attempts to delete a host by (subnet4-id, identifier, identifier-type)."}, 232 | {0} // Sentinel 233 | }; 234 | 235 | // tp_init - called after tp_new has returned an instance 236 | static int 237 | HostMgr_init(HostMgrObject *self, PyObject *args, PyObject *kwds) { 238 | PyErr_SetString(PyExc_TypeError, "cannot create 'kea.HostMgr' instances"); 239 | return (-1); 240 | } 241 | 242 | PyTypeObject HostMgrType = { 243 | .ob_base = PyObject_HEAD_INIT(0) 244 | .tp_name = "kea.HostMgr", 245 | .tp_basicsize = sizeof(HostMgrObject), 246 | .tp_flags = Py_TPFLAGS_DEFAULT, 247 | .tp_doc = PyDoc_STR("Kea server HostMgr"), 248 | .tp_methods = HostMgr_methods, 249 | .tp_init = (initproc) HostMgr_init, 250 | .tp_alloc = PyType_GenericAlloc, 251 | .tp_new = PyType_GenericNew, 252 | }; 253 | 254 | int 255 | HostMgr_define() { 256 | // PyType_Ready - finish type initialisation 257 | if (PyType_Ready(&HostMgrType) < 0) { 258 | return (1); 259 | } 260 | Py_INCREF(&HostMgrType); 261 | // REFCOUNT: PyModule_AddObject steals reference on success 262 | if (PyModule_AddObject(kea_module, "HostMgr", (PyObject *) &HostMgrType) < 0) { 263 | Py_DECREF(&HostMgrType); 264 | return (1); 265 | } 266 | 267 | return (0); 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /keamodule/tests/test_lease4.py: -------------------------------------------------------------------------------- 1 | import kea 2 | import utils 3 | 4 | 5 | class TestLease4_new(utils.BaseTestCase): 6 | 7 | def test_badarg_count(self): 8 | self.assert_constructor_no_arguments(kea.Lease4) 9 | 10 | def test_ok(self): 11 | x = kea.Lease4() 12 | self.assertEqual(1, x.use_count) 13 | 14 | 15 | class TestLease4_addr(utils.BaseTestCase): 16 | 17 | def test_getset(self): 18 | x = kea.Lease4() 19 | self.assertEqual('0.0.0.0', x.addr) 20 | x.addr = '192.168.0.1' 21 | self.assertEqual('192.168.0.1', x.addr) 22 | 23 | def test_bad_type(self): 24 | x = kea.Lease4() 25 | with self.assertRaises(TypeError) as cm: 26 | x.addr = 'bogus' 27 | self.assertEqual(("Failed to convert string to address 'bogus': Invalid argument",), 28 | cm.exception.args) 29 | with self.assertRaises(TypeError) as cm: 30 | x.addr = 0 31 | self.assertEqual(('the addr attribute value must be a str',), cm.exception.args) 32 | 33 | 34 | class TestLease4_valid_lft(utils.BaseTestCase): 35 | 36 | def test_getset(self): 37 | x = kea.Lease4() 38 | self.assertEqual(0, x.valid_lft) 39 | x.valid_lft = 3600 40 | self.assertEqual(3600, x.valid_lft) 41 | 42 | def test_bad_type(self): 43 | x = kea.Lease4() 44 | with self.assertRaises(TypeError) as cm: 45 | x.valid_lft = 'bogus' 46 | self.assertEqual(('the valid_lft attribute value must be an int',), cm.exception.args) 47 | 48 | 49 | class TestLease4_cltt(utils.BaseTestCase): 50 | 51 | def test_getset(self): 52 | x = kea.Lease4() 53 | self.assertEqual(0, x.cltt) 54 | x.cltt = 3600 55 | self.assertEqual(3600, x.cltt) 56 | 57 | def test_bad_type(self): 58 | x = kea.Lease4() 59 | with self.assertRaises(TypeError) as cm: 60 | x.cltt = 'bogus' 61 | self.assertEqual(('the cltt attribute value must be an int',), cm.exception.args) 62 | 63 | 64 | class TestLease4_subnet_id(utils.BaseTestCase): 65 | 66 | def test_getset(self): 67 | x = kea.Lease4() 68 | self.assertEqual(0, x.subnet_id) 69 | x.subnet_id = 5 70 | self.assertEqual(5, x.subnet_id) 71 | 72 | def test_bad_type(self): 73 | x = kea.Lease4() 74 | with self.assertRaises(TypeError) as cm: 75 | x.subnet_id = 'bogus' 76 | self.assertEqual(('the subnet_id attribute value must be an int',), cm.exception.args) 77 | 78 | 79 | class TestLease4_hostname(utils.BaseTestCase): 80 | 81 | def test_getset(self): 82 | x = kea.Lease4() 83 | self.assertIsNone(x.hostname) 84 | x.hostname = 'example.org' 85 | self.assertEqual('example.org', x.hostname) 86 | x.hostname = None 87 | self.assertIsNone(x.hostname) 88 | 89 | def test_bad_type(self): 90 | x = kea.Lease4() 91 | with self.assertRaises(TypeError) as cm: 92 | x.hostname = 3 93 | self.assertEqual(('the hostname attribute value must be a str',), cm.exception.args) 94 | 95 | 96 | class TestLease4_fqdn_fwd(utils.BaseTestCase): 97 | 98 | def test_getset(self): 99 | x = kea.Lease4() 100 | self.assertEqual(False, x.fqdn_fwd) 101 | x.fqdn_fwd = True 102 | self.assertEqual(True, x.fqdn_fwd) 103 | x.fqdn_fwd = False 104 | self.assertEqual(False, x.fqdn_fwd) 105 | 106 | def test_bad_type(self): 107 | x = kea.Lease4() 108 | with self.assertRaises(TypeError) as cm: 109 | x.fqdn_fwd = 'bogus' 110 | self.assertEqual(('the fqdn_fwd attribute value must be a bool',), cm.exception.args) 111 | 112 | 113 | class TestLease4_fqdn_rev(utils.BaseTestCase): 114 | 115 | def test_getset(self): 116 | x = kea.Lease4() 117 | self.assertEqual(False, x.fqdn_rev) 118 | x.fqdn_rev = True 119 | self.assertEqual(True, x.fqdn_rev) 120 | x.fqdn_rev = False 121 | self.assertEqual(False, x.fqdn_rev) 122 | 123 | def test_bad_type(self): 124 | x = kea.Lease4() 125 | with self.assertRaises(TypeError) as cm: 126 | x.fqdn_rev = 'bogus' 127 | self.assertEqual(('the fqdn_rev attribute value must be a bool',), cm.exception.args) 128 | 129 | 130 | class TestLease4_hwaddr(utils.BaseTestCase): 131 | 132 | def test_getset(self): 133 | x = kea.Lease4() 134 | self.assertIsNone(x.hwaddr) 135 | x.hwaddr = '01:02:03:04:05:06' 136 | self.assertEqual('01:02:03:04:05:06', x.hwaddr) 137 | 138 | def test_bad_type(self): 139 | x = kea.Lease4() 140 | with self.assertRaises(TypeError) as cm: 141 | x.hwaddr = 'bogus' 142 | self.assertEqual(("invalid format of the decoded string 'bogus'",), cm.exception.args) 143 | 144 | 145 | class TestLease4_client_id(utils.BaseTestCase): 146 | 147 | def test_getset(self): 148 | x = kea.Lease4() 149 | self.assertIsNone(x.client_id) 150 | x.client_id = '01:02:03:04:05:06' 151 | self.assertEqual('01:02:03:04:05:06', x.client_id) 152 | 153 | def test_bad_type(self): 154 | x = kea.Lease4() 155 | with self.assertRaises(TypeError) as cm: 156 | x.client_id = 'bogus' 157 | self.assertEqual(("'bogus' is not a valid string of hexadecimal digits",), 158 | cm.exception.args) 159 | 160 | 161 | class TestLease4_state(utils.BaseTestCase): 162 | 163 | def test_getset(self): 164 | x = kea.Lease4() 165 | self.assertEqual(0, x.state) 166 | x.state = 5 167 | self.assertEqual(5, x.state) 168 | 169 | def test_bad_type(self): 170 | x = kea.Lease4() 171 | with self.assertRaises(TypeError) as cm: 172 | x.state = 'bogus' 173 | self.assertEqual(('the state attribute value must be an int',), cm.exception.args) 174 | 175 | 176 | class TestLease4_setContext(utils.BaseTestCase): 177 | 178 | def test_badarg_count(self): 179 | x = kea.Lease4() 180 | self.assert_method_one_arg_no_keywords(x.setContext) 181 | 182 | def test_ok(self): 183 | x = kea.Lease4() 184 | self.assertIs(x, x.setContext('foo')) 185 | self.assertIs(x, x.setContext(2)) 186 | self.assertIs(x, x.setContext(True)) 187 | self.assertIs(x, x.setContext([1, 'foo'])) 188 | self.assertIs(x, x.setContext({'foo': 'bar'})) 189 | self.assertIs(x, x.setContext(None)) 190 | 191 | 192 | class TestLease4_getContext(utils.BaseTestCase): 193 | 194 | def test_badarg_count(self): 195 | x = kea.Lease4() 196 | self.assert_method_no_arguments(x.getContext) 197 | 198 | def test_ok(self): 199 | x = kea.Lease4() 200 | self.assertIsNone(x.getContext()) 201 | x.setContext(None) 202 | self.assertIsNone(x.getContext()) 203 | x.setContext('foo') 204 | self.assertEqual('foo', x.getContext()) 205 | x.setContext(2) 206 | self.assertEqual(2, x.getContext()) 207 | x.setContext(True) 208 | self.assertEqual(True, x.getContext()) 209 | x.setContext([1, 'foo']) 210 | self.assertEqual([1, 'foo'], x.getContext()) 211 | x.setContext({'foo': 'bar'}) 212 | self.assertEqual({'foo': 'bar'}, x.getContext()) 213 | 214 | 215 | class TestLease4_toElement(utils.BaseTestCase): 216 | 217 | def test_badarg_count(self): 218 | x = kea.Lease4() 219 | self.assert_method_no_arguments(x.toElement) 220 | 221 | def test_empty(self): 222 | x = kea.Lease4() 223 | with self.assertRaises(RuntimeError) as cm: 224 | self.assertEqual({}, x.toElement()) 225 | self.assertIsInstance(cm.exception, RuntimeError) 226 | self.assertEqual(("hwaddr must not be empty",), cm.exception.args) 227 | 228 | def test_ok(self): 229 | x = kea.Lease4() 230 | x.hwaddr = '01:02:03:04:05:06' 231 | self.assertEqual({'cltt': 0, 232 | 'fqdn-fwd': False, 233 | 'fqdn-rev': False, 234 | 'hostname': '', 235 | 'hw-address': '01:02:03:04:05:06', 236 | 'ip-address': '0.0.0.0', 237 | 'state': 0, 238 | 'subnet-id': 0, 239 | 'valid-lft': 0}, x.toElement()) 240 | x.cltt = 3600 241 | x.fqdn_fwd = x.fqdn_rev = True 242 | x.hostname = 'example.com' 243 | x.addr = '192.168.0.1' 244 | x.state = 3 245 | x.subnet_id = 4 246 | x.valid_lft = 1800 247 | x.client_id = '02:03:04:05:06:07' 248 | self.assertEqual({'client-id': '02:03:04:05:06:07', 249 | 'cltt': 3600, 250 | 'fqdn-fwd': True, 251 | 'fqdn-rev': True, 252 | 'hostname': 'example.com', 253 | 'hw-address': '01:02:03:04:05:06', 254 | 'ip-address': '192.168.0.1', 255 | 'state': 3, 256 | 'subnet-id': 4, 257 | 'valid-lft': 1800}, x.toElement()) 258 | -------------------------------------------------------------------------------- /keamodule/cfg_subnets4.cc: -------------------------------------------------------------------------------- 1 | #include "keamodule.h" 2 | 3 | using namespace std; 4 | using namespace isc::hooks; 5 | using namespace isc::dhcp; 6 | using namespace isc::data; 7 | using namespace isc::asiolink; 8 | 9 | extern "C" { 10 | 11 | static PyObject * 12 | CfgSubnets4_add(CfgSubnets4Object *self, PyObject *args) { 13 | Subnet4Object *subnet; 14 | 15 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 16 | if (!PyArg_ParseTuple(args, "O!", &Subnet4Type, &subnet)) { 17 | return (0); 18 | } 19 | try { 20 | self->ptr->add(subnet->ptr); 21 | Py_RETURN_NONE; 22 | } 23 | catch (const exception &e) { 24 | PyErr_SetString(PyExc_TypeError, e.what()); 25 | return (0); 26 | } 27 | } 28 | 29 | static PyObject * 30 | CfgSubnets4_clear(CfgSubnets4Object *self, PyObject *args) { 31 | try { 32 | self->ptr->clear(); 33 | Py_RETURN_NONE; 34 | } 35 | catch (const exception &e) { 36 | PyErr_SetString(PyExc_TypeError, e.what()); 37 | return (0); 38 | } 39 | } 40 | 41 | static PyObject * 42 | CfgSubnets4_delSubnetID(CfgSubnets4Object *self, PyObject *args) { 43 | uint32_t subnet_id; 44 | 45 | if (!PyArg_ParseTuple(args, "I", &subnet_id)) { 46 | return (0); 47 | } 48 | try { 49 | self->ptr->del(subnet_id); 50 | Py_RETURN_NONE; 51 | } 52 | catch (const exception &e) { 53 | PyErr_SetString(PyExc_TypeError, e.what()); 54 | return (0); 55 | } 56 | } 57 | 58 | static PyObject * 59 | CfgSubnets4_replace(CfgSubnets4Object *self, PyObject *args) { 60 | Subnet4Object *subnet; 61 | 62 | // REFCOUNT: PyArg_ParseTuple - returns borrowed references 63 | if (!PyArg_ParseTuple(args, "O!", &Subnet4Type, &subnet)) { 64 | return (0); 65 | } 66 | try { 67 | Subnet4Ptr ptr = self->ptr->replace(subnet->ptr); 68 | if (!ptr) { 69 | Py_RETURN_NONE; 70 | } 71 | // REFCOUNT: Subnet4_from_ptr - returns new reference 72 | return (Subnet4_from_ptr(ptr)); 73 | } 74 | catch (const exception &e) { 75 | PyErr_SetString(PyExc_TypeError, e.what()); 76 | return (0); 77 | } 78 | } 79 | 80 | static PyObject * 81 | CfgSubnets4_getAll(CfgSubnets4Object *self, PyObject *args) { 82 | try { 83 | const Subnet4Collection *all = self->ptr->getAll(); 84 | if (!all) { 85 | Py_RETURN_NONE; 86 | } 87 | // REFCOUNT: PyList_New - returns new reference 88 | PyObject *list = PyList_New(0); 89 | if (!list) { 90 | return (0); 91 | } 92 | for (Subnet4Ptr ptr : *all) { 93 | // REFCOUNT: Subnet4_from_ptr - returns new reference 94 | PyObject *subnet = Subnet4_from_ptr(ptr); 95 | // REFCOUNT: PyList_Append - reference neutral 96 | if (!subnet || PyList_Append(list, subnet) < 0) { 97 | Py_DECREF(list); 98 | return (0); 99 | } 100 | Py_DECREF(subnet); 101 | } 102 | return (list); 103 | } 104 | catch (const exception &e) { 105 | PyErr_SetString(PyExc_TypeError, e.what()); 106 | return (0); 107 | } 108 | } 109 | 110 | static PyObject * 111 | CfgSubnets4_getSubnet(CfgSubnets4Object *self, PyObject *args) { 112 | unsigned long subnet_id; 113 | 114 | if (!PyArg_ParseTuple(args, "k", &subnet_id)) { 115 | return (0); 116 | } 117 | 118 | try { 119 | Subnet4Ptr ptr = self->ptr->getSubnet(subnet_id); 120 | if (!ptr) { 121 | Py_RETURN_NONE; 122 | } 123 | // REFCOUNT: Subnet4_from_ptr - returns new reference 124 | return (Subnet4_from_ptr(ptr)); 125 | } 126 | catch (const exception &e) { 127 | PyErr_SetString(PyExc_TypeError, e.what()); 128 | return (0); 129 | } 130 | } 131 | 132 | static PyObject * 133 | CfgSubnets4_selectSubnet(CfgSubnets4Object *self, PyObject *args) { 134 | char *addr; 135 | 136 | if (!PyArg_ParseTuple(args, "s", &addr)) { 137 | return (0); 138 | } 139 | 140 | try { 141 | #ifdef HAVE_NONCONST_CFGSUBNETS4_SELECTSUBNET 142 | Subnet4Ptr ptr = self->ptr->selectSubnet(IOAddress(string(addr))); 143 | if (!ptr) { 144 | Py_RETURN_NONE; 145 | } 146 | // REFCOUNT: Subnet4_from_constptr - returns new reference 147 | return (Subnet4_from_ptr(ptr)); 148 | #else 149 | ConstSubnet4Ptr ptr = self->ptr->selectSubnet(IOAddress(string(addr))); 150 | if (!ptr) { 151 | Py_RETURN_NONE; 152 | } 153 | // REFCOUNT: Subnet4_from_constptr - returns new reference 154 | return (Subnet4_from_constptr(ptr)); 155 | #endif 156 | } 157 | catch (const exception &e) { 158 | PyErr_SetString(PyExc_TypeError, e.what()); 159 | return (0); 160 | } 161 | } 162 | 163 | static PyObject * 164 | CfgSubnets4_toElement(CfgSubnets4Object *self, PyObject *args) { 165 | try { 166 | ElementPtr ptr = self->ptr->toElement(); 167 | // element_to_object - returns new reference 168 | return (element_to_object(ptr)); 169 | } 170 | catch (const exception &e) { 171 | PyErr_SetString(PyExc_TypeError, e.what()); 172 | return (0); 173 | } 174 | } 175 | 176 | static PyMethodDef CfgSubnets4_methods[] = { 177 | {"add", (PyCFunction) CfgSubnets4_add, METH_VARARGS, 178 | "Adds new subnet to the configuration."}, 179 | {"clear", (PyCFunction) CfgSubnets4_clear, METH_NOARGS, 180 | "Clears all subnets from the configuration."}, 181 | {"delSubnetID", (PyCFunction) CfgSubnets4_delSubnetID, METH_VARARGS, 182 | "Removes subnet from the configuration."}, 183 | {"replace", (PyCFunction) CfgSubnets4_replace, METH_VARARGS, 184 | "Replaces subnet in the configuration. This method replaces a subnet by another subnet with the same ID." 185 | " The prefix should be the same too."}, 186 | {"getAll", (PyCFunction) CfgSubnets4_getAll, METH_NOARGS, 187 | "Returns collection of all IPv4 subnets."}, 188 | {"getSubnet", (PyCFunction) CfgSubnets4_getSubnet, METH_VARARGS, 189 | "Returns subnet with specified subnet-id value."}, 190 | {"selectSubnet", (PyCFunction) CfgSubnets4_selectSubnet, METH_VARARGS, 191 | "Returns a pointer to the selected subnet."}, 192 | {"toElement", (PyCFunction) CfgSubnets4_toElement, METH_NOARGS, 193 | "Unparse configuration object."}, 194 | {0} // Sentinel 195 | }; 196 | 197 | static PyObject * 198 | CfgSubnets4_use_count(OptionObject *self, void *closure) { 199 | // REFCOUNT: PyLong_FromLong - returns new reference 200 | return (PyLong_FromLong(self->ptr.use_count())); 201 | } 202 | 203 | static PyGetSetDef CfgSubnets4_getsetters[] = { 204 | {(char *)"use_count", (getter) CfgSubnets4_use_count, (setter) 0, (char *)"shared_ptr use count", 0}, 205 | {0} // Sentinel 206 | }; 207 | 208 | // tp_dealloc - called when refcount is zero 209 | static void 210 | CfgSubnets4_dealloc(CfgSubnets4Object *self) { 211 | self->ptr.~CfgSubnets4Ptr(); 212 | Py_TYPE(self)->tp_free((PyObject *) self); 213 | } 214 | 215 | // tp_init - called after tp_new has returned an instance 216 | static int 217 | CfgSubnets4_init(CfgSubnets4Object *self, PyObject *args, PyObject *kwds) { 218 | new(&self->ptr) CfgSubnets4Ptr; 219 | 220 | PyErr_SetString(PyExc_TypeError, "cannot create 'kea.CfgSubnets4' instances"); 221 | return (-1); 222 | } 223 | 224 | // tp_new - allocate space and initialisation that can be repeated 225 | static PyObject * 226 | CfgSubnets4_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 227 | CfgSubnets4Object *self; 228 | self = (CfgSubnets4Object *) type->tp_alloc(type, 0); 229 | if (self) { 230 | new(&self->ptr) CfgSubnets4Ptr; 231 | } 232 | return ((PyObject *) self); 233 | } 234 | 235 | PyTypeObject CfgSubnets4Type = { 236 | .ob_base = PyObject_HEAD_INIT(0) 237 | .tp_name = "kea.CfgSubnets4", 238 | .tp_basicsize = sizeof(CfgSubnets4Object), 239 | .tp_dealloc = (destructor) CfgSubnets4_dealloc, 240 | .tp_flags = Py_TPFLAGS_DEFAULT, 241 | .tp_doc = PyDoc_STR("Kea server CfgSubnets4"), 242 | .tp_methods = CfgSubnets4_methods, 243 | .tp_getset = CfgSubnets4_getsetters, 244 | .tp_init = (initproc) CfgSubnets4_init, 245 | .tp_alloc = PyType_GenericAlloc, 246 | .tp_new = CfgSubnets4_new, 247 | }; 248 | 249 | PyObject * 250 | CfgSubnets4_from_ptr(CfgSubnets4Ptr &ptr) { 251 | // REFCOUNT: PyObject_New - returns new reference 252 | CfgSubnets4Object *self = PyObject_New(CfgSubnets4Object, &CfgSubnets4Type); 253 | if (self) { 254 | new(&self->ptr) CfgSubnets4Ptr; 255 | self->ptr = ptr; 256 | } 257 | return ((PyObject *)self); 258 | } 259 | 260 | int 261 | CfgSubnets4_define() { 262 | // PyType_Ready - finish type initialisation 263 | if (PyType_Ready(&CfgSubnets4Type) < 0) { 264 | return (1); 265 | } 266 | Py_INCREF(&CfgSubnets4Type); 267 | // REFCOUNT: PyModule_AddObject steals reference on success 268 | if (PyModule_AddObject(kea_module, "CfgSubnets4", (PyObject *) &CfgSubnets4Type) < 0) { 269 | Py_DECREF(&CfgSubnets4Type); 270 | return (1); 271 | } 272 | 273 | return (0); 274 | } 275 | 276 | } 277 | --------------------------------------------------------------------------------