{"cells":[{"cell_type":"markdown","source":["## SQL at Scale with Spark SQL and DataFrames\n\nSpark SQL brings native support for SQL to Spark and streamlines the process of querying data stored both in RDDs (Spark’s distributed datasets) and in external sources. Spark SQL conveniently blurs the lines between RDDs and relational tables. Unifying these powerful abstractions makes it easy for developers to intermix SQL commands querying external data with complex analytics, all within in a single application. Concretely, Spark SQL will allow developers to:\n\n- Import relational data from Parquet files and Hive tables\n- Run SQL queries over imported data and existing RDDs\n- Easily write RDDs out to Hive tables or Parquet files\n\nSpark SQL also includes a cost-based optimizer, columnar storage, and code generation to make queries fast. At the same time, it scales to thousands of nodes and multi-hour queries using the Spark engine, which provides full mid-query fault tolerance, without having to worry about using a different engine for historical data.\n\n_For getting a deeper perspective into the background, concepts, architecture of Spark SQL and DataFrames you can check out the original article, __['SQL at Scale with Apache Spark SQL and DataFrames - Concepts, Architecture and Examples'](https://medium.com/p/c567853a702f)___\n\nThis tutorial will familiarize you with essential Spark capabilities to deal with structured data typically often obtained from databases or flat files. We will explore typical ways of querying and aggregating relational data by leveraging concepts of DataFrames and SQL using Spark. We will work on an interesting dataset from the [KDD Cup 1999](http://kdd.ics.uci.edu/databases/kddcup99/kddcup99.html) and try to query the data using high level abstrations like the dataframe which has already been a hit in popular data analysis tools like R and Python. We will also look at how easy it is to build data queries using the SQL language which you have learnt and retrieve insightful information from our data. This also happens at scale without us having to do a lot more since Spark distributes these data structures efficiently in the backend which makes our queries scalable and as efficient as possible."],"metadata":{}},{"cell_type":"code","source":["import pandas as pd\nimport matplotlib.pyplot as plt\nplt.style.use('fivethirtyeight')"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
"]}}],"execution_count":2},{"cell_type":"markdown","source":["## Data Retrieval\n\nWe will use data from the [KDD Cup 1999](http://kdd.ics.uci.edu/databases/kddcup99/kddcup99.html), which is the data set used for The Third International Knowledge Discovery and Data Mining Tools Competition, which was held in conjunction with KDD-99 The Fifth International Conference on Knowledge Discovery and Data Mining. The competition task was to build a network intrusion detector, a predictive model capable of distinguishing between \"bad\" connections, called intrusions or attacks, and \"good\" normal connections. This database contains a standard set of data to be audited, which includes a wide variety of intrusions simulated in a military network environment. \n\nWe will be using the reduced dataset `kddcup.data_10_percent.gz` containing nearly half million nework interactions since we would be downloading this Gzip file from the web locally and then work on the same. If you have a good, stable internet connection, feel free to download and work with the full dataset available as `kddcup.data.gz`"],"metadata":{}},{"cell_type":"markdown","source":["#### Working with data from the web\n\nDealing with datasets retrieved from the web can be a bit tricky in Databricks. Fortunately, we have some excellent utility packages like `dbutils` which help in making our job easier. Let's take a quick look at some essential functions for this module."],"metadata":{}},{"cell_type":"code","source":["dbutils.help()"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
\nThis module provides various utilities for users to interact with the rest of Databricks.\n

fs: DbfsUtils -> Manipulates the Databricks filesystem (DBFS) from the console
meta: MetaUtils -> Methods to hook into the compiler (EXPERIMENTAL)
notebook: NotebookUtils -> Utilities for the control flow of a notebook (EXPERIMENTAL)
preview: Preview -> Utilities under preview category
secrets: SecretUtils -> Provides utilities for leveraging secrets within notebooks
widgets: WidgetsUtils -> Methods to create and get bound value of input widgets inside notebooks

"]}}],"execution_count":5},{"cell_type":"markdown","source":["#### Retrieve and store data in Databricks\n\nWe will now leverage the python `urllib` library to extract the KDD Cup 99 data from their web repository, store it in a temporary location and then move it to the Databricks filesystem which can enable easy access to this data for analysis\n\n__Note:__ If you skip this step and download the data directly, you may end up getting a `InvalidInputException: Input path does not exist` error"],"metadata":{}},{"cell_type":"code","source":["import urllib\nurllib.urlretrieve(\"http://kdd.ics.uci.edu/databases/kddcup99/kddcup.data_10_percent.gz\", \"/tmp/kddcup_data.gz\")\ndbutils.fs.mv(\"file:/tmp/kddcup_data.gz\", \"dbfs:/kdd/kddcup_data.gz\")\ndisplay(dbutils.fs.ls(\"dbfs:/kdd\"))"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
pathnamesize
dbfs:/kdd/kddcup_data.gzkddcup_data.gz2144903
"]}}],"execution_count":7},{"cell_type":"markdown","source":["## Building the KDD Dataset\n\nNow that we have our data stored in the Databricks filesystem. Let's load up our data from the disk into Spark's traditional abstracted data structure, the [Resilient Distributed Dataset (RDD)](https://spark.apache.org/docs/latest/rdd-programming-guide.html#resilient-distributed-datasets-rdds)"],"metadata":{}},{"cell_type":"code","source":["data_file = \"dbfs:/kdd/kddcup_data.gz\"\nraw_rdd = sc.textFile(data_file).cache()\nraw_rdd.take(5)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
Out[2]: \n[u'0,tcp,http,SF,181,5450,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,8,8,0.00,0.00,0.00,0.00,1.00,0.00,0.00,9,9,1.00,0.00,0.11,0.00,0.00,0.00,0.00,0.00,normal.',\n u'0,tcp,http,SF,239,486,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,8,8,0.00,0.00,0.00,0.00,1.00,0.00,0.00,19,19,1.00,0.00,0.05,0.00,0.00,0.00,0.00,0.00,normal.',\n u'0,tcp,http,SF,235,1337,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,8,8,0.00,0.00,0.00,0.00,1.00,0.00,0.00,29,29,1.00,0.00,0.03,0.00,0.00,0.00,0.00,0.00,normal.',\n u'0,tcp,http,SF,219,1337,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,6,6,0.00,0.00,0.00,0.00,1.00,0.00,0.00,39,39,1.00,0.00,0.03,0.00,0.00,0.00,0.00,0.00,normal.',\n u'0,tcp,http,SF,217,2032,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,6,6,0.00,0.00,0.00,0.00,1.00,0.00,0.00,49,49,1.00,0.00,0.02,0.00,0.00,0.00,0.00,0.00,normal.']\n
"]}}],"execution_count":9},{"cell_type":"code","source":["type(raw_rdd)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
Out[3]: pyspark.rdd.RDD\n
"]}}],"execution_count":10},{"cell_type":"markdown","source":["## Building a Spark DataFrame on our Data\n\nA Spark DataFrame is an interesting data structure representing a distributed collecion of data. A DataFrame is a Dataset organized into named columns. It is conceptually equivalent to a table in a relational database or a dataframe in R/Python, but with richer optimizations under the hood. DataFrames can be constructed from a wide array of sources such as: structured data files, tables in Hive, external databases, or existing RDDs in our case.\n\nTypically the entry point into all SQL functionality in Spark is the `SQLContext` class. To create a basic instance of this call, all we need is a `SparkContext` reference. In Databricks this global context object is available as `sc` for this purpose."],"metadata":{}},{"cell_type":"code","source":["from pyspark.sql import SQLContext\nsqlContext = SQLContext(sc)\nsqlContext "],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
Out[4]: <pyspark.sql.context.SQLContext at 0x7f470b54d490>\n
"]}}],"execution_count":12},{"cell_type":"markdown","source":["#### Splitting the CSV data\nEach entry in our RDD is a comma-separated line of data which we first need to split before we can parse and build our dataframe"],"metadata":{}},{"cell_type":"code","source":["csv_rdd = raw_rdd.map(lambda row: row.split(\",\"))\nprint(csv_rdd.take(2))\nprint(type(csv_rdd))"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
[[u'0', u'tcp', u'http', u'SF', u'181', u'5450', u'0', u'0', u'0', u'0', u'0', u'1', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'8', u'8', u'0.00', u'0.00', u'0.00', u'0.00', u'1.00', u'0.00', u'0.00', u'9', u'9', u'1.00', u'0.00', u'0.11', u'0.00', u'0.00', u'0.00', u'0.00', u'0.00', u'normal.'], [u'0', u'tcp', u'http', u'SF', u'239', u'486', u'0', u'0', u'0', u'0', u'0', u'1', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'8', u'8', u'0.00', u'0.00', u'0.00', u'0.00', u'1.00', u'0.00', u'0.00', u'19', u'19', u'1.00', u'0.00', u'0.05', u'0.00', u'0.00', u'0.00', u'0.00', u'0.00', u'normal.']]\n<class 'pyspark.rdd.PipelinedRDD'>\n
"]}}],"execution_count":14},{"cell_type":"markdown","source":["#### Check the total number of features (columns)\nWe can use the following code to check the total number of potential columns in our dataset"],"metadata":{}},{"cell_type":"code","source":["len(csv_rdd.take(1)[0])"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
Out[6]: 42\n
"]}}],"execution_count":16},{"cell_type":"markdown","source":["#### Data Understanding and Parsing\n\nThe KDD 99 Cup data consists of different attributes captured from connection data. The full list of attributes in the data can be obtained [__here__](http://kdd.ics.uci.edu/databases/kddcup99/kddcup.names) and further details pertaining to the description for each attribute\\column can be found [__here__](http://kdd.ics.uci.edu/databases/kddcup99/task.html). We will just be using some specific columns from the dataset, the details of which are specified below.\n\n\n| feature num | feature name | description | type |\n|-------------|--------------------|--------------------------------------------------------------|------------|\n| 1 | duration | length (number of seconds) of the connection | continuous |\n| 2 | protocol_type | type of the protocol, e.g. tcp, udp, etc. | discrete |\n| 3 | service | network service on the destination, e.g., http, telnet, etc. | discrete |\n| 4 | src_bytes | number of data bytes from source to destination | continuous |\n| 5 | dst_bytes | number of data bytes from destination to source | continuous |\n| 6 | flag | normal or error status of the connection | discrete |\n| 7 | wrong_fragment | number of ``wrong'' fragments | continuous |\n| 8 | urgent | number of urgent packets | continuous |\n| 9 | hot | number of ``hot'' indicators | continuous |\n| 10 | num_failed_logins | number of failed login attempts | continuous |\n| 11 | num_compromised | number of ``compromised'' conditions | continuous |\n| 12 | su_attempted | 1 if ``su root'' command attempted; 0 otherwise | discrete |\n| 13 | num_root | number of ``root'' accesses | continuous |\n| 14 | num_file_creations | number of file creation operations | continuous |\n\nWe will be extracting the following columns based on their positions in each datapoint (row) and build a new RDD as follows"],"metadata":{}},{"cell_type":"code","source":["from pyspark.sql import Row\n\nparsed_rdd = csv_rdd.map(lambda r: Row(\n duration=int(r[0]), \n protocol_type=r[1],\n service=r[2],\n flag=r[3],\n src_bytes=int(r[4]),\n dst_bytes=int(r[5]),\n wrong_fragment=int(r[7]),\n urgent=int(r[8]),\n hot=int(r[9]),\n num_failed_logins=int(r[10]),\n num_compromised=int(r[12]),\n su_attempted=r[14],\n num_root=int(r[15]),\n num_file_creations=int(r[16]),\n label=r[-1]\n )\n)\nparsed_rdd.take(5)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
Out[7]: \n[Row(dst_bytes=5450, duration=0, flag=u'SF', hot=0, label=u'normal.', num_compromised=0, num_failed_logins=0, num_file_creations=0, num_root=0, protocol_type=u'tcp', service=u'http', src_bytes=181, su_attempted=u'0', urgent=0, wrong_fragment=0),\n Row(dst_bytes=486, duration=0, flag=u'SF', hot=0, label=u'normal.', num_compromised=0, num_failed_logins=0, num_file_creations=0, num_root=0, protocol_type=u'tcp', service=u'http', src_bytes=239, su_attempted=u'0', urgent=0, wrong_fragment=0),\n Row(dst_bytes=1337, duration=0, flag=u'SF', hot=0, label=u'normal.', num_compromised=0, num_failed_logins=0, num_file_creations=0, num_root=0, protocol_type=u'tcp', service=u'http', src_bytes=235, su_attempted=u'0', urgent=0, wrong_fragment=0),\n Row(dst_bytes=1337, duration=0, flag=u'SF', hot=0, label=u'normal.', num_compromised=0, num_failed_logins=0, num_file_creations=0, num_root=0, protocol_type=u'tcp', service=u'http', src_bytes=219, su_attempted=u'0', urgent=0, wrong_fragment=0),\n Row(dst_bytes=2032, duration=0, flag=u'SF', hot=0, label=u'normal.', num_compromised=0, num_failed_logins=0, num_file_creations=0, num_root=0, protocol_type=u'tcp', service=u'http', src_bytes=217, su_attempted=u'0', urgent=0, wrong_fragment=0)]\n
"]}}],"execution_count":18},{"cell_type":"markdown","source":["#### Constructing the DataFrame\nNow that our data is neatly parsed and formatted, let's build our DataFrame!"],"metadata":{}},{"cell_type":"code","source":["df = sqlContext.createDataFrame(parsed_rdd)\ndisplay(df.head(10))"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
dst_bytesdurationflaghotlabelnum_compromisednum_failed_loginsnum_file_creationsnum_rootprotocol_typeservicesrc_bytessu_attemptedurgentwrong_fragment
54500SF0normal.0000tcphttp181000
4860SF0normal.0000tcphttp239000
13370SF0normal.0000tcphttp235000
13370SF0normal.0000tcphttp219000
20320SF0normal.0000tcphttp217000
20320SF0normal.0000tcphttp217000
19400SF0normal.0000tcphttp212000
40870SF0normal.0000tcphttp159000
1510SF0normal.0000tcphttp210000
7860SF1normal.0000tcphttp212000
"]}}],"execution_count":20},{"cell_type":"markdown","source":["Now, we can easily have a look at our dataframe's schema using tne `printSchema(...)` function."],"metadata":{}},{"cell_type":"code","source":["df.printSchema()"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
root\n-- dst_bytes: long (nullable = true)\n-- duration: long (nullable = true)\n-- flag: string (nullable = true)\n-- hot: long (nullable = true)\n-- label: string (nullable = true)\n-- num_compromised: long (nullable = true)\n-- num_failed_logins: long (nullable = true)\n-- num_file_creations: long (nullable = true)\n-- num_root: long (nullable = true)\n-- protocol_type: string (nullable = true)\n-- service: string (nullable = true)\n-- src_bytes: long (nullable = true)\n-- su_attempted: string (nullable = true)\n-- urgent: long (nullable = true)\n-- wrong_fragment: long (nullable = true)\n\n
"]}}],"execution_count":22},{"cell_type":"markdown","source":["## Building a temporary table \n\nWe can leverage the `registerTempTable()` function to build a temporaty table to run SQL commands on our DataFrame at scale! A point to remember is that the lifetime of this temp table is tied to the session. It creates an in-memory table that is scoped to the cluster in which it was created. The data is stored using Hive's highly-optimized, in-memory columnar format. \n\nYou can also check out `saveAsTable()` which creates a permanent, physical table stored in S3 using the Parquet format. This table is accessible to all clusters. The table metadata including the location of the file(s) is stored within the Hive metastore.`"],"metadata":{}},{"cell_type":"code","source":["help(df.registerTempTable)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
Help on method registerTempTable in module pyspark.sql.dataframe:\n\nregisterTempTable(self, name) method of pyspark.sql.dataframe.DataFrame instance\n Registers this DataFrame as a temporary table using the given name.\n \n The lifetime of this temporary table is tied to the :class:`SparkSession`\n that was used to create this :class:`DataFrame`.\n \n >>> df.registerTempTable("people")\n >>> df2 = spark.sql("select * from people")\n >>> sorted(df.collect()) == sorted(df2.collect())\n True\n >>> spark.catalog.dropTempView("people")\n \n .. note:: Deprecated in 2.0, use createOrReplaceTempView instead.\n \n .. versionadded:: 1.3\n\n
"]}}],"execution_count":24},{"cell_type":"code","source":["df.registerTempTable(\"connections\")"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["\n
"]}}],"execution_count":25},{"cell_type":"markdown","source":["## Executing SQL at Scale\nLet's look at a few examples of how we can run SQL queries on our table based off our dataframe. We will start with some simple queries and then look at aggregations, filters, sorting, subqueries and pivots"],"metadata":{}},{"cell_type":"markdown","source":["### Connections based on the protocol type\n\nLet's look at how we can get the total number of connections based on the type of connectivity protocol. First we will get this information using normal DataFrame DSL syntax to perform aggregations."],"metadata":{}},{"cell_type":"code","source":["display(df.groupBy('protocol_type')\n .count()\n .orderBy('count', ascending=False))"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
protocol_typecount
icmp283602
tcp190065
udp20354
"]}}],"execution_count":28},{"cell_type":"markdown","source":["Can we also use SQL to perform the same aggregation? Yes we can leverage the table we built earlier for this!"],"metadata":{}},{"cell_type":"code","source":["protocols = sqlContext.sql(\"\"\"\n SELECT protocol_type, count(*) as freq\n FROM connections\n GROUP BY protocol_type\n ORDER BY 2 DESC\n \"\"\")\ndisplay(protocols)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
protocol_typefreq
icmp283602
tcp190065
udp20354
"]}}],"execution_count":30},{"cell_type":"markdown","source":["You can clearly see, that you get the same results and you do not need to worry about your background infrastructure or how the code is executed. Just write simple SQL!"],"metadata":{}},{"cell_type":"markdown","source":["### Connections based on good or bad (attack types) signatures\n\nWe will now run a simple aggregation to check the total number of connections based on good (normal) or bad (intrusion attacks) types."],"metadata":{}},{"cell_type":"code","source":["labels = sqlContext.sql(\"\"\"\n SELECT label, count(*) as freq\n FROM connections\n GROUP BY label\n ORDER BY 2 DESC\n \"\"\")\ndisplay(labels)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
labelfreq
smurf.280790
neptune.107201
normal.97278
back.2203
satan.1589
ipsweep.1247
portsweep.1040
warezclient.1020
teardrop.979
pod.264
nmap.231
guess_passwd.53
buffer_overflow.30
land.21
warezmaster.20
imap.12
rootkit.10
loadmodule.9
ftp_write.8
multihop.7
phf.4
perl.3
spy.2
"]}}],"execution_count":33},{"cell_type":"markdown","source":["We have a lot of different attack types. We can visualize this in the form of a bar chart. The simplest way is to use the excellent interface options in the Databricks notebook itself as depicted in the following figure!\n\n![](https://cdn-images-1.medium.com/max/800/1*MWtgLR6H4siUB1Ugc8sqog.png)\n\nThis gives us the following nice looking bar chart! Which you can customize further by clicking on __`Plot Options`__ as needed."],"metadata":{}},{"cell_type":"code","source":["labels = sqlContext.sql(\"\"\"\n SELECT label, count(*) as freq\n FROM connections\n GROUP BY label\n ORDER BY 2 DESC\n \"\"\")\ndisplay(labels)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
labelfreq
smurf.280790
neptune.107201
normal.97278
back.2203
satan.1589
ipsweep.1247
portsweep.1040
warezclient.1020
teardrop.979
pod.264
nmap.231
guess_passwd.53
buffer_overflow.30
land.21
warezmaster.20
imap.12
rootkit.10
loadmodule.9
ftp_write.8
multihop.7
phf.4
perl.3
spy.2
"]}}],"execution_count":35},{"cell_type":"markdown","source":["Another way is to write the code yourself to do it. You can extract the aggregated data as a `pandas` DataFrame and then plot it as a regular bar chart."],"metadata":{}},{"cell_type":"code","source":["labels_df = pd.DataFrame(labels.toPandas())\nlabels_df.set_index(\"label\", drop=True,inplace=True)\nlabels_fig = labels_df.plot(kind='barh')\n\nplt.rcParams[\"figure.figsize\"] = (7, 5)\nplt.rcParams.update({'font.size': 10})\nplt.tight_layout()\ndisplay(labels_fig.figure)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"image/png":["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"]}}],"execution_count":37},{"cell_type":"markdown","source":["### Connections based on protocols and attacks\n\nLet's look at which protocols are most vulnerable to attacks now based on the following SQL query."],"metadata":{}},{"cell_type":"code","source":["attack_protocol = sqlContext.sql(\"\"\"\n SELECT \n protocol_type, \n CASE label\n WHEN 'normal.' THEN 'no attack'\n ELSE 'attack'\n END AS state,\n COUNT(*) as freq\n FROM connections\n GROUP BY protocol_type, state\n ORDER BY 3 DESC\n \"\"\")\ndisplay(attack_protocol)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
protocol_typestatefreq
icmpattack282314
tcpattack113252
tcpno attack76813
udpno attack19177
icmpno attack1288
udpattack1177
"]}}],"execution_count":39},{"cell_type":"markdown","source":["Well, looks like ICMP connections followed by TCP connections have had the maximum attacks!"],"metadata":{}},{"cell_type":"markdown","source":["### Connection stats based on protocols and attacks\n\nLet's take a look at some statistical measures pertaining to these protocols and attacks for our connection requests."],"metadata":{}},{"cell_type":"code","source":["attack_stats = sqlContext.sql(\"\"\"\n SELECT \n protocol_type, \n CASE label\n WHEN 'normal.' THEN 'no attack'\n ELSE 'attack'\n END AS state,\n COUNT(*) as total_freq,\n ROUND(AVG(src_bytes), 2) as mean_src_bytes,\n ROUND(AVG(dst_bytes), 2) as mean_dst_bytes,\n ROUND(AVG(duration), 2) as mean_duration,\n SUM(num_failed_logins) as total_failed_logins,\n SUM(num_compromised) as total_compromised,\n SUM(num_file_creations) as total_file_creations,\n SUM(su_attempted) as total_root_attempts,\n SUM(num_root) as total_root_acceses\n FROM connections\n GROUP BY protocol_type, state\n ORDER BY 3 DESC\n \"\"\")\ndisplay(attack_stats)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
protocol_typestatetotal_freqmean_src_bytesmean_dst_bytesmean_durationtotal_failed_loginstotal_compromisedtotal_file_creationstotal_root_attemptstotal_root_acceses
icmpattack282314932.140.00.00000.00
tcpattack1132529880.38881.4123.19572269761.0152
tcpno attack768131439.314263.9711.0818277645917.05456
udpno attack1917798.0189.891054.630000.00
icmpno attack128891.470.00.00000.00
udpattack117727.50.230.00000.00
"]}}],"execution_count":42},{"cell_type":"markdown","source":["Looks like average amount of data being transmitted in TCP requests are much higher which is not surprising. Interestingly attacks have a much higher average payload of data being transmitted from the source to the destination."],"metadata":{}},{"cell_type":"markdown","source":["#### Filtering connection stats based on the TCP protocol by service and attack type\n\nLet's take a closer look at TCP attacks given that we have more relevant data and statistics for the same. We will now aggregate different types of TCP attacks based on service, attack type and observe different metrics."],"metadata":{}},{"cell_type":"code","source":["tcp_attack_stats = sqlContext.sql(\"\"\"\n SELECT \n service,\n label as attack_type,\n COUNT(*) as total_freq,\n ROUND(AVG(duration), 2) as mean_duration,\n SUM(num_failed_logins) as total_failed_logins,\n SUM(num_file_creations) as total_file_creations,\n SUM(su_attempted) as total_root_attempts,\n SUM(num_root) as total_root_acceses\n FROM connections\n WHERE protocol_type = 'tcp'\n AND label != 'normal.'\n GROUP BY service, attack_type\n ORDER BY total_freq DESC\n \"\"\")\ndisplay(tcp_attack_stats)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
serviceattack_typetotal_freqmean_durationtotal_failed_loginstotal_file_creationstotal_root_attemptstotal_root_acceses
privateneptune.1013170.0000.00
httpback.22030.13000.00
othersatan.12210.0000.00
privateportsweep.7251915.81000.00
ftp_datawarezclient.708403.71000.00
ftpwarezclient.3071063.79000.00
otherportsweep.2601058.22000.00
telnetneptune.1970.0000.00
httpneptune.1920.0000.00
fingerneptune.1770.0000.00
ftp_dataneptune.1700.0000.00
privatesatan.1700.05000.00
csnet_nsneptune.1230.0000.00
smtpneptune.1200.0000.00
remote_jobneptune.1180.0000.00
pop_3neptune.1180.0000.00
discardneptune.1150.0000.00
iso_tsapneptune.1150.0000.00
systatneptune.1130.0000.00
domainneptune.1120.0000.00
gopherneptune.1120.0000.00
shellneptune.1110.0000.00
echoneptune.1110.0000.00
sql_netneptune.1090.0000.00
authneptune.1080.0000.00
rjeneptune.1080.0000.00
printerneptune.1070.0000.00
whoisneptune.1070.0000.00
courierneptune.1070.0000.00
bgpneptune.1060.0000.00
nntpneptune.1060.0000.00
netbios_ssnneptune.1060.0000.00
kloginneptune.1060.0000.00
uucp_pathneptune.1050.0000.00
nnspneptune.1050.0000.00
mtpneptune.1050.0000.00
imap4neptune.1050.0000.00
ftpneptune.1040.0000.00
uucpneptune.1040.0000.00
sunrpcneptune.1040.0000.00
timeneptune.1030.0000.00
loginneptune.1020.0000.00
hostnamesneptune.1020.0000.00
efsneptune.1020.0000.00
sshneptune.1020.0000.00
daytimeneptune.1020.0000.00
netbios_nsneptune.1010.0000.00
pop_2neptune.1010.0000.00
supdupneptune.1010.0000.00
ldapneptune.1010.0000.00
vmnetneptune.1010.0000.00
execneptune.990.0000.00
linkneptune.990.0000.00
privatenmap.990.0000.00
netbios_dgmneptune.990.0000.00
http_443neptune.980.0000.00
kshellneptune.980.0000.00
nameneptune.970.0000.00
ctfneptune.960.0000.00
netstatneptune.920.0000.00
otherneptune.910.0000.00
Z39_50neptune.910.0000.00
privateipsweep.680.0000.00
telnetguess_passwd.532.725600.00
telnetbuffer_overflow.21130.670150.05
fingerland.200.0000.00
ftp_datawarezmaster.188.06000.00
imap4imap.126.0000.016
ftp_databuffer_overflow.80.0000.00
telnetloadmodule.563.8090.03
telnetrootkit.5197.4120.025
otherwarezclient.53031.0000.00
httpphf.44.5000.00
ftp_dataftp_write.40.0000.01
vmnetportsweep.40.0000.00
supdupportsweep.410171.5000.00
ftp_dataipsweep.30.0000.00
csnet_nsportsweep.313484.0000.00
telnetperl.341.33060.06
ftp_dataloadmodule.30.0000.00
fingersatan.31.0000.00
ftp_datamultihop.30.33000.00
httpportsweep.313689.67000.00
ftpportsweep.30.0000.00
ftp_datasatan.30.0000.00
pop_3portsweep.313446.33000.00
gopheripsweep.34.33000.00
httpipsweep.32.0000.00
ftpftp_write.229.0020.00
sunrpcportsweep.20.0000.00
smtpportsweep.20.0000.00
ftpmultihop.2185.5020.00
rjeipsweep.20.0000.00
printerportsweep.215467.5000.00
httpsatan.20.0000.00
telnetspy.2318.0011.00
ftpwarezmaster.278.00220.00
systatportsweep.20.0000.00
mtpipsweep.20.0000.00
smtpsatan.20.5000.00
telnetmultihop.2458.0080.093
X11satan.20.0000.00
linkipsweep.20.0000.00
ftp_dataportsweep.221224.0000.00
whoisportsweep.22.0000.00
timeipsweep.21.0000.00
telnetportsweep.20.0000.00
loginftp_write.2100.5000.01
netstatportsweep.20.0000.00
uucp_pathportsweep.14.0000.00
sshportsweep.10.0000.00
telnetipsweep.16.0000.00
uucpsatan.10.0000.00
whoisipsweep.10.0000.00
linkportsweep.10.0000.00
nntpsatan.15.0000.00
nntpnmap.10.0000.00
remote_jobipsweep.10.0000.00
vmnetsatan.10.0000.00
ftpipsweep.12.0000.00
ftprootkit.121.0010.00
fingeripsweep.12.0000.00
discardsatan.111.0000.00
telnetnmap.10.0000.00
courierportsweep.130619.0000.00
echoportsweep.10.0000.00
efsportsweep.130835.0000.00
ftpbuffer_overflow.17.0040.00
ftp_datarootkit.10.0000.02
pop_3nmap.10.0000.00
sunrpcsatan.111.0000.00
http_443portsweep.10.0000.00
gophersatan.12.0000.00
ctfnmap.10.0000.00
telnetland.10.0000.00
fingerportsweep.12.0000.00
pop_3satan.15.0000.00
uucpportsweep.130418.0000.00
domainipsweep.16.0000.00
hostnamessatan.10.0000.00
telnetsatan.13.0000.00
remote_jobportsweep.10.0000.00
rjeportsweep.11.0000.00
gopherportsweep.10.0000.00
netstatsatan.10.0000.00
IRCsatan.10.0000.00
nameipsweep.10.0000.00
smtpipsweep.10.0000.00
netbios_nsportsweep.10.0000.00
ftploadmodule.17.0040.00
sshipsweep.16.0000.00
sql_netportsweep.10.0000.00
netbios_ssnportsweep.10.0000.00
daytimeportsweep.10.0000.00
hostnamesportsweep.10.0000.00
ftpsatan.18.0000.00
pm_dumpsatan.10.0000.00
Z39_50portsweep.10.0000.00
"]}}],"execution_count":45},{"cell_type":"markdown","source":["There are a lot of attack types and the preceding output shows a specific section of the same."],"metadata":{}},{"cell_type":"markdown","source":["### Filtering connection stats based on the TCP protocol by service and attack type\n\nWe will now filter some of these attack types by imposing some constraints based on duration, file creations, root accesses in our query."],"metadata":{}},{"cell_type":"code","source":["tcp_attack_stats = sqlContext.sql(\"\"\"\n SELECT \n service,\n label as attack_type,\n COUNT(*) as total_freq,\n ROUND(AVG(duration), 2) as mean_duration,\n SUM(num_failed_logins) as total_failed_logins,\n SUM(num_file_creations) as total_file_creations,\n SUM(su_attempted) as total_root_attempts,\n SUM(num_root) as total_root_acceses\n FROM connections\n WHERE (protocol_type = 'tcp'\n AND label != 'normal.')\n GROUP BY service, attack_type\n HAVING (mean_duration >= 50\n AND total_file_creations >= 5\n AND total_root_acceses >= 1)\n ORDER BY total_freq DESC\n \"\"\")\ndisplay(tcp_attack_stats)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
serviceattack_typetotal_freqmean_durationtotal_failed_loginstotal_file_creationstotal_root_attemptstotal_root_acceses
telnetbuffer_overflow.21130.670150.05
telnetloadmodule.563.8090.03
telnetmultihop.2458.0080.093
"]}}],"execution_count":48},{"cell_type":"markdown","source":["Interesting to see multihop attacks being able to get root accesses to the destination hosts!"],"metadata":{}},{"cell_type":"markdown","source":["### Subqueries to filter TCP attack types based on service\n\nLet's try to get all the TCP attacks based on service and attack type such that the overall mean duration of these attacks is greater than zero (`> 0`). For this we can do an inner query with all aggregation statistics and then extract the relevant queries and apply a mean duration filter in the outer query as shown below."],"metadata":{}},{"cell_type":"code","source":["tcp_attack_stats = sqlContext.sql(\"\"\"\n SELECT \n t.service,\n t.attack_type,\n t.total_freq\n FROM\n (SELECT \n service,\n label as attack_type,\n COUNT(*) as total_freq,\n ROUND(AVG(duration), 2) as mean_duration,\n SUM(num_failed_logins) as total_failed_logins,\n SUM(num_file_creations) as total_file_creations,\n SUM(su_attempted) as total_root_attempts,\n SUM(num_root) as total_root_acceses\n FROM connections\n WHERE protocol_type = 'tcp'\n AND label != 'normal.'\n GROUP BY service, attack_type\n ORDER BY total_freq DESC) as t\n WHERE t.mean_duration > 0 \n \"\"\")\ndisplay(tcp_attack_stats)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
serviceattack_typetotal_freq
httpback.2203
privateportsweep.725
ftp_datawarezclient.708
ftpwarezclient.307
otherportsweep.260
privatesatan.170
telnetguess_passwd.53
telnetbuffer_overflow.21
ftp_datawarezmaster.18
imap4imap.12
otherwarezclient.5
telnetloadmodule.5
telnetrootkit.5
httpphf.4
supdupportsweep.4
gopheripsweep.3
telnetperl.3
ftp_datamultihop.3
csnet_nsportsweep.3
pop_3portsweep.3
fingersatan.3
httpipsweep.3
httpportsweep.3
telnetspy.2
smtpsatan.2
ftpmultihop.2
whoisportsweep.2
timeipsweep.2
ftpftp_write.2
ftpwarezmaster.2
printerportsweep.2
ftp_dataportsweep.2
telnetmultihop.2
loginftp_write.2
uucp_pathportsweep.1
ftprootkit.1
telnetipsweep.1
nntpsatan.1
fingerportsweep.1
ftpbuffer_overflow.1
ftpipsweep.1
sunrpcsatan.1
discardsatan.1
ftploadmodule.1
courierportsweep.1
domainipsweep.1
rjeportsweep.1
ftpsatan.1
gophersatan.1
fingeripsweep.1
pop_3satan.1
uucpportsweep.1
sshipsweep.1
telnetsatan.1
efsportsweep.1
"]}}],"execution_count":51},{"cell_type":"markdown","source":["This is nice! Now an interesting way to also view this data is to use a pivot table where one attribute represents rows and another one represents columns. Let's see if we can leverage Spark DataFrames to do this!"],"metadata":{}},{"cell_type":"markdown","source":["### Building a Pivot Table from Aggregated Data\n\nHere, we will build upon the previous DataFrame object we obtained where we aggregated attacks based on type and service. For this, we can leverage the power of Spark DataFrames and the DataFrame DSL."],"metadata":{}},{"cell_type":"code","source":["display((tcp_attack_stats.groupby('service')\n .pivot('attack_type')\n .agg({'total_freq':'max'})\n .na.fill(0))\n)"],"metadata":{},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":["
serviceback.buffer_overflow.ftp_write.guess_passwd.imap.ipsweep.loadmodule.multihop.perl.phf.portsweep.rootkit.satan.spy.warezclient.warezmaster.
telnet021053015230051200
ftp012001120001103072
pop_30000000000301000
discard0000000000001000
login0020000000000000
smtp0000000000002000
domain0000010000000000
http2203000030004300000
courier0000000000100000
other000000000026000050
efs0000000000100000
private00000000007250170000
ftp_data0000000300200070818
whois0000000000200000
nntp0000000000001000
uucp_path0000000000100000
supdup0000000000400000
finger0000010000103000
printer0000000000200000
time0000020000000000
csnet_ns0000000000300000
sunrpc0000000000001000
imap400001200000000000
gopher0000030000001000
uucp0000000000100000
ssh0000010000000000
rje0000000000100000
"]}}],"execution_count":54},{"cell_type":"markdown","source":["We get a nice neat pivot table showing all the occurences based on service and attack type!\n\nThere are plenty of articles\\tutorials available online so I would recommend you to check them out. Some useful resources for you to check out include, [the complete guide to Spark SQL from Databricks](https://docs.databricks.com/spark/latest/spark-sql/index.html)."],"metadata":{}}],"metadata":{"name":"Working with SQL at Scale - Spark SQL Tutorial","notebookId":3704545280501166},"nbformat":4,"nbformat_minor":0}