Skip to content
This repository was archived by the owner on Jan 12, 2022. It is now read-only.

Commit ac0582e

Browse files
Added Python modules and C++ code interacting with them
1 parent cb2ebdb commit ac0582e

File tree

6 files changed

+198
-6
lines changed

6 files changed

+198
-6
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11
# ospf_cplusplus_python_integration
2-
Experiments on integration of Python extensions with C++ OSPF base implementation.
2+
Experiments on the integration of Python extensions with C++ OSPF base implementation.
33

44
The base code is the Release 2.16 of the ospfd routing software created by John T. Moy, and described in the book [OSPF Complete Implementation](https://dl.acm.org/doi/book/10.5555/1481642).
55

66
Executed in Docker containers running Ubuntu 16.04 integrated with GNS3.
77

8+
## Installing and running
9+
10+
Before running the program, a configuration file `ospfd.conf` must be created or placed inside the /etc directory of the machine or Docker container running the program. A sample [configuration file](https://github.com/migueldgoncalves/ospf_cplusplus_python_integration/blob/main/ospfd.conf) is included in this repository.
11+
12+
It is necessary to provide the absolute path to the /python directory of the source code in the line 98 of the [ospfd_linux.C](https://github.com/migueldgoncalves/ospf_cplusplus_python_integration/blob/cb2ebdbeb76b8e787ac0cff179b629237d9ff22d/linux/ospfd_linux.C#L98) file. Currently, it is assumed that the path is /ospf/python.
13+
14+
After copying the content of this repository to the machine, it is necessary to open a terminal and go to the /linux directory of the source code. The program is compiled by running `make install`, and then executed by running `ospfd`. The command `ospfd` can be executed anywhere, and must be run as `root`.
15+
16+
**Note**: When running Docker containers inside GNS3 networks, an /etc directory can be seen inside the directories for each container, inside the respective GNS3 network directory in the host machine. This /etc directory is different from the Ubuntu /etc directory, and is inaccessible from inside the container.
17+
18+
## Python code
19+
20+
Two Python modules have been created along with C++ code interacting with them, producing some output. The Python code is stored in the /python directory of this repository, while the C++ code interacting with Python is stored in the file [ospfd_linux.C](https://github.com/migueldgoncalves/ospf_cplusplus_python_integration/blob/cb2ebdbeb76b8e787ac0cff179b629237d9ff22d/linux/ospfd_linux.C#L93). The [Makefile](https://github.com/migueldgoncalves/ospf_cplusplus_python_integration/blob/main/linux/Makefile) was also updated with Python-specific flags.
21+
22+
The experiments involve calling Python functions with different types of arguments and returns, and manipulating Python
23+
objects of different types.
24+
825
## See also
926

1027
* <http://www.ospf.org> - Original source code by John T. Moy, and respective patches
1128

12-
* <https://github.com/pfa/ospfd-diffs> - Most of required changes to run base code in Ubuntu 16.04
29+
* <https://github.com/pfa/ospfd-diffs> - Most of required changes to run the base code in Ubuntu 16.04
1330

1431
* <https://github.com/migueldgoncalves/ospf_cplusplus_python_integration/releases/tag/Base_code_ubuntu_16> - Base code patched to run in Ubuntu 16, without Python code

linux/Makefile

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ CGI_DIR = /usr/lib/cgi-bin
77
CPPFLAGS = -I. -I../src -I/usr/local/include "-DUSE_INTERP_ERRORLINE" # ErrorLine is deprecated, requires flag
88
CXXFLAGS = -O -g -Wall -Woverloaded-virtual -Wcast-qual -Wuninitialized
99
CFLAGS = -O -g -Wall -Woverloaded-virtual -Wcast-qual -Wuninitialized
10+
# Python recommended compilation flags, as obtained by "python3-config --cflags"
11+
PCOMPILEFLAGS = -I/usr/include/python3.5m -I/usr/include/python3.5m -Wno-unused-result -Wsign-compare -g \
12+
-fstack-protector-strong -Wformat -Werror=format-security -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
13+
# Python recommended linking flags, as obtained by "python3-config --ldflags"
14+
PLINKFLAGS = -L/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu -L/usr/lib -lpython3.5m -lpthread -ldl -lutil -lm \
15+
-Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions
1016

1117
OBJS = asbrlsa.o \
1218
asexlsa.o \
@@ -56,8 +62,8 @@ install: ospfd ospfd_mon ospfd_browser
5662
cp ospfd.tcl ${INSTALL_DIR}
5763

5864
ospfd: ospfd_linux.C linux.o system.o tcppkt.o ${OBJS}
59-
g++ $(CXXFLAGS) $(CPPFLAGS) ospfd_linux.C linux.o system.o \
60-
tcppkt.o ${OBJS} \
65+
g++ $(CXXFLAGS) $(CPPFLAGS) $(PCOMPILEFLAGS) ospfd_linux.C linux.o system.o \
66+
tcppkt.o ${OBJS} $(PLINKFLAGS) \
6167
-DINSTALL_DIR=\"${INSTALL_DIR}\" -ltcl -lm -ldl -o ospfd
6268

6369
ospfd_mon: tcppkt.o lsa_prn.o
@@ -71,11 +77,11 @@ clean:
7177
# Stuff to automatically maintain dependency files
7278

7379
.c.o:
74-
gcc -MD $(CFLAGS) $(CPPFLAGS) -c $<
80+
gcc -MD $(CFLAGS) $(CPPFLAGS) $(PCOMPILEFLAGS) $(PLINKFLAGS) -c $<
7581
@mkdir -p .depfiles ; mv $*.d .depfiles
7682

7783
.C.o:
78-
g++ -MD $(CXXFLAGS) $(CPPFLAGS) -c $<
84+
g++ -MD $(CXXFLAGS) $(CPPFLAGS) $(PCOMPILEFLAGS) $(PLINKFLAGS) -c $<
7985
@mkdir -p .depfiles ; mv $*.d .depfiles
8086

8187
-include $(OBJS:%.o=.depfiles/%.d)

linux/ospfd_linux.C

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
1818
*/
1919

20+
#include <Python.h>
2021
#include <stdio.h>
2122
#include <stdlib.h>
2223
#include <sys/types.h>
@@ -89,6 +90,81 @@ void reconfig(int)
8990
int main(int, char * [])
9091

9192
{
93+
// BEGINNING OF PYTHON-RELATED CODE
94+
95+
// Initialize the Python interpreter
96+
Py_Initialize();
97+
// Important: Replace with absolute path to Python files folder
98+
PyRun_SimpleString("import sys\nfrom time import time,ctime; sys.path.insert(0, '/ospf/python')\nprint('Python started at',ctime(time()))\n");
99+
100+
// Run Python function returning a string, with no arguments
101+
PyObject *pName, *pModule, *pFunc, *pArgs, *pValue;
102+
pName = PyUnicode_FromString((char*)"example");
103+
pModule = PyImport_Import(pName);
104+
pFunc = PyObject_GetAttrString(pModule, (char*)"no_arguments");
105+
pArgs = PyTuple_Pack(0, NULL);
106+
pValue = PyObject_CallObject(pFunc, pArgs);
107+
char* result = {};
108+
result = _PyUnicode_AsString(pValue);
109+
printf("%s", result);
110+
111+
// Run Python function returning a string, with one argument
112+
pFunc = PyObject_GetAttrString(PyImport_Import(PyUnicode_FromString((char*)"example")), (char*)"string_example");
113+
pArgs = PyTuple_Pack(1, PyUnicode_FromString((char*)"This experiment was successful"));
114+
result = _PyUnicode_AsString(PyObject_CallObject(pFunc, pArgs));
115+
printf("%s", result);
116+
117+
// Run Python function returning an int
118+
pFunc = PyObject_GetAttrString(PyImport_Import(PyUnicode_FromString((char*)"example")), (char*)"int_example");
119+
pArgs = PyTuple_Pack(1, PyLong_FromLong(10));
120+
long long_result = 0;
121+
long_result = PyLong_AsLong(PyObject_CallObject(pFunc, pArgs));
122+
printf("%s%d%s", "The received number was: ", long_result, "\n");
123+
124+
// Run Python function returning a boolean
125+
pFunc = PyObject_GetAttrString(PyImport_Import(PyUnicode_FromString((char*)"example")), (char*)"bool_example");
126+
pArgs = PyTuple_Pack(1, Py_False);
127+
bool bool_result = 0;
128+
bool_result = PyBool_Check(PyObject_CallObject(pFunc, pArgs));
129+
printf("%s%d%s", "The received boolean was: ", bool_result, "\n");
130+
131+
// Run Python function returning a bytes object
132+
pFunc = PyObject_GetAttrString(PyImport_Import(PyUnicode_FromString((char*)"example")), (char*)"bytes_example");
133+
pArgs = PyTuple_Pack(1, PyBytes_FromString("abcd\x61\x62\x63\x64"));
134+
result = PyBytes_AsString(PyObject_CallObject(pFunc, pArgs));
135+
printf("%s%s%s", "The received bytes were: ", result, "\n");
136+
137+
// Run Python function returning a list
138+
pFunc = PyObject_GetAttrString(PyImport_Import(PyUnicode_FromString((char*)"example")), (char*)"list_example");
139+
PyObject *list = PyList_New(0);
140+
PyList_Append(list, PyUnicode_FromString((char*)"a"));
141+
PyList_Append(list, PyUnicode_FromString((char*)"b"));
142+
pArgs = PyTuple_Pack(1, list);
143+
PyObject *list_result = PyObject_CallObject(pFunc, pArgs);
144+
printf("%s%d%s", "The length of the received list is: ", PyList_Size(list_result), "\n");
145+
const char *element_1 = PyUnicode_AS_DATA(PyList_GetItem(list_result, 0));
146+
const char *element_2 = PyUnicode_AS_DATA(PyList_GetItem(list_result, 1));
147+
const char *element_3 = PyUnicode_AS_DATA(PyList_GetItem(list_result, 2));
148+
printf("%s%s%s%s%s", "The elements of the received list are: ", element_1, element_2, element_3, "\n");
149+
150+
// Create a custom data object, pack it to bytes, and unpack the bytes back to the original data object
151+
PyObject *module = PyImport_ImportModule((char*)"data_object");
152+
PyObject *custom_class = PyObject_GetAttrString(module, (char*)"ExtensionAbr");
153+
PyObject *instance = PyObject_CallObject(custom_class, PyTuple_Pack(0, NULL));
154+
PyObject *metric = PyLong_FromLong(10);
155+
PyObject *neighbor_router_id = PyUnicode_FromString((char*)"1.1.1.1");
156+
PyObject_CallMethodObjArgs(instance, PyUnicode_FromString((char*)"add_abr_info"), metric, neighbor_router_id, NULL);
157+
PyObject *printed_object = PyObject_CallMethodObjArgs(instance, PyUnicode_FromString((char*)"__str__"), NULL);
158+
printf("%s%s%s", "The printed custom object is: ", _PyUnicode_AsString(printed_object), "\n");
159+
PyObject *object_bytes = PyObject_CallMethodObjArgs(instance, PyUnicode_FromString((char*)"pack_lsa_body"), NULL);
160+
PyObject *restored_instance = PyObject_CallMethodObjArgs(instance, PyUnicode_FromString((char*)"unpack_lsa_body"), object_bytes, NULL);
161+
printed_object = PyObject_CallMethodObjArgs(restored_instance, PyUnicode_FromString((char*)"__str__"), NULL);
162+
printf("%s%s%s", "The restored printed custom object is: ", _PyUnicode_AsString(printed_object), "\n");
163+
164+
Py_Finalize();
165+
166+
// END OF PYTHON-RELATED CODE
167+
92168
int n_fd;
93169
itimerval itim;
94170
fd_set fdset;

ospfd.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
routerid 1.1.1.1
2+
area 0.0.0.0
3+
interface eth0 10
4+
area 0.0.0.0
5+
interface eth1 10

python/data_object.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import ipaddress
2+
import struct
3+
4+
# > - Big-endian
5+
# L - Unsigned long (4 bytes) - struct.pack("> L", 1) -> b'\x00\x00\x00\x01
6+
BASE_FORMAT_STRING = "> L L" # Determines the format of the byte object to be created
7+
8+
9+
class ExtensionAbr: # Adapted from https://github.com/migueldgoncalves/ospf-multiarea-arbitrary-topology
10+
11+
def __init__(self):
12+
self.abr_list = [] # 8 bytes / ABR
13+
14+
# Adds data for one ABR to the LSA body
15+
def add_abr_info(self, metric, neighbor_router_id):
16+
abr_info = [metric, neighbor_router_id]
17+
if not self.has_abr_info(neighbor_router_id):
18+
self.abr_list.append(abr_info)
19+
20+
def has_abr_info(self, neighbor_router_id):
21+
has_abr_info = False
22+
for abr_info in self.abr_list:
23+
if abr_info[1] == neighbor_router_id:
24+
has_abr_info = True
25+
return has_abr_info
26+
27+
def get_abr_info(self, neighbor_router_id):
28+
for abr_info in self.abr_list:
29+
if abr_info[1] == neighbor_router_id:
30+
return abr_info
31+
return None
32+
33+
# Deletes data for one ABR from the LSA body
34+
def delete_abr_info(self, neighbor_router_id):
35+
for abr_info in self.abr_list:
36+
if abr_info[1] == neighbor_router_id:
37+
self.abr_list.remove(abr_info)
38+
39+
# Creates byte object suitable to be sent and recognized as the body of an ABR-LSA of the OSPF extension
40+
def pack_lsa_body(self):
41+
body_bytes = b''
42+
for abr_info in self.abr_list:
43+
metric = int(abr_info[0])
44+
decimal_neighbor_router_id = self.ipv4_to_decimal(abr_info[1])
45+
body_bytes += struct.pack(BASE_FORMAT_STRING, metric, decimal_neighbor_router_id)
46+
return body_bytes
47+
48+
# Converts byte stream to body of an ABR-LSA of the OSPF extension
49+
def unpack_lsa_body(self, body_bytes):
50+
extension_abr_lsa = ExtensionAbr()
51+
while len(body_bytes) > 0:
52+
abr_parameters = struct.unpack(BASE_FORMAT_STRING, body_bytes[:8])
53+
metric = int(abr_parameters[0])
54+
neighbor_router_id = self.decimal_to_ipv4(abr_parameters[1])
55+
extension_abr_lsa.add_abr_info(metric, neighbor_router_id)
56+
body_bytes = body_bytes[8:]
57+
return extension_abr_lsa
58+
59+
# Converts IPv4 addresses to numbers between 0 and 4294967295
60+
@staticmethod
61+
def ipv4_to_decimal(ip_address):
62+
return int(ipaddress.IPv4Address(ip_address))
63+
64+
# Converts numbers between 0 and 4294967295 to IPv4 addresses
65+
@staticmethod
66+
def decimal_to_ipv4(decimal):
67+
return str(ipaddress.IPv4Address(int(decimal)))
68+
69+
def __str__(self):
70+
return str({"ABRs": self.abr_list})

python/example.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
def no_arguments():
2+
return "This function receives no arguments\n"
3+
4+
def string_example(input_string):
5+
return "This is the string supplied to Python: " + input_string + "\n"
6+
7+
def int_example(input_int):
8+
return input_int + 1
9+
10+
def bool_example(input_bool):
11+
return not input_bool
12+
13+
def bytes_example(input_bytes):
14+
return input_bytes + b'\x31\x32\x33\x34\x01\x02\x31'
15+
16+
def list_example(input_list):
17+
input_list.append("c")
18+
return input_list

0 commit comments

Comments
 (0)