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