├── README.md ├── Activity_Classification.ipynb ├── .ipynb_checkpoints ├── Activity_Classification-checkpoint.ipynb ├── Flow_Feature_Extraction-checkpoint.ipynb └── App_Classification-checkpoint.ipynb ├── Flow_Feature_Extraction.ipynb ├── App_Classification.ipynb └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | This repo contains the dataset and code published in the article 2 | Y.Heng, V.Chandrasekhar and J.G.Andrews, "UTMobileNetTraffic2021: A Labeled Public Network Traffic Dataset", to appear in IEEE Networking Letters 3 | Digital Object Identifier: 10.1109/LNET.2021.3098455 4 | 5 | # UTMobileNetTraffic2021 6 | To download the dataset, visit https://utexas.box.com/s/okrimcsz1mn9ec4j667kbb00d9gt16ii 7 | 8 | To run the code: 9 | 1. Place the downloaded dataset under ./Data folder. 10 | 2. Create a ./Processed Data folder to store processed data. 11 | 3. Use the Flow_Feature_Extraction notebook to extract flow features. 12 | 4. Use the App_Classification and Activity_Classification notebooks to reproduce the application classification and activity classification examples. 13 | -------------------------------------------------------------------------------- /Activity_Classification.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "------ action classification -------\n", 13 | "------ dropbox -------\n", 14 | "RFC Accuracy: 82.28% (6.33%)\n", 15 | "KNN Accuracy: 82.65% (7.85%)\n", 16 | "XGB Accuracy: 81.55% (6.32%)\n", 17 | "------ facebook -------\n", 18 | "RFC Accuracy: 64.68% (11.22%)\n", 19 | "KNN Accuracy: 66.52% (6.05%)\n", 20 | "XGB Accuracy: 60.41% (7.44%)\n", 21 | "------ gmail -------\n", 22 | "RFC Accuracy: 84.53% (9.63%)\n", 23 | "KNN Accuracy: 82.95% (9.60%)\n", 24 | "XGB Accuracy: 85.03% (8.50%)\n", 25 | "------ google-drive -------\n", 26 | "RFC Accuracy: 79.91% (12.65%)\n", 27 | "KNN Accuracy: 75.27% (6.96%)\n", 28 | "XGB Accuracy: 79.00% (12.15%)\n", 29 | "------ hulu -------\n", 30 | "RFC Accuracy: 93.06% (2.96%)\n", 31 | "KNN Accuracy: 90.89% (2.98%)\n", 32 | "XGB Accuracy: 93.35% (3.06%)\n", 33 | "------ instagram -------\n", 34 | "RFC Accuracy: 98.30% (2.60%)\n", 35 | "KNN Accuracy: 96.11% (4.34%)\n", 36 | "XGB Accuracy: 97.71% (2.80%)\n", 37 | "------ netflix -------\n", 38 | "RFC Accuracy: 86.79% (8.08%)\n", 39 | "KNN Accuracy: 81.79% (9.34%)\n", 40 | "XGB Accuracy: 88.26% (6.50%)\n", 41 | "------ pandora -------\n", 42 | "RFC Accuracy: 73.41% (2.73%)\n", 43 | "KNN Accuracy: 69.55% (3.82%)\n", 44 | "XGB Accuracy: 72.62% (3.27%)\n", 45 | "------ reddit -------\n", 46 | "RFC Accuracy: 71.86% (7.41%)\n", 47 | "KNN Accuracy: 72.06% (6.35%)\n", 48 | "XGB Accuracy: 72.81% (6.98%)\n", 49 | "------ spotify -------\n", 50 | "RFC Accuracy: 75.20% (3.47%)\n", 51 | "KNN Accuracy: 69.89% (5.40%)\n", 52 | "XGB Accuracy: 73.87% (3.50%)\n", 53 | "------ twitter -------\n", 54 | "RFC Accuracy: 65.56% (5.92%)\n", 55 | "KNN Accuracy: 64.58% (3.09%)\n", 56 | "XGB Accuracy: 65.88% (7.44%)\n", 57 | "------ youtube -------\n", 58 | "RFC Accuracy: 84.02% (5.18%)\n", 59 | "KNN Accuracy: 74.90% (7.07%)\n", 60 | "XGB Accuracy: 82.67% (5.91%)\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "\"\"\"\n", 66 | "@author: Yuqiang (Ethan) Heng\n", 67 | "\"\"\"\n", 68 | "import numpy as np\n", 69 | "import pandas as pd\n", 70 | "import matplotlib.pyplot as plt\n", 71 | "from sklearn.utils import resample\n", 72 | "\n", 73 | "from sklearn.model_selection import cross_val_score, KFold, train_test_split, StratifiedKFold, GridSearchCV\n", 74 | "from sklearn.preprocessing import LabelEncoder, StandardScaler\n", 75 | "from sklearn.metrics import accuracy_score\n", 76 | "from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier\n", 77 | "from sklearn.metrics import plot_confusion_matrix\n", 78 | "\n", 79 | "from sklearn.neighbors import KNeighborsClassifier\n", 80 | "from sklearn import svm\n", 81 | "from xgboost import XGBClassifier\n", 82 | "\n", 83 | "df_all = pd.read_csv('./Processed Data/random_scenario_bi_flow_features.csv',low_memory=False).dropna().drop('Unnamed: 0',axis=1)\n", 84 | "\n", 85 | "dl_features = [f for f in df_all.columns.values if f.split('_')[0] == 'dl']\n", 86 | "ul_features = [f for f in df_all.columns.values if f.split('_')[0] == 'ul']\n", 87 | "features = np.concatenate((dl_features,ul_features))\n", 88 | "\n", 89 | "print('------ action classification -------')\n", 90 | "for app_name in df_all['app'].unique():\n", 91 | " df = df_all[df_all['app']==app_name]\n", 92 | " if len(df['action'].unique())==1:\n", 93 | " continue\n", 94 | " print('------ {} -------'.format(app_name))\n", 95 | " X = df[dl_features]\n", 96 | " scaler = StandardScaler()\n", 97 | " X = scaler.fit_transform(X)\n", 98 | " le = LabelEncoder()\n", 99 | " Y = le.fit_transform(df['action'])\n", 100 | " \n", 101 | " kfold = KFold(n_splits=10, shuffle = True, random_state=7)\n", 102 | " rfc = RandomForestClassifier()\n", 103 | " rfc_results = cross_val_score(rfc, X, Y, cv=kfold, n_jobs = kfold.n_splits)\n", 104 | " print(\"RFC Accuracy: %.2f%% (%.2f%%)\" % (rfc_results.mean()*100, rfc_results.std()*100))\n", 105 | " \n", 106 | " knn = KNeighborsClassifier()\n", 107 | " knn_results = cross_val_score(knn, X, Y, cv=kfold, n_jobs = kfold.n_splits) \n", 108 | " print(\"KNN Accuracy: %.2f%% (%.2f%%)\" % (knn_results.mean()*100, knn_results.std()*100))\n", 109 | " \n", 110 | " xgb = XGBClassifier()\n", 111 | " xgb_results = cross_val_score(xgb, X, Y, cv=kfold, n_jobs = kfold.n_splits)\n", 112 | " print(\"XGB Accuracy: %.2f%% (%.2f%%)\" % (xgb_results.mean()*100, xgb_results.std()*100))" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [] 121 | } 122 | ], 123 | "metadata": { 124 | "kernelspec": { 125 | "display_name": "Python 3 (ipykernel)", 126 | "language": "python", 127 | "name": "python3" 128 | }, 129 | "language_info": { 130 | "codemirror_mode": { 131 | "name": "ipython", 132 | "version": 3 133 | }, 134 | "file_extension": ".py", 135 | "mimetype": "text/x-python", 136 | "name": "python", 137 | "nbconvert_exporter": "python", 138 | "pygments_lexer": "ipython3", 139 | "version": "3.7.4" 140 | } 141 | }, 142 | "nbformat": 4, 143 | "nbformat_minor": 4 144 | } 145 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/Activity_Classification-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 3, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "------ action classification -------\n", 13 | "------ dropbox -------\n", 14 | "RFC Accuracy: 81.55% (5.34%)\n", 15 | "KNN Accuracy: 74.42% (8.10%)\n", 16 | "XGB Accuracy: 81.27% (8.12%)\n", 17 | "------ facebook -------\n", 18 | "RFC Accuracy: 65.63% (9.63%)\n", 19 | "KNN Accuracy: 70.80% (8.37%)\n", 20 | "XGB Accuracy: 62.81% (11.84%)\n", 21 | "------ gmail -------\n", 22 | "RFC Accuracy: 81.05% (8.22%)\n", 23 | "KNN Accuracy: 78.42% (7.61%)\n", 24 | "XGB Accuracy: 82.63% (10.27%)\n", 25 | "------ google-drive -------\n", 26 | "RFC Accuracy: 73.55% (10.96%)\n", 27 | "KNN Accuracy: 66.09% (5.58%)\n", 28 | "XGB Accuracy: 67.91% (10.07%)\n", 29 | "------ hulu -------\n", 30 | "RFC Accuracy: 93.89% (3.52%)\n", 31 | "KNN Accuracy: 91.11% (4.57%)\n", 32 | "XGB Accuracy: 92.64% (3.46%)\n", 33 | "------ instagram -------\n", 34 | "RFC Accuracy: 98.27% (2.65%)\n", 35 | "KNN Accuracy: 97.22% (3.73%)\n", 36 | "XGB Accuracy: 95.95% (5.89%)\n", 37 | "------ netflix -------\n", 38 | "RFC Accuracy: 84.96% (6.94%)\n", 39 | "KNN Accuracy: 80.22% (8.78%)\n", 40 | "XGB Accuracy: 87.27% (7.67%)\n", 41 | "------ pandora -------\n", 42 | "RFC Accuracy: 73.34% (4.35%)\n", 43 | "KNN Accuracy: 68.28% (4.01%)\n", 44 | "XGB Accuracy: 73.26% (4.36%)\n", 45 | "------ reddit -------\n", 46 | "RFC Accuracy: 69.83% (6.41%)\n", 47 | "KNN Accuracy: 70.20% (7.14%)\n", 48 | "XGB Accuracy: 70.02% (7.12%)\n", 49 | "------ spotify -------\n", 50 | "RFC Accuracy: 78.91% (5.25%)\n", 51 | "KNN Accuracy: 73.83% (5.50%)\n", 52 | "XGB Accuracy: 79.29% (6.19%)\n", 53 | "------ twitter -------\n", 54 | "RFC Accuracy: 67.47% (5.80%)\n", 55 | "KNN Accuracy: 66.82% (3.48%)\n", 56 | "XGB Accuracy: 68.11% (6.10%)\n", 57 | "------ youtube -------\n", 58 | "RFC Accuracy: 83.76% (6.06%)\n", 59 | "KNN Accuracy: 78.41% (5.37%)\n", 60 | "XGB Accuracy: 82.11% (6.54%)\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "\"\"\"\n", 66 | "@author: Yuqiang (Ethan) Heng\n", 67 | "\"\"\"\n", 68 | "import numpy as np\n", 69 | "import pandas as pd\n", 70 | "import matplotlib.pyplot as plt\n", 71 | "from sklearn.utils import resample\n", 72 | "\n", 73 | "from sklearn.model_selection import cross_val_score, KFold, train_test_split, StratifiedKFold, GridSearchCV\n", 74 | "from sklearn.preprocessing import LabelEncoder, StandardScaler\n", 75 | "from sklearn.metrics import accuracy_score\n", 76 | "from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier\n", 77 | "from sklearn.metrics import plot_confusion_matrix\n", 78 | "\n", 79 | "from sklearn.neighbors import KNeighborsClassifier\n", 80 | "from sklearn import svm\n", 81 | "from xgboost import XGBClassifier\n", 82 | "\n", 83 | "df_all = pd.read_csv('./Processed Data/random_scenario_bi_flow_features.csv',low_memory=False).dropna().drop('Unnamed: 0',axis=1)\n", 84 | "\n", 85 | "dl_features = [f for f in df_all.columns.values if f.split('_')[0] == 'dl']\n", 86 | "ul_features = [f for f in df_all.columns.values if f.split('_')[0] == 'ul']\n", 87 | "features = np.concatenate((dl_features,ul_features))\n", 88 | "\n", 89 | "print('------ action classification -------')\n", 90 | "for app_name in df_all['app'].unique():\n", 91 | " df = df_all[df_all['app']==app_name]\n", 92 | " if len(df['action'].unique())==1:\n", 93 | " continue\n", 94 | " print('------ {} -------'.format(app_name))\n", 95 | " X = df[dl_features]\n", 96 | " scaler = StandardScaler()\n", 97 | " X = scaler.fit_transform(X)\n", 98 | " le = LabelEncoder()\n", 99 | " Y = le.fit_transform(df['action'])\n", 100 | " \n", 101 | " kfold = KFold(n_splits=10, shuffle = True, random_state=7)\n", 102 | " rfc = RandomForestClassifier()\n", 103 | " rfc_results = cross_val_score(rfc, X, Y, cv=kfold, n_jobs = kfold.n_splits)\n", 104 | " print(\"RFC Accuracy: %.2f%% (%.2f%%)\" % (rfc_results.mean()*100, rfc_results.std()*100))\n", 105 | " \n", 106 | " knn = KNeighborsClassifier()\n", 107 | " knn_results = cross_val_score(knn, X, Y, cv=kfold, n_jobs = kfold.n_splits) \n", 108 | " print(\"KNN Accuracy: %.2f%% (%.2f%%)\" % (knn_results.mean()*100, knn_results.std()*100))\n", 109 | " \n", 110 | " xgb = XGBClassifier()\n", 111 | " xgb_results = cross_val_score(xgb, X, Y, cv=kfold, n_jobs = kfold.n_splits)\n", 112 | " print(\"XGB Accuracy: %.2f%% (%.2f%%)\" % (xgb_results.mean()*100, xgb_results.std()*100))" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [] 121 | } 122 | ], 123 | "metadata": { 124 | "kernelspec": { 125 | "display_name": "Python 3", 126 | "language": "python", 127 | "name": "python3" 128 | }, 129 | "language_info": { 130 | "codemirror_mode": { 131 | "name": "ipython", 132 | "version": 3 133 | }, 134 | "file_extension": ".py", 135 | "mimetype": "text/x-python", 136 | "name": "python", 137 | "nbconvert_exporter": "python", 138 | "pygments_lexer": "ipython3", 139 | "version": "3.7.4" 140 | } 141 | }, 142 | "nbformat": 4, 143 | "nbformat_minor": 4 144 | } 145 | -------------------------------------------------------------------------------- /Flow_Feature_Extraction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "['dropbox' 'facebook' 'gmail' 'google-drive' 'hulu' 'instagram'\n", 13 | " 'messenger' 'netflix' 'pandora' 'pinterest' 'reddit' 'spotify' 'twitter'\n", 14 | " 'youtube'] 14 288\n", 15 | "['dropbox_download' 'dropbox_upload' 'facebook_scroll-newsfeed'\n", 16 | " 'facebook_search-page' 'gmail_open-email' 'gmail_send-email'\n", 17 | " 'google-drive_download' 'google-drive_upload' 'hulu_scroll-home'\n", 18 | " 'hulu_watch-video' 'instagram_IgSearchBrowse' 'instagram_send-message'\n", 19 | " 'messenger_send-message' 'netflix_browse-home' 'netflix_watch-video'\n", 20 | " 'pandora_play-music' 'pandora_search-music' 'pinterest_tap-board'\n", 21 | " 'reddit_browse' 'reddit_post' 'spotify_play-music' 'spotify_search-music'\n", 22 | " 'twitter_post-tweet' 'twitter_scroll-feed' 'twitter_send-message'\n", 23 | " 'youtube_play-video' 'youtube_search'] 27\n" 24 | ] 25 | } 26 | ], 27 | "source": [ 28 | "\"\"\"\n", 29 | "@author: Yuqiang (Ethan) Heng\n", 30 | "\"\"\"\n", 31 | "import numpy as np\n", 32 | "import pandas as pd\n", 33 | "from tqdm import tqdm\n", 34 | "from os import listdir\n", 35 | "from os.path import isfile, join\n", 36 | "\n", 37 | "scenario = 'random' #deterministic, random or wild\n", 38 | "if scenario == 'random':\n", 39 | " mypath = './Data/Randomized Automated Data'\n", 40 | "elif scenario == 'deterministic':\n", 41 | " mypath = './Data/Deterministic Automated Data'\n", 42 | "elif scenario == 'wild':\n", 43 | " mypath = './Data/Wild Test Data'\n", 44 | "else:\n", 45 | " raise NameError('Dataset Not Supported')\n", 46 | "\n", 47 | "onlyfiles = [f for f in listdir(mypath) if isfile(join(mypath, f))]\n", 48 | "apps = np.unique([f.split('_')[0] for f in onlyfiles])\n", 49 | "print(apps, len(apps), len(onlyfiles))\n", 50 | "app_actions = np.unique(['_'.join(f.split('_')[:2]) for f in onlyfiles])\n", 51 | "print(app_actions, len(app_actions))\n", 52 | "\n", 53 | "sel_apps = apps\n", 54 | "sel_app_files = {i:[] for i in sel_apps}\n", 55 | "\n", 56 | "for fname in onlyfiles:\n", 57 | " app_name = fname.split('_')[0]\n", 58 | " if app_name in sel_apps:\n", 59 | " sel_app_files[app_name].append(fname)" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 2, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "flow_columns = ['ip.src', 'srcport', 'ip.dst', 'dstport', 'protocal']\n", 69 | "\n", 70 | "def get_protocal(row):\n", 71 | " if not pd.isnull(row['tcp.len']):\n", 72 | " return 'TCP'\n", 73 | " elif not pd.isnull(row['udp.length']):\n", 74 | " return 'UDP'\n", 75 | " else:\n", 76 | " return 'Unknown'\n", 77 | " \n", 78 | "def get_srt_port(row):\n", 79 | " if not pd.isnull(row['tcp.len']):\n", 80 | " return row['tcp.srcport']\n", 81 | " elif not pd.isnull(row['udp.length']):\n", 82 | " return row['udp.srcport']\n", 83 | " else:\n", 84 | " return 'Unknown'\n", 85 | " \n", 86 | "def get_dst_port(row):\n", 87 | " if not pd.isnull(row['tcp.len']):\n", 88 | " return row['tcp.dstport']\n", 89 | " elif not pd.isnull(row['udp.length']):\n", 90 | " return row['udp.dstport']\n", 91 | " else:\n", 92 | " return 'Unknown'\n", 93 | " \n", 94 | "columns = ['frame.number','frame.time','frame.len','frame.cap_len','ip.hdr_len',\n", 95 | " 'ip.dsfield.ecn','ip.len','ip.frag_offset','ip.ttl','ip.proto','ip.src',\n", 96 | " 'ip.dst','tcp.hdr_len','tcp.len','tcp.srcport','tcp.dstport','tcp.flags.ns',\n", 97 | " 'tcp.flags.fin','tcp.window_size_value','tcp.urgent_pointer','tcp.option_kind',\n", 98 | " 'tcp.option_len','udp.srcport','udp.dstport','udp.length']\n", 99 | "\n", 100 | "def compute_flow_features(df):\n", 101 | " flow_features = {}\n", 102 | " flow_features['total_num_pkts'] = len(df)\n", 103 | " pkt_size = df['ip.len'].astype(float)\n", 104 | " flow_features['total_num_bytes'] = pkt_size.sum()\n", 105 | " flow_features['min_pkt_size'] = pkt_size.min()\n", 106 | " flow_features['max_pkt_size'] = pkt_size.max()\n", 107 | " flow_features['mean_pkt_size'] = pkt_size.mean()\n", 108 | " flow_features['std_pkt_size'] = pkt_size.std()\n", 109 | " iat = pd.to_datetime(df['frame.time']).diff(1).dt.total_seconds().iloc[1:]\n", 110 | " flow_features['min_iat'] = iat.min()\n", 111 | " flow_features['max_iat'] = iat.max()\n", 112 | " flow_features['mean_iat'] = iat.mean()\n", 113 | " flow_features['std_iat'] = iat.std()\n", 114 | " flow_features['dur'] = iat.sum()\n", 115 | " return flow_features\n", 116 | "\n", 117 | "def process_df_by_flow(df):\n", 118 | " df['protocal'] = df.apply(lambda row: get_protocal(row), axis=1)\n", 119 | " df['srcport'] = df.apply(lambda row: get_srt_port(row), axis=1)\n", 120 | " df['dstport'] = df.apply(lambda row: get_dst_port(row), axis=1) \n", 121 | " df_flow = pd.DataFrame()\n", 122 | " flow_columns = ['ip.src', 'srcport', 'ip.dst', 'dstport', 'protocal']\n", 123 | " ul_flows = {}\n", 124 | " dl_flows = {}\n", 125 | " for flow, flow_df in df.groupby(by=flow_columns):\n", 126 | " if flow[0].split('.')[0] == '10':\n", 127 | " ul_flows[flow] = compute_flow_features(flow_df)\n", 128 | " else:\n", 129 | " dl_flows[flow] = compute_flow_features(flow_df)\n", 130 | " for ul_flow, ul_flow_features in ul_flows.items():\n", 131 | " for dl_flow, dl_flow_features in dl_flows.items():\n", 132 | " if (ul_flow[0] == dl_flow[2]) & (ul_flow[2] == dl_flow[0]) & (ul_flow[1] == dl_flow[3]) & (ul_flow[3] == dl_flow[1]) & (ul_flow[4] == dl_flow[4]):\n", 133 | " ul_flow_features = {'ul_'+feature_name:feature for feature_name,feature in ul_flow_features.items()}\n", 134 | " dl_flow_features = {'dl_'+feature_name:feature for feature_name,feature in dl_flow_features.items()}\n", 135 | " bi_flow_features = {**ul_flow_features,**dl_flow_features}\n", 136 | " bi_flow_features['ip_A'] = ul_flow[0]\n", 137 | " bi_flow_features['port_A'] = ul_flow[1]\n", 138 | " bi_flow_features['ip_B'] = ul_flow[2]\n", 139 | " bi_flow_features['port_B'] = ul_flow[3]\n", 140 | " bi_flow_features['protocal'] = ul_flow[4]\n", 141 | " df_flow = df_flow.append(bi_flow_features, ignore_index=True)\n", 142 | " return df_flow\n", 143 | "\n", 144 | "def clean_up_duplicate(row):\n", 145 | " if len(str(row['ip.hdr_len']).split(','))>1:\n", 146 | " row['ip.hdr_len'] = str(row['ip.hdr_len']).split(',')[1]\n", 147 | " if len(str(row['ip.len']).split(','))>1:\n", 148 | " row['ip.len'] = str(row['ip.len']).split(',')[1]\n", 149 | " else:\n", 150 | " row['ip.len'] = str(row['ip.len']).split(',')[0]\n", 151 | " if len(row['ip.src'].split(','))>1:\n", 152 | " row['ip.src'] = row['ip.src'].split(',')[1]\n", 153 | " if len(row['ip.dst'].split(','))>1:\n", 154 | " row['ip.dst'] = row['ip.dst'].split(',')[1]\n", 155 | " return row" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 3, 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "name": "stderr", 165 | "output_type": "stream", 166 | "text": [ 167 | "C:\\Users\\ethan\\Anaconda3\\envs\\p37\\lib\\site-packages\\dateutil\\parser\\_parser.py:1212: UnknownTimezoneWarning: tzname CDT identified but not understood. Pass `tzinfos` argument in order to correctly return a timezone-aware datetime. In a future version, this will raise an exception.\n", 168 | " category=UnknownTimezoneWarning)\n" 169 | ] 170 | }, 171 | { 172 | "name": "stdout", 173 | "output_type": "stream", 174 | "text": [ 175 | "Finished processing random scenario data.\n" 176 | ] 177 | } 178 | ], 179 | "source": [ 180 | "df_all = pd.DataFrame()\n", 181 | "for app in sel_apps:\n", 182 | " integrity = True\n", 183 | " df_app = pd.DataFrame()\n", 184 | " for fname in sel_app_files[app]:\n", 185 | " action = fname.split('_')[1]\n", 186 | " df = pd.read_csv(join(mypath,fname),usecols = columns,low_memory=False)\n", 187 | " df = df[df['ip.src'].notna()]\n", 188 | " \n", 189 | " df = df.apply(lambda row:clean_up_duplicate(row),axis=1)\n", 190 | " \n", 191 | " # Remove self loop pkts\n", 192 | " df = df[(df['ip.src']!='127.0.0.1') & (df['ip.dst']!='127.0.0.1')]\n", 193 | " try:\n", 194 | " df_flow = process_df_by_flow(df)\n", 195 | " df_flow['action'] = action\n", 196 | " df_app = df_app.append(df_flow)\n", 197 | " except:\n", 198 | " integrity = False\n", 199 | " print('\\n Error while processing {}. \\n'.format(fname))\n", 200 | "\n", 201 | " df_app['app'] = app\n", 202 | " \n", 203 | " if integrity:\n", 204 | " df_all = df_all.append(df_app)\n", 205 | " \n", 206 | "df_all.to_csv('./Processed Data/{}_scenario_bi_flow_features.csv'.format(scenario))\n", 207 | "print('Finished processing {} scenario data.'.format(scenario))" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": null, 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [] 216 | } 217 | ], 218 | "metadata": { 219 | "kernelspec": { 220 | "display_name": "Python 3 (ipykernel)", 221 | "language": "python", 222 | "name": "python3" 223 | }, 224 | "language_info": { 225 | "codemirror_mode": { 226 | "name": "ipython", 227 | "version": 3 228 | }, 229 | "file_extension": ".py", 230 | "mimetype": "text/x-python", 231 | "name": "python", 232 | "nbconvert_exporter": "python", 233 | "pygments_lexer": "ipython3", 234 | "version": "3.7.4" 235 | } 236 | }, 237 | "nbformat": 4, 238 | "nbformat_minor": 4 239 | } 240 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/Flow_Feature_Extraction-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "['dropbox' 'facebook' 'gmail' 'google-drive' 'hulu' 'instagram'\n", 13 | " 'messenger' 'netflix' 'pandora' 'pinterest' 'reddit' 'spotify' 'twitter'\n", 14 | " 'youtube'] 14 288\n", 15 | "['dropbox_download' 'dropbox_upload' 'facebook_scroll-newsfeed'\n", 16 | " 'facebook_search-page' 'gmail_open-email' 'gmail_send-email'\n", 17 | " 'google-drive_download' 'google-drive_upload' 'hulu_scroll-home'\n", 18 | " 'hulu_watch-video' 'instagram_IgSearchBrowse' 'instagram_send-message'\n", 19 | " 'messenger_send-message' 'netflix_browse-home' 'netflix_watch-video'\n", 20 | " 'pandora_play-music' 'pandora_search-music' 'pinterest_tap-board'\n", 21 | " 'reddit_browse' 'reddit_post' 'spotify_play-music' 'spotify_search-music'\n", 22 | " 'twitter_post-tweet' 'twitter_scroll-feed' 'twitter_send-message'\n", 23 | " 'youtube_play-video' 'youtube_search'] 27\n" 24 | ] 25 | } 26 | ], 27 | "source": [ 28 | "\"\"\"\n", 29 | "@author: Yuqiang (Ethan) Heng\n", 30 | "\"\"\"\n", 31 | "import numpy as np\n", 32 | "import pandas as pd\n", 33 | "from tqdm import tqdm\n", 34 | "from os import listdir\n", 35 | "from os.path import isfile, join\n", 36 | "\n", 37 | "scenario = 'random' #deterministic, random or wild\n", 38 | "if scenario == 'random':\n", 39 | " mypath = './Data/Randomized Automated Data'\n", 40 | "elif scenario == 'deterministic':\n", 41 | " mypath = './Data/Deterministic Automated Data'\n", 42 | "elif scenario == 'wild':\n", 43 | " mypath = './Data/Wild Test Data'\n", 44 | "else:\n", 45 | " raise NameError('Dataset Not Supported')\n", 46 | "\n", 47 | "onlyfiles = [f for f in listdir(mypath) if isfile(join(mypath, f))]\n", 48 | "apps = np.unique([f.split('_')[0] for f in onlyfiles])\n", 49 | "print(apps, len(apps), len(onlyfiles))\n", 50 | "app_actions = np.unique(['_'.join(f.split('_')[:2]) for f in onlyfiles])\n", 51 | "print(app_actions, len(app_actions))\n", 52 | "\n", 53 | "sel_apps = apps\n", 54 | "sel_app_files = {i:[] for i in sel_apps}\n", 55 | "\n", 56 | "for fname in onlyfiles:\n", 57 | " app_name = fname.split('_')[0]\n", 58 | " if app_name in sel_apps:\n", 59 | " sel_app_files[app_name].append(fname)" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 2, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "flow_columns = ['ip.src', 'srcport', 'ip.dst', 'dstport', 'protocal']\n", 69 | "\n", 70 | "def get_protocal(row):\n", 71 | " if not pd.isnull(row['tcp.len']):\n", 72 | " return 'TCP'\n", 73 | " elif not pd.isnull(row['udp.length']):\n", 74 | " return 'UDP'\n", 75 | " else:\n", 76 | " return 'Unknown'\n", 77 | " \n", 78 | "def get_srt_port(row):\n", 79 | " if not pd.isnull(row['tcp.len']):\n", 80 | " return row['tcp.srcport']\n", 81 | " elif not pd.isnull(row['udp.length']):\n", 82 | " return row['udp.srcport']\n", 83 | " else:\n", 84 | " return 'Unknown'\n", 85 | " \n", 86 | "def get_dst_port(row):\n", 87 | " if not pd.isnull(row['tcp.len']):\n", 88 | " return row['tcp.dstport']\n", 89 | " elif not pd.isnull(row['udp.length']):\n", 90 | " return row['udp.dstport']\n", 91 | " else:\n", 92 | " return 'Unknown'\n", 93 | " \n", 94 | "columns = ['frame.number','frame.time','frame.len','frame.cap_len','ip.hdr_len',\n", 95 | " 'ip.dsfield.ecn','ip.len','ip.frag_offset','ip.ttl','ip.proto','ip.src',\n", 96 | " 'ip.dst','tcp.hdr_len','tcp.len','tcp.srcport','tcp.dstport','tcp.flags.ns',\n", 97 | " 'tcp.flags.fin','tcp.window_size_value','tcp.urgent_pointer','tcp.option_kind',\n", 98 | " 'tcp.option_len','udp.srcport','udp.dstport','udp.length']\n", 99 | "\n", 100 | "def compute_flow_features(df):\n", 101 | " flow_features = {}\n", 102 | " flow_features['total_num_pkts'] = len(df)\n", 103 | " pkt_size = df['ip.len'].astype(float)\n", 104 | " flow_features['total_num_bytes'] = pkt_size.sum()\n", 105 | " flow_features['min_pkt_size'] = pkt_size.min()\n", 106 | " flow_features['max_pkt_size'] = pkt_size.max()\n", 107 | " flow_features['mean_pkt_size'] = pkt_size.mean()\n", 108 | " flow_features['std_pkt_size'] = pkt_size.std()\n", 109 | " iat = pd.to_datetime(df['frame.time']).diff(1).dt.total_seconds().iloc[1:]\n", 110 | " flow_features['min_iat'] = iat.min()\n", 111 | " flow_features['max_iat'] = iat.max()\n", 112 | " flow_features['mean_iat'] = iat.mean()\n", 113 | " flow_features['std_iat'] = iat.std()\n", 114 | " flow_features['dur'] = iat.sum()\n", 115 | " return flow_features\n", 116 | "\n", 117 | "def process_df_by_flow(df):\n", 118 | " df['protocal'] = df.apply(lambda row: get_protocal(row), axis=1)\n", 119 | " df['srcport'] = df.apply(lambda row: get_srt_port(row), axis=1)\n", 120 | " df['dstport'] = df.apply(lambda row: get_dst_port(row), axis=1) \n", 121 | " df_flow = pd.DataFrame()\n", 122 | " flow_columns = ['ip.src', 'srcport', 'ip.dst', 'dstport', 'protocal']\n", 123 | " ul_flows = {}\n", 124 | " dl_flows = {}\n", 125 | " for flow, flow_df in df.groupby(by=flow_columns):\n", 126 | " if flow[0].split('.')[0] == '10':\n", 127 | " ul_flows[flow] = compute_flow_features(flow_df)\n", 128 | " else:\n", 129 | " dl_flows[flow] = compute_flow_features(flow_df)\n", 130 | " for ul_flow, ul_flow_features in ul_flows.items():\n", 131 | " for dl_flow, dl_flow_features in dl_flows.items():\n", 132 | " if (ul_flow[0] == dl_flow[2]) & (ul_flow[2] == dl_flow[0]) & (ul_flow[1] == dl_flow[3]) & (ul_flow[3] == dl_flow[1]) & (ul_flow[4] == dl_flow[4]):\n", 133 | " ul_flow_features = {'ul_'+feature_name:feature for feature_name,feature in ul_flow_features.items()}\n", 134 | " dl_flow_features = {'dl_'+feature_name:feature for feature_name,feature in ul_flow_features.items()}\n", 135 | " bi_flow_features = {**ul_flow_features,**dl_flow_features}\n", 136 | " bi_flow_features['ip_A'] = ul_flow[0]\n", 137 | " bi_flow_features['port_A'] = ul_flow[1]\n", 138 | " bi_flow_features['ip_B'] = ul_flow[2]\n", 139 | " bi_flow_features['port_B'] = ul_flow[3]\n", 140 | " bi_flow_features['protocal'] = ul_flow[4]\n", 141 | " df_flow = df_flow.append(bi_flow_features, ignore_index=True)\n", 142 | " return df_flow\n", 143 | "\n", 144 | "def clean_up_duplicate(row):\n", 145 | " if len(str(row['ip.hdr_len']).split(','))>1:\n", 146 | " row['ip.hdr_len'] = str(row['ip.hdr_len']).split(',')[1]\n", 147 | " if len(str(row['ip.len']).split(','))>1:\n", 148 | " row['ip.len'] = str(row['ip.len']).split(',')[1]\n", 149 | " else:\n", 150 | " row['ip.len'] = str(row['ip.len']).split(',')[0]\n", 151 | " if len(row['ip.src'].split(','))>1:\n", 152 | " row['ip.src'] = row['ip.src'].split(',')[1]\n", 153 | " if len(row['ip.dst'].split(','))>1:\n", 154 | " row['ip.dst'] = row['ip.dst'].split(',')[1]\n", 155 | " return row" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 3, 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "name": "stderr", 165 | "output_type": "stream", 166 | "text": [ 167 | "C:\\Users\\ethan\\Anaconda3\\envs\\p37\\lib\\site-packages\\dateutil\\parser\\_parser.py:1218: UnknownTimezoneWarning: tzname CDT identified but not understood. Pass `tzinfos` argument in order to correctly return a timezone-aware datetime. In a future version, this will raise an exception.\n", 168 | " category=UnknownTimezoneWarning)\n" 169 | ] 170 | }, 171 | { 172 | "name": "stdout", 173 | "output_type": "stream", 174 | "text": [ 175 | "Finished processing random scenario data.\n" 176 | ] 177 | } 178 | ], 179 | "source": [ 180 | "df_all = pd.DataFrame()\n", 181 | "for app in sel_apps:\n", 182 | " integrity = True\n", 183 | " df_app = pd.DataFrame()\n", 184 | " for fname in sel_app_files[app]:\n", 185 | " action = fname.split('_')[1]\n", 186 | " df = pd.read_csv(join(mypath,fname),usecols = columns,low_memory=False)\n", 187 | " df = df[df['ip.src'].notna()]\n", 188 | " \n", 189 | " df = df.apply(lambda row:clean_up_duplicate(row),axis=1)\n", 190 | " \n", 191 | " # Remove self loop pkts\n", 192 | " df = df[(df['ip.src']!='127.0.0.1') & (df['ip.dst']!='127.0.0.1')]\n", 193 | " try:\n", 194 | " df_flow = process_df_by_flow(df)\n", 195 | " df_flow['action'] = action\n", 196 | " df_app = df_app.append(df_flow)\n", 197 | " except:\n", 198 | " integrity = False\n", 199 | " print('\\n Error while processing {}. \\n'.format(fname))\n", 200 | "\n", 201 | " df_app['app'] = app\n", 202 | " \n", 203 | " if integrity:\n", 204 | " df_all = df_all.append(df_app)\n", 205 | " \n", 206 | "df_all.to_csv('./Processed Data/{}_scenario_bi_flow_features.csv'.format(scenario))\n", 207 | "print('Finished processing {} scenario data.'.format(scenario))" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": null, 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [] 216 | } 217 | ], 218 | "metadata": { 219 | "kernelspec": { 220 | "display_name": "Python 3", 221 | "language": "python", 222 | "name": "python3" 223 | }, 224 | "language_info": { 225 | "codemirror_mode": { 226 | "name": "ipython", 227 | "version": 3 228 | }, 229 | "file_extension": ".py", 230 | "mimetype": "text/x-python", 231 | "name": "python", 232 | "nbconvert_exporter": "python", 233 | "pygments_lexer": "ipython3", 234 | "version": "3.7.4" 235 | } 236 | }, 237 | "nbformat": 4, 238 | "nbformat_minor": 4 239 | } 240 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/App_Classification-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | " Application RFC XGB KNN\n", 13 | "13 YouTube 0.896184 0.891841 0.832910\n", 14 | "0 Dropbox 0.849748 0.845574 0.737569\n", 15 | "8 Pandora 0.844988 0.850521 0.773454\n", 16 | "11 Spotify 0.840391 0.845886 0.749615\n", 17 | "4 Hulu 0.837389 0.830147 0.774495\n", 18 | "12 Twitter 0.782884 0.777376 0.732368\n", 19 | "10 Reddit 0.769563 0.785333 0.637437\n", 20 | "7 Netflix 0.724149 0.722462 0.622800\n", 21 | "9 Pinterest 0.711339 0.734378 0.638909\n", 22 | "3 Google Drive 0.687112 0.647523 0.508215\n", 23 | "5 Instagram 0.653390 0.677274 0.456870\n", 24 | "1 Facebook 0.614627 0.587601 0.480290\n", 25 | "2 Gmail 0.550867 0.560655 0.466720\n", 26 | "6 Messenger 0.482096 0.442239 0.453955\n" 27 | ] 28 | }, 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "" 33 | ] 34 | }, 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | }, 39 | { 40 | "data": { 41 | "image/png": "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\n", 42 | "text/plain": [ 43 | "
" 44 | ] 45 | }, 46 | "metadata": { 47 | "needs_background": "light" 48 | }, 49 | "output_type": "display_data" 50 | } 51 | ], 52 | "source": [ 53 | "\"\"\"\n", 54 | "@author: Yuqiang (Ethan) Heng\n", 55 | "\"\"\"\n", 56 | "import numpy as np\n", 57 | "import pandas as pd\n", 58 | "import matplotlib.pyplot as plt\n", 59 | "from sklearn.utils import resample\n", 60 | "\n", 61 | "from sklearn.model_selection import cross_val_score, KFold, train_test_split, StratifiedKFold, GridSearchCV\n", 62 | "from sklearn.preprocessing import LabelEncoder, StandardScaler\n", 63 | "from sklearn.metrics import accuracy_score,classification_report\n", 64 | "from sklearn.ensemble import RandomForestClassifier\n", 65 | "from sklearn.metrics import plot_confusion_matrix\n", 66 | "\n", 67 | "from sklearn.neighbors import KNeighborsClassifier\n", 68 | "from xgboost import XGBClassifier\n", 69 | "\n", 70 | "df = pd.read_csv('./Processed Data/random_scenario_bi_flow_features.csv',low_memory=False).dropna().drop('Unnamed: 0',axis=1)\n", 71 | "dl_features = [f for f in df.columns.values if f.split('_')[0] == 'dl']\n", 72 | "ul_features = [f for f in df.columns.values if f.split('_')[0] == 'ul']\n", 73 | "features = np.concatenate((dl_features,ul_features))\n", 74 | "\n", 75 | "rfc_f1_per_app = {app:[] for app in df['app'].unique()}\n", 76 | "knn_f1_per_app = {app:[] for app in df['app'].unique()}\n", 77 | "xgb_f1_per_app = {app:[] for app in df['app'].unique()}\n", 78 | "\n", 79 | "num_apps = len(df['app'].unique())\n", 80 | "X = df[dl_features]\n", 81 | "scaler = StandardScaler()\n", 82 | "X = scaler.fit_transform(X)\n", 83 | "le = LabelEncoder()\n", 84 | "Y = le.fit_transform(df['app'])\n", 85 | "kfold = KFold(n_splits=10, shuffle = True, random_state=7)\n", 86 | "for k, (train, test) in enumerate(kfold.split(X, Y)):\n", 87 | " rfc = RandomForestClassifier()\n", 88 | " rfc.fit(X[train], Y[train])\n", 89 | " rfc_predit = rfc.predict(X[test])\n", 90 | " rfc_report = classification_report(Y[test],rfc_predit,target_names=le.inverse_transform(np.arange(num_apps)),digits=3,output_dict=True)\n", 91 | "\n", 92 | " knn = KNeighborsClassifier()\n", 93 | " knn.fit(X[train], Y[train])\n", 94 | " knn_predit = knn.predict(X[test])\n", 95 | " knn_report = classification_report(Y[test],knn_predit,target_names=le.inverse_transform(np.arange(num_apps)),digits=3,output_dict=True)\n", 96 | " \n", 97 | " xgb = XGBClassifier()\n", 98 | " xgb.fit(X[train], Y[train])\n", 99 | " xgb_predit = xgb.predict(X[test])\n", 100 | " xgb_report = classification_report(Y[test],xgb_predit,target_names=le.inverse_transform(np.arange(num_apps)),digits=3,output_dict=True)\n", 101 | " \n", 102 | " for app_name in df['app'].unique():\n", 103 | " rfc_f1_per_app[app_name].append(rfc_report[app_name]['f1-score'])\n", 104 | " knn_f1_per_app[app_name].append(knn_report[app_name]['f1-score'])\n", 105 | " xgb_f1_per_app[app_name].append(xgb_report[app_name]['f1-score'])\n", 106 | " \n", 107 | "app_names = {'youtube':'YouTube','spotify':'Spotify','dropbox':'Dropbox','pandora':'Pandora','hulu':'Hulu',\n", 108 | " 'twitter':'Twitter','reddit':'Reddit','pinterest':'Pinterest','netflix':'Netflix','google-drive':'Google Drive',\n", 109 | " 'instagram':'Instagram','facebook':'Facebook','gmail':'Gmail','messenger':'Messenger','hangout':'Hangout','google-maps':'Google Maps'}\n", 110 | "\n", 111 | "plot_df = pd.DataFrame({'Application':[app_names[app_name] for app_name in df['app'].unique()],\n", 112 | " 'RFC':[np.array(rfc_f1_per_app[app_name]).mean() for app_name in df['app'].unique()],\n", 113 | " 'XGB':[np.array(xgb_f1_per_app[app_name]).mean() for app_name in df['app'].unique()], \n", 114 | " 'KNN':[np.array(knn_f1_per_app[app_name]).mean() for app_name in df['app'].unique()]})\n", 115 | "plot_df = plot_df.sort_values(by='RFC',ascending=False)\n", 116 | "print(plot_df)\n", 117 | "plot_df.plot(x='Application',kind='bar',ylabel='F1 score',stacked=False)" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [] 126 | } 127 | ], 128 | "metadata": { 129 | "kernelspec": { 130 | "display_name": "Python 3", 131 | "language": "python", 132 | "name": "python3" 133 | }, 134 | "language_info": { 135 | "codemirror_mode": { 136 | "name": "ipython", 137 | "version": 3 138 | }, 139 | "file_extension": ".py", 140 | "mimetype": "text/x-python", 141 | "name": "python", 142 | "nbconvert_exporter": "python", 143 | "pygments_lexer": "ipython3", 144 | "version": "3.7.4" 145 | } 146 | }, 147 | "nbformat": 4, 148 | "nbformat_minor": 4 149 | } 150 | -------------------------------------------------------------------------------- /App_Classification.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | " Application RFC XGB KNN\n", 13 | "13 YouTube 0.847235 0.826373 0.741991\n", 14 | "4 Hulu 0.837932 0.837674 0.721624\n", 15 | "8 Pandora 0.829433 0.806141 0.739040\n", 16 | "0 Dropbox 0.826243 0.844555 0.716427\n", 17 | "12 Twitter 0.774605 0.754877 0.696406\n", 18 | "11 Spotify 0.768241 0.780756 0.659212\n", 19 | "10 Reddit 0.720930 0.706750 0.621206\n", 20 | "7 Netflix 0.698558 0.699436 0.564100\n", 21 | "3 Google Drive 0.667352 0.620614 0.458475\n", 22 | "9 Pinterest 0.656701 0.654207 0.568252\n", 23 | "5 Instagram 0.607730 0.607450 0.424835\n", 24 | "2 Gmail 0.545453 0.534553 0.418209\n", 25 | "1 Facebook 0.530474 0.484483 0.425020\n", 26 | "6 Messenger 0.462191 0.458498 0.422588\n" 27 | ] 28 | }, 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "" 33 | ] 34 | }, 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | }, 39 | { 40 | "data": { 41 | "image/png": "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ePFihgwZwoQJE7LUyYHAyuFZPWZ1jR07lrFjxy457tevX5vdxyZPnlzvbRxyyCEccsghpdTJXUNmZi3OgcDMrMW1TNfQFldv0eb48YMf76WamJktX1omEPR1DmRmVhZ3DZmZtTgHAjOzFueuITNrabXdro3qSrftqquuyhtvvAGk6aLHHXccU6ZM4corr+Scc87h2Wef5YMf/GC7ayVx4okn8qMf/QiA8847jzfeeIPTTjutoTq7RZDJFldv0ebLzOz9TJkyhW9961vcfvvtDBs2DIB11llnyQd9rZVWWokbb7yRl17Km63fgcAM5zKynnfffffxjW98g1tvvbVNqojDDjuM66+/vm6OoRVXXJGxY8dywQUXZK2LA4EBbtGY9aR33nmHMWPGcPPNNzNiRNvdeVdddVUOO+wwfvzjH9d979FHH821117La6/lW61faiCQNFrSU5LmSBpX5/VTJM0svmZJek/SWmXWycyst/Xv358dd9yRK664ou7rxx57LFdffTWvv/56u9cGDRrEQQcdxEUXXZStPqUFAkn9gPHAbsCmwAGSNq2+JiLOjYiRETES+Dfgnoho3x4y62mnrd72yyyjFVZYgV/96ldMmzaNs846q93ra6yxBgceeCCXXnpp3fcff/zxXHHFFSxYsCBPfbKUUt/2wJyImBsRC4GJwJhOrj8AuK7E+piZLTdWWWUVbrnlFq699tq6LYMTTzyRn/70pyxatKjda2uttRZf/vKXO2xRLKsyp4+uBzxXdTwP+Hi9CyWtAowmbXZf7/WxwFhgyci6WV/SbivPgQe2vcDZWXtNb67SX2uttbj99tvZeeedWWedddq8ts4667D33nt3ODB80kkncckll2SpR5mBQHXORQfX7gXc31G3UERcDlwOMGrUqI7KsOWYU2SYLVVZFwAwdOhQnnnmGQDGjGnbaXL++edz/vnn133fuuuu2+nGNcuizK6hecDQquMhwAsdXLs/7hYyM+sVZQaCacCGkoZLGkD6sJ9Ue5Gk1YFPA78psS5mZtaB0rqGImKRpGOAO4B+wJURMVvSEcXrlxWX7g3cGRF5hr/NzN5HRCDV673u+yKWvfe81FxDETEZmFxz7rKa4wnAhDLrYWZWMXDgQF5++WXWXnvtpgsGEcHLL7/MwIEDl+l9TjpnZi1lyJAhzJs3j/nz5/d2VUoxcOBA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\n", 42 | "text/plain": [ 43 | "
" 44 | ] 45 | }, 46 | "metadata": { 47 | "needs_background": "light" 48 | }, 49 | "output_type": "display_data" 50 | } 51 | ], 52 | "source": [ 53 | "\"\"\"\n", 54 | "@author: Yuqiang (Ethan) Heng\n", 55 | "\"\"\"\n", 56 | "import numpy as np\n", 57 | "import pandas as pd\n", 58 | "import matplotlib.pyplot as plt\n", 59 | "from sklearn.utils import resample\n", 60 | "\n", 61 | "from sklearn.model_selection import cross_val_score, KFold, train_test_split, StratifiedKFold, GridSearchCV\n", 62 | "from sklearn.preprocessing import LabelEncoder, StandardScaler\n", 63 | "from sklearn.metrics import accuracy_score,classification_report\n", 64 | "from sklearn.ensemble import RandomForestClassifier\n", 65 | "from sklearn.metrics import plot_confusion_matrix\n", 66 | "\n", 67 | "from sklearn.neighbors import KNeighborsClassifier\n", 68 | "from xgboost import XGBClassifier\n", 69 | "\n", 70 | "df = pd.read_csv('./Processed Data/random_scenario_bi_flow_features.csv',low_memory=False).dropna().drop('Unnamed: 0',axis=1)\n", 71 | "dl_features = [f for f in df.columns.values if f.split('_')[0] == 'dl']\n", 72 | "ul_features = [f for f in df.columns.values if f.split('_')[0] == 'ul']\n", 73 | "features = np.concatenate((dl_features,ul_features))\n", 74 | "\n", 75 | "rfc_f1_per_app = {app:[] for app in df['app'].unique()}\n", 76 | "knn_f1_per_app = {app:[] for app in df['app'].unique()}\n", 77 | "xgb_f1_per_app = {app:[] for app in df['app'].unique()}\n", 78 | "\n", 79 | "num_apps = len(df['app'].unique())\n", 80 | "X = df[dl_features]\n", 81 | "scaler = StandardScaler()\n", 82 | "X = scaler.fit_transform(X)\n", 83 | "le = LabelEncoder()\n", 84 | "Y = le.fit_transform(df['app'])\n", 85 | "kfold = KFold(n_splits=10, shuffle = True, random_state=7)\n", 86 | "for k, (train, test) in enumerate(kfold.split(X, Y)):\n", 87 | " rfc = RandomForestClassifier()\n", 88 | " rfc.fit(X[train], Y[train])\n", 89 | " rfc_predit = rfc.predict(X[test])\n", 90 | " rfc_report = classification_report(Y[test],rfc_predit,target_names=le.inverse_transform(np.arange(num_apps)),digits=3,output_dict=True)\n", 91 | "\n", 92 | " knn = KNeighborsClassifier()\n", 93 | " knn.fit(X[train], Y[train])\n", 94 | " knn_predit = knn.predict(X[test])\n", 95 | " knn_report = classification_report(Y[test],knn_predit,target_names=le.inverse_transform(np.arange(num_apps)),digits=3,output_dict=True)\n", 96 | " \n", 97 | " xgb = XGBClassifier()\n", 98 | " xgb.fit(X[train], Y[train])\n", 99 | " xgb_predit = xgb.predict(X[test])\n", 100 | " xgb_report = classification_report(Y[test],xgb_predit,target_names=le.inverse_transform(np.arange(num_apps)),digits=3,output_dict=True)\n", 101 | " \n", 102 | " for app_name in df['app'].unique():\n", 103 | " rfc_f1_per_app[app_name].append(rfc_report[app_name]['f1-score'])\n", 104 | " knn_f1_per_app[app_name].append(knn_report[app_name]['f1-score'])\n", 105 | " xgb_f1_per_app[app_name].append(xgb_report[app_name]['f1-score'])\n", 106 | " \n", 107 | "app_names = {'youtube':'YouTube','spotify':'Spotify','dropbox':'Dropbox','pandora':'Pandora','hulu':'Hulu',\n", 108 | " 'twitter':'Twitter','reddit':'Reddit','pinterest':'Pinterest','netflix':'Netflix','google-drive':'Google Drive',\n", 109 | " 'instagram':'Instagram','facebook':'Facebook','gmail':'Gmail','messenger':'Messenger','hangout':'Hangout','google-maps':'Google Maps'}\n", 110 | "\n", 111 | "plot_df = pd.DataFrame({'Application':[app_names[app_name] for app_name in df['app'].unique()],\n", 112 | " 'RFC':[np.array(rfc_f1_per_app[app_name]).mean() for app_name in df['app'].unique()],\n", 113 | " 'XGB':[np.array(xgb_f1_per_app[app_name]).mean() for app_name in df['app'].unique()], \n", 114 | " 'KNN':[np.array(knn_f1_per_app[app_name]).mean() for app_name in df['app'].unique()]})\n", 115 | "plot_df = plot_df.sort_values(by='RFC',ascending=False)\n", 116 | "print(plot_df)\n", 117 | "plot_df.plot(x='Application',kind='bar',ylabel='F1 score',stacked=False)" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [] 126 | } 127 | ], 128 | "metadata": { 129 | "kernelspec": { 130 | "display_name": "Python 3 (ipykernel)", 131 | "language": "python", 132 | "name": "python3" 133 | }, 134 | "language_info": { 135 | "codemirror_mode": { 136 | "name": "ipython", 137 | "version": 3 138 | }, 139 | "file_extension": ".py", 140 | "mimetype": "text/x-python", 141 | "name": "python", 142 | "nbconvert_exporter": "python", 143 | "pygments_lexer": "ipython3", 144 | "version": "3.7.4" 145 | } 146 | }, 147 | "nbformat": 4, 148 | "nbformat_minor": 4 149 | } 150 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------