diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 41f2ffe..8cd41fa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -83,6 +83,9 @@ jobs:
- name: Install linting tools
run: sudo pip install -U flake8 black
+ - name: Install packages for tests
+ run: sudo apt-get -q update && sudo apt-get -qy install nmap rpcbind
+
- name: Run black
run: black -t py36 --check test/test_masscanned.py test/src/
diff --git a/src/proto/mod.rs b/src/proto/mod.rs
index 78b65e1..9000eca 100644
--- a/src/proto/mod.rs
+++ b/src/proto/mod.rs
@@ -36,10 +36,14 @@ use ssh::SSH_PATTERN_CLIENT_PROTOCOL;
mod ghost;
use ghost::GHOST_PATTERN_SIGNATURE;
+mod rpc;
+use rpc::RPC_CALL;
+
const PROTO_HTTP: usize = 1;
const PROTO_STUN: usize = 2;
const PROTO_SSH: usize = 3;
const PROTO_GHOST: usize = 4;
+const PROTO_RPC: usize = 5;
struct TCPControlBlock {
proto_state: usize,
@@ -85,6 +89,11 @@ fn proto_init() -> Smack {
PROTO_GHOST,
SmackFlags::ANCHOR_BEGIN,
);
+ smack.add_pattern(
+ RPC_CALL,
+ PROTO_RPC,
+ SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
+ );
smack.compile();
smack
}
@@ -132,6 +141,7 @@ pub fn repl<'a>(
PROTO_STUN => stun::repl(data, masscanned, &mut client_info),
PROTO_SSH => ssh::repl(data, masscanned, &mut client_info),
PROTO_GHOST => ghost::repl(data, masscanned, &mut client_info),
+ PROTO_RPC => rpc::repl(data, masscanned, &mut client_info),
_ => {
debug!("id: {}", id);
None
diff --git a/src/proto/rpc.rs b/src/proto/rpc.rs
new file mode 100644
index 0000000..0619ed7
--- /dev/null
+++ b/src/proto/rpc.rs
@@ -0,0 +1,495 @@
+// 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 .
+
+use log::warn;
+use std::convert::TryInto;
+use std::net::IpAddr;
+
+use crate::client::ClientInfo;
+use crate::Masscanned;
+
+// last fragment (1 bit) + fragment len (31 bits) / length XID (random) / message type: call (0) / RPC version (0-255) / Program: Portmap (99840 - 100095) / Program version (*, random versions used, see below) / / Procedure: ??? (0-255)
+pub const RPC_CALL: &[u8; 28] =
+ b"********\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
+
+#[derive(Debug)]
+enum RpcState {
+ Frag,
+ Xid,
+ MessageType,
+ RpcVersion,
+ Program,
+ ProgramVersion,
+ Procedure,
+ CredsFlavor,
+ CredsLen,
+ Creds,
+ VerifFlavor,
+ VerifLen,
+ Verif,
+ End,
+}
+
+#[derive(Debug)]
+struct ProtocolState {
+ state: RpcState,
+ last_frag: bool,
+ frag_len: u32,
+ xid: u32,
+ message_type: u32,
+ rpc_version: u32,
+ program: u32,
+ prog_version: u32,
+ procedure: u32,
+ creds_flavor: u32,
+ creds_data: Vec,
+ verif_flavor: u32,
+ verif_data: Vec,
+ payload: Vec,
+ cur_len: u32,
+ data_len: u32,
+}
+
+struct Rpcb {
+ program: u32,
+ version: u32,
+ netid: String,
+ addr: String,
+ port: u16,
+ owner: String,
+}
+
+impl ProtocolState {
+ fn new() -> Self {
+ ProtocolState {
+ state: RpcState::Frag,
+ last_frag: false,
+ frag_len: 0,
+ xid: 0,
+ message_type: 0,
+ rpc_version: 0,
+ program: 0,
+ prog_version: 0,
+ procedure: 0,
+ creds_flavor: 0,
+ creds_data: Vec::::new(),
+ verif_flavor: 0,
+ verif_data: Vec::::new(),
+ payload: Vec::::new(),
+ cur_len: 0,
+ data_len: 0,
+ }
+ }
+}
+
+fn read_u32(pstate: &mut ProtocolState, byte: u8, value: u32, next_state: RpcState) -> u32 {
+ pstate.cur_len += 1;
+ if pstate.cur_len == 4 {
+ pstate.state = next_state;
+ pstate.cur_len = 0;
+ }
+ value * 256 + byte as u32
+}
+
+fn read_string(pstate: &mut ProtocolState, next_state: RpcState) {
+ pstate.data_len -= 1;
+ if pstate.data_len == 0 {
+ pstate.state = next_state;
+ }
+}
+
+fn rpc_parse(pstate: &mut ProtocolState, data: &[u8]) {
+ for byte in data {
+ match pstate.state {
+ RpcState::Frag => {
+ if pstate.cur_len == 0 {
+ match byte & 128 {
+ 0 => pstate.last_frag = false,
+ _ => pstate.last_frag = true,
+ };
+ pstate.frag_len = (*byte & 127) as u32;
+ } else {
+ pstate.frag_len = *byte as u32;
+ }
+ pstate.cur_len += 1;
+ if pstate.cur_len == 4 {
+ pstate.state = RpcState::Xid;
+ pstate.cur_len = 0;
+ }
+ }
+ RpcState::Xid => {
+ pstate.xid = read_u32(pstate, *byte, pstate.xid, RpcState::MessageType)
+ }
+ RpcState::MessageType => {
+ pstate.message_type =
+ read_u32(pstate, *byte, pstate.message_type, RpcState::RpcVersion)
+ }
+ RpcState::RpcVersion => {
+ pstate.rpc_version = read_u32(pstate, *byte, pstate.rpc_version, RpcState::Program)
+ }
+ RpcState::Program => {
+ pstate.program = read_u32(pstate, *byte, pstate.program, RpcState::ProgramVersion)
+ }
+ RpcState::ProgramVersion => {
+ pstate.prog_version =
+ read_u32(pstate, *byte, pstate.prog_version, RpcState::Procedure)
+ }
+ RpcState::Procedure => {
+ pstate.procedure = read_u32(pstate, *byte, pstate.procedure, RpcState::CredsFlavor)
+ }
+ RpcState::CredsFlavor => {
+ pstate.creds_flavor =
+ read_u32(pstate, *byte, pstate.creds_flavor, RpcState::CredsLen)
+ }
+ RpcState::CredsLen => {
+ pstate.data_len = read_u32(pstate, *byte, pstate.data_len, RpcState::Creds);
+ if matches!(pstate.state, RpcState::Creds) && pstate.data_len == 0 {
+ pstate.state = RpcState::VerifFlavor
+ }
+ }
+ RpcState::Creds => {
+ pstate.creds_data.push(*byte);
+ read_string(pstate, RpcState::VerifFlavor)
+ }
+ RpcState::VerifFlavor => {
+ pstate.verif_flavor =
+ read_u32(pstate, *byte, pstate.verif_flavor, RpcState::VerifLen)
+ }
+ RpcState::VerifLen => {
+ pstate.data_len = read_u32(pstate, *byte, pstate.data_len, RpcState::Verif);
+ if matches!(pstate.state, RpcState::Verif) && pstate.cur_len == 0 {
+ pstate.state = RpcState::End
+ }
+ }
+ RpcState::Verif => {
+ pstate.verif_data.push(*byte);
+ read_string(pstate, RpcState::End)
+ }
+ RpcState::End => {
+ pstate.payload.push(*byte);
+ }
+ };
+ }
+}
+
+fn get_nth_byte(value: u32, nth: u8) -> u8 {
+ let shift = 8 * (3 - nth);
+ ((value & (0xff << shift)) >> shift).try_into().unwrap()
+}
+
+fn push_u32(buffer: &mut Vec, data: u32) {
+ for i in 0..4 {
+ buffer.push(get_nth_byte(data, i));
+ }
+}
+
+fn push_string_pad(buffer: &mut Vec, data: String) {
+ let len: u32 = data.len().try_into().unwrap();
+ push_u32(buffer, len);
+ buffer.append(&mut data.as_bytes().to_vec());
+ if len % 4 != 0 {
+ for _ in 0..(4 - (len % 4)) {
+ buffer.append(&mut b"\x00".to_vec());
+ }
+ }
+}
+
+fn build_repl_portmap(pstate: ProtocolState, client_info: &ClientInfo) -> Vec {
+ let mut resp = Vec::::new();
+ match pstate.procedure {
+ // 0 => {}
+ 3 => {
+ // getaddr / getport
+ // accepted state: 0 (RPC executed successfully)
+ resp.extend([0, 0, 0, 0]);
+ let localport = client_info.port.dst.unwrap();
+ match pstate.prog_version {
+ 2 => {
+ push_u32(&mut resp, localport as u32);
+ }
+ 3 | 4 => {
+ let addr = format!(
+ "{}.{}.{}",
+ client_info.ip.dst.unwrap(),
+ localport >> 8,
+ localport % 256
+ );
+ push_string_pad(&mut resp, addr);
+ }
+ _ => panic!("Wrong RPC version"),
+ }
+ }
+ 4 => {
+ // dump
+ // accepted state: 0 (RPC executed successfully)
+ resp.extend([0, 0, 0, 0]);
+ let localaddr = client_info.ip.dst.unwrap();
+ let localport = client_info.port.dst.unwrap();
+ let netid = match localaddr {
+ IpAddr::V4(_) => "tcp",
+ IpAddr::V6(_) => "tcp6",
+ };
+ for rpcb in [
+ Rpcb {
+ program: 100000,
+ version: 2,
+ netid: netid.to_string(),
+ addr: format!("{}", localaddr),
+ port: localport,
+ owner: "superuser".to_string(),
+ },
+ Rpcb {
+ program: 100000,
+ version: 3,
+ netid: netid.to_string(),
+ addr: format!("{}", localaddr),
+ port: localport,
+ owner: "superuser".to_string(),
+ },
+ Rpcb {
+ program: 100000,
+ version: 4,
+ netid: netid.to_string(),
+ addr: format!("{}", localaddr),
+ port: localport,
+ owner: "superuser".to_string(),
+ },
+ ] {
+ resp.append(&mut b"\x00\x00\x00\x01".to_vec()); // value follows: yes
+ push_u32(&mut resp, rpcb.program);
+ push_u32(&mut resp, rpcb.version);
+ match pstate.prog_version {
+ 2 => {
+ push_u32(
+ &mut resp,
+ match rpcb.netid.as_str() {
+ "tcp" => 6,
+ "tcp6" => 6,
+ "udp" => 17,
+ "udp6" => 17,
+ _ => 0,
+ },
+ );
+ push_u32(&mut resp, localport as u32);
+ }
+ 3 | 4 => {
+ push_string_pad(&mut resp, rpcb.netid);
+ push_string_pad(
+ &mut resp,
+ format!("{}.{}.{}", rpcb.addr, rpcb.port >> 8, rpcb.port & 0xff),
+ );
+ push_string_pad(&mut resp, rpcb.owner);
+ }
+ _ => panic!("Wrong RPC version"),
+ }
+ }
+ resp.append(&mut b"\x00\x00\x00\x00".to_vec()); // value follows: no
+ }
+ _ => {
+ // accepted state: 5 (program can't support procedure)
+ resp.extend([0, 0, 0, 5]);
+ }
+ }
+ warn!(
+ "RPC: Portmap version {}, procedure {}",
+ pstate.prog_version, pstate.procedure
+ );
+ resp
+}
+
+fn build_repl_unknownprog(pstate: ProtocolState, _client_info: &ClientInfo) -> Vec {
+ warn!(
+ "Unknown program {}, procedure {}: accepted state 1",
+ pstate.program, pstate.procedure
+ );
+ // accepted state: 1 (remote hasn't exported program)
+ vec![0, 0, 0, 1]
+}
+
+fn build_repl(pstate: ProtocolState, client_info: &ClientInfo) -> Vec {
+ // TODO: test RPC versions, drop non calls?
+ let mut resp = Vec::::new();
+ push_u32(&mut resp, pstate.xid);
+ // message_type: 1 (reply)
+ // reply_state: 0 (accepted)
+ // verifier: 0 (auth null)
+ // verifier length: 0
+ resp.extend([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+ if pstate.prog_version < 2 || pstate.prog_version > 4 {
+ /*
+ * Scanners (e.g., Nmap script rpc-grind) often use random
+ * values for program version to find out if a program is
+ * supported, so for any program, we answer with "remote can't
+ * support version" accepted state.
+ */
+ // accepted state: 2 (remote can't support version)
+ // prog_version min: 2
+ // prog_version max: 4
+ let prog_version = match pstate.prog_version {
+ 104316 => "104316 (Nmap probe TCP RPCCheck)".to_string(),
+ x => x.to_string(),
+ };
+ warn!(
+ "RPC: unsupported version {} for program {}",
+ prog_version, pstate.program
+ );
+ resp.extend([0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 4]);
+ } else if pstate.procedure == 0 {
+ /*
+ * RPC clients (e.g., Linux kernel NFS client, rpcbind CLI
+ * tool) would often send a NULL procedure (0) call before any
+ * real operation .
+ */
+ // accepted state: 0 (RPC executed successfully)
+ warn!("RPC: NULL procedure call for program {}", pstate.program);
+ resp.extend([0, 0, 0, 0]);
+ } else {
+ let mut specif_resp = match pstate.program {
+ 100000 => build_repl_portmap(pstate, client_info),
+ _ => build_repl_unknownprog(pstate, client_info),
+ };
+ resp.append(&mut specif_resp);
+ }
+ let length: u32 = resp.len().try_into().unwrap();
+ let mut final_resp = Vec::::new();
+ for i in 0..4 {
+ match i {
+ 0 => final_resp.push(get_nth_byte(length, i) | 0x80),
+ _ => final_resp.push(get_nth_byte(length, i)),
+ };
+ }
+ final_resp.append(&mut resp);
+ final_resp
+}
+
+pub fn repl<'a>(
+ data: &'a [u8],
+ _masscanned: &Masscanned,
+ client_info: &ClientInfo,
+) -> Option> {
+ let mut pstate = ProtocolState::new();
+ rpc_parse(&mut pstate, data);
+ // warn!("RPC {:#?}", pstate);
+ match pstate.state {
+ RpcState::End => Some(build_repl(pstate, client_info)),
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::client::ClientInfoSrcDst;
+ use std::net::Ipv4Addr;
+
+ const CLIENT_INFO: ClientInfo = ClientInfo {
+ mac: ClientInfoSrcDst {
+ src: None,
+ dst: None,
+ },
+ ip: ClientInfoSrcDst {
+ src: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 0))),
+ dst: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 1))),
+ },
+ transport: None,
+ port: ClientInfoSrcDst {
+ src: Some(12345),
+ dst: Some(111),
+ },
+ cookie: None,
+ };
+
+ #[test]
+ fn test_probe_nmap() {
+ let mut pstate = ProtocolState::new();
+ rpc_parse(&mut pstate, b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
+ assert!(matches!(pstate.state, RpcState::End));
+ assert!(pstate.xid == 0x72fe1d13);
+ assert!(pstate.rpc_version == 2);
+ assert!(pstate.program == 100000);
+ assert!(pstate.prog_version == 104316);
+ assert!(pstate.procedure == 0);
+ assert!(pstate.creds_flavor == 0);
+ assert!(pstate.creds_data.len() == 0);
+ assert!(pstate.verif_flavor == 0);
+ assert!(pstate.verif_data.len() == 0);
+ let resp = build_repl(pstate, &CLIENT_INFO);
+ assert!(resp == b"\x80\x00\x00\x20\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
+ }
+
+ #[test]
+ fn test_probe_nmap_split1() {
+ let mut pstate = ProtocolState::new();
+ for byte in b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" {
+ rpc_parse(&mut pstate, &[*byte]);
+ }
+ assert!(matches!(pstate.state, RpcState::End));
+ assert!(pstate.xid == 0x72fe1d13);
+ assert!(pstate.rpc_version == 2);
+ assert!(pstate.program == 100000);
+ assert!(pstate.prog_version == 104316);
+ assert!(pstate.procedure == 0);
+ assert!(pstate.creds_flavor == 0);
+ assert!(pstate.creds_data.len() == 0);
+ assert!(pstate.verif_flavor == 0);
+ assert!(pstate.verif_data.len() == 0);
+ let resp = build_repl(pstate, &CLIENT_INFO);
+ assert!(resp == b"\x80\x00\x00\x20\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
+ }
+
+ #[test]
+ fn test_probe_nmap_split2() {
+ let mut pstate = ProtocolState::new();
+ for data in [
+ b"\x80\x00\x00\x28\x72\xfe\x1d",
+ b"\x13\x00\x00\x00\x00\x00\x00",
+ b"\x00\x02\x00\x01\x86\xa0\x00",
+ b"\x01\x97\x7c\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00",
+ b"\x00\x00\x00\x00\x00\x00\x00",
+ ] {
+ rpc_parse(&mut pstate, data);
+ }
+ rpc_parse(&mut pstate, b"\x00\x00");
+ assert!(matches!(pstate.state, RpcState::End));
+ assert!(pstate.xid == 0x72fe1d13);
+ assert!(pstate.rpc_version == 2);
+ assert!(pstate.program == 100000);
+ assert!(pstate.prog_version == 104316);
+ assert!(pstate.procedure == 0);
+ assert!(pstate.creds_flavor == 0);
+ assert!(pstate.creds_data.len() == 0);
+ assert!(pstate.verif_flavor == 0);
+ assert!(pstate.verif_data.len() == 0);
+ let resp = build_repl(pstate, &CLIENT_INFO);
+ assert!(resp == b"\x80\x00\x00\x20\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
+ }
+
+ #[test]
+ fn test_probe_portmap_v4_dump() {
+ let mut pstate = ProtocolState::new();
+ rpc_parse(&mut pstate, b"\x80\x00\x00\x28\x01\x1b\x60\xa6\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
+ assert!(matches!(pstate.state, RpcState::End));
+ assert!(pstate.rpc_version == 2);
+ assert!(pstate.program == 100000);
+ assert!(pstate.prog_version == 4);
+ assert!(pstate.procedure == 4); // dump
+ assert!(pstate.creds_flavor == 0);
+ assert!(pstate.creds_data.len() == 0);
+ assert!(pstate.verif_flavor == 0);
+ assert!(pstate.verif_data.len() == 0);
+ }
+}
diff --git a/test/requirements.txt b/test/requirements.txt
index ebda39b..fd8f3e8 100644
--- a/test/requirements.txt
+++ b/test/requirements.txt
@@ -1,2 +1,3 @@
+ivre
scapy
requests
diff --git a/test/src/all.py b/test/src/all.py
index c4b740e..c73d65b 100644
--- a/test/src/all.py
+++ b/test/src/all.py
@@ -14,11 +14,16 @@
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see .
+import json
import logging
+import os
from socket import AF_INET6
+from subprocess import check_call
import struct
+from tempfile import NamedTemporaryFile
import zlib
+from ivre.db import DBNmap
from scapy.compat import raw
from scapy.data import ETHER_BROADCAST
from scapy.layers.inet import ICMP, IP, TCP, UDP
@@ -1166,6 +1171,63 @@ def test_ipv4_tcp_ghost():
)
+@test
+def test_rpc_nmap():
+ with NamedTemporaryFile(delete=False) as xml_result:
+ check_call(
+ [
+ "nmap",
+ "-n",
+ "-vv",
+ "-oX",
+ "-",
+ IPV4_ADDR,
+ "-sSV",
+ "-p",
+ "111",
+ "--script",
+ "rpcinfo,rpc-grind",
+ ],
+ stdout=xml_result,
+ )
+ with NamedTemporaryFile(delete=False, mode="w") as json_result:
+ DBNmap(output=json_result).store_scan(xml_result.name)
+ os.unlink(xml_result.name)
+ with open(json_result.name) as fdesc:
+ results = [json.loads(line) for line in fdesc]
+ os.unlink(json_result.name)
+ assert len(results) == 1, f"Expected 1 result, got {len(results)}"
+ result = results[0]
+ assert len(result["ports"]) == 1, f"Expected 1 port, got {len(result['ports'])}"
+ port = result["ports"][0]
+ assert port["port"] == 111 and port["protocol"] == "tcp"
+ assert port["service_name"] in {"rpcbind", "nfs"}
+ assert port["service_extrainfo"] in {"RPC #100000", "RPC #100003"}
+ assert len(port["scripts"]) == 1, f"Expected 1 script, got {len(port['scripts'])}"
+ script = port["scripts"][0]
+ assert script["id"] == "rpcinfo", "Expected rpcinfo script, not found"
+ assert len(script["rpcinfo"]) == 1
+
+
+@test
+def test_rpcinfo():
+ with NamedTemporaryFile(delete=False) as rpcout:
+ check_call(["rpcinfo", "-p", IPV4_ADDR], stdout=rpcout)
+ with open(rpcout.name) as fdesc:
+ found = []
+ for line in fdesc:
+ line = line.split()
+ if line[0] == "program":
+ # header
+ continue
+ assert line[0] == "100000", f"Expected program 100000, got {line[0]}"
+ found.append(int(line[1]))
+ assert len(found) == 3, f"Expected three versions, got {found}"
+ for i in range(2, 5):
+ assert i in found, f"Missing version {i} in {found}"
+ os.unlink(rpcout.name)
+
+
def test_all():
global TESTS
# execute tests
diff --git a/test/test_masscanned.py b/test/test_masscanned.py
index de4548a..54fee57 100755
--- a/test/test_masscanned.py
+++ b/test/test_masscanned.py
@@ -49,6 +49,7 @@ def setup_logs():
def cleanup_net(iface):
+ global ipfile
subprocess.check_call(["ip", "link", "delete", iface])
subprocess.check_call(
[
@@ -66,6 +67,10 @@ def cleanup_net(iface):
]
)
subprocess.check_call(["iptables", "-D", "INPUT", "-i", iface, "-j", "DROP"])
+ try:
+ os.unlink(ipfile.name)
+ except NameError:
+ pass
def setup_net(iface):