CI: add build workflow

This commit is contained in:
Pierre Lalet 2021-12-08 22:50:05 +01:00
parent dbd4d57222
commit 12aa60b848
6 changed files with 106 additions and 10 deletions

82
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,82 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
name: Build masscanned
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v2
- name: Get Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Run cargo build
uses: actions-rs/cargo@v1
with:
command: build
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
- name: Create build archive
run: tar cf masscanned.tar target/debug/masscanned
- name: Upload binary
uses: actions/upload-artifact@v2
with:
name: masscanned.tar
path: masscanned.tar
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v2
- name: Get binary
uses: actions/download-artifact@v2
with:
name: masscanned.tar
- name: Extract build archive
run: tar xf masscanned.tar
- name: Use Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: sudo pip install -r test/requirements.txt
- name: Run tests
run: sudo python test/test_masscanned.py
- name: Display logs
run: echo STDOUT; cat test/res/masscanned.stdout && echo && echo STDERR && cat test/res/masscanned.stderr
if: failure()

2
.gitignore vendored
View file

@ -5,6 +5,8 @@ Cargo.lock
# Vim temporary files # Vim temporary files
*.swp *.swp
*.swo *.swo
# Emacs temporary files
*~
*__pycache__* *__pycache__*
test/res/* test/res/*

View file

@ -1,3 +1,5 @@
[![Build masscanned](https://github.com/ivre/masscanned/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/ivre/masscanned/actions/workflows/test.yml?branch=master)
# Masscanned # Masscanned
**Masscanned** (name inspired, of course, by [masscan](https://github.com/robertdavidgraham/masscan)) **Masscanned** (name inspired, of course, by [masscan](https://github.com/robertdavidgraham/masscan))

2
test/requirements.txt Normal file
View file

@ -0,0 +1,2 @@
scapy
requests

View file

@ -29,20 +29,22 @@ LOG = logging.getLogger(__name__)
LOG.setLevel(logging.DEBUG) LOG.setLevel(logging.DEBUG)
LOG.addHandler(ch) LOG.addHandler(ch)
tests = list() tests = []
errors = []
# decorator to automatically add a function to tests # decorator to automatically add a function to tests
def test(f): def test(f):
global errors, tests
OK = "\033[1mOK\033[0m" OK = "\033[1mOK\033[0m"
KO = "\033[1m\033[1;%dmKO\033[0m" % 31 KO = "\033[1m\033[1;%dmKO\033[0m" % 31
global tests
fname = f.__name__.ljust(50, '.') fname = f.__name__.ljust(50, '.')
def w(iface): def w(iface):
try: try:
f(iface) f(iface)
LOG.info("{}{}".format(fname, OK)) LOG.info("{}{}".format(fname, OK))
except AssertionError as e: except AssertionError as e:
LOG.info("{}{}: {}".format(fname, KO, e)) LOG.error("{}{}: {}".format(fname, KO, e))
errors.append(fname)
tests.append(w) tests.append(w)
return w return w
@ -80,7 +82,7 @@ def check_ipv6_checksum(pkt):
@test @test
def test_arp_req(iface): def test_arp_req(iface):
##### ARP ##### ##### ARP #####
arp_req = Ether()/ARP(psrc='192.0.0.2', pdst=IPV4_ADDR) arp_req = Ether(dst=ETHER_BROADCAST)/ARP(psrc='192.0.0.2', pdst=IPV4_ADDR)
arp_repl = iface.sr1(arp_req, timeout=1) arp_repl = iface.sr1(arp_req, timeout=1)
assert(arp_repl is not None), "expecting answer, got nothing" assert(arp_repl is not None), "expecting answer, got nothing"
assert(ARP in arp_repl), "no ARP layer found" assert(ARP in arp_repl), "no ARP layer found"
@ -425,7 +427,8 @@ def test_ipv4_udp_stun(iface):
assert(length == 12), "expected length 12, got {}".format(length) assert(length == 12), "expected length 12, got {}".format(length)
assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic) assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
assert(tid == b'\x00' * 12), "expected tid 0x000000000000000000000000, got {:x}".format(tid) assert(tid == b'\x00' * 12), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
assert(data == bytes.fromhex("000100080001") + struct.pack(">H", sport) + bytes.fromhex("00000000")), "unexpected data" assert(data[:8] == bytes.fromhex("000100080001") + struct.pack(">H", sport)), f"unexpected data {data!r}"
assert(len(data) == 12), f"unexpected data {data!r}"
@test @test
def test_ipv6_udp_stun(iface): def test_ipv6_udp_stun(iface):
@ -474,7 +477,8 @@ def test_ipv4_udp_stun_change_port(iface):
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_) assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
assert(length == 12), "expected length 12, got {}".format(length) assert(length == 12), "expected length 12, got {}".format(length)
assert(tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c")), "expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid assert(tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c")), "expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
assert(data == bytes.fromhex("000100080001") + struct.pack(">H", sport) + bytes.fromhex("00000000")), "unexpected data" assert(data[:8] == bytes.fromhex("000100080001") + struct.pack(">H", sport)), f"unexpected data {data!r}"
assert(len(data) == 12), f"unexpected data {data!r}"
@test @test
def test_ipv6_udp_stun_change_port(iface): def test_ipv6_udp_stun_change_port(iface):
@ -591,3 +595,4 @@ def test_all(iface):
# execute tests # execute tests
for t in tests: for t in tests:
t(iface) t(iface)
return len(errors)

View file

@ -42,7 +42,8 @@ LOG = logging.getLogger(__name__)
LOG.setLevel(logging.INFO) LOG.setLevel(logging.INFO)
LOG.addHandler(ch) LOG.addHandler(ch)
conf.iface = 'tap0' IFACE = "tap0"
conf.verb = 0 conf.verb = 0
# prepare configuration file for masscanned # prepare configuration file for masscanned
@ -52,7 +53,8 @@ with open(ipfile, "w") as f:
f.write("{}\n".format(IPV6_ADDR)) f.write("{}\n".format(IPV6_ADDR))
# create test interface # create test interface
tap = TunTapInterface(resolve_iface(conf.iface)) tap = TunTapInterface(IFACE)
conf.iface = resolve_iface(IFACE)
# set interface # set interface
subprocess.run("ip a a dev {} 192.0.0.2".format(conf.iface), shell=True) subprocess.run("ip a a dev {} 192.0.0.2".format(conf.iface), shell=True)
@ -67,12 +69,13 @@ masscanned = subprocess.Popen("RUST_BACKTRACE=1 ./target/debug/masscanned -vvvvv
sleep(1) sleep(1)
try: try:
test_all(tap) result = test_all(tap)
except AssertionError: except AssertionError:
pass result = -1
# terminate masscanned # terminate masscanned
masscanned.kill() masscanned.kill()
# terminate capture # terminate capture
sleep(2) sleep(2)
tcpdump.kill() tcpdump.kill()
sys.exit(result)