diff --git a/.gitignore b/.gitignore
index b1dd8ef..cd152a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,6 @@ pkg_info.json
*.swp
.DS_Store
-# ctags
+# ctags/cscope
tags
-
+cscope.out
diff --git a/binding/core.py b/binding/core.py
index b8d47f6..c89020b 100644
--- a/binding/core.py
+++ b/binding/core.py
@@ -18,6 +18,7 @@
import os
from rpc import OstinatoRpcChannel, OstinatoRpcController, RpcError
import protocols.protocol_pb2 as ost_pb
+import protocols.emulproto_pb2 as emul
from __init__ import __version__
class DroneProxy(object):
diff --git a/client/arpstatusmodel.cpp b/client/arpstatusmodel.cpp
new file mode 100644
index 0000000..cd6af5d
--- /dev/null
+++ b/client/arpstatusmodel.cpp
@@ -0,0 +1,151 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#include "arpstatusmodel.h"
+
+#include "port.h"
+
+#include "emulproto.pb.h"
+
+#include
+
+enum {
+ kIp4Address,
+ kMacAddress,
+ kStatus,
+ kFieldCount
+};
+
+static QStringList columns_ = QStringList()
+ << "IPv4 Address"
+ << "Mac Address"
+ << "Status";
+
+ArpStatusModel::ArpStatusModel(QObject *parent)
+ : QAbstractTableModel(parent)
+{
+ port_ = NULL;
+ deviceIndex_ = -1;
+ neighbors_ = NULL;
+}
+
+int ArpStatusModel::rowCount(const QModelIndex &parent) const
+{
+ if (!port_ || deviceIndex_ < 0 || parent.isValid())
+ return 0;
+
+ return port_->numArp(deviceIndex_);
+}
+
+int ArpStatusModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return columns_.size();
+}
+
+QVariant ArpStatusModel::headerData(
+ int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ switch (orientation) {
+ case Qt::Horizontal:
+ return columns_[section];
+ case Qt::Vertical:
+ return QString("%1").arg(section + 1);
+ default:
+ Q_ASSERT(false); // Unreachable
+ }
+
+ return QVariant();
+}
+
+QVariant ArpStatusModel::data(const QModelIndex &index, int role) const
+{
+ QString str;
+
+ if (!port_ || deviceIndex_ < 0 || !index.isValid())
+ return QVariant();
+
+ int arpIdx = index.row();
+ int field = index.column();
+
+ Q_ASSERT(arpIdx < port_->numArp(deviceIndex_));
+ Q_ASSERT(field < kFieldCount);
+
+ const OstEmul::ArpEntry &arp = neighbors_->arp(arpIdx);
+
+ switch (field) {
+ case kIp4Address:
+ switch (role) {
+ case Qt::DisplayRole:
+ return QHostAddress(arp.ip4()).toString();
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kMacAddress:
+ switch (role) {
+ case Qt::DisplayRole:
+ return QString("%1").arg(arp.mac(), 6*2, 16, QChar('0'))
+ .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:")
+ .toUpper();
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kStatus:
+ switch (role) {
+ case Qt::DisplayRole:
+ return arp.mac() ?
+ QString("Resolved") : QString("Failed");
+ default:
+ break;
+ }
+ return QVariant();
+
+ default:
+ Q_ASSERT(false); // unreachable!
+ break;
+ }
+
+ qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
+ return QVariant();
+}
+
+void ArpStatusModel::setDeviceIndex(Port *port, int deviceIndex)
+{
+ port_ = port;
+ deviceIndex_ = deviceIndex;
+ if (port_)
+ neighbors_ = port_->deviceNeighbors(deviceIndex);
+ reset();
+}
+
+void ArpStatusModel::updateArpStatus()
+{
+ reset();
+}
diff --git a/client/arpstatusmodel.h b/client/arpstatusmodel.h
new file mode 100644
index 0000000..64debaa
--- /dev/null
+++ b/client/arpstatusmodel.h
@@ -0,0 +1,55 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _ARP_STATUS_MODEL_H
+#define _ARP_STATUS_MODEL_H
+
+#include
+
+class Port;
+namespace OstEmul {
+ class DeviceNeighborList;
+}
+
+class ArpStatusModel: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ ArpStatusModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role) const;
+
+ void setDeviceIndex(Port *port, int deviceIndex);
+
+public slots:
+ void updateArpStatus();
+
+private:
+ Port *port_;
+ int deviceIndex_;
+ const OstEmul::DeviceNeighborList *neighbors_;
+};
+
+#endif
+
diff --git a/client/devicegroupdialog.cpp b/client/devicegroupdialog.cpp
new file mode 100644
index 0000000..f5b7f2b
--- /dev/null
+++ b/client/devicegroupdialog.cpp
@@ -0,0 +1,355 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#include "devicegroupdialog.h"
+
+#include "port.h"
+#include "spinboxdelegate.h"
+
+#include "emulproto.pb.h"
+#include "uint128.h"
+
+#include
+#include
+
+enum { kVlanId, kVlanCount, kVlanStep, kVlanCfi, kVlanPrio, kVlanTpid,
+ kVlanColumns };
+static QStringList vlanTableColumnHeaders = QStringList()
+ << "Vlan Id" << "Count" << "Step" << "CFI/DE" << "Prio" << "TPID";
+
+enum { kIpNone, kIp4, kIp6, kIpDual };
+static QStringList ipStackItems = QStringList()
+ << "None" << "IPv4" << "IPv6" << "Dual";
+
+inline UInt128 UINT128(OstEmul::Ip6Address x)
+{
+ return UInt128(x.hi(), x.lo());
+}
+
+inline OstEmul::Ip6Address IP6ADDR(UInt128 x)
+{
+ OstEmul::Ip6Address ip;
+
+ ip.set_hi(x.hi64());
+ ip.set_lo(x.lo64());
+ return ip;
+}
+
+DeviceGroupDialog::DeviceGroupDialog(
+ Port *port,
+ int deviceGroupIndex,
+ QWidget *parent,
+ Qt::WindowFlags flags)
+ : QDialog(parent, flags), port_(port), index_(deviceGroupIndex)
+{
+ // Setup the Dialog
+ setupUi(this);
+
+ vlanTagCount->setRange(0, kMaxVlanTags);
+
+ // Populate the Vlan Table with placeholders - we do this so that
+ // user entered values are retained during the lifetime of the dialog
+ // even if user is playing around with number of vlan tags
+ vlans->setRowCount(kMaxVlanTags);
+ vlans->setColumnCount(kVlanColumns);
+ vlans->setHorizontalHeaderLabels(vlanTableColumnHeaders);
+ for (int i = 0; i < kMaxVlanTags; i++) {
+ // Use same default values as defined in .proto
+ vlans->setItem(i, kVlanId,
+ new QTableWidgetItem(QString::number(100*(i+1))));
+ vlans->setItem(i, kVlanCount,
+ new QTableWidgetItem(QString::number(1)));
+ vlans->setItem(i, kVlanStep,
+ new QTableWidgetItem(QString::number(1)));
+ vlans->setItem(i, kVlanCfi,
+ new QTableWidgetItem(QString::number(0)));
+ vlans->setItem(i, kVlanPrio,
+ new QTableWidgetItem(QString::number(0)));
+ vlans->setItem(i, kVlanTpid,
+ new QTableWidgetItem(QString("0x8100")));
+ }
+
+ // Set SpinBoxDelegate for all columns except TPID
+ SpinBoxDelegate *spd = new SpinBoxDelegate(this);
+ spd->setColumnRange(kVlanId, 0, 4095);
+ spd->setColumnRange(kVlanStep, 0, 4095);
+ spd->setColumnRange(kVlanCfi, 0, 1);
+ spd->setColumnRange(kVlanPrio, 0, 7);
+ for (int i = 0; i < kVlanColumns; i++) {
+ if (i != kVlanTpid)
+ vlans->setItemDelegateForColumn(i, spd);
+ }
+
+ vlans->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
+ vlans->resizeRowsToContents();
+
+ // Set vlan tag count *after* adding all items, so connected slots
+ // can access the items
+ vlanTagCount->setValue(kMaxVlanTags);
+
+ ipStack->insertItems(0, ipStackItems);
+
+ // TODO: DeviceGroup Traversal; hide buttons for now
+ // NOTE for implementation: Use a QHash
+ // to store modified values while traversing; in accept()
+ // update port->deviceGroups[] from the QHash
+ prev->setHidden(true);
+ next->setHidden(true);
+
+ // TODO: Preview devices expanded from deviceGroup configuration
+ // for user convenience
+
+ // setup dialog to auto-resize as widgets are hidden or shown
+ layout()->setSizeConstraint(QLayout::SetFixedSize);
+
+ connect(devicePerVlanCount, SIGNAL(valueChanged(const QString&)),
+ this, SLOT(updateTotalDeviceCount()));
+
+ connect(ip4Address, SIGNAL(textEdited(const QString&)),
+ this, SLOT(updateIp4Gateway()));
+ connect(ip4PrefixLength, SIGNAL(valueChanged(const QString&)),
+ this, SLOT(updateIp4Gateway()));
+ connect(ip6Address, SIGNAL(textEdited(const QString&)),
+ this, SLOT(updateIp6Gateway()));
+ connect(ip6PrefixLength, SIGNAL(valueChanged(const QString&)),
+ this, SLOT(updateIp6Gateway()));
+
+ loadDeviceGroup();
+}
+
+void DeviceGroupDialog::accept()
+{
+ storeDeviceGroup();
+ QDialog::accept();
+}
+
+//
+// Private Slots
+//
+void DeviceGroupDialog::on_vlanTagCount_valueChanged(int value)
+{
+ Q_ASSERT((value >= 0) && (value <= kMaxVlanTags));
+
+ for (int row = 0; row < kMaxVlanTags; row++)
+ vlans->setRowHidden(row, row >= value);
+
+ vlans->setVisible(value > 0);
+ updateTotalVlanCount();
+}
+
+void DeviceGroupDialog::on_vlans_cellChanged(int row, int col)
+{
+ if (col != kVlanCount)
+ return;
+
+ if (vlans->isRowHidden(row))
+ return;
+
+ updateTotalVlanCount();
+}
+
+void DeviceGroupDialog::on_ipStack_currentIndexChanged(int index)
+{
+ switch (index) {
+ case kIpNone:
+ ip4->hide();
+ ip6->hide();
+ break;
+ case kIp4:
+ ip4->show();
+ ip6->hide();
+ break;
+ case kIp6:
+ ip4->hide();
+ ip6->show();
+ break;
+ case kIpDual:
+ ip4->show();
+ ip6->show();
+ break;
+ default:
+ Q_ASSERT(false); // Unreachable!
+ break;
+ }
+}
+
+void DeviceGroupDialog::updateTotalVlanCount()
+{
+ int count = vlanTagCount->value() ? 1 : 0;
+ for (int i = 0; i < vlanTagCount->value(); i++)
+ count *= vlans->item(i, kVlanCount)->text().toUInt();
+ vlanCount->setValue(count);
+
+ updateTotalDeviceCount();
+}
+
+void DeviceGroupDialog::updateTotalDeviceCount()
+{
+ totalDeviceCount->setValue(qMax(vlanCount->value(), 1)
+ * devicePerVlanCount->value());
+}
+
+void DeviceGroupDialog::updateIp4Gateway()
+{
+ quint32 net = ip4Address->value() & (~0 << (32 - ip4PrefixLength->value()));
+ ip4Gateway->setValue(net | 0x01);
+}
+
+void DeviceGroupDialog::updateIp6Gateway()
+{
+ UInt128 net = ip6Address->value()
+ & (~UInt128(0, 0) << (128 - ip6PrefixLength->value()));
+ ip6Gateway->setValue(net | UInt128(0, 1));
+}
+
+void DeviceGroupDialog::loadDeviceGroup()
+{
+ const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(index_);
+ int tagCount = 0;
+ // use 1-indexed id so that it matches the port id used in the
+ // RFC 4814 compliant mac addresses assigned by default to deviceGroups
+ // XXX: use deviceGroupId also as part of the id?
+ quint32 id = (port_->id()+1) & 0xff;
+
+ Q_ASSERT(devGrp);
+
+ name->setText(QString::fromStdString(devGrp->core().name()));
+
+ if (devGrp->has_encap() && devGrp->encap().HasExtension(OstEmul::vlan)) {
+ OstEmul::VlanEmulation vlan = devGrp->encap()
+ .GetExtension(OstEmul::vlan);
+ tagCount = vlan.stack_size();
+ for (int i = 0; i < tagCount; i++) {
+ OstEmul::VlanEmulation::Vlan v = vlan.stack(i);
+ vlans->item(i, kVlanPrio)->setText(
+ QString::number((v.vlan_tag() >> 13) & 0x7));
+ vlans->item(i, kVlanCfi)->setText(
+ QString::number((v.vlan_tag() >> 12) & 0x1));
+ vlans->item(i, kVlanId)->setText(
+ QString::number(v.vlan_tag() & 0x0fff));
+ vlans->item(i, kVlanCount)->setText(QString::number(v.count()));
+ vlans->item(i, kVlanStep)->setText(QString::number(v.step()));
+ vlans->item(i, kVlanTpid)->setText(QString("0x%1")
+ .arg(v.tpid(), 0, 16));
+ }
+ }
+ vlanTagCount->setValue(tagCount);
+
+ updateTotalVlanCount();
+ devicePerVlanCount->setValue(devGrp->device_count());
+
+ OstEmul::MacEmulation mac = devGrp->GetExtension(OstEmul::mac);
+ Q_ASSERT(mac.has_address());
+ macAddress->setValue(mac.address());
+ macStep->setValue(mac.step());
+
+ OstEmul::Ip4Emulation ip4 = devGrp->GetExtension(OstEmul::ip4);
+ // If address is not set, assign one from RFC 2544 space - 192.18.0.0/15
+ // Use port Id as the 3rd octet of the address
+ ip4Address->setValue(ip4.has_address() ?
+ ip4.address() : 0xc6120002 | (id << 8));
+ ip4PrefixLength->setValue(ip4.prefix_length());
+ ip4Step->setValue(ip4.has_step()? ip4.step() : 1);
+ ip4Gateway->setValue(ip4.has_default_gateway() ?
+ ip4.default_gateway() : 0xc6120001 | (id << 8));
+
+ OstEmul::Ip6Emulation ip6 = devGrp->GetExtension(OstEmul::ip6);
+ // If address is not set, assign one from RFC 5180 space 2001:0200::/64
+ // Use port Id as the 3rd hextet of the address
+ ip6Address->setValue(ip6.has_address() ?
+ UINT128(ip6.address()) :
+ UInt128((0x200102000000ULL | id) << 16, 2));
+ ip6PrefixLength->setValue(ip6.prefix_length());
+ ip6Step->setValue(ip6.has_step() ? UINT128(ip6.step()) : UInt128(0, 1));
+ ip6Gateway->setValue(ip6.has_default_gateway() ?
+ UINT128(ip6.default_gateway()) :
+ UInt128((0x200102000000ULL | id) << 16, 1));
+
+ int stk = kIpNone;
+ if (devGrp->HasExtension(OstEmul::ip4))
+ if (devGrp->HasExtension(OstEmul::ip6))
+ stk = kIpDual;
+ else
+ stk = kIp4;
+ else if (devGrp->HasExtension(OstEmul::ip6))
+ stk = kIp6;
+ ipStack->setCurrentIndex(stk);
+}
+
+void DeviceGroupDialog::storeDeviceGroup()
+{
+ OstProto::DeviceGroup *devGrp = port_->mutableDeviceGroupByIndex(index_);
+ int tagCount = vlanTagCount->value();
+
+ Q_ASSERT(devGrp);
+
+ devGrp->mutable_core()->set_name(name->text().toStdString());
+
+ OstEmul::VlanEmulation *vlan = devGrp->mutable_encap()
+ ->MutableExtension(OstEmul::vlan);
+ vlan->clear_stack();
+ for (int i = 0; i < tagCount; i++) {
+ OstEmul::VlanEmulation::Vlan *v = vlan->add_stack();
+ v->set_vlan_tag(
+ vlans->item(i, kVlanPrio)->text().toUInt() << 13
+ | vlans->item(i, kVlanCfi)->text().toUInt() << 12
+ | vlans->item(i, kVlanId)->text().toUInt());
+ v->set_count(vlans->item(i, kVlanCount)->text().toUInt());
+ v->set_step(vlans->item(i, kVlanStep)->text().toUInt());
+ v->set_tpid(vlans->item(i, kVlanTpid)->text().toUInt(NULL, 16));
+ }
+
+ if (!tagCount)
+ devGrp->clear_encap();
+
+ devGrp->set_device_count(devicePerVlanCount->value());
+
+ OstEmul::MacEmulation *mac = devGrp->MutableExtension(OstEmul::mac);
+ mac->set_address(macAddress->value());
+ mac->set_step(macStep->value());
+
+ if (ipStack->currentIndex() == kIp4
+ || ipStack->currentIndex() == kIpDual) {
+ OstEmul::Ip4Emulation *ip4 = devGrp->MutableExtension(OstEmul::ip4);
+ ip4->set_address(ip4Address->value());
+ ip4->set_prefix_length(ip4PrefixLength->value());
+ ip4->set_default_gateway(ip4Gateway->value());
+ ip4->set_step(ip4Step->value());
+
+ if (ipStack->currentIndex() == kIp4)
+ devGrp->ClearExtension(OstEmul::ip6);
+ }
+
+ if (ipStack->currentIndex() == kIp6
+ || ipStack->currentIndex() == kIpDual) {
+ OstEmul::Ip6Emulation *ip6 = devGrp->MutableExtension(OstEmul::ip6);
+ ip6->mutable_address()->CopyFrom(IP6ADDR(ip6Address->value()));
+ ip6->set_prefix_length(ip6PrefixLength->value());
+ ip6->mutable_step()->CopyFrom(IP6ADDR(ip6Step->value()));
+ ip6->mutable_default_gateway()->CopyFrom(IP6ADDR(ip6Gateway->value()));
+
+ if (ipStack->currentIndex() == kIp6)
+ devGrp->ClearExtension(OstEmul::ip4);
+ }
+
+ if (ipStack->currentIndex() == kIpNone) {
+ devGrp->ClearExtension(OstEmul::ip4);
+ devGrp->ClearExtension(OstEmul::ip6);
+ }
+}
diff --git a/client/devicegroupdialog.h b/client/devicegroupdialog.h
new file mode 100644
index 0000000..41f2358
--- /dev/null
+++ b/client/devicegroupdialog.h
@@ -0,0 +1,56 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _DEVICE_GROUP_DIALOG_H
+#define _DEVICE_GROUP_DIALOG_H
+
+#include "ui_devicegroupdialog.h"
+
+#include
+
+class Port;
+
+class DeviceGroupDialog: public QDialog, private Ui::DeviceGroupDialog
+{
+ Q_OBJECT
+public:
+ DeviceGroupDialog(Port *port, int deviceGroupIndex,
+ QWidget *parent = NULL, Qt::WindowFlags flags = 0);
+
+ virtual void accept();
+private slots:
+ void on_vlanTagCount_valueChanged(int value);
+ void on_vlans_cellChanged(int row, int col);
+ void on_ipStack_currentIndexChanged(int index);
+
+ void updateTotalVlanCount();
+ void updateTotalDeviceCount();
+ void updateIp4Gateway();
+ void updateIp6Gateway();
+
+ void loadDeviceGroup();
+ void storeDeviceGroup();
+private:
+ static const int kMaxVlanTags = 4;
+
+ Port *port_;
+ int index_;
+};
+
+#endif
diff --git a/client/devicegroupdialog.ui b/client/devicegroupdialog.ui
new file mode 100644
index 0000000..cd9bf87
--- /dev/null
+++ b/client/devicegroupdialog.ui
@@ -0,0 +1,385 @@
+
+ DeviceGroupDialog
+
+
+
+ 0
+ 0
+ 504
+ 465
+
+
+
+ Devices
+
+
+ -
+
+
-
+
+
+ Name
+
+
+
+ -
+
+
+ -
+
+
+ Vlan Tags
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Total Vlans
+
+
+
+ -
+
+
+ false
+
+
+
+ -
+
+
+ Devices Per Vlan
+
+
+ devicePerVlanCount
+
+
+
+ -
+
+
+ -
+
+
+ Total Devices
+
+
+
+ -
+
+
+ false
+
+
+
+ -
+
+
+ Mac Address
+
+
+
+ -
+
+
+ -
+
+
+ Step
+
+
+
+ -
+
+
+ -
+
+
+ IP Stack
+
+
+
+ -
+
+
+
+
+ -
+
+
+ QFrame::Box
+
+
+ QFrame::Plain
+
+
+
-
+
+
+ IPv4 Address
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ /
+
+
+
+ -
+
+
+ Step
+
+
+
+ -
+
+
+ -
+
+
+ Gateway
+
+
+
+ -
+
+
+ -
+
+
+ 1
+
+
+ 32
+
+
+
+
+
+
+ -
+
+
+ QFrame::Box
+
+
+ QFrame::Plain
+
+
+
-
+
+
+ IPv6 Address
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ /
+
+
+
+ -
+
+
+ Step
+
+
+
+ -
+
+
+ -
+
+
+ Gateway
+
+
+
+ -
+
+
+ -
+
+
+ 1
+
+
+ 128
+
+
+
+
+
+
+ -
+
+
-
+
+
-
+
+
+ <
+
+
+
+ -
+
+
+ >
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+ MacEdit
+ QLineEdit
+
+
+
+ Ip4Edit
+ QLineEdit
+
+
+
+ Ip6Edit
+ QLineEdit
+
+
+
+ IntEdit
+ QSpinBox
+
+
+
+
+ name
+ vlanTagCount
+ vlans
+ devicePerVlanCount
+ macAddress
+ macStep
+ ipStack
+ ip4Address
+ ip4PrefixLength
+ ip4Step
+ ip4Gateway
+ ip6Address
+ ip6PrefixLength
+ ip6Step
+ ip6Gateway
+ prev
+ next
+ buttonBox
+
+
+
+
+ buttonBox
+ accepted()
+ DeviceGroupDialog
+ accept()
+
+
+ 227
+ 289
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ DeviceGroupDialog
+ reject()
+
+
+ 295
+ 295
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp
new file mode 100644
index 0000000..8749d74
--- /dev/null
+++ b/client/devicegroupmodel.cpp
@@ -0,0 +1,282 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#include "devicegroupmodel.h"
+
+#include "port.h"
+
+#include "emulproto.pb.h"
+#include "uint128.h"
+
+#include
+
+enum {
+ kName,
+ kVlanCount,
+ kDeviceCount, // Across all vlans
+ kIp,
+ kIp4Address,
+ kIp6Address,
+ kFieldCount
+};
+
+static QStringList columns_ = QStringList()
+ << "Name"
+ << "Vlans"
+ << "Devices"
+ << "IP Stack"
+ << "IPv4 Address"
+ << "IPv6 Address";
+
+DeviceGroupModel::DeviceGroupModel(QObject *parent)
+ : QAbstractTableModel(parent)
+{
+ port_ = NULL;
+}
+
+int DeviceGroupModel::rowCount(const QModelIndex &parent) const
+{
+ if (!port_ || parent.isValid())
+ return 0;
+
+ return port_->numDeviceGroups();
+}
+
+int DeviceGroupModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return columns_.size();
+}
+
+QVariant DeviceGroupModel::headerData(
+ int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ switch (orientation) {
+ case Qt::Horizontal:
+ return columns_[section];
+ case Qt::Vertical:
+ return QString("%1").arg(section + 1);
+ default:
+ Q_ASSERT(false); // Unreachable
+ }
+
+ return QVariant();
+}
+
+QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const
+{
+ if (!port_ || !index.isValid())
+ return QVariant();
+
+ int dgIdx = index.row();
+ int field = index.column();
+
+ Q_ASSERT(dgIdx < port_->numDeviceGroups());
+ Q_ASSERT(field < kFieldCount);
+
+ const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(dgIdx);
+
+ Q_ASSERT(devGrp);
+
+ switch (field) {
+ case kName:
+ switch (role) {
+ case Qt::DisplayRole:
+ return QString::fromStdString(devGrp->core().name());
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kVlanCount:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (int v = vlanCount(devGrp))
+ return v;
+ return QString("None");
+ case Qt::TextAlignmentRole:
+ return Qt::AlignRight;
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kDeviceCount:
+ switch (role) {
+ case Qt::DisplayRole:
+ return qMax(vlanCount(devGrp), 1)*devGrp->device_count();
+ case Qt::TextAlignmentRole:
+ return Qt::AlignRight;
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kIp:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (devGrp->HasExtension(OstEmul::ip4))
+ if (devGrp->HasExtension(OstEmul::ip6))
+ return QString("Dual Stack");
+ else
+ return QString("IPv4");
+ else if (devGrp->HasExtension(OstEmul::ip6))
+ return QString("IPv6");
+ else
+ return QString("None");
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kIp4Address:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (devGrp->HasExtension(OstEmul::ip4))
+ return QHostAddress(
+ devGrp->GetExtension(OstEmul::ip4)
+ .address()).toString();
+ else
+ return QString("--");
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kIp6Address:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (devGrp->HasExtension(OstEmul::ip6)) {
+ OstEmul::Ip6Address ip = devGrp->GetExtension(
+ OstEmul::ip6).address();
+ return QHostAddress(
+ UInt128(ip.hi(), ip.lo()).toArray())
+ .toString();
+ }
+ else
+ return QString("--");
+ default:
+ break;
+ }
+ return QVariant();
+
+ default:
+ Q_ASSERT(false); // unreachable!
+ break;
+ }
+
+ qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
+ return QVariant();
+}
+
+bool DeviceGroupModel::setData(
+ const QModelIndex &index,
+ const QVariant &value,
+ int role)
+{
+ if (!port_)
+ return false;
+
+ // TODO; when implementing also implement flags() to
+ // return ItemIsEditable
+ return false;
+}
+
+bool DeviceGroupModel::insertRows(
+ int row,
+ int count,
+ const QModelIndex &parent)
+{
+ int c = 0;
+
+ Q_ASSERT(!parent.isValid());
+
+ beginInsertRows(parent, row, row+count-1);
+ for (int i = 0; i < count; i++) {
+ if (port_->newDeviceGroupAt(row))
+ c++;
+ }
+ endInsertRows();
+
+ if (c != count) {
+ qWarning("failed to insert rows in DeviceGroupModel at row %d; "
+ "requested = %d, actual = %d", row, count, c);
+ return false;
+ }
+
+ return true;
+}
+
+bool DeviceGroupModel::removeRows(
+ int row,
+ int count,
+ const QModelIndex &parent)
+{
+ int c = 0;
+
+ Q_ASSERT(!parent.isValid());
+
+ beginRemoveRows(parent, row, row+count-1);
+ for (int i = 0; i < count; i++) {
+ if (port_->deleteDeviceGroupAt(row))
+ c++;
+ }
+ endRemoveRows();
+
+ if (c != count) {
+ qWarning("failed to delete rows in DeviceGroupModel at row %d; "
+ "requested = %d, actual = %d", row, count, c);
+ return false;
+ }
+
+ return true;
+}
+
+void DeviceGroupModel::setPort(Port *port)
+{
+ port_ = port;
+ reset();
+}
+
+//
+// ---------------------- Private Methods -----------------------
+//
+int DeviceGroupModel::vlanCount(const OstProto::DeviceGroup *deviceGroup) const
+{
+ if (!deviceGroup->has_encap()
+ || !deviceGroup->encap().HasExtension(OstEmul::vlan))
+ return 0;
+
+ OstEmul::VlanEmulation vlan = deviceGroup->encap()
+ .GetExtension(OstEmul::vlan);
+ int numTags = vlan.stack_size();
+ int count = 1;
+
+ for (int i = 0; i < numTags; i++)
+ count *= vlan.stack(i).count();
+
+ return count;
+}
diff --git a/client/devicegroupmodel.h b/client/devicegroupmodel.h
new file mode 100644
index 0000000..9c0ec3f
--- /dev/null
+++ b/client/devicegroupmodel.h
@@ -0,0 +1,59 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _DEVICE_GROUP_MODEL_H
+#define _DEVICE_GROUP_MODEL_H
+
+#include
+#include
+
+class Port;
+namespace OstProto {
+ class DeviceGroup;
+};
+
+class DeviceGroupModel: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ DeviceGroupModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role) const;
+ bool setData(const QModelIndex &index, const QVariant &value,
+ int role = Qt::EditRole);
+ bool insertRows (int row, int count,
+ const QModelIndex &parent = QModelIndex());
+ bool removeRows (int row, int count,
+ const QModelIndex &parent = QModelIndex());
+
+ void setPort(Port *port);
+
+private:
+ int vlanCount(const OstProto::DeviceGroup *deviceGroup) const;
+
+ Port *port_;
+};
+
+#endif
+
diff --git a/client/devicemodel.cpp b/client/devicemodel.cpp
new file mode 100644
index 0000000..d917e80
--- /dev/null
+++ b/client/devicemodel.cpp
@@ -0,0 +1,291 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#include "devicemodel.h"
+
+#include "arpstatusmodel.h"
+#include "ndpstatusmodel.h"
+#include "port.h"
+
+#include "emulproto.pb.h"
+#include "uint128.h"
+
+#include
+#include
+#include
+#include
+
+enum {
+ kMacAddress,
+ kVlans,
+ kIp4Address,
+ kIp4Gateway,
+ kIp6Address,
+ kIp6Gateway,
+ kArpInfo,
+ kNdpInfo,
+ kFieldCount
+};
+
+static QStringList columns_ = QStringList()
+ << "Mac"
+ << "Vlans"
+ << "IPv4 Address"
+ << "IPv4 Gateway"
+ << "IPv6 Address"
+ << "IPv6 Gateway"
+ << "ARP"
+ << "NDP";
+
+DeviceModel::DeviceModel(QObject *parent)
+ : QAbstractTableModel(parent)
+{
+ port_ = NULL;
+ arpStatusModel_ = new ArpStatusModel(this);
+ ndpStatusModel_ = new NdpStatusModel(this);
+}
+
+int DeviceModel::rowCount(const QModelIndex &parent) const
+{
+ if (!port_ || parent.isValid())
+ return 0;
+
+ return port_->numDevices();
+}
+
+int DeviceModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return columns_.size();
+}
+
+QVariant DeviceModel::headerData(
+ int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ switch (orientation) {
+ case Qt::Horizontal:
+ return columns_[section];
+ case Qt::Vertical:
+ return QString("%1").arg(section + 1);
+ default:
+ Q_ASSERT(false); // Unreachable
+ }
+
+ return QVariant();
+}
+
+QVariant DeviceModel::data(const QModelIndex &index, int role) const
+{
+ QString str;
+
+ if (!port_ || !index.isValid())
+ return QVariant();
+
+ int devIdx = index.row();
+ int field = index.column();
+
+ Q_ASSERT(devIdx < port_->numDevices());
+ Q_ASSERT(field < kFieldCount);
+
+ const OstEmul::Device *dev = port_->deviceByIndex(devIdx);
+
+ Q_ASSERT(dev);
+
+ switch (field) {
+ case kMacAddress:
+ switch (role) {
+ case Qt::DisplayRole:
+ return QString("%1").arg(dev->mac(), 6*2, 16, QChar('0'))
+ .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:")
+ .toUpper();
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kVlans:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (!dev->vlan_size())
+ return QString("None");
+ for (int i = 0; i < dev->vlan_size(); i++)
+ str.append(i == 0 ? "" : ", ")
+ .append(QString::number(dev->vlan(i) & 0xfff));
+ return str;
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kIp4Address:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (dev->has_ip4_prefix_length())
+ return QString("%1/%2")
+ .arg(QHostAddress(dev->ip4()).toString())
+ .arg(dev->ip4_prefix_length());
+ else
+ return QString("--");
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kIp4Gateway:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (dev->has_ip4_prefix_length())
+ return QHostAddress(dev->ip4_default_gateway())
+ .toString();
+ else
+ return QString("--");
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kIp6Address:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (dev->has_ip6_prefix_length()) {
+ OstEmul::Ip6Address ip = dev->ip6();
+ return QHostAddress(
+ UInt128(ip.hi(), ip.lo()).toArray())
+ .toString();
+ }
+ else
+ return QString("--");
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kIp6Gateway:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (dev->has_ip6_prefix_length()) {
+ OstEmul::Ip6Address ip = dev->ip6_default_gateway();
+ return QHostAddress(
+ UInt128(ip.hi(), ip.lo()).toArray())
+ .toString();
+ }
+ else
+ return QString("--");
+ default:
+ break;
+ }
+ return QVariant();
+
+ case kArpInfo:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (dev->has_ip4_prefix_length())
+ return QString("%1/%2")
+ .arg(port_->numArpResolved(devIdx))
+ .arg(port_->numArp(devIdx));
+ else
+ return QString("--");
+ default:
+ if (dev->has_ip4_prefix_length())
+ return drillableStyle(role);
+ break;
+ }
+ return QVariant();
+
+ case kNdpInfo:
+ switch (role) {
+ case Qt::DisplayRole:
+ if (dev->has_ip6_prefix_length())
+ return QString("%1/%2")
+ .arg(port_->numNdpResolved(devIdx))
+ .arg(port_->numNdp(devIdx));
+ else
+ return QString("--");
+ default:
+ if (dev->has_ip6_prefix_length())
+ return drillableStyle(role);
+ break;
+ }
+ return QVariant();
+
+ default:
+ Q_ASSERT(false); // unreachable!
+ break;
+ }
+
+ qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
+
+ return QVariant();
+}
+
+void DeviceModel::setPort(Port *port)
+{
+ port_ = port;
+ if (port_)
+ connect(port_, SIGNAL(deviceInfoChanged()), SLOT(updateDeviceList()));
+ reset();
+}
+
+QAbstractItemModel* DeviceModel::detailModel(const QModelIndex &index)
+{
+ if (!index.isValid())
+ return NULL;
+
+ switch(index.column()) {
+ case kArpInfo:
+ arpStatusModel_->setDeviceIndex(port_, index.row());
+ return arpStatusModel_;
+ case kNdpInfo:
+ ndpStatusModel_->setDeviceIndex(port_, index.row());
+ return ndpStatusModel_;
+ default:
+ return NULL;
+ }
+}
+
+void DeviceModel::updateDeviceList()
+{
+ reset();
+}
+
+// Style roles for drillable fields
+QVariant DeviceModel::drillableStyle(int role) const
+{
+ QFont f;
+ switch (role) {
+ case Qt::ToolTipRole:
+ return QString("Click for details ...");
+ case Qt::ForegroundRole:
+ return QBrush(QColor(Qt::blue));
+ case Qt::FontRole:
+ f.setUnderline(true);
+ return f;
+ default:
+ break;
+ }
+ return QVariant();
+}
+
diff --git a/client/devicemodel.h b/client/devicemodel.h
new file mode 100644
index 0000000..a16a77d
--- /dev/null
+++ b/client/devicemodel.h
@@ -0,0 +1,57 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _DEVICE_MODEL_H
+#define _DEVICE_MODEL_H
+
+#include
+
+class ArpStatusModel;
+class NdpStatusModel;
+class Port;
+
+class DeviceModel: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ DeviceModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role) const;
+
+ void setPort(Port *port);
+ QAbstractItemModel* detailModel(const QModelIndex &index);
+
+public slots:
+ void updateDeviceList();
+
+private:
+ QVariant drillableStyle(int role) const;
+
+ Port *port_;
+ ArpStatusModel *arpStatusModel_;
+ NdpStatusModel *ndpStatusModel_;
+};
+
+#endif
+
diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp
new file mode 100644
index 0000000..7f84a2c
--- /dev/null
+++ b/client/deviceswidget.cpp
@@ -0,0 +1,249 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#include "deviceswidget.h"
+
+#include "devicegroupdialog.h"
+#include "port.h"
+#include "portgrouplist.h"
+
+#include
+#include
+
+DevicesWidget::DevicesWidget(QWidget *parent)
+ : QWidget(parent), portGroups_(NULL)
+{
+ setupUi(this);
+ deviceGroupList->setVisible(deviceConfig->isChecked());
+ deviceList->setVisible(deviceInfo->isChecked());
+ refresh->setVisible(deviceInfo->isChecked());
+ deviceDetail->hide();
+
+ deviceGroupList->verticalHeader()->setDefaultSectionSize(
+ deviceGroupList->verticalHeader()->minimumSectionSize());
+ deviceList->verticalHeader()->setDefaultSectionSize(
+ deviceList->verticalHeader()->minimumSectionSize());
+ deviceDetail->verticalHeader()->setDefaultSectionSize(
+ deviceDetail->verticalHeader()->minimumSectionSize());
+
+ // Populate DeviceGroup Context Menu Actions
+ deviceGroupList->addAction(actionNewDeviceGroup);
+ deviceGroupList->addAction(actionEditDeviceGroup);
+ deviceGroupList->addAction(actionDeleteDeviceGroup);
+
+ // DevicesWidget's actions is an aggegate of all sub-widget's actions
+ addActions(deviceGroupList->actions());
+}
+
+void DevicesWidget::setPortGroupList(PortGroupList *portGroups)
+{
+ portGroups_ = portGroups;
+
+ deviceGroupList->setModel(portGroups_->getDeviceGroupModel());
+ deviceList->setModel(portGroups_->getDeviceModel());
+
+ connect(portGroups_->getDeviceGroupModel(),
+ SIGNAL(rowsInserted(QModelIndex, int, int)),
+ SLOT(updateDeviceViewActions()));
+ connect(portGroups_->getDeviceGroupModel(),
+ SIGNAL(rowsRemoved(QModelIndex, int, int)),
+ SLOT(updateDeviceViewActions()));
+
+ connect(deviceGroupList->selectionModel(),
+ SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
+ SLOT(updateDeviceViewActions()));
+ connect(deviceGroupList->selectionModel(),
+ SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
+ SLOT(updateDeviceViewActions()));
+
+ connect(deviceList->selectionModel(),
+ SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
+ SLOT(when_deviceList_currentChanged(const QModelIndex&)));
+
+ // FIXME: hardcoding
+ deviceGroupList->resizeColumnToContents(1); // Vlan Count
+ deviceGroupList->resizeColumnToContents(2); // Device Count
+
+ // FIXME: hardcoding
+ deviceList->resizeColumnToContents(1); // Vlan Id(s)
+ deviceList->resizeColumnToContents(6); // ARP Info
+ deviceList->resizeColumnToContents(7); // NDP Info
+
+ updateDeviceViewActions();
+}
+
+void DevicesWidget::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape) {
+ deviceDetail->hide();
+ event->accept();
+ }
+ else
+ event->ignore();
+}
+
+void DevicesWidget::setCurrentPortIndex(const QModelIndex &portIndex)
+{
+ Port *port = NULL;
+
+ if (!portGroups_)
+ return;
+
+ // XXX: We assume portIndex corresponds to sourceModel, not proxyModel
+ if (portGroups_->isPort(portIndex))
+ port = &portGroups_->port(portIndex);
+
+ portGroups_->getDeviceGroupModel()->setPort(port);
+ portGroups_->getDeviceModel()->setPort(port);
+ currentPortIndex_ = portIndex;
+
+ updateDeviceViewActions();
+}
+
+void DevicesWidget::updateDeviceViewActions()
+{
+ QItemSelectionModel *devSel = deviceGroupList->selectionModel();
+
+ if (!portGroups_)
+ return;
+
+ // For some reason hasSelection() returns true even if selection size is 0
+ // so additional check for size introduced
+ if (devSel->hasSelection() && (devSel->selection().size() > 0)) {
+ // If more than one non-contiguous ranges selected,
+ // disable "New" and "Edit"
+ if (devSel->selection().size() > 1) {
+ actionNewDeviceGroup->setDisabled(true);
+ actionEditDeviceGroup->setDisabled(true);
+ }
+ else {
+ actionNewDeviceGroup->setEnabled(true);
+
+ // Enable "Edit" only if the single range has a single row
+ if (devSel->selection().at(0).height() > 1)
+ actionEditDeviceGroup->setDisabled(true);
+ else
+ actionEditDeviceGroup->setEnabled(true);
+ }
+
+ // Delete is always enabled as long as we have a selection
+ actionDeleteDeviceGroup->setEnabled(true);
+ }
+ else {
+ qDebug("No device selection");
+ if (portGroups_->isPort(currentPortIndex_))
+ actionNewDeviceGroup->setEnabled(true);
+ else
+ actionNewDeviceGroup->setDisabled(true);
+ actionEditDeviceGroup->setDisabled(true);
+ actionDeleteDeviceGroup->setDisabled(true);
+ }
+}
+
+//
+// DeviceGroup slots
+//
+
+void DevicesWidget::on_deviceInfo_toggled(bool checked)
+{
+ refresh->setVisible(checked);
+ deviceGroupList->setHidden(checked);
+ deviceList->setVisible(checked);
+ deviceDetail->hide();
+}
+
+void DevicesWidget::on_actionNewDeviceGroup_triggered()
+{
+ // In case nothing is selected, insert 1 row at the top
+ int row = 0, count = 1;
+ QItemSelection selection = deviceGroupList->selectionModel()->selection();
+
+ if (!portGroups_)
+ return;
+
+ // In case we have a single range selected; insert as many rows as
+ // in the singe selected range before the top of the selected range
+ if (selection.size() == 1) {
+ row = selection.at(0).top();
+ count = selection.at(0).height();
+ }
+
+ portGroups_->getDeviceGroupModel()->insertRows(row, count);
+}
+
+void DevicesWidget::on_actionDeleteDeviceGroup_triggered()
+{
+ QModelIndex index;
+
+ if (!portGroups_)
+ return;
+
+ if (deviceGroupList->selectionModel()->hasSelection()) {
+ while(deviceGroupList->selectionModel()->selectedRows().size()) {
+ index = deviceGroupList->selectionModel()->selectedRows().at(0);
+ portGroups_->getDeviceGroupModel()->removeRows(index.row(), 1);
+ }
+ }
+}
+
+void DevicesWidget::on_actionEditDeviceGroup_triggered()
+{
+ QItemSelection selection = deviceGroupList->selectionModel()->selection();
+
+ // Ensure we have only one range selected which contains only one row
+ if ((selection.size() == 1) && (selection.at(0).height() == 1))
+ on_deviceGroupList_activated(selection.at(0).topLeft());
+}
+
+void DevicesWidget::on_deviceGroupList_activated(const QModelIndex &index)
+{
+ if (!index.isValid() || !portGroups_)
+ return;
+
+ DeviceGroupDialog dgd(&portGroups_->port(currentPortIndex_), index.row());
+ dgd.exec();
+}
+
+void DevicesWidget::on_refresh_clicked()
+{
+ if (!portGroups_)
+ return;
+
+ Q_ASSERT(portGroups_->isPort(currentPortIndex_));
+ QModelIndex curPortGroup = portGroups_->getPortModel()
+ ->parent(currentPortIndex_);
+ Q_ASSERT(curPortGroup.isValid());
+ Q_ASSERT(portGroups_->isPortGroup(curPortGroup));
+
+ deviceDetail->hide();
+ portGroups_->portGroup(curPortGroup)
+ .getDeviceInfo(portGroups_->port(currentPortIndex_).id());
+}
+
+void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index)
+{
+ if (!index.isValid() || !portGroups_)
+ return;
+
+ QAbstractItemModel *detailModel = portGroups_->getDeviceModel()
+ ->detailModel(index);
+
+ deviceDetail->setModel(detailModel);
+ deviceDetail->setVisible(detailModel != NULL);
+}
diff --git a/client/deviceswidget.h b/client/deviceswidget.h
new file mode 100644
index 0000000..d5ef524
--- /dev/null
+++ b/client/deviceswidget.h
@@ -0,0 +1,61 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _DEVICES_WIDGET_H
+#define _DEVICES_WIDGET_H
+
+#include "ui_deviceswidget.h"
+#include
+
+class PortGroupList;
+
+class DevicesWidget: public QWidget, private Ui::DevicesWidget
+{
+ Q_OBJECT
+
+public:
+ DevicesWidget(QWidget *parent = NULL);
+ void setPortGroupList(PortGroupList *portGroups);
+
+ virtual void keyPressEvent(QKeyEvent *event);
+
+public slots:
+ void setCurrentPortIndex(const QModelIndex &portIndex);
+
+private slots:
+ void updateDeviceViewActions();
+
+ void on_deviceInfo_toggled(bool checked);
+
+ void on_actionNewDeviceGroup_triggered();
+ void on_actionDeleteDeviceGroup_triggered();
+ void on_actionEditDeviceGroup_triggered();
+ void on_deviceGroupList_activated(const QModelIndex &index);
+
+ void on_refresh_clicked();
+
+ void when_deviceList_currentChanged(const QModelIndex &index);
+
+private:
+ PortGroupList *portGroups_;
+ QModelIndex currentPortIndex_;
+};
+
+#endif
+
diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui
new file mode 100644
index 0000000..898dec0
--- /dev/null
+++ b/client/deviceswidget.ui
@@ -0,0 +1,138 @@
+
+ DevicesWidget
+
+
+
+ 0
+ 0
+ 675
+ 328
+
+
+
+ Form
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
-
+
+
+ Configuration
+
+
+ true
+
+
+
+ -
+
+
+ Information
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 131
+ 23
+
+
+
+
+ -
+
+
+ Refresh
+
+
+
+
+
+ -
+
+
+ Qt::ActionsContextMenu
+
+
+ QFrame::StyledPanel
+
+
+ 1
+
+
+ QAbstractItemView::ExtendedSelection
+
+
+ QAbstractItemView::SelectRows
+
+
+
+ -
+
+
+
+ 0
+ 1
+
+
+
+ QAbstractItemView::SelectRows
+
+
+
+ -
+
+
+ QAbstractItemView::SingleSelection
+
+
+
+
+
+
+ :/icons/devicegroup_add.png
+
+
+ New Device Group
+
+
+
+
+ :/icons/devicegroup_delete.png
+
+
+ Delete Device Group
+
+
+
+
+ :/icons/devicegroup_edit.png
+
+
+ Edit Device Group
+
+
+
+
+
+
+
+
diff --git a/client/icons/devicegroup_add.png b/client/icons/devicegroup_add.png
new file mode 100644
index 0000000..fac186b
Binary files /dev/null and b/client/icons/devicegroup_add.png differ
diff --git a/client/icons/devicegroup_delete.png b/client/icons/devicegroup_delete.png
new file mode 100644
index 0000000..3a8c373
Binary files /dev/null and b/client/icons/devicegroup_delete.png differ
diff --git a/client/icons/devicegroup_edit.png b/client/icons/devicegroup_edit.png
new file mode 100644
index 0000000..eb06df3
Binary files /dev/null and b/client/icons/devicegroup_edit.png differ
diff --git a/client/icons/neighbor_clear.png b/client/icons/neighbor_clear.png
new file mode 100644
index 0000000..d9eefc2
Binary files /dev/null and b/client/icons/neighbor_clear.png differ
diff --git a/client/icons/neighbor_resolve.png b/client/icons/neighbor_resolve.png
new file mode 100644
index 0000000..ff803be
Binary files /dev/null and b/client/icons/neighbor_resolve.png differ
diff --git a/client/main.cpp b/client/main.cpp
index 65864f0..34f689e 100644
--- a/client/main.cpp
+++ b/client/main.cpp
@@ -25,8 +25,10 @@ along with this program. If not, see
#include "settings.h"
#include
+#include
#include
#include
+#include
#include
@@ -68,6 +70,7 @@ int main(int argc, char* argv[])
appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString());
Preferences::initDefaults();
+ qsrand(QDateTime::currentDateTime().toTime_t());
mainWindow = new MainWindow;
mainWindow->show();
diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp
index 2f459aa..b7032d2 100644
--- a/client/mainwindow.cpp
+++ b/client/mainwindow.cpp
@@ -27,12 +27,18 @@ along with this program. If not, see
#include "portstatswindow.h"
#include "portswindow.h"
#include "preferences.h"
+#include "sessionfileformat.h"
#include "settings.h"
#include "ui_about.h"
#include "updater.h"
+#include "fileformat.pb.h"
+
#include
+#include
+#include
#include
+#include
extern const char* version;
extern const char* revision;
@@ -75,7 +81,7 @@ MainWindow::MainWindow(QWidget *parent)
setupUi(this);
- menuFile->insertActions(menuFile->actions().at(0), portsWindow->actions());
+ menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions());
statsDock->setWidget(statsWindow);
addDockWidget(Qt::BottomDockWidgetArea, statsDock);
@@ -133,6 +139,102 @@ MainWindow::~MainWindow()
delete localServer_;
}
+void MainWindow::on_actionOpenSession_triggered()
+{
+ qDebug("Open Session Action");
+
+ static QString dirName;
+ QString fileName;
+ QStringList fileTypes = SessionFileFormat::supportedFileTypes(
+ SessionFileFormat::kOpenFile);
+ QString fileType;
+ QString errorStr;
+ bool ret;
+
+ if (portsWindow->portGroupCount()) {
+ if (QMessageBox::question(this,
+ tr("Open Session"),
+ tr("Existing session will be lost. Proceed?"),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) == QMessageBox::No)
+ goto _exit;
+ }
+
+ if (fileTypes.size())
+ fileType = fileTypes.at(0);
+
+ fileName = QFileDialog::getOpenFileName(this, tr("Open Session"),
+ dirName, fileTypes.join(";;"), &fileType);
+ if (fileName.isEmpty())
+ goto _exit;
+
+ ret = openSession(fileName, errorStr);
+ if (!ret || !errorStr.isEmpty()) {
+ QMessageBox msgBox(this);
+ QStringList str = errorStr.split("\n\n\n\n");
+
+ msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical);
+ msgBox.setWindowTitle(qApp->applicationName());
+ msgBox.setText(str.at(0));
+ if (str.size() > 1)
+ msgBox.setDetailedText(str.at(1));
+ msgBox.setStandardButtons(QMessageBox::Ok);
+
+ msgBox.exec();
+ }
+ dirName = QFileInfo(fileName).absolutePath();
+
+_exit:
+ return;
+}
+
+void MainWindow::on_actionSaveSession_triggered()
+{
+ qDebug("Save Session Action");
+
+ static QString fileName;
+ QStringList fileTypes = SessionFileFormat::supportedFileTypes(
+ SessionFileFormat::kSaveFile);
+ QString fileType;
+ QString errorStr;
+ QFileDialog::Options options;
+
+ if (portsWindow->reservedPortCount()) {
+ QString myself = appSettings->value(kUserKey, kUserDefaultValue)
+ .toString();
+ if (QMessageBox::question(this,
+ tr("Save Session"),
+ QString("Some ports are reserved!\n\nOnly ports reserved by %1 will be saved. Proceed?").arg(myself),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) == QMessageBox::No)
+ goto _exit;
+ }
+
+ // On Mac OS with Native Dialog, getSaveFileName() ignores fileType.
+ // Although currently there's only one supported file type, we may
+ // have more in the future
+#if defined(Q_OS_MAC)
+ options |= QFileDialog::DontUseNativeDialog;
+#endif
+
+ if (fileTypes.size())
+ fileType = fileTypes.at(0);
+
+ fileName = QFileDialog::getSaveFileName(this, tr("Save Session"),
+ fileName, fileTypes.join(";;"), &fileType, options);
+ if (fileName.isEmpty())
+ goto _exit;
+
+ if (!saveSession(fileName, fileType, errorStr))
+ QMessageBox::critical(this, qApp->applicationName(), errorStr);
+ else if (!errorStr.isEmpty())
+ QMessageBox::warning(this, qApp->applicationName(), errorStr);
+
+ fileName = QFileInfo(fileName).absolutePath();
+_exit:
+ return;
+}
+
void MainWindow::on_actionPreferences_triggered()
{
Preferences *preferences = new Preferences();
@@ -172,3 +274,120 @@ void MainWindow::onNewVersion(QString newVersion)
statusBar()->showMessage(QString("New Ostinato version %1 available. "
"Visit http://ostinato.org to download").arg(newVersion));
}
+
+//! Returns true on success (or user cancel) and false on failure
+bool MainWindow::openSession(QString fileName, QString &error)
+{
+ bool ret = false;
+ QDialog *optDialog;
+ QProgressDialog progress("Opening Session", "Cancel", 0, 0, this);
+ OstProto::SessionContent session;
+ SessionFileFormat *fmt = SessionFileFormat::fileFormatFromFile(fileName);
+
+ if (fmt == NULL) {
+ error = tr("Unknown session file format");
+ goto _fail;
+ }
+
+ if ((optDialog = fmt->openOptionsDialog()))
+ {
+ int ret;
+ optDialog->setParent(this, Qt::Dialog);
+ ret = optDialog->exec();
+ optDialog->setParent(0, Qt::Dialog);
+ if (ret == QDialog::Rejected)
+ goto _user_opt_cancel;
+ }
+
+ progress.setAutoReset(false);
+ progress.setAutoClose(false);
+ progress.setMinimumDuration(0);
+ progress.show();
+
+ setDisabled(true);
+ progress.setEnabled(true); // to override the mainWindow disable
+
+ connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString)));
+ connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int)));
+ connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int)));
+ connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel()));
+
+ fmt->openAsync(fileName, session, error);
+ qDebug("after open async");
+
+ while (!fmt->isFinished())
+ qApp->processEvents();
+ qDebug("wait over for async operation");
+
+ if (!fmt->result())
+ goto _fail;
+
+ // process any remaining events posted from the thread
+ for (int i = 0; i < 10; i++)
+ qApp->processEvents();
+
+ // XXX: user can't cancel operation from here on!
+ progress.close();
+
+ portsWindow->openSession(&session, error);
+
+_user_opt_cancel:
+ ret = true;
+
+_fail:
+ progress.close();
+ setEnabled(true);
+ return ret;
+}
+
+bool MainWindow::saveSession(QString fileName, QString fileType, QString &error)
+{
+ bool ret = false;
+ QProgressDialog progress("Saving Session", "Cancel", 0, 0, this);
+ SessionFileFormat *fmt = SessionFileFormat::fileFormatFromType(fileType);
+ OstProto::SessionContent session;
+
+ if (fmt == NULL)
+ goto _fail;
+
+ progress.setAutoReset(false);
+ progress.setAutoClose(false);
+ progress.setMinimumDuration(0);
+ progress.show();
+
+ setDisabled(true);
+ progress.setEnabled(true); // to override the mainWindow disable
+
+ // Fill in session
+ ret = portsWindow->saveSession(&session, error, &progress);
+ if (!ret)
+ goto _user_cancel;
+
+ connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString)));
+ connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int)));
+ connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int)));
+ connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel()));
+
+ fmt->saveAsync(session, fileName, error);
+ qDebug("after save async");
+
+ while (!fmt->isFinished())
+ qApp->processEvents();
+ qDebug("wait over for async operation");
+
+ ret = fmt->result();
+ goto _exit;
+
+_user_cancel:
+ goto _exit;
+
+_fail:
+ error = QString("Unsupported File Type - %1").arg(fileType);
+ goto _exit;
+
+_exit:
+ progress.close();
+ setEnabled(true);
+ return ret;
+}
+
diff --git a/client/mainwindow.h b/client/mainwindow.h
index e05ab54..fa575f3 100644
--- a/client/mainwindow.h
+++ b/client/mainwindow.h
@@ -34,6 +34,9 @@ class MainWindow : public QMainWindow, private Ui::MainWindow
Q_OBJECT
private:
+ bool openSession(QString fileName, QString &error);
+ bool saveSession(QString fileName, QString fileType, QString &error);
+
QProcess *localServer_;
PortsWindow *portsWindow;
PortStatsWindow *statsWindow;
@@ -48,6 +51,8 @@ public:
~MainWindow();
public slots:
+ void on_actionOpenSession_triggered();
+ void on_actionSaveSession_triggered();
void on_actionPreferences_triggered();
void on_actionViewRestoreDefaults_triggered();
void on_actionHelpAbout_triggered();
diff --git a/client/mainwindow.ui b/client/mainwindow.ui
index 82dfc93..6e79e3d 100644
--- a/client/mainwindow.ui
+++ b/client/mainwindow.ui
@@ -5,8 +5,8 @@
0
0
- 700
- 550
+ 1024
+ 600
@@ -17,18 +17,12 @@
+ -
+
+
+ Resolve Neighbors
+
+
+ Resolve Device Neighbors on selected port(s)
+
+
+ Resolve Neighbors
+
+
+ :/icons/neighbor_resolve.png
+
+
+
+ -
+
+
+ Clear Neighbors
+
+
+ Clear Device Neighbors on selected port(s)
+
+
+ Clear Neighbors
+
+
+ :/icons/neighbor_clear.png
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
-
diff --git a/client/portswindow.cpp b/client/portswindow.cpp
index 21668f2..4c85150 100644
--- a/client/portswindow.cpp
+++ b/client/portswindow.cpp
@@ -19,18 +19,24 @@ along with this program. If not, see
#include "portswindow.h"
-#include "abstractfileformat.h"
+#include "deviceswidget.h"
#include "portconfigdialog.h"
#include "settings.h"
#include "streamconfigdialog.h"
+#include "streamfileformat.h"
#include "streamlistdelegate.h"
+#include "fileformat.pb.h"
+
#include
#include
#include
+#include
#include
#include
+extern QMainWindow *mainWindow;
+
PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
: QWidget(parent), proxyPortModel(NULL)
{
@@ -44,6 +50,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
plm = pgl;
setupUi(this);
+ devicesWidget->setPortGroupList(plm);
tvPortList->header()->hide();
@@ -61,7 +68,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvPortList->addAction(actionExclusive_Control);
tvPortList->addAction(actionPort_Configuration);
- // Populate StramList Context Menu Actions
+ // Populate StreamList Context Menu Actions
tvStreamList->addAction(actionNew_Stream);
tvStreamList->addAction(actionEdit_Stream);
tvStreamList->addAction(actionDuplicate_Stream);
@@ -74,12 +81,17 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->addAction(actionOpen_Streams);
tvStreamList->addAction(actionSave_Streams);
- // PortList and StreamList actions combined make this window's actions
+ // PortList, StreamList, DeviceWidget actions combined
+ // make this window's actions
addActions(tvPortList->actions());
sep = new QAction(this);
sep->setSeparator(true);
addAction(sep);
addActions(tvStreamList->actions());
+ sep = new QAction(this);
+ sep->setSeparator(true);
+ addAction(sep);
+ addActions(devicesWidget->actions());
tvStreamList->setModel(plm->getStreamModel());
@@ -112,6 +124,9 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(when_portView_currentChanged(const QModelIndex&,
const QModelIndex&)));
+ connect(this,
+ SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
+ devicesWidget, SLOT(setCurrentPortIndex(const QModelIndex&)));
connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateStreamViewActions()));
@@ -128,7 +143,8 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->resizeColumnToContents(StreamModel::StreamIcon);
tvStreamList->resizeColumnToContents(StreamModel::StreamStatus);
- // Initially we don't have any ports/streams - so send signal triggers
+ // Initially we don't have any ports/streams/devices
+ // - so send signal triggers
when_portView_currentChanged(QModelIndex(), QModelIndex());
updateStreamViewActions();
@@ -157,6 +173,116 @@ PortsWindow::~PortsWindow()
delete proxyPortModel;
}
+int PortsWindow::portGroupCount()
+{
+ return plm->numPortGroups();
+}
+
+int PortsWindow::reservedPortCount()
+{
+ int count = 0;
+ int n = portGroupCount();
+
+ for (int i = 0; i < n; i++)
+ count += plm->portGroupByIndex(i).numReservedPorts();
+
+ return count;
+}
+
+//! Always return true
+bool PortsWindow::openSession(
+ const OstProto::SessionContent *session,
+ QString &error)
+{
+ QProgressDialog progress("Opening Session", NULL,
+ 0, session->port_groups_size(), mainWindow);
+ progress.show();
+ progress.setEnabled(true); // since parent (mainWindow) is disabled
+
+ plm->removeAllPortGroups();
+
+ for (int i = 0; i < session->port_groups_size(); i++) {
+ const OstProto::PortGroupContent &pgc = session->port_groups(i);
+ PortGroup *pg = new PortGroup(QString::fromStdString(
+ pgc.server_name()),
+ quint16(pgc.server_port()));
+ pg->setConfigAtConnect(&pgc);
+ plm->addPortGroup(*pg);
+ progress.setValue(i+1);
+ }
+
+ return true;
+}
+
+/*!
+ * Prepare content to be saved for a session
+ *
+ * If port reservation is in use, saves only 'my' reserved ports
+ *
+ * Returns false, if user cancels op; true, otherwise
+ */
+bool PortsWindow::saveSession(
+ OstProto::SessionContent *session, // OUT param
+ QString &error,
+ QProgressDialog *progress)
+{
+ int n = portGroupCount();
+ QString myself;
+
+ if (progress) {
+ progress->setLabelText("Preparing Ports and PortGroups ...");
+ progress->setRange(0, n);
+ }
+
+ if (reservedPortCount())
+ myself = appSettings->value(kUserKey, kUserDefaultValue).toString();
+
+ for (int i = 0; i < n; i++)
+ {
+ PortGroup &pg = plm->portGroupByIndex(i);
+ OstProto::PortGroupContent *pgc = session->add_port_groups();
+
+ pgc->set_server_name(pg.serverName().toStdString());
+ pgc->set_server_port(pg.serverPort());
+
+ for (int j = 0; j < pg.numPorts(); j++)
+ {
+ if (myself != pg.mPorts.at(j)->userName())
+ continue;
+
+ OstProto::PortContent *pc = pgc->add_ports();
+ OstProto::Port *p = pc->mutable_port_config();
+
+ // XXX: We save the entire OstProto::Port even though some
+ // fields may be ephemeral; while opening we use only relevant
+ // fields
+ pg.mPorts.at(j)->protoDataCopyInto(p);
+
+ for (int k = 0; k < pg.mPorts.at(j)->numStreams(); k++)
+ {
+ OstProto::Stream *s = pc->add_streams();
+ pg.mPorts.at(j)->streamByIndex(k)->protoDataCopyInto(*s);
+ }
+
+ for (int k = 0; k < pg.mPorts.at(j)->numDeviceGroups(); k++)
+ {
+ OstProto::DeviceGroup *dg = pc->add_device_groups();
+ dg->CopyFrom(*(pg.mPorts.at(j)->deviceGroupByIndex(k)));
+ }
+ }
+
+ if (progress) {
+ if (progress->wasCanceled())
+ return false;
+ progress->setValue(i);
+ }
+ if (i % 2 == 0)
+ qApp->processEvents();
+ }
+
+ return true;
+}
+
void PortsWindow::showMyReservedPortsOnly(bool enabled)
{
if (!proxyPortModel)
@@ -240,6 +366,8 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
SLOT(updatePortRates()));
}
}
+
+ emit currentPortChanged(current, previous);
}
void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft,
@@ -684,6 +812,9 @@ void PortsWindow::on_actionOpen_Streams_triggered()
{
qDebug("Open Streams Action");
+ QStringList fileTypes = StreamFileFormat::supportedFileTypes(
+ StreamFileFormat::kOpenFile);
+ QString fileType;
QModelIndex current = tvPortList->selectionModel()->currentIndex();
static QString dirName;
QString fileName;
@@ -696,7 +827,11 @@ void PortsWindow::on_actionOpen_Streams_triggered()
Q_ASSERT(plm->isPort(current));
- fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"), dirName);
+ if (fileTypes.size())
+ fileType = fileTypes.at(0);
+
+ fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"),
+ dirName, fileTypes.join(";;"), &fileType);
if (fileName.isEmpty())
goto _exit;
@@ -750,7 +885,8 @@ void PortsWindow::on_actionSave_Streams_triggered()
QModelIndex current = tvPortList->selectionModel()->currentIndex();
static QString fileName;
- QStringList fileTypes = AbstractFileFormat::supportedFileTypes();
+ QStringList fileTypes = StreamFileFormat::supportedFileTypes(
+ StreamFileFormat::kSaveFile);
QString fileType;
QString errorStr;
QFileDialog::Options options;
diff --git a/client/portswindow.h b/client/portswindow.h
index 99f81e9..b407270 100644
--- a/client/portswindow.h
+++ b/client/portswindow.h
@@ -25,15 +25,14 @@ along with this program. If not, see
#include "ui_portswindow.h"
#include "portgrouplist.h"
-/* TODO
-HIGH
-MED
-LOW
-*/
-
class QAbstractItemDelegate;
+class QProgressDialog;
class QSortFilterProxyModel;
+namespace OstProto {
+ class SessionContent;
+}
+
class PortsWindow : public QWidget, private Ui::PortsWindow
{
Q_OBJECT
@@ -45,6 +44,19 @@ public:
PortsWindow(PortGroupList *pgl, QWidget *parent = 0);
~PortsWindow();
+ int portGroupCount();
+ int reservedPortCount();
+
+ bool openSession(const OstProto::SessionContent *session,
+ QString &error);
+ bool saveSession(OstProto::SessionContent *session,
+ QString &error,
+ QProgressDialog *progress = NULL);
+
+signals:
+ void currentPortChanged(const QModelIndex ¤t,
+ const QModelIndex &previous);
+
private:
QString lastNewPortGroup;
QAbstractItemDelegate *delegate;
diff --git a/client/portswindow.ui b/client/portswindow.ui
index d3a2c55..cbab508 100644
--- a/client/portswindow.ui
+++ b/client/portswindow.ui
@@ -5,7 +5,7 @@
0
0
- 710
+ 663
352
@@ -22,6 +22,12 @@
false
+
+
+ 1
+ 0
+
+
Qt::ActionsContextMenu
@@ -30,11 +36,17 @@
+
+
+ 2
+ 0
+
+
0
-
+
0
@@ -47,115 +59,141 @@
0
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- QFrame::StyledPanel
-
-
- QFrame::Sunken
-
-
-
-
-
-
- Avg pps
-
-
- true
-
-
-
- -
-
-
- -
-
-
- Avg bps
-
-
-
- -
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
- -
+
-
+
+
+ QFrame::Panel
+
+
+ QFrame::Raised
+
+
+
+ 3
+
+
+ 3
+
+
+ 3
+
+
+ 3
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Apply
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
+
+ Streams
+
-
-
-
- Apply
+
+
-
+
+
+ Avg pps
+
+
+ true
+
+
+
+ -
+
+
+ -
+
+
+ Avg bps
+
+
+
+ -
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 1
+
+
+
+ Qt::ActionsContextMenu
+
+
+ QFrame::StyledPanel
+
+
+ 1
+
+
+ QAbstractItemView::ExtendedSelection
+
+
+ QAbstractItemView::SelectRows
+
+
+
+
+ Devices
+
+
-
-
-
- Qt::Vertical
-
-
-
- 20
- 0
-
-
-
+
-
-
-
- -
-
-
-
- 0
- 1
-
-
-
- Qt::ActionsContextMenu
-
-
- QFrame::StyledPanel
-
-
- 1
-
-
- QAbstractItemView::ExtendedSelection
-
-
- QAbstractItemView::SelectRows
-
+
@@ -267,6 +305,14 @@
+
+
+ DevicesWidget
+ QWidget
+
+ 1
+
+
@@ -278,12 +324,12 @@
setEnabled(bool)
- 313
- 28
+ 326
+ 80
- 380
- 28
+ 454
+ 79
@@ -294,12 +340,12 @@
setEnabled(bool)
- 333
- 55
+ 523
+ 80
- 395
- 56
+ 651
+ 88
diff --git a/client/stream.cpp b/client/stream.cpp
index 2305930..6bc9ab5 100644
--- a/client/stream.cpp
+++ b/client/stream.cpp
@@ -49,3 +49,13 @@ void Stream::storeProtocolWidgets()
qWarning("%s: DOES NOTHING", __PRETTY_FUNCTION__);
return;
}
+
+quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex)
+{
+ return 0;
+}
+
+quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex)
+{
+ return 0;
+}
diff --git a/common/emulation.h b/common/emulation.h
new file mode 100644
index 0000000..919849d
--- /dev/null
+++ b/common/emulation.h
@@ -0,0 +1,55 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _EMULATION_H
+#define _EMULATION_H
+
+#include "emulproto.pb.h"
+
+#include
+
+static inline OstProto::DeviceGroup* newDeviceGroup(uint portId)
+{
+ OstProto::DeviceGroup *devGrp = new OstProto::DeviceGroup;
+
+ // To ensure that DeviceGroup have a unique key, we assign
+ // a random mac address upon creation; ideally, it would
+ // have been good to inherit OstProto::DeviceGroup and define
+ // a new constructor, but ProtoBuf forbids against inheriting
+ // generated classes, so we use this helper function instead
+ //
+ // Create a mac address as per RFC 4814 Sec 4.2
+ // (RR & 0xFC):PP:PP:RR:RR:RR
+ // where RR is a random number, PP:PP is 1-indexed port index
+ // NOTE: although qrand() return type is a int, the max value
+ // is RAND_MAX (stdlib.h) which is often 16-bit only, so we
+ // use two random numbers
+ quint32 r1 = qrand(), r2 = qrand();
+ quint64 mac;
+ mac = quint64(r1 & 0xfc00) << 32
+ | quint64(portId + 1) << 24
+ | quint64((r1 & 0xff) << 16 | (r2 & 0xffff));
+ devGrp->MutableExtension(OstEmul::mac)->set_address(mac);
+
+ return devGrp;
+}
+
+
+#endif
+
diff --git a/common/emulproto.proto b/common/emulproto.proto
new file mode 100644
index 0000000..8c34d9b
--- /dev/null
+++ b/common/emulproto.proto
@@ -0,0 +1,122 @@
+/*
+Copyright (C) 2015 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 "protocol.proto";
+
+package OstEmul;
+
+// =======
+// Encap
+// =======
+message VlanEmulation {
+ message Vlan {
+ optional uint32 tpid = 1 [default = 0x8100];
+
+ // includes prio, cfi and vlanid
+ optional uint32 vlan_tag = 2 [default = 100];
+
+ optional uint32 count = 10 [default = 1];
+ optional uint32 step = 11 [default = 1];
+ }
+
+ repeated Vlan stack = 1; // outer to inner
+}
+
+extend OstProto.EncapEmulation {
+ optional VlanEmulation vlan = 1000;
+}
+
+// ===========
+// Protocols
+// ===========
+message MacEmulation {
+ optional uint64 address = 1; // no default - need unique value
+ optional uint64 step = 10 [default = 1];
+}
+
+// No default values for IP addresses - user needs to explicitly set that
+// 'coz we derive if a device has a single/dual or no IP stack on the basis
+// of whether OstProto.DeviceGroup.ip[46] is set
+message Ip4Emulation {
+ optional uint32 address = 1;
+ optional uint32 prefix_length = 2 [default = 24];
+ optional uint32 default_gateway = 3;
+
+ optional uint32 step = 10 [default = 1];
+ // FIXME: step for gateway?
+}
+
+message Ip6Address {
+ optional uint64 hi = 1;
+ optional uint64 lo = 2;
+}
+
+message Ip6Emulation {
+ optional Ip6Address address = 1;
+ optional uint32 prefix_length = 2 [default = 64];
+ optional Ip6Address default_gateway = 3;
+
+ optional Ip6Address step = 10;
+ // FIXME: step for gateway?
+}
+
+extend OstProto.DeviceGroup {
+ optional MacEmulation mac = 2001;
+
+ optional Ip4Emulation ip4 = 3000;
+ optional Ip6Emulation ip6 = 3001;
+}
+
+message Device {
+ optional uint64 mac = 1;
+
+ repeated uint32 vlan = 2; // includes tpid 'n vlan tag
+
+ optional uint32 ip4 = 10;
+ optional uint32 ip4_prefix_length = 11;
+ optional uint32 ip4_default_gateway = 12;
+
+ optional Ip6Address ip6 = 20;
+ optional uint32 ip6_prefix_length = 21;
+ optional Ip6Address ip6_default_gateway = 22;
+}
+
+extend OstProto.PortDeviceList {
+ repeated Device device = 100;
+}
+
+message ArpEntry {
+ optional uint32 ip4 = 1;
+ optional uint64 mac = 2;
+}
+
+message NdpEntry {
+ optional Ip6Address ip6 = 1;
+ optional uint64 mac = 2;
+}
+
+message DeviceNeighborList {
+ optional uint32 device_index = 1;
+ repeated ArpEntry arp = 2;
+ repeated NdpEntry ndp = 3;
+}
+
+extend OstProto.PortNeighborList {
+ repeated DeviceNeighborList device_neighbor = 100;
+}
diff --git a/common/fileformat.proto b/common/fileformat.proto
index ce2a688..10339d7 100644
--- a/common/fileformat.proto
+++ b/common/fileformat.proto
@@ -24,6 +24,7 @@ package OstProto;
enum FileType {
kReservedFileType = 0;
kStreamsFileType = 1;
+ kSessionFileType = 10;
}
message FileMetaData {
@@ -36,8 +37,30 @@ message FileMetaData {
required string generator_revision = 7;
}
+message PortContent {
+ optional Port port_config = 1;
+ repeated Stream streams = 2;
+ repeated DeviceGroup device_groups = 3;
+}
+
+message PortGroupContent {
+ optional string server_name = 1;
+ optional uint32 server_port = 2;
+
+ repeated PortContent ports = 15;
+}
+
+message SessionContent {
+ repeated PortGroupContent port_groups = 1;
+}
+
message FileContentMatter {
optional StreamConfigList streams = 1;
+ // TODO: optional DeviceGroupConfigList device_groups = 2;
+ // TODO: optional PortContent port = 3;
+ // FIXME: (single) portgroup? is there a usecase for this?
+
+ optional SessionContent session = 10;
}
/*
diff --git a/common/intedit.h b/common/intedit.h
new file mode 100644
index 0000000..e5392e8
--- /dev/null
+++ b/common/intedit.h
@@ -0,0 +1,41 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _INT_EDIT_H
+#define _INT_EDIT_H
+
+#include
+
+#include
+
+class IntEdit: public QSpinBox
+{
+public:
+ IntEdit(QWidget *parent = 0);
+};
+
+inline IntEdit::IntEdit(QWidget *parent)
+ : QSpinBox(parent)
+{
+ setRange(INT_MIN, INT_MAX);
+ setButtonSymbols(QAbstractSpinBox::NoButtons);
+}
+
+#endif
+
diff --git a/common/ip4edit.h b/common/ip4edit.h
new file mode 100644
index 0000000..1efc717
--- /dev/null
+++ b/common/ip4edit.h
@@ -0,0 +1,52 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _IP4_EDIT_H
+#define _IP4_EDIT_H
+
+#include
+#include
+
+class Ip4Edit: public QLineEdit
+{
+public:
+ Ip4Edit(QWidget *parent = 0);
+
+ quint32 value();
+ void setValue(quint32 val);
+};
+
+inline Ip4Edit::Ip4Edit(QWidget *parent)
+ : QLineEdit(parent)
+{
+ setInputMask(QString("000.000.000.000; "));
+}
+
+inline quint32 Ip4Edit::value()
+{
+ return QHostAddress(text()).toIPv4Address();
+}
+
+inline void Ip4Edit::setValue(quint32 val)
+{
+ setText(QHostAddress(val).toString());
+}
+
+#endif
+
diff --git a/common/ip6edit.h b/common/ip6edit.h
new file mode 100644
index 0000000..891461b
--- /dev/null
+++ b/common/ip6edit.h
@@ -0,0 +1,79 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _IP6_EDIT_H
+#define _IP6_EDIT_H
+
+#include "ipv6addressvalidator.h"
+#include "uint128.h"
+
+#include
+#include
+
+class Ip6Edit: public QLineEdit
+{
+public:
+ Ip6Edit(QWidget *parent = 0);
+
+ UInt128 value();
+ quint64 valueHi64();
+ quint64 valueLo64();
+ void setValue(UInt128 val);
+ void setValue(quint64 hi, quint64 lo);
+ void setValue(const QString &val);
+};
+
+inline Ip6Edit::Ip6Edit(QWidget *parent)
+ : QLineEdit(parent)
+{
+ setValidator(new IPv6AddressValidator(this));
+}
+
+inline UInt128 Ip6Edit::value()
+{
+ Q_IPV6ADDR addr = QHostAddress(text()).toIPv6Address();
+ return UInt128((quint8*)&addr);
+}
+
+inline quint64 Ip6Edit::valueHi64()
+{
+ return value().hi64();
+}
+
+inline quint64 Ip6Edit::valueLo64()
+{
+ return value().lo64();
+}
+
+inline void Ip6Edit::setValue(UInt128 val)
+{
+ setText(QHostAddress(val.toArray()).toString());
+}
+
+inline void Ip6Edit::setValue(quint64 hi, quint64 lo)
+{
+ UInt128 ip(hi, lo);
+ setValue(ip);
+}
+
+inline void Ip6Edit::setValue(const QString &val)
+{
+ setText(QHostAddress(val).toString());
+}
+#endif
diff --git a/common/mac.cpp b/common/mac.cpp
index cfe35a6..3a7df21 100644
--- a/common/mac.cpp
+++ b/common/mac.cpp
@@ -19,6 +19,8 @@ along with this program. If not, see
#include "mac.h"
+#include "../common/streambase.h"
+
#include
#define uintToMacStr(num) \
@@ -28,6 +30,7 @@ along with this program. If not, see
MacProtocol::MacProtocol(StreamBase *stream, AbstractProtocol *parent)
: AbstractProtocol(stream, parent)
{
+ forResolve_ = false;
}
MacProtocol::~MacProtocol()
@@ -124,6 +127,15 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib,
data.dst_mac_step();
dstMac = data.dst_mac() - u;
break;
+ case OstProto::Mac::e_mm_resolve:
+ if (forResolve_)
+ dstMac = 0;
+ else {
+ forResolve_ = true;
+ dstMac = mpStream->neighborMacAddress(streamIndex);
+ forResolve_ = false;
+ }
+ break;
default:
qWarning("Unhandled dstMac_mode %d", data.dst_mac_mode());
}
@@ -169,6 +181,15 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib,
data.src_mac_step();
srcMac = data.src_mac() - u;
break;
+ case OstProto::Mac::e_mm_resolve:
+ if (forResolve_)
+ srcMac = 0;
+ else {
+ forResolve_ = true;
+ srcMac = mpStream->deviceMacAddress(streamIndex);
+ forResolve_ = false;
+ }
+ break;
default:
qWarning("Unhandled srcMac_mode %d", data.src_mac_mode());
}
@@ -331,11 +352,23 @@ int MacProtocol::protocolFrameVariableCount() const
{
int count = AbstractProtocol::protocolFrameVariableCount();
- if (data.dst_mac_mode() != OstProto::Mac::e_mm_fixed)
- count = AbstractProtocol::lcm(count, data.dst_mac_count());
+ switch (data.dst_mac_mode()) {
+ case OstProto::Mac::e_mm_inc:
+ case OstProto::Mac::e_mm_dec:
+ count = AbstractProtocol::lcm(count, data.dst_mac_count());
+ break;
+ default:
+ break;
+ }
- if (data.src_mac_mode() != OstProto::Mac::e_mm_fixed)
- count = AbstractProtocol::lcm(count, data.src_mac_count());
+ switch (data.src_mac_mode()) {
+ case OstProto::Mac::e_mm_inc:
+ case OstProto::Mac::e_mm_dec:
+ count = AbstractProtocol::lcm(count, data.src_mac_count());
+ break;
+ default:
+ break;
+ }
return count;
}
diff --git a/common/mac.h b/common/mac.h
index 3469d40..792f325 100644
--- a/common/mac.h
+++ b/common/mac.h
@@ -67,6 +67,7 @@ public:
private:
OstProto::Mac data;
+ mutable bool forResolve_;
};
#endif
diff --git a/common/mac.proto b/common/mac.proto
index 2055223..1bd07f4 100644
--- a/common/mac.proto
+++ b/common/mac.proto
@@ -28,6 +28,7 @@ message Mac {
e_mm_fixed = 0;
e_mm_inc = 1;
e_mm_dec = 2;
+ e_mm_resolve = 3; // dst: resolve neighbor; src: from device config
}
// Dst Mac
diff --git a/common/mac.ui b/common/mac.ui
index 821cf00..f3604bb 100644
--- a/common/mac.ui
+++ b/common/mac.ui
@@ -5,8 +5,8 @@
0
0
- 391
- 116
+ 400
+ 200
@@ -81,6 +81,11 @@
Decrement
+ -
+
+ Resolve
+
+
-
@@ -143,6 +148,11 @@
Decrement
+ -
+
+ Resolve
+
+
-
@@ -168,7 +178,17 @@
- -
+
-
+
+
+ Please ensure that a corresponding device is configured on the port to enable source/destination mac address resolution. A corresponding device is one which has VLANs and source/gateway IP corresponding to this stream.
+
+
+ true
+
+
+
+ -
Qt::Vertical
diff --git a/common/macconfig.cpp b/common/macconfig.cpp
index f17c140..1e50854 100644
--- a/common/macconfig.cpp
+++ b/common/macconfig.cpp
@@ -28,6 +28,12 @@ MacConfigForm::MacConfigForm(QWidget *parent)
QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}");
setupUi(this);
+ resolveInfo->hide();
+#if 0
+ // not working for some reason
+ resolveInfo->setPixmap(resolveInfo->style()->standardIcon(
+ QStyle::SP_MessageBoxInformation).pixmap(128));
+#endif
leDstMac->setValidator(new QRegExpValidator(reMac, this));
leSrcMac->setValidator(new QRegExpValidator(reMac, this));
leDstMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this));
@@ -46,30 +52,50 @@ MacConfigForm* MacConfigForm::createInstance()
void MacConfigForm::on_cmbDstMacMode_currentIndexChanged(int index)
{
- if (index == OstProto::Mac::e_mm_fixed)
- {
- leDstMacCount->setEnabled(false);
- leDstMacStep->setEnabled(false);
- }
- else
- {
- leDstMacCount->setEnabled(true);
- leDstMacStep->setEnabled(true);
+ switch (index) {
+ case OstProto::Mac::e_mm_resolve:
+ leDstMac->setEnabled(false);
+ leDstMacCount->setEnabled(false);
+ leDstMacStep->setEnabled(false);
+ break;
+ case OstProto::Mac::e_mm_fixed:
+ leDstMac->setEnabled(true);
+ leDstMacCount->setEnabled(false);
+ leDstMacStep->setEnabled(false);
+ break;
+ default:
+ leDstMac->setEnabled(true);
+ leDstMacCount->setEnabled(true);
+ leDstMacStep->setEnabled(true);
+ break;
}
+ resolveInfo->setVisible(
+ cmbDstMacMode->currentIndex() == OstProto::Mac::e_mm_resolve
+ || cmbSrcMacMode->currentIndex() == OstProto::Mac::e_mm_resolve);
}
void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index)
{
- if (index == OstProto::Mac::e_mm_fixed)
- {
- leSrcMacCount->setEnabled(false);
- leSrcMacStep->setEnabled(false);
- }
- else
- {
- leSrcMacCount->setEnabled(true);
- leSrcMacStep->setEnabled(true);
+ switch (index) {
+ case OstProto::Mac::e_mm_resolve:
+ leSrcMac->setEnabled(false);
+ leSrcMacCount->setEnabled(false);
+ leSrcMacStep->setEnabled(false);
+ break;
+ case OstProto::Mac::e_mm_fixed:
+ leSrcMac->setEnabled(true);
+ leSrcMacCount->setEnabled(false);
+ leSrcMacStep->setEnabled(false);
+ break;
+ default:
+ leSrcMac->setEnabled(true);
+ leSrcMacCount->setEnabled(true);
+ leSrcMacStep->setEnabled(true);
+ break;
}
+ resolveInfo->setVisible(
+ cmbDstMacMode->currentIndex() == OstProto::Mac::e_mm_resolve
+ || cmbSrcMacMode->currentIndex() == OstProto::Mac::e_mm_resolve);
}
void MacConfigForm::loadWidget(AbstractProtocol *proto)
diff --git a/common/macedit.h b/common/macedit.h
new file mode 100644
index 0000000..390c6a3
--- /dev/null
+++ b/common/macedit.h
@@ -0,0 +1,54 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _MAC_EDIT_H
+#define _MAC_EDIT_H
+
+#include
+
+class MacEdit: public QLineEdit
+{
+public:
+ MacEdit(QWidget *parent = 0);
+
+ quint64 value();
+ void setValue(quint64 val);
+};
+
+inline MacEdit::MacEdit(QWidget *parent)
+ : QLineEdit(parent)
+{
+ QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}");
+
+ setValidator(new QRegExpValidator(reMac, this));
+}
+
+inline quint64 MacEdit::value()
+{
+ return text().remove(QChar(':')).toULongLong(NULL, 16);
+}
+
+inline void MacEdit::setValue(quint64 val)
+{
+ setText(QString("%1").arg(val, 6*2, 16, QChar('0'))
+ .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:").toUpper());
+}
+
+#endif
+
diff --git a/common/fileformat.cpp b/common/nativefileformat.cpp
similarity index 76%
rename from common/fileformat.cpp
rename to common/nativefileformat.cpp
index 4edd980..e2fc3eb 100644
--- a/common/fileformat.cpp
+++ b/common/nativefileformat.cpp
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2010 Srivats P.
+Copyright (C) 2010, 2016 Srivats P.
This file is part of "Ostinato"
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-#include "fileformat.h"
+#include "nativefileformat.h"
#include "crc32c.h"
@@ -25,15 +25,29 @@ along with this program. If not, see
#include
#include
-#include
+#define tr(str) QObject::tr(str)
-const std::string FileFormat::kFileMagicValue = "\xa7\xb7OSTINATO";
+const std::string NativeFileFormat::kFileMagicValue = "\xa7\xb7OSTINATO";
-FileFormat fileFormat;
+static const int kBaseHex = 16;
-const int kBaseHex = 16;
+static QString fileTypeStr(OstProto::FileType fileType)
+{
+ switch (fileType) {
+ case OstProto::kReservedFileType:
+ return QString("Reserved");
+ case OstProto::kStreamsFileType:
+ return QString("Streams");
+ case OstProto::kSessionFileType:
+ return QString("Streams");
+ default:
+ Q_ASSERT(false);
+ }
-FileFormat::FileFormat()
+ return QString("Unknown");
+}
+
+NativeFileFormat::NativeFileFormat()
{
/*
* We don't have any "real" work to do here in the constructor.
@@ -54,20 +68,18 @@ FileFormat::FileFormat()
Q_ASSERT(cksum.ByteSize() == kFileChecksumSize);
}
-FileFormat::~FileFormat()
-{
-}
-
-bool FileFormat::openStreams(const QString fileName,
- OstProto::StreamConfigList &streams, QString &error)
+bool NativeFileFormat::open(
+ const QString fileName,
+ OstProto::FileType fileType,
+ OstProto::FileMeta &meta,
+ OstProto::FileContent &content,
+ QString &error)
{
QFile file(fileName);
QByteArray buf;
int size, contentOffset, contentSize;
quint32 calcCksum;
OstProto::FileMagic magic;
- OstProto::FileMeta meta;
- OstProto::FileContent content;
OstProto::FileChecksum cksum, zeroCksum;
if (!file.open(QIODevice::ReadOnly))
@@ -95,7 +107,7 @@ bool FileFormat::openStreams(const QString fileName,
// Parse and verify magic
if (!magic.ParseFromArray(
- (void*)(buf.constData() + kFileMagicOffset),
+ (void*)(buf.constData() + kFileMagicOffset),
kFileMagicSize))
{
goto _magic_parse_fail;
@@ -105,7 +117,7 @@ bool FileFormat::openStreams(const QString fileName,
// Parse and verify checksum
if (!cksum.ParseFromArray(
- (void*)(buf.constData() + size - kFileChecksumSize),
+ (void*)(buf.constData() + size - kFileChecksumSize),
kFileChecksumSize))
{
goto _cksum_parse_fail;
@@ -118,7 +130,7 @@ bool FileFormat::openStreams(const QString fileName,
{
goto _zero_cksum_serialize_fail;
}
-
+
calcCksum = checksumCrc32C((quint8*) buf.constData(), size);
qDebug("checksum \nExpected:%x Actual:%x",
@@ -129,17 +141,18 @@ bool FileFormat::openStreams(const QString fileName,
// Parse the metadata first before we parse the full contents
if (!meta.ParseFromArray(
- (void*)(buf.constData() + kFileMetaDataOffset),
- size - kFileMetaDataOffset))
+ (void*)(buf.constData() + kFileMetaDataOffset),
+ fileMetaSize((quint8*)buf.constData(), size)))
{
goto _metadata_parse_fail;
}
- qDebug("%s: File MetaData (INFORMATION) - \n%s", __FUNCTION__,
+ qDebug("%s: File MetaData (INFORMATION) - \n%s", __FUNCTION__,
QString().fromStdString(meta.DebugString()).toAscii().constData());
+ qDebug("%s: END MetaData", __FUNCTION__);
// MetaData Validation(s)
- if (meta.data().file_type() != OstProto::kStreamsFileType)
+ if (meta.data().file_type() != fileType)
goto _unexpected_file_type;
if (meta.data().format_version_major() != kFileFormatVersionMajor)
@@ -165,34 +178,26 @@ bool FileFormat::openStreams(const QString fileName,
// ByteSize() does not include the Tag/Key, so we add 2 for that
contentOffset = kFileMetaDataOffset + meta.data().ByteSize() + 2;
contentSize = size - contentOffset - kFileChecksumSize;
+ qDebug("%s: content offset/size = %d/%d", __FUNCTION__,
+ contentOffset, contentSize);
// Parse full contents
if (!content.ParseFromArray(
- (void*)(buf.constData() + contentOffset),
+ (void*)(buf.constData() + contentOffset),
contentSize))
{
goto _content_parse_fail;
}
- if (!content.matter().has_streams())
- goto _missing_streams;
-
- postParseFixup(meta.data(), content);
-
- streams.CopyFrom(content.matter().streams());
-
return true;
-_missing_streams:
- error = QString(tr("%1 does not contain any streams")).arg(fileName);
- goto _fail;
_content_parse_fail:
error = QString(tr("Failed parsing %1 contents")).arg(fileName);
qDebug("Error: %s", QString().fromStdString(
- content.matter().InitializationErrorString())
+ content.InitializationErrorString())
.toAscii().constData());
qDebug("Debug: %s", QString().fromStdString(
- content.matter().DebugString()).toAscii().constData());
+ content.DebugString()).toAscii().constData());
goto _fail;
_incompatible_file_version:
error = QString(tr("%1 is in an incompatible format version - %2.%3.%4"
@@ -206,7 +211,9 @@ _incompatible_file_version:
.arg(kFileFormatVersionRevision);
goto _fail;
_unexpected_file_type:
- error = QString(tr("%1 is not a streams file")).arg(fileName);
+ error = QString(tr("%1 is not a %2 file"))
+ .arg(fileName)
+ .arg(fileTypeStr(fileType));
goto _fail;
_metadata_parse_fail:
error = QString(tr("Failed parsing %1 meta data")).arg(fileName);
@@ -260,12 +267,14 @@ _fail:
return false;
}
-bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
- const QString fileName, QString &error)
+bool NativeFileFormat::save(
+ OstProto::FileType fileType,
+ const OstProto::FileContent &content,
+ const QString fileName,
+ QString &error)
{
OstProto::FileMagic magic;
OstProto::FileMeta meta;
- OstProto::FileContent content;
OstProto::FileChecksum cksum;
QFile file(fileName);
int metaSize, contentSize;
@@ -280,13 +289,12 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
Q_ASSERT(cksum.IsInitialized());
initFileMetaData(*(meta.mutable_data()));
- meta.mutable_data()->set_file_type(OstProto::kStreamsFileType);
+ meta.mutable_data()->set_file_type(fileType);
Q_ASSERT(meta.IsInitialized());
- if (!streams.IsInitialized())
- goto _stream_not_init;
+ if (!content.IsInitialized())
+ goto _content_not_init;
- content.mutable_matter()->mutable_streams()->CopyFrom(streams);
Q_ASSERT(content.IsInitialized());
metaSize = meta.ByteSize();
@@ -323,7 +331,7 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
goto _zero_cksum_serialize_fail;
}
- emit status("Calculating checksum...");
+ // TODO: emit status("Calculating checksum...");
// Calculate and write checksum
calcCksum = checksumCrc32C((quint8*)buf.constData(), buf.size());
@@ -338,7 +346,7 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
qDebug("Writing %d bytes", buf.size());
//qDebug("%s", QString(buf.toHex()).toAscii().constData());
- emit status("Writing to disk...");
+ // TODO: emit status("Writing to disk...");
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
goto _open_fail;
@@ -346,7 +354,7 @@ bool FileFormat::saveStreams(const OstProto::StreamConfigList streams,
goto _write_fail;
file.close();
-
+
return true;
_write_fail:
@@ -387,18 +395,20 @@ _magic_serialize_fail:
magic.InitializationErrorString()))
.arg(QString().fromStdString(magic.DebugString()));
goto _fail;
-_stream_not_init:
- error = QString(tr("Internal Error: Streams not initialized\n%1\n%2"))
+_content_not_init:
+ error = QString(tr("Internal Error: Content not initialized\n%1\n%2"))
.arg(QString().fromStdString(
- streams.InitializationErrorString()))
- .arg(QString().fromStdString(streams.DebugString()));
+ content.InitializationErrorString()))
+ .arg(QString().fromStdString(content.DebugString()));
goto _fail;
_fail:
qDebug("%s", error.toAscii().constData());
return false;
}
-bool FileFormat::isMyFileFormat(const QString fileName)
+bool NativeFileFormat::isNativeFileFormat(
+ const QString fileName,
+ OstProto::FileType fileType)
{
bool ret = false;
QFile file(fileName);
@@ -408,13 +418,25 @@ bool FileFormat::isMyFileFormat(const QString fileName)
if (!file.open(QIODevice::ReadOnly))
goto _exit;
- buf = file.peek(kFileMagicOffset + kFileMagicSize);
- if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset),
+ // Assume tag/length for MetaData will fit in 8 bytes
+ buf = file.peek(kFileMagicOffset + kFileMagicSize + 8);
+ if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset),
kFileMagicSize))
goto _close_exit;
- if (magic.value() == kFileMagicValue)
- ret = true;
+ if (magic.value() == kFileMagicValue) {
+ OstProto::FileMeta meta;
+ int metaSize = fileMetaSize((quint8*)buf.constData(), buf.size());
+ buf = file.peek(kFileMagicOffset + kFileMagicSize + metaSize);
+ if (!meta.ParseFromArray(
+ (void*)(buf.constData() + kFileMetaDataOffset), metaSize)) {
+ qDebug("%s: File MetaData\n%s", __FUNCTION__,
+ QString().fromStdString(meta.DebugString()).toAscii().constData());
+ goto _close_exit;
+ }
+ if (meta.data().file_type() == fileType)
+ ret = true;
+ }
_close_exit:
file.close();
@@ -422,15 +444,7 @@ _exit:
return ret;
}
-bool FileFormat::isMyFileType(const QString fileType)
-{
- if (fileType.startsWith("Ostinato"))
- return true;
- else
- return false;
-}
-
-void FileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
+void NativeFileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
{
// Fill in the "native" file format version
metaData.set_format_version_major(kFileFormatVersionMajor);
@@ -445,9 +459,53 @@ void FileFormat::initFileMetaData(OstProto::FileMetaData &metaData)
qApp->property("revision").toString().toUtf8().constData());
}
+int NativeFileFormat::fileMetaSize(const quint8* file, int size)
+{
+ int i = kFileMetaDataOffset;
+ uint result, shift;
+ const int kWireTypeLengthDelimited = 2;
+
+ // An embedded Message field is encoded as
+ //
+ // See Protobuf Encoding for more details
+
+ // Decode 'Key' varint
+ result = 0;
+ shift = 0;
+ while (i < size) {
+ quint8 byte = file[i++];
+ result |= (byte & 0x7f) << shift;
+ if (!(byte & 0x80)) // MSB == 0?
+ break;
+ shift += 7;
+ }
+
+ if (i >= size)
+ return 0;
+
+ Q_ASSERT(result == ((OstProto::File::kMetaDataFieldNumber << 3)
+ | kWireTypeLengthDelimited));
+
+ // Decode 'Length' varint
+ result = 0;
+ shift = 0;
+ while (i < size) {
+ quint8 byte = file[i++];
+ result |= (byte & 0x7f) << shift;
+ if (!(byte & 0x80)) // MSB == 0?
+ break;
+ shift += 7;
+ }
+
+ if (i >= size)
+ return 0;
+
+ return int(result+(i-kFileMetaDataOffset));
+}
+
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
/*! Fixup content to what is expected in the native version */
-void FileFormat::postParseFixup(OstProto::FileMetaData metaData,
+void NativeFileFormat::postParseFixup(OstProto::FileMetaData metaData,
OstProto::FileContent &content)
{
Q_ASSERT(metaData.format_version_major() == kFileFormatVersionMajor);
@@ -460,7 +518,7 @@ void FileFormat::postParseFixup(OstProto::FileMetaData metaData,
int n = content.matter().streams().stream_size();
for (int i = 0; i < n; i++)
{
- OstProto::StreamControl *sctl =
+ OstProto::StreamControl *sctl =
content.mutable_matter()->mutable_streams()->mutable_stream(i)->mutable_control();
sctl->set_packets_per_sec(sctl->obsolete_packets_per_sec());
sctl->set_bursts_per_sec(sctl->obsolete_bursts_per_sec());
@@ -473,7 +531,7 @@ void FileFormat::postParseFixup(OstProto::FileMetaData metaData,
case 0:
default:
- qWarning("%s: minor version %u unhandled", __FUNCTION__,
+ qWarning("%s: minor version %u unhandled", __FUNCTION__,
metaData.format_version_minor());
Q_ASSERT_X(false, "postParseFixup", "unhandled minor version");
}
diff --git a/common/fileformat.h b/common/nativefileformat.h
similarity index 51%
rename from common/fileformat.h
rename to common/nativefileformat.h
index f2c5b32..83759ae 100644
--- a/common/fileformat.h
+++ b/common/nativefileformat.h
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2010 Srivats P.
+Copyright (C) 2010, 2016 Srivats P.
This file is part of "Ostinato"
@@ -16,31 +16,48 @@ 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
*/
-#ifndef _FILE_FORMAT_H
-#define _FILE_FORMAT_H
+#ifndef _NATIVE_FILE_FORMAT_H
+#define _NATIVE_FILE_FORMAT_H
-#include "abstractfileformat.h"
+/*
+ * This file contains helper functions for the native file format
+ * defined in fileformat.proto
+ *
+ * The actual file format classes - (Ostm)FileFormat and OssnFileFormat
+ * use multiple inheritance from the abstract interface class and this
+ * helper class
+ *
+ * The primary reason for the existence of this class is to have a common
+ * code for dealing with native file formats
+ */
#include "fileformat.pb.h"
-class FileFormat : public AbstractFileFormat
+#include
+
+class NativeFileFormat
{
public:
- FileFormat();
- ~FileFormat();
+ NativeFileFormat();
- virtual bool openStreams(const QString fileName,
- OstProto::StreamConfigList &streams, QString &error);
- virtual bool saveStreams(const OstProto::StreamConfigList streams,
- const QString fileName, QString &error);
+ bool open(const QString fileName,
+ OstProto::FileType fileType,
+ OstProto::FileMeta &meta,
+ OstProto::FileContent &content,
+ QString &error);
+ bool save(OstProto::FileType fileType,
+ const OstProto::FileContent &content,
+ const QString fileName,
+ QString &error);
- bool isMyFileFormat(const QString fileName);
- bool isMyFileType(const QString fileType);
+ bool isNativeFileFormat(const QString fileName,
+ OstProto::FileType fileType);
+ void postParseFixup(OstProto::FileMetaData metaData,
+ OstProto::FileContent &content);
private:
void initFileMetaData(OstProto::FileMetaData &metaData);
- void postParseFixup(OstProto::FileMetaData metaData,
- OstProto::FileContent &content);
+ int fileMetaSize(const quint8* file, int size);
static const int kFileMagicSize = 12;
static const int kFileChecksumSize = 5;
@@ -50,13 +67,11 @@ private:
static const int kFileMetaDataOffset = kFileMagicSize;
static const std::string kFileMagicValue;
-
+
// Native file format version
static const uint kFileFormatVersionMajor = 0;
static const uint kFileFormatVersionMinor = 2;
static const uint kFileFormatVersionRevision = 4;
};
-extern FileFormat fileFormat;
-
#endif
diff --git a/common/ossnfileformat.cpp b/common/ossnfileformat.cpp
new file mode 100644
index 0000000..adb943c
--- /dev/null
+++ b/common/ossnfileformat.cpp
@@ -0,0 +1,91 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#include "ossnfileformat.h"
+
+OssnFileFormat ossnFileFormat;
+
+OssnFileFormat::OssnFileFormat()
+ : SessionFileFormat(), NativeFileFormat()
+{
+ // Do Nothing
+}
+
+bool OssnFileFormat::open(const QString fileName,
+ OstProto::SessionContent &session, QString &error)
+{
+ OstProto::FileMeta meta;
+ OstProto::FileContent content;
+ bool ret = NativeFileFormat::open(fileName, OstProto::kSessionFileType,
+ meta, content, error);
+ if (!ret)
+ goto _exit;
+
+ if (!content.matter().has_session())
+ goto _missing_session;
+
+ postParseFixup(meta.data(), content);
+
+ session.CopyFrom(content.matter().session());
+
+ return true;
+
+_missing_session:
+ error = QString(tr("%1 does not contain a session")).arg(fileName);
+ goto _fail;
+_fail:
+ qDebug("%s", error.toAscii().constData());
+_exit:
+ return false;
+}
+
+bool OssnFileFormat::save(const OstProto::SessionContent &session,
+ const QString fileName, QString &error)
+{
+ OstProto::FileContent content;
+
+ if (!session.IsInitialized())
+ goto _session_not_init;
+
+ content.mutable_matter()->mutable_session()->CopyFrom(session);
+ Q_ASSERT(content.IsInitialized());
+
+ return NativeFileFormat::save(OstProto::kSessionFileType, content,
+ fileName, error);
+
+_session_not_init:
+ error = QString(tr("Internal Error: Session not initialized\n%1\n%2"))
+ .arg(QString().fromStdString(
+ session.InitializationErrorString()))
+ .arg(QString().fromStdString(session.DebugString()));
+ goto _fail;
+_fail:
+ qDebug("%s", error.toAscii().constData());
+ return false;
+}
+
+bool OssnFileFormat::isMyFileFormat(const QString fileName)
+{
+ return isNativeFileFormat(fileName, OstProto::kSessionFileType);
+}
+
+bool OssnFileFormat::isMyFileType(const QString fileType)
+{
+ return fileType.contains("(*.ossn)") ? true : false;
+}
diff --git a/common/ossnfileformat.h b/common/ossnfileformat.h
new file mode 100644
index 0000000..e6412b4
--- /dev/null
+++ b/common/ossnfileformat.h
@@ -0,0 +1,43 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _OSSN_FILE_FORMAT_H
+#define _OSSN_FILE_FORMAT_H
+
+#include "nativefileformat.h"
+#include "sessionfileformat.h"
+
+class OssnFileFormat : public SessionFileFormat, public NativeFileFormat
+{
+public:
+ OssnFileFormat();
+
+ virtual bool open(const QString fileName,
+ OstProto::SessionContent &session, QString &error);
+ virtual bool save(const OstProto::SessionContent &session,
+ const QString fileName, QString &error);
+
+ virtual bool isMyFileFormat(const QString fileName);
+ virtual bool isMyFileType(const QString fileType);
+};
+
+extern OssnFileFormat ossnFileFormat;
+
+#endif
+
diff --git a/common/ostmfileformat.cpp b/common/ostmfileformat.cpp
new file mode 100644
index 0000000..3cbd75c
--- /dev/null
+++ b/common/ostmfileformat.cpp
@@ -0,0 +1,94 @@
+/*
+Copyright (C) 2010 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
+*/
+
+#include "ostmfileformat.h"
+
+OstmFileFormat fileFormat;
+
+OstmFileFormat::OstmFileFormat()
+ : StreamFileFormat(), NativeFileFormat()
+{
+ // Do Nothing!
+}
+
+bool OstmFileFormat::open(const QString fileName,
+ OstProto::StreamConfigList &streams, QString &error)
+{
+ OstProto::FileMeta meta;
+ OstProto::FileContent content;
+ bool ret = NativeFileFormat::open(fileName, OstProto::kStreamsFileType,
+ meta, content, error);
+ if (!ret)
+ goto _fail;
+
+ if (!content.matter().has_streams())
+ goto _missing_streams;
+
+ postParseFixup(meta.data(), content);
+
+ streams.CopyFrom(content.matter().streams());
+
+ return true;
+
+_missing_streams:
+ error = QString(tr("%1 does not contain any streams")).arg(fileName);
+ goto _fail;
+_fail:
+ qDebug("%s", error.toAscii().constData());
+ return false;
+}
+
+bool OstmFileFormat::save(const OstProto::StreamConfigList streams,
+ const QString fileName, QString &error)
+{
+ OstProto::FileContent content;
+
+ if (!streams.IsInitialized())
+ goto _stream_not_init;
+
+ content.mutable_matter()->mutable_streams()->CopyFrom(streams);
+ Q_ASSERT(content.IsInitialized());
+
+ return NativeFileFormat::save(OstProto::kStreamsFileType, content,
+ fileName, error);
+
+_stream_not_init:
+ error = QString(tr("Internal Error: Streams not initialized\n%1\n%2"))
+ .arg(QString().fromStdString(
+ streams.InitializationErrorString()))
+ .arg(QString().fromStdString(streams.DebugString()));
+ goto _fail;
+_fail:
+ qDebug("%s", error.toAscii().constData());
+ return false;
+}
+
+bool OstmFileFormat::isMyFileFormat(const QString fileName)
+{
+ return isNativeFileFormat(fileName, OstProto::kStreamsFileType);
+}
+
+bool OstmFileFormat::isMyFileType(const QString fileType)
+{
+ if (fileType.startsWith("Ostinato"))
+ return true;
+ else
+ return false;
+}
+
diff --git a/common/ostmfileformat.h b/common/ostmfileformat.h
new file mode 100644
index 0000000..f44ea74
--- /dev/null
+++ b/common/ostmfileformat.h
@@ -0,0 +1,43 @@
+/*
+Copyright (C) 2010 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
+*/
+#ifndef _OSTM_FILE_FORMAT_H
+#define _OSTM_FILE_FORMAT_H
+
+#include "nativefileformat.h"
+#include "streamfileformat.h"
+
+#include "fileformat.pb.h"
+
+class OstmFileFormat : public StreamFileFormat, public NativeFileFormat
+{
+public:
+ OstmFileFormat();
+
+ virtual bool open(const QString fileName,
+ OstProto::StreamConfigList &streams, QString &error);
+ virtual bool save(const OstProto::StreamConfigList streams,
+ const QString fileName, QString &error);
+
+ bool isMyFileFormat(const QString fileName);
+ bool isMyFileType(const QString fileType);
+};
+
+extern OstmFileFormat fileFormat;
+
+#endif
diff --git a/common/ostproto.pro b/common/ostproto.pro
index 8d14c1a..5aff222 100644
--- a/common/ostproto.pro
+++ b/common/ostproto.pro
@@ -7,6 +7,9 @@ LIBS += \
PROTOS = \
protocol.proto \
+ emulproto.proto
+
+PROTOS += \
mac.proto \
payload.proto \
eth2.proto \
@@ -35,7 +38,7 @@ PROTOS = \
textproto.proto \
userscript.proto \
hexdump.proto \
- sample.proto
+ sample.proto
HEADERS = \
abstractprotocol.h \
diff --git a/common/ostprotogui.pro b/common/ostprotogui.pro
index a91beea..7785def 100644
--- a/common/ostprotogui.pro
+++ b/common/ostprotogui.pro
@@ -35,16 +35,20 @@ PROTOS = \
# TODO: Move fileformat related stuff into a different library - why?
HEADERS = \
ostprotolib.h \
- abstractfileformat.h \
- fileformat.h \
ipv4addressdelegate.h \
ipv6addressdelegate.h \
+ nativefileformat.h \
+ ossnfileformat.h \
+ ostmfileformat.h \
pcapfileformat.h \
pdmlfileformat.h \
pythonfileformat.h \
pdmlprotocol.h \
pdmlprotocols.h \
- pdmlreader.h
+ pdmlreader.h \
+ sessionfileformat.h \
+ streamfileformat.h \
+ spinboxdelegate.h
HEADERS += \
abstractprotocolconfig.h \
@@ -79,14 +83,18 @@ HEADERS += \
SOURCES += \
ostprotolib.cpp \
- abstractfileformat.cpp \
- fileformat.cpp \
+ nativefileformat.cpp \
+ ossnfileformat.cpp \
+ ostmfileformat.cpp \
pcapfileformat.cpp \
pdmlfileformat.cpp \
pythonfileformat.cpp \
pdmlprotocol.cpp \
pdmlprotocols.cpp \
pdmlreader.cpp \
+ sessionfileformat.cpp \
+ streamfileformat.cpp \
+ spinboxdelegate.cpp
SOURCES += \
protocolwidgetfactory.cpp \
diff --git a/common/pcapfileformat.cpp b/common/pcapfileformat.cpp
index e96b684..9f352dd 100644
--- a/common/pcapfileformat.cpp
+++ b/common/pcapfileformat.cpp
@@ -91,7 +91,7 @@ PcapFileFormat::~PcapFileFormat()
delete importDialog_;
}
-bool PcapFileFormat::openStreams(const QString fileName,
+bool PcapFileFormat::open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error)
{
bool isOk = false;
@@ -325,7 +325,7 @@ bool PcapFileFormat::openStreams(const QString fileName,
goto _diff_fail;
}
- if (!saveStreams(streams, importedPcapFile.fileName(), error))
+ if (!save(streams, importedPcapFile.fileName(), error))
{
error.append("Error saving imported streams as PCAP for diff");
goto _diff_fail;
@@ -553,7 +553,7 @@ bool PcapFileFormat::readPacket(PcapPacketHeader &pktHdr, QByteArray &pktBuf)
return true;
}
-bool PcapFileFormat::saveStreams(const OstProto::StreamConfigList streams,
+bool PcapFileFormat::save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error)
{
bool isOk = false;
diff --git a/common/pcapfileformat.h b/common/pcapfileformat.h
index 064aaf1..cb0c461 100644
--- a/common/pcapfileformat.h
+++ b/common/pcapfileformat.h
@@ -19,7 +19,7 @@ along with this program. If not, see
#ifndef _PCAP_FILE_FORMAT_H
#define _PCAP_FILE_FORMAT_H
-#include "abstractfileformat.h"
+#include "streamfileformat.h"
#include "ui_pcapfileimport.h"
#include
@@ -39,7 +39,7 @@ private:
};
class PdmlReader;
-class PcapFileFormat : public AbstractFileFormat
+class PcapFileFormat : public StreamFileFormat
{
friend class PdmlReader;
@@ -47,9 +47,9 @@ public:
PcapFileFormat();
~PcapFileFormat();
- bool openStreams(const QString fileName,
+ bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error);
- bool saveStreams(const OstProto::StreamConfigList streams,
+ bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error);
virtual QDialog* openOptionsDialog();
diff --git a/common/pdmlfileformat.cpp b/common/pdmlfileformat.cpp
index e567d99..f522a8c 100644
--- a/common/pdmlfileformat.cpp
+++ b/common/pdmlfileformat.cpp
@@ -35,7 +35,7 @@ PdmlFileFormat::~PdmlFileFormat()
{
}
-bool PdmlFileFormat::openStreams(const QString fileName,
+bool PdmlFileFormat::open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error)
{
bool isOk = false;
@@ -75,12 +75,12 @@ _exit:
return isOk;
}
-bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams,
+bool PdmlFileFormat::save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error)
{
bool isOk = false;
QTemporaryFile pcapFile;
- AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromType("PCAP");
+ StreamFileFormat *fmt = StreamFileFormat::fileFormatFromType("PCAP");
QProcess tshark;
Q_ASSERT(fmt);
@@ -97,7 +97,7 @@ bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams,
connect(fmt, SIGNAL(progress(int)), this, SIGNAL(progress(int)));
emit status("Writing intermediate PCAP file...");
- isOk = fmt->saveStreams(streams, pcapFile.fileName(), error);
+ isOk = fmt->save(streams, pcapFile.fileName(), error);
qDebug("generating PDML %s", fileName.toAscii().constData());
emit status("Converting PCAP to PDML...");
diff --git a/common/pdmlfileformat.h b/common/pdmlfileformat.h
index e05026a..bc1d15b 100644
--- a/common/pdmlfileformat.h
+++ b/common/pdmlfileformat.h
@@ -19,17 +19,17 @@ along with this program. If not, see
#ifndef _PDML_FILE_FORMAT_H
#define _PDML_FILE_FORMAT_H
-#include "abstractfileformat.h"
+#include "streamfileformat.h"
-class PdmlFileFormat : public AbstractFileFormat
+class PdmlFileFormat : public StreamFileFormat
{
public:
PdmlFileFormat();
~PdmlFileFormat();
- virtual bool openStreams(const QString fileName,
+ virtual bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error);
- virtual bool saveStreams(const OstProto::StreamConfigList streams,
+ virtual bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error);
bool isMyFileFormat(const QString fileName);
diff --git a/common/protocol.proto b/common/protocol.proto
index fdfb5bd..439d28f 100644
--- a/common/protocol.proto
+++ b/common/protocol.proto
@@ -274,6 +274,55 @@ message Notification {
optional PortIdList port_id_list = 6;
}
+
+/*
+ * Protocol Emulation
+ */
+message DeviceGroupId {
+ required uint32 id = 1;
+}
+
+message DeviceGroupCore {
+ optional string name = 1;
+}
+
+message DeviceGroupIdList {
+ required PortId port_id = 1;
+ repeated DeviceGroupId device_group_id = 2;
+}
+
+message EncapEmulation {
+ // Encap Protocols implemented as extensions
+ extensions 1000 to 1999;
+}
+
+message DeviceGroup {
+ required DeviceGroupId device_group_id = 1;
+ optional DeviceGroupCore core = 2;
+ optional EncapEmulation encap = 3;
+ optional uint32 device_count = 4 [default = 1]; // per-encap
+
+ // Device Protocols implemented as extensions
+ extensions 2000 to 5999;
+}
+
+message DeviceGroupConfigList {
+ required PortId port_id = 1;
+ repeated DeviceGroup device_group = 2;
+}
+
+message PortDeviceList {
+ required PortId port_id = 1;
+
+ extensions 100 to 199;
+}
+
+message PortNeighborList {
+ required PortId port_id = 1;
+
+ extensions 100 to 199;
+}
+
service OstService {
rpc getPortIdList(Void) returns (PortIdList);
rpc getPortConfig(PortIdList) returns (PortConfigList);
@@ -296,5 +345,18 @@ service OstService {
rpc clearStats(PortIdList) returns (Ack);
rpc checkVersion(VersionInfo) returns (VersionCompatibility);
+
+ // Device Emulation
+ rpc getDeviceGroupIdList(PortId) returns (DeviceGroupIdList);
+ rpc getDeviceGroupConfig(DeviceGroupIdList) returns (DeviceGroupConfigList);
+ rpc addDeviceGroup(DeviceGroupIdList) returns (Ack);
+ rpc deleteDeviceGroup(DeviceGroupIdList) returns (Ack);
+ rpc modifyDeviceGroup(DeviceGroupConfigList) returns (Ack);
+
+ rpc getDeviceList(PortId) returns (PortDeviceList);
+
+ rpc resolveDeviceNeighbors(PortIdList) returns (Ack);
+ rpc clearDeviceNeighbors(PortIdList) returns (Ack);
+ rpc getDeviceNeighbors(PortId) returns (PortNeighborList);
}
diff --git a/common/pythonfileformat.cpp b/common/pythonfileformat.cpp
index cec283b..d9e65ba 100644
--- a/common/pythonfileformat.cpp
+++ b/common/pythonfileformat.cpp
@@ -46,14 +46,14 @@ PythonFileFormat::~PythonFileFormat()
// Nothing to do
}
-bool PythonFileFormat::openStreams(const QString /*fileName*/,
+bool PythonFileFormat::open(const QString /*fileName*/,
OstProto::StreamConfigList &/*streams*/, QString &/*error*/)
{
// NOT SUPPORTED!
return false;
}
-bool PythonFileFormat::saveStreams(const OstProto::StreamConfigList streams,
+bool PythonFileFormat::save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error)
{
QFile file(fileName);
diff --git a/common/pythonfileformat.h b/common/pythonfileformat.h
index 55a6452..ee8e4bf 100644
--- a/common/pythonfileformat.h
+++ b/common/pythonfileformat.h
@@ -20,19 +20,19 @@ along with this program. If not, see
#ifndef _PYTHON_FILE_FORMAT_H
#define _PYTHON_FILE_FORMAT_H
-#include "abstractfileformat.h"
+#include "streamfileformat.h"
#include
-class PythonFileFormat : public AbstractFileFormat
+class PythonFileFormat : public StreamFileFormat
{
public:
PythonFileFormat();
~PythonFileFormat();
- virtual bool openStreams(const QString fileName,
+ virtual bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error);
- virtual bool saveStreams(const OstProto::StreamConfigList streams,
+ virtual bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error);
bool isMyFileFormat(const QString fileName);
diff --git a/common/sessionfileformat.cpp b/common/sessionfileformat.cpp
new file mode 100644
index 0000000..9e14530
--- /dev/null
+++ b/common/sessionfileformat.cpp
@@ -0,0 +1,117 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#include "sessionfileformat.h"
+
+#include "ossnfileformat.h"
+
+#include
+
+SessionFileFormat::SessionFileFormat()
+{
+ stop_ = false;
+}
+
+SessionFileFormat::~SessionFileFormat()
+{
+}
+
+QDialog* SessionFileFormat::openOptionsDialog()
+{
+ return NULL;
+}
+
+QDialog* SessionFileFormat::saveOptionsDialog()
+{
+ return NULL;
+}
+
+QStringList SessionFileFormat::supportedFileTypes(Operation op)
+{
+ QStringList fileTypes;
+
+ fileTypes << "Ostinato Session (*.ossn)";
+
+ if (op == kOpenFile)
+ fileTypes << "All files (*)";
+
+ return fileTypes;
+}
+
+void SessionFileFormat::openAsync(const QString fileName,
+ OstProto::SessionContent &session, QString &error)
+{
+ fileName_ = fileName;
+ openSession_ = &session;
+ error_ = &error;
+ op_ = kOpenFile;
+ stop_ = false;
+
+ start();
+}
+
+void SessionFileFormat::saveAsync(
+ const OstProto::SessionContent &session,
+ const QString fileName, QString &error)
+{
+ saveSession_ = &session;
+ fileName_ = fileName;
+ error_ = &error;
+ op_ = kSaveFile;
+ stop_ = false;
+
+ start();
+}
+
+bool SessionFileFormat::result()
+{
+ return result_;
+}
+
+SessionFileFormat* SessionFileFormat::fileFormatFromFile(
+ const QString fileName)
+{
+ if (ossnFileFormat.isMyFileFormat(fileName))
+ return &ossnFileFormat;
+
+ return NULL;
+}
+
+SessionFileFormat* SessionFileFormat::fileFormatFromType(
+ const QString fileType)
+{
+
+ if (ossnFileFormat.isMyFileType(fileType))
+ return &ossnFileFormat;
+
+ return NULL;
+}
+
+void SessionFileFormat::cancel()
+{
+ stop_ = true;
+}
+
+void SessionFileFormat::run()
+{
+ if (op_ == kOpenFile)
+ result_ = open(fileName_, *openSession_, *error_);
+ else if (op_ == kSaveFile)
+ result_ = save(*saveSession_, fileName_, *error_);
+}
diff --git a/common/sessionfileformat.h b/common/sessionfileformat.h
new file mode 100644
index 0000000..31fe16c
--- /dev/null
+++ b/common/sessionfileformat.h
@@ -0,0 +1,87 @@
+/*
+Copyright (C) 2016 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
+*/
+
+#ifndef _SESSION_FILE_FORMAT_H
+#define _SESSION_FILE_FORMAT_H
+
+#include "fileformat.pb.h"
+#include "protocol.pb.h"
+
+#include
+#include
+
+class QDialog;
+
+class SessionFileFormat : public QThread
+{
+ Q_OBJECT
+public:
+ enum Operation { kOpenFile, kSaveFile };
+
+ SessionFileFormat();
+ virtual ~SessionFileFormat();
+
+ virtual bool open(const QString fileName,
+ OstProto::SessionContent &session, QString &error) = 0;
+ virtual bool save(const OstProto::SessionContent &session,
+ const QString fileName, QString &error) = 0;
+
+ virtual QDialog* openOptionsDialog();
+ virtual QDialog* saveOptionsDialog();
+
+ void openAsync(const QString fileName,
+ OstProto::SessionContent &session, QString &error);
+ void saveAsync(const OstProto::SessionContent &session,
+ const QString fileName, QString &error);
+
+ bool result();
+
+ static QStringList supportedFileTypes(Operation op);
+
+ static SessionFileFormat* fileFormatFromFile(const QString fileName);
+ static SessionFileFormat* fileFormatFromType(const QString fileType);
+
+ virtual bool isMyFileFormat(const QString fileName) = 0;
+ virtual bool isMyFileType(const QString fileType) = 0;
+
+signals:
+ void status(QString text);
+ void target(int value);
+ void progress(int value);
+
+public slots:
+ void cancel();
+
+protected:
+ void run();
+
+ bool stop_;
+
+private:
+ QString fileName_;
+ OstProto::SessionContent *openSession_;
+ const OstProto::SessionContent *saveSession_;
+ QString *error_;
+ Operation op_;
+ bool result_;
+
+};
+
+#endif
+
diff --git a/common/spinboxdelegate.cpp b/common/spinboxdelegate.cpp
new file mode 100644
index 0000000..d0376c2
--- /dev/null
+++ b/common/spinboxdelegate.cpp
@@ -0,0 +1,112 @@
+/*
+Copyright (C) 2016 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
+
+This file incorporates work covered by the following copyright and
+permission notice:
+
+ Copyright (C) 2015 The Qt Company Ltd.
+ Contact: http://www.qt.io/licensing/
+
+ This file is part of the examples of the Qt Toolkit.
+
+ $QT_BEGIN_LICENSE:BSD$
+ You may use this file under the terms of the BSD license as follows:
+
+ "Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ Neither the name of The Qt Company Ltd nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+
+ $QT_END_LICENSE$
+*/
+
+//#include
+
+#include "spinboxdelegate.h"
+
+#include
+
+SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
+ : QItemDelegate(parent)
+{
+}
+
+QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
+ const QStyleOptionViewItem &/* option */,
+ const QModelIndex &index) const
+{
+ QSpinBox *editor = new QSpinBox(parent);
+ editor->setMinimum(colMin_.contains(index.column()) ?
+ colMin_.value(index.column()) : 0);
+ editor->setMaximum(colMax_.contains(index.column()) ?
+ colMax_.value(index.column()) : INT_MAX);
+
+ return editor;
+}
+
+void SpinBoxDelegate::setColumnRange(int col, int min, int max)
+{
+ colMin_.insert(col, min);
+ colMax_.insert(col, max);
+}
+
+void SpinBoxDelegate::setEditorData(QWidget *editor,
+ const QModelIndex &index) const
+{
+ int value = index.model()->data(index, Qt::EditRole).toInt();
+
+ QSpinBox *spinBox = static_cast(editor);
+ spinBox->setValue(value);
+}
+
+void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+{
+ QSpinBox *spinBox = static_cast(editor);
+ spinBox->interpretText();
+ int value = spinBox->value();
+
+ model->setData(index, value, Qt::EditRole);
+}
+
+void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
+{
+ editor->setGeometry(option.rect);
+}
diff --git a/common/spinboxdelegate.h b/common/spinboxdelegate.h
new file mode 100644
index 0000000..a253623
--- /dev/null
+++ b/common/spinboxdelegate.h
@@ -0,0 +1,93 @@
+/*
+Copyright (C) 2016 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
+
+This file incorporates work covered by the following copyright and
+permission notice:
+
+ Copyright (C) 2015 The Qt Company Ltd.
+ Contact: http://www.qt.io/licensing/
+
+ This file is part of the examples of the Qt Toolkit.
+
+ $QT_BEGIN_LICENSE:BSD$
+ You may use this file under the terms of the BSD license as follows:
+
+ "Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ Neither the name of The Qt Company Ltd nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+
+ $QT_END_LICENSE$
+*/
+
+#ifndef _SPIN_BOX_DELEGATE_H
+#define _SPIN_BOX_DELEGATE_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class SpinBoxDelegate : public QItemDelegate
+{
+ Q_OBJECT
+
+public:
+ SpinBoxDelegate(QObject *parent = 0);
+
+ void setColumnRange(int col, int min, int max);
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+
+ void setEditorData(QWidget *editor, const QModelIndex &index) const;
+ void setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const;
+
+ void updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option, const QModelIndex &index) const;
+private:
+ QHash colMin_;
+ QHash colMax_;
+};
+
+#endif
+
diff --git a/common/streambase.cpp b/common/streambase.cpp
index 361e634..622e3d5 100644
--- a/common/streambase.cpp
+++ b/common/streambase.cpp
@@ -24,8 +24,11 @@ along with this program. If not, see
#include "protocolmanager.h"
extern ProtocolManager *OstProtocolManager;
+extern quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex);
+extern quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex);
-StreamBase::StreamBase() :
+StreamBase::StreamBase(int portId) :
+ portId_(portId),
mStreamId(new OstProto::StreamId),
mCore(new OstProto::StreamCore),
mControl(new OstProto::StreamControl)
@@ -522,18 +525,22 @@ int StreamBase::frameCount() const
return count;
}
+// Returns packet length - if bufMaxSize < frameLen(), returns truncated
+// length i.e. bufMaxSize
int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const
{
- int pktLen, len = 0;
+ int maxSize, size, pktLen, len = 0;
pktLen = frameLen(frameIndex);
// pktLen is adjusted for CRC/FCS which will be added by the NIC
pktLen -= kFcsSize;
- if ((pktLen < 0) || (pktLen > bufMaxSize))
+ if (pktLen <= 0)
return 0;
+ maxSize = qMin(pktLen, bufMaxSize);
+
ProtocolListIterator *iter;
iter = createProtocolListIterator();
@@ -545,17 +552,33 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const
proto = iter->next();
ba = proto->protocolFrameValue(frameIndex);
- if (len + ba.size() < bufMaxSize)
- memcpy(buf+len, ba.constData(), ba.size());
- len += ba.size();
+ size = qMin(ba.size(), maxSize-len);
+ memcpy(buf+len, ba.constData(), size);
+ len += size;
+
+ if (len == maxSize)
+ break;
}
delete iter;
- // Pad with zero, if required
- if (len < pktLen)
- memset(buf+len, 0, pktLen-len);
+ // Pad with zero, if required and if we have space
+ if (len < maxSize) {
+ size = maxSize-len;
+ memset(buf+len, 0, size);
+ len += size;
+ }
- return pktLen;
+ return len;
+}
+
+quint64 StreamBase::deviceMacAddress(int frameIndex) const
+{
+ return getDeviceMacAddress(portId_, int(mStreamId->id()), frameIndex);
+}
+
+quint64 StreamBase::neighborMacAddress(int frameIndex) const
+{
+ return getNeighborMacAddress(portId_, int(mStreamId->id()), frameIndex);
}
bool StreamBase::preflightCheck(QString &result) const
diff --git a/common/streambase.h b/common/streambase.h
index 11ac2c2..ca6e6be 100644
--- a/common/streambase.h
+++ b/common/streambase.h
@@ -33,15 +33,8 @@ class ProtocolListIterator;
class StreamBase
{
-private:
- OstProto::StreamId *mStreamId;
- OstProto::StreamCore *mCore;
- OstProto::StreamControl *mControl;
-
- ProtocolList *currentFrameProtocols;
-
public:
- StreamBase();
+ StreamBase(int portId = -1);
~StreamBase();
void protoDataCopyFrom(const OstProto::Stream &stream);
@@ -143,9 +136,22 @@ public:
int frameProtocolLength(int frameIndex) const;
int frameCount() const;
int frameValue(uchar *buf, int bufMaxSize, int frameIndex) const;
+
+ quint64 deviceMacAddress(int frameIndex) const;
+ quint64 neighborMacAddress(int frameIndex) const;
+
bool preflightCheck(QString &result) const;
static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2);
+
+private:
+ int portId_;
+
+ OstProto::StreamId *mStreamId;
+ OstProto::StreamCore *mCore;
+ OstProto::StreamControl *mControl;
+
+ ProtocolList *currentFrameProtocols;
};
#endif
diff --git a/common/abstractfileformat.cpp b/common/streamfileformat.cpp
similarity index 65%
rename from common/abstractfileformat.cpp
rename to common/streamfileformat.cpp
index 234795a..d00dda6 100644
--- a/common/abstractfileformat.cpp
+++ b/common/streamfileformat.cpp
@@ -17,74 +17,82 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-#include "abstractfileformat.h"
+#include "streamfileformat.h"
-#include "fileformat.h"
+#include "ostmfileformat.h"
#include "pcapfileformat.h"
#include "pdmlfileformat.h"
#include "pythonfileformat.h"
#include
-AbstractFileFormat::AbstractFileFormat()
+StreamFileFormat::StreamFileFormat()
{
stop_ = false;
}
-AbstractFileFormat::~AbstractFileFormat()
+StreamFileFormat::~StreamFileFormat()
{
}
-QDialog* AbstractFileFormat::openOptionsDialog()
+QDialog* StreamFileFormat::openOptionsDialog()
{
return NULL;
}
-QDialog* AbstractFileFormat::saveOptionsDialog()
+QDialog* StreamFileFormat::saveOptionsDialog()
{
return NULL;
}
-QStringList AbstractFileFormat::supportedFileTypes()
+QStringList StreamFileFormat::supportedFileTypes(Operation op)
{
- return QStringList()
- << "Ostinato (*)"
+ QStringList fileTypes;
+
+ fileTypes
+ << "Ostinato (*.ostm)"
<< "PCAP (*)"
- << "PDML (*.pdml)"
- << "PythonScript (*.py)";
+ << "PDML (*.pdml)";
+
+ if (op == kSaveFile)
+ fileTypes << "PythonScript (*.py)";
+ else if (op == kOpenFile)
+ fileTypes << "All files (*)";
+
+ return fileTypes;
}
-void AbstractFileFormat::openStreamsOffline(const QString fileName,
+void StreamFileFormat::openAsync(const QString fileName,
OstProto::StreamConfigList &streams, QString &error)
{
fileName_ = fileName;
openStreams_ = &streams;
error_ = &error;
- op_ = kOpen;
+ op_ = kOpenFile;
stop_ = false;
start();
}
-void AbstractFileFormat::saveStreamsOffline(
+void StreamFileFormat::saveAsync(
const OstProto::StreamConfigList streams,
const QString fileName, QString &error)
{
saveStreams_ = streams;
fileName_ = fileName;
error_ = &error;
- op_ = kSave;
+ op_ = kSaveFile;
stop_ = false;
start();
}
-bool AbstractFileFormat::result()
+bool StreamFileFormat::result()
{
return result_;
}
-AbstractFileFormat* AbstractFileFormat::fileFormatFromFile(
+StreamFileFormat* StreamFileFormat::fileFormatFromFile(
const QString fileName)
{
if (fileFormat.isMyFileFormat(fileName))
@@ -99,7 +107,7 @@ AbstractFileFormat* AbstractFileFormat::fileFormatFromFile(
return NULL;
}
-AbstractFileFormat* AbstractFileFormat::fileFormatFromType(
+StreamFileFormat* StreamFileFormat::fileFormatFromType(
const QString fileType)
{
@@ -118,15 +126,15 @@ AbstractFileFormat* AbstractFileFormat::fileFormatFromType(
return NULL;
}
-void AbstractFileFormat::cancel()
+void StreamFileFormat::cancel()
{
stop_ = true;
}
-void AbstractFileFormat::run()
+void StreamFileFormat::run()
{
- if (op_ == kOpen)
- result_ = openStreams(fileName_, *openStreams_, *error_);
- else if (op_ == kSave)
- result_ = saveStreams(saveStreams_, fileName_, *error_);
+ if (op_ == kOpenFile)
+ result_ = open(fileName_, *openStreams_, *error_);
+ else if (op_ == kSaveFile)
+ result_ = save(saveStreams_, fileName_, *error_);
}
diff --git a/common/abstractfileformat.h b/common/streamfileformat.h
similarity index 70%
rename from common/abstractfileformat.h
rename to common/streamfileformat.h
index 1f8447d..474ab09 100644
--- a/common/abstractfileformat.h
+++ b/common/streamfileformat.h
@@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
-#ifndef _ABSTRACT_FILE_FORMAT_H
-#define _ABSTRACT_FILE_FORMAT_H
+#ifndef _STREAM_FILE_FORMAT_H
+#define _STREAM_FILE_FORMAT_H
#include "protocol.pb.h"
@@ -27,32 +27,34 @@ along with this program. If not, see
class QDialog;
-class AbstractFileFormat : public QThread
+class StreamFileFormat : public QThread
{
Q_OBJECT
public:
- AbstractFileFormat();
- virtual ~AbstractFileFormat();
+ enum Operation { kOpenFile, kSaveFile };
- virtual bool openStreams(const QString fileName,
+ StreamFileFormat();
+ virtual ~StreamFileFormat();
+
+ virtual bool open(const QString fileName,
OstProto::StreamConfigList &streams, QString &error) = 0;
- virtual bool saveStreams(const OstProto::StreamConfigList streams,
+ virtual bool save(const OstProto::StreamConfigList streams,
const QString fileName, QString &error) = 0;
virtual QDialog* openOptionsDialog();
virtual QDialog* saveOptionsDialog();
- void openStreamsOffline(const QString fileName,
+ void openAsync(const QString fileName,
OstProto::StreamConfigList &streams, QString &error);
- void saveStreamsOffline(const OstProto::StreamConfigList streams,
+ void saveAsync(const OstProto::StreamConfigList streams,
const QString fileName, QString &error);
bool result();
- static QStringList supportedFileTypes();
+ static QStringList supportedFileTypes(Operation op);
- static AbstractFileFormat* fileFormatFromFile(const QString fileName);
- static AbstractFileFormat* fileFormatFromType(const QString fileType);
+ static StreamFileFormat* fileFormatFromFile(const QString fileName);
+ static StreamFileFormat* fileFormatFromType(const QString fileType);
#if 0
bool isMyFileFormat(const QString fileName) = 0;
@@ -73,16 +75,11 @@ protected:
bool stop_;
private:
- enum kOp
- {
- kOpen,
- kSave
- };
QString fileName_;
OstProto::StreamConfigList *openStreams_;
OstProto::StreamConfigList saveStreams_;
QString *error_;
- kOp op_;
+ Operation op_;
bool result_;
};
diff --git a/common/uint128.h b/common/uint128.h
new file mode 100644
index 0000000..d39e133
--- /dev/null
+++ b/common/uint128.h
@@ -0,0 +1,185 @@
+/*
+Copyright (C) 2015 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
+*/
+
+#ifndef _UINT128_H
+#define _UINT128_H
+
+#include
+
+#include
+#include
+
+class UInt128
+{
+public:
+ UInt128();
+ UInt128(quint64 hi, quint64 lo);
+ UInt128(quint8 *value);
+
+ quint64 hi64() const;
+ quint64 lo64() const;
+ quint8* toArray() const;
+
+ bool operator==(const UInt128 &other) const;
+ bool operator!=(const UInt128 &other) const;
+ UInt128 operator+(const UInt128 &other) const;
+ UInt128 operator*(const uint &other) const;
+ UInt128 operator<<(const int &shift) const;
+ UInt128 operator~() const;
+ UInt128 operator&(const UInt128 &other) const;
+ UInt128 operator|(const UInt128 &other) const;
+
+private:
+ quint64 hi_;
+ quint64 lo_;
+ quint8 array_[16];
+};
+
+inline UInt128::UInt128()
+{
+ // Do nothing - value will be garbage like any other uint
+}
+
+inline UInt128::UInt128(quint64 hi, quint64 lo)
+{
+ hi_ = hi;
+ lo_ = lo;
+}
+
+inline UInt128::UInt128(quint8 *value)
+{
+ hi_ = (quint64(value[0]) << 56)
+ | (quint64(value[1]) << 48)
+ | (quint64(value[2]) << 40)
+ | (quint64(value[3]) << 32)
+ | (quint64(value[4]) << 24)
+ | (quint64(value[5]) << 16)
+ | (quint64(value[6]) << 8)
+ | (quint64(value[7]) << 0);
+
+ lo_ = (quint64(value[ 8]) << 56)
+ | (quint64(value[ 9]) << 48)
+ | (quint64(value[10]) << 40)
+ | (quint64(value[11]) << 32)
+ | (quint64(value[12]) << 24)
+ | (quint64(value[13]) << 16)
+ | (quint64(value[14]) << 8)
+ | (quint64(value[15]) << 0);
+}
+
+inline quint64 UInt128::hi64() const
+{
+ return hi_;
+}
+
+inline quint64 UInt128::lo64() const
+{
+ return lo_;
+}
+
+inline quint8* UInt128::toArray() const
+{
+ *(quint64*)(array_ + 0) = qToBigEndian(hi_);
+ *(quint64*)(array_ + 8) = qToBigEndian(lo_);
+
+ return (quint8*)array_;
+}
+
+inline bool UInt128::operator==(const UInt128 &other) const
+{
+ return ((hi_ == other.hi_) && (lo_ == other.lo_));
+}
+
+inline bool UInt128::operator!=(const UInt128 &other) const
+{
+ return ((hi_ != other.hi_) || (lo_ != other.lo_));
+}
+
+inline UInt128 UInt128::operator+(const UInt128 &other) const
+{
+ UInt128 sum;
+
+ sum.lo_ = lo_ + other.lo_;
+ sum.hi_ = hi_ + other.hi_ + (sum.lo_ < lo_);
+
+ return sum;
+}
+
+inline UInt128 UInt128::operator*(const uint &other) const
+{
+ UInt128 product;
+
+ // FIXME
+ product.hi_ = 0;
+ product.lo_ = lo_ * other;
+
+ return product;
+}
+
+inline UInt128 UInt128::operator<<(const int &shift) const
+{
+ UInt128 shifted;
+
+ if (shift < 64)
+ return UInt128((hi_<>(64-shift)), lo_ << shift);
+
+ return UInt128(hi_<<(shift-64), 0);
+}
+
+inline UInt128 UInt128::operator~() const
+{
+ return UInt128(~hi_, ~lo_);
+}
+
+inline UInt128 UInt128::operator&(const UInt128 &other) const
+{
+ return UInt128(hi_ & other.hi_, lo_ & other.lo_);
+}
+
+inline UInt128 UInt128::operator|(const UInt128 &other) const
+{
+ return UInt128(hi_ | other.hi_, lo_ | other.lo_);
+}
+
+template <> inline UInt128 qFromBigEndian(const uchar *src)
+{
+ quint64 hi, lo;
+
+ hi = qFromBigEndian(src);
+ lo = qFromBigEndian(src+8);
+
+ return UInt128(hi, lo);
+}
+
+template <> inline UInt128 qToBigEndian(const UInt128 src)
+{
+ quint64 hi, lo;
+
+ hi = qToBigEndian(src.hi64());
+ lo = qToBigEndian(src.lo64());
+
+ return UInt128(hi, lo);
+}
+
+inline uint qHash(const UInt128 &key)
+{
+ return qHash(key.hi64()) ^ qHash(key.lo64());
+}
+
+#endif
diff --git a/rpc/pbrpcchannel.cpp b/rpc/pbrpcchannel.cpp
index 6719351..dff54b5 100644
--- a/rpc/pbrpcchannel.cpp
+++ b/rpc/pbrpcchannel.cpp
@@ -32,6 +32,7 @@ PbRpcChannel::PbRpcChannel(QString serverName, quint16 port,
isPending = false;
pendingMethodId = -1; // don't care as long as isPending is false
+ method = NULL;
controller = NULL;
done = NULL;
response = NULL;
@@ -107,9 +108,10 @@ void PbRpcChannel::CallMethod(
{
RpcCall call;
qDebug("RpcChannel: queueing rpc since method %d is pending;<----\n "
- "queued method = %d\n"
+ "queued method = %d:%s\n"
"queued message = \n%s\n---->",
- pendingMethodId, method->index(), req->DebugString().c_str());
+ pendingMethodId, method->index(), method->name().c_str(),
+ req->DebugString().c_str());
call.method = method;
call.controller = controller;
@@ -128,7 +130,8 @@ void PbRpcChannel::CallMethod(
if (!req->IsInitialized())
{
qWarning("RpcChannel: missing required fields in request <----");
- qDebug("req = \n%s", req->DebugString().c_str());
+ qDebug("req = %s\n%s", method->input_type()->name().c_str(),
+ req->DebugString().c_str());
qDebug("error = \n%s\n--->", req->InitializationErrorString().c_str());
controller->SetFailed("Required fields missing");
@@ -137,6 +140,7 @@ void PbRpcChannel::CallMethod(
}
pendingMethodId = method->index();
+ this->method=method;
this->controller=controller;
this->done=done;
this->response=response;
@@ -153,8 +157,10 @@ void PbRpcChannel::CallMethod(
qDebug("client(%s) sending %d bytes <----", __FUNCTION__,
PB_HDR_SIZE + len);
BUFDUMP(msg, PB_HDR_SIZE);
- qDebug("method = %d\n req = \n%s\n---->",
- method->index(), req->DebugString().c_str());
+ qDebug("method = %d:%s\n req = %s\n%s\n---->",
+ method->index(), method->name().c_str(),
+ method->input_type()->name().c_str(),
+ req->DebugString().c_str());
}
mpSocket->write(msg, PB_HDR_SIZE);
@@ -308,14 +314,18 @@ _top:
if (method != 13)
{
qDebug("client(%s): Received Msg <---- ", __FUNCTION__);
- qDebug("method = %d\nresp = \n%s\n---->",
- method, response->DebugString().c_str());
+ qDebug("method = %d:%s\nresp = %s\n%s\n---->",
+ method, this->method->name().c_str(),
+ this->method->output_type()->name().c_str(),
+ response->DebugString().c_str());
}
if (!response->IsInitialized())
{
qWarning("RpcChannel: missing required fields in response <----");
- qDebug("resp = \n%s", response->DebugString().c_str());
+ qDebug("resp = %s\n%s",
+ this->method->output_type()->name().c_str(),
+ response->DebugString().c_str());
qDebug("error = \n%s\n--->",
response->InitializationErrorString().c_str());
@@ -419,6 +429,7 @@ _top:
done->Run();
pendingMethodId = -1;
+ this->method = NULL;
controller = NULL;
response = NULL;
isPending = false;
@@ -428,9 +439,11 @@ _top:
{
RpcCall call = pendingCallList.takeFirst();
qDebug("RpcChannel: executing queued method <----\n"
- "method = %d\n"
- "req = \n%s\n---->",
- call.method->index(), call.request->DebugString().c_str());
+ "method = %d:%s\n"
+ "req = %s\n%s\n---->",
+ call.method->index(), call.method->name().c_str(),
+ call.method->input_type()->name().c_str(),
+ call.request->DebugString().c_str());
CallMethod(call.method, call.controller, call.request, call.response,
call.done);
}
@@ -475,6 +488,7 @@ void PbRpcChannel::on_mpSocket_disconnected()
qDebug("In %s", __FUNCTION__);
pendingMethodId = -1;
+ method = NULL;
controller = NULL;
response = NULL;
isPending = false;
diff --git a/rpc/pbrpcchannel.h b/rpc/pbrpcchannel.h
index 5c598f4..8c77412 100644
--- a/rpc/pbrpcchannel.h
+++ b/rpc/pbrpcchannel.h
@@ -49,6 +49,7 @@ class PbRpcChannel : public QObject, public ::google::protobuf::RpcChannel
/*! \todo (MED) : change controller, done and response to references
instead of pointers? */
+ const ::google::protobuf::MethodDescriptor *method;
::google::protobuf::RpcController *controller;
::google::protobuf::Closure *done;
::google::protobuf::Message *response;
diff --git a/rpc/rpcconn.cpp b/rpc/rpcconn.cpp
index bd42c3c..db1c428 100644
--- a/rpc/rpcconn.cpp
+++ b/rpc/rpcconn.cpp
@@ -28,6 +28,7 @@ along with this program. If not, see
#include
#include
+#include
#include
#include
#include
@@ -82,6 +83,7 @@ void RpcConnection::start()
return;
}
qDebug("clientSock Thread = %p", clientSock->thread());
+ qsrand(QDateTime::currentDateTime().toTime_t());
connId.setLocalData(new QString(id.arg(clientSock->peerAddress().toString())
.arg(clientSock->peerPort())));
diff --git a/server/abstractport.cpp b/server/abstractport.cpp
index 8c7d747..0a97ff9 100644
--- a/server/abstractport.cpp
+++ b/server/abstractport.cpp
@@ -21,8 +21,10 @@ along with this program. If not, see
#include "abstractport.h"
-#include "../common/streambase.h"
#include "../common/abstractprotocol.h"
+#include "../common/streambase.h"
+#include "devicemanager.h"
+#include "packetbuffer.h"
#include
#include
@@ -47,6 +49,8 @@ AbstractPort::AbstractPort(int id, const char *device)
linkState_ = OstProto::LinkStateUnknown;
minPacketSetSize_ = 1;
+ deviceManager_ = new DeviceManager(this);
+
maxStatsValue_ = ULLONG_MAX; // assume 64-bit stats
memset((void*) &stats_, 0, sizeof(stats_));
resetStats();
@@ -54,6 +58,7 @@ AbstractPort::AbstractPort(int id, const char *device)
AbstractPort::~AbstractPort()
{
+ delete deviceManager_;
}
void AbstractPort::init()
@@ -84,6 +89,11 @@ bool AbstractPort::modify(const OstProto::Port &port)
return ret;
}
+DeviceManager* AbstractPort::deviceManager()
+{
+ return deviceManager_;
+}
+
StreamBase* AbstractPort::streamAtIndex(int index)
{
Q_ASSERT(index < streamList_.size());
@@ -581,8 +591,8 @@ void AbstractPort::stats(PortStats *stats)
stats->rxBytes = (stats_.rxBytes >= epochStats_.rxBytes) ?
stats_.rxBytes - epochStats_.rxBytes :
stats_.rxBytes + (maxStatsValue_ - epochStats_.rxBytes);
- stats->rxPps = stats_.rxPps;
- stats->rxBps = stats_.rxBps;
+ stats->rxPps = stats_.rxPps;
+ stats->rxBps = stats_.rxBps;
stats->txPkts = (stats_.txPkts >= epochStats_.txPkts) ?
stats_.txPkts - epochStats_.txPkts :
@@ -590,8 +600,8 @@ void AbstractPort::stats(PortStats *stats)
stats->txBytes = (stats_.txBytes >= epochStats_.txBytes) ?
stats_.txBytes - epochStats_.txBytes :
stats_.txBytes + (maxStatsValue_ - epochStats_.txBytes);
- stats->txPps = stats_.txPps;
- stats->txBps = stats_.txBps;
+ stats->txPps = stats_.txPps;
+ stats->txBps = stats_.txBps;
stats->rxDrops = (stats_.rxDrops >= epochStats_.rxDrops) ?
stats_.rxDrops - epochStats_.rxDrops :
@@ -606,3 +616,82 @@ void AbstractPort::stats(PortStats *stats)
stats_.rxFrameErrors - epochStats_.rxFrameErrors :
stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors);
}
+
+void AbstractPort::clearDeviceNeighbors()
+{
+ deviceManager_->clearDeviceNeighbors();
+ isSendQueueDirty_ = true;
+}
+
+void AbstractPort::resolveDeviceNeighbors()
+{
+ // For a user triggered 'Resolve Neighbors', the behaviour we want is
+ // IP not in cache - send ARP/NDP request
+ // IP present in cache, but unresolved - re-send ARP/NDP request
+ // IP present in cache and resolved - don't sent ARP/NDP
+ //
+ // Device does not resend ARP/NDP requests if the IP address is
+ // already present in the cache, irrespective of whether it is
+ // resolved or not (this is done to avoid sending duplicate requests).
+ //
+ // So, to get the behaviour we want, let's clear all unresolved neighbors
+ // before calling resolve
+ deviceManager_->clearDeviceNeighbors(Device::kUnresolvedNeighbors);
+
+ // Resolve gateway for each device first ...
+ deviceManager_->resolveDeviceGateways();
+
+ // ... then resolve neighbor for each unique frame of each stream
+ // NOTE:
+ // 1. All the frames may have the same destination ip,but may have
+ // different source ip so may belong to a different emulated device;
+ // so we cannot optimize and send only one ARP
+ // 2. For a unidirectional stream, at egress, this will create ARP
+ // entries on the DUT for each of the source addresses
+ //
+ // TODO(optimization): Identify if stream does not vary in srcIp or dstIp
+ // - in which case resolve for only one frame of the stream
+ for (int i = 0; i < streamList_.size(); i++)
+ {
+ const StreamBase *stream = streamList_.at(i);
+ int frameCount = stream->frameVariableCount();
+
+ for (int j = 0; j < frameCount; j++) {
+ // we need the packet contents only uptil the L3 header
+ int pktLen = stream->frameValue(pktBuf_, kMaxL3PktSize, j);
+ if (pktLen) {
+ PacketBuffer pktBuf(pktBuf_, pktLen);
+ deviceManager_->resolveDeviceNeighbor(&pktBuf);
+ }
+ }
+ }
+ isSendQueueDirty_ = true;
+}
+
+quint64 AbstractPort::deviceMacAddress(int streamId, int frameIndex)
+{
+ // we need the packet contents only uptil the L3 header
+ StreamBase *s = stream(streamId);
+ int pktLen = s->frameValue(pktBuf_, kMaxL3PktSize, frameIndex);
+
+ if (pktLen) {
+ PacketBuffer pktBuf(pktBuf_, pktLen);
+ return deviceManager_->deviceMacAddress(&pktBuf);
+ }
+
+ return 0;
+}
+
+quint64 AbstractPort::neighborMacAddress(int streamId, int frameIndex)
+{
+ // we need the packet contents only uptil the L3 header
+ StreamBase *s = stream(streamId);
+ int pktLen = s->frameValue(pktBuf_, kMaxL3PktSize, frameIndex);
+
+ if (pktLen) {
+ PacketBuffer pktBuf(pktBuf_, pktLen);
+ return deviceManager_->neighborMacAddress(&pktBuf);
+ }
+
+ return 0;
+}
diff --git a/server/abstractport.h b/server/abstractport.h
index 3e99cb9..939e701 100644
--- a/server/abstractport.h
+++ b/server/abstractport.h
@@ -25,9 +25,14 @@ along with this program. If not, see
#include "../common/protocol.pb.h"
+class DeviceManager;
class StreamBase;
+class PacketBuffer;
class QIODevice;
+// TODO: send notification back to client(s)
+#define notify qWarning
+
class AbstractPort
{
public:
@@ -106,6 +111,17 @@ public:
void stats(PortStats *stats);
void resetStats() { epochStats_ = stats_; }
+ DeviceManager* deviceManager();
+ virtual void startDeviceEmulation() = 0;
+ virtual void stopDeviceEmulation() = 0;
+ virtual int sendEmulationPacket(PacketBuffer *pktBuf) = 0;
+
+ void clearDeviceNeighbors();
+ void resolveDeviceNeighbors();
+
+ quint64 deviceMacAddress(int streamId, int frameIndex);
+ quint64 neighborMacAddress(int streamId, int frameIndex);
+
protected:
void addNote(QString note);
@@ -122,12 +138,20 @@ protected:
struct PortStats stats_;
//! \todo Need lock for stats access/update
+ DeviceManager *deviceManager_;
+
private:
bool isSendQueueDirty_;
static const int kMaxPktSize = 16384;
uchar pktBuf_[kMaxPktSize];
+ // When finding a corresponding device for a packet, we need to inspect
+ // only uptil the L3 header; in the worst case this would be -
+ // mac (12) + 4 x vlan (16) + ethType (2) + ipv6 (40) = 74 bytes
+ // let's round it up to 80 bytes
+ static const int kMaxL3PktSize = 80;
+
/*! \note StreamBase::id() and index into streamList[] are NOT same! */
QList streamList_;
diff --git a/server/device.cpp b/server/device.cpp
new file mode 100644
index 0000000..6655af3
--- /dev/null
+++ b/server/device.cpp
@@ -0,0 +1,1118 @@
+/*
+Copyright (C) 2015 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
+*/
+
+#include "device.h"
+
+#include "../common/emulproto.pb.h"
+#include "devicemanager.h"
+#include "packetbuffer.h"
+
+#include
+#include
+
+const int kBaseHex = 16;
+const quint64 kBcastMac = 0xffffffffffffULL;
+const quint16 kEthTypeIp4 = 0x0800;
+const quint16 kEthTypeIp6 = 0x86dd;
+const int kIp6HdrLen = 40;
+const quint8 kIpProtoIcmp6 = 58;
+
+/*
+ * NOTE:
+ * 1. Device Key is (VLANS + MAC) - is assumed to be unique for a device
+ * 2. Device clients/users (viz. DeviceManager) should take care when
+ * setting params that change the key, if the key is used elsewhere
+ * (e.g. in a hash)
+ */
+
+inline quint32 sumUInt128(UInt128 value)
+{
+ quint8 *arr = value.toArray();
+ quint32 sum = 0;
+
+ for (int i = 0; i < 16; i += 2)
+ sum += qToBigEndian(*((quint16*)(arr + i)));
+
+ return sum;
+}
+
+inline bool isIp6Mcast(UInt128 ip)
+{
+ return (ip.hi64() >> 56) == 0xff;
+}
+
+Device::Device(DeviceManager *deviceManager)
+{
+ deviceManager_ = deviceManager;
+
+ for (int i = 0; i < kMaxVlan; i++)
+ vlan_[i] = 0;
+ numVlanTags_ = 0;
+ mac_ = 0;
+
+ hasIp4_ = false;
+ hasIp6_ = false;
+
+ clearKey();
+}
+
+void Device::setVlan(int index, quint16 vlan, quint16 tpid)
+{
+ int ofs;
+
+ if ((index < 0) || (index >= kMaxVlan)) {
+ qWarning("%s: vlan index %d out of range (0 - %d)", __FUNCTION__,
+ index, kMaxVlan - 1);
+ return;
+ }
+
+ vlan_[index] = (tpid << 16) | vlan;
+
+ ofs = index * sizeof(quint16);
+ key_[ofs] = vlan >> 8;
+ key_[ofs+1] = vlan & 0xff;
+
+ if (index >= numVlanTags_)
+ numVlanTags_ = index + 1;
+}
+
+quint64 Device::mac()
+{
+ return mac_;
+}
+
+void Device::setMac(quint64 mac)
+{
+ int ofs = kMaxVlan * sizeof(quint16);
+
+ mac_ = mac & ~(0xffffULL << 48);
+ memcpy(key_.data() + ofs, (char*)&mac_, sizeof(mac_));
+}
+
+void Device::setIp4(quint32 address, int prefixLength, quint32 gateway)
+{
+ ip4_ = address;
+ ip4PrefixLength_ = prefixLength;
+ ip4Gateway_ = gateway;
+ hasIp4_ = true;
+}
+
+void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway)
+{
+ ip6_ = address;
+ ip6PrefixLength_ = prefixLength;
+ ip6Gateway_ = gateway;
+ hasIp6_ = true;
+}
+
+void Device::getConfig(OstEmul::Device *deviceConfig)
+{
+ for (int i = 0; i < numVlanTags_; i++)
+ deviceConfig->add_vlan(vlan_[i]);
+ deviceConfig->set_mac(mac_);
+
+ if (hasIp4_) {
+ deviceConfig->set_ip4(ip4_);
+ deviceConfig->set_ip4_prefix_length(ip4PrefixLength_);
+ deviceConfig->set_ip4_default_gateway(ip4Gateway_);
+ }
+
+ if (hasIp6_) {
+ deviceConfig->mutable_ip6()->set_hi(ip6_.hi64());
+ deviceConfig->mutable_ip6()->set_lo(ip6_.lo64());
+ deviceConfig->set_ip6_prefix_length(ip6PrefixLength_);
+ deviceConfig->mutable_ip6_default_gateway()->set_hi(ip6Gateway_.hi64());
+ deviceConfig->mutable_ip6_default_gateway()->set_lo(ip6Gateway_.lo64());
+ }
+}
+
+QString Device::config()
+{
+ QString config;
+
+ for (int i = 0; i < numVlanTags_; i++) {
+ config.append(i == 0 ? "vlans=" : "|");
+ config.append(
+ (vlan_[i] >> 16) != kVlanTpid ?
+ QString("0x%1-%2")
+ .arg(vlan_[i] >> 16, 4, kBaseHex, QChar('0'))
+ .arg(vlan_[i] & 0xFFFF) :
+ QString("%1")
+ .arg(vlan_[i] & 0xFFFF));
+ }
+
+ config.append(QString(" mac=%1")
+ .arg(mac_, 12, kBaseHex, QChar('0')));
+ if (hasIp4_)
+ config.append(QString(" ip4=%1/%2")
+ .arg(QHostAddress(ip4_).toString())
+ .arg(ip4PrefixLength_));
+ if (hasIp6_)
+ config.append(QString(" ip6=%1/%2")
+ .arg(QHostAddress(ip6_.toArray()).toString())
+ .arg(ip6PrefixLength_));
+
+ return config;
+}
+
+DeviceKey Device::key()
+{
+ return key_;
+}
+
+void Device::clearKey()
+{
+ key_.fill(0, kMaxVlan * sizeof(quint16) + sizeof(quint64));
+}
+
+int Device::encapSize()
+{
+ // ethernet header + vlans
+ int size = 14 + 4*numVlanTags_;
+
+ return size;
+}
+
+void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type)
+{
+ int ofs;
+ quint64 srcMac = mac_;
+ uchar *p = pktBuf->push(encapSize());
+
+ if (!p) {
+ qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__,
+ encapSize(), pktBuf->head(), pktBuf->data());
+ goto _exit;
+ }
+
+ *(quint32*)(p ) = qToBigEndian(quint32(dstMac >> 16));
+ *(quint16*)(p + 4) = qToBigEndian(quint16(dstMac & 0xffff));
+ *(quint32*)(p + 6) = qToBigEndian(quint32(srcMac >> 16));
+ *(quint16*)(p + 10) = qToBigEndian(quint16(srcMac & 0xffff));
+ ofs = 12;
+ for (int i = 0; i < numVlanTags_; i++) {
+ *(quint32*)(p + ofs) = qToBigEndian(vlan_[i]);
+ ofs += 4;
+ }
+ *(quint16*)(p + ofs) = qToBigEndian(type);
+ ofs += 2;
+
+ Q_ASSERT(ofs == encapSize());
+
+_exit:
+ return;
+}
+
+// We expect pktBuf to point to EthType on entry
+void Device::receivePacket(PacketBuffer *pktBuf)
+{
+ quint16 ethType = qFromBigEndian(pktBuf->data());
+ pktBuf->pull(2);
+
+ qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
+
+ switch(ethType)
+ {
+ case 0x0806: // ARP
+ if (hasIp4_)
+ receiveArp(pktBuf);
+ break;
+
+ case 0x0800: // IPv4
+ if (hasIp4_)
+ receiveIp4(pktBuf);
+ break;
+
+ case 0x86dd: // IPv6
+ if (hasIp6_)
+ receiveIp6(pktBuf);
+ break;
+
+ default:
+ break;
+ }
+ // FIXME: temporary hack till DeviceManager clones pbufs
+ pktBuf->push(2);
+}
+
+void Device::transmitPacket(PacketBuffer *pktBuf)
+{
+ deviceManager_->transmitPacket(pktBuf);
+}
+
+void Device::resolveGateway()
+{
+ if (hasIp4_)
+ sendArpRequest(ip4Gateway_);
+
+ if (hasIp6_)
+ sendNeighborSolicit(ip6Gateway_);
+}
+
+void Device::clearNeighbors(Device::NeighborSet set)
+{
+ QMutableHashIterator arpIter(arpTable_);
+ QMutableHashIterator ndpIter(ndpTable_);
+
+ switch (set) {
+ case kAllNeighbors:
+ arpTable_.clear();
+ ndpTable_.clear();
+ break;
+
+ case kUnresolvedNeighbors:
+ while (arpIter.hasNext()) {
+ arpIter.next();
+ if (arpIter.value() == 0)
+ arpIter.remove();
+ }
+
+ while (ndpIter.hasNext()) {
+ ndpIter.next();
+ if (ndpIter.value() == 0)
+ ndpIter.remove();
+ }
+ break;
+ default:
+ Q_ASSERT(false); // Unreachable!
+ }
+}
+
+// Resolve the Neighbor IP address for this to-be-transmitted pktBuf
+// We expect pktBuf to point to EthType on entry
+void Device::resolveNeighbor(PacketBuffer *pktBuf)
+{
+ quint16 ethType = qFromBigEndian(pktBuf->data());
+ pktBuf->pull(2);
+
+ qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
+
+ switch(ethType)
+ {
+ case 0x0800: // IPv4
+ if (hasIp4_)
+ sendArpRequest(pktBuf);
+ break;
+
+ case 0x86dd: // IPv6
+ if (hasIp6_)
+ sendNeighborSolicit(pktBuf);
+ break;
+
+ default:
+ break;
+ }
+ // FIXME: temporary hack till DeviceManager clones pbufs
+ pktBuf->push(2);
+}
+
+// Append this device's neighbors to the list
+void Device::getNeighbors(OstEmul::DeviceNeighborList *neighbors)
+{
+ QList ip4List = arpTable_.keys();
+ QList ip6List = ndpTable_.keys();
+ QList macList;
+
+ macList = arpTable_.values();
+ Q_ASSERT(ip4List.size() == macList.size());
+
+ for (int i = 0; i < ip4List.size(); i++) {
+ OstEmul::ArpEntry *arp = neighbors->add_arp();
+ arp->set_ip4(ip4List.at(i));
+ arp->set_mac(macList.at(i));
+ }
+
+ macList = ndpTable_.values();
+ Q_ASSERT(ip6List.size() == macList.size());
+
+ for (int i = 0; i < ip6List.size(); i++) {
+ OstEmul::NdpEntry *ndp = neighbors->add_ndp();
+ ndp->mutable_ip6()->set_hi(ip6List.at(i).hi64());
+ ndp->mutable_ip6()->set_lo(ip6List.at(i).lo64());
+ ndp->set_mac(macList.at(i));
+ }
+}
+
+// Are we the source of the given packet?
+// We expect pktBuf to point to EthType on entry
+bool Device::isOrigin(const PacketBuffer *pktBuf)
+{
+ const uchar *pktData = pktBuf->data();
+ quint16 ethType = qFromBigEndian(pktData);
+
+ qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
+ pktData += 2;
+
+ // We know only about IP packets - adjust for ethType length (2 bytes)
+ // when checking that we have a complete IP header
+ if ((ethType == 0x0800) && hasIp4_) { // IPv4
+ int ipHdrLen = (pktData[0] & 0x0F) << 2;
+ quint32 srcIp;
+
+ if (pktBuf->length() < (ipHdrLen+2)) {
+ qDebug("incomplete IPv4 header: expected %d, actual %d",
+ ipHdrLen, pktBuf->length());
+ return false;
+ }
+
+ srcIp = qFromBigEndian(pktData + ipHdrLen - 8);
+ qDebug("%s: pktSrcIp/selfIp = 0x%x/0x%x", __FUNCTION__, srcIp, ip4_);
+ return (srcIp == ip4_);
+ }
+ else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6
+ UInt128 srcIp;
+ if (pktBuf->length() < (kIp6HdrLen+2)) {
+ qDebug("incomplete IPv6 header: expected %d, actual %d",
+ kIp6HdrLen, pktBuf->length()-2);
+ return false;
+ }
+
+ srcIp = qFromBigEndian(pktData + 8);
+ qDebug("%s: pktSrcIp6/selfIp6 = %llx-%llx/%llx-%llx", __FUNCTION__,
+ srcIp.hi64(), srcIp.lo64(), ip6_.hi64(), ip6_.lo64());
+ return (srcIp == ip6_);
+ }
+
+ return false;
+}
+
+// Return the mac address corresponding to the dstIp of the given packet
+// We expect pktBuf to point to EthType on entry
+quint64 Device::neighborMac(const PacketBuffer *pktBuf)
+{
+ const uchar *pktData = pktBuf->data();
+ quint16 ethType = qFromBigEndian(pktData);
+
+ qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
+ pktData += 2;
+
+ // We know only about IP packets
+ if ((ethType == 0x0800) && hasIp4_) { // IPv4
+ int ipHdrLen = (pktData[0] & 0x0F) << 2;
+ quint32 dstIp, tgtIp, mask;
+
+ if (pktBuf->length() < ipHdrLen) {
+ qDebug("incomplete IPv4 header: expected %d, actual %d",
+ ipHdrLen, pktBuf->length());
+ return false;
+ }
+
+ dstIp = qFromBigEndian(pktData + ipHdrLen - 4);
+ mask = ~0 << (32 - ip4PrefixLength_);
+ qDebug("dst %x mask %x self %x", dstIp, mask, ip4_);
+ tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_;
+
+ return arpTable_.value(tgtIp);
+ }
+ else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6
+ UInt128 dstIp, tgtIp, mask;
+
+ if (pktBuf->length() < (kIp6HdrLen+2)) {
+ qDebug("incomplete IPv6 header: expected %d, actual %d",
+ kIp6HdrLen, pktBuf->length()-2);
+ return false;
+ }
+
+ dstIp = qFromBigEndian(pktData + 24);
+ mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_);
+ qDebug("dst %s mask %s self %s",
+ qPrintable(QHostAddress(dstIp.toArray()).toString()),
+ qPrintable(QHostAddress(mask.toArray()).toString()),
+ qPrintable(QHostAddress(ip6_.toArray()).toString()));
+ tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_;
+
+ return ndpTable_.value(tgtIp);
+ }
+
+ return false;
+}
+
+//
+// Private Methods
+//
+/*
+ * ---------------------------------------------------------
+ * IPv4 related private methods
+ * ---------------------------------------------------------
+ */
+void Device::receiveArp(PacketBuffer *pktBuf)
+{
+ PacketBuffer *rspPkt;
+ uchar *pktData = pktBuf->data();
+ int offset = 0;
+ quint16 hwType, protoType;
+ quint8 hwAddrLen, protoAddrLen;
+ quint16 opCode;
+ quint64 srcMac, tgtMac;
+ quint32 srcIp, tgtIp;
+
+ // Extract tgtIp first to check quickly if this packet is for us or not
+ tgtIp = qFromBigEndian(pktData + 24);
+ if (tgtIp != ip4_) {
+ qDebug("tgtIp %s is not me %s",
+ qPrintable(QHostAddress(tgtIp).toString()),
+ qPrintable(QHostAddress(ip4_).toString()));
+ return;
+ }
+
+ // Extract annd verify ARP packet contents
+ hwType = qFromBigEndian(pktData + offset);
+ offset += 2;
+ if (hwType != 1) // Mac
+ goto _invalid_exit;
+
+ protoType = qFromBigEndian(pktData + offset);
+ offset += 2;
+ if (protoType != 0x0800) // IPv4
+ goto _invalid_exit;
+
+ hwAddrLen = pktData[offset];
+ offset += 1;
+ if (hwAddrLen != 6)
+ goto _invalid_exit;
+
+ protoAddrLen = pktData[offset];
+ offset += 1;
+ if (protoAddrLen != 4)
+ goto _invalid_exit;
+
+ opCode = qFromBigEndian(pktData + offset);
+ offset += 2;
+
+ srcMac = qFromBigEndian(pktData + offset);
+ offset += 4;
+ srcMac = (srcMac << 16) | qFromBigEndian(pktData + offset);
+ offset += 2;
+
+ srcIp = qFromBigEndian(pktData + offset);
+ offset += 4;
+
+ tgtMac = qFromBigEndian(pktData + offset);
+ offset += 4;
+ tgtMac = (tgtMac << 16) | qFromBigEndian(pktData + offset);
+ offset += 2;
+
+ switch (opCode)
+ {
+ case 1: // ARP Request
+ arpTable_.insert(srcIp, srcMac);
+
+ rspPkt = new PacketBuffer;
+ rspPkt->reserve(encapSize());
+ pktData = rspPkt->put(28);
+ if (pktData) {
+ // HTYP, PTYP
+ *(quint32*)(pktData ) = qToBigEndian(quint32(0x00010800));
+ // HLEN, PLEN, OPER
+ *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0x06040002));
+ // Source H/W Addr, Proto Addr
+ *(quint32*)(pktData+ 8) = qToBigEndian(quint32(mac_ >> 16));
+ *(quint16*)(pktData+12) = qToBigEndian(quint16(mac_ & 0xffff));
+ *(quint32*)(pktData+14) = qToBigEndian(ip4_);
+ // Target H/W Addr, Proto Addr
+ *(quint32*)(pktData+18) = qToBigEndian(quint32(srcMac >> 16));
+ *(quint16*)(pktData+22) = qToBigEndian(quint16(srcMac & 0xffff));
+ *(quint32*)(pktData+24) = qToBigEndian(srcIp);
+ }
+
+ encap(rspPkt, srcMac, 0x0806);
+ transmitPacket(rspPkt);
+
+ qDebug("Sent ARP Reply for srcIp/tgtIp=%s/%s",
+ qPrintable(QHostAddress(srcIp).toString()),
+ qPrintable(QHostAddress(tgtIp).toString()));
+ break;
+ case 2: // ARP Response
+ arpTable_.insert(srcIp, srcMac);
+ break;
+
+ default:
+ break;
+ }
+
+ return;
+
+_invalid_exit:
+ qWarning("Invalid ARP content");
+ return;
+}
+
+// Send ARP request for the IPv4 packet in pktBuf
+// pktBuf points to start of IP header
+void Device::sendArpRequest(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ int ipHdrLen = (pktData[0] & 0x0F) << 2;
+ quint32 srcIp = ip4_, dstIp, mask, tgtIp;
+
+ if (pktBuf->length() < ipHdrLen) {
+ qDebug("incomplete IPv4 header: expected %d, actual %d",
+ ipHdrLen, pktBuf->length());
+ return;
+ }
+
+ dstIp = qFromBigEndian(pktData + ipHdrLen - 4);
+
+ mask = ~0 << (32 - ip4PrefixLength_);
+ qDebug("dst %x src %x mask %x", dstIp, srcIp, mask);
+ tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip4Gateway_;
+
+ sendArpRequest(tgtIp);
+
+}
+
+void Device::sendArpRequest(quint32 tgtIp)
+{
+ quint32 srcIp = ip4_;
+ PacketBuffer *reqPkt;
+ uchar *pktData;
+
+ // Validate target IP
+ if (!tgtIp)
+ return;
+
+ // This function will be called once per unique stream - which
+ // may all have the same dst IP; even if dst IP are different the
+ // gateway for the different dst IP may all be same. However,
+ // we don't want to send duplicate ARP requests, so we check
+ // if the tgtIP is already in the cache (resolved or unresolved)
+ // and if so, we don't resend it
+ if (arpTable_.contains(tgtIp))
+ return;
+
+ reqPkt = new PacketBuffer;
+ reqPkt->reserve(encapSize());
+ pktData = reqPkt->put(28);
+ if (pktData) {
+ // HTYP, PTYP
+ *(quint32*)(pktData ) = qToBigEndian(quint32(0x00010800));
+ // HLEN, PLEN, OPER
+ *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0x06040001));
+ // Source H/W Addr, Proto Addr
+ *(quint32*)(pktData+ 8) = qToBigEndian(quint32(mac_ >> 16));
+ *(quint16*)(pktData+12) = qToBigEndian(quint16(mac_ & 0xffff));
+ *(quint32*)(pktData+14) = qToBigEndian(srcIp);
+ // Target H/W Addr, Proto Addr
+ *(quint32*)(pktData+18) = qToBigEndian(quint32(0));
+ *(quint16*)(pktData+22) = qToBigEndian(quint16(0));
+ *(quint32*)(pktData+24) = qToBigEndian(tgtIp);
+ }
+
+ encap(reqPkt, kBcastMac, 0x0806);
+ transmitPacket(reqPkt);
+ arpTable_.insert(tgtIp, 0);
+
+ qDebug("Sent ARP Request for srcIp/tgtIp=%s/%s",
+ qPrintable(QHostAddress(srcIp).toString()),
+ qPrintable(QHostAddress(tgtIp).toString()));
+}
+
+void Device::receiveIp4(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ uchar ipProto;
+ quint32 dstIp;
+
+ if (pktData[0] != 0x45) {
+ qDebug("%s: Unsupported IP version or options (%02x) ", __FUNCTION__,
+ pktData[0]);
+ goto _invalid_exit;
+ }
+
+ if (pktBuf->length() < 20) {
+ qDebug("incomplete IPv4 header: expected 20, actual %d",
+ pktBuf->length());
+ goto _invalid_exit;
+ }
+
+ // XXX: We don't verify IP Header checksum
+
+ dstIp = qFromBigEndian(pktData + 16);
+ if (dstIp != ip4_) {
+ qDebug("%s: dstIp %x is not me (%x)", __FUNCTION__, dstIp, ip4_);
+ goto _invalid_exit;
+ }
+
+ ipProto = pktData[9];
+ qDebug("%s: ipProto = %d", __FUNCTION__, ipProto);
+ switch (ipProto) {
+ case 1: // ICMP
+ pktBuf->pull(20);
+ receiveIcmp4(pktBuf);
+ break;
+ default:
+ qWarning("%s: Unsupported ipProto %d", __FUNCTION__, ipProto);
+ break;
+ }
+
+_invalid_exit:
+ return;
+}
+
+// This function assumes we are replying back to the same IP
+// that originally sent us the packet and therefore we can reuse the
+// ingress packet for egress; in other words, it assumes the
+// original IP header is intact and will just reuse it after
+// minimal modifications
+void Device::sendIp4Reply(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->push(20);
+ uchar origTtl = pktData[8];
+ uchar ipProto = pktData[9];
+ quint32 srcIp, dstIp;
+ quint32 sum;
+
+ // Swap src/dst IP addresses
+ dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt
+ srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt
+
+ if (!arpTable_.contains(dstIp)) {
+ qWarning("%s: mac not found for %s; unable to send IPv4 packet",
+ __FUNCTION__, qPrintable(QHostAddress(dstIp).toString()));
+ return;
+ }
+
+ *(quint32*)(pktData + 12) = qToBigEndian(srcIp);
+ *(quint32*)(pktData + 16) = qToBigEndian(dstIp);
+
+ // Reset TTL
+ pktData[8] = 64;
+
+ // Incremental checksum update (RFC 1624 [Eqn.3])
+ // HC' = ~(~HC + ~m + m')
+ sum = quint16(~qFromBigEndian(pktData + 10)); // old cksum
+ sum += quint16(~quint16(origTtl << 8 | ipProto)); // old value
+ sum += quint16(pktData[8] << 8 | ipProto); // new value
+ while(sum >> 16)
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ *(quint16*)(pktData + 10) = qToBigEndian(quint16(~sum));
+
+ encap(pktBuf, arpTable_.value(dstIp), 0x0800);
+ transmitPacket(pktBuf);
+}
+
+void Device::receiveIcmp4(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ quint32 sum;
+
+ // XXX: We don't verify icmp checksum
+
+ // We handle only ping request
+ if (pktData[0] != 8) { // Echo Request
+ qDebug("%s: Ignoring non echo request (%d)", __FUNCTION__, pktData[0]);
+ return;
+ }
+
+ pktData[0] = 0; // Echo Reply
+
+ // Incremental checksum update (RFC 1624 [Eqn.3])
+ // HC' = ~(~HC + ~m + m')
+ sum = quint16(~qFromBigEndian(pktData + 2)); // old cksum
+ sum += quint16(~quint16(8 << 8 | pktData[1])); // old value
+ sum += quint16(0 << 8 | pktData[1]); // new value
+ while(sum >> 16)
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ *(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum));
+
+ sendIp4Reply(pktBuf);
+ qDebug("Sent ICMP Echo Reply");
+}
+
+/*
+ * ---------------------------------------------------------
+ * IPV6 related private methods
+ * ---------------------------------------------------------
+ */
+
+void Device::receiveIp6(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ uchar ipProto;
+ UInt128 dstIp;
+
+ if ((pktData[0] & 0xF0) != 0x60) {
+ qDebug("%s: Unsupported IP version (%02x) ", __FUNCTION__,
+ pktData[0]);
+ goto _invalid_exit;
+ }
+
+ if (pktBuf->length() < kIp6HdrLen) {
+ qDebug("incomplete IPv6 header: expected %d, actual %d",
+ kIp6HdrLen, pktBuf->length());
+ goto _invalid_exit;
+ }
+
+ // FIXME: check for specific mcast address(es) instead of any mcast?
+ dstIp = qFromBigEndian(pktData + 24);
+ if (!isIp6Mcast(dstIp) && (dstIp != ip6_)) {
+ qDebug("%s: dstIp %s is not me (%s)", __FUNCTION__,
+ qPrintable(QHostAddress(dstIp.toArray()).toString()),
+ qPrintable(QHostAddress(ip6_.toArray()).toString()));
+ goto _invalid_exit;
+ }
+
+ ipProto = pktData[6];
+ switch (ipProto) {
+ case kIpProtoIcmp6:
+ pktBuf->pull(kIp6HdrLen);
+ receiveIcmp6(pktBuf);
+ break;
+ default:
+ break;
+ }
+
+_invalid_exit:
+ return;
+}
+
+// pktBuf should point to start of IP payload
+bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol)
+{
+ int payloadLen = pktBuf->length();
+ uchar *p = pktBuf->push(kIp6HdrLen);
+ quint64 dstMac = ndpTable_.value(dstIp);
+
+ if (!p) {
+ qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__,
+ kIp6HdrLen, pktBuf->head(), pktBuf->data());
+ goto _error_exit;
+ }
+
+ // In case of mcast, derive dstMac
+ if ((dstIp.hi64() >> 56) == 0xff)
+ dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff);
+
+ if (!dstMac) {
+ qWarning("%s: mac not found for %s; unable to send IPv6 packet",
+ __FUNCTION__,
+ qPrintable(QHostAddress(dstIp.toArray()).toString()));
+ goto _error_exit;
+ }
+
+ // Ver(4), TrfClass(8), FlowLabel(8)
+ *(quint32*)(p ) = qToBigEndian(quint32(0x60000000));
+ *(quint16*)(p+ 4) = qToBigEndian(quint16(payloadLen));
+ p[6] = protocol;
+ p[7] = 255; // HopLimit
+ memcpy(p+ 8, ip6_.toArray(), 16); // Source IP
+ memcpy(p+24, dstIp.toArray(), 16); // Destination IP
+
+ // FIXME: both these functions should return success/failure
+ encap(pktBuf, dstMac, kEthTypeIp6);
+ transmitPacket(pktBuf);
+
+ return true;
+
+_error_exit:
+ return false;
+}
+
+// This function assumes we are replying back to the same IP
+// that originally sent us the packet and therefore we can reuse the
+// ingress packet for egress; in other words, it assumes the
+// original IP header is intact and will just reuse it after
+// minimal modifications
+void Device::sendIp6Reply(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->push(kIp6HdrLen);
+ UInt128 srcIp, dstIp;
+
+ // Swap src/dst IP addresses
+ dstIp = qFromBigEndian(pktData + 8); // srcIp in original pkt
+ srcIp = qFromBigEndian(pktData + 24); // dstIp in original pkt
+
+ if (!ndpTable_.contains(dstIp)) {
+ qWarning("%s: mac not found for %s; unable to send IPv6 packet",
+ __FUNCTION__,
+ qPrintable(QHostAddress(dstIp.toArray()).toString()));
+ return;
+ }
+
+ memcpy(pktData + 8, srcIp.toArray(), 16); // Source IP
+ memcpy(pktData + 24, dstIp.toArray(), 16); // Destination IP
+
+ // Reset TTL
+ pktData[7] = 64;
+
+ encap(pktBuf, ndpTable_.value(dstIp), 0x86dd);
+ transmitPacket(pktBuf);
+}
+
+void Device::receiveIcmp6(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ quint8 type = pktData[0];
+ quint32 sum;
+
+ // XXX: We don't verify icmp checksum
+
+ switch (type) {
+ case 128: // ICMPv6 Echo Request
+ pktData[0] = 129; // Echo Reply
+
+ // Incremental checksum update (RFC 1624 [Eqn.3])
+ // HC' = ~(~HC + ~m + m')
+ sum = quint16(~qFromBigEndian(pktData + 2)); // old cksum
+ sum += quint16(~quint16(128 << 8 | pktData[1])); // old value
+ sum += quint16(129 << 8 | pktData[1]); // new value
+ while(sum >> 16)
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ *(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum));
+
+ sendIp6Reply(pktBuf);
+ qDebug("Sent ICMPv6 Echo Reply");
+ break;
+
+ case 135: // Neigh Solicit
+ case 136: // Neigh Advt
+ receiveNdp(pktBuf);
+ break;
+ default:
+ break;
+ }
+}
+
+void Device::receiveNdp(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ quint8 type = pktData[0];
+ int len = pktBuf->length();
+ int minLen = 24 + (type == 136 ? 8 : 0); // NA should have the Target TLV
+
+ if (len < minLen) {
+ qDebug("%s: incomplete NS/NA header: expected %d, actual %d",
+ __FUNCTION__, minLen, pktBuf->length());
+ goto _invalid_exit;
+ }
+
+ switch (type)
+ {
+ case 135: { // Neigh Solicit
+ // TODO: Validation as per RFC 4861
+ sendNeighborAdvertisement(pktBuf);
+ break;
+ }
+ case 136: { // Neigh Advt
+ quint8 flags = pktData[4];
+ const quint8 kSFlag = 0x40;
+ const quint8 kOFlag = 0x20;
+ UInt128 tgtIp = qFromBigEndian(pktData + 8);
+ quint64 mac = ndpTable_.value(tgtIp);
+
+ // Update NDP table only for solicited responses
+ if (!(flags & kSFlag))
+ break;
+
+ if ((flags & kOFlag) || (mac == 0)) {
+ // Check if we have a Target Link-Layer TLV
+ if ((pktData[24] != 2) || (pktData[25] != 1))
+ goto _invalid_exit;
+ mac = qFromBigEndian(pktData + 26);
+ mac = (mac << 16) | qFromBigEndian(pktData + 30);
+ ndpTable_.insert(tgtIp, mac);
+ }
+ break;
+ }
+ }
+_invalid_exit:
+ return;
+}
+
+// Send NS for the IPv6 packet in pktBuf
+// caller is responsible to check that pktBuf originates from this device
+// pktBuf should point to start of IP header
+void Device::sendNeighborSolicit(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ UInt128 srcIp = ip6_, dstIp, mask, tgtIp;
+
+ if (pktBuf->length() < kIp6HdrLen) {
+ qDebug("incomplete IPv6 header: expected %d, actual %d",
+ kIp6HdrLen, pktBuf->length());
+ return;
+ }
+
+ dstIp = qFromBigEndian(pktData + 24);
+
+ mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_);
+ qDebug("%s: dst %s src %s mask %s", __FUNCTION__,
+ qPrintable(QHostAddress(dstIp.toArray()).toString()),
+ qPrintable(QHostAddress(srcIp.toArray()).toString()),
+ qPrintable(QHostAddress(mask.toArray()).toString()));
+ tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip6Gateway_;
+
+ sendNeighborSolicit(tgtIp);
+}
+
+void Device::sendNeighborSolicit(UInt128 tgtIp)
+{
+ UInt128 dstIp, srcIp = ip6_;
+ PacketBuffer *reqPkt;
+ uchar *pktData;
+
+ // Validate target IP
+ if (tgtIp == UInt128(0, 0))
+ return;
+
+ // Do we already have a NDP entry (resolved or unresolved)?
+ // If so, don't resend (see note in sendArpRequest())
+ if (ndpTable_.contains(tgtIp))
+ return;
+
+ // Form the solicited node address to be used as dstIp
+ // ff02::1:ffXX:XXXX/104
+ dstIp = UInt128((quint64(0xff02) << 48),
+ (quint64(0x01ff) << 24) | (tgtIp.lo64() & 0xFFFFFF));
+
+ reqPkt = new PacketBuffer;
+ reqPkt->reserve(encapSize() + kIp6HdrLen);
+ pktData = reqPkt->put(32);
+ if (pktData) {
+ // Calculate checksum first -
+ // start with fixed fields in ICMP Header and IPv6 Pseudo Header ...
+ quint32 sum = 0x8700 + 0x0101 + 32 + kIpProtoIcmp6;
+
+ // then variable fields from ICMP header ...
+ sum += sumUInt128(tgtIp);
+ sum += (mac_ >> 32) + ((mac_ >> 16) & 0xffff) + (mac_ & 0xffff);
+
+ // and variable fields from IPv6 pseudo header
+ sum += sumUInt128(ip6_);
+ sum += sumUInt128(dstIp);
+
+ while(sum >> 16)
+ sum = (sum & 0xFFFF) + (sum >> 16);
+
+ // Type, Code
+ *(quint16*)(pktData ) = qToBigEndian(quint16(0x8700));
+ // Checksum
+ *(quint16*)(pktData+ 2) = qToBigEndian(quint16(~sum));
+ // Reserved
+ *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0));
+ // Target IP
+ memcpy(pktData+ 8, tgtIp.toArray(), 16);
+ // Source Addr TLV + MacAddr
+ *(quint16*)(pktData+24) = qToBigEndian(quint16(0x0101));
+ *(quint32*)(pktData+26) = qToBigEndian(quint32(mac_ >> 16));
+ *(quint16*)(pktData+30) = qToBigEndian(quint16(mac_ & 0xffff));
+ }
+
+ if (!sendIp6(reqPkt, dstIp , kIpProtoIcmp6))
+ return;
+
+ ndpTable_.insert(tgtIp, 0);
+
+ qDebug("Sent NDP Request for srcIp/tgtIp=%s/%s",
+ qPrintable(QHostAddress(srcIp.toArray()).toString()),
+ qPrintable(QHostAddress(tgtIp.toArray()).toString()));
+}
+
+// Send NA for the NS packet in pktBuf
+// pktBuf should point to start of ICMPv6 header
+void Device::sendNeighborAdvertisement(PacketBuffer *pktBuf)
+{
+ PacketBuffer *naPkt;
+ uchar *pktData = pktBuf->data();
+ quint16 flags = 0x6000; // solicit = 1; overide = 1
+ uchar *ip6Hdr;
+ UInt128 tgtIp, srcIp;
+
+ tgtIp = qFromBigEndian(pktData + 8);
+ if (tgtIp != ip6_) {
+ qDebug("%s: NS tgtIp %s is not us %s", __FUNCTION__,
+ qPrintable(QHostAddress(tgtIp.toArray()).toString()),
+ qPrintable(QHostAddress(ip6_.toArray()).toString()));
+ ip6Hdr = pktBuf->push(kIp6HdrLen);
+ return;
+ }
+
+ ip6Hdr = pktBuf->push(kIp6HdrLen);
+ srcIp = qFromBigEndian(ip6Hdr + 8);
+
+ if (srcIp == UInt128(0, 0)) {
+ // reset solicit flag
+ flags &= ~0x4000;
+ // NA should be sent to All nodes address
+ srcIp = UInt128(quint64(0xff02) << 48, quint64(1));
+ }
+ else if (pktBuf->length() >= 32) { // have TLVs?
+ if ((pktData[24] == 0x01) && (pktData[25] == 0x01)) { // Source TLV
+ quint64 mac;
+ mac = qFromBigEndian(pktData + 26);
+ mac = (mac << 16) | qFromBigEndian(pktData + 30);
+ ndpTable_.insert(srcIp, mac);
+ }
+ }
+
+ naPkt = new PacketBuffer;
+ naPkt->reserve(encapSize() + kIp6HdrLen);
+ pktData = naPkt->put(32);
+ if (pktData) {
+ // Calculate checksum first -
+ // start with fixed fields in ICMP Header and IPv6 Pseudo Header ...
+ quint32 sum = (0x8800 + flags + 0x0201) + (32 + kIpProtoIcmp6);
+
+ // then variable fields from ICMP header ...
+ sum += sumUInt128(tgtIp);
+ sum += (mac_ >> 32) + ((mac_ >> 16) & 0xffff) + (mac_ & 0xffff);
+
+ // and variable fields from IPv6 pseudo header
+ sum += sumUInt128(ip6_);
+ sum += sumUInt128(srcIp);
+
+ while(sum >> 16)
+ sum = (sum & 0xFFFF) + (sum >> 16);
+
+ // Type, Code
+ *(quint16*)(pktData ) = qToBigEndian(quint16(0x8800));
+ // Checksum
+ *(quint16*)(pktData+ 2) = qToBigEndian(quint16(~sum));
+ // Flags-Reserved
+ *(quint32*)(pktData+ 4) = qToBigEndian(quint32(flags << 16));
+ // Target IP
+ memcpy(pktData+ 8, tgtIp.toArray(), 16);
+ // Target Addr TLV + MacAddr
+ *(quint16*)(pktData+24) = qToBigEndian(quint16(0x0201));
+ *(quint32*)(pktData+26) = qToBigEndian(quint32(mac_ >> 16));
+ *(quint16*)(pktData+30) = qToBigEndian(quint16(mac_ & 0xffff));
+ }
+
+ if (!sendIp6(naPkt, srcIp , kIpProtoIcmp6))
+ return;
+
+ qDebug("Sent Neigh Advt to dstIp for tgtIp=%s/%s",
+ qPrintable(QHostAddress(srcIp.toArray()).toString()),
+ qPrintable(QHostAddress(tgtIp.toArray()).toString()));
+}
+
+bool operator<(const DeviceKey &a1, const DeviceKey &a2)
+{
+ int i = 0;
+
+ while (i < a1.size()) {
+ if (uchar(a1.at(i)) < uchar(a2.at(i)))
+ return true;
+ if (uchar(a1.at(i)) > uchar(a2.at(i)))
+ return false;
+ i++;
+ }
+
+ return false;
+}
diff --git a/server/device.h b/server/device.h
new file mode 100644
index 0000000..f77615b
--- /dev/null
+++ b/server/device.h
@@ -0,0 +1,124 @@
+/*
+Copyright (C) 2015 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
+*/
+
+#ifndef _DEVICE_H
+#define _DEVICE_H
+
+#include "../common/emulproto.pb.h"
+#include "../common/protocol.pb.h"
+#include "../common/uint128.h"
+
+#include
+#include
+
+class DeviceManager;
+class PacketBuffer;
+
+class DeviceKey: public QByteArray
+{
+};
+
+class Device
+{
+public:
+ static const quint16 kVlanTpid = 0x8100;
+
+ enum NeighborSet {
+ kAllNeighbors,
+ kUnresolvedNeighbors
+ };
+
+public:
+ Device(DeviceManager *deviceManager);
+
+ void setVlan(int index, quint16 vlan, quint16 tpid = kVlanTpid);
+ quint64 mac();
+ void setMac(quint64 mac);
+ void setIp4(quint32 address, int prefixLength, quint32 gateway);
+ void setIp6(UInt128 address, int prefixLength, UInt128 gateway);
+ void getConfig(OstEmul::Device *deviceConfig);
+ QString config();
+
+ DeviceKey key();
+ void clearKey();
+
+ int encapSize();
+ void encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type);
+
+ void receivePacket(PacketBuffer *pktBuf);
+ void transmitPacket(PacketBuffer *pktBuf);
+
+ void resolveGateway();
+
+ void clearNeighbors(Device::NeighborSet set);
+ void resolveNeighbor(PacketBuffer *pktBuf);
+ void getNeighbors(OstEmul::DeviceNeighborList *neighbors);
+
+ bool isOrigin(const PacketBuffer *pktBuf);
+ quint64 neighborMac(const PacketBuffer *pktBuf);
+
+private: // methods
+ void receiveArp(PacketBuffer *pktBuf);
+ void sendArpRequest(PacketBuffer *pktBuf);
+ void sendArpRequest(quint32 tgtIp);
+
+ void receiveIp4(PacketBuffer *pktBuf);
+ void sendIp4Reply(PacketBuffer *pktBuf);
+
+ void receiveIcmp4(PacketBuffer *pktBuf);
+
+ void receiveIp6(PacketBuffer *pktBuf);
+ bool sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol);
+ void sendIp6Reply(PacketBuffer *pktBuf);
+
+ void receiveIcmp6(PacketBuffer *pktBuf);
+
+ void receiveNdp(PacketBuffer *pktBuf);
+ void sendNeighborSolicit(PacketBuffer *pktBuf);
+ void sendNeighborSolicit(UInt128 tgtIp);
+ void sendNeighborAdvertisement(PacketBuffer *pktBuf);
+
+private: // data
+ static const int kMaxVlan = 4;
+
+ DeviceManager *deviceManager_;
+
+ int numVlanTags_;
+ quint32 vlan_[kMaxVlan];
+ quint64 mac_;
+
+ bool hasIp4_;
+ quint32 ip4_;
+ int ip4PrefixLength_;
+ quint32 ip4Gateway_;
+
+ bool hasIp6_;
+ UInt128 ip6_;
+ int ip6PrefixLength_;
+ UInt128 ip6Gateway_;
+
+ DeviceKey key_;
+
+ QHash arpTable_;
+ QHash ndpTable_;
+};
+
+bool operator<(const DeviceKey &a1, const DeviceKey &a2);
+#endif
+
diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp
new file mode 100644
index 0000000..95a2420
--- /dev/null
+++ b/server/devicemanager.cpp
@@ -0,0 +1,494 @@
+/*
+Copyright (C) 2015 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
+*/
+
+#include "devicemanager.h"
+
+#include "abstractport.h"
+#include "device.h"
+#include "../common/emulation.h"
+#include "packetbuffer.h"
+
+#include "../common/emulproto.pb.h"
+
+#include
+
+#define __STDC_FORMAT_MACROS
+#include
+
+const quint64 kBcastMac = 0xffffffffffffULL;
+
+inline UInt128 UINT128(OstEmul::Ip6Address x)
+{
+ return UInt128(x.hi(), x.lo());
+}
+
+inline bool isMacMcast(quint64 mac)
+{
+ return (mac >> 40) & 0x01 == 0x01;
+}
+
+
+// XXX: Port owning DeviceManager already uses locks, so we don't use any
+// locks within DeviceManager to protect deviceGroupList_ et.al.
+
+DeviceManager::DeviceManager(AbstractPort *parent)
+{
+ port_ = parent;
+}
+
+DeviceManager::~DeviceManager()
+{
+ foreach(Device *dev, deviceList_)
+ delete dev;
+
+ foreach(OstProto::DeviceGroup *devGrp, deviceGroupList_)
+ delete devGrp;
+}
+
+int DeviceManager::deviceGroupCount()
+{
+ return deviceGroupList_.size();
+}
+
+const OstProto::DeviceGroup* DeviceManager::deviceGroupAtIndex(int index)
+{
+ if ((index < 0) || (index >= deviceGroupCount())) {
+ qWarning("%s: index %d out of range (0 - %d)", __FUNCTION__,
+ index, deviceGroupCount() - 1);
+ return NULL;
+ }
+
+ // Sort List by 'id', get the id at 'index' and then corresponding devGrp
+ return deviceGroupList_.value(deviceGroupList_.uniqueKeys().value(index));
+}
+
+const OstProto::DeviceGroup* DeviceManager::deviceGroup(uint deviceGroupId)
+{
+ return deviceGroupList_.value(deviceGroupId);
+}
+
+bool DeviceManager::addDeviceGroup(uint deviceGroupId)
+{
+ OstProto::DeviceGroup *deviceGroup;
+
+ if (deviceGroupList_.contains(deviceGroupId)) {
+ qWarning("%s: deviceGroup id %u already exists", __FUNCTION__,
+ deviceGroupId);
+ return false;
+ }
+
+ deviceGroup = newDeviceGroup(port_->id());
+ deviceGroup->mutable_device_group_id()->set_id(deviceGroupId);
+ deviceGroupList_.insert(deviceGroupId, deviceGroup);
+
+ enumerateDevices(deviceGroup, kAdd);
+
+ // Start emulation when first device is added
+ if ((deviceCount() == 1) && port_)
+ port_->startDeviceEmulation();
+
+ return true;
+}
+
+bool DeviceManager::deleteDeviceGroup(uint deviceGroupId)
+{
+ OstProto::DeviceGroup *deviceGroup;
+ if (!deviceGroupList_.contains(deviceGroupId)) {
+ qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__,
+ deviceGroupId);
+ return false;
+ }
+
+ deviceGroup = deviceGroupList_.take(deviceGroupId);
+ enumerateDevices(deviceGroup, kDelete);
+ delete deviceGroup;
+
+ // Stop emulation if no devices remain
+ if ((deviceCount() == 0) && port_)
+ port_->stopDeviceEmulation();
+
+ return true;
+}
+
+bool DeviceManager::modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup)
+{
+ quint32 id = deviceGroup->device_group_id().id();
+ OstProto::DeviceGroup *myDeviceGroup = deviceGroupList_.value(id);
+ if (!myDeviceGroup) {
+ qWarning("%s: deviceGroup id %u does not exist", __FUNCTION__, id);
+ return false;
+ }
+
+ enumerateDevices(myDeviceGroup, kDelete);
+
+ myDeviceGroup->CopyFrom(*deviceGroup);
+ // If mac step is 0, silently override to 1 - otherwise we won't have
+ // unique DeviceKeys
+ if (myDeviceGroup->GetExtension(OstEmul::mac).step() == 0)
+ myDeviceGroup->MutableExtension(OstEmul::mac)->set_step(1);
+ // Default value for ip6 step should be 1 (not 0)
+ if (myDeviceGroup->HasExtension(OstEmul::ip6)
+ && !myDeviceGroup->GetExtension(OstEmul::ip6).has_step())
+ myDeviceGroup->MutableExtension(OstEmul::ip6)
+ ->mutable_step()->set_lo(1);
+
+ enumerateDevices(myDeviceGroup, kAdd);
+
+ return true;
+}
+
+int DeviceManager::deviceCount()
+{
+ return deviceList_.size();
+}
+
+void DeviceManager::getDeviceList(
+ OstProto::PortDeviceList *deviceList)
+{
+ foreach(Device *device, sortedDeviceList_) {
+ OstEmul::Device *dev =
+ deviceList->AddExtension(OstEmul::device);
+ device->getConfig(dev);
+ }
+}
+
+void DeviceManager::receivePacket(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ int offset = 0;
+ Device dk(this);
+ Device *device;
+ quint64 dstMac;
+ quint16 ethType;
+ quint16 vlan;
+ int idx = 0;
+
+ // We assume pkt is ethernet
+ // TODO: extend for other link layer types
+
+ // All frames we are interested in should be at least 32 bytes
+ if (pktBuf->length() < 32) {
+ qWarning("short frame of %d bytes, skipping ...", pktBuf->length());
+ goto _exit;
+ }
+
+ // Extract dstMac
+ dstMac = qFromBigEndian(pktData + offset);
+ offset += 4;
+ dstMac = (dstMac << 16) | qFromBigEndian(pktData + offset);
+
+ qDebug("dstMac %012" PRIx64, dstMac);
+
+ // XXX: Treat multicast as bcast
+ if (isMacMcast(dstMac))
+ dstMac = kBcastMac;
+
+ dk.setMac(dstMac);
+ offset += 2;
+
+ // Skip srcMac - don't care
+ offset += 6;
+
+_eth_type:
+ // Extract EthType
+ ethType = qFromBigEndian(pktData + offset);
+ qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
+
+ if (tpidList_.contains(ethType)) {
+ offset += 2;
+ vlan = qFromBigEndian(pktData + offset);
+ dk.setVlan(idx++, vlan);
+ offset += 2;
+ qDebug("%s: idx: %d vlan %d", __FUNCTION__, idx, vlan);
+ goto _eth_type;
+ }
+
+ pktBuf->pull(offset);
+
+ if (dstMac == kBcastMac) {
+ QList list = bcastList_.values(dk.key());
+ // FIXME: We need to clone the pktBuf before passing to each
+ // device, otherwise only the first device gets the original
+ // packet - all subsequent ones get the modified packet!
+ // NOTE: modification may not be in the pkt data buffer but
+ // in the HDTE pointers - which is bad as well!
+ foreach(Device *device, list)
+ device->receivePacket(pktBuf);
+ goto _exit;
+ }
+
+ // Is it destined for us?
+ device = deviceList_.value(dk.key());
+ if (!device) {
+ qDebug("%s: dstMac %012llx is not us", __FUNCTION__, dstMac);
+ goto _exit;
+ }
+
+ device->receivePacket(pktBuf);
+
+_exit:
+ delete pktBuf;
+}
+
+void DeviceManager::transmitPacket(PacketBuffer *pktBuf)
+{
+ port_->sendEmulationPacket(pktBuf);
+}
+
+void DeviceManager::resolveDeviceGateways()
+{
+ foreach(Device *device, deviceList_) {
+ device->resolveGateway();
+ }
+}
+
+void DeviceManager::clearDeviceNeighbors(Device::NeighborSet set)
+{
+ foreach(Device *device, deviceList_)
+ device->clearNeighbors(set);
+}
+
+void DeviceManager::getDeviceNeighbors(
+ OstProto::PortNeighborList *neighborList)
+{
+ int count = 0;
+
+ foreach(Device *device, sortedDeviceList_) {
+ OstEmul::DeviceNeighborList *neighList =
+ neighborList->AddExtension(OstEmul::device_neighbor);
+ neighList->set_device_index(count++);
+ device->getNeighbors(neighList);
+ }
+}
+
+void DeviceManager::resolveDeviceNeighbor(PacketBuffer *pktBuf)
+{
+ Device *device = originDevice(pktBuf);
+
+ if (device)
+ device->resolveNeighbor(pktBuf);
+}
+
+quint64 DeviceManager::deviceMacAddress(PacketBuffer *pktBuf)
+{
+ Device *device = originDevice(pktBuf);
+
+ return device ? device->mac() : 0;
+}
+
+quint64 DeviceManager::neighborMacAddress(PacketBuffer *pktBuf)
+{
+ Device *device = originDevice(pktBuf);
+
+ return device ? device->neighborMac(pktBuf) : 0;
+}
+
+// ------------------------------------ //
+// Private Methods
+// ------------------------------------ //
+
+Device* DeviceManager::originDevice(PacketBuffer *pktBuf)
+{
+ uchar *pktData = pktBuf->data();
+ int offset = 12; // start parsing after mac addresses
+ Device dk(this);
+ quint16 ethType;
+ quint16 vlan;
+ int idx = 0;
+
+ // Do we have any devices at all?
+ if (!deviceCount())
+ return NULL;
+
+ // pktBuf will not have the correct dstMac populated, so use bcastMac
+ // and search for device by IP
+
+ dk.setMac(kBcastMac);
+
+_eth_type:
+ ethType = qFromBigEndian(pktData + offset);
+ qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType);
+
+ if (tpidList_.contains(ethType)) {
+ offset += 2;
+ vlan = qFromBigEndian(pktData + offset);
+ dk.setVlan(idx++, vlan);
+ offset += 2;
+ qDebug("%s: idx: %d vlan %d", __FUNCTION__, idx, vlan);
+ goto _eth_type;
+ }
+
+ pktBuf->pull(offset);
+
+ foreach(Device *device, bcastList_.values(dk.key())) {
+ if (device->isOrigin(pktBuf))
+ return device;
+ }
+
+ qDebug("couldn't find origin device for packet");
+ return NULL;
+}
+
+void DeviceManager::enumerateDevices(
+ const OstProto::DeviceGroup *deviceGroup,
+ Operation oper)
+{
+ Device dk(this);
+ OstEmul::VlanEmulation pbVlan = deviceGroup->encap()
+ .GetExtension(OstEmul::vlan);
+ int numTags = pbVlan.stack_size();
+ int n = 1;
+ QList vlanCount;
+
+ bool hasIp4 = deviceGroup->HasExtension(OstEmul::ip4);
+ bool hasIp6 = deviceGroup->HasExtension(OstEmul::ip6);
+ OstEmul::MacEmulation mac = deviceGroup->GetExtension(OstEmul::mac);
+ OstEmul::Ip4Emulation ip4 = deviceGroup->GetExtension(OstEmul::ip4);
+ OstEmul::Ip6Emulation ip6 = deviceGroup->GetExtension(OstEmul::ip6);
+
+ /*
+ * vlanCount[] stores the number of unique vlans at each tag level
+ * e.g. for a 3-tag config with 2, 3, 4 vlans at each level respectively
+ * vlanCount = [24, 12, 4]
+ * 0 - 0, 0, 0
+ * 1 - 0, 0, 1
+ * 2 - 0, 0, 2
+ * 3 - 0, 0, 3
+ * 4 - 0, 1, 0
+ * 5 - 0, 1, 1
+ * 6 - 0, 1, 2
+ * 7 - 0, 1, 3
+ * 8 - 0, 2, 0
+ * 9 - 0, 2, 1
+ * 10 - 0, 2, 2
+ * 11 - 0, 2, 3
+ * 12 - 1, 0, 0
+ * 13 - 1, 0, 1
+ * 14 - 1, 0, 2
+ * 15 - 1, 0, 3
+ * 16 - 1, 1, 0
+ * 17 - 1, 1, 1
+ * 18 - 1, 1, 2
+ * 19 - 1, 1, 3
+ * 21 - 1, 2, 0
+ * 21 - 1, 2, 1
+ * 22 - 1, 2, 2
+ * 23 - 1, 2, 3
+ *
+ * Note that vlanCount[0] repesents total-number-of-vlans
+ *
+ * Another way to think about this is that at a particular vlan tag
+ * level, we need to repeat a particular vlan-id as many times as the
+ * next level's count before we can increment the vlan-id at that level
+ *
+ * We use this list to calculate the vlan ids for each tag level for
+ * all the vlans.
+ *
+ * For implementation convenience we append a '1' as the last element
+ */
+ vlanCount.append(n);
+ for (int i = numTags - 1; i >= 0 ; i--) {
+ OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(i);
+ n *= vlan.count();
+ vlanCount.prepend(n);
+
+ // Update TPID list
+ switch (oper) {
+ case kAdd:
+ tpidList_[vlan.tpid()]++;
+ break;
+ case kDelete:
+ tpidList_[vlan.tpid()]--;
+ if (tpidList_[vlan.tpid()] == 0)
+ tpidList_.remove(vlan.tpid());
+ break;
+ default:
+ Q_ASSERT(0); // Unreachable
+ }
+ }
+
+ QHash::const_iterator iter = tpidList_.constBegin();
+ qDebug("Port %s TPID List:", port_->name());
+ while (iter != tpidList_.constEnd()) {
+ qDebug("tpid: %x (%d)", iter.key(), iter.value());
+ iter++;
+ }
+
+ for (int i = 0; i < vlanCount.at(0); i++) {
+ for (int j = 0; j < numTags; j++) {
+ OstEmul::VlanEmulation::Vlan vlan = pbVlan.stack(j);
+ quint16 vlanAdd = (i/vlanCount.at(j+1) % vlan.count())*vlan.step();
+
+ dk.setVlan(j, vlan.vlan_tag() + vlanAdd, vlan.tpid());
+ }
+
+ for (uint k = 0; k < deviceGroup->device_count(); k++) {
+ Device *device;
+ quint64 macAdd = k * mac.step();
+ quint32 ip4Add = k * ip4.step();
+ UInt128 ip6Add = UINT128(ip6.step()) * k;
+
+ dk.setMac(mac.address() + macAdd);
+ if (hasIp4)
+ dk.setIp4(ip4.address() + ip4Add,
+ ip4.prefix_length(),
+ ip4.default_gateway());
+ if (hasIp6)
+ dk.setIp6(UINT128(ip6.address()) + ip6Add,
+ ip6.prefix_length(),
+ UINT128(ip6.default_gateway()));
+
+ switch (oper) {
+ case kAdd:
+ if (deviceList_.contains(dk.key())) {
+ qWarning("%s: error adding device %s (EEXIST)",
+ __FUNCTION__, qPrintable(dk.config()));
+ break;
+ }
+ device = new Device(this);
+ *device = dk;
+ deviceList_.insert(dk.key(), device);
+ sortedDeviceList_.insert(dk.key(), device);
+
+ dk.setMac(kBcastMac);
+ bcastList_.insert(dk.key(), device);
+ qDebug("enumerate(add): %s", qPrintable(device->config()));
+ break;
+
+ case kDelete:
+ device = deviceList_.take(dk.key());
+ if (!device) {
+ qWarning("%s: error deleting device %s (NOTFOUND)",
+ __FUNCTION__, qPrintable(dk.config()));
+ break;
+ }
+ qDebug("enumerate(del): %s", qPrintable(device->config()));
+ delete device;
+ sortedDeviceList_.take(dk.key()); // already freed above
+
+ dk.setMac(kBcastMac);
+ bcastList_.take(dk.key()); // device already freed above
+ break;
+
+ default:
+ Q_ASSERT(0); // Unreachable
+ }
+ } // foreach device
+ } // foreach vlan
+}
diff --git a/server/devicemanager.h b/server/devicemanager.h
new file mode 100644
index 0000000..92d4cd0
--- /dev/null
+++ b/server/devicemanager.h
@@ -0,0 +1,82 @@
+/*
+Copyright (C) 2015 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
+*/
+
+#ifndef _DEVICE_MANAGER_H
+#define _DEVICE_MANAGER_H
+
+#include "device.h"
+
+#include
+#include
+#include
+#include
+
+class AbstractPort;
+class PacketBuffer;
+namespace OstProto {
+ class DeviceGroup;
+};
+
+class DeviceManager
+{
+public:
+ DeviceManager(AbstractPort *parent = 0);
+ ~DeviceManager();
+
+ int deviceGroupCount();
+ const OstProto::DeviceGroup* deviceGroupAtIndex(int index);
+ const OstProto::DeviceGroup* deviceGroup(uint deviceGroupId);
+
+ bool addDeviceGroup(uint deviceGroupId);
+ bool deleteDeviceGroup(uint deviceGroupId);
+ bool modifyDeviceGroup(const OstProto::DeviceGroup *deviceGroup);
+
+ int deviceCount();
+ void getDeviceList(OstProto::PortDeviceList *deviceList);
+
+ void receivePacket(PacketBuffer *pktBuf);
+ void transmitPacket(PacketBuffer *pktBuf);
+
+ void resolveDeviceGateways();
+
+ void clearDeviceNeighbors(Device::NeighborSet set = Device::kAllNeighbors);
+ void resolveDeviceNeighbor(PacketBuffer *pktBuf);
+ void getDeviceNeighbors(OstProto::PortNeighborList *neighborList);
+
+ quint64 deviceMacAddress(PacketBuffer *pktBuf);
+ quint64 neighborMacAddress(PacketBuffer *pktBuf);
+
+private:
+ enum Operation { kAdd, kDelete };
+
+ Device* originDevice(PacketBuffer *pktBuf);
+ void enumerateDevices(
+ const OstProto::DeviceGroup *deviceGroup,
+ Operation oper);
+
+ AbstractPort *port_;
+ QHash deviceGroupList_;
+ QHash deviceList_; // fast access to devices
+ QMap sortedDeviceList_; // sorted access to devices
+ QMultiHash bcastList_;
+ QHash tpidList_; // Key: TPID, Value: RefCount
+};
+
+#endif
+
diff --git a/server/drone.cpp b/server/drone.cpp
index 8f8b632..1bdba20 100644
--- a/server/drone.cpp
+++ b/server/drone.cpp
@@ -69,3 +69,8 @@ bool Drone::init()
return true;
}
+
+MyService* Drone::rpcService()
+{
+ return service;
+}
diff --git a/server/drone.h b/server/drone.h
index ab05fe1..a12f1e7 100644
--- a/server/drone.h
+++ b/server/drone.h
@@ -32,6 +32,7 @@ public:
Drone(QObject *parent = 0);
~Drone();
bool init();
+ MyService* rpcService();
private:
RpcServer *rpcServer;
diff --git a/server/drone.pro b/server/drone.pro
index 5022a5b..42f324e 100644
--- a/server/drone.pro
+++ b/server/drone.pro
@@ -33,6 +33,8 @@ LIBS += -lprotobuf
HEADERS += drone.h \
myservice.h
SOURCES += \
+ devicemanager.cpp \
+ device.cpp \
drone_main.cpp \
drone.cpp \
portmanager.cpp \
@@ -43,6 +45,7 @@ SOURCES += \
winpcapport.cpp
SOURCES += myservice.cpp
SOURCES += pcapextra.cpp
+SOURCES += packetbuffer.cpp
QMAKE_DISTCLEAN += object_script.*
diff --git a/server/drone_main.cpp b/server/drone_main.cpp
index e5c1abd..f80e62d 100644
--- a/server/drone_main.cpp
+++ b/server/drone_main.cpp
@@ -35,6 +35,7 @@ extern ProtocolManager *OstProtocolManager;
extern char *version;
extern char *revision;
+Drone *drone;
QSettings *appSettings;
int myport;
@@ -47,7 +48,6 @@ int main(int argc, char *argv[])
{
int exitCode = 0;
QCoreApplication app(argc, argv);
- Drone *drone;
// TODO: command line options
// -v (--version)
diff --git a/server/myservice.cpp b/server/myservice.cpp
index 0a4f7ca..3d8531c 100644
--- a/server/myservice.cpp
+++ b/server/myservice.cpp
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2010 Srivats P.
+Copyright (C) 2010-2015 Srivats P.
This file is part of "Ostinato"
@@ -20,6 +20,8 @@ along with this program. If not, see
#include "myservice.h"
+#include "drone.h"
+
#if 0
#include
#include
@@ -31,11 +33,14 @@ along with this program. If not, see
#include "../common/streambase.h"
#include "../rpc/pbrpccontroller.h"
+#include "device.h"
+#include "devicemanager.h"
#include "portmanager.h"
#include
+extern Drone *drone;
extern char *version;
MyService::MyService()
@@ -239,7 +244,7 @@ void MyService::addStream(::google::protobuf::RpcController* controller,
// Append a new "default" stream - actual contents of the new stream is
// expected in a subsequent "modifyStream" request - set the stream id
// now itself however!!!
- stream = new StreamBase;
+ stream = new StreamBase(portId);
stream->setId(request->stream_id(i).id());
portInfo[portId]->addStream(stream);
}
@@ -358,6 +363,8 @@ void MyService::startTransmit(::google::protobuf::RpcController* /*controller*/,
continue; //! \todo (LOW): partial RPC?
portLock[portId]->lockForWrite();
+ if (portInfo[portId]->isDirty())
+ portInfo[portId]->updatePacketList();
portInfo[portId]->startTransmit();
portLock[portId]->unlock();
}
@@ -591,3 +598,378 @@ _invalid_version:
controller->SetFailed("invalid version information");
done->Run();
}
+
+/*
+ * ===================================================================
+ * Device Emulation
+ * ===================================================================
+ * XXX: Streams and Devices are largely non-overlapping from a RPC
+ * point of view but they *do* intersect e.g. when a stream is trying to
+ * find its associated device and info from that device such as src/dst
+ * mac addresses. For this reason, both set of RPCs share the common
+ * port level locking
+ * ===================================================================
+ */
+void MyService::getDeviceGroupIdList(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortId* request,
+ ::OstProto::DeviceGroupIdList* response,
+ ::google::protobuf::Closure* done)
+{
+ DeviceManager *devMgr;
+ int portId;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ portId = request->id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ goto _invalid_port;
+
+ devMgr = portInfo[portId]->deviceManager();
+
+ response->mutable_port_id()->set_id(portId);
+ portLock[portId]->lockForRead();
+ for (int i = 0; i < devMgr->deviceGroupCount(); i++)
+ {
+ OstProto::DeviceGroupId *dgid;
+
+ dgid = response->add_device_group_id();
+ dgid->CopyFrom(devMgr->deviceGroupAtIndex(i)->device_group_id());
+ }
+ portLock[portId]->unlock();
+
+ done->Run();
+ return;
+
+_invalid_port:
+ controller->SetFailed("Invalid Port Id");
+ done->Run();
+}
+
+void MyService::getDeviceGroupConfig(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupIdList* request,
+ ::OstProto::DeviceGroupConfigList* response,
+ ::google::protobuf::Closure* done)
+{
+ DeviceManager *devMgr;
+ int portId;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ portId = request->port_id().id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ goto _invalid_port;
+
+ devMgr = portInfo[portId]->deviceManager();
+
+ response->mutable_port_id()->set_id(portId);
+ portLock[portId]->lockForRead();
+ for (int i = 0; i < request->device_group_id_size(); i++)
+ {
+ const OstProto::DeviceGroup *dg;
+
+ dg = devMgr->deviceGroup(request->device_group_id(i).id());
+ if (!dg)
+ continue; //! \todo(LOW): Partial status of RPC
+
+ response->add_device_group()->CopyFrom(*dg);
+ }
+ portLock[portId]->unlock();
+
+ done->Run();
+ return;
+
+_invalid_port:
+ controller->SetFailed("invalid portid");
+ done->Run();
+}
+
+void MyService::addDeviceGroup(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupIdList* request,
+ ::OstProto::Ack* /*response*/,
+ ::google::protobuf::Closure* done)
+{
+ DeviceManager *devMgr;
+ int portId;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ portId = request->port_id().id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ goto _invalid_port;
+
+ devMgr = portInfo[portId]->deviceManager();
+
+ if (portInfo[portId]->isTransmitOn())
+ goto _port_busy;
+
+ portLock[portId]->lockForWrite();
+ for (int i = 0; i < request->device_group_id_size(); i++)
+ {
+ quint32 id = request->device_group_id(i).id();
+ const OstProto::DeviceGroup *dg = devMgr->deviceGroup(id);
+
+ // If device group with same id as in request exists already ==> error!
+ if (dg)
+ continue; //! \todo (LOW): Partial status of RPC
+
+ devMgr->addDeviceGroup(id);
+ }
+ portLock[portId]->unlock();
+
+ //! \todo (LOW): fill-in response "Ack"????
+
+ done->Run();
+ return;
+
+_port_busy:
+ controller->SetFailed("Port Busy");
+ goto _exit;
+
+_invalid_port:
+ controller->SetFailed("invalid portid");
+_exit:
+ done->Run();
+}
+
+void MyService::deleteDeviceGroup(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupIdList* request,
+ ::OstProto::Ack* /*response*/,
+ ::google::protobuf::Closure* done)
+{
+ DeviceManager *devMgr;
+ int portId;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ portId = request->port_id().id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ goto _invalid_port;
+
+ devMgr = portInfo[portId]->deviceManager();
+
+ if (portInfo[portId]->isTransmitOn())
+ goto _port_busy;
+
+ portLock[portId]->lockForWrite();
+ for (int i = 0; i < request->device_group_id_size(); i++)
+ devMgr->deleteDeviceGroup(request->device_group_id(i).id());
+ portLock[portId]->unlock();
+
+ //! \todo (LOW): fill-in response "Ack"????
+
+ done->Run();
+ return;
+
+_port_busy:
+ controller->SetFailed("Port Busy");
+ goto _exit;
+_invalid_port:
+ controller->SetFailed("invalid portid");
+_exit:
+ done->Run();
+}
+
+void MyService::modifyDeviceGroup(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupConfigList* request,
+ ::OstProto::Ack* /*response*/,
+ ::google::protobuf::Closure* done)
+{
+ DeviceManager *devMgr;
+ int portId;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ portId = request->port_id().id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ goto _invalid_port;
+
+ devMgr = portInfo[portId]->deviceManager();
+
+ if (portInfo[portId]->isTransmitOn())
+ goto _port_busy;
+
+ portLock[portId]->lockForWrite();
+ for (int i = 0; i < request->device_group_size(); i++)
+ devMgr->modifyDeviceGroup(&request->device_group(i));
+ portLock[portId]->unlock();
+
+ //! \todo(LOW): fill-in response "Ack"????
+
+ done->Run();
+ return;
+
+_port_busy:
+ controller->SetFailed("Port Busy");
+ goto _exit;
+_invalid_port:
+ controller->SetFailed("invalid portid");
+_exit:
+ done->Run();
+}
+
+void MyService::getDeviceList(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortId* request,
+ ::OstProto::PortDeviceList* response,
+ ::google::protobuf::Closure* done)
+{
+ DeviceManager *devMgr;
+ int portId;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ portId = request->id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ goto _invalid_port;
+
+ devMgr = portInfo[portId]->deviceManager();
+
+ response->mutable_port_id()->set_id(portId);
+ portLock[portId]->lockForRead();
+ devMgr->getDeviceList(response);
+ portLock[portId]->unlock();
+
+ done->Run();
+ return;
+
+_invalid_port:
+ controller->SetFailed("Invalid Port Id");
+ done->Run();
+}
+
+void MyService::resolveDeviceNeighbors(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortIdList* request,
+ ::OstProto::Ack* response,
+ ::google::protobuf::Closure* done)
+{
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ for (int i = 0; i < request->port_id_size(); i++)
+ {
+ int portId;
+
+ portId = request->port_id(i).id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ continue; //! \todo (LOW): partial RPC?
+
+ portLock[portId]->lockForWrite();
+ portInfo[portId]->resolveDeviceNeighbors();
+ portLock[portId]->unlock();
+ }
+
+ //! \todo (LOW): fill-in response "Ack"????
+
+ done->Run();
+}
+
+void MyService::clearDeviceNeighbors(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortIdList* request,
+ ::OstProto::Ack* response,
+ ::google::protobuf::Closure* done)
+{
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ for (int i = 0; i < request->port_id_size(); i++)
+ {
+ int portId;
+
+ portId = request->port_id(i).id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ continue; //! \todo (LOW): partial RPC?
+
+ portLock[portId]->lockForWrite();
+ portInfo[portId]->clearDeviceNeighbors();
+ portLock[portId]->unlock();
+ }
+
+ //! \todo (LOW): fill-in response "Ack"????
+
+ done->Run();
+}
+
+void MyService::getDeviceNeighbors(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortId* request,
+ ::OstProto::PortNeighborList* response,
+ ::google::protobuf::Closure* done)
+{
+ DeviceManager *devMgr;
+ int portId;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+ portId = request->id();
+ if ((portId < 0) || (portId >= portInfo.size()))
+ goto _invalid_port;
+
+ devMgr = portInfo[portId]->deviceManager();
+
+ response->mutable_port_id()->set_id(portId);
+ portLock[portId]->lockForRead();
+ devMgr->getDeviceNeighbors(response);
+ portLock[portId]->unlock();
+
+ done->Run();
+ return;
+
+_invalid_port:
+ controller->SetFailed("Invalid Port Id");
+ done->Run();
+}
+
+/*
+ * ===================================================================
+ * Friends
+ * TODO: Encap these global functions into a DeviceBroker singleton?
+ * ===================================================================
+ */
+quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex)
+{
+ MyService *service = drone->rpcService();
+ DeviceManager *devMgr = NULL;
+ quint64 mac;
+
+ if (!service)
+ return 0;
+
+ if ((portId >= 0) && (portId < service->portInfo.size()))
+ devMgr = service->portInfo[portId]->deviceManager();
+
+ if (!devMgr || !devMgr->deviceCount())
+ return 0;
+
+ service->portLock[portId]->lockForWrite();
+ mac = service->portInfo[portId]->deviceMacAddress(streamId, frameIndex);
+ service->portLock[portId]->unlock();
+
+ return mac;
+}
+
+quint64 getNeighborMacAddress(int portId, int streamId, int frameIndex)
+{
+ MyService *service = drone->rpcService();
+ DeviceManager *devMgr = NULL;
+ quint64 mac;
+
+ if (!service)
+ return 0;
+
+ if ((portId >= 0) && (portId < service->portInfo.size()))
+ devMgr = service->portInfo[portId]->deviceManager();
+
+ if (!devMgr || !devMgr->deviceCount())
+ return 0;
+
+ service->portLock[portId]->lockForWrite();
+ mac = service->portInfo[portId]->neighborMacAddress(streamId, frameIndex);
+ service->portLock[portId]->unlock();
+
+ return mac;
+}
diff --git a/server/myservice.h b/server/myservice.h
index ea0cc78..acd8c63 100644
--- a/server/myservice.h
+++ b/server/myservice.h
@@ -105,6 +105,59 @@ public:
::OstProto::VersionCompatibility* response,
::google::protobuf::Closure* done);
+ // DeviceGroup and Protocol Emulation
+ virtual void getDeviceGroupIdList(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortId* request,
+ ::OstProto::DeviceGroupIdList* response,
+ ::google::protobuf::Closure* done);
+ virtual void getDeviceGroupConfig(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupIdList* request,
+ ::OstProto::DeviceGroupConfigList* response,
+ ::google::protobuf::Closure* done);
+ virtual void addDeviceGroup(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupIdList* request,
+ ::OstProto::Ack* response,
+ ::google::protobuf::Closure* done);
+ virtual void deleteDeviceGroup(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupIdList* request,
+ ::OstProto::Ack* response,
+ ::google::protobuf::Closure* done);
+ virtual void modifyDeviceGroup(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::DeviceGroupConfigList* request,
+ ::OstProto::Ack* response,
+ ::google::protobuf::Closure* done);
+
+ virtual void getDeviceList(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortId* request,
+ ::OstProto::PortDeviceList* response,
+ ::google::protobuf::Closure* done);
+
+ virtual void resolveDeviceNeighbors(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortIdList* request,
+ ::OstProto::Ack* response,
+ ::google::protobuf::Closure* done);
+ virtual void clearDeviceNeighbors(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortIdList* request,
+ ::OstProto::Ack* response,
+ ::google::protobuf::Closure* done);
+ virtual void getDeviceNeighbors(
+ ::google::protobuf::RpcController* controller,
+ const ::OstProto::PortId* request,
+ ::OstProto::PortNeighborList* response,
+ ::google::protobuf::Closure* done);
+
+ friend quint64 getDeviceMacAddress(
+ int portId, int streamId, int frameIndex);
+ friend quint64 getNeighborMacAddress(
+ int portId, int streamId, int frameIndex);
signals:
void notification(int notifType, SharedProtobufMessage notifData);
diff --git a/server/packetbuffer.cpp b/server/packetbuffer.cpp
new file mode 100644
index 0000000..0480de7
--- /dev/null
+++ b/server/packetbuffer.cpp
@@ -0,0 +1,110 @@
+/*
+Copyright (C) 2015 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
+*/
+
+#include "packetbuffer.h"
+
+// PacketBuffer with full control
+PacketBuffer::PacketBuffer(int size)
+{
+ if (size == 0)
+ size = 1600;
+
+ buffer_ = new uchar[size];
+ is_own_buffer_ = true;
+
+ head_ = data_ = tail_ = buffer_;
+ end_ = head_ + size;
+}
+
+// PacketBuffer wrapping already existing const buffer
+PacketBuffer::PacketBuffer(const uchar *buffer, int size)
+{
+ // FIXME: ugly const_cast hack!!
+ buffer_ = const_cast(buffer);
+ is_own_buffer_ = false;
+
+ head_ = data_ = buffer_;
+ tail_ = end_ = buffer_ + size;
+}
+
+PacketBuffer::~PacketBuffer()
+{
+ if (is_own_buffer_)
+ delete[] buffer_;
+}
+
+int PacketBuffer::length() const
+{
+ return tail_ - data_;
+}
+
+uchar* PacketBuffer::head() const
+{
+ return head_;
+}
+
+uchar* PacketBuffer::data() const
+{
+ return data_;
+}
+
+uchar* PacketBuffer::tail() const
+{
+ return tail_;
+}
+
+uchar* PacketBuffer::end() const
+{
+ return end_;
+}
+
+void PacketBuffer::reserve(int len)
+{
+ data_ += len;
+ tail_ += len;
+}
+
+uchar* PacketBuffer::pull(int len)
+{
+ if ((tail_ - data_) < len)
+ return NULL;
+ data_ += len;
+
+ return data_;
+}
+
+uchar* PacketBuffer::push(int len)
+{
+ if ((data_ - head_) < len)
+ return NULL;
+ data_ -= len;
+
+ return data_;
+}
+
+uchar* PacketBuffer::put(int len)
+{
+ uchar *oldTail = tail_;
+
+ if ((end_ - tail_) < len)
+ return NULL;
+ tail_ += len;
+
+ return oldTail;
+}
diff --git a/server/packetbuffer.h b/server/packetbuffer.h
new file mode 100644
index 0000000..5b7fbe7
--- /dev/null
+++ b/server/packetbuffer.h
@@ -0,0 +1,51 @@
+/*
+Copyright (C) 2015 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
+*/
+
+#ifndef _PACKET_BUFFER_H
+#define _PACKET_BUFFER_H
+
+#include
+
+class PacketBuffer
+{
+public:
+ PacketBuffer(int size = 0);
+ PacketBuffer(const uchar *buffer, int size);
+ ~PacketBuffer();
+
+ int length() const;
+
+ uchar* head() const;
+ uchar* data() const;
+ uchar* tail() const;
+ uchar* end() const;
+
+ void reserve(int len);
+ uchar* pull(int len);
+ uchar* push(int len);
+ uchar* put(int len);
+
+private:
+ uchar *buffer_;
+ bool is_own_buffer_;
+ uchar *head_, *data_, *tail_, *end_;
+};
+
+#endif
+
diff --git a/server/pcapport.cpp b/server/pcapport.cpp
index d690553..45024a1 100644
--- a/server/pcapport.cpp
+++ b/server/pcapport.cpp
@@ -19,6 +19,9 @@ along with this program. If not, see
#include "pcapport.h"
+#include "devicemanager.h"
+#include "packetbuffer.h"
+
#include
#ifdef Q_OS_WIN32
@@ -81,6 +84,7 @@ PcapPort::PcapPort(int id, const char *device)
monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_);
transmitter_ = new PortTransmitter(device);
capturer_ = new PortCapturer(device);
+ emulXcvr_ = new EmulationTransceiver(device, deviceManager_);
if (!monitorRx_->handle() || !monitorTx_->handle())
isUsable_ = false;
@@ -97,12 +101,8 @@ PcapPort::PcapPort(int id, const char *device)
{
if (strcmp(device, dev->name) == 0)
{
-#ifdef Q_OS_WIN32
- data_.set_name(QString("if%1").arg(id).toStdString());
-#else
if (dev->name)
data_.set_name(dev->name);
-#endif
if (dev->description)
data_.set_description(dev->description);
@@ -133,6 +133,7 @@ PcapPort::~PcapPort()
if (monitorTx_)
monitorTx_->stop();
+ delete emulXcvr_;
delete capturer_;
delete transmitter_;
@@ -176,6 +177,26 @@ bool PcapPort::setRateAccuracy(AbstractPort::Accuracy accuracy)
return false;
}
+void PcapPort::startDeviceEmulation()
+{
+ emulXcvr_->start();
+}
+
+void PcapPort::stopDeviceEmulation()
+{
+ emulXcvr_->stop();
+}
+
+int PcapPort::sendEmulationPacket(PacketBuffer *pktBuf)
+{
+ return emulXcvr_->transmitPacket(pktBuf);
+}
+
+/*
+ * ------------------------------------------------------------------- *
+ * Port Monitor
+ * ------------------------------------------------------------------- *
+ */
PcapPort::PortMonitor::PortMonitor(const char *device, Direction direction,
AbstractPort::PortStats *stats)
{
@@ -321,6 +342,11 @@ void PcapPort::PortMonitor::stop()
pcap_breakloop(handle());
}
+/*
+ * ------------------------------------------------------------------- *
+ * Port Transmitter
+ * ------------------------------------------------------------------- *
+ */
PcapPort::PortTransmitter::PortTransmitter(const char *device)
{
char errbuf[PCAP_ERRBUF_SIZE] = "";
@@ -750,6 +776,11 @@ void PcapPort::PortTransmitter::udelay(unsigned long usec)
#endif
}
+/*
+ * ------------------------------------------------------------------- *
+ * Port Capturer
+ * ------------------------------------------------------------------- *
+ */
PcapPort::PortCapturer::PortCapturer(const char *device)
{
device_ = QString::fromAscii(device);
@@ -884,3 +915,218 @@ QFile* PcapPort::PortCapturer::captureFile()
{
return &capFile_;
}
+
+
+/*
+ * ------------------------------------------------------------------- *
+ * Transmit+Receiver for Device/ProtocolEmulation
+ * ------------------------------------------------------------------- *
+ */
+PcapPort::EmulationTransceiver::EmulationTransceiver(const char *device,
+ DeviceManager *deviceManager)
+{
+ device_ = QString::fromAscii(device);
+ deviceManager_ = deviceManager;
+ stop_ = false;
+ state_ = kNotStarted;
+ handle_ = NULL;
+}
+
+PcapPort::EmulationTransceiver::~EmulationTransceiver()
+{
+ stop();
+}
+
+void PcapPort::EmulationTransceiver::run()
+{
+ int flags = PCAP_OPENFLAG_PROMISCUOUS;
+ char errbuf[PCAP_ERRBUF_SIZE] = "";
+ struct bpf_program bpf;
+#if 0
+ const char *capture_filter =
+ "arp or icmp or icmp6 or "
+ "(vlan and (arp or icmp or icmp6)) or "
+ "(vlan and vlan and (arp or icmp or icmp6)) or "
+ "(vlan and vlan and vlan and (arp or icmp or icmp6)) or "
+ "(vlan and vlan and vlan and vlan and (arp or icmp or icmp6))";
+/*
+ Ideally we should use the above filter, but the 'vlan' capture filter
+ in libpcap is implemented as a kludge. From the pcap-filter man page -
+
+ vlan [vlan_id]
+ Note that the first vlan keyword encountered in expression changes
+ the decoding offsets for the remainder of expression on the
+ assumption that the packet is a VLAN packet.
+
+ The vlan [vlan_id] expression may be used more than once, to filter on
+ VLAN hierarchies. Each use of that expression increments the filter
+ offsets by 4.
+
+ See https://ask.wireshark.org/questions/31953/unusual-behavior-with-stacked-vlan-tags-and-capture-filter
+
+ So we use the modified filter expression that works as we intend. If ever
+ libpcap changes their implementation, this will need to change as well.
+*/
+#else
+ const char *capture_filter =
+ "arp or icmp or icmp6 or "
+ "(vlan and (arp or icmp or icmp6)) or "
+ "(vlan and (arp or icmp or icmp6)) or "
+ "(vlan and (arp or icmp or icmp6)) or "
+ "(vlan and (arp or icmp or icmp6))";
+#endif
+
+ const int optimize = 1;
+
+ qDebug("In %s", __PRETTY_FUNCTION__);
+
+#ifdef Q_OS_WIN32
+ flags |= PCAP_OPENFLAG_NOCAPTURE_LOCAL;
+#endif
+
+_retry:
+#ifdef Q_OS_WIN32
+ // NOCAPTURE_LOCAL needs windows only pcap_open()
+ handle_ = pcap_open(qPrintable(device_), 65535,
+ flags, 100 /* ms */, NULL, errbuf);
+#else
+ handle_ = pcap_open_live(qPrintable(device_), 65535,
+ flags, 100 /* ms */, errbuf);
+#endif
+
+ if (handle_ == NULL)
+ {
+ if (flags && QString(errbuf).contains("promiscuous"))
+ {
+ notify("Unable to set promiscuous mode on <%s> - "
+ "device emulation will not work", qPrintable(device_));
+ goto _exit;
+ }
+#ifdef Q_OS_WIN32
+ else if ((flags & PCAP_OPENFLAG_NOCAPTURE_LOCAL)
+ && QString(errbuf).contains("loopback"))
+ {
+ qDebug("Can't set no local capture mode %s", qPrintable(device_));
+ flags &= ~PCAP_OPENFLAG_NOCAPTURE_LOCAL;
+ goto _retry;
+ }
+#endif
+ else
+ {
+ notify("Unable to open <%s> [%s] - device emulation will not work",
+ qPrintable(device_), errbuf);
+ goto _exit;
+ }
+ }
+
+ // TODO: for now the filter is hardcoded to accept tagged/untagged
+ // ARP/NDP or ICMPv4/v6; when more protocols are added, we may need
+ // to derive this filter based on which protocols are configured
+ // on the devices
+ if (pcap_compile(handle_, &bpf, capture_filter, optimize, 0) < 0)
+ {
+ qWarning("%s: error compiling filter: %s", qPrintable(device_),
+ pcap_geterr(handle_));
+ goto _skip_filter;
+ }
+
+ if (pcap_setfilter(handle_, &bpf) < 0)
+ {
+ qWarning("%s: error setting filter: %s", qPrintable(device_),
+ pcap_geterr(handle_));
+ goto _skip_filter;
+ }
+
+_skip_filter:
+ state_ = kRunning;
+ while (1)
+ {
+ int ret;
+ struct pcap_pkthdr *hdr;
+ const uchar *data;
+
+ ret = pcap_next_ex(handle_, &hdr, &data);
+ switch (ret)
+ {
+ case 1:
+ {
+ PacketBuffer *pktBuf = new PacketBuffer(data, hdr->caplen);
+#if 0
+ for (int i = 0; i < 64; i++) {
+ printf("%02x ", data[i]);
+ if (i % 16 == 0)
+ printf("\n");
+ }
+ printf("\n");
+#endif
+ // XXX: deviceManager should free pktBuf before returning
+ // from this call; if it needs to process the pkt async
+ // it should make a copy as the pktBuf's data buffer is
+ // owned by libpcap which does not guarantee data will
+ // persist across calls to pcap_next_ex()
+ deviceManager_->receivePacket(pktBuf);
+ break;
+ }
+ case 0:
+ // timeout: just go back to the loop
+ break;
+ case -1:
+ qWarning("%s: error reading packet (%d): %s",
+ __PRETTY_FUNCTION__, ret, pcap_geterr(handle_));
+ break;
+ case -2:
+ default:
+ qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__,
+ ret);
+ }
+
+ if (stop_)
+ {
+ qDebug("user requested receiver stop\n");
+ break;
+ }
+ }
+ pcap_close(handle_);
+ handle_ = NULL;
+ stop_ = false;
+
+_exit:
+ state_ = kFinished;
+}
+
+void PcapPort::EmulationTransceiver::start()
+{
+ if (state_ == kRunning) {
+ qWarning("Receive start requested but is already running!");
+ return;
+ }
+
+ state_ = kNotStarted;
+ QThread::start();
+
+ while (state_ == kNotStarted)
+ QThread::msleep(10);
+}
+
+void PcapPort::EmulationTransceiver::stop()
+{
+ if (state_ == kRunning) {
+ stop_ = true;
+ while (state_ == kRunning)
+ QThread::msleep(10);
+ }
+ else {
+ qWarning("Receive stop requested but is not running!");
+ return;
+ }
+}
+
+bool PcapPort::EmulationTransceiver::isRunning()
+{
+ return (state_ == kRunning);
+}
+
+int PcapPort::EmulationTransceiver::transmitPacket(PacketBuffer *pktBuf)
+{
+ return pcap_sendpacket(handle_, pktBuf->data(), pktBuf->length());
+}
diff --git a/server/pcapport.h b/server/pcapport.h
index 7776a8e..e0c20cf 100644
--- a/server/pcapport.h
+++ b/server/pcapport.h
@@ -70,6 +70,10 @@ public:
virtual bool isCaptureOn() { return capturer_->isRunning(); }
virtual QIODevice* captureData() { return capturer_->captureFile(); }
+ virtual void startDeviceEmulation();
+ virtual void stopDeviceEmulation();
+ virtual int sendEmulationPacket(PacketBuffer *pktBuf);
+
protected:
enum Direction
{
@@ -227,6 +231,32 @@ protected:
volatile State state_;
};
+ class EmulationTransceiver: public QThread
+ {
+ public:
+ EmulationTransceiver(const char *device, DeviceManager *deviceManager);
+ ~EmulationTransceiver();
+ void run();
+ void start();
+ void stop();
+ bool isRunning();
+ int transmitPacket(PacketBuffer *pktBuf);
+
+ private:
+ enum State
+ {
+ kNotStarted,
+ kRunning,
+ kFinished
+ };
+
+ QString device_;
+ DeviceManager *deviceManager_;
+ volatile bool stop_;
+ pcap_t *handle_;
+ volatile State state_;
+ };
+
PortMonitor *monitorRx_;
PortMonitor *monitorTx_;
@@ -235,6 +265,7 @@ protected:
private:
PortTransmitter *transmitter_;
PortCapturer *capturer_;
+ EmulationTransceiver *emulXcvr_;
static pcap_if_t *deviceList_;
};
diff --git a/test/TODO.md b/test/TODO.md
new file mode 100644
index 0000000..c6002b5
--- /dev/null
+++ b/test/TODO.md
@@ -0,0 +1,46 @@
+# TODO - Test Cases
+
+## Session Save/Open
+ * Verify save session prompts before opening Save Dialog about only reserved ports being saved if there are some reserved ports and not prompted at all if no ports are reserved
+ * Verify each save session triggers the file dialog at the last path used
+ * Verify saved session file is correct
+ * All portgroups are saved
+ * All suitable (wrt reservation) ports are saved
+ * All port configuration is saved
+ * For each port -
+ * All streams are saved with correct contents
+ * All deviceGroups are saved with correct contents
+ * OSSN Session file format tests (TODO: expand)
+ * Verify no file is saved if user clicks 'Cancel' on the progress dialog while saving session file
+
+ * On open session, verify user is prompted before opening the file dialog if there are existing portgroups and not prompted at all if there are no port groups
+ * Verify each open session triggers the file dialog at the last path used
+ * Verify open file dialog file filter has `(*.ssn *.*)`
+ * Verify opening a unsupported format file triggers an error and existing session is not changed
+ * Verify all existing portgroups are removed before new ones from the file are created and configured
+ * Verify that only ports in the opened session file are overwritten and other ports are not changed
+ * Verify that if port in the opened session file was reserved at save time, and the same port is now reserved by someone else, it is not changed and user is informed; if current port reservation is by self, port is overwritten with contents from the session file; all reservations made by open session are with self username, not the username who had reserved the port during save time (in other words, allow session files to be exchanged between users)
+ * Verify no unnecessary RPCs during open session
+ * if port has no existing streams, deleteStreams() is not invoked
+ * if port has no existing deviceGroups, deleteDeviceGroups() is not invoked
+ * if port config has no change, modifyPort() is not invoked
+ * if opened port has no streams, addStreams()/modifyStreams() is not invoked
+ * if opened port has no deviceGroups, addDeviceGroups()/modifyDeviceGroups() is not invoked
+ * Verify open session is successful
+ * All streams are restored with correct contents
+ * All deviceGroups are restored with correct contents
+ * Port config (TxMode, ExclusiveMode, UserName) is changed, if required
+ * OSSN Session file format tests (TODO: expand)
+ * Verify no change in existing port groups if user clicks 'Cancel' on the options dialog
+ * Verify no change in existing port groups if user clicks 'Cancel' on the progress dialog while opening session file
+ * Verify all old portgroups are removed before new portgroups from the session file are added
+ * Verify config of portGroups loaded from the session file are correct
+ * Verify config of a portGroup is NOT restored to the config saved in session file after a open session - disconnect - connect
+
+## Streams Save/Open
+ * Verify save file dialog file filter has all supported types but no `*.*`
+ * Stream file format tests (TODO: expand)
+
+ * Verify open file dialog file filter has all supported types except `*.py` and `*.*`
+ * Verify opening a unsupported format file triggers an error and existing streams are not changed
+ * Stream file format tests (TODO: expand)
diff --git a/test/emultest.py b/test/emultest.py
new file mode 100644
index 0000000..1124354
--- /dev/null
+++ b/test/emultest.py
@@ -0,0 +1,1265 @@
+#! /usr/bin/env python
+
+# standard modules
+import ipaddress
+import logging
+import os
+import re
+import subprocess
+import sys
+import time
+
+import pytest
+
+from fabric.api import run, env, sudo
+
+from utils import get_tshark
+
+sys.path.insert(1, '../binding')
+from core import ost_pb, emul, DroneProxy
+from rpc import RpcError
+from protocols.mac_pb2 import mac, Mac
+from protocols.ip4_pb2 import ip4, Ip4
+from protocols.ip6_pb2 import ip6, Ip6
+from protocols.vlan_pb2 import vlan
+
+use_defaults = True
+
+tshark = get_tshark(minversion = '1.6')
+if tshark is None:
+ print 'tshark >= 1.6 not found'
+ sys.exit(1)
+
+# initialize defaults - drone
+host_name = '127.0.0.1'
+
+# initialize defaults - DUT
+env.use_shell = False
+env.user = 'tc'
+env.password = 'tc'
+env.host_string = 'localhost:50022'
+
+# setup logging
+log = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+# command-line option/arg processing
+if len(sys.argv) > 1:
+ if sys.argv[1] in ('-d', '--use-defaults'):
+ use_defaults = True
+ if sys.argv[1] in ('-h', '--help'):
+ print('%s [OPTION]...' % (sys.argv[0]))
+ print('Options:')
+ print(' -d --use-defaults run using default values')
+ print(' -h --help show this help')
+ sys.exit(0)
+
+print('This module uses the following topology -')
+print('')
+print(' +-------+ +-------+')
+print(' | |Tx--->---Rx|-+ |')
+print(' | Drone | | v DUT |')
+print(' | |Rx---<---Tx|-+ |')
+print(' +-------+ +-------+')
+print('')
+print('Drone has 2 ports connected to DUT. Packets sent on the Tx port')
+print('are expected to be forwarded by the DUT and received back on the')
+print('Rx port')
+print('')
+
+if not use_defaults:
+ s = raw_input('Drone\'s Hostname/IP [%s]: ' % (host_name))
+ host_name = s or host_name
+ s = raw_input('DUT\'s Hostname/IP [%s]: ' % (env.host_string))
+ env.host_string = s or env.host_string
+ # FIXME: get inputs for dut rx/tx ports
+
+
+# Convenience class to interwork with OstEmul::Ip6Address() and
+# the python ipaddress module
+class ip6_address(ipaddress.IPv6Interface):
+ def __init__(self, addr):
+ if type(addr) is str:
+ super(ip6_address, self).__init__(unicode(addr))
+ elif type(addr) is int:
+ super(ip6_address, self).__init__(addr)
+ else:
+ super(ip6_address, self).__init__(addr.hi << 64 | addr.lo)
+ self.ip6 = emul.Ip6Address()
+ self.ip6.hi = int(self) >> 64
+ self.ip6.lo = int(self) & 0xffffffffffffffff
+
+ self.prefixlen = self.network.prefixlen
+
+ # we assume gateway is the lowest IP host address in the network
+ gateway = self.network.network_address + 1
+ self.gateway = emul.Ip6Address()
+ self.gateway.hi = int(gateway) >> 64
+ self.gateway.lo = int(gateway) & 0xffffffffffffffff
+
+# ================================================================= #
+# ----------------------------------------------------------------- #
+# FIXTURES
+# ----------------------------------------------------------------- #
+# ================================================================= #
+# ----------------------------------------------------------------- #
+# Baseline Configuration for subsequent testcases
+# NOTES
+# * All test cases will emulate devices on both rx and tx ports
+# * Each test case will create traffic streams corresponding to
+# the devices to check
+# ----------------------------------------------------------------- #
+
+@pytest.fixture(scope='module')
+def drone(request):
+ drn = DroneProxy(host_name)
+
+ log.info('connecting to drone(%s:%d)' % (drn.hostName(), drn.portNumber()))
+ drn.connect()
+
+ def fin():
+ drn.disconnect()
+ request.addfinalizer(fin)
+
+ return drn
+
+@pytest.fixture(scope='module')
+def ports(request, drone):
+ port_id_list = drone.getPortIdList()
+ port_config_list = drone.getPortConfig(port_id_list)
+ assert len(port_config_list.port) != 0
+
+ # print port list and find default tx/rx ports
+ tx_number = -1
+ rx_number = -1
+ print port_config_list
+ print('Port List')
+ print('---------')
+ for port in port_config_list.port:
+ print('%d.%s (%s)' % (port.port_id.id, port.name, port.description))
+ # use a vhost port as default tx/rx port
+ if ('vhost' in port.name or 'sun' in port.description.lower()):
+ if tx_number < 0:
+ tx_number = port.port_id.id
+ elif rx_number < 0:
+ rx_number = port.port_id.id
+ if ('eth1' in port.name):
+ tx_number = port.port_id.id
+ if ('eth2' in port.name):
+ rx_number = port.port_id.id
+
+ assert tx_number >= 0
+ assert rx_number >= 0
+
+ print('Using port %d as tx port(s)' % tx_number)
+ print('Using port %d as rx port(s)' % rx_number)
+
+ ports.tx = ost_pb.PortIdList()
+ ports.tx.port_id.add().id = tx_number;
+
+ ports.rx = ost_pb.PortIdList()
+ ports.rx.port_id.add().id = rx_number;
+ return ports
+
+@pytest.fixture(scope='module')
+def dut(request):
+ # Enable IP forwarding on the DUT (aka make it a router)
+ sudo('sysctl -w net.ipv4.ip_forward=1')
+ sudo('sysctl -w net.ipv6.conf.all.forwarding=1')
+
+@pytest.fixture(scope='module')
+def dut_ports(request):
+ dut_ports.rx = 'eth1'
+ dut_ports.tx = 'eth2'
+
+ # delete all configuration on the DUT interfaces
+ sudo('ip address flush dev ' + dut_ports.rx)
+ sudo('ip address flush dev ' + dut_ports.tx)
+ return dut_ports
+
+@pytest.fixture(scope='module')
+def emul_ports(request, drone, ports):
+ emul_ports = ost_pb.PortIdList()
+ emul_ports.port_id.add().id = ports.tx.port_id[0].id;
+ emul_ports.port_id.add().id = ports.rx.port_id[0].id;
+ return emul_ports
+
+@pytest.fixture(scope='module')
+def dgid_list(request, drone, ports):
+ # ----------------------------------------------------------------- #
+ # create emulated device(s) on tx/rx ports - each test case will
+ # modify and reuse these devices as per its needs
+ # ----------------------------------------------------------------- #
+
+ # delete existing devices, if any, on tx port
+ dgid_list.tx = drone.getDeviceGroupIdList(ports.tx.port_id[0])
+ drone.deleteDeviceGroup(dgid_list.tx)
+
+ # add a emulated device group on tx port
+ dgid_list.tx = ost_pb.DeviceGroupIdList()
+ dgid_list.tx.port_id.CopyFrom(ports.tx.port_id[0])
+ dgid_list.tx.device_group_id.add().id = 1
+ log.info('adding tx device_group %d' % dgid_list.tx.device_group_id[0].id)
+ drone.addDeviceGroup(dgid_list.tx)
+
+ # delete existing devices, if any, on rx port
+ dgid_list.rx = drone.getDeviceGroupIdList(ports.rx.port_id[0])
+ drone.deleteDeviceGroup(dgid_list.rx)
+
+ # add a emulated device group on rx port
+ dgid_list.rx = ost_pb.DeviceGroupIdList()
+ dgid_list.rx.port_id.CopyFrom(ports.rx.port_id[0])
+ dgid_list.rx.device_group_id.add().id = 1
+ log.info('adding rx device_group %d' % dgid_list.rx.device_group_id[0].id)
+ drone.addDeviceGroup(dgid_list.rx)
+
+ def fin():
+ dgid_list = drone.getDeviceGroupIdList(ports.tx.port_id[0])
+ drone.deleteDeviceGroup(dgid_list)
+ dgid_list = drone.getDeviceGroupIdList(ports.rx.port_id[0])
+ drone.deleteDeviceGroup(dgid_list)
+ request.addfinalizer(fin)
+
+ return dgid_list
+
+@pytest.fixture(scope='module')
+def stream_clear(request, drone, ports):
+ # delete existing streams, if any, on tx port
+ sid_list = drone.getStreamIdList(ports.tx.port_id[0])
+ drone.deleteStream(sid_list)
+
+@pytest.fixture
+def dut_ip(request, dut_ports):
+ sudo('ip address add 10.10.1.1/24 dev ' + dut_ports.rx)
+ sudo('ip address add 10.10.2.1/24 dev ' + dut_ports.tx)
+
+ sudo('ip -6 address add 1234:1::1/96 dev ' + dut_ports.rx)
+ sudo('ip -6 address add 1234:2::1/96 dev ' + dut_ports.tx)
+
+ def fin():
+ sudo('ip address delete 10.10.1.1/24 dev ' + dut_ports.rx)
+ sudo('ip address delete 10.10.2.1/24 dev ' + dut_ports.tx)
+
+ sudo('ip -6 address delete 1234:1::1/96 dev ' + dut_ports.rx)
+ sudo('ip -6 address delete 1234:2::1/96 dev ' + dut_ports.tx)
+ request.addfinalizer(fin)
+
+@pytest.fixture
+def dut_vlans(request, dut_ports):
+ class Devices(object):
+ pass
+
+ def create_vdev(devices, vlancfgs):
+ # device(s) with ALL vlan tags are what we configure and add to netns;
+ # those with intermediate no of vlans are created but not configured
+ if len(vlancfgs) == 0:
+ assert len(devices.rx) == len(devices.tx)
+ dut_vlans.vlans = []
+ for i in range(len(devices.rx)):
+ vrf = 'vrf' + str(i+1)
+ sudo('ip netns add ' + vrf)
+ sudo('ip netns exec ' + vrf
+ + ' sysctl -w net.ipv6.conf.all.forwarding=1')
+
+ dev = devices.rx[i]
+ sudo('ip link set ' + dev
+ + ' netns ' + vrf)
+ sudo('ip netns exec ' + vrf
+ + ' ip addr add 10.1.1.1/24'
+ + ' dev ' + dev)
+ sudo('ip netns exec ' + vrf
+ + ' ip -6 addr add 1234:1::1/96'
+ + ' dev ' + dev)
+ sudo('ip netns exec ' + vrf
+ + ' ip link set ' + dev + ' up')
+
+ dev = devices.tx[i]
+ sudo('ip link set ' + dev
+ + ' netns ' + vrf)
+ sudo('ip netns exec ' + vrf
+ + ' ip addr add 10.1.2.1/24'
+ + ' dev ' + dev)
+ sudo('ip netns exec ' + vrf
+ + ' ip -6 addr add 1234:2::1/96'
+ + ' dev ' + dev)
+ sudo('ip netns exec ' + vrf
+ + ' ip link set ' + dev + ' up')
+
+ dut_vlans.vlans.append(dev[dev.find('.')+1:])
+ return dut_vlans.vlans
+ vcfg = vlancfgs[0]
+ new_devs = Devices()
+ new_devs.tx = []
+ new_devs.rx = []
+ for dev in devices.rx+devices.tx:
+ for k in range(vcfg['count']):
+ vlan_id = vcfg['base'] + k*vcfg.get('step', 1)
+ if 'tpid' in vcfg and vcfg['tpid'] == 0x88a8:
+ tpid = '802.1ad'
+ else:
+ tpid = '802.1q'
+ dev_name = dev + '.' + str(vlan_id)
+ sudo('ip link add link ' + dev
+ + ' name ' + dev_name
+ + ' type vlan id ' + str(vlan_id)
+ + ' proto ' + tpid)
+ sudo('ip link set ' + dev_name + ' up')
+ if dev in devices.rx:
+ new_devs.rx.append(dev_name)
+ else:
+ new_devs.tx.append(dev_name)
+ print (str(len(devices.rx)*2*vcfg['count'])+' devices created')
+
+ create_vdev(new_devs, vlancfgs[1:])
+
+ def delete_vdev():
+ if len(vlancfgs) == 0:
+ return
+
+ vrf_count = 1
+ for vcfg in vlancfgs:
+ vrf_count = vrf_count * vcfg['count']
+
+ # deleting netns will also delete the devices inside it
+ for i in range(vrf_count):
+ vrf = 'vrf' + str(i+1)
+ sudo('ip netns delete ' + vrf)
+
+ # if we have only single tagged vlans, then we are done
+ if len(vlancfgs) == 1:
+ return
+
+ # if we have 2 or more stacked vlan tags, we need to delete the
+ # intermediate stacked vlan devices which are in the root namespace;
+ # deleting first level tag vlan devices will delete vlan devices
+ # stacked on top of it also
+ vcfg = vlancfgs[0]
+ for dev in devices.tx+devices.rx:
+ for k in range(vcfg['count']):
+ vdev = dev + '.' + str(vcfg['base']+k)
+ sudo('ip link delete ' + vdev)
+
+
+ vlancfgs = getattr(request.function, 'vlan_cfg')
+ devices = Devices()
+ devices.tx = [dut_ports.tx]
+ devices.rx = [dut_ports.rx]
+ create_vdev(devices, vlancfgs)
+
+ request.addfinalizer(delete_vdev)
+
+
+# ================================================================= #
+# ----------------------------------------------------------------- #
+# TEST CASES
+# ----------------------------------------------------------------- #
+# ================================================================= #
+
+@pytest.mark.parametrize('dev_cfg', [
+ {'ip_ver': [4], 'mac_step': 1, 'ip_step': 1},
+ {'ip_ver': [6], 'mac_step': 1, 'ip_step': 1},
+ {'ip_ver': [4, 6], 'mac_step': 2, 'ip_step': 5},
+])
+def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip,
+ stream_clear, emul_ports, dgid_list, dev_cfg):
+ # ----------------------------------------------------------------- #
+ # TESTCASE: Emulate multiple IPv4 devices (no vlans)
+ # DUT
+ # /.1 \.1
+ # / \
+ # 10.10.1/24 10.10.2/24
+ # 1234::1/96 1234::2/96
+ # / \
+ # /.101-105 \.101-105
+ # Host1(s) Host2(s)
+ # ----------------------------------------------------------------- #
+
+ num_devs = 5
+ has_ip4 = True if 4 in dev_cfg['ip_ver'] else False
+ has_ip6 = True if 6 in dev_cfg['ip_ver'] else False
+ mac_step = dev_cfg['mac_step']
+ ip_step = dev_cfg['ip_step']
+
+ # configure the tx device(s)
+ devgrp_cfg = ost_pb.DeviceGroupConfigList()
+ devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0])
+ dg = devgrp_cfg.device_group.add()
+ dg.device_group_id.id = dgid_list.tx.device_group_id[0].id
+ dg.core.name = "Host1"
+ dg.device_count = num_devs
+ dg.Extensions[emul.mac].address = 0x000102030a01
+ if (mac_step != 1):
+ dg.Extensions[emul.mac].step = mac_step
+ if has_ip4:
+ ip = dg.Extensions[emul.ip4]
+ ip.address = 0x0a0a0165
+ ip.prefix_length = 24
+ if (ip_step != 1):
+ ip.step = ip_step
+ ip.default_gateway = 0x0a0a0101
+ if has_ip6:
+ ip = dg.Extensions[emul.ip6]
+ ip6addr = ip6_address('1234:1::65/96')
+ ip.address.CopyFrom(ip6addr.ip6)
+ ip.prefix_length = ip6addr.prefixlen
+ if (ip_step != 1):
+ ip.step.CopyFrom(ip6_address(ip_step).ip6)
+ ip.default_gateway.CopyFrom(ip6addr.gateway)
+
+ drone.modifyDeviceGroup(devgrp_cfg)
+
+ # configure the rx device(s)
+ devgrp_cfg = ost_pb.DeviceGroupConfigList()
+ devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0])
+ dg = devgrp_cfg.device_group.add()
+ dg.device_group_id.id = dgid_list.rx.device_group_id[0].id
+ dg.core.name = "Host1"
+ dg.device_count = num_devs
+ dg.Extensions[emul.mac].address = 0x000102030b01
+ if (mac_step != 1):
+ dg.Extensions[emul.mac].step = mac_step
+ if has_ip4:
+ ip = dg.Extensions[emul.ip4]
+ ip.address = 0x0a0a0265
+ ip.prefix_length = 24
+ if (ip_step != 1):
+ ip.step = ip_step
+ ip.default_gateway = 0x0a0a0201
+ if has_ip6:
+ ip = dg.Extensions[emul.ip6]
+ ip6addr = ip6_address('1234:2::65/96')
+ ip.address.CopyFrom(ip6addr.ip6)
+ ip.prefix_length = ip6addr.prefixlen
+ if (ip_step != 1):
+ ip.step.CopyFrom(ip6_address(ip_step).ip6)
+ ip.default_gateway.CopyFrom(ip6addr.gateway)
+
+ drone.modifyDeviceGroup(devgrp_cfg)
+
+ # add the tx stream(s) - we may need more than one
+ stream_id = ost_pb.StreamIdList()
+ stream_id.port_id.CopyFrom(ports.tx.port_id[0])
+ for i in range(len(dev_cfg['ip_ver'])):
+ stream_id.stream_id.add().id = i
+ log.info('adding tx_stream %d' % stream_id.stream_id[i].id)
+
+ drone.addStream(stream_id)
+
+ # configure the tx stream(s)
+ stream_cfg = ost_pb.StreamConfigList()
+ stream_cfg.port_id.CopyFrom(ports.tx.port_id[0])
+ for i in range(len(dev_cfg['ip_ver'])):
+ s = stream_cfg.stream.add()
+ s.stream_id.id = stream_id.stream_id[i].id
+ s.core.is_enabled = True
+ s.core.frame_len = 1024
+ s.control.packets_per_sec = 100
+ s.control.num_packets = 10
+
+ # setup stream protocols as mac:eth2:ip:udp:payload
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber
+ p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve
+ p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve
+
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber
+
+ if dev_cfg['ip_ver'][i] == 4:
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber
+ ip = None
+ ip = p.Extensions[ip4]
+ ip.src_ip = 0x0a0a0165
+ if ip_step == 1:
+ ip.src_ip_mode = Ip4.e_im_inc_host
+ ip.src_ip_count = num_devs
+ else:
+ vf = p.variable_field.add()
+ vf.type = ost_pb.VariableField.kCounter32
+ vf.offset = 12
+ vf.mask = 0x000000FF
+ vf.value = 0x00000065
+ vf.step = ip_step
+ vf.mode = ost_pb.VariableField.kIncrement
+ vf.count = num_devs
+ ip.dst_ip = 0x0a0a0265
+ if ip_step == 1:
+ ip.dst_ip_mode = Ip4.e_im_inc_host
+ ip.dst_ip_count = num_devs
+ else:
+ vf = p.variable_field.add()
+ vf.type = ost_pb.VariableField.kCounter32
+ vf.offset = 16
+ vf.mask = 0x000000FF
+ vf.value = 0x00000065
+ vf.step = ip_step
+ vf.mode = ost_pb.VariableField.kIncrement
+ vf.count = num_devs
+ elif dev_cfg['ip_ver'][i] == 6:
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber
+ ip = p.Extensions[ip6]
+ ip6addr = ip6_address('1234:1::65/96')
+ ip.src_addr_hi = ip6addr.ip6.hi
+ ip.src_addr_lo = ip6addr.ip6.lo
+ if ip_step == 1:
+ ip.src_addr_mode = Ip6.kIncHost
+ ip.src_addr_count = num_devs
+ ip.src_addr_prefix = ip6addr.prefixlen
+ else:
+ vf = p.variable_field.add()
+ vf.type = ost_pb.VariableField.kCounter32
+ vf.offset = 20
+ vf.mask = 0xFFFFFFFF
+ vf.value = 0x00000065
+ vf.step = ip_step
+ vf.mode = ost_pb.VariableField.kIncrement
+ vf.count = num_devs
+ ip6addr = ip6_address('1234:2::65/96')
+ ip.dst_addr_hi = ip6addr.ip6.hi
+ ip.dst_addr_lo = ip6addr.ip6.lo
+ if ip_step == 1:
+ ip.dst_addr_mode = Ip6.kIncHost
+ ip.dst_addr_count = num_devs
+ ip.dst_addr_prefix = ip6addr.prefixlen
+ else:
+ vf = p.variable_field.add()
+ vf.type = ost_pb.VariableField.kCounter32
+ vf.offset = 36
+ vf.mask = 0xFFFFFFFF
+ vf.value = 0x00000065
+ vf.step = ip_step
+ vf.mode = ost_pb.VariableField.kIncrement
+ vf.count = num_devs
+ else:
+ assert False # unreachable
+
+ 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[i].id)
+
+ drone.modifyStream(stream_cfg)
+
+ # FIXME(needed?): clear tx/rx stats
+ log.info('clearing tx/rx stats')
+ drone.clearStats(ports.tx)
+ drone.clearStats(ports.rx)
+
+ # clear arp/ndp on DUT
+ sudo('ip neigh flush all')
+ arp_cache = run('ip neigh show')
+ assert re.search('10.10.[1-2].1\d\d.*lladdr', arp_cache) == None
+ assert re.search('1234:[1-2]::\[\da-f]+.*lladdr', arp_cache) == None
+
+ # wait for interface to do DAD? Otherwise we don't get replies for NS
+ # FIXME: find alternative to sleep
+ time.sleep(5)
+
+ # clear ARP/NDP cache
+ log.info('clearing ARP/NDP cache on tx/rx port')
+ drone.clearDeviceNeighbors(emul_ports)
+
+ log.info('retrieving ARP/NDP entries on tx port to verify clear')
+ device_list = drone.getDeviceList(emul_ports.port_id[0])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on tx port')
+ for devcfg, device in zip(device_config, devices):
+ if has_ip4:
+ for arp in device.arp:
+ print('%s: %s %012x' %
+ (str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ assert(len(device.arp) == 0)
+ if has_ip6:
+ for ndp in device.ndp:
+ print('%s: %s %012x' %
+ (str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ assert(len(device.ndp) == 0)
+
+ log.info('retrieving ARP/NDP entries on rx port to verify clear')
+ device_list = drone.getDeviceList(emul_ports.port_id[1])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on rx port')
+ for devcfg, device in zip(device_config, devices):
+ if has_ip4:
+ for arp in device.arp:
+ print('%s: %s %012x' %
+ (str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ assert(len(device.arp) == 0)
+ if has_ip6:
+ for ndp in device.ndp:
+ print('%s: %s %012x' %
+ (str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ assert(len(device.ndp) == 0)
+
+ # resolve ARP/NDP on tx/rx ports
+ log.info('resolving Neighbors on tx/rx ports ...')
+ drone.startCapture(emul_ports)
+ drone.resolveDeviceNeighbors(emul_ports)
+ time.sleep(3)
+ drone.stopCapture(emul_ports)
+
+ # verify ARP/NDP Requests sent out from tx port
+ buff = drone.getCaptureBuffer(emul_ports.port_id[0])
+ drone.saveCaptureBuffer(buff, 'capture.pcap')
+ log.info('dumping Tx capture buffer (all)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap'])
+ print(cap_pkts)
+ log.info('dumping Tx capture buffer (filtered)')
+ for i in range(len(dev_cfg['ip_ver'])):
+ if dev_cfg['ip_ver'][i] == 4:
+ filter = '(arp.opcode == 1)' \
+ ' && (arp.src.proto_ipv4 == 10.10.1.)' \
+ ' && (arp.dst.proto_ipv4 == 10.10.1.1)' \
+ ' && !expert.severity'
+ elif dev_cfg['ip_ver'][i] == 6:
+ filter = '(icmpv6.type == 135)' \
+ ' && (ipv6.src == 1234:1::)' \
+ ' && (icmpv6.nd.ns.target_address == 1234:1::1)' \
+ ' && !expert.severity'
+ for j in range(num_devs):
+ if dev_cfg['ip_ver'][i] == 4:
+ filter = filter.replace('', str(101+j*ip_step))
+ elif dev_cfg['ip_ver'][i] == 6:
+ filter = filter.replace('', format(0x65+j*ip_step, 'x'))
+ #print filter
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
+ '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == 1
+
+ # verify ARP/NDP Requests sent out from rx port
+ buff = drone.getCaptureBuffer(emul_ports.port_id[1])
+ drone.saveCaptureBuffer(buff, 'capture.pcap')
+ log.info('dumping Rx capture buffer (all)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap'])
+ print(cap_pkts)
+ log.info('dumping Rx capture buffer (filtered)')
+ for i in range(len(dev_cfg['ip_ver'])):
+ if dev_cfg['ip_ver'][i] == 4:
+ filter = '(arp.opcode == 1)' \
+ ' && (arp.src.proto_ipv4 == 10.10.2.)' \
+ ' && (arp.dst.proto_ipv4 == 10.10.2.1)' \
+ ' && !expert.severity'
+ elif dev_cfg['ip_ver'][i] == 6:
+ filter = '(icmpv6.type == 135)' \
+ ' && (ipv6.src == 1234:2::)' \
+ ' && (icmpv6.nd.ns.target_address == 1234:2::1)' \
+ ' && !expert.severity'
+ for j in range(num_devs):
+ if dev_cfg['ip_ver'][i] == 4:
+ filter = filter.replace('', str(101+j*ip_step))
+ elif dev_cfg['ip_ver'][i] == 6:
+ filter = filter.replace('', format(0x65+j*ip_step, 'x'))
+ print filter
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
+ '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == 1
+
+ # retrieve and verify ARP/NDP Table on tx/rx ports
+ log.info('retrieving ARP/NDP entries on tx port')
+ device_list = drone.getDeviceList(emul_ports.port_id[0])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on tx port')
+ for devcfg, device in zip(device_config, devices):
+ if has_ip4:
+ resolved = False
+ for arp in device.arp:
+ print('%s: %s %012x' %
+ (str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac):
+ resolved = True
+ assert resolved
+ if has_ip6:
+ resolved = False
+ for ndp in device.ndp:
+ print('%s: %s %012x' %
+ (str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac):
+ resolved = True
+ assert resolved
+
+ log.info('retrieving ARP/NDP entries on rx port')
+ device_list = drone.getDeviceList(emul_ports.port_id[1])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on rx port')
+ for devcfg, device in zip(device_config, devices):
+ if has_ip4:
+ resolved = False
+ for arp in device.arp:
+ print('%s: %s %012x' %
+ (str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac):
+ resolved = True
+ assert resolved
+ if has_ip6:
+ resolved = False
+ for ndp in device.ndp:
+ print('%s: %s %012x' %
+ (str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac):
+ resolved = True
+ assert resolved
+
+ # ping the tx devices from the DUT
+ for i in range(num_devs):
+ if has_ip4:
+ out = run('ping -c3 10.10.1.'+str(101+i*ip_step), warn_only=True)
+ assert '100% packet loss' not in out
+ if has_ip6:
+ out = run('ping -6 -c3 1234:1::'+format(101+i*ip_step, 'x'),
+ warn_only=True)
+ assert '100% packet loss' not in out
+
+ # ping the rx devices from the DUT
+ for i in range(num_devs):
+ if has_ip4:
+ out = run('ping -c3 10.10.2.'+str(101+i*ip_step), warn_only=True)
+ assert '100% packet loss' not in out
+ if has_ip6:
+ out = run('ping -6 -c3 1234:2::'+format(101+i*ip_step, 'x'),
+ warn_only=True)
+ assert '100% packet loss' not in out
+
+ # We are all set now - so transmit the stream now
+ drone.startCapture(ports.rx)
+ drone.startTransmit(ports.tx)
+ log.info('waiting for transmit to finish ...')
+ time.sleep(3)
+ drone.stopTransmit(ports.tx)
+ drone.stopCapture(ports.rx)
+
+ buff = drone.getCaptureBuffer(ports.rx.port_id[0])
+ drone.saveCaptureBuffer(buff, 'capture.pcap')
+ log.info('dumping Rx capture buffer (all)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap'])
+ print(cap_pkts)
+ log.info('dumping Rx capture buffer (filtered)')
+ for i in range(num_devs):
+ if has_ip4:
+ filter = '(ip.src == 10.10.1.' + str(101+i*ip_step) + ')' \
+ ' && (ip.dst == 10.10.2.' + str(101+i*ip_step) + ')' \
+ ' && (eth.dst == 00:01:02:03:0b:' \
+ + format(1+i*mac_step, '02x')+')'
+ print('filter: %s' % filter)
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
+ '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == s.control.num_packets/num_devs
+ if has_ip6:
+ filter = '(ipv6.src == 1234:1::' \
+ + format(101+i*ip_step, 'x') + ')' \
+ + ' && (ipv6.dst == 1234:2::' \
+ + format(101+i*ip_step, 'x') + ')' \
+ + ' && (eth.dst == 00:01:02:03:0b:' \
+ + format(1+i*mac_step, '02x') + ')'
+ print('filter: %s' % filter)
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
+ '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == s.control.num_packets/num_devs
+ os.remove('capture.pcap')
+
+ drone.stopTransmit(ports.tx)
+ run('ip neigh show')
+
+@pytest.mark.parametrize('vlan_cfg,ip_ver', [
+ ([{'base': 11, 'count': 5}], [6]),
+
+ ([{'base': 11, 'count': 2},
+ {'base': 21, 'count': 3}], [4]),
+
+ ([{'base': 11, 'count': 2, 'tpid': 0x88a8},
+ {'base': 21, 'count': 3}], [4, 6]),
+
+ ([{'base': 11, 'count': 2},
+ {'base': 21, 'count': 3, 'step': 2},
+ {'base': 31, 'count': 2, 'step': 3}], [6]),
+
+ ([{'base': 11, 'count': 2},
+ {'base': 21, 'count': 3},
+ {'base': 31, 'count': 2},
+ {'base': 1, 'count': 3}], [4])
+])
+def test_multiEmulDevPerVlan(request, drone, ports, dut, dut_ports,
+ stream_clear, emul_ports, dgid_list, vlan_cfg, ip_ver):
+ # ----------------------------------------------------------------- #
+ # TESTCASE: Emulate multiple IPv4 device per multiple single-tag VLANs
+ #
+ # +- DUT -+
+ # .1/ \.1
+ # / \
+ # 10.1.1/24 10.1.2/24
+ # 1234:1/96 1234:2/96
+ # [vlans] [vlans]
+ # / \
+ # /.101-103 \.101-103
+ # Host1(s) Host2(s)
+ # ----------------------------------------------------------------- #
+
+ num_vlans = 1
+ for vcfg in vlan_cfg:
+ num_vlans = num_vlans * vcfg['count']
+ test_multiEmulDevPerVlan.vlan_cfg = vlan_cfg
+ num_devs_per_vlan = 3
+ has_ip4 = True if 4 in ip_ver else False
+ has_ip6 = True if 6 in ip_ver else False
+ if has_ip4:
+ tx_ip4_base = 0x0a010165
+ rx_ip4_base = 0x0a010265
+ if has_ip6:
+ tx_ip6_base = ip6_address('1234:1::65/96')
+ rx_ip6_base = ip6_address('1234:2::65/96')
+
+ # configure vlans on the DUT
+ dut_vlans(request, dut_ports)
+
+ assert len(dut_vlans.vlans) == num_vlans
+ vlan_filter = []
+ for i in range(len(dut_vlans.vlans)):
+ vlan_filter.append('')
+ ids = dut_vlans.vlans[i].split('.')
+ for j in range(len(ids)):
+ filter = '(frame[:4]==:)' \
+ .replace('', str(12+j*4)) \
+ .replace('', '{:04x}'.format(
+ vlan_cfg[j].get('tpid', 0x8100))) \
+ .replace('', '{:04x}'.format(int(ids[j])))
+ if len(vlan_filter[i]) > 0:
+ vlan_filter[i] += ' && '
+ vlan_filter[i] += filter
+ print i, vlan_filter[i]
+
+ # configure the tx device(s)
+ devgrp_cfg = ost_pb.DeviceGroupConfigList()
+ devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0])
+ dg = devgrp_cfg.device_group.add()
+ dg.device_group_id.id = dgid_list.tx.device_group_id[0].id
+ dg.core.name = "Host1"
+ for vcfg in vlan_cfg:
+ v = dg.encap.Extensions[emul.vlan].stack.add()
+ v.vlan_tag = vcfg['base']
+ v.count = vcfg['count']
+ if 'tpid' in vcfg:
+ v.tpid = vcfg['tpid']
+ if 'step' in vcfg:
+ v.step = vcfg['step']
+ dg.device_count = num_devs_per_vlan
+ dg.Extensions[emul.mac].address = 0x000102030a01
+ if has_ip4:
+ ip = dg.Extensions[emul.ip4]
+ ip.address = tx_ip4_base
+ ip.prefix_length = 24
+ ip.default_gateway = (tx_ip4_base & 0xffffff00) | 0x01
+ if has_ip6:
+ ip = dg.Extensions[emul.ip6]
+ ip.address.CopyFrom(tx_ip6_base.ip6)
+ ip.prefix_length = tx_ip6_base.prefixlen
+ ip.default_gateway.CopyFrom(tx_ip6_base.gateway)
+
+ drone.modifyDeviceGroup(devgrp_cfg)
+
+ # configure the rx device(s)
+ devgrp_cfg = ost_pb.DeviceGroupConfigList()
+ devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0])
+ dg = devgrp_cfg.device_group.add()
+ dg.device_group_id.id = dgid_list.rx.device_group_id[0].id
+ dg.core.name = "Host1"
+ for vcfg in vlan_cfg:
+ v = dg.encap.Extensions[emul.vlan].stack.add()
+ v.vlan_tag = vcfg['base']
+ v.count = vcfg['count']
+ if 'tpid' in vcfg:
+ v.tpid = vcfg['tpid']
+ if 'step' in vcfg:
+ v.step = vcfg['step']
+ dg.device_count = num_devs_per_vlan
+ dg.Extensions[emul.mac].address = 0x000102030b01
+ if has_ip4:
+ ip = dg.Extensions[emul.ip4]
+ ip.address = rx_ip4_base
+ ip.prefix_length = 24
+ ip.default_gateway = (rx_ip4_base & 0xffffff00) | 0x01
+ if has_ip6:
+ ip = dg.Extensions[emul.ip6]
+ ip.address.CopyFrom(rx_ip6_base.ip6)
+ ip.prefix_length = rx_ip6_base.prefixlen
+ ip.default_gateway.CopyFrom(rx_ip6_base.gateway)
+
+ drone.modifyDeviceGroup(devgrp_cfg)
+
+ # add the tx stream(s) - we need more than one stream
+ stream_id = ost_pb.StreamIdList()
+ stream_id.port_id.CopyFrom(ports.tx.port_id[0])
+ for i in range(num_vlans * len(ip_ver)):
+ stream_id.stream_id.add().id = i
+ log.info('adding tx_stream %d' % stream_id.stream_id[i].id)
+
+ drone.addStream(stream_id)
+
+ # configure the tx stream(s)
+ stream_cfg = ost_pb.StreamConfigList()
+ stream_cfg.port_id.CopyFrom(ports.tx.port_id[0])
+ for i in range(num_vlans):
+ for j in range(len(ip_ver)):
+ idx = i*len(ip_ver) + j
+ s = stream_cfg.stream.add()
+ s.stream_id.id = stream_id.stream_id[idx].id
+ s.core.name = 'stream ' + str(s.stream_id.id)
+ s.core.is_enabled = True
+ s.core.ordinal = idx
+ s.core.frame_len = 1024
+ s.control.packets_per_sec = 100
+ s.control.num_packets = num_devs_per_vlan
+
+ # setup stream protocols as mac:vlan:eth2:ip4:udp:payload
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber
+ p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve
+ p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve
+
+ ids = dut_vlans.vlans[i].split('.')
+ for id, idx in zip(ids, range(len(ids))):
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kVlanFieldNumber
+ p.Extensions[vlan].vlan_tag = int(id)
+ if 'tpid' in vlan_cfg[idx]:
+ p.Extensions[vlan].tpid = vlan_cfg[idx]['tpid']
+ p.Extensions[vlan].is_override_tpid = True
+
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber
+
+ if ip_ver[j] == 4:
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber
+ ip = p.Extensions[ip4]
+ ip.src_ip = tx_ip4_base
+ ip.src_ip_mode = Ip4.e_im_inc_host
+ ip.src_ip_count = num_devs_per_vlan
+ ip.dst_ip = rx_ip4_base
+ ip.dst_ip_mode = Ip4.e_im_inc_host
+ ip.dst_ip_count = num_devs_per_vlan
+ elif ip_ver[j] == 6:
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber
+ ip = p.Extensions[ip6]
+
+ ip.src_addr_hi = tx_ip6_base.ip6.hi
+ ip.src_addr_lo = tx_ip6_base.ip6.lo
+ ip.src_addr_mode = Ip6.kIncHost
+ ip.src_addr_count = num_devs_per_vlan
+ ip.src_addr_prefix = tx_ip6_base.prefixlen
+
+ ip.dst_addr_hi = rx_ip6_base.ip6.hi
+ ip.dst_addr_lo = rx_ip6_base.ip6.lo
+ ip.dst_addr_mode = Ip6.kIncHost
+ ip.dst_addr_count = num_devs_per_vlan
+ ip.dst_addr_prefix = rx_ip6_base.prefixlen
+ else:
+ assert false # unreachable
+
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kUdpFieldNumber
+ p = s.protocol.add()
+ p.protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber
+
+ log.info('configuring tx_stream %d' % stream_id.stream_id[idx].id)
+
+ drone.modifyStream(stream_cfg)
+
+ # clear arp/ndp on DUT
+ for i in range(num_vlans):
+ vrf = 'vrf' + str(i+1)
+
+ sudo('ip netns exec ' + vrf
+ + ' ip neigh flush all')
+ arp_cache = sudo('ip netns exec ' + vrf
+ + ' ip neigh show')
+ assert re.search('10.1.[1-2].20[1-5].*lladdr', arp_cache) == None
+ assert re.search('1234:[1-2]::\[\da-f]+.*lladdr', arp_cache) == None
+
+ # wait for interface to do DAD? Otherwise we don't get replies for NS
+ # FIXME: find alternative to sleep
+ if has_ip6:
+ time.sleep(5)
+
+ # clear ARP/NDP cache
+ log.info('clearing ARP/NDP cache on tx/rx port')
+ drone.clearDeviceNeighbors(emul_ports)
+
+ log.info('retrieving ARP/NDP entries on tx port to verify clear')
+ device_list = drone.getDeviceList(emul_ports.port_id[0])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on tx port')
+ for devcfg, device in zip(device_config, devices):
+ if has_ip4:
+ for arp in device.arp:
+ print('%s: %s %012x' %
+ (str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ assert(len(device.arp) == 0)
+ if has_ip6:
+ for ndp in device.ndp:
+ print('%s: %s %012x' %
+ (str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ assert(len(device.ndp) == 0)
+
+ log.info('retrieving ARP/NDP entries on rx port to verify clear')
+ device_list = drone.getDeviceList(emul_ports.port_id[1])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on rx port')
+ for devcfg, device in zip(device_config, devices):
+ if has_ip4:
+ for arp in device.arp:
+ print('%s: %s %012x' %
+ (str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ assert(len(device.arp) == 0)
+ if has_ip6:
+ for ndp in device.ndp:
+ print('%s: %s %012x' %
+ (str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ assert(len(device.ndp) == 0)
+
+ # resolve ARP/NDP on tx/rx ports
+ log.info('resolving Neighbors on tx/rx ports ...')
+ drone.startCapture(emul_ports)
+ drone.resolveDeviceNeighbors(emul_ports)
+ time.sleep(3)
+ drone.stopCapture(emul_ports)
+
+ # verify ARP/NDP Requests sent out from tx port
+ buff = drone.getCaptureBuffer(emul_ports.port_id[0])
+ drone.saveCaptureBuffer(buff, 'capture.pcap')
+ log.info('dumping Tx capture buffer (all)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap'])
+ print(cap_pkts)
+ log.info('dumping Tx capture buffer (all pkts - vlans only)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
+ '-Tfields', '-eframe.number', '-eieee8021ad.id', '-evlan.id'])
+ print(cap_pkts)
+ log.info('dumping Tx capture buffer (filtered)')
+ for i in range(num_vlans):
+ for j in range(len(ip_ver)):
+ if ip_ver[j] == 4:
+ filter = vlan_filter[i] + ' && (arp.opcode == 1)' \
+ ' && (arp.src.proto_ipv4 == 10.1.1.)' \
+ ' && (arp.dst.proto_ipv4 == 10.1.1.1)' \
+ ' && !expert.severity'
+ elif ip_ver[j] == 6:
+ filter = vlan_filter[i] + ' && (icmpv6.type == 135)' \
+ ' && (ipv6.src == 1234:1::)' \
+ ' && (icmpv6.nd.ns.target_address == 1234:1::1)' \
+ ' && !expert.severity'
+ for k in range(num_devs_per_vlan):
+ if ip_ver[j] == 4:
+ filter = filter.replace('', str(101+k))
+ elif ip_ver[j] == 6:
+ filter = filter.replace('', format(0x65+k, 'x'))
+ print filter
+ cap_pkts = subprocess.check_output([tshark, '-nr',
+ 'capture.pcap', '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == 1
+
+ # verify ARP/NDP Requests sent out from rx port
+ buff = drone.getCaptureBuffer(emul_ports.port_id[1])
+ drone.saveCaptureBuffer(buff, 'capture.pcap')
+ log.info('dumping Rx capture buffer (all)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap'])
+ print(cap_pkts)
+ log.info('dumping Rx capture buffer (all pkts - vlans only)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
+ '-Tfields', '-eframe.number', '-eieee8021ad.id', '-evlan.id'])
+ print(cap_pkts)
+ log.info('dumping Rx capture buffer (filtered)')
+ for i in range(num_vlans):
+ for j in range(len(ip_ver)):
+ if ip_ver[j] == 4:
+ filter = vlan_filter[i] + ' && (arp.opcode == 1)' \
+ ' && (arp.src.proto_ipv4 == 10.1.2.)' \
+ ' && (arp.dst.proto_ipv4 == 10.1.2.1)' \
+ ' && !expert.severity'
+ elif ip_ver[j] == 6:
+ filter = vlan_filter[i] + ' && (icmpv6.type == 135)' \
+ ' && (ipv6.src == 1234:2::)' \
+ ' && (icmpv6.nd.ns.target_address == 1234:2::1)' \
+ ' && !expert.severity'
+ for k in range(num_devs_per_vlan):
+ if ip_ver[j] == 4:
+ filter = filter.replace('', str(101+k))
+ elif ip_ver[j] == 6:
+ filter = filter.replace('', format(0x65+k, 'x'))
+ print filter
+ cap_pkts = subprocess.check_output([tshark, '-nr',
+ 'capture.pcap', '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == 1
+
+ # retrieve and verify ARP/NDP Table on tx/rx ports
+ log.info('retrieving ARP/NDP entries on tx port')
+ device_list = drone.getDeviceList(emul_ports.port_id[0])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on tx port')
+ for devcfg, device in zip(device_config, devices):
+ vlans = ''
+ for v in devcfg.vlan:
+ vlans += str(v & 0xffff) + ' '
+ if has_ip4:
+ resolved = False
+ for arp in device.arp:
+ print('%s%s: %s %012x' %
+ (vlans, str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac):
+ resolved = True
+ assert resolved
+ if has_ip6:
+ resolved = False
+ for ndp in device.ndp:
+ print('%s%s: %s %012x' %
+ (vlans, str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac):
+ resolved = True
+ assert resolved
+
+ log.info('retrieving ARP entries on rx port')
+ device_list = drone.getDeviceList(emul_ports.port_id[1])
+ device_config = device_list.Extensions[emul.device]
+ neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1])
+ devices = neigh_list.Extensions[emul.device_neighbor]
+ log.info('ARP/NDP Table on rx port')
+ for devcfg, device in zip(device_config, devices):
+ vlans = ''
+ for v in devcfg.vlan:
+ vlans += str(v & 0xffff) + ' '
+ if has_ip4:
+ resolved = False
+ for arp in device.arp:
+ print('%s%s: %s %012x' %
+ (vlans, str(ipaddress.ip_address(devcfg.ip4)),
+ str(ipaddress.ip_address(arp.ip4)), arp.mac))
+ if (arp.ip4 == devcfg.ip4_default_gateway) and (arp.mac):
+ resolved = True
+ assert resolved
+ if has_ip6:
+ resolved = False
+ for ndp in device.ndp:
+ print('%s%s: %s %012x' %
+ (vlans, str(ip6_address(devcfg.ip6)),
+ str(ip6_address(ndp.ip6)), ndp.mac))
+ if (ndp.ip6 == devcfg.ip6_default_gateway) and (ndp.mac):
+ resolved = True
+ assert resolved
+
+ # ping the tx devices from the DUT
+ for i in range(num_vlans):
+ vrf = 'vrf' + str(i+1)
+ for j in range(num_devs_per_vlan):
+ if has_ip4:
+ out = sudo('ip netns exec ' + vrf
+ + ' ping -c3 10.1.1.'+str(101+j), warn_only=True)
+ assert '100% packet loss' not in out
+ if has_ip6:
+ out = sudo('ip netns exec ' + vrf
+ + ' ping -6 -c3 1234:1::'+format(101+j, 'x'),
+ warn_only=True)
+ assert '100% packet loss' not in out
+
+ # ping the rx devices from the DUT
+ for i in range(num_vlans):
+ vrf = 'vrf' + str(i+1)
+ for j in range(num_devs_per_vlan):
+ if has_ip4:
+ out = sudo('ip netns exec ' + vrf
+ + ' ping -c3 10.1.2.'+str(101+j), warn_only=True)
+ assert '100% packet loss' not in out
+ if has_ip6:
+ out = sudo('ip netns exec ' + vrf
+ + ' ping -6 -c3 1234:2::'+format(101+j, 'x'),
+ warn_only=True)
+ assert '100% packet loss' not in out
+
+ # we are all set - send data stream(s)
+ drone.startCapture(ports.rx)
+ drone.startTransmit(ports.tx)
+ log.info('waiting for transmit to finish ...')
+ time.sleep(5)
+ drone.stopTransmit(ports.tx)
+ drone.stopCapture(ports.rx)
+
+ # verify data packets received on rx port
+ buff = drone.getCaptureBuffer(ports.rx.port_id[0])
+ drone.saveCaptureBuffer(buff, 'capture.pcap')
+ log.info('dumping Rx capture buffer (all)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap'])
+ print(cap_pkts)
+ log.info('dumping Rx capture buffer (all pkts - vlans only)')
+ cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
+ '-Tfields', '-eframe.number', '-eieee8021ad.id', '-evlan.id'])
+ print(cap_pkts)
+ log.info('dumping Rx capture buffer (filtered)')
+ for i in range(num_vlans):
+ for j in range(num_devs_per_vlan):
+ if has_ip4:
+ filter = vlan_filter[i] + \
+ ' && (ip.src == 10.1.1.' + str(101+j) + ')' \
+ ' && (ip.dst == 10.1.2.' + str(101+j) + ')' \
+ ' && (eth.dst == 00:01:02:03:0b:' + format(1+j, '02x')+')'
+ print('filter: %s' % filter)
+ cap_pkts = subprocess.check_output([tshark, '-nr',
+ 'capture.pcap', '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == 1
+ if has_ip6:
+ filter = vlan_filter[i] \
+ + ' && (ipv6.src == 1234:1::' + format(101+j, 'x') + ')' \
+ + ' && (ipv6.dst == 1234:2::' + format(101+j, 'x') + ')' \
+ + ' && (eth.dst == 00:01:02:03:0b:' \
+ + format(1+j, '02x') + ')'
+ print('filter: %s' % filter)
+ cap_pkts = subprocess.check_output([tshark, '-nr',
+ 'capture.pcap', '-Y', filter])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == 1
+ os.remove('capture.pcap')
+
+#
+# TODO
+# * Verify that device_index in OstEmul.DeviceNeighborList matches the
+# correct device in OstEmul.PortDeviceList
+# * Verify ARP/NDP resolution in a bridging topology
+# * Verify non-default IP prefix-length
+# * Verify bi-directional streams
+#
+import pytest
+pytest.main(__file__)
+
diff --git a/test/harness.py b/test/harness.py
index e6c2c92..7eb1b27 100644
--- a/test/harness.py
+++ b/test/harness.py
@@ -46,6 +46,12 @@ class TestSuite:
def passed(self):
return passed == total and self.completed
+class TestPreRequisiteError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+ def __str__(self):
+ return self.msg
+
def extract_column(text, col):
"""Given a text table, return items in the specified column as a list"""
lines = text.splitlines()
diff --git a/test/pktlentest.py b/test/pktlentest.py
index 0e49d07..de8808d 100644
--- a/test/pktlentest.py
+++ b/test/pktlentest.py
@@ -9,6 +9,7 @@ import sys
import time
from harness import extract_column
+from utils import get_tshark
sys.path.insert(1, '../binding')
from core import ost_pb, DroneProxy
@@ -21,10 +22,7 @@ host_name = '127.0.0.1'
tx_number = -1
rx_number = -1
-if sys.platform == 'win32':
- tshark = r'C:\Program Files\Wireshark\tshark.exe'
-else:
- tshark = 'tshark'
+tshark = get_tshark(minversion='1.2.0')
fmt = 'column.format:"Packet#","%m","Time","%t","Source","%uns","Destination","%und","Protocol","%p","Size","%L","Info","%i","Expert","%a"'
fmt_col = 7 # col# in fmt for Size/PktLength
@@ -32,8 +30,10 @@ fmt_col = 7 # col# in fmt for Size/PktLength
# setup protocol number dictionary
proto_number = {}
proto_number['mac'] = ost_pb.Protocol.kMacFieldNumber
+proto_number['vlan'] = ost_pb.Protocol.kVlanFieldNumber
proto_number['eth2'] = ost_pb.Protocol.kEth2FieldNumber
proto_number['ip4'] = ost_pb.Protocol.kIp4FieldNumber
+proto_number['ip6'] = ost_pb.Protocol.kIp6FieldNumber
proto_number['udp'] = ost_pb.Protocol.kUdpFieldNumber
proto_number['payload'] = ost_pb.Protocol.kPayloadFieldNumber
@@ -115,8 +115,7 @@ def ports(request, drone):
return ports
-protolist=['mac eth2 ip4 udp payload', 'mac eth2 ip4 udp']
-@pytest.fixture(scope='module', params=protolist)
+@pytest.fixture(scope='module')
def stream(request, drone, ports):
global proto_number
@@ -136,7 +135,39 @@ def stream(request, drone, ports):
s.control.packets_per_sec = 100
s.control.num_packets = 10
- # setup stream protocols as mac:eth2:ip:udp:payload
+ # XXX: don't setup any stream protocols
+
+ def fin():
+ # delete streams
+ log.info('deleting tx_stream %d' % stream_id.stream_id[0].id)
+ drone.deleteStream(stream_id)
+
+ request.addfinalizer(fin)
+
+ return stream_cfg
+
+protolist=['mac eth2 ip4 udp payload', 'mac eth2 ip4 udp']
+@pytest.fixture(scope='module', params=protolist)
+def stream_toggle_payload(request, drone, ports):
+ global proto_number
+
+ # add a stream
+ stream_id = ost_pb.StreamIdList()
+ stream_id.port_id.CopyFrom(ports.tx.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(ports.tx.port_id[0])
+ s = stream_cfg.stream.add()
+ s.stream_id.id = stream_id.stream_id[0].id
+ s.core.is_enabled = True
+ s.control.packets_per_sec = 100
+ s.control.num_packets = 10
+
+ # setup stream protocols
s.ClearField("protocol")
protos = request.param.split()
for p in protos:
@@ -156,11 +187,12 @@ def stream(request, drone, ports):
ost_pb.StreamCore.e_fl_dec,
ost_pb.StreamCore.e_fl_random
])
-def test_packet_length(drone, ports, stream, mode):
- """ Test random length packets """
+def test_framelen_modes(drone, ports, stream_toggle_payload, mode):
+ """ Test various framelen modes """
min_pkt_len = 100
max_pkt_len = 1000
+ stream = stream_toggle_payload
stream.stream[0].core.len_mode = mode
stream.stream[0].core.frame_len_min = min_pkt_len
stream.stream[0].core.frame_len_max = max_pkt_len
@@ -168,6 +200,13 @@ def test_packet_length(drone, ports, stream, mode):
log.info('configuring tx_stream %d' % stream.stream[0].stream_id.id)
drone.modifyStream(stream)
+ if stream.stream[0].protocol[-1].protocol_id.id \
+ == ost_pb.Protocol.kPayloadFieldNumber:
+ filter = 'udp && !expert.severity'
+ else:
+ filter = 'udp && eth.fcs_bad==1' \
+ '&& ip.checksum_bad==0 && udp.checksum_bad==0'
+
# clear tx/rx stats
log.info('clearing tx/rx stats')
drone.clearStats(ports.tx)
@@ -187,9 +226,11 @@ def test_packet_length(drone, ports, stream, mode):
log.info('dumping Rx capture buffer')
cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capture.pcap'])
print(cap_pkts)
+ print(filter)
cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capture.pcap',
- '-R', 'udp', '-o', fmt])
+ '-Y', filter, '-o', fmt])
print(cap_pkts)
+ assert cap_pkts.count('\n') == stream.stream[0].control.num_packets
result = extract_column(cap_pkts, fmt_col)
print(result)
diffSum = 0
@@ -217,3 +258,51 @@ def test_packet_length(drone, ports, stream, mode):
finally:
drone.stopTransmit(ports.tx)
+def test_frame_trunc(drone, ports, stream):
+ """ Test frame is truncated even if the protocol sizes exceed framelen """
+
+ #stream.stream[0].core.frame_len_min = min_pkt_len
+
+ # setup stream protocols as mac:vlan:eth2:ip6:udp:payload
+ stream.stream[0].ClearField("protocol")
+ protos = ['mac', 'vlan', 'eth2', 'ip6', 'udp', 'payload']
+ for p in protos:
+ stream.stream[0].protocol.add().protocol_id.id = proto_number[p]
+
+ # note: unless truncated minimum size of payload protocol is 1
+ exp_framelen = min(12+4+2+40+8+1, 60)
+
+ log.info('configuring tx_stream %d' % stream.stream[0].stream_id.id)
+ drone.modifyStream(stream)
+
+ # clear tx/rx stats
+ log.info('clearing tx/rx stats')
+ drone.clearStats(ports.tx)
+ drone.clearStats(ports.rx)
+
+ try:
+ drone.startCapture(ports.rx)
+ drone.startTransmit(ports.tx)
+ log.info('waiting for transmit to finish ...')
+ time.sleep(3)
+ drone.stopTransmit(ports.tx)
+ drone.stopCapture(ports.rx)
+
+ log.info('getting Rx capture buffer')
+ buff = drone.getCaptureBuffer(ports.rx.port_id[0])
+ drone.saveCaptureBuffer(buff, 'capture.pcap')
+ log.info('dumping Rx capture buffer')
+ cap_pkts = subprocess.check_output([tshark, '-n', '-r', 'capture.pcap',
+ '-Y', 'vlan', '-o', fmt])
+ print(cap_pkts)
+ assert cap_pkts.count('\n') == stream.stream[0].control.num_packets
+ result = extract_column(cap_pkts, fmt_col)
+ print(result)
+ for i in range(len(result)):
+ assert (int(result[i]) == exp_framelen)
+
+ os.remove('capture.pcap')
+ except RpcError as e:
+ raise
+ finally:
+ drone.stopTransmit(ports.tx)
diff --git a/test/utils.py b/test/utils.py
new file mode 100644
index 0000000..709b0b7
--- /dev/null
+++ b/test/utils.py
@@ -0,0 +1,22 @@
+#! /usr/bin/env python
+import subprocess
+import sys
+
+#from distutils.spawn import find_executable
+from distutils.version import LooseVersion
+
+def get_tshark(minversion = None):
+ tshark = 'tshark'
+ #if sys.platform == 'win32':
+ # tshark = find_executable(tshark)
+ if tshark and minversion:
+ out = subprocess.check_output([tshark, '-v']).split(None, 2)
+ # we expect output to be of the form 'Tshark ..."
+ ver = out[1]
+ if LooseVersion(ver) < LooseVersion(minversion):
+ tshark = None
+
+ if tshark is not None:
+ print('%s version %s found' % (tshark, ver))
+ return tshark
+