Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added BIN +68 KB .coverage
Binary file not shown.
18 changes: 18 additions & 0 deletions 18 .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Python test

on: [push]

jobs:
build:

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Test with pytest
run: |
sudo pip install pytest pytest-cov
sudo -E pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
7 changes: 7 additions & 0 deletions 7 .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"test"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
9 changes: 4 additions & 5 deletions 9 CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ here on GitHub.
Please note we have a code of conduct, please follow it in all your interactions with the project.

## Pull Request Process
** All PRs must merge into the dev branch, and not into master**. The dev branch is the only one that can merge into master directly.

1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
2. Make sure all corresponding test cases pass.
3. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
4. Version number will be increased only when merging dev into master, so that a version update may carry multiple PRs from various developers. The versioning scheme we use is [SemVer](http://semver.org/).
88 changes: 88 additions & 0 deletions 88 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,91 @@ which payloads to send to the remote device. For that, we have several classes i
`payload_provider` module. You may want to create your own provider by extending
`payload_provider.PayloadProvider`. If you are interested in that, you should check the
documentation of both `executor` and `payload_provider` module.

## Code Structure

### Top Level Directory Layout
Our project directory structure contains all src files in the pythonping folder, test cases in another folder, and helping documentation in on the top level directory.

```
.
├── pythonping # Source files
├── test # Automated Testcases for the package
├── CODE_OF_CONDUCT # An md file containing code of conduct
├── CONTRIBUTING # Contributing Guidlins
├── LICENSE # MIT License
├── README.md # An md file
└── setup.py # Instalation
```

A UML Diagram of the code structure is below:

![ER1](https://raw.githubusercontent.com/alessandromaggio/pythonping/master/docs/UML-Diagram.png)

As per the uml diagram above five distinct classes outside of init exist in this package: Executor, Icmp, Payload Provider, and Utils. Each of them rely on attributes which have been listed as sub-classes for brevities sake. An overview of each class is as follows.

### Utils
Simply generates random text. See function random_text.

### Network
Opens a socket to send and recive data. See functions send, recv, and del.

### Payload Provider
Generates ICMP Payloads with no Headers. It's functionaly a interface. It has three
functions init, iter, and next, which are all implmented by subclasses List, Repeat, and Sweep which store payloads in diffrent lists.

### ICMP
Generates the ICMP heaser through subclass ICMPType, and various helper functions.

### Executor
Has various subclasses including Message, Response, Success, and Communicator used for sending icmp packets and collecting data.

### Init
Uses network, executor, payload_provider and utils.random_text to construct and send ICMP packets to ping a network.

## Tests
A test package exists under the folder test, and contains a serise of unit tests. Before commiting changes make sure to run the test bench and make sure all corrisponding cases pass. For new functionality new test cases must be added and documented.

To run testcases we can simply use the ```unitest discover``` utility by running the following command:

```
python -m unittest discover <test_directory>
```

To run the test cases in a specific file FILE we must run the following command:

```
python -m unittest discover -s <test_directory> -p FILE
```

Another option is to run the following from the top level directory:

```
pytest test
```

To test for coverage simply run:

```
coverage run -m pytest test
```

## Contributing
Before contributing read through the contribution guidlines found the CONTRIBUTING file.

### Code Style
A few key points when contributing to this repo are as follows:
1. Use tabs over spaces.
2. Format doc strings as such:
```
DESCRIPTION

:param X: DESCRIPTION
:type X: Type
:param Y: DESCRIPTION
:type Y: Type
```
Please add doc strings to all functions added.
3. Do not add spaces between docstring and first function line.
4. Do not go over 200 characters per line.
5. When closing multiline items under brackets('()', '[]', ... etc) put the closing bracket on it's own line.
Binary file added BIN +29.9 KB docs/UML-Diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions 7 pythonping/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
from random import randint
from . import network, executor, payload_provider
from .utils import random_text
from random import randint


# this needs to be available across all thread usages and will hold ints
Expand All @@ -20,6 +20,7 @@ def ping(target,
verbose=False,
out=sys.stdout,
match=False,
source=None,
out_format='legacy'):
"""Pings a remote host and handles the responses

Expand Down Expand Up @@ -76,8 +77,10 @@ def ping(target,
SEED_IDs.append(seed_id)
break


comm = executor.Communicator(target, provider, timeout, interval, socket_options=options, verbose=verbose, output=out,
seed_id=seed_id, repr_format=out_format)
seed_id=seed_id, source=source, repr_format=out_format)

comm.run(match_payloads=match)

SEED_IDs.remove(seed_id)
Expand Down
100 changes: 59 additions & 41 deletions 100 pythonping/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,35 +92,34 @@ def success(self):
def error_message(self):
if self.message is None:
return 'No response'
else:
if self.message.packet.message_type == 0 and self.message.packet.message_code == 0:
# Echo Reply, response OK - no error
return None
elif self.message.packet.message_type == 3:
# Destination unreachable, returning more details based on message code
unreachable_messages = [
'Network Unreachable',
'Host Unreachable',
'Protocol Unreachable',
'Port Unreachable',
'Fragmentation Required',
'Source Route Failed',
'Network Unknown',
'Host Unknown',
'Source Host Isolated',
'Communication with Destination Network is Administratively Prohibited',
'Communication with Destination Host is Administratively Prohibited',
'Network Unreachable for ToS',
'Host Unreachable for ToS',
'Communication Administratively Prohibited',
'Host Precedence Violation',
'Precedence Cutoff in Effect'
]
try:
return unreachable_messages[self.message.packet.message_code]
except IndexError:
# Should never generate IndexError, this serves as additional protection
return 'Unreachable'
if self.message.packet.message_type == 0 and self.message.packet.message_code == 0:
# Echo Reply, response OK - no error
return None
if self.message.packet.message_type == 3:
# Destination unreachable, returning more details based on message code
unreachable_messages = [
'Network Unreachable',
'Host Unreachable',
'Protocol Unreachable',
'Port Unreachable',
'Fragmentation Required',
'Source Route Failed',
'Network Unknown',
'Host Unknown',
'Source Host Isolated',
'Communication with Destination Network is Administratively Prohibited',
'Communication with Destination Host is Administratively Prohibited',
'Network Unreachable for ToS',
'Host Unreachable for ToS',
'Communication Administratively Prohibited',
'Host Precedence Violation',
'Precedence Cutoff in Effect'
]
try:
return unreachable_messages[self.message.packet.message_code]
except IndexError:
# Should never generate IndexError, this serves as additional protection
return 'Unreachable'
# Error was not identified
return 'Network Error'

Expand All @@ -131,28 +130,26 @@ def time_elapsed_ms(self):
def legacy_repr(self):
if self.message is None:
return 'Request timed out'
elif self.success:
if self.success:
return 'Reply from {0}, {1} bytes in {2}ms'.format(self.message.source,
len(self.message.packet.raw),
self.time_elapsed_ms)
else:
# Not successful, but with some code (e.g. destination unreachable)
return '{0} from {1} in {2}ms'.format(self.error_message, self.message.source, self.time_elapsed_ms)
# Not successful, but with some code (e.g. destination unreachable)
return '{0} from {1} in {2}ms'.format(self.error_message, self.message.source, self.time_elapsed_ms)

def __repr__(self):
if self.repr_format == 'legacy':
return self.legacy_repr()
if self.message is None:
return 'Timed out'
elif self.success:
if self.success:
return 'status=OK\tfrom={0}\tms={1}\t\tbytes\tsnt={2}\trcv={3}'.format(
self.message.source,
self.time_elapsed_ms,
len(self.source_request.raw)+20,
len(self.message.packet.raw)
)
else:
return 'status=ERR\tfrom={1}\terror="{0}"'.format(self.message.source, self.error_message)
return 'status=ERR\tfrom={1}\terror="{0}"'.format(self.message.source, self.error_message)

class ResponseList:
"""Represents a series of ICMP responses"""
Expand All @@ -172,7 +169,8 @@ def __init__(self, initial_set=[], verbose=False, output=sys.stdout):
self.rtt_avg = 0
self.rtt_min = 0
self.rtt_max = 0
self.packets_lost = 0
self.stats_packets_sent = 0
self.stats_packets_returned = 0
for response in initial_set:
self.append(response)

Expand Down Expand Up @@ -212,9 +210,13 @@ def rtt_avg_ms(self):

def clear(self):
self._responses = []
self.stats_packets_sent = 0
self.stats_packets_returned = 0


def append(self, value):
self._responses.append(value)
self.stats_packets_sent += 1
if len(self) == 1:
self.rtt_avg = value.time_elapsed
self.rtt_max = value.time_elapsed
Expand All @@ -226,12 +228,28 @@ def append(self, value):
self.rtt_max = value.time_elapsed
if value.time_elapsed < self.rtt_min:
self.rtt_min = value.time_elapsed

self.packets_lost = self.packets_lost + ((0 if value.success else 1) - self.packets_lost) / len(self)
if value.success:
self.stats_packets_returned += 1

if self.verbose:
print(value, file=self.output)

@property
def stats_packets_lost(self):
return self.stats_packets_sent - self.stats_packets_returned

@property
def stats_success_ratio(self):
return self.stats_packets_returned / self.stats_packets_sent

@property
def stats_lost_ratio(self):
return 1 - self.stats_success_ratio

@property
def packets_lost(self):
return self.stats_lost_ratio

def __len__(self):
return len(self._responses)

Expand All @@ -251,7 +269,7 @@ def __iter__(self):
class Communicator:
"""Instance actually communicating over the network, sending messages and handling responses"""
def __init__(self, target, payload_provider, timeout, interval, socket_options=(), seed_id=None,
verbose=False, output=sys.stdout, repr_format=None):
verbose=False, output=sys.stdout, source=None, repr_format=None):
"""Creates an instance that can handle communication with the target device

:param target: IP or hostname of the remote device
Expand All @@ -272,7 +290,7 @@ def __init__(self, target, payload_provider, timeout, interval, socket_options=(
:type output: file
:param repr_format: How to __repr__ the response. Allowed: legacy, None
:type repr_format: str"""
self.socket = network.Socket(target, 'icmp', source=None, options=socket_options)
self.socket = network.Socket(target, 'icmp', options=socket_options, source=source)
self.provider = payload_provider
self.timeout = timeout
self.interval = interval
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.