diff --git a/.hgignore b/.hgignore index cd5490f..7e37eb9 100644 --- a/.hgignore +++ b/.hgignore @@ -1,6 +1,7 @@ syntax: glob # generated object files +*.pyc *.o *.a *.exe @@ -20,6 +21,7 @@ Makefile* # protobuf generated files *.pb.h *.pb.cc +*_pb2.py # ostinato generated files version.cpp diff --git a/binding/README.txt b/binding/README.txt new file mode 100644 index 0000000..e69de29 diff --git a/binding/__init__.py b/binding/__init__.py new file mode 100644 index 0000000..161ae68 --- /dev/null +++ b/binding/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + diff --git a/binding/core.py b/binding/core.py new file mode 100644 index 0000000..a25d965 --- /dev/null +++ b/binding/core.py @@ -0,0 +1,48 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +from rpc import OstinatoRpcChannel, OstinatoRpcController +import protocols.protocol_pb2 as ost_pb + +class DroneProxy(object): + + def __init__(self, host_name, port_number=7878): + self.host = host_name + self.port = port_number + self.channel = OstinatoRpcChannel() + self.stub = ost_pb.OstService_Stub(self.channel) + + for method in self.stub.GetDescriptor().methods: + fn = lambda request, method_name=method.name: \ + self.callRpcMethod(method_name, request) + self.__dict__[method.name] = fn + + def hostName(self): + return self.host + + def portNumber(self): + return self.port + + def connect(self): + self.channel.connect(self.host, self.port) + + def callRpcMethod(self, method_name, request): + controller = OstinatoRpcController() + ost_pb.OstService_Stub.__dict__[method_name]( + self.stub, controller, request, None) + return controller.response + diff --git a/binding/example.py b/binding/example.py new file mode 100644 index 0000000..54d13ef --- /dev/null +++ b/binding/example.py @@ -0,0 +1,111 @@ +# standard modules +import sys +import time +import logging + +# ostinato modules - prepend 'ostinato.' to the module names when using +# an installed package i.e ostinato.core and ostinato.protocols.xxx +from core import ost_pb, DroneProxy +from protocols.mac_pb2 import mac +from protocols.ip4_pb2 import ip4, Ip4 + +host_name = '127.0.0.1' +tx_port_number = 1 +rx_port_number = 1 + +# setup logging +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG) + +drone = DroneProxy(host_name) + +try: + # connect to drone + log.info('connecting to drone(%s:%d)' + % (drone.hostName(), drone.portNumber())) + drone.connect() + + tx_port = ost_pb.PortIdList() + tx_port.port_id.add().id = tx_port_number; + + rx_port = ost_pb.PortIdList() + rx_port.port_id.add().id = rx_port_number; + + # verify tx and rx ports exist + log.info('verifying tx_port %d' % tx_port.port_id[0].id) + port_config_list = drone.getPortConfig(tx_port) + log.info('-->' + port_config_list.__str__()) + if len(port_config_list.port) <= 0: + log.error('invalid tx_port' + + tx_port_number) + sys.exit(1) + + log.info('verifying rx_port %d' % rx_port.port_id[0].id) + port_config_list = drone.getPortConfig(rx_port) + log.info('-->' + port_config_list.__str__()) + if len(port_config_list.port) <= 0: + log.error('invalid rx_port' + + rx_port_number) + sys.exit(1) + + # add a stream + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(tx_port.port_id[0]) + stream_id.stream_id.add().id = 1 + log.info('adding tx_stream %d' % stream_id.stream_id[0].id) + drone.addStream(stream_id) + + # configure the stream + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[0].id + s.core.is_enabled = 1 + s.control.num_packets = 5 + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac = 0x001122334455 + p.Extensions[mac].src_mac = 0x00aabbccddee + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + p.Extensions[ip4].src_ip = 0x01020304 + p.Extensions[ip4].dst_ip = 0x05060708 + p.Extensions[ip4].dst_ip_mode = Ip4.e_im_inc_host + + s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber + s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) + drone.modifyStream(stream_cfg) + + # clear tx/rx stats + log.info('clearing tx/rx stats') + drone.clearStats(tx_port) + drone.clearStats(rx_port) + + # start transmit + log.info('starting transmit') + drone.startTx(tx_port) + + # wait for transmit to finish + log.info('waiting for transmit to finish ...') + time.sleep(7) + + # get tx/rx stats + log.info('retreiving stats') + tx_stats = drone.getStats(tx_port) + rx_stats = drone.getStats(rx_port) + + log.info('--> (tx_stats)' + tx_stats.__str__()) + log.info('--> (rx_stats)' + rx_stats.__str__()) + log.info('tx pkts = %d, rx pkts = %d' % + (tx_stats.port_stats[0].tx_pkts, rx_stats.port_stats[0].rx_pkts)) + +except Exception, ex: + log.exception(ex) + sys.exit(1) diff --git a/binding/protocols/__init__.py b/binding/protocols/__init__.py new file mode 100644 index 0000000..161ae68 --- /dev/null +++ b/binding/protocols/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + diff --git a/binding/rpc.py b/binding/rpc.py new file mode 100644 index 0000000..6da362f --- /dev/null +++ b/binding/rpc.py @@ -0,0 +1,63 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +from google.protobuf.service import RpcChannel +from google.protobuf.service import RpcController +import socket +import struct + +class OstinatoRpcController(RpcController): + def __init__(self): + super(OstinatoRpcController, self).__init__() + +class OstinatoRpcChannel(RpcChannel): + def __init__(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + def connect(self, host, port): + self.sock.connect((host, port)) + + def CallMethod(self, method, controller, request, response_class, done): + OST_PB_MSG_HDR_SIZE = 8 + OST_PB_MSG_TYPE_REQUEST = 1 + req = request.SerializeToString() + self.sock.sendall(struct.pack('>HHI', + OST_PB_MSG_TYPE_REQUEST, method.index, len(req)) + req) + + hdr = '' + while len(hdr) < OST_PB_MSG_HDR_SIZE: + chunk = self.sock.recv(OST_PB_MSG_HDR_SIZE - len(hdr)) + if chunk == '': + raise RuntimeError("socket connection broken") + hdr = hdr + chunk + + (type, method, resp_len) = struct.unpack('>HHI', hdr) + + resp = '' + while len(resp) < resp_len: + chunk = self.sock.recv(resp_len - len(resp)) + if chunk == '': + raise RuntimeError("socket connection broken") + resp = resp + chunk + + response = response_class() + response.ParseFromString(resp) + + controller.response = response + + + diff --git a/binding/setup.py b/binding/setup.py new file mode 100644 index 0000000..a36face --- /dev/null +++ b/binding/setup.py @@ -0,0 +1,42 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +import os +import shutil +import sys +from setuptools import setup + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +if sys.argv[1] == 'clean_sdist': + shutil.rmtree('dist', ignore_errors = True) + shutil.rmtree('ostinato.egg-info', ignore_errors = True) + sys.exit(0) + +setup(name = 'ostinato', + version = 'FIXME', + author = 'Srivats P', + author_email = 'pstavirs@gmail.com', + license = "GPLv3+", + url = 'http://ostinato.org', + description = 'Ostinato is a network packet and traffic generator and analyzer. It aims to be "Wireshark in Reverse" and become complementary to Wireshark. It features custom packet crafting via a GUI or a script', + long_description = read('README.txt'), + install_requires = ['google.protobuf>=2.3'], + packages=['ostinato', 'ostinato.protocols'], + package_dir={'ostinato': ''} + ) diff --git a/common/ostproto.pro b/common/ostproto.pro index 05024e4..e4467dc 100644 --- a/common/ostproto.pro +++ b/common/ostproto.pro @@ -107,5 +107,8 @@ SOURCES += \ QMAKE_DISTCLEAN += object_script.* +#binding.depends = compiler_protobuf_py_make_all +#QMAKE_EXTRA_TARGETS += binding + include(../protobuf.pri) diff --git a/common/protocol.proto b/common/protocol.proto index 9a74654..07012b5 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -19,6 +19,7 @@ along with this program. If not, see package OstProto; option cc_generic_services = true; +option py_generic_services = true; message StreamId { required uint32 id = 1; diff --git a/protobuf.pri b/protobuf.pri index 30e5130..6316a38 100644 --- a/protobuf.pri +++ b/protobuf.pri @@ -20,7 +20,8 @@ for(p, PROTOPATH):PROTOPATHS += --proto_path=$${p} protobuf_decl.name = protobuf header protobuf_decl.input = PROTOS protobuf_decl.output = ${QMAKE_FILE_BASE}.pb.h -protobuf_decl.commands = protoc --cpp_out="." $${PROTOPATHS} ${QMAKE_FILE_NAME} +#protobuf_decl.commands = protoc --cpp_out="." $${PROTOPATHS} ${QMAKE_FILE_NAME} +protobuf_decl.commands = protoc --cpp_out="." --python_out="../binding/protocols" $${PROTOPATHS} ${QMAKE_FILE_NAME} protobuf_decl.variable_out = GENERATED_FILES QMAKE_EXTRA_COMPILERS += protobuf_decl @@ -31,3 +32,11 @@ protobuf_impl.depends = ${QMAKE_FILE_BASE}.pb.h protobuf_impl.commands = $$escape_expand(\n) protobuf_impl.variable_out = GENERATED_SOURCES QMAKE_EXTRA_COMPILERS += protobuf_impl + +protobuf_py.name = protobuf python binding +protobuf_py.input = PROTOS +protobuf_py.output = ../binding/protocols/${QMAKE_FILE_BASE}_pb2.py +protobuf_py.commands = $$escape_expand(\n) +#protobuf_py.commands = protoc --python_out="../binding/protocols" $${PROTOPATHS} ${QMAKE_FILE_NAME} +protobuf_py.variable_out = GENERATED_FILES +QMAKE_EXTRA_COMPILERS += protobuf_py