Network Monitoring Using Pywebpush,nmap and Flask

Vraj Patel
5 min readFeb 7, 2021

Step-by-Step tutorial to build your first network monitoring web application with Python.

Requirements & Setup

  1. Of course, we need Python — 3.7 would be great
  2. Installing flask using pip install flask
  3. Here is the link for source code: https://github.com/vrajpatel2312/network-monitor-push-notification
  4. This will be our directory structure:
# You can refer to above repository for source code and related filesnetwork monitor push notification
-- static
----- images
------- badge.png
------- icon.png
----- index.css
----- main.js
----- sw.js
-- templates
----- index.html
-- main.py

Open up your terminal in the project directory and then proceed further.

Here we will be using python3, so lets create a virtualenv using python3

virtualenv -p python3 venv# Make sure to activate it
source venv/bin/activate

Now install the following requirements

# req.txt can be found from the above repository linkpip install -r req.txt

Create Self Signed Certificate

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

Generate the VAPIDs via following command:

openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem

Create base64 encoded DER representation of the keys

openssl ec -in ./vapid_private.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txtopenssl ec -in ./vapid_private.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt

These VAPIDs keys will be used in the newly developed backend service. We will be using pywebpush library for sending the web push notification. We will be wrapping the push like below by using newly generated keys:

Now lets create a flask app to make use of this function send_web_push

we will create these files, follow the above directory structure:

  • main.py
  • main.js
  • sw.js
  • index.html

main.py

# main.pyimport logging
import json, os
import nmap3
import time
nmap = nmap3.Nmap()
from flask import request, Response, render_template, jsonify, Flask, current_app
from pywebpush import webpush, WebPushException
results = nmap.scan_top_ports("192.168.0.1/23", args="-sT")
del results['runtime']
del results['stats']
hosts=results.keys()
print(hosts)
app = Flask(__name__)
app.config['SECRET_KEY'] = '9OLWxND4o83j4K4iuopO'
DER_BASE64_ENCODED_PRIVATE_KEY_FILE_PATH = os.path.join(os.getcwd(),"private_key.txt")
DER_BASE64_ENCODED_PUBLIC_KEY_FILE_PATH = os.path.join(os.getcwd(),"public_key.txt")
VAPID_PRIVATE_KEY = open(DER_BASE64_ENCODED_PRIVATE_KEY_FILE_PATH, "r+").readline().strip("\n")
VAPID_PUBLIC_KEY = open(DER_BASE64_ENCODED_PUBLIC_KEY_FILE_PATH, "r+").read().strip("\n")
VAPID_CLAIMS = {
"sub": "vraj.vup@gmail.com"
}
def send_web_push(subscription_information, message_body):
return webpush(
subscription_info=subscription_information,
data=message_body,
vapid_private_key=VAPID_PRIVATE_KEY,
vapid_claims=VAPID_CLAIMS
)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/sw.js', methods=['GET'])
def sw():
return current_app.send_static_file('sw.js')

@app.route("/subscription/", methods=["GET", "POST"])
def subscription():
"""
POST creates a subscription
GET returns vapid public key which clients uses to send around push notification
"""
if request.method == "GET":
return Response(response=json.dumps({"public_key": VAPID_PUBLIC_KEY}),
headers={"Access-Control-Allow-Origin": "*"}, content_type="application/json")
subscription_token = request.get_json("subscription_token")
return Response(status=201, mimetype="application/json")
@app.route("/push_v1/",methods=['POST'])
def push_v1(message):

#print("is_json",request.is_json)
if not request.json or not request.json.get('sub_token'):
return jsonify({'failed':1})
#print("request.json",request.json)token = request.json.get('sub_token')
try:
token = json.loads(token)
send_web_push(token, message)
return jsonify({'success':1})
except Exception as e:
print("error",e)
return jsonify({'failed':str(e)})
@app.route("/scan/",methods=['POST'])
def scan():
global hosts
k=0
while k < 1:
print("running")
result = nmap.scan_top_ports("192.168.0.1/23", args="-sT")
del result['runtime']
del result['stats']
hostsupdates=result.keys()
print(hostsupdates)
for i in hostsupdates:
if i not in hosts:
message=str(i)+" just connected to network"
print(message)
push_v1(message)
for j in hosts:
if j not in hostsupdates:
message=str(j)+" just disconnected from network"
print(message)
push_v1(message)
hosts=hostsupdates
time.sleep(60)
pass
if __name__ == "__main__":
app.run(ssl_context=('cert.pem', 'key.pem'),debug=True,host='192.168.1.225')#change host with your machine's local ip

static/main.js

// main.js'use strict';// const applicationServerPublicKey = "BNbxGYNMhEIi9zrneh7mqV4oUanjLUK3m+mYZBc62frMKrEoMk88r3Lk596T0ck9xlT+aok0fO1KXBLV4+XqxYM=";
const pushButton = document.querySelector('.js-push-btn');
let isSubscribed = false;
let swRegistration = null;
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function updateBtn() {
if (Notification.permission === 'denied') {
pushButton.textContent = 'Push Messaging Blocked.';
pushButton.disabled = true;
updateSubscriptionOnServer(null);
return;
}
if (isSubscribed) {
pushButton.textContent = 'Disable Push Messaging';
} else {
pushButton.textContent = 'Enable Push Messaging';
}
pushButton.disabled = false;
}
function updateSubscriptionOnServer(subscription) {
// TODO: Send subscription to application server
const subscriptionJson = document.querySelector('.js-subscription-json');
const subscriptionDetails =
document.querySelector('.js-subscription-details');
if (subscription) {
subscriptionJson.textContent = JSON.stringify(subscription);
subscriptionDetails.classList.remove('is-invisible');
} else {
subscriptionDetails.classList.add('is-invisible');
}
}
function subscribeUser() {
const applicationServerPublicKey = localStorage.getItem('applicationServerPublicKey');
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
})
.then(function(subscription) {
console.log('User is subscribed.');
updateSubscriptionOnServer(subscription);
localStorage.setItem('sub_token',JSON.stringify(subscription));
isSubscribed = true;
updateBtn();
})
.catch(function(err) {
console.log('Failed to subscribe the user: ', err);
updateBtn();
});
}
function unsubscribeUser() {
swRegistration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.catch(function(error) {
console.log('Error unsubscribing', error);
})
.then(function() {
updateSubscriptionOnServer(null);
console.log('User is unsubscribed.');
isSubscribed = false;
updateBtn();
});
}
function initializeUI() {
pushButton.addEventListener('click', function() {
pushButton.disabled = true;
if (isSubscribed) {
unsubscribeUser();
} else {
subscribeUser();
}
});
// Set the initial subscription value
swRegistration.pushManager.getSubscription()
.then(function(subscription) {
isSubscribed = !(subscription === null);
updateSubscriptionOnServer(subscription); if (isSubscribed) {
console.log('User IS subscribed.');
} else {
console.log('User is NOT subscribed.');
}
updateBtn();
});
}
if ('serviceWorker' in navigator && 'PushManager' in window) {
console.log('Service Worker and Push is supported');
navigator.serviceWorker.register("/static/sw.js")
.then(function(swReg) {
console.log('Service Worker is registered', swReg);
swRegistration = swReg;
initializeUI();
})
.catch(function(error) {
console.error('Service Worker Error', error);
});
} else {
console.warn('Push meapplicationServerPublicKeyssaging is not supported');
pushButton.textContent = 'Push Not Supported';
}
function push_message() {
console.log("sub_token", localStorage.getItem('sub_token'));
$.ajax({
type: "POST",
url: "/push_v1/",
contentType: 'application/json; charset=utf-8',
dataType:'json',
data: JSON.stringify({'sub_token':localStorage.getItem('sub_token')}),
success: function( data ){
console.log("success",data);
},
error: function( jqXhr, textStatus, errorThrown ){
console.log("error",errorThrown);
}
});
}
$(document).ready(function(){
$.ajax({
type:"GET",
url:'/subscription/',
success:function(response){
console.log("response",response);
localStorage.setItem('applicationServerPublicKey',response.public_key);
}
})
});

static/sw.js

// sw.js'use strict';/* eslint-disable max-len */// const applicationServerPublicKey = "BNbxGYNMhEIi9zrneh7mqV4oUanjLUK3m+mYZBc62frMKrEoMk88r3Lk596T0ck9xlT+aok0fO1KXBLV4+XqxYM=";/* eslint-enable max-len */function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
const title = 'Push Codelab';
const options = {
body: `"${event.data.text()}"`,
icon: 'images/icon.png',
badge: 'images/badge.png'
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener('notificationclick', function(event) {
console.log('[Service Worker] Notification click Received.');
event.notification.close(); event.waitUntil(
clients.openWindow('https://developers.google.com/web/')
);
});
self.addEventListener('pushsubscriptionchange', function(event) {
console.log('[Service Worker]: \'pushsubscriptionchange\' event fired.');
const applicationServerPublicKey = localStorage.getItem('applicationServerPublicKey');
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
event.waitUntil(
self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
})
.then(function(newSubscription) {
// TODO: Send to application server
console.log('[Service Worker] New subscription: ', newSubscription);
})
);
});

templates/index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Network Notification</title><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
<script defer src="https://code.getmdl.io/1.2.1/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
</head>
<body><header>
<h1>Local Network Devices</h1>
</header>
<main>
<p>Welcome to the webpush notification. The button below needs to be
fixed to support subscribing to push.</p>
<p>
<button disabled class="js-push-btn mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
Enable Push Messaging
</button>
</p>
<section class="subscription-details js-subscription-details is-invisible">

<button type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" onclick="push_message()">Start Push Notification</button>
</section>
</main>
<script src="{{ url_for('static',filename='main.js') }}"></script>
<script src="https://code.getmdl.io/1.2.1/material.min.js"></script>
</body>
</html>

Everythings is setup by now, just make sure you have followed the same directory structure for your files too. or you can have a look at the above repository.

Now run the flask server:

# you can change the port inside main.pypython main.py

Visit your site

http://IP:5000/

you will see somthing like this

click on ENABLE PUSH MESSAGING, and allow the notification from the dialog box which appears after clicking.

if no dialog box appears, click on i icon on the left side of your address bar where url is entered and allow the notifcations.

At the bottom, there is Start Push Notification Button, click on that and you will get a push notification in your browser.

--

--