Skip to content

Instantly share code, notes, and snippets.

@sueszli
Last active January 26, 2024 14:14
Show Gist options
  • Save sueszli/e7d2c1fecdc9bd7e10f364a6b470c04f to your computer and use it in GitHub Desktop.
Save sueszli/e7d2c1fecdc9bd7e10f364a6b470c04f to your computer and use it in GitHub Desktop.
network traffic analysis
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We are receiving identical data from 4 different multicast publishers on seperate lines in a `.pcap` file.\n",
"\n",
"The only difference between the data is the timestamp.\n",
"\n",
"We want to find the 2 packages with the fastest time of arrival."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Parsing the data into a pandas dataframe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `.pcap` file contains the data in a binary format about which we can read more [here](https://wiki.wireshark.org/Development/LibpcapFileFormat)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/var/folders/bh/4ympj4l52bs8wxpg114kdh1m0000gn/T/ipykernel_37466/605009882.py:28: FutureWarning: The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.\n",
" df = pd.concat([df, pd.DataFrame([[timestamp, source, seqno]], columns=[\"timestamp\", \"source\", \"seqno\"])])\n"
]
}
],
"source": [
"from pathlib import Path\n",
"import dpkt\n",
"import io\n",
"import socket\n",
"\n",
"import pandas as pd\n",
"\n",
"df = pd.DataFrame(columns=[\"timestamp\", \"source\", \"seqno\"])\n",
"\n",
"file = open(Path(\"./traffic.pcap\"), \"rb\")\n",
"for ts, buf in dpkt.pcap.Reader(file):\n",
" eth = dpkt.ethernet.Ethernet(buf)\n",
" ip = eth.data\n",
" tcp = ip.data\n",
"\n",
" # parse packet\n",
" timestamp: float = ts\n",
" source: str = socket.inet_ntoa(ip.src) + \":\" + str(tcp.sport)\n",
" destination: str = socket.inet_ntoa(ip.dst) + \":\" + str(tcp.dport) # ignore, is our own ip\n",
" data: str = io.BytesIO(tcp.data).read().decode(\"utf-8\").strip()\n",
"\n",
" # parse data (not relevant)\n",
" symbol = data.split(\" \")[1].strip()\n",
" seqno = int(data.split(\" \")[3].strip())\n",
" price = int(data.split(\" \")[5].strip())\n",
"\n",
" # store data in dataframe\n",
" df = pd.concat([df, pd.DataFrame([[timestamp, source, seqno]], columns=[\"timestamp\", \"source\", \"seqno\"])])\n",
"\n",
"assert len(df) > 0"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now I just wonder whether the `timestamp` has the same semantics as the logical timestamp `seqno`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Same order: False\n"
]
}
],
"source": [
"sort_by_timestamp = df.sort_values(by=[\"timestamp\"])\n",
"sort_by_seqno = df.sort_values(by=[\"seqno\"])\n",
"same_order = sort_by_timestamp.equals(sort_by_seqno)\n",
"\n",
"print(\"Same order: \" + str(same_order))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we know that they're different. Let's keep them both but stick to the timestamp."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Finding the two fastest packages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now all we need to do is sort by the timestamp and take the first two rows."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>timestamp</th>\n",
" <th>source</th>\n",
" <th>seqno</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1.473412e+09</td>\n",
" <td>10.10.10.4:33000</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1.473412e+09</td>\n",
" <td>10.10.10.1:33000</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" timestamp source seqno\n",
"0 1.473412e+09 10.10.10.4:33000 0\n",
"0 1.473412e+09 10.10.10.1:33000 0"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_sorted = df.sort_values(\"seqno\")\n",
"fastest_packages = df_sorted.head(2)\n",
"\n",
"fastest_packages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Comparing publishers"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally let's compare the publishers with some metrics."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'machine nr.1': {'avg': 499.79107509469986,\n",
" 'median': 499.7542179822922,\n",
" 'std': 288.82414498791394},\n",
" 'machine nr.2': {'avg': 500.82151508741117,\n",
" 'median': 501.29140400886536,\n",
" 'std': 288.6779254909906},\n",
" 'machine nr.3': {'avg': 499.7361731772423,\n",
" 'median': 499.83047103881836,\n",
" 'std': 288.8137098297796},\n",
" 'machine nr.4': {'avg': 499.68862091493605,\n",
" 'median': 499.7177765369415,\n",
" 'std': 288.8120833932828}}"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# get delta from fastest machine\n",
"fastest_ts = df_sorted.head(1)[\"timestamp\"].values[0]\n",
"\n",
"df_delta = pd.DataFrame(columns=[\"source\", \"delta\"])\n",
"for index, row in df_sorted.iterrows():\n",
" delta = row[\"timestamp\"] - fastest_ts if not row[\"timestamp\"] == fastest_ts else 0\n",
" df_delta = pd.concat([df_delta, pd.DataFrame([[row[\"source\"], delta]], columns=[\"source\", \"delta\"])])\n",
"df_delta = df_delta.sort_values(\"delta\")\n",
"\n",
"# group by source\n",
"metrics = {}\n",
"grouped = df_delta.groupby(\"source\")\n",
"for name, group in grouped:\n",
" ip = name.split(\":\")[0]\n",
" name = \"machine nr.\" + ip.split(\".\")[3]\n",
" avg = group[\"delta\"].mean()\n",
" median = group[\"delta\"].median()\n",
" std = group[\"delta\"].std()\n",
" metrics[name] = {\n",
" \"avg\": avg,\n",
" \"median\": median,\n",
" \"std\": std\n",
" }\n",
"\n",
"metrics\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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UGDWrFkAgHXr1mH37t1awSg+Ph4xMTHw9fXF2LFjkZKSgvr6esyZM0fTp66uTutoX3l5OYqKijBw4EA4OTlp1aBWqxEWFgZnZ2eoVCoYGRnB3d0d2dnZCAkJwaBBg1o9aqfLPnSplYiIiKgj3T7UnT9/HrNmzcLly5dhY2ODwMBAHD58GDY2NgB+P4JWVlamtc7MmTNx6dIlLFu2DBUVFfD29kZWVpbWDQnHjh3DxIkTNe/j4+MBADExMcjIyNDanlwux+rVqxEUFARjY2NNu5eXF/bt26ep5U667EOXWomIiIg6IhNCCEMXQV3v6tWrsLKyQm1tLSwtLQ1dDtG9k2xl6ApaSq41dAVE1IPp+zu8x11TR0REREQtMdQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDXQ9w7tw5TJgwAe7u7vD09MTOnTsNXRIRERF1M0aGLoA6ZmRkhJSUFHh7e6OiogI+Pj6YNm0azM3NDV0a9SJDEr8wdAmtOmNi6AqIiLoHhroewMHBAQ4ODgAAe3t7WFtbo6amhqGOiIiINAx++jU5ORkymUzr5ebm1uF6qampGDJkCExMTODv748jR450qk9zczNef/11DB06FKampnB1dcXKlSshhOjS8R06dAjh4eFQKpWQyWTIzMzUezwAUFBQgObmZjg6OnZpnURERNSzGTzUAYCHhwcuXryoeeXm5rbbX6VSIT4+HklJSTh+/Di8vLwwZcoUVFVV6dznzTffRFpaGtatW4cff/wRb775Jt566y28//77be43Ly8PN27caNFeXFyMysrKVtepr6+Hl5cXUlNT72o8AFBTU4Po6Ghs2LCh3c+HiIiIep9uEeqMjIxgb2+veVlbW7fbf+3atZg3bx7mzJkDd3d3rF+/HmZmZti0aZPOfb799ls8/vjjmD59OoYMGYInn3wSkydPbvMImVqtRlxcHCIjI9Hc3KxpLykpQUhICLZs2dLqemFhYVi1ahVmzJhxV+NpbGxEREQEEhMTERAQ0O7nQ0RERL1Ptwh1p0+fhlKphIuLC6KionD27Nk2+zY1NaGgoAChoaGaNrlcjtDQUOTn5+vcJyAgADk5OTh16hQA4MSJE8jNzUVYWFir+5XL5dizZw8KCwsRHR0NtVqNsrIyhISEICIiAkuXLtVr7LrUKoRAbGwsQkJCMHv27Ha3l5qaCnd3d/j5+elVDxEREfVMBg91/v7+yMjIQFZWFtLS0lBeXo6goCBcu3at1f7V1dVobm6GnZ2dVrudnR0qKip07pOYmIhnnnkGbm5u6NOnDx588EEsXrwYUVFRbdaqVCqxf/9+5ObmIjIyEiEhIQgNDUVaWpre49el1ry8PKhUKmRmZsLb2xve3t44efJkq9uLi4tDcXExjh49qndNRERE1PMY/O7X24+MeXp6wt/fH87OztixYwfmzp17z/a7Y8cObNu2Ddu3b4eHhweKioqwePFiKJVKxMTEtLmek5MTtm7diuDgYLi4uCA9PR0ymeye1QkAgYGBUKvV93QfRERE1LMZ/Ejdnfr3748RI0agtLS01eXW1tZQKBQtbkyorKyEvb29zn1eeeUVzdG6MWPGYPbs2ViyZAnWrFnTbn2VlZWYP38+wsPD0dDQgCVLlug7VJ1rJSIiIupItwt1dXV1KCsr0zyX7U7Gxsbw8fFBTk6Opk2tViMnJwfjxo3TuU9DQwPkcu3hKxSKdo+IVVdXY9KkSRg1ahR27dqFnJwcqFQqJCQk6D1eXWolIiIi6ojBT78mJCQgPDwczs7OuHDhApKSkqBQKDBr1iwAwLp167B7926t0BMfH4+YmBj4+vpi7NixSElJQX19PebMmaNzn/DwcLzxxhtwcnKCh4cHCgsLsXbtWjz33HOt1qlWqxEWFgZnZ2eoVCoYGRnB3d0d2dnZCAkJwaBBg1o9aldXV6d11LG8vBxFRUUYOHAgnJycdB4PERERUXsMHurOnz+PWbNm4fLly7CxsUFgYCAOHz4MGxsbAL8fHSsrK9NaZ+bMmbh06RKWLVuGiooKeHt7IysrS+tmg476vP/++3j99dfx4osvoqqqCkqlEgsWLMCyZctarVMul2P16tUICgqCsbGxpt3Lywv79u3T1HunY8eOYeLEiZr38fHxAICYmBhkZGToPB4iIiKi9shEV3+FAnULV69ehZWVFWpra2FpaWnockgCuu93v0YauoSWkmsNXQER9WD6/g7vdtfUEREREVHnMdQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDHREREZEEMNQRERERSQBDXQ9w7tw5TJgwAe7u7vD09MTOnTsNXRIRERF1M0aGLoA6ZmRkhJSUFHh7e6OiogI+Pj6YNm0azM3NDV0aERERdRMMdT2Ag4MDHBwcAAD29vawtrZGTU0NQx0RERFpGPz0a3JyMmQymdbLzc2tw/VSU1MxZMgQmJiYwN/fH0eOHOl0n19//RXPPvssHnjgAZiammLMmDE4duxYl40NAA4dOoTw8HAolUrIZDJkZmbqPR4AKCgoQHNzMxwdHbu0TiIiIurZDB7qAMDDwwMXL17UvHJzc9vtr1KpEB8fj6SkJBw/fhxeXl6YMmUKqqqqdO5z5coVjB8/Hn369MGXX36J4uJivPPOOxgwYECb+83Ly8ONGzdatBcXF6OysrLVderr6+Hl5YXU1NS7Gg8A1NTUIDo6Ghs2bGj38yEiIqLeRyaEEIYsIDk5GZmZmSgqKtJ5HX9/f/j5+WHdunUAALVaDUdHRyxatAiJiYk69UlMTEReXh6++eYbnfapVqvx0EMPYfjw4fj444+hUCgAACUlJQgODkZ8fDyWLl3a7jZkMhl2796NiIiITo+nsbERjz76KObNm4fZs2d3WO/Vq1dhZWWF2tpaWFpa6jRGovYMSfzC0CW06oxJpKFLaCm51tAVEFEPpu/v8G5xpO706dNQKpVwcXFBVFQUzp4922bfpqYmFBQUIDQ0VNMml8sRGhqK/Px8nft89tln8PX1xVNPPQVbW1s8+OCD2LhxY5v7lcvl2LNnDwoLCxEdHQ21Wo2y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RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBLAUEdEREQkAQx1RERERBJgZOgCiIiIOiXZytAVtC651tAVUC/HI3U9wLlz5zBhwgS4u7vD09MTO3fuNHRJRERE1M3wSF0PYGRkhJSUFHh7e6OiogI+Pj6YNm0azM3NDV0aERERdRMMdT2Ag4MDHBwcAAD29vawtrZGTU0NQx0RERFpdKvTr3/5y18gk8mwePHidvtdu3YNixcvhrOzM0xNTREQEICjR492qk9zczNef/11DB06FKampnB1dcXKlSshhOjSMR06dAjh4eFQKpWQyWTIzMxstV9qaiqGDBkCExMT+Pv748iRI632KygoQHNzMxwdHbu0TiIiIurZuk2oO3r0KP7+97/D09Ozw77PP/88srOzsXXrVpw8eRKTJ09GaGgofv31V537vPnmm0hLS8O6devw448/4s0338Rbb72F999/v8395uXl4caNGy3ai4uLUVlZ2eo69fX18PLyQmpqapvbValUiI+PR1JSEo4fPw4vLy9MmTIFVVVVWv1qamoQHR2NDRs2tPv5EBERUe/TLUJdXV0doqKisHHjRgwYMKDdvtevX8cnn3yCt956C4888giGDRuG5ORkDBs2DGlpaTr3+fbbb/H4449j+vTpGDJkCJ588klMnjy5zSNkarUacXFxiIyMRHNzs6a9pKQEISEh2LJlS6vrhYWFYdWqVZgxY0abY1q7di3mzZuHOXPmwN3dHevXr4eZmRk2bdqk6dPY2IiIiAgkJiYiICCg3c+IiIiIep9uEeri4uIwffp0hIaGdtj35s2baG5uhomJiVa7qakpcnNzde4TEBCAnJwcnDp1CgBw4sQJ5ObmIiwsrNX9yuVy7NmzB4WFhYiOjoZarUZZWRlCQkIQERGBpUuXdnrcANDU1ISCggKtscvlcoSGhiI/Px8AIIRAbGwsQkJCMHv27Ha3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m/Nff/0VU6dOxVNPPaVVR3fEee+aeX/llVcwd+5c/PLLL1i+fDmio6Px+eefd8vrwwDO+93O+/vvv49r167h1VdfbXc/7WGoo/umT58+Wu9lMplW261/qNRqNQDg448/RkJCAt555x2MGzcOFhYW+Otf/4rvvvsOAGBqaqr3fm/tQ9f+4v9/m15dXR0AYOPGjfD399fqp1Ao7roOc3PzdrfR03DOu2bOL1y4gIkTJyIgIAAbNmzosL+hcd67Zt6tra1hbW2NESNGYNSoUXB0dMThw4cxbty4Dtc1BM773c37/v37kZ+f3+I7aX19fREVFYUtW7a0u3+AoY66sby8PAQEBODFF1/UtJWVlWl+trCwwJAhQ5CTk4OJEyfel5rs7OygVCrx888/Iyoq6r7sszfhnLf066+/YuLEifDx8cHmzZshl0vvoQWc947dCgeNjY0GrqTrcN61vffee1i1apXm/YULFzBlyhSoVKoWAbMtDHXUbQ0fPhwffvgh9u7di6FDh2Lr1q04evQohg4dqumTnJyMF154Aba2tggLC8O1a9eQl5eHRYsW3bO6li9fjpdffhlWVlaYOnUqGhsbcezYMVy5cgXx8fFdtp8jR44gOjoaOTk5GDRoEADg7NmzqKmpwdmzZ9Hc3Kx5ftGwYcPQr1+/Ltu3oXDOtef8119/xYQJE+Ds7Iy3334bly5d0vS1t7fvsv0aGudde96/++47HD16FIGBgRgwYADKysrw+uuvw9XVtdsepdMH51173p2cnLSW3/o33dXVFYMHD9Zpmwx11G0tWLAAhYWFmDlzJmQyGWbNmoUXX3wRX375paZPTEwM/vOf/+Ddd99FQkICrK2t7/mznJ5//nmYmZnhr3/9K1555RWYm5tjzJgxWLx4cZfup6GhASUlJVrPMlq2bJnWIfgHH3wQAHDgwAFMmDChS/dvCJxz7TnPzs5GaWkpSktLW/yjfut0kRRw3rXn3czMDLt27UJSUhLq6+vh4OCAqVOn4n/+539anJrryTjvLf+Nv1syIaV/GYiIiIh6KeldnEFERETUCzHUEREREUkAQx0RERGRBDDUEREREUkAQx0RERGRBDDUEREREUkAQx0RERGRBDDUEREREUkAQx0RERGRBDDUEREREUkAQx0RERGRBDDUEREREUnA/wMaG3q84owXjgAAAABJRU5ErkJggg==",
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# plot metrics as bar chart\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"# Prepare data for plotting\n",
"labels = list(metrics.keys())\n",
"avg = [metrics[m]['avg'] for m in labels]\n",
"median = [metrics[m]['median'] for m in labels]\n",
"std = [metrics[m]['std'] for m in labels]\n",
"\n",
"x = np.arange(len(labels)) # the label locations\n",
"width = 0.3 # the width of the bars\n",
"\n",
"fig, ax = plt.subplots()\n",
"rects1 = ax.bar(x - width, avg, width, label='Avg')\n",
"rects2 = ax.bar(x, median, width, label='Median')\n",
"# rects3 = ax.bar(x + width, std, width, label='Std')\n",
"\n",
"# Add some text for labels, title and custom x-axis tick labels, etc.\n",
"ax.set_ylabel('Scores')\n",
"ax.set_title('Scores by machine and metric')\n",
"ax.set_xticks(x)\n",
"ax.set_xticklabels(labels)\n",
"ax.legend()\n",
"\n",
"ax.set_yscale('log')\n",
"fig.tight_layout()\n",
"\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment