mirror of
https://github.com/ivre/masscanned.git
synced 2025-10-01 22:28:20 +00:00
Publication of masscanned v0.2.0
This commit is contained in:
commit
dbd4d57222
36 changed files with 6542 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/target/
|
||||
Cargo.lock
|
||||
**/*.rs.bk
|
||||
|
||||
# Vim temporary files
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
*__pycache__*
|
||||
test/res/*
|
43
Cargo.toml
Normal file
43
Cargo.toml
Normal file
|
@ -0,0 +1,43 @@
|
|||
# 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/>.
|
||||
|
||||
[package]
|
||||
name = "masscanned"
|
||||
version = "0.2.0"
|
||||
authors = ["_Frky <3105926+Frky@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
pcap = "0.7.0"
|
||||
pcap-file = "1.1.1"
|
||||
pnet = "0.26.0"
|
||||
# pnet = { path = "libpnet" }
|
||||
clap = "2.33.3"
|
||||
log = "0.4.11"
|
||||
stderrlog = "0.5.0"
|
||||
itertools = "0.9.0"
|
||||
rand = "0.7.3"
|
||||
dns-parser = "0.8.0"
|
||||
netdevice = "0.1.1"
|
||||
bitflags = "1.2.1"
|
||||
lazy_static = "1.4.0"
|
||||
siphasher = "0.3"
|
||||
chrono = "0.4.19"
|
||||
byteorder = "1.4.3"
|
||||
|
||||
[[bin]]
|
||||
name = "masscanned"
|
||||
path = "src/masscanned.rs"
|
674
LICENSE
Normal file
674
LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program 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.
|
||||
|
||||
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
292
README.md
Normal file
292
README.md
Normal file
|
@ -0,0 +1,292 @@
|
|||
# Masscanned
|
||||
|
||||
**Masscanned** (name inspired, of course, by [masscan](https://github.com/robertdavidgraham/masscan))
|
||||
is a network responder. Its purpose is to provide generic answers to as many protocols as possible,
|
||||
and with as few asumptions as possible on the client's intentions.
|
||||
|
||||
> *Let them talk first.*
|
||||
|
||||
Just like [masscan](https://github.com/robertdavidgraham/masscan), **masscanned** implements its own,
|
||||
userland network stack, similarly to [honeyd](http://honeyd.org/). It is designed to interact
|
||||
with scanners and opportunistic bots as far as possible, and to support as many protocols as possible.
|
||||
|
||||
For example, when it receives network packets:
|
||||
|
||||
* **masscanned** answers to `ARP who is-at` with `ARP is-at` (for its IP addresses),
|
||||
* **masscanned** answers to `ICMP Echo Request` with `ICMP Echo Reply`,
|
||||
* **masscanned** answers to `TCP SYN` (any port) with `TCP SYN/ACK` on any port,
|
||||
* **masscanned** answers to `HTTP` requests (any verb) over `TCP/UDP` (any port) with a `HTTP 401` web page.
|
||||
|
||||

|
||||
|
||||
**Masscanned** currently supports most common protocols at layers 2-3-4, and a few application
|
||||
protocols:
|
||||
|
||||
* `Eth::ARP::REQ`,
|
||||
* `Eth::IPv{4,6}::ICMP::ECHO-REQ`,
|
||||
* `Eth::IPv{4,6}::TCP::SYN` (all ports),
|
||||
* `Eth::IPv{4,6}::TCP::PSHACK` (all ports),
|
||||
* `Eth::IPv6::ICMP::ND_NS`.
|
||||
* `Eth::IPv{4,6}::{TCP,UDP}::HTTP` (all HTTP verbs),
|
||||
* `Eth::IPv{4,6}::{TCP,UDP}::STUN`,
|
||||
* `Eth::IPv{4,6}::{TCP,UDP}::SSH` (Server Protocol only).
|
||||
|
||||
## Try it locally
|
||||
|
||||
1. Build **masscanned**
|
||||
```
|
||||
$ cargo build
|
||||
```
|
||||
2. Create a new net namespace
|
||||
```
|
||||
# ip netns add masscanned
|
||||
```
|
||||
3. Create veth between the two namespaces
|
||||
```
|
||||
# ip link add vethmasscanned type veth peer veth netns masscanned
|
||||
# ip link set vethmasscanned up
|
||||
# ip -n masscanned link set veth up
|
||||
```
|
||||
4. Set IP on local veth to have a route for outgoing packets
|
||||
```
|
||||
# ip addr add dev vethmasscanned 192.168.0.0/31
|
||||
```
|
||||
5. Run **masscanned** in the namespace
|
||||
```
|
||||
# ip netns exec masscanned ./target/debug/masscanned --iface veth -v[vv]
|
||||
```
|
||||
6. With another terminal, send packets to **masscanned**
|
||||
```
|
||||
# arping 192.168.0.1
|
||||
# ping 192.168.0.1
|
||||
# nc -n -v 192.168.0.1 80
|
||||
# nc -n -v -u 192.168.0.1 80
|
||||
...
|
||||
```
|
||||
|
||||
## Protocols
|
||||
|
||||
### Layer 2
|
||||
|
||||
#### ARP
|
||||
|
||||
`masscanned` anwsers to `ARP` requests, for requests that target an `IPv4` address
|
||||
that is handled by `masscanned` (*i.e.*, an address that is in the
|
||||
IP address file given with option `-f`).
|
||||
|
||||
The answer contains the first of the following possible `MAC` addresses:
|
||||
|
||||
* the `MAC` address specified with `-a` in command line if any,
|
||||
* the `MAC` address of the interface specified with `-i` in command line if any,
|
||||
* or the `masscanned` default `MAC` address, *i.e.*, `c0:ff:ee:c0:ff:ee`.
|
||||
|
||||
#### Ethernet
|
||||
|
||||
`masscanned` answers to `Ethernet` frames, if and only if the following requirements are met:
|
||||
|
||||
* the destination address of the frame should be handled by `masscanned`, which means:
|
||||
|
||||
* `masscanned` own `MAC` address,
|
||||
* the broadcast `MAC` address `ff:ff:ff:ff:ff:ff`,
|
||||
* a multicast `MAC` address corresponding to one of the `IPv4` addresses handled by `masscanned` ([RFC 1112](https://datatracker.ietf.org/doc/html/rfc1112)),
|
||||
* a multicast `MAC` address corresponding to one of the `IPv6` addresses handled by `masscanned` ;
|
||||
|
||||
* `EtherType` field is one of `ARP`, `IPv4` or `IPv6`.
|
||||
|
||||
**Note:** even for a non-multicast IP address, `masscanned` will respond to L2 frames addressed to the corresponding multicast `MAC` address.
|
||||
For instance, if `masscanned` handles `10.11.12.13`, it will answer to frames addressed to `01:00:5e:0b:0c:0d`.
|
||||
|
||||
### Layer 3
|
||||
|
||||
#### IPv4/IPv6
|
||||
|
||||
`masscanned` answers to `IPv4` and `IPv6` packets, only if:
|
||||
|
||||
* no `IP` address is specified in a file (*i.e.*, no `-f` option is specified or the file is empty),
|
||||
|
||||
**or**
|
||||
|
||||
* the destination IP address of the incoming packet is one of the IP addresses handled by `masscanned`.
|
||||
|
||||
An additionnal requirement is that the next layer protocol is supported - see below.
|
||||
|
||||
#### IPv4
|
||||
|
||||
The following L4 protocols are suppported for an `IPv4` packet:
|
||||
|
||||
* `ICMPv4`
|
||||
* `UDP`
|
||||
* `TCP`
|
||||
|
||||
If the next layer protocol is not one of them, the packet is dropped.
|
||||
|
||||
#### IPv6
|
||||
|
||||
The following L4 protocols are suppported for an `IPv6` packet:
|
||||
|
||||
* `ICMPv6`
|
||||
* `UDP`
|
||||
* `TCP`
|
||||
|
||||
If the next layer protocol is not one of them, the packet is dropped.
|
||||
|
||||
### Layer 3+/4
|
||||
|
||||
#### ICMPv4
|
||||
|
||||
`masscanned` answers to `ICMPv4` packets if and only if:
|
||||
|
||||
* the `ICMP` type of the incoming packet is `EchoRequest` (`8`),
|
||||
* the `ICMP` code of the incoming packet is `0`.
|
||||
|
||||
If these conditions are met, `masscanned` answers with an `ICMP` packet of type `EchoReply` (`0`),
|
||||
code `0` and the same payload as the incoming packet, as specified by [RFC 792](https://datatracker.ietf.org/doc/html/rfc792).
|
||||
|
||||
#### ICMPv6
|
||||
|
||||
`masscanned` answers to `ICMPv6` packets if and only if:
|
||||
|
||||
* the `ICMP` type is `NeighborSol` (`135`) **and**:
|
||||
* no IP (v4 or v6) was speficied for `masscanned`
|
||||
* **or** the target address of the Neighbor Solicitation is one of `masccanned`
|
||||
|
||||
*In that case, the answer is a `Neighbor Advertisement` (`136`) packet with `masscanned` `MAC` address*
|
||||
|
||||
**or**
|
||||
|
||||
* the `ICMP` type is `EchoRequest` (`128`)
|
||||
|
||||
*In that case, the answer is a `EchoReply` (`129`) packet.*
|
||||
|
||||
#### TCP
|
||||
|
||||
`masscanned` answers to the following `TCP` packets:
|
||||
|
||||
* if the received packet has flags `PSH` and `ACK`, `masscanned` checks the **SYNACK-cookie**, and if valid answers at least a `ACK`, or a `PSH-ACK` if
|
||||
a supported protocol (Layer 5/6/7) has been detected,
|
||||
* if the received packet has flag `ACK`, it is ignored,
|
||||
* if the received packet has flag `RST` or `FIN-ACK`, it is ignored,
|
||||
* if the received packet has flag `SYN`, then `masscanned` answers with a `SYN-ACK` packet, setting a **SYNACK-cookie** in the sequence number.
|
||||
|
||||
#### UDP
|
||||
|
||||
`masscanned` answers to an `UDP` packet if and only if the upper-layer protocol
|
||||
is handled and provides an answer.
|
||||
|
||||
### Protocols
|
||||
|
||||
#### HTTP
|
||||
|
||||
#### STUN
|
||||
|
||||
#### SSH
|
||||
|
||||
`masscanned` answers to `SSH` `Client: Protocol` messages with the following `Server: Protocol` message:
|
||||
|
||||
```
|
||||
SSH-2.0-1\r\n
|
||||
```
|
||||
|
||||
## Internals
|
||||
|
||||
### Tests
|
||||
|
||||
#### Unit tests
|
||||
|
||||
```
|
||||
$ cargo test
|
||||
Compiling masscanned v0.2.0 (/zdata/workdir/masscanned)
|
||||
Finished test [unoptimized + debuginfo] target(s) in 2.34s
|
||||
Running target/debug/deps/masscanned-b86211a090e50323
|
||||
|
||||
running 36 tests
|
||||
test client::client_info::tests::test_client_info_eq ... ok
|
||||
test layer_2::arp::tests::test_arp_reply ... ok
|
||||
test layer_3::ipv4::tests::test_ipv4_reply ... ok
|
||||
test layer_3::ipv6::tests::test_ipv6_reply ... ok
|
||||
test layer_4::icmpv6::tests::test_icmpv6_reply ... ok
|
||||
test layer_2::tests::test_eth_reply ... ok
|
||||
test layer_4::icmpv6::tests::test_nd_na_reply ... ok
|
||||
test layer_4::tcp::tests::test_synack_cookie_ipv4 ... ok
|
||||
test layer_4::icmpv4::tests::test_icmpv4_reply ... ok
|
||||
test layer_4::tcp::tests::test_synack_cookie_ipv6 ... ok
|
||||
test proto::http::test_http_request_field ... ok
|
||||
test proto::http::test_http_request_no_field ... ok
|
||||
test proto::http::test_http_request_line ... ok
|
||||
test proto::http::test_http_verb ... ok
|
||||
test proto::stun::tests::test_change_request_port ... ok
|
||||
test proto::stun::tests::test_proto_stun_ipv6 ... ok
|
||||
test proto::stun::tests::test_proto_stun_ipv4 ... ok
|
||||
test proto::stun::tests::test_change_request_port_overflow ... ok
|
||||
test smack::smack::tests::test_anchor_end ... ok
|
||||
test smack::smack::tests::test_anchor_begin ... ok
|
||||
test smack::smack::tests::test_multiple_matches ... ok
|
||||
test smack::smack::tests::test_http_banner ... ok
|
||||
test smack::smack::tests::test_multiple_matches_wildcard ... ok
|
||||
test smack::smack::tests::test_proto ... ok
|
||||
test smack::smack::tests::test_wildcard ... ok
|
||||
test proto::tests::test_proto_dispatch_ssh ... ok
|
||||
test proto::tests::test_proto_dispatch_stun ... ok
|
||||
test synackcookie::tests::test_clientinfo ... ok
|
||||
test synackcookie::tests::test_ip4_dst ... ok
|
||||
test synackcookie::tests::test_ip4_src ... ok
|
||||
test synackcookie::tests::test_ip4 ... ok
|
||||
test synackcookie::tests::test_ip6 ... ok
|
||||
test synackcookie::tests::test_key ... ok
|
||||
test synackcookie::tests::test_tcp_dst ... ok
|
||||
test synackcookie::tests::test_tcp_src ... ok
|
||||
test smack::smack::tests::test_pattern ... ok
|
||||
|
||||
test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
```
|
||||
|
||||
#### Functional tests
|
||||
|
||||
```
|
||||
# ./test/test_masscanned.py
|
||||
tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
||||
INFO test_arp_req......................................OK
|
||||
INFO test_arp_req_other_ip.............................OK
|
||||
INFO test_ipv4_req.....................................OK
|
||||
INFO test_eth_req_other_mac............................OK
|
||||
INFO test_ipv4_req_other_ip............................OK
|
||||
INFO test_icmpv4_echo_req..............................OK
|
||||
INFO test_icmpv6_neighbor_solicitation.................OK
|
||||
INFO test_icmpv6_neighbor_solicitation_other_ip........OK
|
||||
INFO test_icmpv6_echo_req..............................OK
|
||||
INFO test_tcp_syn......................................OK
|
||||
INFO test_ipv4_tcp_psh_ack.............................OK
|
||||
INFO test_ipv6_tcp_psh_ack.............................OK
|
||||
INFO test_ipv4_tcp_http................................OK
|
||||
INFO test_ipv6_tcp_http................................OK
|
||||
INFO test_ipv4_udp_http................................OK
|
||||
INFO test_ipv6_udp_http................................OK
|
||||
INFO test_ipv4_tcp_http_ko.............................OK
|
||||
INFO test_ipv4_udp_http_ko.............................OK
|
||||
INFO test_ipv6_tcp_http_ko.............................OK
|
||||
INFO test_ipv6_udp_http_ko.............................OK
|
||||
INFO test_ipv4_udp_stun................................OK
|
||||
INFO test_ipv6_udp_stun................................OK
|
||||
INFO test_ipv4_udp_stun_change_port....................OK
|
||||
INFO test_ipv6_udp_stun_change_port....................OK
|
||||
INFO test_ipv4_tcp_ssh.................................OK
|
||||
INFO test_ipv4_udp_ssh.................................OK
|
||||
INFO test_ipv6_tcp_ssh.................................OK
|
||||
INFO test_ipv6_udp_ssh.................................OK
|
||||
tcpdump: pcap_loop: The interface disappeared
|
||||
604 packets captured
|
||||
604 packets received by filter
|
||||
0 packets dropped by kernel
|
||||
```
|
||||
|
||||
### Logging Policy
|
||||
|
||||
* `ERR`: any error - will always be displayed.
|
||||
* `WARN`, `-v`: responses sent by `masscanned`.
|
||||
* `INFO`, `-vv`: packets not handled, packets ignored.
|
||||
* `DEBUG`, `-vvv`: all packets received and sent by `masscanned`.
|
||||
|
||||
## To Do
|
||||
|
||||
* Drop incoming packets if checksum is incorrect
|
||||
* Fix source address when answering to multicast packets.
|
BIN
doc/demo.gif
Normal file
BIN
doc/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 MiB |
182
src/client/client_info.rs
Normal file
182
src/client/client_info.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
// 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/>.
|
||||
|
||||
use std::fmt::{Display, Error};
|
||||
use std::hash::Hash;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use pnet::packet::ip::IpNextHeaderProtocol;
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[derive(PartialEq, Hash, Copy, Clone)]
|
||||
pub struct ClientInfoSrcDst<A: Hash + PartialEq + Clone> {
|
||||
pub src: Option<A>,
|
||||
pub dst: Option<A>,
|
||||
}
|
||||
|
||||
/* Structure to describe useful information
|
||||
* about a client connection, such as:
|
||||
* - source mac address
|
||||
* - source and dest. IP address
|
||||
* - transport layer protocol
|
||||
* - source and dest. transport port
|
||||
* - syn cookie
|
||||
**/
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ClientInfo {
|
||||
pub mac: ClientInfoSrcDst<MacAddr>,
|
||||
pub ip: ClientInfoSrcDst<IpAddr>,
|
||||
pub transport: Option<IpNextHeaderProtocol>,
|
||||
pub port: ClientInfoSrcDst<u16>,
|
||||
pub cookie: Option<u32>,
|
||||
}
|
||||
|
||||
impl ClientInfo {
|
||||
pub fn new() -> Self {
|
||||
ClientInfo {
|
||||
mac: ClientInfoSrcDst::<MacAddr> {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst::<IpAddr> {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst::<u16> {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
cookie: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ClientInfo {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.mac != other.mac {
|
||||
return false;
|
||||
}
|
||||
if self.ip != other.ip {
|
||||
return false;
|
||||
}
|
||||
if self.transport != other.transport {
|
||||
return false;
|
||||
}
|
||||
if self.port != other.port {
|
||||
return false;
|
||||
}
|
||||
/* this next case should never occur with TCP and UDP,
|
||||
* but this implementation tries to remain transport-protocol-agnostic
|
||||
**/
|
||||
if self.cookie != other.cookie {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ClientInfo {}
|
||||
|
||||
impl Display for ClientInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), Error> {
|
||||
write!(
|
||||
f,
|
||||
"{:>width_ip$}:{:<width_port$} > {:>width_port$}:{:<width_ip$}",
|
||||
self.ip.src.unwrap(),
|
||||
self.port.src.unwrap(),
|
||||
self.port.dst.unwrap(),
|
||||
self.ip.dst.unwrap(),
|
||||
width_ip = 15,
|
||||
width_port = 5
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pnet::packet::ip::IpNextHeaderProtocols;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
impl ClientInfo {
|
||||
pub fn new_test() -> Self {
|
||||
ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: Some(MacAddr::new(0, 0, 0, 0, 0, 0)),
|
||||
dst: Some(MacAddr::new(0, 0, 0, 0, 0, 0)),
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
|
||||
dst: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
|
||||
},
|
||||
transport: Some(IpNextHeaderProtocols::Tcp),
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(0),
|
||||
dst: Some(0),
|
||||
},
|
||||
cookie: Some(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_info_eq() {
|
||||
let client_ref = ClientInfo::new_test();
|
||||
/* two clients with different mac addr should be different */
|
||||
let mut client_test = ClientInfo::new_test();
|
||||
assert!(client_test == client_ref);
|
||||
client_test.mac.src = Some(MacAddr::new(1, 0, 0, 0, 0, 0));
|
||||
assert!(client_test != client_ref);
|
||||
client_test.mac.src = Some(MacAddr::new(0, 0, 0, 0, 0, 0));
|
||||
client_test.mac.dst = Some(MacAddr::new(1, 0, 0, 0, 0, 0));
|
||||
assert!(client_test != client_ref);
|
||||
client_test.mac.dst = Some(MacAddr::new(0, 0, 0, 0, 0, 0));
|
||||
assert!(client_test == client_ref);
|
||||
/* two clients with different ip addr should be different */
|
||||
let mut client_test = ClientInfo::new_test();
|
||||
assert!(client_test == client_ref);
|
||||
client_test.ip.src = Some(IpAddr::V4(Ipv4Addr::new(1, 0, 0, 0)));
|
||||
assert!(client_test != client_ref);
|
||||
client_test.ip.src = Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
|
||||
client_test.ip.dst = Some(IpAddr::V4(Ipv4Addr::new(1, 0, 0, 0)));
|
||||
assert!(client_test != client_ref);
|
||||
/* two clients with different tranport layer should be different */
|
||||
let mut client_test = ClientInfo::new_test();
|
||||
assert!(client_test == client_ref);
|
||||
client_test.transport = Some(IpNextHeaderProtocols::Udp);
|
||||
assert!(client_test != client_ref);
|
||||
client_test.transport = Some(IpNextHeaderProtocols::Tcp);
|
||||
assert!(client_test == client_ref);
|
||||
/* two clients with different tranport ports should be different */
|
||||
let mut client_test = ClientInfo::new_test();
|
||||
assert!(client_test == client_ref);
|
||||
client_test.port.src = Some(1);
|
||||
assert!(client_test != client_ref);
|
||||
client_test.port.src = Some(0);
|
||||
client_test.port.dst = Some(1);
|
||||
assert!(client_test != client_ref);
|
||||
client_test.port.dst = Some(0);
|
||||
assert!(client_test == client_ref);
|
||||
/* two clients with different cookies should be different */
|
||||
let mut client_test = ClientInfo::new_test();
|
||||
assert!(client_test == client_ref);
|
||||
client_test.cookie = Some(1);
|
||||
assert!(client_test != client_ref);
|
||||
client_test.cookie = Some(0);
|
||||
assert!(client_test == client_ref);
|
||||
}
|
||||
}
|
19
src/client/mod.rs
Normal file
19
src/client/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
// 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/>.
|
||||
|
||||
mod client_info;
|
||||
|
||||
pub use client_info::{ClientInfo, ClientInfoSrcDst};
|
116
src/layer_2/arp.rs
Normal file
116
src/layer_2/arp.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use pnet::packet::{
|
||||
arp::{ArpHardwareTypes, ArpOperations, ArpPacket, MutableArpPacket},
|
||||
/* Import needed for traits */
|
||||
Packet as _,
|
||||
};
|
||||
|
||||
use crate::Masscanned;
|
||||
|
||||
pub fn repl<'a, 'b>(
|
||||
arp_req: &'a ArpPacket,
|
||||
masscanned: &Masscanned,
|
||||
) -> Option<MutableArpPacket<'b>> {
|
||||
let mut arp_repl =
|
||||
MutableArpPacket::owned(arp_req.packet().to_vec()).expect("error parsing ARP packet");
|
||||
/* Build ARP answer depending of the type of request */
|
||||
match arp_req.get_operation() {
|
||||
ArpOperations::Request => {
|
||||
let ip = IpAddr::V4(arp_req.get_target_proto_addr());
|
||||
/* Ignore ARP requests for IP addresses not handled by masscanned */
|
||||
if let Some(ip_addr_list) = masscanned.ip_addresses {
|
||||
if !ip_addr_list.contains(&ip) {
|
||||
info!(
|
||||
"Ignoring ARP request from {} for IP {}",
|
||||
arp_req.get_sender_hw_addr(),
|
||||
ip
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Fill ARP reply */
|
||||
arp_repl.set_operation(ArpOperations::Reply);
|
||||
arp_repl.set_hardware_type(ArpHardwareTypes::Ethernet);
|
||||
arp_repl.set_sender_hw_addr(masscanned.mac);
|
||||
arp_repl.set_target_hw_addr(arp_req.get_sender_hw_addr().to_owned());
|
||||
arp_repl.set_target_proto_addr(arp_req.get_sender_proto_addr().to_owned());
|
||||
arp_repl.set_sender_proto_addr(arp_req.get_target_proto_addr().to_owned());
|
||||
warn!(
|
||||
"ARP-Reply to {} for IP {}",
|
||||
arp_req.get_sender_hw_addr(),
|
||||
arp_repl.get_sender_proto_addr()
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
info!("ARP Operation not handled: {:?}", arp_repl.get_operation());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(arp_repl)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn test_arp_reply() {
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(Ipv4Addr::new(0, 1, 2, 3)));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
let mut arp_req =
|
||||
MutableArpPacket::owned([0; 28].to_vec()).expect("error constructing ARP request");
|
||||
arp_req.set_hardware_type(ArpHardwareTypes::Ethernet);
|
||||
arp_req.set_operation(ArpOperations::Request);
|
||||
arp_req.set_sender_hw_addr(
|
||||
MacAddr::from_str("55:44:33:22:11:00").expect("error parsing MAC address"),
|
||||
);
|
||||
arp_req.set_target_hw_addr(
|
||||
MacAddr::from_str("00:00:00:00:00:00").expect("error parsing MAC address"),
|
||||
);
|
||||
arp_req.set_sender_proto_addr(Ipv4Addr::new(3, 2, 1, 0));
|
||||
/* Test getting an ARP reply for a legitimate IP address */
|
||||
arp_req.set_target_proto_addr(Ipv4Addr::new(0, 1, 2, 3));
|
||||
if let Some(arp_repl) = repl(&arp_req.to_immutable(), &masscanned) {
|
||||
assert!(arp_repl.get_hardware_type() == ArpHardwareTypes::Ethernet);
|
||||
assert!(arp_repl.get_operation() == ArpOperations::Reply);
|
||||
assert!(arp_repl.get_sender_hw_addr() == masscanned.mac);
|
||||
assert!(arp_repl.get_sender_proto_addr() == Ipv4Addr::new(0, 1, 2, 3));
|
||||
} else {
|
||||
panic!("Expected ARP reply - got None");
|
||||
}
|
||||
/* Ensure no response is returned for an other IP address */
|
||||
arp_req.set_target_proto_addr(Ipv4Addr::new(1, 1, 2, 3));
|
||||
let arp_repl = repl(&arp_req.to_immutable(), &masscanned);
|
||||
assert!(arp_repl == None);
|
||||
}
|
||||
}
|
260
src/layer_2/mod.rs
Normal file
260
src/layer_2/mod.rs
Normal file
|
@ -0,0 +1,260 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
use std::collections::HashSet;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use pnet::packet::{
|
||||
arp::ArpPacket,
|
||||
ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket},
|
||||
ipv4::checksum as ipv4_checksum,
|
||||
ipv4::Ipv4Packet,
|
||||
ipv6::Ipv6Packet,
|
||||
Packet as Pkt,
|
||||
};
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::layer_3;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub mod arp;
|
||||
|
||||
/* representation of a 6-bytes Ethernet address */
|
||||
type EtherAddr = [u8; 6];
|
||||
|
||||
/* This function builds the list of layer-2 destination addresses
|
||||
* that masscanned is authorized to answer to. It includes:
|
||||
* - masscanned own MAC address,
|
||||
* - layer 2 broadcast MAC addresses,
|
||||
* - layer 2 IPv6 multicast MAC address,
|
||||
* - layer 2 IPv6 solicited-node multicast addresses for each IPv6 address
|
||||
* of masscanned
|
||||
**/
|
||||
pub fn get_authorized_eth_addr(
|
||||
mac: &MacAddr,
|
||||
ip_addresses: Option<&HashSet<IpAddr>>,
|
||||
) -> HashSet<MacAddr> {
|
||||
let mut auth_addr = HashSet::new();
|
||||
auth_addr.insert(MacAddr::broadcast());
|
||||
auth_addr.insert(*mac);
|
||||
/* add IPv6 multicast addr */
|
||||
auth_addr.insert(
|
||||
"33:33:00:00:00:01"
|
||||
.parse()
|
||||
.expect("error parsing generic MAC address"),
|
||||
);
|
||||
/* Add:
|
||||
* - IPv4 multicast address for every IPv4
|
||||
* - IPv6 Solicited-Node multicast address for every IPv6
|
||||
**/
|
||||
if let Some(ip_addr) = ip_addresses {
|
||||
for addr in ip_addr {
|
||||
match addr {
|
||||
IpAddr::V4(ipv4) => {
|
||||
let mut eth_ma: EtherAddr = [0; 6];
|
||||
eth_ma[0] = 0x01;
|
||||
eth_ma[1] = 0x00;
|
||||
eth_ma[2] = 0x5e;
|
||||
/* RFC 1112 - https://datatracker.ietf.org/doc/html/rfc1112
|
||||
* Section 6.4:
|
||||
* An IP host group address is mapped to an Ethernet multicast address
|
||||
* by placing the low-order 23-bits of the IP address into the low-order
|
||||
* 23 bits of the Ethernet multicast address 01-00-5E-00-00-00 (hex).
|
||||
**/
|
||||
eth_ma[3] = ipv4.octets()[1] & 0x7f;
|
||||
eth_ma[4] = ipv4.octets()[2];
|
||||
eth_ma[5] = ipv4.octets()[3];
|
||||
auth_addr.insert(MacAddr::from(eth_ma));
|
||||
}
|
||||
IpAddr::V6(ipv6) => {
|
||||
let mut eth_snma: EtherAddr = [0; 6];
|
||||
eth_snma[0] = 0x33;
|
||||
eth_snma[1] = 0x33;
|
||||
/* multicast MAC address corresponding to solicited-node
|
||||
* multicast IPv6 address */
|
||||
eth_snma[2] = 0xff;
|
||||
eth_snma[3] = ipv6.octets()[13];
|
||||
eth_snma[4] = ipv6.octets()[14];
|
||||
eth_snma[5] = ipv6.octets()[15];
|
||||
auth_addr.insert(MacAddr::from(eth_snma));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auth_addr
|
||||
}
|
||||
|
||||
pub fn reply<'a, 'b>(
|
||||
eth_req: &'a EthernetPacket,
|
||||
masscanned: &Masscanned,
|
||||
mut client_info: &mut ClientInfo,
|
||||
) -> Option<MutableEthernetPacket<'b>> {
|
||||
debug!("receiving Ethernet packet: {:?}", eth_req);
|
||||
let mut eth_repl;
|
||||
/* First, check if the destination MAC address is one of those masscanned
|
||||
* is authorized to answer to (avoid answering to packets addressed to
|
||||
* other machines)
|
||||
**/
|
||||
if !get_authorized_eth_addr(&masscanned.mac, masscanned.ip_addresses)
|
||||
.contains(ð_req.get_destination())
|
||||
{
|
||||
info!(
|
||||
"Ignoring Ethernet packet from {} to {}",
|
||||
eth_req.get_source(),
|
||||
eth_req.get_destination(),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
/* Fill client information for this packet with MAC addresses (src and dst) */
|
||||
client_info.mac.src = Some(eth_req.get_source());
|
||||
client_info.mac.dst = Some(eth_req.get_destination());
|
||||
/* Build next layer payload for answer depending on the incoming packet */
|
||||
match eth_req.get_ethertype() {
|
||||
/* Construct answer to ARP request */
|
||||
EtherTypes::Arp => {
|
||||
let arp_req = ArpPacket::new(eth_req.payload()).expect("error parsing ARP packet");
|
||||
if let Some(arp_repl) = arp::repl(&arp_req, masscanned) {
|
||||
let arp_len = arp_repl.packet().len();
|
||||
let eth_len = EthernetPacket::minimum_packet_size() + arp_len;
|
||||
eth_repl = MutableEthernetPacket::owned(vec![0; eth_len])
|
||||
.expect("error constructing an Ethernet Packet");
|
||||
eth_repl.set_ethertype(EtherTypes::Arp);
|
||||
eth_repl.set_payload(arp_repl.packet());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Construct answer to IPv4 packet */
|
||||
EtherTypes::Ipv4 => {
|
||||
let ipv4_req = if let Some(p) = Ipv4Packet::new(eth_req.payload()) {
|
||||
p
|
||||
} else {
|
||||
warn!("error parsing IPv4 packet");
|
||||
return None;
|
||||
};
|
||||
if let Some(mut ipv4_repl) =
|
||||
layer_3::ipv4::repl(&ipv4_req, masscanned, &mut client_info)
|
||||
{
|
||||
ipv4_repl.set_checksum(ipv4_checksum(&ipv4_repl.to_immutable()));
|
||||
let ipv4_len = ipv4_repl.packet().len();
|
||||
let eth_len = EthernetPacket::minimum_packet_size() + ipv4_len;
|
||||
eth_repl = MutableEthernetPacket::owned(vec![0; eth_len])
|
||||
.expect("error constructing an Ethernet Packet");
|
||||
eth_repl.set_ethertype(EtherTypes::Ipv4);
|
||||
eth_repl.set_payload(ipv4_repl.packet());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Construct answer to IPv6 packet */
|
||||
EtherTypes::Ipv6 => {
|
||||
let ipv6_req = Ipv6Packet::new(eth_req.payload()).expect("error parsing IPv6 packet");
|
||||
if let Some(ipv6_repl) = layer_3::ipv6::repl(&ipv6_req, masscanned, &mut client_info) {
|
||||
let ipv6_len = ipv6_repl.packet().len();
|
||||
let eth_len = EthernetPacket::minimum_packet_size() + ipv6_len;
|
||||
eth_repl = MutableEthernetPacket::owned(vec![0; eth_len])
|
||||
.expect("error constructing an Ethernet Packet");
|
||||
eth_repl.set_ethertype(EtherTypes::Ipv6);
|
||||
eth_repl.set_payload(ipv6_repl.packet());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Log & drop unknown network protocol */
|
||||
_ => {
|
||||
info!("Ethernet type not handled: {:?}", eth_req.get_ethertype());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
eth_repl.set_source(masscanned.mac);
|
||||
eth_repl.set_destination(eth_req.get_source());
|
||||
debug!("sending Ethernet packet: {:?}", eth_repl);
|
||||
Some(eth_repl)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_eth_reply() {
|
||||
/* test payload is IP(src="3.2.1.0", dst=".".join(str(b) for b in [0xaa, 0x99,
|
||||
* 0x88, 0x77]))/ICMP() */
|
||||
let payload = b"E\x00\x00\x1c\x00\x01\x00\x00@\x01C\xce\x03\x02\x01\x00\xaa\x99\x88w\x08\x00\xf7\xff\x00\x00\x00\x00";
|
||||
let test_mac_addr =
|
||||
MacAddr::from_str("55:44:33:22:11:00").expect("error parsing MAC address");
|
||||
let mut client_info = ClientInfo::new();
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(Ipv4Addr::new(0xaa, 0x99, 0x88, 0x77)));
|
||||
ips.insert(IpAddr::V6(Ipv6Addr::new(
|
||||
0x7777, 0x7777, 0x7777, 0x7777, 0x7777, 0x7777, 0xaabb, 0xccdd,
|
||||
)));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
let mut eth_req = MutableEthernetPacket::owned(vec![
|
||||
0;
|
||||
EthernetPacket::minimum_packet_size()
|
||||
+ payload.len()
|
||||
])
|
||||
.expect("error constructing ethernet packet");
|
||||
eth_req.set_source(test_mac_addr);
|
||||
eth_req.set_payload(payload);
|
||||
/* Test answer to legitimate dest. */
|
||||
let dest_mac = [
|
||||
masscanned.mac,
|
||||
MacAddr::from_str("ff:ff:ff:ff:ff:ff").unwrap(),
|
||||
MacAddr::from_str("01:00:5e:19:88:77").unwrap(),
|
||||
MacAddr::from_str("33:33:ff:bb:cc:dd").unwrap(),
|
||||
];
|
||||
for mac in dest_mac.iter() {
|
||||
println!("testing mac: {:?}", mac);
|
||||
eth_req.set_ethertype(EtherTypes::Ipv4);
|
||||
eth_req.set_destination(*mac);
|
||||
if let Some(eth_repl) = reply(ð_req.to_immutable(), &masscanned, &mut client_info) {
|
||||
assert!(eth_repl.get_source() == masscanned.mac);
|
||||
assert!(eth_repl.get_destination() == test_mac_addr);
|
||||
assert!(eth_repl.get_ethertype() == EtherTypes::Ipv4);
|
||||
} else {
|
||||
panic!("expected an Ethernet answer, got None");
|
||||
}
|
||||
}
|
||||
/* Test answer to non-legitimate dest. */
|
||||
let dest_mac = [
|
||||
MacAddr::from_str("aa:bb:cc:dd:ee:ff").unwrap(),
|
||||
MacAddr::from_str("ff:ff:ff:ff:ff:fe").unwrap(),
|
||||
MacAddr::from_str("01:00:5e:00:11:22").unwrap(),
|
||||
MacAddr::from_str("33:33:aa:bb:cc:de").unwrap(),
|
||||
MacAddr::from_str("01:00:5e:99:88:77").unwrap(),
|
||||
MacAddr::from_str("33:33:aa:bb:cc:dd").unwrap(),
|
||||
];
|
||||
for mac in dest_mac.iter() {
|
||||
println!("testing mac: {:?}", mac);
|
||||
eth_req.set_ethertype(EtherTypes::Ipv4);
|
||||
eth_req.set_destination(*mac);
|
||||
let eth_repl = reply(ð_req.to_immutable(), &masscanned, &mut client_info);
|
||||
assert!(eth_repl == None);
|
||||
}
|
||||
}
|
||||
}
|
210
src/layer_3/ipv4.rs
Normal file
210
src/layer_3/ipv4.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
use std::convert::TryInto;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use pnet::packet::{
|
||||
icmp::checksum as ipv4_checksum_icmp,
|
||||
icmp::IcmpPacket,
|
||||
ip::IpNextHeaderProtocols,
|
||||
ipv4::{Ipv4Flags, Ipv4Packet, MutableIpv4Packet},
|
||||
tcp::ipv4_checksum as ipv4_checksum_tcp,
|
||||
tcp::TcpPacket,
|
||||
udp::ipv4_checksum as ipv4_checksum_udp,
|
||||
udp::UdpPacket,
|
||||
Packet,
|
||||
};
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::layer_4;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub fn repl<'a, 'b>(
|
||||
ip_req: &'a Ipv4Packet,
|
||||
masscanned: &Masscanned,
|
||||
mut client_info: &mut ClientInfo,
|
||||
) -> Option<MutableIpv4Packet<'b>> {
|
||||
debug!("receiving IPv4 packet: {:?}", ip_req);
|
||||
/* If masscanned is configured with IP addresses, then
|
||||
* check that the dest. IP address of the packet is one of
|
||||
* those handled by masscanned - otherwise, drop the packet.
|
||||
**/
|
||||
if let Some(ip_addr_list) = masscanned.ip_addresses {
|
||||
if !ip_addr_list.contains(&IpAddr::V4(ip_req.get_destination())) {
|
||||
info!(
|
||||
"Ignoring IP packet from {} for {}",
|
||||
ip_req.get_source(),
|
||||
ip_req.get_destination()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Fill client info with source and dest. IP addresses */
|
||||
client_info.ip.src = Some(IpAddr::V4(ip_req.get_source()));
|
||||
client_info.ip.dst = Some(IpAddr::V4(ip_req.get_destination()));
|
||||
/* Fill client info with transport layer procotol */
|
||||
client_info.transport = Some(ip_req.get_next_level_protocol());
|
||||
let mut ip_repl;
|
||||
match ip_req.get_next_level_protocol() {
|
||||
/* Answer to an ICMP packet */
|
||||
IpNextHeaderProtocols::Icmp => {
|
||||
let icmp_req = IcmpPacket::new(ip_req.payload()).expect("error parsing ICMP packet");
|
||||
if let Some(mut icmp_repl) = layer_4::icmpv4::repl(&icmp_req, masscanned, &client_info)
|
||||
{
|
||||
icmp_repl.set_checksum(ipv4_checksum_icmp(&icmp_repl.to_immutable()));
|
||||
let icmp_len = icmp_repl.packet().len();
|
||||
let ip_len = MutableIpv4Packet::minimum_packet_size() + icmp_len;
|
||||
ip_repl = MutableIpv4Packet::owned(vec![0; ip_len])
|
||||
.expect("error constructing an IPv4 packet");
|
||||
ip_repl.set_total_length(ip_len as u16);
|
||||
// FIXME
|
||||
ip_repl.set_header_length(5);
|
||||
ip_repl.set_payload(icmp_repl.packet());
|
||||
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Answer to a TCP packet */
|
||||
IpNextHeaderProtocols::Tcp => {
|
||||
let tcp_req = TcpPacket::new(ip_req.payload()).expect("error parsing TCP packet");
|
||||
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
|
||||
tcp_repl.set_checksum(ipv4_checksum_tcp(
|
||||
&tcp_repl.to_immutable(),
|
||||
&ip_req.get_destination(),
|
||||
&ip_req.get_source(),
|
||||
));
|
||||
let tcp_len = tcp_repl.packet().len();
|
||||
let ip_len = Ipv4Packet::minimum_packet_size() + tcp_len;
|
||||
ip_repl = MutableIpv4Packet::owned(vec![0; ip_len])
|
||||
.expect("error constructing an IPv4 packet");
|
||||
ip_repl.set_total_length(ip_len as u16);
|
||||
// FIXME
|
||||
ip_repl.set_header_length(5);
|
||||
ip_repl.set_payload(tcp_repl.packet());
|
||||
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Answer to an UDP packet */
|
||||
IpNextHeaderProtocols::Udp => {
|
||||
let udp_req = UdpPacket::new(ip_req.payload()).expect("error parsing UDP packet");
|
||||
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
|
||||
udp_repl.set_checksum(ipv4_checksum_udp(
|
||||
&udp_repl.to_immutable(),
|
||||
&ip_req.get_destination(),
|
||||
&ip_req.get_source(),
|
||||
));
|
||||
let udp_len = udp_repl.packet().len();
|
||||
udp_repl.set_length(udp_len.try_into().unwrap());
|
||||
debug!("udp len: {}", udp_len);
|
||||
let ip_len = Ipv4Packet::minimum_packet_size() + udp_len;
|
||||
ip_repl = MutableIpv4Packet::owned(vec![0; ip_len])
|
||||
.expect("error constructing an IPv4 packet");
|
||||
ip_repl.set_total_length(ip_len as u16);
|
||||
// FIXME
|
||||
ip_repl.set_header_length(5);
|
||||
ip_repl.set_payload(udp_repl.packet());
|
||||
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Next layer protocol not handled (yet) - dropping packet */
|
||||
_ => {
|
||||
info!(
|
||||
"IPv4 upper layer not handled: {:?}",
|
||||
ip_req.get_next_level_protocol()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
/* Set IP packet fields before sending */
|
||||
ip_repl.set_version(4);
|
||||
ip_repl.set_ttl(64);
|
||||
ip_repl.set_identification(0);
|
||||
/* These values are already initialized with 0s
|
||||
* ip_repl.set_dscp(0);
|
||||
* ip_repl.set_ecn(0);
|
||||
* ip_repl.set_identification(0);
|
||||
**/
|
||||
/* Do not fragment packet */
|
||||
ip_repl.set_flags(Ipv4Flags::DontFragment);
|
||||
/* Set source and dest. IP address */
|
||||
/* FIXME when dest. was a multicast IP address */
|
||||
ip_repl.set_source(ip_req.get_destination());
|
||||
ip_repl.set_destination(ip_req.get_source());
|
||||
debug!("sending IPv4 packet: {:?}", ip_repl);
|
||||
Some(ip_repl)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn test_ipv4_reply() {
|
||||
/* test payload is scapy> ICMP() */
|
||||
let payload = b"\x08\x00\xf7\xff\x00\x00\x00\x00";
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
let mut ip_req =
|
||||
MutableIpv4Packet::owned(vec![0; Ipv4Packet::minimum_packet_size() + payload.len()])
|
||||
.expect("error constructing IPv4 packet");
|
||||
ip_req.set_version(4);
|
||||
ip_req.set_ttl(64);
|
||||
ip_req.set_identification(0);
|
||||
ip_req.set_flags(Ipv4Flags::DontFragment);
|
||||
ip_req.set_source(test_ip_addr);
|
||||
ip_req.set_header_length(5);
|
||||
/* Set test payload for layer 4 */
|
||||
ip_req.set_total_length(ip_req.packet().len() as u16);
|
||||
ip_req.set_payload(payload);
|
||||
/* Set next protocol */
|
||||
ip_req.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
|
||||
/* Send to a legitimate IP address */
|
||||
ip_req.set_destination(masscanned_ip_addr);
|
||||
if let Some(ip_repl) = repl(&ip_req.to_immutable(), &masscanned, &mut client_info) {
|
||||
assert!(ip_repl.get_destination() == test_ip_addr);
|
||||
assert!(ip_repl.get_source() == masscanned_ip_addr);
|
||||
assert!(ip_repl.get_next_level_protocol() == IpNextHeaderProtocols::Icmp);
|
||||
assert!(ip_repl.get_total_length() == ip_repl.packet().len() as u16);
|
||||
} else {
|
||||
panic!("expected an IP answer, got None");
|
||||
}
|
||||
/* Send to a non-legitimate IP address */
|
||||
ip_req.set_destination(Ipv4Addr::new(2, 2, 2, 2));
|
||||
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||
}
|
||||
}
|
217
src/layer_3/ipv6.rs
Normal file
217
src/layer_3/ipv6.rs
Normal file
|
@ -0,0 +1,217 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use pnet::packet::{
|
||||
icmpv6::{checksum as icmpv6_checksum, Icmpv6Packet, Icmpv6Types},
|
||||
ip::IpNextHeaderProtocols,
|
||||
ipv6::{Ipv6Packet, MutableIpv6Packet},
|
||||
tcp::{ipv6_checksum as ipv6_checksum_tcp, TcpPacket},
|
||||
udp::{ipv6_checksum as ipv6_checksum_udp, UdpPacket},
|
||||
Packet,
|
||||
};
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::layer_4;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub fn repl<'a, 'b>(
|
||||
ip_req: &'a Ipv6Packet,
|
||||
masscanned: &Masscanned,
|
||||
mut client_info: &mut ClientInfo,
|
||||
) -> Option<MutableIpv6Packet<'b>> {
|
||||
debug!("receiving IPv6 packet: {:?}", ip_req);
|
||||
let src = ip_req.get_source();
|
||||
let mut dst = ip_req.get_destination();
|
||||
/* If masscanned is configured with IP addresses, check that
|
||||
* the dest. IP address corresponds to one of those
|
||||
* Otherwise, drop the packet.
|
||||
**/
|
||||
if let Some(ip_addr_list) = masscanned.ip_addresses {
|
||||
if !ip_addr_list.contains(&IpAddr::V6(dst))
|
||||
&& ip_req.get_next_header() != IpNextHeaderProtocols::Icmpv6
|
||||
{
|
||||
info!("Ignoring IP packet from {} for {}", &src, &dst);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Fill client info with source and dest. IP address */
|
||||
client_info.ip.src = Some(IpAddr::V6(ip_req.get_source()));
|
||||
client_info.ip.dst = Some(IpAddr::V6(ip_req.get_destination()));
|
||||
/* Fill client info with transport layer procotol */
|
||||
client_info.transport = Some(ip_req.get_next_header());
|
||||
let mut ip_repl;
|
||||
match ip_req.get_next_header() {
|
||||
/* Answer to ICMPv6 */
|
||||
IpNextHeaderProtocols::Icmpv6 => {
|
||||
let icmp_req =
|
||||
Icmpv6Packet::new(ip_req.payload()).expect("error parsing ICMPv6 packet");
|
||||
if let (Some(mut icmp_repl), dst_addr) =
|
||||
layer_4::icmpv6::repl(&icmp_req, masscanned, &client_info)
|
||||
{
|
||||
if let Some(ip) = dst_addr {
|
||||
dst = ip;
|
||||
}
|
||||
/* Compute checksum of upper layer */
|
||||
icmp_repl.set_checksum(icmpv6_checksum(&icmp_repl.to_immutable(), &src, &dst));
|
||||
/* Compute answer length */
|
||||
let icmp_len = icmp_repl.packet().len();
|
||||
let ip_len = MutableIpv6Packet::minimum_packet_size() + icmp_len;
|
||||
/* Create answer packet */
|
||||
ip_repl = MutableIpv6Packet::owned(vec![0; ip_len])
|
||||
.expect("error constructing an IPv6 packet");
|
||||
/* Set next header protocol and payload */
|
||||
ip_repl.set_next_header(IpNextHeaderProtocols::Icmpv6);
|
||||
ip_repl.set_payload_length(icmp_len as u16);
|
||||
ip_repl.set_payload(&icmp_repl.packet().to_vec());
|
||||
/* Special value of hlim for ICMP */
|
||||
if let Icmpv6Types::NeighborAdvert = icmp_repl.get_icmpv6_type() {
|
||||
ip_repl.set_hop_limit(255);
|
||||
};
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Answer to TCP */
|
||||
IpNextHeaderProtocols::Tcp => {
|
||||
let tcp_req = TcpPacket::new(ip_req.payload()).expect("error parsing TCP packet");
|
||||
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
|
||||
/* Compute and set TCP checksum */
|
||||
tcp_repl.set_checksum(ipv6_checksum_tcp(
|
||||
&tcp_repl.to_immutable(),
|
||||
&ip_req.get_destination(),
|
||||
&ip_req.get_source(),
|
||||
));
|
||||
/* Compute answer length */
|
||||
let tcp_len = tcp_repl.packet().len();
|
||||
let ip_len = Ipv6Packet::minimum_packet_size() + tcp_len;
|
||||
/* Create answer packet */
|
||||
ip_repl = MutableIpv6Packet::owned(vec![0; ip_len])
|
||||
.expect("error constructing an IPv6 packet");
|
||||
/* Set next header protocol and payload */
|
||||
ip_repl.set_next_header(IpNextHeaderProtocols::Tcp);
|
||||
ip_repl.set_payload_length(tcp_len as u16);
|
||||
ip_repl.set_payload(&tcp_repl.packet());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Answer to UDP */
|
||||
IpNextHeaderProtocols::Udp => {
|
||||
let udp_req = UdpPacket::new(ip_req.payload()).expect("error parsing UDP packet");
|
||||
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
|
||||
/* Compute and set UDP checksum */
|
||||
udp_repl.set_checksum(ipv6_checksum_udp(
|
||||
&udp_repl.to_immutable(),
|
||||
&ip_req.get_destination(),
|
||||
&ip_req.get_source(),
|
||||
));
|
||||
/* Compute answer length */
|
||||
let udp_len = udp_repl.packet().len();
|
||||
let ip_len = Ipv6Packet::minimum_packet_size() + udp_len;
|
||||
/* Create answer packet */
|
||||
ip_repl = MutableIpv6Packet::owned(vec![0; ip_len])
|
||||
.expect("error constructing an IPv6 packet");
|
||||
/* Set next header protocol and payload */
|
||||
ip_repl.set_next_header(IpNextHeaderProtocols::Udp);
|
||||
ip_repl.set_payload_length(udp_len as u16);
|
||||
ip_repl.set_payload(&udp_repl.packet());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Other protocols are not handled (yet) - dropping */
|
||||
_ => {
|
||||
info!(
|
||||
"IPv6 upper layer not handled: {:?}",
|
||||
ip_req.get_next_header()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
/* If not already set, we set the hlim value */
|
||||
if ip_repl.get_hop_limit() == 0 {
|
||||
ip_repl.set_hop_limit(64);
|
||||
}
|
||||
/* Set IP version */
|
||||
ip_repl.set_version(6);
|
||||
/* Set packet source and dest. */
|
||||
ip_repl.set_source(dst);
|
||||
ip_repl.set_destination(src);
|
||||
debug!("sending IPv6 packet: {:?}", ip_repl);
|
||||
Some(ip_repl)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn test_ipv6_reply() {
|
||||
/* test payload is scapy> IPv6(src="7777:6666:5555:4444:3333:2222:1111:0000",
|
||||
* dst="0000:1111:2222:3333:4444:5555:6666:7777")/TCP(sport=12345, dport=54321,
|
||||
* flags="S"))[TCP] */
|
||||
let payload = b"09\xd41\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xcf\xbc\x00\x00";
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv6Addr::new(
|
||||
0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
|
||||
);
|
||||
let masscanned_ip_addr = Ipv6Addr::new(
|
||||
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777,
|
||||
);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V6(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
let mut ip_req =
|
||||
MutableIpv6Packet::owned(vec![0; Ipv6Packet::minimum_packet_size() + payload.len()])
|
||||
.expect("error constructing IPv6 packet");
|
||||
ip_req.set_version(6);
|
||||
ip_req.set_source(test_ip_addr);
|
||||
/* Set test payload for layer 4 */
|
||||
ip_req.set_payload_length(payload.len() as u16);
|
||||
ip_req.set_payload(payload);
|
||||
/* Set next protocol */
|
||||
ip_req.set_next_header(IpNextHeaderProtocols::Tcp);
|
||||
/* Send to a legitimate IP address */
|
||||
ip_req.set_destination(masscanned_ip_addr);
|
||||
if let Some(ip_repl) = repl(&ip_req.to_immutable(), &masscanned, &mut client_info) {
|
||||
assert!(ip_repl.get_destination() == test_ip_addr);
|
||||
assert!(ip_repl.get_source() == masscanned_ip_addr);
|
||||
assert!(ip_repl.get_next_header() == IpNextHeaderProtocols::Tcp);
|
||||
assert!(ip_repl.get_payload_length() == ip_repl.payload().len() as u16);
|
||||
} else {
|
||||
panic!("expected an IP answer, got None");
|
||||
}
|
||||
/* Send to a non-legitimate IP address */
|
||||
ip_req.set_destination(Ipv6Addr::new(
|
||||
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7778,
|
||||
));
|
||||
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||
}
|
||||
}
|
18
src/layer_3/mod.rs
Normal file
18
src/layer_3/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// 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/>.
|
||||
|
||||
pub mod ipv4;
|
||||
pub mod ipv6;
|
119
src/layer_4/icmpv4.rs
Normal file
119
src/layer_4/icmpv4.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
|
||||
use pnet::packet::{
|
||||
icmp::{IcmpCode, IcmpPacket, IcmpTypes, MutableIcmpPacket},
|
||||
Packet,
|
||||
};
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub fn repl<'a, 'b>(
|
||||
icmp_req: &'a IcmpPacket,
|
||||
_masscanned: &Masscanned,
|
||||
mut _client_info: &ClientInfo,
|
||||
) -> Option<MutableIcmpPacket<'b>> {
|
||||
debug!("receiving ICMPv4 packet: {:?}", icmp_req);
|
||||
let mut icmp_repl;
|
||||
match icmp_req.get_icmp_type() {
|
||||
IcmpTypes::EchoRequest => {
|
||||
/* Check code of ICMP packet */
|
||||
if icmp_req.get_icmp_code() != IcmpCode(0) {
|
||||
info!("ICMP code not handled: {:?}", icmp_req.get_icmp_code());
|
||||
return None;
|
||||
}
|
||||
/* Compute answer length */
|
||||
let payload_len = icmp_req.payload().len();
|
||||
let icmp_len = MutableIcmpPacket::minimum_packet_size() + payload_len;
|
||||
/* Construct answer packet */
|
||||
icmp_repl = MutableIcmpPacket::owned(vec![0; icmp_len])
|
||||
.expect("error constructing an ICMP packet");
|
||||
/* Set ICMP type and code */
|
||||
icmp_repl.set_icmp_type(IcmpTypes::EchoReply);
|
||||
icmp_repl.set_icmp_code(IcmpCode(0));
|
||||
/* Set payload identical to incoming packet
|
||||
* See RFC 792 - https://datatracker.ietf.org/doc/html/rfc792 p15
|
||||
* "The data received in the echo message must be returned in the echo
|
||||
* reply message."
|
||||
**/
|
||||
icmp_repl.set_payload(icmp_req.payload());
|
||||
warn!("ICMP-Echo-Reply to ICMP-Echo-Request");
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
debug!("sending ICMPv4 packet: {:?}", icmp_repl);
|
||||
Some(icmp_repl)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn test_icmpv4_reply() {
|
||||
/* test payload is scapy> ICMP() */
|
||||
let payload = b"testpayload";
|
||||
let mut client_info = ClientInfo::new();
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: None,
|
||||
};
|
||||
let mut icmp_req =
|
||||
MutableIcmpPacket::owned(vec![0; IcmpPacket::minimum_packet_size() + payload.len()])
|
||||
.expect("error constructing ICMPv4 packet");
|
||||
/* Set ICMP payload */
|
||||
icmp_req.set_payload(payload);
|
||||
/* Set legitimate ICMP type and code */
|
||||
icmp_req.set_icmp_type(IcmpTypes::EchoRequest);
|
||||
icmp_req.set_icmp_code(IcmpCode(0));
|
||||
if let Some(icmp_repl) = repl(&icmp_req.to_immutable(), &masscanned, &mut client_info) {
|
||||
assert!(icmp_repl.get_icmp_type() == IcmpTypes::EchoReply);
|
||||
assert!(icmp_repl.get_icmp_code() == IcmpCode(0));
|
||||
assert!(icmp_repl.payload() == payload);
|
||||
} else {
|
||||
panic!("expected an IP answer, got None");
|
||||
}
|
||||
/* Set wrong code */
|
||||
icmp_req.set_icmp_code(IcmpCode(1));
|
||||
assert!(repl(&icmp_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||
/* Set wrong type */
|
||||
icmp_req.set_icmp_code(IcmpCode(0));
|
||||
icmp_req.set_icmp_type(IcmpTypes::EchoReply);
|
||||
assert!(repl(&icmp_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||
/* Try with another payload */
|
||||
icmp_req.set_icmp_type(IcmpTypes::EchoRequest);
|
||||
let payload = b"newpayload!";
|
||||
icmp_req.set_payload(payload);
|
||||
if let Some(icmp_repl) = repl(&icmp_req.to_immutable(), &masscanned, &mut client_info) {
|
||||
assert!(icmp_repl.get_icmp_type() == IcmpTypes::EchoReply);
|
||||
assert!(icmp_repl.get_icmp_code() == IcmpCode(0));
|
||||
assert!(icmp_repl.payload() == payload);
|
||||
} else {
|
||||
panic!("expected an IP answer, got None");
|
||||
}
|
||||
}
|
||||
}
|
266
src/layer_4/icmpv6.rs
Normal file
266
src/layer_4/icmpv6.rs
Normal file
|
@ -0,0 +1,266 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
use std::convert::From;
|
||||
use std::net::{IpAddr, Ipv6Addr};
|
||||
|
||||
use pnet::packet::{
|
||||
icmpv6::ndp::{
|
||||
Icmpv6Codes, MutableNeighborAdvertPacket, NdpOption, NdpOptionPacket, NdpOptionTypes,
|
||||
NeighborAdvert, NeighborAdvertFlags, NeighborSolicitPacket,
|
||||
},
|
||||
icmpv6::{Icmpv6, Icmpv6Packet, Icmpv6Types, MutableIcmpv6Packet},
|
||||
Packet,
|
||||
};
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub fn nd_ns_repl<'a, 'b>(
|
||||
nd_ns_req: &'a NeighborSolicitPacket,
|
||||
masscanned: &Masscanned,
|
||||
_client_info: &ClientInfo,
|
||||
) -> Option<MutableNeighborAdvertPacket<'b>> {
|
||||
debug!("receiving ND-NS packet: {:?}", nd_ns_req);
|
||||
/* If masscanned is configured with IP addresses, then
|
||||
* check that the dest. IP address of the packet is one of
|
||||
* those handled by masscanned - otherwise, drop the packet.
|
||||
**/
|
||||
if let Some(addresses) = masscanned.ip_addresses {
|
||||
if !addresses.contains(&IpAddr::V6(nd_ns_req.get_target_addr())) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Set answer option to TargetLLAddr(2) */
|
||||
let ndp_opt = NdpOption {
|
||||
option_type: NdpOptionTypes::TargetLLAddr,
|
||||
/* From RFC 4861, section 4.6:
|
||||
* Length 8-bit unsigned integer. The length of the option
|
||||
* (including the type and length fields) in units of
|
||||
* 8 octets. The value 0 is invalid. Nodes MUST
|
||||
* silently discard an ND packet that contains an
|
||||
* option with length zero.
|
||||
**/
|
||||
length: 1,
|
||||
/* From RFC 4861, section 4.6:
|
||||
* Options should be padded when necessary to ensure that they end on
|
||||
* their natural 64-bit boundaries.
|
||||
* In this case, no need as 6 bytes (mac addr) + 2 bytes (option type
|
||||
* and length) = 8 bytes
|
||||
**/
|
||||
data: Vec::from(<[u8; 6]>::from(masscanned.mac)),
|
||||
};
|
||||
/* Compute site of options to construct ndp packet */
|
||||
let ndp_opt_size = NdpOptionPacket::packet_size(&ndp_opt);
|
||||
/* Neighbor advertisement response content */
|
||||
let ndp_na = NeighborAdvert {
|
||||
icmpv6_type: Icmpv6Types::NeighborAdvert,
|
||||
icmpv6_code: Icmpv6Codes::NoCode,
|
||||
checksum: 0,
|
||||
flags: NeighborAdvertFlags::Override | NeighborAdvertFlags::Solicited,
|
||||
reserved: 0,
|
||||
target_addr: nd_ns_req.get_target_addr(),
|
||||
options: vec![],
|
||||
payload: vec![],
|
||||
};
|
||||
/* Construct ND-NA response packet */
|
||||
let mut nd_na_repl = MutableNeighborAdvertPacket::owned(vec![
|
||||
0;
|
||||
/* Size includes the options */
|
||||
MutableNeighborAdvertPacket::packet_size(&ndp_na)
|
||||
+ ndp_opt_size
|
||||
])
|
||||
.expect("error constructing a ND-NA packet");
|
||||
/* Set content of response */
|
||||
nd_na_repl.populate(&ndp_na);
|
||||
/* Set content of options */
|
||||
nd_na_repl.set_options(&[ndp_opt]);
|
||||
warn!("ND-NA to ND-NS for {}", nd_ns_req.get_target_addr());
|
||||
debug!("sending ND-NA packet: {:?}", nd_na_repl);
|
||||
Some(nd_na_repl)
|
||||
}
|
||||
|
||||
/* Because L3 may not know the dest. IPv6 address of the packet in the case
|
||||
* of a ND-NS packet, this function returns the reply *plus* the dest. IPv6
|
||||
* address in the case of a ND-NS, so that L3 knows to which masscanned IP
|
||||
* address the packet was targetting */
|
||||
pub fn repl<'a, 'b>(
|
||||
icmp_req: &'a Icmpv6Packet,
|
||||
masscanned: &Masscanned,
|
||||
client_info: &ClientInfo,
|
||||
) -> (Option<MutableIcmpv6Packet<'b>>, Option<Ipv6Addr>) {
|
||||
debug!("receiving ICMPv6 packet: {:?}", icmp_req);
|
||||
let mut dst_ip = None;
|
||||
if icmp_req.get_icmpv6_code() != Icmpv6Codes::NoCode {
|
||||
return (None, None);
|
||||
}
|
||||
let mut icmp_repl;
|
||||
match icmp_req.get_icmpv6_type() {
|
||||
/* Answer to a neighbor solicitation packet (aka ARP for IPv6) */
|
||||
Icmpv6Types::NeighborSolicit => {
|
||||
let nd_ns_req =
|
||||
NeighborSolicitPacket::new(icmp_req.packet()).expect("error parsing ND-NS packet");
|
||||
/* Construct the answer to the NS - should be a ND-NA */
|
||||
if let Some(nd_na_repl) = nd_ns_repl(&nd_ns_req, masscanned, &client_info) {
|
||||
dst_ip = Some(nd_ns_req.get_target_addr());
|
||||
icmp_repl = MutableIcmpv6Packet::owned(nd_na_repl.packet().to_vec())
|
||||
.expect("error constructing an ICMPv6 packet");
|
||||
} else {
|
||||
return (None, None);
|
||||
}
|
||||
}
|
||||
/* Answer to an echo request packet */
|
||||
Icmpv6Types::EchoRequest => {
|
||||
/* Construct the echo reply packet */
|
||||
let echo_repl = Icmpv6 {
|
||||
icmpv6_type: Icmpv6Types::EchoReply,
|
||||
icmpv6_code: Icmpv6Codes::NoCode,
|
||||
checksum: 0,
|
||||
/* Same payload as the echo request */
|
||||
payload: icmp_req.payload().to_vec(),
|
||||
};
|
||||
icmp_repl = MutableIcmpv6Packet::owned(vec![0; Icmpv6Packet::packet_size(&echo_repl)])
|
||||
.expect("error constructing an ICMPv6 packet");
|
||||
icmp_repl.populate(&echo_repl);
|
||||
warn!("ICMPv6-Echo-Reply to ICMPv6-Echo-Request");
|
||||
}
|
||||
_ => {
|
||||
info!(
|
||||
"ICMPv6 packet not handled: {:?}",
|
||||
icmp_req.get_icmpv6_type()
|
||||
);
|
||||
return (None, None);
|
||||
}
|
||||
};
|
||||
debug!("sending ICMPv6 packet: {:?}", icmp_repl);
|
||||
(Some(icmp_repl), dst_ip)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pnet::packet::icmpv6::ndp::{MutableNeighborSolicitPacket, NeighborSolicit};
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn test_nd_na_reply() {
|
||||
let client_info = ClientInfo::new();
|
||||
let masscanned_ip_addr = Ipv6Addr::new(
|
||||
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777,
|
||||
);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V6(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
/* Legitimate solicitation */
|
||||
let ndp_ns = NeighborSolicit {
|
||||
icmpv6_type: Icmpv6Types::NeighborSolicit,
|
||||
icmpv6_code: Icmpv6Codes::NoCode,
|
||||
checksum: 0,
|
||||
reserved: 0,
|
||||
target_addr: masscanned_ip_addr,
|
||||
options: vec![],
|
||||
payload: vec![],
|
||||
};
|
||||
let mut nd_ns = MutableNeighborSolicitPacket::owned(vec![
|
||||
0;
|
||||
/* Size includes the options */
|
||||
MutableNeighborSolicitPacket::packet_size(&ndp_ns)
|
||||
//+ ndp_opt_size
|
||||
])
|
||||
.expect("error constructing ND-NS packet");
|
||||
nd_ns.populate(&ndp_ns);
|
||||
if let Some(nd_na) = nd_ns_repl(&nd_ns.to_immutable(), &masscanned, &client_info) {
|
||||
assert!(nd_na.get_icmpv6_code() == Icmpv6Codes::NoCode);
|
||||
assert!(nd_na.get_icmpv6_type() == Icmpv6Types::NeighborAdvert);
|
||||
assert!(nd_na.get_target_addr() == masscanned_ip_addr);
|
||||
assert!(nd_na.get_options().len() == 1);
|
||||
let nd_na_opt = &nd_na.get_options()[0];
|
||||
assert!(nd_na_opt.option_type == NdpOptionTypes::TargetLLAddr);
|
||||
assert!(nd_na_opt.data.len() == 6);
|
||||
assert!(nd_na_opt.length == 1);
|
||||
assert!(
|
||||
MacAddr::new(
|
||||
nd_na_opt.data[0],
|
||||
nd_na_opt.data[1],
|
||||
nd_na_opt.data[2],
|
||||
nd_na_opt.data[3],
|
||||
nd_na_opt.data[4],
|
||||
nd_na_opt.data[5]
|
||||
) == masscanned.mac
|
||||
);
|
||||
} else {
|
||||
panic!("expected a ND NA answer, got None");
|
||||
}
|
||||
/* Solicitation for another IPv6 address */
|
||||
let ndp_ns = NeighborSolicit {
|
||||
icmpv6_type: Icmpv6Types::NeighborSolicit,
|
||||
icmpv6_code: Icmpv6Codes::NoCode,
|
||||
checksum: 0,
|
||||
reserved: 0,
|
||||
target_addr: Ipv6Addr::new(
|
||||
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x8888,
|
||||
),
|
||||
options: vec![],
|
||||
payload: vec![],
|
||||
};
|
||||
nd_ns.populate(&ndp_ns);
|
||||
assert!(nd_ns_repl(&nd_ns.to_immutable(), &masscanned, &client_info) == None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_icmpv6_reply() {
|
||||
let payload = b"testpayload";
|
||||
let client_info = ClientInfo::new();
|
||||
let masscanned_ip_addr = Ipv6Addr::new(
|
||||
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777,
|
||||
);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V6(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
let mut icmpv6_echo_req = MutableIcmpv6Packet::owned(vec![
|
||||
0;
|
||||
MutableIcmpv6Packet::minimum_packet_size()
|
||||
+ payload.len()
|
||||
])
|
||||
.expect("error constructing Icmpv6 packet");
|
||||
icmpv6_echo_req.set_icmpv6_code(Icmpv6Codes::NoCode);
|
||||
icmpv6_echo_req.set_icmpv6_type(Icmpv6Types::EchoRequest);
|
||||
icmpv6_echo_req.set_payload(payload);
|
||||
if let (Some(_icmpv6_echo_repl), _) =
|
||||
repl(&icmpv6_echo_req.to_immutable(), &masscanned, &client_info)
|
||||
{
|
||||
} else {
|
||||
panic!("expected ICMPv6 echo repy - got None");
|
||||
}
|
||||
}
|
||||
}
|
20
src/layer_4/mod.rs
Normal file
20
src/layer_4/mod.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
// 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/>.
|
||||
|
||||
pub mod icmpv4;
|
||||
pub mod icmpv6;
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
218
src/layer_4/tcp.rs
Normal file
218
src/layer_4/tcp.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
|
||||
use pnet::packet::{
|
||||
tcp::{MutableTcpPacket, TcpFlags, TcpPacket},
|
||||
Packet,
|
||||
};
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::proto;
|
||||
use crate::synackcookie;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub fn repl<'a, 'b>(
|
||||
tcp_req: &'a TcpPacket,
|
||||
masscanned: &Masscanned,
|
||||
mut client_info: &mut ClientInfo,
|
||||
) -> Option<MutableTcpPacket<'b>> {
|
||||
debug!("receiving TCP packet: {:?}", tcp_req);
|
||||
/* Fill client info with source and dest. TCP port */
|
||||
client_info.port.src = Some(tcp_req.get_source());
|
||||
client_info.port.dst = Some(tcp_req.get_destination());
|
||||
/* Construct response TCP packet */
|
||||
let mut tcp_repl;
|
||||
match tcp_req.get_flags() {
|
||||
/* Answer to data */
|
||||
flags if flags & (TcpFlags::PSH | TcpFlags::ACK) == (TcpFlags::PSH | TcpFlags::ACK) => {
|
||||
/* First check the synack cookie */
|
||||
let ackno = if tcp_req.get_acknowledgement() > 0 {
|
||||
tcp_req.get_acknowledgement() - 1
|
||||
} else {
|
||||
/* underflow hack */
|
||||
0xFFFFFFFF
|
||||
};
|
||||
/* Compute syncookie */
|
||||
if let Ok(cookie) = synackcookie::generate(&client_info, &masscanned.synack_key) {
|
||||
if cookie != ackno {
|
||||
info!("PSH-ACK ignored: synackcookie not valid");
|
||||
return None;
|
||||
}
|
||||
client_info.cookie = Some(cookie);
|
||||
}
|
||||
warn!("ACK to PSH-ACK on port {}", tcp_req.get_destination());
|
||||
let payload = tcp_req.payload();
|
||||
/* Any answer to upper-layer protocol? */
|
||||
if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info) {
|
||||
tcp_repl = MutableTcpPacket::owned(
|
||||
[vec![0; MutableTcpPacket::minimum_packet_size()], repl].concat(),
|
||||
)
|
||||
.expect("error constructing a TCP packet");
|
||||
tcp_repl.set_flags(TcpFlags::ACK | TcpFlags::PSH);
|
||||
} else {
|
||||
tcp_repl =
|
||||
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()])
|
||||
.expect("error constructing a TCP packet");
|
||||
tcp_repl.set_flags(TcpFlags::ACK);
|
||||
}
|
||||
tcp_repl.set_acknowledgement(tcp_req.get_sequence() + (tcp_req.payload().len() as u32));
|
||||
tcp_repl.set_sequence(tcp_req.get_acknowledgement());
|
||||
}
|
||||
/* Answer to ACK: nothing */
|
||||
flags if flags == TcpFlags::ACK => {
|
||||
/* answer here when server needs to speak first after handshake */
|
||||
return None;
|
||||
}
|
||||
/* Answer to RST and FIN: nothing */
|
||||
flags if (flags == TcpFlags::RST || flags == (TcpFlags::FIN | TcpFlags::ACK)) => {
|
||||
return None;
|
||||
}
|
||||
/* Answer to SYN */
|
||||
flags if flags & TcpFlags::SYN == TcpFlags::SYN => {
|
||||
tcp_repl = MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()])
|
||||
.expect("error constructing a TCP packet");
|
||||
tcp_repl.set_flags(TcpFlags::ACK);
|
||||
tcp_repl.set_flags(TcpFlags::SYN | TcpFlags::ACK);
|
||||
tcp_repl.set_acknowledgement(tcp_req.get_sequence() + 1);
|
||||
/* generate a SYNACK-cookie (same as masscan) */
|
||||
tcp_repl.set_sequence(
|
||||
synackcookie::generate(&client_info, &masscanned.synack_key).unwrap(),
|
||||
);
|
||||
warn!("SYN-ACK to ACK on port {}", tcp_req.get_destination());
|
||||
}
|
||||
_ => {
|
||||
info!("TCP flag not handled: {}", tcp_req.get_flags());
|
||||
return None;
|
||||
}
|
||||
}
|
||||
/* Set source and dest. port for response packet from client info */
|
||||
/* Note: client info could have been modified by upper layers (e.g., STUN) */
|
||||
tcp_repl.set_source(client_info.port.dst.unwrap());
|
||||
tcp_repl.set_destination(client_info.port.src.unwrap());
|
||||
/* Set TCP headers */
|
||||
tcp_repl.set_data_offset(5);
|
||||
tcp_repl.set_window(65535);
|
||||
debug!("sending TCP packet: {:?}", tcp_repl);
|
||||
Some(tcp_repl)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::client::ClientInfoSrcDst;
|
||||
use pnet::util::MacAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[test]
|
||||
fn test_synack_cookie_ipv4() {
|
||||
let masscanned = Masscanned {
|
||||
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
||||
ip_addresses: None,
|
||||
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
||||
iface: None,
|
||||
};
|
||||
/* reference */
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(90, 64, 122, 203));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let mut client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
|
||||
let mut tcp_req =
|
||||
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
|
||||
tcp_req.set_source(tcp_sport);
|
||||
tcp_req.set_destination(tcp_dport);
|
||||
tcp_req.set_flags(TcpFlags::SYN);
|
||||
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
|
||||
if some_tcp_repl == None {
|
||||
assert!(false);
|
||||
return;
|
||||
}
|
||||
let tcp_repl = some_tcp_repl.unwrap();
|
||||
assert!(synackcookie::_check(
|
||||
&client_info,
|
||||
tcp_repl.get_sequence(),
|
||||
&masscanned.synack_key
|
||||
));
|
||||
assert!(cookie == tcp_repl.get_sequence());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_synack_cookie_ipv6() {
|
||||
let masscanned = Masscanned {
|
||||
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
||||
ip_addresses: None,
|
||||
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
||||
iface: None,
|
||||
};
|
||||
/* reference */
|
||||
let ip_src = IpAddr::V6(Ipv6Addr::new(234, 52, 183, 47, 184, 172, 64, 141));
|
||||
let ip_dst = IpAddr::V6(Ipv6Addr::new(25, 179, 227, 231, 53, 216, 45, 144));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let mut client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
|
||||
let mut tcp_req =
|
||||
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
|
||||
tcp_req.set_source(tcp_sport);
|
||||
tcp_req.set_destination(tcp_dport);
|
||||
tcp_req.set_flags(TcpFlags::SYN);
|
||||
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
|
||||
if some_tcp_repl == None {
|
||||
assert!(false);
|
||||
return;
|
||||
}
|
||||
let tcp_repl = some_tcp_repl.unwrap();
|
||||
assert!(synackcookie::_check(
|
||||
&client_info,
|
||||
tcp_repl.get_sequence(),
|
||||
&masscanned.synack_key
|
||||
));
|
||||
assert!(cookie == tcp_repl.get_sequence());
|
||||
}
|
||||
}
|
54
src/layer_4/udp.rs
Normal file
54
src/layer_4/udp.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
|
||||
use pnet::packet::{
|
||||
udp::{MutableUdpPacket, UdpPacket},
|
||||
Packet,
|
||||
};
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::proto;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub fn repl<'a, 'b>(
|
||||
udp_req: &'a UdpPacket,
|
||||
masscanned: &Masscanned,
|
||||
mut client_info: &mut ClientInfo,
|
||||
) -> Option<MutableUdpPacket<'b>> {
|
||||
debug!("receiving UDP packet: {:?}", udp_req);
|
||||
/* Fill client info with source and dest. UDP port */
|
||||
client_info.port.src = Some(udp_req.get_source());
|
||||
client_info.port.dst = Some(udp_req.get_destination());
|
||||
let payload = udp_req.payload();
|
||||
let mut udp_repl;
|
||||
if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info) {
|
||||
udp_repl = MutableUdpPacket::owned(
|
||||
[vec![0; MutableUdpPacket::minimum_packet_size()], repl].concat(),
|
||||
)
|
||||
.expect("error constructing a UDP packet");
|
||||
udp_repl.set_length(udp_repl.packet().len() as u16);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
/* Set source and dest. port for response packet from client info */
|
||||
/* Note: client info could have been modified by upper layers (e.g., STUN) */
|
||||
udp_repl.set_source(client_info.port.dst.unwrap());
|
||||
udp_repl.set_destination(client_info.port.src.unwrap());
|
||||
debug!("sending UDP packet: {:?}", udp_repl);
|
||||
Some(udp_repl)
|
||||
}
|
218
src/masscanned.rs
Normal file
218
src/masscanned.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
// 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/>.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate lazy_static;
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use log::*;
|
||||
use pnet::{
|
||||
datalink::{self, Channel::Ethernet, DataLinkReceiver, DataLinkSender, NetworkInterface},
|
||||
packet::{
|
||||
ethernet::{EthernetPacket, MutableEthernetPacket},
|
||||
Packet,
|
||||
},
|
||||
util::MacAddr,
|
||||
};
|
||||
|
||||
use crate::utils::IpAddrParser;
|
||||
|
||||
mod client;
|
||||
mod layer_2;
|
||||
mod layer_3;
|
||||
mod layer_4;
|
||||
mod proto;
|
||||
mod smack;
|
||||
mod synackcookie;
|
||||
mod utils;
|
||||
|
||||
const VERSION: &str = "0.2.0";
|
||||
const DEFAULT_MAC_ADDR: &str = "c0:ff:ee:c0:ff:ee";
|
||||
|
||||
pub struct Masscanned<'a> {
|
||||
pub synack_key: [u64; 2],
|
||||
pub mac: MacAddr,
|
||||
/* iface is an Option to make tests easier */
|
||||
pub iface: Option<&'a NetworkInterface>,
|
||||
pub ip_addresses: Option<&'a HashSet<IpAddr>>,
|
||||
}
|
||||
|
||||
/* Get the L2 network interface from its name */
|
||||
// TODO testme
|
||||
// TODO handle errors
|
||||
fn get_interface(iface_name: &str) -> Option<NetworkInterface> {
|
||||
let interface_names_match = |iface: &NetworkInterface| iface.name == iface_name;
|
||||
// Find the network interface with the provided name
|
||||
let interfaces = datalink::interfaces();
|
||||
interfaces.into_iter().find(interface_names_match)
|
||||
}
|
||||
|
||||
/* Get two L2 channels:
|
||||
* - one to send data
|
||||
* - one to receive data
|
||||
*/
|
||||
// TODO testme
|
||||
// TODO handle errors
|
||||
fn get_channel(
|
||||
interface: &NetworkInterface,
|
||||
) -> (
|
||||
Box<(dyn DataLinkSender + 'static)>,
|
||||
Box<(dyn DataLinkReceiver + 'static)>,
|
||||
) {
|
||||
// Create a new channel, dealing with layer 2 packets
|
||||
match datalink::channel(&interface, Default::default()) {
|
||||
Ok(Ethernet(tx, rx)) => (tx, rx),
|
||||
Ok(_) => panic!("Unhandled channel type"),
|
||||
Err(e) => panic!(
|
||||
"An error occurred when creating the datalink channel: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn reply<'a, 'b>(packet: &'a [u8], masscanned: &Masscanned) -> Option<MutableEthernetPacket<'b>> {
|
||||
let mut client_info = client::ClientInfo::new();
|
||||
let eth_req = EthernetPacket::new(packet).expect("impossible to parse Ethernet packet");
|
||||
layer_2::reply(ð_req, masscanned, &mut client_info)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
/* parse arguments from CLI */
|
||||
let args = App::new("Network responder - answer them all")
|
||||
.version(VERSION)
|
||||
.about("Network answering machine for various network protocols (L2-L3-L4 + applications)")
|
||||
.arg(
|
||||
Arg::with_name("interface")
|
||||
.short("i")
|
||||
.long("iface")
|
||||
.value_name("iface")
|
||||
.help("the interface to use for receiving/sending packets")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("mac")
|
||||
.short("a")
|
||||
.long("mac-addr")
|
||||
.help("MAC address to use in the response packets")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ip")
|
||||
.short("f")
|
||||
.long("ip-addr-file")
|
||||
.help("File with the list of IP addresses to impersonate")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbosity")
|
||||
.short("v")
|
||||
.multiple(true)
|
||||
.help("Increase message verbosity"),
|
||||
)
|
||||
.get_matches();
|
||||
let verbose = args.occurrences_of("verbosity") as usize;
|
||||
/* initialise logger */
|
||||
stderrlog::new()
|
||||
.module(module_path!())
|
||||
.verbosity(verbose)
|
||||
.init()
|
||||
.expect("error while initializing logging module");
|
||||
warn!("warn messages enabled");
|
||||
info!("info messages enabled");
|
||||
debug!("debug messages enabled");
|
||||
trace!("trace messages enabled");
|
||||
info!("Command line arguments:");
|
||||
for arg in &args.args {
|
||||
info!("....{:?}", arg);
|
||||
}
|
||||
let iface = if let Some(i) = get_interface(
|
||||
args.value_of("interface")
|
||||
.expect("error parsing iface argument"),
|
||||
) {
|
||||
i
|
||||
} else {
|
||||
error!(
|
||||
"Cannot open interface \"{}\" - are you sure it exists?",
|
||||
args.value_of("interface")
|
||||
.expect("error parsing iface argument")
|
||||
);
|
||||
return;
|
||||
};
|
||||
if iface.flags & (netdevice::IFF_UP.bits() as u32) == 0 {
|
||||
error!("specified interface is DOWN");
|
||||
return;
|
||||
}
|
||||
let mac = if let Some(m) = args.value_of("mac") {
|
||||
MacAddr::from_str(m).expect("error parsing provided MAC address")
|
||||
} else if let Some(m) = iface.mac {
|
||||
m
|
||||
} else {
|
||||
MacAddr::from_str(DEFAULT_MAC_ADDR).expect("error parsing default MAC address")
|
||||
};
|
||||
/* Parse ip address file specified */
|
||||
/* FIXME: .and_then(|path| File::open(path).map(|file| )).unwrap_or_default() ? */
|
||||
let ip_list = if let Some(ref path) = args.value_of("ip") {
|
||||
if let Ok(file) = File::open(path) {
|
||||
info!("parsing ip address file: {}", &path);
|
||||
file.extract_ip_addresses_only(None)
|
||||
} else {
|
||||
HashSet::new()
|
||||
}
|
||||
} else {
|
||||
HashSet::new()
|
||||
};
|
||||
let ip_addresses = if !ip_list.is_empty() {
|
||||
Some(&ip_list)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac,
|
||||
iface: Some(&iface),
|
||||
ip_addresses,
|
||||
};
|
||||
info!("interface......{}", masscanned.iface.unwrap().name);
|
||||
info!("mac address....{}", masscanned.mac);
|
||||
let (mut tx, mut rx) = get_channel(masscanned.iface.unwrap());
|
||||
loop {
|
||||
/* check if network interface is still up */
|
||||
if masscanned.iface.unwrap().flags & (netdevice::IFF_UP.bits() as u32) == 0 {
|
||||
error!("interface is DOWN - aborting");
|
||||
break;
|
||||
}
|
||||
match rx.next() {
|
||||
Ok(packet) => {
|
||||
if let Some(pkt_rep) = reply(packet, &masscanned) {
|
||||
tx.send_to(pkt_rep.packet(), None);
|
||||
} else {
|
||||
info!("packet not handled: {:?}", packet);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("An error occurred while reading: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
388
src/proto/http.rs
Normal file
388
src/proto/http.rs
Normal file
|
@ -0,0 +1,388 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
|
||||
use chrono::Utc;
|
||||
use lazy_static::lazy_static;
|
||||
use std::str;
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::smack::{
|
||||
Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_INSENSITIVE, UNANCHORED_STATE,
|
||||
};
|
||||
use crate::Masscanned;
|
||||
|
||||
pub const HTTP_VERBS: [&str; 9] = [
|
||||
"GET", "PUT", "POST", "HEAD", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH",
|
||||
];
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum HttpField {
|
||||
Verb,
|
||||
// Incomplete,
|
||||
// Server,
|
||||
ContentLength,
|
||||
ContentType,
|
||||
// Via,
|
||||
// Location,
|
||||
Unknown,
|
||||
NewLine,
|
||||
}
|
||||
|
||||
const HTTP_STATE_START: usize = 0;
|
||||
const HTTP_STATE_VERB: usize = 1;
|
||||
const HTTP_STATE_SPACE: usize = 2;
|
||||
const HTTP_STATE_URI: usize = 3;
|
||||
const HTTP_STATE_H: usize = 4;
|
||||
const HTTP_STATE_T1: usize = 5;
|
||||
const HTTP_STATE_T2: usize = 6;
|
||||
const HTTP_STATE_P: usize = 7;
|
||||
const HTTP_STATE_SLASH: usize = 8;
|
||||
const HTTP_STATE_VERSION_MAJ: usize = 9;
|
||||
const HTTP_STATE_VERSION_MIN: usize = 10;
|
||||
|
||||
const HTTP_STATE_FIELD_START: usize = 32;
|
||||
const HTTP_STATE_FIELD_NAME: usize = 33;
|
||||
const HTTP_STATE_FIELD_VALUE: usize = 34;
|
||||
const HTTP_STATE_CONTENT: usize = 64;
|
||||
|
||||
const HTTP_STATE_FAIL: usize = 0xFFFF;
|
||||
|
||||
struct ProtocolState {
|
||||
state: usize,
|
||||
state_bis: usize,
|
||||
smack_state: usize,
|
||||
smack_id: usize,
|
||||
http_verb: Vec<u8>,
|
||||
http_uri: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ProtocolState {
|
||||
fn new() -> Self {
|
||||
ProtocolState {
|
||||
state: HTTP_STATE_START,
|
||||
state_bis: 0,
|
||||
smack_state: BASE_STATE,
|
||||
smack_id: NO_MATCH,
|
||||
http_verb: Vec::<u8>::new(),
|
||||
http_uri: Vec::<u8>::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HTTP_PATTERN: [(&str, HttpField, SmackFlags); 4] = [
|
||||
(
|
||||
"Content-Length",
|
||||
HttpField::ContentLength,
|
||||
SmackFlags::ANCHOR_BEGIN,
|
||||
),
|
||||
(
|
||||
"Content-Type",
|
||||
HttpField::ContentType,
|
||||
SmackFlags::ANCHOR_BEGIN,
|
||||
),
|
||||
(":", HttpField::Unknown, SmackFlags::EMPTY),
|
||||
("\n", HttpField::NewLine, SmackFlags::EMPTY),
|
||||
];
|
||||
|
||||
lazy_static! {
|
||||
static ref HTTP_SMACK: Smack = http_init();
|
||||
}
|
||||
|
||||
fn http_init() -> Smack {
|
||||
let mut smack = Smack::new("http".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
for verb in HTTP_VERBS.iter() {
|
||||
smack.add_pattern(
|
||||
verb.as_bytes(),
|
||||
HttpField::Verb as usize,
|
||||
SmackFlags::ANCHOR_BEGIN,
|
||||
);
|
||||
}
|
||||
for p in HTTP_PATTERN.iter() {
|
||||
smack.add_pattern(p.0.as_bytes(), p.1 as usize, p.2);
|
||||
}
|
||||
smack.compile();
|
||||
smack
|
||||
}
|
||||
|
||||
fn http_parse(pstate: &mut ProtocolState, data: &[u8]) {
|
||||
/* RFC 2616:
|
||||
* The Request-Line begins with a method token, followed by the
|
||||
* Request-URI and the protocol version, and ending with CRLF. The
|
||||
* elements are separated by SP characters. No CR or LF is allowed
|
||||
* except in the final CRLF sequence.
|
||||
*/
|
||||
let mut i = 0;
|
||||
while i < data.len() {
|
||||
match pstate.state {
|
||||
HTTP_STATE_START => {
|
||||
pstate.state += 1;
|
||||
continue;
|
||||
}
|
||||
HTTP_STATE_VERB => {
|
||||
let i_save = i;
|
||||
pstate.smack_id = HTTP_SMACK.search_next(&mut pstate.smack_state, data, &mut i);
|
||||
pstate.http_verb.extend_from_slice(&data[i_save..i]);
|
||||
i -= 1;
|
||||
if pstate.smack_id == HttpField::Verb as usize {
|
||||
pstate.state += 1;
|
||||
} else if pstate.smack_id == NO_MATCH {
|
||||
/* if in UNANCHORED_STATE, it means we'll never get a match from now on */
|
||||
if pstate.smack_state == UNANCHORED_STATE {
|
||||
pstate.state = HTTP_STATE_FAIL;
|
||||
} else {
|
||||
/* continue getting input */
|
||||
}
|
||||
}
|
||||
}
|
||||
HTTP_STATE_SPACE => {
|
||||
if data[i] == b' ' {
|
||||
pstate.state += 1;
|
||||
} else {
|
||||
pstate.state = HTTP_STATE_FAIL;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_URI => {
|
||||
if data[i] != b' ' {
|
||||
pstate.http_uri.push(data[i]);
|
||||
} else {
|
||||
pstate.state += 1;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_H | HTTP_STATE_T1 | HTTP_STATE_T2 | HTTP_STATE_P | HTTP_STATE_SLASH => {
|
||||
if data[i] != b"HTTP/"[pstate.state - HTTP_STATE_H] {
|
||||
pstate.state = HTTP_STATE_FAIL;
|
||||
} else {
|
||||
pstate.state += 1;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_VERSION_MAJ => {
|
||||
if data[i] == b'.' {
|
||||
pstate.state += 1;
|
||||
} else if !data[i].is_ascii_digit() {
|
||||
pstate.state = HTTP_STATE_FAIL;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_VERSION_MIN => {
|
||||
/* ignore \r to be compliant with relaxed implementations of the protocole */
|
||||
if data[i] == b'\r' {
|
||||
} else if data[i] == b'\n' {
|
||||
pstate.state = HTTP_STATE_FIELD_START;
|
||||
} else if !data[i].is_ascii_digit() {
|
||||
pstate.state = HTTP_STATE_FAIL;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_FIELD_START => {
|
||||
if data[i] == b'\r' {
|
||||
} else if data[i] == b'\n' {
|
||||
pstate.state_bis = 0;
|
||||
pstate.state = HTTP_STATE_CONTENT;
|
||||
} else {
|
||||
pstate.state_bis = 0;
|
||||
pstate.state = HTTP_STATE_FIELD_NAME;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_FIELD_NAME => {
|
||||
if data[i] == b'\r' || data[i] == b'\n' {
|
||||
pstate.state = HTTP_STATE_FAIL;
|
||||
} else if data[i] == b':' {
|
||||
pstate.state = HTTP_STATE_FIELD_VALUE;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_FIELD_VALUE => {
|
||||
if data[i] == b'\r' {
|
||||
} else if data[i] == b'\n' {
|
||||
pstate.state = HTTP_STATE_FIELD_START;
|
||||
}
|
||||
}
|
||||
HTTP_STATE_FAIL => {
|
||||
return;
|
||||
}
|
||||
HTTP_STATE_CONTENT => { /* so far, do not parse content */ }
|
||||
_ => {}
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repl<'a>(
|
||||
data: &'a [u8],
|
||||
_masscanned: &Masscanned,
|
||||
_client_info: &ClientInfo,
|
||||
) -> Option<Vec<u8>> {
|
||||
debug!("receiving HTTP data");
|
||||
let mut pstate = ProtocolState::new();
|
||||
http_parse(&mut pstate, data);
|
||||
if pstate.state == HTTP_STATE_FAIL {
|
||||
debug!("data in not correctly formatted - not responding");
|
||||
debug!("pstate: {}", pstate.state);
|
||||
return None;
|
||||
}
|
||||
let content = "\
|
||||
<html>
|
||||
<head><title>401 Authorization Required</title></head>
|
||||
<body bgcolor=\"white\">
|
||||
<center><h1>401 Authorization Required</h1></center>
|
||||
<hr><center>nginx/1.14.2</center>
|
||||
</body>
|
||||
</html>
|
||||
";
|
||||
let repl_data = format!(
|
||||
"\
|
||||
HTTP/1.1 401 Unauthorized
|
||||
Server: nginx/1.14.2
|
||||
Date: {}
|
||||
Content-Type: text/html
|
||||
Content-Length: {}
|
||||
Connection: keep-alive
|
||||
WWW-Authenticate: Basic realm=\"Access to admin page\"
|
||||
|
||||
{}
|
||||
",
|
||||
Utc::now().to_rfc2822(),
|
||||
content.len(),
|
||||
content
|
||||
)
|
||||
.into_bytes();
|
||||
debug!("sending HTTP data");
|
||||
warn!(
|
||||
"HTTP/1.1 401 to {} {}",
|
||||
str::from_utf8(&pstate.http_verb).unwrap(),
|
||||
str::from_utf8(&pstate.http_uri).unwrap()
|
||||
);
|
||||
Some(repl_data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_verb() {
|
||||
/* all at once */
|
||||
for verb in HTTP_VERBS.iter() {
|
||||
let mut pstate = ProtocolState::new();
|
||||
assert!(pstate.state == HTTP_STATE_START);
|
||||
assert!(pstate.smack_state == BASE_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
http_parse(&mut pstate, &verb.as_bytes());
|
||||
assert!(pstate.state == HTTP_STATE_SPACE);
|
||||
assert!(pstate.smack_id == (HttpField::Verb as usize));
|
||||
assert!(pstate.http_verb == verb.as_bytes());
|
||||
}
|
||||
/* byte by byte */
|
||||
for verb in HTTP_VERBS.iter() {
|
||||
let mut pstate = ProtocolState::new();
|
||||
assert!(pstate.state == HTTP_STATE_START);
|
||||
assert!(pstate.smack_state == BASE_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
for i in 0..verb.len() {
|
||||
if i > 0 {
|
||||
assert!(pstate.state == HTTP_STATE_VERB);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
}
|
||||
http_parse(&mut pstate, &verb.as_bytes()[i..i + 1]);
|
||||
}
|
||||
assert!(pstate.state == HTTP_STATE_SPACE);
|
||||
assert!(pstate.smack_id == (HttpField::Verb as usize));
|
||||
assert!(pstate.http_verb == verb.as_bytes());
|
||||
}
|
||||
/* KO test: XXX */
|
||||
let mut pstate = ProtocolState::new();
|
||||
assert!(pstate.state == HTTP_STATE_START);
|
||||
assert!(pstate.smack_state == BASE_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
http_parse(&mut pstate, "XXX".as_bytes());
|
||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
/* KO test: XGET */
|
||||
let mut pstate = ProtocolState::new();
|
||||
assert!(pstate.state == HTTP_STATE_START);
|
||||
assert!(pstate.smack_state == BASE_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
http_parse(&mut pstate, "XGET".as_bytes());
|
||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
/* KO test: GEX */
|
||||
let mut pstate = ProtocolState::new();
|
||||
assert!(pstate.state == HTTP_STATE_START);
|
||||
assert!(pstate.smack_state == BASE_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
http_parse(&mut pstate, "GEX".as_bytes());
|
||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
/* KO test: GE T */
|
||||
let mut pstate = ProtocolState::new();
|
||||
assert!(pstate.state == HTTP_STATE_START);
|
||||
assert!(pstate.smack_state == BASE_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
http_parse(&mut pstate, "GE T".as_bytes());
|
||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||
assert!(pstate.smack_id == NO_MATCH);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_request_line() {
|
||||
let mut pstate = ProtocolState::new();
|
||||
let data = "GET /index.php HTTP/1.1\r\n".as_bytes();
|
||||
for i in 0..data.len() {
|
||||
http_parse(&mut pstate, &data[i..i + 1]);
|
||||
if i < 2 {
|
||||
assert!(pstate.state == HTTP_STATE_VERB);
|
||||
} else if i == 2 {
|
||||
assert!(pstate.state == HTTP_STATE_SPACE);
|
||||
} else if 3 <= i && i <= 13 {
|
||||
assert!(pstate.state == HTTP_STATE_URI);
|
||||
} else if 14 <= i && i <= 19 {
|
||||
assert!(pstate.state == HTTP_STATE_H + (i - 14));
|
||||
} else if i == 20 {
|
||||
assert!(pstate.state == HTTP_STATE_VERSION_MAJ);
|
||||
} else if 21 <= i && i <= 23 {
|
||||
assert!(pstate.state == HTTP_STATE_VERSION_MIN);
|
||||
} else if i == 24 {
|
||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_request_field() {
|
||||
let mut pstate = ProtocolState::new();
|
||||
let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
|
||||
http_parse(&mut pstate, req);
|
||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||
let field = b"Content-Length";
|
||||
http_parse(&mut pstate, field);
|
||||
assert!(pstate.state == HTTP_STATE_FIELD_NAME);
|
||||
let dot = b": ";
|
||||
http_parse(&mut pstate, dot);
|
||||
assert!(pstate.state == HTTP_STATE_FIELD_VALUE);
|
||||
let value = b": 0\r\n";
|
||||
http_parse(&mut pstate, value);
|
||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_request_no_field() {
|
||||
let mut pstate = ProtocolState::new();
|
||||
let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
|
||||
http_parse(&mut pstate, req);
|
||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||
let crlf = "\r\n".as_bytes();
|
||||
http_parse(&mut pstate, crlf);
|
||||
assert!(pstate.state == HTTP_STATE_CONTENT);
|
||||
}
|
239
src/proto/mod.rs
Normal file
239
src/proto/mod.rs
Normal file
|
@ -0,0 +1,239 @@
|
|||
// 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/>.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use log::*;
|
||||
use pnet::packet::ip::IpNextHeaderProtocols;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::smack::{Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_SENSITIVE};
|
||||
use crate::Masscanned;
|
||||
|
||||
mod http;
|
||||
use http::HTTP_VERBS;
|
||||
|
||||
mod stun;
|
||||
use stun::{STUN_PATTERN_CHANGE_REQUEST, STUN_PATTERN_EMPTY, STUN_PATTERN_MAGIC};
|
||||
|
||||
mod ssh;
|
||||
use ssh::SSH_PATTERN_CLIENT_PROTOCOL;
|
||||
|
||||
const PROTO_HTTP: usize = 1;
|
||||
const PROTO_STUN: usize = 2;
|
||||
const PROTO_SSH: usize = 3;
|
||||
|
||||
struct TCPControlBlock {
|
||||
proto_state: usize,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref PROTO_SMACK: Smack = proto_init();
|
||||
static ref CONTABLE: Mutex<HashMap<u32, TCPControlBlock>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
fn proto_init() -> Smack {
|
||||
let mut smack = Smack::new("proto".to_string(), SMACK_CASE_SENSITIVE);
|
||||
/* HTTP markers */
|
||||
for (_, v) in HTTP_VERBS.iter().enumerate() {
|
||||
smack.add_pattern(
|
||||
format!("{} /", v).as_bytes(),
|
||||
PROTO_HTTP,
|
||||
SmackFlags::ANCHOR_BEGIN,
|
||||
);
|
||||
}
|
||||
smack.add_pattern(
|
||||
STUN_PATTERN_MAGIC,
|
||||
PROTO_STUN,
|
||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||
);
|
||||
smack.add_pattern(
|
||||
STUN_PATTERN_EMPTY,
|
||||
PROTO_STUN,
|
||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::ANCHOR_END | SmackFlags::WILDCARDS,
|
||||
);
|
||||
smack.add_pattern(
|
||||
STUN_PATTERN_CHANGE_REQUEST,
|
||||
PROTO_STUN,
|
||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::ANCHOR_END | SmackFlags::WILDCARDS,
|
||||
);
|
||||
smack.add_pattern(
|
||||
SSH_PATTERN_CLIENT_PROTOCOL,
|
||||
PROTO_SSH,
|
||||
SmackFlags::ANCHOR_BEGIN,
|
||||
);
|
||||
smack.compile();
|
||||
smack
|
||||
}
|
||||
|
||||
pub fn repl<'a>(
|
||||
data: &'a [u8],
|
||||
masscanned: &Masscanned,
|
||||
mut client_info: &mut ClientInfo,
|
||||
) -> Option<Vec<u8>> {
|
||||
debug!("packet payload: {:?}", data);
|
||||
let mut id;
|
||||
if client_info.transport == Some(IpNextHeaderProtocols::Tcp) && client_info.cookie == None {
|
||||
error!("Unexpected empty cookie");
|
||||
return None;
|
||||
} else if client_info.cookie != None {
|
||||
/* proto over TCP */
|
||||
let cookie = client_info.cookie.unwrap();
|
||||
let mut ct = CONTABLE.lock().unwrap();
|
||||
if !ct.contains_key(&cookie) {
|
||||
ct.insert(
|
||||
cookie,
|
||||
TCPControlBlock {
|
||||
proto_state: BASE_STATE,
|
||||
},
|
||||
);
|
||||
}
|
||||
let mut i = 0;
|
||||
let mut tcb = ct.get_mut(&cookie).unwrap();
|
||||
let mut state = tcb.proto_state;
|
||||
id = PROTO_SMACK.search_next(&mut state, &data.to_vec(), &mut i);
|
||||
tcb.proto_state = state;
|
||||
} else {
|
||||
/* proto over else (e.g., UDP) */
|
||||
let mut i = 0;
|
||||
let mut state = BASE_STATE;
|
||||
id = PROTO_SMACK.search_next(&mut state, &data.to_vec(), &mut i);
|
||||
/* because we are not over TCP, we can afford to assume end of pattern */
|
||||
if id == NO_MATCH {
|
||||
id = PROTO_SMACK.search_next_end(&mut state);
|
||||
}
|
||||
}
|
||||
/* proto over else (e.g., UDP) */
|
||||
if id == PROTO_HTTP {
|
||||
return http::repl(data, masscanned, client_info);
|
||||
} else if id == PROTO_STUN {
|
||||
return stun::repl(data, masscanned, &mut client_info);
|
||||
} else if id == PROTO_SSH {
|
||||
return ssh::repl(data, masscanned, &mut client_info);
|
||||
} else {
|
||||
debug!("id: {}", id);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn test_proto_dispatch_stun() {
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||
client_info.port.src = Some(65000);
|
||||
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
/***** TEST STUN - MAGIC *****/
|
||||
/* test payload is:
|
||||
* - bind request: 0x0001
|
||||
* - length: 0x0000
|
||||
* - magic cookie: 0x2112a442
|
||||
* - message: empty
|
||||
*/
|
||||
let payload =
|
||||
b"\x00\x01\x00\x00\x21\x12\xa4\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got nothing");
|
||||
};
|
||||
/***** TEST STUN - EMPTY *****/
|
||||
/* test payload is:
|
||||
* - bind request: 0x0001
|
||||
* - length: 0x0000
|
||||
* - magic cookie: 0xaabbccdd
|
||||
* - message: empty
|
||||
*/
|
||||
let payload =
|
||||
b"\x00\x01\x00\x00\xaa\xbb\xcc\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got nothing");
|
||||
};
|
||||
/***** TEST STUN - CHANGE_REQUEST *****/
|
||||
/* test payload is:
|
||||
* - bind request: 0x0001
|
||||
* - length: 0x0008
|
||||
* - message: change request
|
||||
*/
|
||||
let payload =
|
||||
b"\x00\x01\x00\x08\x01\xdb\xd4]4\x9f\xe2RQ\x19\x05,\x93\x14f4\x00\x03\x00\x04\x00\x00\x00\x00";
|
||||
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got nothing");
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proto_dispatch_ssh() {
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||
client_info.port.src = Some(65000);
|
||||
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
/***** TEST SSH *****/
|
||||
let payloads = [
|
||||
"SSH-2.0-PUTTY",
|
||||
"SSH-2.0-Go",
|
||||
"SSH-2.0-libssh2_1.4.3",
|
||||
"SSH-2.0-PuTTY",
|
||||
"SSH-2.0-AsyncSSH_2.1.0",
|
||||
"SSH-2.0-libssh2_1.9.0",
|
||||
"SSH-2.0-libssh2_1.7.0",
|
||||
"SSH-2.0-8.35 FlowSsh: FlowSshNet_SftpStress54.38.116.473",
|
||||
"SSH-2.0-libssh_0.9.5",
|
||||
"SSH-2.0-OpenSSH_6.7p1 Raspbian-5+deb8u3",
|
||||
];
|
||||
for payload in payloads.iter() {
|
||||
let _ssh_resp = if let Some(r) = repl(payload.as_bytes(), &masscanned, &mut client_info)
|
||||
{
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got nothing");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
36
src/proto/ssh.rs
Normal file
36
src/proto/ssh.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
|
||||
use std::str;
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::Masscanned;
|
||||
|
||||
pub const SSH_PATTERN_CLIENT_PROTOCOL: &[u8; 7] = b"SSH-2.0";
|
||||
|
||||
pub fn repl<'a>(
|
||||
data: &'a [u8],
|
||||
_masscanned: &Masscanned,
|
||||
mut _client_info: &mut ClientInfo,
|
||||
) -> Option<Vec<u8>> {
|
||||
debug!("receiving SSH data");
|
||||
let repl_data = b"SSH-2.0-1\r\n".to_vec();
|
||||
debug!("sending SSH answer");
|
||||
warn!("SSH server banner to {}", str::from_utf8(&data).unwrap().trim_end());
|
||||
return Some(repl_data);
|
||||
}
|
793
src/proto/stun.rs
Normal file
793
src/proto/stun.rs
Normal file
|
@ -0,0 +1,793 @@
|
|||
// 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/>.
|
||||
|
||||
use log::*;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use std::io;
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::Masscanned;
|
||||
|
||||
/* RFC 5389: The magic cookie field MUST contain the fixed value 0x2112A442 in
|
||||
network byte order. */
|
||||
/* Note: disabled for now due to a « bug » in smack */
|
||||
pub const STUN_PATTERN_MAGIC: &[u8; 8] = b"\x00\x01**\x21\x12\xa4\x42";
|
||||
pub const STUN_PATTERN_EMPTY: &[u8; 20] = b"\x00\x01\x00\x00****************";
|
||||
/* RFC 3489: support without cookie */
|
||||
pub const STUN_PATTERN_CHANGE_REQUEST: &[u8; 28] =
|
||||
b"\x00\x01\x00\x08****************\x00\x03\x00\x04\x00\x00\x00*";
|
||||
pub const _STUN_MAGIC: u32 = 0x2112a442;
|
||||
|
||||
pub const STUN_CLASS_REQUEST: u8 = 0b00;
|
||||
#[allow(dead_code)]
|
||||
pub const STUN_CLASS_INDICATE: u8 = 0b01;
|
||||
pub const STUN_CLASS_SUCCESS_RESPONSE: u8 = 0b10;
|
||||
#[allow(dead_code)]
|
||||
pub const STUN_CLASS_FAILURE_RESPONSE: u8 = 0b11;
|
||||
|
||||
pub const STUN_ATTR_MAPPED_ADDRESS: u16 = 0x0001;
|
||||
pub const STUN_ATTR_CHANGE_REQUEST: u16 = 0x0003;
|
||||
|
||||
pub const STUN_METHOD_BINDING: u16 = 0x001;
|
||||
|
||||
pub const STUN_PROTOCOL_FAMILY_IPV4: u8 = 0x01;
|
||||
pub const STUN_PROTOCOL_FAMILY_IPV6: u8 = 0x02;
|
||||
|
||||
pub const STUN_CHANGE_REQUEST_MASK_IP: u32 = 0x00000004;
|
||||
pub const STUN_CHANGE_REQUEST_MASK_PORT: u32 = 0x00000002;
|
||||
|
||||
struct StunGenericAttribute {
|
||||
type_: u16,
|
||||
length: u16,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl StunGenericAttribute {
|
||||
#[allow(dead_code)]
|
||||
fn new(data: &[u8]) -> Result<Self, io::Error> {
|
||||
if data.len() < 4 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"not enough data",
|
||||
));
|
||||
}
|
||||
let type_ = BigEndian::read_u16(&data[0..2]);
|
||||
let length = BigEndian::read_u16(&data[2..4]);
|
||||
if data.len() < 4 + length as usize {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"not enough data",
|
||||
));
|
||||
}
|
||||
let data = data[4..4 + length as usize].to_vec();
|
||||
Ok(StunGenericAttribute {
|
||||
type_,
|
||||
length,
|
||||
data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for &StunGenericAttribute {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut v = Vec::<u8>::new();
|
||||
v.append(&mut self.type_.to_be_bytes().to_vec());
|
||||
v.append(&mut self.length.to_be_bytes().to_vec());
|
||||
v.append(&mut self.data.clone());
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
struct StunChangeRequestAttribute {
|
||||
type_: u16,
|
||||
length: u16,
|
||||
change_ip: bool,
|
||||
change_port: bool,
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for &StunChangeRequestAttribute {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut v = Vec::<u8>::new();
|
||||
v.append(&mut self.type_.to_be_bytes().to_vec());
|
||||
v.append(&mut self.length.to_be_bytes().to_vec());
|
||||
let mut flags: u32 = 0;
|
||||
if self.change_ip {
|
||||
flags |= STUN_CHANGE_REQUEST_MASK_IP;
|
||||
}
|
||||
if self.change_port {
|
||||
flags |= STUN_CHANGE_REQUEST_MASK_PORT;
|
||||
}
|
||||
v.append(&mut flags.to_be_bytes().to_vec());
|
||||
v
|
||||
}
|
||||
}
|
||||
struct StunMappedAddressAttribute {
|
||||
type_: u16,
|
||||
length: u16,
|
||||
reserved: u8,
|
||||
protocol_family: u8,
|
||||
port: u16,
|
||||
ip: IpAddr,
|
||||
}
|
||||
|
||||
impl StunMappedAddressAttribute {
|
||||
fn new(ip: IpAddr, port: u16) -> Self {
|
||||
StunMappedAddressAttribute {
|
||||
type_: STUN_ATTR_MAPPED_ADDRESS,
|
||||
length: 4 + if let IpAddr::V4(_) = ip { 4 } else { 16 },
|
||||
reserved: 0,
|
||||
protocol_family: if let IpAddr::V4(_) = ip {
|
||||
STUN_PROTOCOL_FAMILY_IPV4
|
||||
} else {
|
||||
STUN_PROTOCOL_FAMILY_IPV6
|
||||
},
|
||||
port: port,
|
||||
ip: ip,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for &StunMappedAddressAttribute {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut v = Vec::<u8>::new();
|
||||
v.append(&mut self.type_.to_be_bytes().to_vec());
|
||||
v.append(&mut self.length.to_be_bytes().to_vec());
|
||||
v.push(self.reserved);
|
||||
v.push(self.protocol_family);
|
||||
v.push(((self.port & 0xFF00) >> 8).try_into().unwrap());
|
||||
v.push((self.port & 0x00FF).try_into().unwrap());
|
||||
let mut ip = if let IpAddr::V4(ip) = self.ip {
|
||||
ip.octets().to_vec()
|
||||
} else if let IpAddr::V6(ip) = self.ip {
|
||||
ip.octets().to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
v.append(&mut ip);
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
enum StunAttribute {
|
||||
MappedAddress(StunMappedAddressAttribute),
|
||||
ChangeRequest(StunChangeRequestAttribute),
|
||||
Generic(StunGenericAttribute),
|
||||
}
|
||||
|
||||
impl StunAttribute {
|
||||
fn len(&self) -> u16 {
|
||||
match self {
|
||||
StunAttribute::MappedAddress(s) => s.length,
|
||||
StunAttribute::ChangeRequest(s) => s.length,
|
||||
StunAttribute::Generic(s) => s.length,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn type_(&self) -> u16 {
|
||||
match self {
|
||||
StunAttribute::MappedAddress(s) => s.type_,
|
||||
StunAttribute::ChangeRequest(s) => s.type_,
|
||||
StunAttribute::Generic(s) => s.type_,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for StunAttribute {
|
||||
fn from(v: Vec<u8>) -> Self {
|
||||
if v.len() < 4 {
|
||||
panic!("not enough data");
|
||||
}
|
||||
let type_ = BigEndian::read_u16(&v[0..2]);
|
||||
let length = BigEndian::read_u16(&v[2..4]);
|
||||
if v.len() < 4 + length as usize {
|
||||
panic!("not enough data");
|
||||
}
|
||||
match type_ {
|
||||
STUN_ATTR_MAPPED_ADDRESS => {
|
||||
let reserved = v[4];
|
||||
let protocol_family = v[5];
|
||||
let port = BigEndian::read_u16(&v[6..8]);
|
||||
StunAttribute::MappedAddress(StunMappedAddressAttribute {
|
||||
type_,
|
||||
length,
|
||||
reserved,
|
||||
protocol_family,
|
||||
port,
|
||||
ip: if protocol_family == STUN_PROTOCOL_FAMILY_IPV4 {
|
||||
IpAddr::V4(Ipv4Addr::new(v[8], v[9], v[10], v[11]))
|
||||
} else if protocol_family == STUN_PROTOCOL_FAMILY_IPV6 {
|
||||
IpAddr::V6(Ipv6Addr::new(
|
||||
BigEndian::read_u16(&v[8..10]),
|
||||
BigEndian::read_u16(&v[10..12]),
|
||||
BigEndian::read_u16(&v[12..14]),
|
||||
BigEndian::read_u16(&v[14..16]),
|
||||
BigEndian::read_u16(&v[16..18]),
|
||||
BigEndian::read_u16(&v[18..20]),
|
||||
BigEndian::read_u16(&v[20..22]),
|
||||
BigEndian::read_u16(&v[22..24]),
|
||||
))
|
||||
} else {
|
||||
panic!("unexpected protocol family");
|
||||
},
|
||||
})
|
||||
}
|
||||
STUN_ATTR_CHANGE_REQUEST => StunAttribute::ChangeRequest(StunChangeRequestAttribute {
|
||||
type_,
|
||||
length,
|
||||
change_ip: (BigEndian::read_u32(&v[4..8]) & STUN_CHANGE_REQUEST_MASK_IP)
|
||||
== STUN_CHANGE_REQUEST_MASK_IP,
|
||||
change_port: (BigEndian::read_u32(&v[4..8]) & STUN_CHANGE_REQUEST_MASK_PORT)
|
||||
== STUN_CHANGE_REQUEST_MASK_PORT,
|
||||
}),
|
||||
_ => StunAttribute::Generic(StunGenericAttribute {
|
||||
type_,
|
||||
length,
|
||||
data: v[4..].to_vec(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for &StunAttribute {
|
||||
fn into(self) -> Vec<u8> {
|
||||
match self {
|
||||
StunAttribute::Generic(s) => s.into(),
|
||||
StunAttribute::MappedAddress(s) => s.into(),
|
||||
StunAttribute::ChangeRequest(s) => s.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
struct StunPacket {
|
||||
class: u8,
|
||||
method: u16,
|
||||
length: u16,
|
||||
magic: u32,
|
||||
id: u128,
|
||||
data: Vec<u8>,
|
||||
attributes: Vec<StunAttribute>,
|
||||
}
|
||||
|
||||
impl StunPacket {
|
||||
fn new(data: &[u8]) -> Result<Self, io::Error> {
|
||||
if data.len() < 20 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"not enough data",
|
||||
));
|
||||
}
|
||||
let class: u8 = ((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4);
|
||||
let method: u16 = (((data[0] & 0b00111110) << 7) as u16) | ((data[1] & 0b11101111) as u16);
|
||||
let length: u16 = BigEndian::read_u16(&data[2..4]);
|
||||
let magic: u32 = BigEndian::read_u32(&data[4..8]);
|
||||
let id: u128 = ((BigEndian::read_u64(&data[8..16]) as u128) << 32)
|
||||
| (BigEndian::read_u32(&data[16..20]) as u128);
|
||||
if data.len() < 20 + length as usize {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"not enough data",
|
||||
));
|
||||
}
|
||||
let data: Vec<u8> = data[20..(20 + length) as usize].to_vec();
|
||||
let mut stun = StunPacket {
|
||||
class,
|
||||
method,
|
||||
length,
|
||||
magic,
|
||||
id,
|
||||
data,
|
||||
attributes: Vec::<StunAttribute>::new(),
|
||||
};
|
||||
stun.attributes = stun.get_attributes();
|
||||
Ok(stun)
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
StunPacket {
|
||||
class: 0,
|
||||
method: 0,
|
||||
length: 0,
|
||||
magic: 0,
|
||||
id: 0,
|
||||
data: Vec::new(),
|
||||
attributes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_attributes(&self) -> Vec<StunAttribute> {
|
||||
let mut i = 0;
|
||||
let mut attributes = Vec::<StunAttribute>::new();
|
||||
while i + 4 < self.data.len() {
|
||||
let attr = StunAttribute::from(self.data[i..].to_vec());
|
||||
i += 4 + attr.len() as usize;
|
||||
attributes.push(attr);
|
||||
}
|
||||
attributes
|
||||
}
|
||||
|
||||
fn set_length(&mut self) {
|
||||
self.length = 0;
|
||||
for attr in &self.attributes {
|
||||
self.length += 4 + attr.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for StunPacket {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut v = Vec::<u8>::new();
|
||||
// first cocktail with class and method bits
|
||||
v.push(
|
||||
TryInto::<u8>::try_into((self.method >> 7) & 0b00111110).unwrap()
|
||||
| TryInto::<u8>::try_into((self.class & 0b10) >> 1).unwrap(),
|
||||
);
|
||||
// second cocktail with class and method bits
|
||||
v.push(
|
||||
TryInto::<u8>::try_into((self.method & 0b01110000) << 1).unwrap()
|
||||
| TryInto::<u8>::try_into((self.class & 0b01) << 4).unwrap()
|
||||
| TryInto::<u8>::try_into(self.method & 0b00001111).unwrap(),
|
||||
);
|
||||
v.append(&mut self.length.to_be_bytes().to_vec());
|
||||
v.append(&mut self.magic.to_be_bytes().to_vec());
|
||||
v.append(&mut self.id.to_be_bytes()[4..].to_vec());
|
||||
for attr in &self.attributes {
|
||||
v.append(&mut attr.into());
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
struct StunPacket {
|
||||
class: u8,
|
||||
method: u16,
|
||||
length: u16,
|
||||
id: u128,
|
||||
data: Vec<u8>,
|
||||
attributes: Vec<StunAttribute>,
|
||||
}
|
||||
|
||||
impl StunPacket {
|
||||
fn new(data: &[u8]) -> Result<Self, io::Error> {
|
||||
if data.len() < 20 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"not enough data",
|
||||
));
|
||||
}
|
||||
let class: u8 = ((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4);
|
||||
let method: u16 = (((data[0] & 0b00111110) << 7) as u16) | ((data[1] & 0b11101111) as u16);
|
||||
let length: u16 = BigEndian::read_u16(&data[2..4]);
|
||||
let id: u128 = BigEndian::read_u128(&data[4..20]);
|
||||
if data.len() < 20 + length as usize {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"not enough data",
|
||||
));
|
||||
}
|
||||
let data: Vec<u8> = data[20..(20 + length) as usize].to_vec();
|
||||
let mut stun = StunPacket {
|
||||
class,
|
||||
method,
|
||||
length,
|
||||
id,
|
||||
data,
|
||||
attributes: Vec::<StunAttribute>::new(),
|
||||
};
|
||||
stun.attributes = stun.get_attributes();
|
||||
Ok(stun)
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
StunPacket {
|
||||
class: 0,
|
||||
method: 0,
|
||||
length: 0,
|
||||
id: 0,
|
||||
data: Vec::new(),
|
||||
attributes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_attributes(&self) -> Vec<StunAttribute> {
|
||||
let mut i = 0;
|
||||
let mut attributes = Vec::<StunAttribute>::new();
|
||||
while i + 4 < self.data.len() {
|
||||
let attr = StunAttribute::from(self.data[i..].to_vec());
|
||||
i += 4 + attr.len() as usize;
|
||||
attributes.push(attr);
|
||||
}
|
||||
attributes
|
||||
}
|
||||
|
||||
fn set_length(&mut self) {
|
||||
self.length = 0;
|
||||
for attr in &self.attributes {
|
||||
self.length += 4 + attr.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for StunPacket {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut v = Vec::<u8>::new();
|
||||
// first cocktail with class and method bits
|
||||
v.push(
|
||||
TryInto::<u8>::try_into((self.method >> 7) & 0b00111110).unwrap()
|
||||
| TryInto::<u8>::try_into((self.class & 0b10) >> 1).unwrap(),
|
||||
);
|
||||
// second cocktail with class and method bits
|
||||
v.push(
|
||||
TryInto::<u8>::try_into((self.method & 0b01110000) << 1).unwrap()
|
||||
| TryInto::<u8>::try_into((self.class & 0b01) << 4).unwrap()
|
||||
| TryInto::<u8>::try_into(self.method & 0b00001111).unwrap(),
|
||||
);
|
||||
v.append(&mut self.length.to_be_bytes().to_vec());
|
||||
v.append(&mut self.id.to_be_bytes().to_vec());
|
||||
for attr in &self.attributes {
|
||||
v.append(&mut attr.into());
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn repl<'a>(
|
||||
data: &'a [u8],
|
||||
_masscanned: &Masscanned,
|
||||
client_info: ClientInfo,
|
||||
) -> Option<Vec<u8>> {
|
||||
debug!("receiving STUN data");
|
||||
let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) {
|
||||
s
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if stun_req.class != STUN_CLASS_REQUEST {
|
||||
info!(
|
||||
"STUN packet not handled (class unknown: 0b{:b})",
|
||||
stun_req.class
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if stun_req.method != STUN_METHOD_BINDING {
|
||||
info!(
|
||||
"STUN packet not handled (method unknown: 0x{:03x})",
|
||||
stun_req.method
|
||||
);
|
||||
return None;
|
||||
}
|
||||
/*
|
||||
* To be compatible with RFC3489: ignore magic
|
||||
if stun_req.magic != STUN_MAGIC {
|
||||
info!(
|
||||
"STUN packet not handled (magic unknown: 0x{:04x})",
|
||||
stun_req.magic
|
||||
);
|
||||
return None;
|
||||
}
|
||||
*/
|
||||
if client_info.ip.src == None {
|
||||
error!("STUN packet not handled (expected client ip address not found)");
|
||||
return None;
|
||||
}
|
||||
if client_info.port.src == None {
|
||||
error!("STUN packet not handled (expected client port address not found)");
|
||||
return None;
|
||||
}
|
||||
let mut stun_resp: StunPacket = StunPacket::empty();
|
||||
stun_resp.class = STUN_CLASS_SUCCESS_RESPONSE;
|
||||
stun_resp.method = STUN_METHOD_BINDING;
|
||||
stun_resp.id = stun_req.id;
|
||||
stun_resp.attributes = Vec::<StunAttribute>::new();
|
||||
stun_resp.attributes.push(StunAttribute::MappedAddress(
|
||||
StunMappedAddressAttribute::new(client_info.ip.src.unwrap(), client_info.port.src.unwrap()),
|
||||
));
|
||||
stun_resp.set_length();
|
||||
return Some(stun_resp.into());
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn repl<'a>(
|
||||
data: &'a [u8],
|
||||
_masscanned: &Masscanned,
|
||||
mut client_info: &mut ClientInfo,
|
||||
) -> Option<Vec<u8>> {
|
||||
debug!("receiving STUN data");
|
||||
let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) {
|
||||
s
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if stun_req.class != STUN_CLASS_REQUEST {
|
||||
info!(
|
||||
"STUN packet not handled (class unknown: 0b{:b})",
|
||||
stun_req.class
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if stun_req.method != STUN_METHOD_BINDING {
|
||||
info!(
|
||||
"STUN packet not handled (method unknown: 0x{:03x})",
|
||||
stun_req.method
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if client_info.ip.src == None {
|
||||
error!("STUN packet not handled (expected client ip address not found)");
|
||||
return None;
|
||||
}
|
||||
if client_info.port.src == None {
|
||||
error!("STUN packet not handled (expected client port address not found)");
|
||||
return None;
|
||||
}
|
||||
/* Change client_info if CHANGE_REQUEST was set by client */
|
||||
for attr in &stun_req.attributes {
|
||||
if let StunAttribute::ChangeRequest(a) = attr {
|
||||
if a.change_ip {}
|
||||
if a.change_port {
|
||||
client_info.port.dst = Some(client_info.port.dst.unwrap().wrapping_add(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut stun_resp: StunPacket = StunPacket::empty();
|
||||
stun_resp.class = STUN_CLASS_SUCCESS_RESPONSE;
|
||||
stun_resp.method = STUN_METHOD_BINDING;
|
||||
stun_resp.id = stun_req.id;
|
||||
stun_resp.attributes = Vec::<StunAttribute>::new();
|
||||
stun_resp.attributes.push(StunAttribute::MappedAddress(
|
||||
StunMappedAddressAttribute::new(client_info.ip.src.unwrap(), client_info.port.src.unwrap()),
|
||||
));
|
||||
stun_resp.set_length();
|
||||
debug!("sending STUN answer");
|
||||
return Some(stun_resp.into());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pnet::util::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn test_proto_stun_ipv4() {
|
||||
/* test payload is:
|
||||
* - bind request: 0x0001
|
||||
* - length: 0x0000
|
||||
* - magic cookie: 0x2112a442
|
||||
* - id: 0xaabbccddeeffffeeddccbbaa
|
||||
* - message: empty
|
||||
*/
|
||||
let payload =
|
||||
b"\x00\x01\x00\x00\x21\x12\xa4\x42\xaa\xbb\xcc\xdd\xee\xff\xff\xee\xdd\xcc\xbb\xaa";
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||
client_info.port.src = Some(55000);
|
||||
client_info.port.dst = Some(65000);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got None");
|
||||
};
|
||||
let stun_resp = StunPacket::new(&payload_resp).unwrap();
|
||||
assert!(stun_resp.class == STUN_CLASS_SUCCESS_RESPONSE);
|
||||
assert!(stun_resp.method == STUN_METHOD_BINDING);
|
||||
assert!(
|
||||
stun_resp.id
|
||||
== BigEndian::read_u128(
|
||||
b"\x21\x12\xa4\x42\xaa\xbb\xcc\xdd\xee\xff\xff\xee\xdd\xcc\xbb\xaa"
|
||||
)
|
||||
);
|
||||
assert!(stun_resp.attributes.len() == 1);
|
||||
if let StunAttribute::MappedAddress(attr) = &stun_resp.attributes[0] {
|
||||
assert!(attr.type_ == STUN_ATTR_MAPPED_ADDRESS);
|
||||
assert!(attr.length == 8);
|
||||
assert!(attr.reserved == 0);
|
||||
assert!(attr.protocol_family == STUN_PROTOCOL_FAMILY_IPV4);
|
||||
assert!(attr.port == client_info.port.src.unwrap());
|
||||
assert!(attr.ip == client_info.ip.src.unwrap());
|
||||
} else {
|
||||
panic!("expected MappedAddress attribute");
|
||||
}
|
||||
/* Check that client_info was not modified */
|
||||
assert!(client_info.ip.src == Some(IpAddr::V4(test_ip_addr)));
|
||||
assert!(client_info.ip.dst == Some(IpAddr::V4(masscanned_ip_addr)));
|
||||
assert!(client_info.port.src == Some(55000));
|
||||
assert!(client_info.port.dst == Some(65000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proto_stun_ipv6() {
|
||||
/* test payload is:
|
||||
* - bind request: 0x0001
|
||||
* - length: 0x0000
|
||||
* - magic cookie: 0x2112a442
|
||||
* - id: 0xaabbccddeeffffeeddccbbaa
|
||||
* - message: empty
|
||||
*/
|
||||
let payload =
|
||||
b"\x00\x01\x00\x00\x21\x12\xa4\x42\xaa\xbb\xcc\xdd\xee\xff\xff\xee\xdd\xcc\xbb\xaa";
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv6Addr::new(
|
||||
0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
|
||||
);
|
||||
let masscanned_ip_addr = Ipv6Addr::new(
|
||||
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777,
|
||||
);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V6(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
client_info.ip.src = Some(IpAddr::V6(test_ip_addr));
|
||||
client_info.ip.dst = Some(IpAddr::V6(masscanned_ip_addr));
|
||||
client_info.port.src = Some(55000);
|
||||
client_info.port.dst = Some(65000);
|
||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got None");
|
||||
};
|
||||
let stun_resp = StunPacket::new(&payload_resp).unwrap();
|
||||
assert!(stun_resp.class == STUN_CLASS_SUCCESS_RESPONSE);
|
||||
assert!(stun_resp.method == STUN_METHOD_BINDING);
|
||||
assert!(
|
||||
stun_resp.id
|
||||
== BigEndian::read_u128(
|
||||
b"\x21\x12\xa4\x42\xaa\xbb\xcc\xdd\xee\xff\xff\xee\xdd\xcc\xbb\xaa"
|
||||
)
|
||||
);
|
||||
assert!(stun_resp.attributes.len() == 1);
|
||||
if let StunAttribute::MappedAddress(attr) = &stun_resp.attributes[0] {
|
||||
assert!(attr.type_ == STUN_ATTR_MAPPED_ADDRESS);
|
||||
assert!(attr.length == 20);
|
||||
assert!(attr.reserved == 0);
|
||||
assert!(attr.protocol_family == STUN_PROTOCOL_FAMILY_IPV6);
|
||||
assert!(attr.port == client_info.port.src.unwrap());
|
||||
assert!(attr.ip == client_info.ip.src.unwrap());
|
||||
} else {
|
||||
panic!("expected MappedAddress attribute");
|
||||
}
|
||||
/* Check that client_info was not modified */
|
||||
assert!(client_info.ip.src == Some(IpAddr::V6(test_ip_addr)));
|
||||
assert!(client_info.ip.dst == Some(IpAddr::V6(masscanned_ip_addr)));
|
||||
assert!(client_info.port.src == Some(55000));
|
||||
assert!(client_info.port.dst == Some(65000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_change_request_port() {
|
||||
let payload = b"\x00\x01\x00\x08\x03\xa3\xb9FM\xd8\xebu\xe1\x94\x81GB\x93\x84\\\x00\x03\x00\x04\x00\x00\x00\x02";
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||
client_info.port.src = Some(55000);
|
||||
client_info.port.dst = Some(65000);
|
||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got None");
|
||||
};
|
||||
let stun_resp = StunPacket::new(&payload_resp).unwrap();
|
||||
assert!(stun_resp.class == STUN_CLASS_SUCCESS_RESPONSE);
|
||||
assert!(stun_resp.method == STUN_METHOD_BINDING);
|
||||
assert!(
|
||||
stun_resp.id
|
||||
== BigEndian::read_u128(b"\x03\xa3\xb9FM\xd8\xebu\xe1\x94\x81GB\x93\x84\\")
|
||||
);
|
||||
assert!(stun_resp.attributes.len() == 1);
|
||||
if let StunAttribute::MappedAddress(attr) = &stun_resp.attributes[0] {
|
||||
assert!(attr.type_ == STUN_ATTR_MAPPED_ADDRESS);
|
||||
assert!(attr.length == 8);
|
||||
assert!(attr.reserved == 0);
|
||||
assert!(attr.protocol_family == STUN_PROTOCOL_FAMILY_IPV4);
|
||||
assert!(attr.port == client_info.port.src.unwrap());
|
||||
assert!(attr.ip == client_info.ip.src.unwrap());
|
||||
} else {
|
||||
panic!("expected MappedAddress attribute");
|
||||
}
|
||||
/* Check that client_info was not modified */
|
||||
assert!(client_info.ip.src == Some(IpAddr::V4(test_ip_addr)));
|
||||
assert!(client_info.ip.dst == Some(IpAddr::V4(masscanned_ip_addr)));
|
||||
assert!(client_info.port.src == Some(55000));
|
||||
assert!(client_info.port.dst == Some(65001));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_change_request_port_overflow() {
|
||||
let payload = b"\x00\x01\x00\x08\x03\xa3\xb9FM\xd8\xebu\xe1\x94\x81GB\x93\x84\\\x00\x03\x00\x04\x00\x00\x00\x02";
|
||||
let mut client_info = ClientInfo::new();
|
||||
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||
let mut ips = HashSet::new();
|
||||
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||
/* Construct masscanned context object */
|
||||
let masscanned = Masscanned {
|
||||
synack_key: [0, 0],
|
||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||
iface: None,
|
||||
ip_addresses: Some(&ips),
|
||||
};
|
||||
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||
client_info.port.src = Some(55000);
|
||||
client_info.port.dst = Some(65535);
|
||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
||||
r
|
||||
} else {
|
||||
panic!("expected an answer, got None");
|
||||
};
|
||||
let stun_resp = StunPacket::new(&payload_resp).unwrap();
|
||||
assert!(stun_resp.class == STUN_CLASS_SUCCESS_RESPONSE);
|
||||
assert!(stun_resp.method == STUN_METHOD_BINDING);
|
||||
assert!(
|
||||
stun_resp.id
|
||||
== BigEndian::read_u128(b"\x03\xa3\xb9FM\xd8\xebu\xe1\x94\x81GB\x93\x84\\")
|
||||
);
|
||||
assert!(stun_resp.attributes.len() == 1);
|
||||
if let StunAttribute::MappedAddress(attr) = &stun_resp.attributes[0] {
|
||||
assert!(attr.type_ == STUN_ATTR_MAPPED_ADDRESS);
|
||||
assert!(attr.length == 8);
|
||||
assert!(attr.reserved == 0);
|
||||
assert!(attr.protocol_family == STUN_PROTOCOL_FAMILY_IPV4);
|
||||
assert!(attr.port == client_info.port.src.unwrap());
|
||||
assert!(attr.ip == client_info.ip.src.unwrap());
|
||||
} else {
|
||||
panic!("expected MappedAddress attribute");
|
||||
}
|
||||
/* Check that client_info was not modified */
|
||||
assert!(client_info.ip.src == Some(IpAddr::V4(test_ip_addr)));
|
||||
assert!(client_info.ip.dst == Some(IpAddr::V4(masscanned_ip_addr)));
|
||||
assert!(client_info.port.src == Some(55000));
|
||||
assert!(client_info.port.dst == Some(0));
|
||||
}
|
||||
}
|
9
src/smack/mod.rs
Normal file
9
src/smack/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
mod smack;
|
||||
mod smack_constants;
|
||||
mod smack_pattern;
|
||||
mod smack_queue;
|
||||
mod smack_utils;
|
||||
|
||||
pub use smack::Smack;
|
||||
pub use smack_constants::*;
|
||||
pub use smack_utils::SmackFlags;
|
758
src/smack/smack.rs
Normal file
758
src/smack/smack.rs
Normal file
|
@ -0,0 +1,758 @@
|
|||
use std::mem;
|
||||
|
||||
use crate::smack::smack_constants::*;
|
||||
use crate::smack::smack_pattern::SmackPattern;
|
||||
use crate::smack::smack_queue::SmackQueue;
|
||||
use crate::smack::smack_utils::{row_shift_from_symbol_count, SmackFlags};
|
||||
|
||||
struct SmackRow {
|
||||
next_state: Vec<usize>,
|
||||
fail: usize,
|
||||
}
|
||||
|
||||
impl SmackRow {
|
||||
fn new() -> Self {
|
||||
SmackRow {
|
||||
next_state: vec![BASE_STATE; ALPHABET_SIZE],
|
||||
fail: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SmackMatches {
|
||||
m_ids: Vec<usize>,
|
||||
m_count: usize,
|
||||
}
|
||||
|
||||
impl SmackMatches {
|
||||
fn new() -> Self {
|
||||
SmackMatches {
|
||||
m_ids: Vec::new(),
|
||||
m_count: 0,
|
||||
}
|
||||
}
|
||||
fn copy_matches(&mut self, new_ids: Vec<usize>) {
|
||||
for id in &new_ids {
|
||||
if !self.m_ids.contains(id) {
|
||||
self.m_count += 1;
|
||||
self.m_ids.push(*id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Smack {
|
||||
_name: String,
|
||||
is_nocase: bool,
|
||||
is_anchor_begin: bool,
|
||||
is_anchor_end: bool,
|
||||
m_pattern_list: Vec<SmackPattern>,
|
||||
m_pattern_count: usize,
|
||||
m_state_table: Vec<SmackRow>,
|
||||
m_state_count: usize,
|
||||
m_state_max: usize,
|
||||
m_match: Vec<SmackMatches>,
|
||||
m_match_limit: usize,
|
||||
symbol_to_char: Vec<usize>,
|
||||
char_to_symbol: Vec<u8>,
|
||||
symbol_count: usize,
|
||||
row_shift: usize,
|
||||
transitions: Vec<usize>,
|
||||
}
|
||||
|
||||
fn make_copy_of_pattern(pattern: &[u8], is_nocase: bool) -> Vec<u8> {
|
||||
let mut p = pattern.clone().to_vec();
|
||||
for i in 0..p.len() {
|
||||
if is_nocase {
|
||||
p[i] = p[i].to_ascii_lowercase();
|
||||
}
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
impl Smack {
|
||||
pub fn new(name: String, nocase: bool) -> Self {
|
||||
Smack {
|
||||
_name: name,
|
||||
is_nocase: nocase,
|
||||
is_anchor_begin: false,
|
||||
is_anchor_end: false,
|
||||
m_pattern_list: Vec::new(),
|
||||
m_pattern_count: 0,
|
||||
m_state_table: Vec::new(),
|
||||
m_state_count: 0,
|
||||
m_state_max: 0,
|
||||
m_match: Vec::new(),
|
||||
m_match_limit: 0,
|
||||
symbol_to_char: vec![0; ALPHABET_SIZE],
|
||||
char_to_symbol: vec![0; ALPHABET_SIZE],
|
||||
symbol_count: 0,
|
||||
row_shift: 0,
|
||||
transitions: Vec::new(),
|
||||
}
|
||||
}
|
||||
fn create_intermediate_table(&mut self, size: usize) {
|
||||
for _ in 0..size {
|
||||
self.m_state_table.push(SmackRow::new());
|
||||
}
|
||||
}
|
||||
fn create_matches_table(&mut self, size: usize) {
|
||||
for _ in 0..size {
|
||||
self.m_match.push(SmackMatches::new());
|
||||
}
|
||||
}
|
||||
fn add_symbol(&mut self, c: usize) -> usize {
|
||||
for i in 1..self.symbol_count + 1 {
|
||||
if self.symbol_to_char[i] == c {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
self.symbol_count += 1;
|
||||
let symbol = self.symbol_count;
|
||||
self.symbol_to_char[symbol] = c;
|
||||
self.char_to_symbol[c] = symbol.to_le_bytes()[0];
|
||||
symbol
|
||||
}
|
||||
fn add_symbols(&mut self, pattern: &[u8]) {
|
||||
for c in pattern {
|
||||
if self.is_nocase {
|
||||
self.add_symbol(c.to_ascii_lowercase().into());
|
||||
} else {
|
||||
self.add_symbol((*c).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn add_pattern(&mut self, pattern: &[u8], id: usize, flags: SmackFlags) {
|
||||
let p = SmackPattern::new(make_copy_of_pattern(pattern, self.is_nocase), id, flags);
|
||||
if p.is_anchor_begin {
|
||||
self.is_anchor_begin = true;
|
||||
}
|
||||
if p.is_anchor_end {
|
||||
self.is_anchor_end = true;
|
||||
}
|
||||
self.add_symbols(&p.pattern);
|
||||
self.m_pattern_list.push(p);
|
||||
self.m_pattern_count += 1;
|
||||
}
|
||||
fn set_goto(&mut self, r: usize, a: usize, h: usize) {
|
||||
self.m_state_table[r].next_state[a] = h;
|
||||
}
|
||||
fn goto(&self, r: usize, a: usize) -> usize {
|
||||
self.m_state_table[r].next_state[a]
|
||||
}
|
||||
fn set_goto_fail(&mut self, r: usize, h: usize) {
|
||||
self.m_state_table[r].fail = h;
|
||||
}
|
||||
fn goto_fail(&self, r: usize) -> usize {
|
||||
self.m_state_table[r].fail
|
||||
}
|
||||
fn new_state(&mut self) -> usize {
|
||||
self.m_state_count += 1;
|
||||
self.m_state_count - 1
|
||||
}
|
||||
fn add_prefixes(&mut self, p: &SmackPattern) {
|
||||
let mut state = BASE_STATE;
|
||||
let pattern = &p.pattern;
|
||||
if p.is_anchor_begin {
|
||||
state = self.goto(state, CHAR_ANCHOR_START);
|
||||
}
|
||||
let mut i = 0;
|
||||
while i < pattern.len() && self.goto(state, pattern[i].into()) != FAIL_STATE {
|
||||
state = self.goto(state, pattern[i].into());
|
||||
i += 1;
|
||||
}
|
||||
while i < pattern.len() {
|
||||
let new_state = self.new_state();
|
||||
self.set_goto(state, pattern[i].into(), new_state);
|
||||
state = new_state;
|
||||
i += 1;
|
||||
}
|
||||
if p.is_anchor_end {
|
||||
let new_state = self.new_state();
|
||||
self.set_goto(state, CHAR_ANCHOR_END, new_state);
|
||||
state = new_state;
|
||||
}
|
||||
self.m_match[state].copy_matches(vec![p.id]);
|
||||
}
|
||||
fn stage0_compile_prefixes(&mut self) {
|
||||
self.m_state_count = 1;
|
||||
for s in 0..self.m_state_max {
|
||||
for a in 0..ALPHABET_SIZE {
|
||||
self.set_goto(s, a, FAIL_STATE);
|
||||
}
|
||||
}
|
||||
if self.is_anchor_begin {
|
||||
let anchor_begin = self.new_state();
|
||||
self.set_goto(BASE_STATE, CHAR_ANCHOR_START, anchor_begin);
|
||||
}
|
||||
let plist = mem::replace(&mut self.m_pattern_list, Vec::new());
|
||||
for p in plist.iter() {
|
||||
self.add_prefixes(&p);
|
||||
}
|
||||
self.m_pattern_list = plist;
|
||||
for a in 0..ALPHABET_SIZE {
|
||||
if self.goto(BASE_STATE, a) == FAIL_STATE {
|
||||
self.set_goto(BASE_STATE, a, BASE_STATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn stage1_generate_fails(&mut self) {
|
||||
let mut queue: SmackQueue<usize> = SmackQueue::new();
|
||||
for a in 0..ALPHABET_SIZE {
|
||||
let s = self.goto(BASE_STATE, a);
|
||||
if s != BASE_STATE {
|
||||
queue.enqueue(s);
|
||||
self.set_goto_fail(s, BASE_STATE);
|
||||
}
|
||||
}
|
||||
while queue.has_more_items() {
|
||||
let r = queue.dequeue();
|
||||
for a in 0..ALPHABET_SIZE {
|
||||
let s = self.goto(r, a);
|
||||
if s == FAIL_STATE {
|
||||
continue;
|
||||
}
|
||||
if s == r {
|
||||
continue;
|
||||
}
|
||||
queue.enqueue(s);
|
||||
let mut f = self.goto_fail(r);
|
||||
while self.goto(f, a) == FAIL_STATE {
|
||||
f = self.goto_fail(f);
|
||||
}
|
||||
self.set_goto_fail(s, self.goto(f, a));
|
||||
if self.m_match[self.goto(f, a)].m_count > 0 {
|
||||
let gt = self.goto(f, a);
|
||||
let m = mem::take(&mut self.m_match[gt].m_ids);
|
||||
self.m_match[s].copy_matches(m.clone());
|
||||
self.m_match[gt].m_ids = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn stage2_link_fails(&mut self) {
|
||||
let mut queue = SmackQueue::new();
|
||||
for a in 0..ALPHABET_SIZE {
|
||||
if self.goto(BASE_STATE, a) != BASE_STATE {
|
||||
queue.enqueue(self.goto(BASE_STATE, a));
|
||||
}
|
||||
}
|
||||
loop {
|
||||
if !queue.has_more_items() {
|
||||
break;
|
||||
}
|
||||
let r = queue.dequeue();
|
||||
for a in 0..ALPHABET_SIZE {
|
||||
if self.goto(r, a) == FAIL_STATE {
|
||||
self.set_goto(r, a, self.goto(self.goto_fail(r), a));
|
||||
} else if self.goto(r, a) == r {
|
||||
} else {
|
||||
queue.enqueue(self.goto(r, a));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn swap_rows(&mut self, row1: usize, row2: usize) {
|
||||
let tmp = mem::replace(&mut self.m_state_table[row1], SmackRow::new());
|
||||
self.m_state_table[row1] = mem::replace(&mut self.m_state_table[row2], tmp);
|
||||
let tmp = mem::replace(&mut self.m_match[row1], SmackMatches::new());
|
||||
self.m_match[row1] = mem::replace(&mut self.m_match[row2], tmp);
|
||||
for s in 0..self.m_state_count {
|
||||
for a in 0..ALPHABET_SIZE {
|
||||
if self.goto(s, a) == row1 {
|
||||
self.set_goto(s, a, row2);
|
||||
} else if self.goto(s, a) == row2 {
|
||||
self.set_goto(s, a, row1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn stage3_sort(&mut self) {
|
||||
let mut start = 0;
|
||||
let mut end = self.m_state_count;
|
||||
loop {
|
||||
while start < end && self.m_match[start].m_count == 0 {
|
||||
start += 1;
|
||||
}
|
||||
while start < end && self.m_match[end - 1].m_count != 0 {
|
||||
end -= 1;
|
||||
}
|
||||
if start >= end {
|
||||
break;
|
||||
}
|
||||
self.swap_rows(start, end - 1);
|
||||
}
|
||||
self.m_match_limit = start;
|
||||
}
|
||||
fn stage4_make_final_table(&mut self) {
|
||||
let row_count = self.m_state_count;
|
||||
self.row_shift = row_shift_from_symbol_count(self.symbol_count);
|
||||
let column_count = 1 << self.row_shift;
|
||||
self.transitions = vec![0; row_count * column_count];
|
||||
for row in 0..row_count {
|
||||
for c in 0..ALPHABET_SIZE {
|
||||
let symbol = usize::from(self.char_to_symbol[c]);
|
||||
let transition = self.goto(row, c);
|
||||
self.transitions[row * column_count + symbol] = transition;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fixup_wildcards(&mut self) {
|
||||
for i in 0..self.m_pattern_count {
|
||||
let p = &self.m_pattern_list[i];
|
||||
if !p.is_wildcards {
|
||||
continue;
|
||||
}
|
||||
for j in 0..p.pattern.len() {
|
||||
let mut row = 0;
|
||||
let mut offset = 0;
|
||||
let row_size = 1 << self.row_shift;
|
||||
let base_state = if self.is_anchor_begin {
|
||||
UNANCHORED_STATE
|
||||
} else {
|
||||
BASE_STATE
|
||||
};
|
||||
if p.pattern[j] != b'*' {
|
||||
continue;
|
||||
}
|
||||
while offset < j {
|
||||
self.search_next(&mut row, &p.pattern[..j], &mut offset);
|
||||
}
|
||||
row &= 0xFFFFFF;
|
||||
let next_pattern = self.transitions
|
||||
[(row << self.row_shift) + usize::from(self.char_to_symbol[usize::from(b'*')])];
|
||||
for k in 0..row_size {
|
||||
if self.transitions[(row << self.row_shift) + k] == base_state {
|
||||
self.transitions[(row << self.row_shift) + k] = next_pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn inner_match(&self, px: Vec<u8>, length: usize, state: usize) -> (usize, usize) {
|
||||
let px_start = 0;
|
||||
let px_end = length;
|
||||
let mut row = state;
|
||||
let mut idx = px_start;
|
||||
while idx < px_end {
|
||||
let column: usize = self.char_to_symbol[usize::from(px[idx])].into();
|
||||
row = self.transitions[(row << self.row_shift) + column];
|
||||
if row >= self.m_match_limit {
|
||||
break;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
(idx - px_start, row)
|
||||
}
|
||||
fn inner_match_shift7(&self, px: Vec<u8>, length: usize, state: usize) -> (usize, usize) {
|
||||
let px_start = 0;
|
||||
let px_end = length;
|
||||
let mut row = state;
|
||||
let mut idx = px_start;
|
||||
while idx < px_end {
|
||||
let column: usize = self.char_to_symbol[usize::from(px[idx])].into();
|
||||
row = self.transitions[(row << 7) + column];
|
||||
if row >= self.m_match_limit {
|
||||
break;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
(idx - px_start, row)
|
||||
}
|
||||
pub fn search_next(&self, current_state: &mut usize, v_px: &[u8], offset: &mut usize) -> usize {
|
||||
let px = v_px;
|
||||
let length = px.len();
|
||||
let mut i = *offset;
|
||||
let mut id = NO_MATCH;
|
||||
let mut row = *current_state & 0xFFFFFF;
|
||||
let mut current_matches = *current_state >> 24;
|
||||
if current_matches == 0 {
|
||||
if self.row_shift == 7 {
|
||||
let (ii, new_row) = self.inner_match_shift7(px[i..].to_vec(), length - i, row);
|
||||
i += ii;
|
||||
row = new_row;
|
||||
} else {
|
||||
let (ii, new_row) = self.inner_match(px[i..].to_vec(), length - i, row);
|
||||
i += ii;
|
||||
row = new_row;
|
||||
}
|
||||
if self.m_match[row].m_count != 0 {
|
||||
i += 1;
|
||||
current_matches = self.m_match[row].m_count;
|
||||
}
|
||||
}
|
||||
if current_matches != 0 {
|
||||
id = self.m_match[row].m_ids[current_matches - 1];
|
||||
current_matches -= 1;
|
||||
}
|
||||
let new_state = row | (current_matches << 24);
|
||||
*current_state = new_state;
|
||||
*offset = i;
|
||||
id
|
||||
}
|
||||
pub fn search_next_end(&self, current_state: &mut usize) -> usize {
|
||||
let id;
|
||||
let mut row = *current_state & 0xFFFFFF;
|
||||
let mut current_matches = *current_state >> 24;
|
||||
let column = self.char_to_symbol[CHAR_ANCHOR_END];
|
||||
/*
|
||||
* We can enumerate more than one matching end patterns. When we
|
||||
* reach the end of that list, return NOT FOUND.
|
||||
*/
|
||||
if current_matches == 0xFF {
|
||||
return NO_MATCH;
|
||||
}
|
||||
/*
|
||||
* If we've already returned the first result in our list,
|
||||
* then return the next result.
|
||||
*/
|
||||
if current_matches != 0 {
|
||||
id = self.m_match[row].m_ids[current_matches - 1];
|
||||
current_matches -= 1;
|
||||
} else {
|
||||
/*
|
||||
* This is the same logic as for "smack_search()", except there is
|
||||
* only one byte of input -- the virtual character ($) that represents
|
||||
* the anchor at the end of some patterns.
|
||||
*/
|
||||
row = self.transitions[(row << self.row_shift) + column as usize];
|
||||
/* There was no match, so therefore return NOT FOUND */
|
||||
if self.m_match[row].m_count == 0 {
|
||||
return NO_MATCH;
|
||||
}
|
||||
/*
|
||||
* If we reach this point, we have found matches, but
|
||||
* haven't started returning them. So start returning
|
||||
* them. This returns the first one in the list.
|
||||
*/
|
||||
current_matches = self.m_match[row].m_count;
|
||||
id = self.m_match[row].m_ids[current_matches - 1];
|
||||
if current_matches > 0 {
|
||||
current_matches -= 1;
|
||||
} else {
|
||||
current_matches = 0xFF;
|
||||
}
|
||||
}
|
||||
let new_state = row | (current_matches << 24);
|
||||
*current_state = new_state;
|
||||
id
|
||||
}
|
||||
pub fn _next_match(&self, current_state: &mut usize) -> usize {
|
||||
let mut id = NO_MATCH;
|
||||
let row = *current_state & 0xFFFFFF;
|
||||
let mut current_matches = *current_state >> 24;
|
||||
if current_matches != 0 {
|
||||
id = self.m_match[row].m_ids[current_matches - 1];
|
||||
current_matches -= 1;
|
||||
}
|
||||
*current_state = row | (current_matches << 24);
|
||||
return id;
|
||||
}
|
||||
pub fn compile(&mut self) {
|
||||
if self.is_anchor_begin {
|
||||
self.add_symbol(CHAR_ANCHOR_START);
|
||||
}
|
||||
if self.is_anchor_end {
|
||||
self.add_symbol(CHAR_ANCHOR_END);
|
||||
}
|
||||
if self.is_nocase {
|
||||
for i in b'A'..b'Z' + 1 {
|
||||
self.char_to_symbol[usize::from(i)] =
|
||||
self.char_to_symbol[usize::from(i.to_ascii_lowercase())];
|
||||
}
|
||||
}
|
||||
self.m_state_max = 1;
|
||||
for p in self.m_pattern_list.iter() {
|
||||
if p.is_anchor_begin {
|
||||
self.m_state_max += 1;
|
||||
}
|
||||
if p.is_anchor_end {
|
||||
self.m_state_max += 1;
|
||||
}
|
||||
self.m_state_max += p.pattern.len();
|
||||
}
|
||||
self.create_intermediate_table(self.m_state_max);
|
||||
self.create_matches_table(self.m_state_max);
|
||||
self.stage0_compile_prefixes();
|
||||
self.stage1_generate_fails();
|
||||
self.stage2_link_fails();
|
||||
if self.is_anchor_begin {
|
||||
self.swap_rows(BASE_STATE, UNANCHORED_STATE);
|
||||
}
|
||||
self.stage3_sort();
|
||||
self.stage4_make_final_table();
|
||||
// self.dump();
|
||||
// self.dump_transitions();
|
||||
self.fixup_wildcards();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_pattern() {
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
let patterns = vec![
|
||||
"GET",
|
||||
"PUT",
|
||||
"POST",
|
||||
"OPTIONS",
|
||||
"HEAD",
|
||||
"DELETE",
|
||||
"TRACE",
|
||||
"CONNECT",
|
||||
"PROPFIND",
|
||||
"PROPPATCH",
|
||||
"MKCOL",
|
||||
"MKWORKSPACE",
|
||||
"MOVE",
|
||||
"LOCK",
|
||||
"UNLOCK",
|
||||
"VERSION-CONTROL",
|
||||
"REPORT",
|
||||
"CHECKOUT",
|
||||
"CHECKIN",
|
||||
"UNCHECKOUT",
|
||||
"COPY",
|
||||
"UPDATE",
|
||||
"LABEL",
|
||||
"BASELINE-CONTROL",
|
||||
"MERGE",
|
||||
"SEARCH",
|
||||
"ACL",
|
||||
"ORDERPATCH",
|
||||
"PATCH",
|
||||
"MKACTIVITY",
|
||||
];
|
||||
let text = "ahpropfinddf;orderpatchposearchmoversion-controlockasldhf";
|
||||
let mut state = 0;
|
||||
for (i, p) in patterns.iter().enumerate() {
|
||||
smack.add_pattern(p.as_bytes(), i, SmackFlags::EMPTY);
|
||||
}
|
||||
smack.compile();
|
||||
let mut i = 0;
|
||||
let test = |pat: usize, offset: usize, id: usize, i: usize| (pat == id) && (offset == i);
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(test(8, 10, id, i));
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(test(28, 23, id, i));
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(test(27, 23, id, i));
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(test(25, 31, id, i));
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(test(12, 35, id, i));
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(test(15, 48, id, i));
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(test(13, 51, id, i));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anchor_begin() {
|
||||
/* test without anchor */
|
||||
let mut smack = Smack::new("test anchor begin".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"abc", 0, SmackFlags::EMPTY);
|
||||
smack.add_pattern(b"def", 1, SmackFlags::EMPTY);
|
||||
smack.compile();
|
||||
let mut i = 0;
|
||||
let mut state = BASE_STATE;
|
||||
let text = "abc_def";
|
||||
/* should find abc and then def */
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 0);
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 1);
|
||||
/* test with anchor - OK */
|
||||
let mut smack = Smack::new("test anchor begin".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"abc", 0, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.add_pattern(b"def", 1, SmackFlags::EMPTY);
|
||||
smack.compile();
|
||||
let mut i = 0;
|
||||
let mut state = BASE_STATE;
|
||||
let text = "abc_def";
|
||||
/* should find abc and then def */
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 0);
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 1);
|
||||
/* test with anchor - KO */
|
||||
let mut smack = Smack::new("test anchor begin".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"abc", 0, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.add_pattern(b"def", 1, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.compile();
|
||||
let mut i = 0;
|
||||
let mut state = BASE_STATE;
|
||||
let text = "abc_def";
|
||||
/* should find abc and then nothing */
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 0);
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == NO_MATCH);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wildcard() {
|
||||
/* test wildcard without wildcard */
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"abc", 0, SmackFlags::EMPTY);
|
||||
smack.add_pattern(b"egjkfhd", 1, SmackFlags::EMPTY);
|
||||
/* here we do not specify the WILDCARD flag */
|
||||
smack.add_pattern(b"c*ap", 2, SmackFlags::EMPTY);
|
||||
smack.compile();
|
||||
let mut i = 0;
|
||||
let mut state = BASE_STATE;
|
||||
let text = "abc_clap";
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 0);
|
||||
assert!(i == 3);
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id != 2);
|
||||
|
||||
/* test wildcard */
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"abc", 0, SmackFlags::EMPTY);
|
||||
smack.add_pattern(b"egjkfhd", 1, SmackFlags::EMPTY);
|
||||
smack.add_pattern(b"c*ap", 2, SmackFlags::WILDCARDS);
|
||||
smack.compile();
|
||||
let mut i = 0;
|
||||
let mut state = BASE_STATE;
|
||||
let text = "abc_clap";
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 0);
|
||||
assert!(i == 3);
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 2);
|
||||
|
||||
/* test wildcard + anchor beg */
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(
|
||||
b"abc*ef",
|
||||
0,
|
||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||
);
|
||||
smack.compile();
|
||||
let mut i = 0;
|
||||
let mut state = BASE_STATE;
|
||||
let text = "abc_ef";
|
||||
let id = smack.search_next(&mut state, &text.as_bytes().to_vec(), &mut i);
|
||||
assert!(id == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_banner() {
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"Server:", 0, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.add_pattern(b"Via:", 1, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.add_pattern(b"Location:", 2, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.add_pattern(b":", 3, SmackFlags::EMPTY);
|
||||
smack.compile();
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let id = smack.search_next(&mut state, &b"server: lol\n".to_vec(), &mut offset);
|
||||
assert!(id == 3);
|
||||
let id = smack._next_match(&mut state);
|
||||
assert!(id == 0);
|
||||
let id = smack._next_match(&mut state);
|
||||
assert!(id == NO_MATCH);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anchor_end() {
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"def", 0, SmackFlags::ANCHOR_END);
|
||||
smack.compile();
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let mut id = smack.search_next(&mut state, &b"defabcabb".to_vec(), &mut offset);
|
||||
assert!(id == NO_MATCH);
|
||||
id = smack.search_next_end(&mut state);
|
||||
assert!(id == NO_MATCH);
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let mut id = smack.search_next(&mut state, &b"def".to_vec(), &mut offset);
|
||||
assert!(id == NO_MATCH);
|
||||
id = smack.search_next_end(&mut state);
|
||||
assert!(id == 0);
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let mut id = smack.search_next(&mut state, &b"abcdef".to_vec(), &mut offset);
|
||||
assert!(id == NO_MATCH);
|
||||
id = smack.search_next_end(&mut state);
|
||||
assert!(id == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_matches() {
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"aabb", 0, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.add_pattern(b"abb", 1, SmackFlags::EMPTY);
|
||||
smack.add_pattern(b"bb", 2, SmackFlags::EMPTY);
|
||||
smack.compile();
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let id = smack.search_next(&mut state, &b"aabb".to_vec(), &mut offset);
|
||||
assert!(id <= 2);
|
||||
let id = smack._next_match(&mut state);
|
||||
assert!(id <= 2);
|
||||
let id = smack._next_match(&mut state);
|
||||
assert!(id <= 2);
|
||||
let id = smack._next_match(&mut state);
|
||||
assert!(id == NO_MATCH);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_matches_wildcard() {
|
||||
let mut smack = Smack::new("test".to_string(), SMACK_CASE_INSENSITIVE);
|
||||
smack.add_pattern(b"aab", 0, SmackFlags::ANCHOR_BEGIN);
|
||||
smack.add_pattern(b"*ac", 1, SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS);
|
||||
smack.compile();
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let id = smack.search_next(&mut state, &b"aab".to_vec(), &mut offset);
|
||||
assert!(id == 0);
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let id = smack.search_next(&mut state, &b"bac".to_vec(), &mut offset);
|
||||
assert!(id == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proto() {
|
||||
const PROTO_HTTP: usize = 0;
|
||||
const PROTO_SMB: usize = 1;
|
||||
let mut smack = Smack::new("proto".to_string(), SMACK_CASE_SENSITIVE);
|
||||
/* HTTP markers */
|
||||
let http_verbs = [
|
||||
"GET /",
|
||||
"PUT /",
|
||||
"POST /",
|
||||
"HEAD /",
|
||||
"DELETE /",
|
||||
"CONNECT /",
|
||||
"OPTIONS /",
|
||||
"TRACE /",
|
||||
"PATCH /",
|
||||
];
|
||||
for (_, v) in http_verbs.iter().enumerate() {
|
||||
smack.add_pattern(v.as_bytes(), PROTO_HTTP, SmackFlags::ANCHOR_BEGIN);
|
||||
}
|
||||
/* SMB markers */
|
||||
smack.add_pattern(
|
||||
b"\x00\x00**\xffSMB",
|
||||
PROTO_SMB,
|
||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||
);
|
||||
smack.compile();
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let id = smack.search_next(&mut state, &b"HEAD /".to_vec(), &mut offset);
|
||||
assert!(id == PROTO_HTTP);
|
||||
let mut state = BASE_STATE;
|
||||
let mut offset = 0;
|
||||
let id = smack.search_next(&mut state, &b"\x00\x00aa\xffSMB".to_vec(), &mut offset);
|
||||
assert!(id == PROTO_SMB);
|
||||
}
|
||||
}
|
12
src/smack/smack_constants.rs
Normal file
12
src/smack/smack_constants.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
pub const ALPHABET_SIZE: usize = 256 + 2;
|
||||
pub const BASE_STATE: usize = 0;
|
||||
pub const UNANCHORED_STATE: usize = 1;
|
||||
pub const FAIL_STATE: usize = 0xFFFFFFFF;
|
||||
|
||||
pub const CHAR_ANCHOR_START: usize = 256;
|
||||
pub const CHAR_ANCHOR_END: usize = 257;
|
||||
|
||||
pub const NO_MATCH: usize = 0xFFFFFFFFFFFFFFFF;
|
||||
|
||||
pub const SMACK_CASE_INSENSITIVE: bool = true;
|
||||
pub const SMACK_CASE_SENSITIVE: bool = false;
|
21
src/smack/smack_pattern.rs
Normal file
21
src/smack/smack_pattern.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use crate::smack::smack_utils::SmackFlags;
|
||||
|
||||
pub struct SmackPattern {
|
||||
pub id: usize,
|
||||
pub pattern: Vec<u8>,
|
||||
pub is_anchor_begin: bool,
|
||||
pub is_anchor_end: bool,
|
||||
pub is_wildcards: bool,
|
||||
}
|
||||
|
||||
impl SmackPattern {
|
||||
pub fn new(pattern: Vec<u8>, id: usize, flags: SmackFlags) -> Self {
|
||||
SmackPattern {
|
||||
id,
|
||||
is_anchor_begin: flags.contains(SmackFlags::ANCHOR_BEGIN),
|
||||
is_anchor_end: flags.contains(SmackFlags::ANCHOR_END),
|
||||
is_wildcards: flags.contains(SmackFlags::WILDCARDS),
|
||||
pattern,
|
||||
}
|
||||
}
|
||||
}
|
18
src/smack/smack_queue.rs
Normal file
18
src/smack/smack_queue.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
pub struct SmackQueue<T> {
|
||||
queue: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> SmackQueue<T> {
|
||||
pub fn new() -> Self {
|
||||
SmackQueue { queue: Vec::new() }
|
||||
}
|
||||
pub fn enqueue(&mut self, data: T) {
|
||||
self.queue.push(data);
|
||||
}
|
||||
pub fn dequeue(&mut self) -> T {
|
||||
self.queue.remove(0)
|
||||
}
|
||||
pub fn has_more_items(&self) -> bool {
|
||||
!self.queue.is_empty()
|
||||
}
|
||||
}
|
17
src/smack/smack_utils.rs
Normal file
17
src/smack/smack_utils.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
bitflags! {
|
||||
pub struct SmackFlags: usize {
|
||||
const EMPTY = 0x00;
|
||||
const ANCHOR_BEGIN = 0x01;
|
||||
const ANCHOR_END = 0x02;
|
||||
const WILDCARDS = 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_shift_from_symbol_count(symbol_count: usize) -> usize {
|
||||
let mut row_shift = 1;
|
||||
let symbol_count = symbol_count + 1;
|
||||
while (1 << row_shift) < symbol_count {
|
||||
row_shift += 1;
|
||||
}
|
||||
row_shift
|
||||
}
|
357
src/synackcookie/mod.rs
Normal file
357
src/synackcookie/mod.rs
Normal file
|
@ -0,0 +1,357 @@
|
|||
use crate::client::ClientInfo;
|
||||
use siphasher::sip::SipHasher24;
|
||||
use std::convert::TryInto;
|
||||
use std::hash::Hasher;
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
|
||||
pub fn generate(client_info: &ClientInfo, key: &[u64; 2]) -> Result<u32, io::Error> {
|
||||
/* check parameters */
|
||||
/* ip fields must not be None */
|
||||
if client_info.ip.src == None || client_info.ip.dst == None {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"IP addresses must not be None",
|
||||
));
|
||||
}
|
||||
/* port fields must not be None */
|
||||
if client_info.port.src == None || client_info.port.dst == None {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Ports must not be None",
|
||||
));
|
||||
}
|
||||
let mut sip = SipHasher24::new_with_keys(key[0], key[1]);
|
||||
/* check IPAddr type */
|
||||
if let Some(IpAddr::V6(s)) = client_info.ip.src {
|
||||
if let Some(IpAddr::V6(d)) = client_info.ip.dst {
|
||||
sip.write_u128(s.into());
|
||||
sip.write_u128(d.into());
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"The two IP addresses (src and dst) must be of same type",
|
||||
));
|
||||
}
|
||||
} else if let Some(IpAddr::V4(s)) = client_info.ip.src {
|
||||
if let Some(IpAddr::V4(d)) = client_info.ip.dst {
|
||||
sip.write_u32(s.into());
|
||||
sip.write_u32(d.into());
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"The two IP addresses (src and dst) must be of same type",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unknown data type",
|
||||
));
|
||||
}
|
||||
sip.write_u16(client_info.port.src.unwrap());
|
||||
sip.write_u16(client_info.port.dst.unwrap());
|
||||
Ok((sip.finish() & 0xFFFFFFFF).try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn _check(client_info: &ClientInfo, val: u32, key: &[u64; 2]) -> bool {
|
||||
if let Ok(cookie) = generate(client_info, &key) {
|
||||
cookie == val
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::client::ClientInfoSrcDst;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[test]
|
||||
fn test_ip4() {
|
||||
let key = [0xfb3818fcf501729d, 0xeb3b3e8720618e69];
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let res = generate(&client_info, &key);
|
||||
if let Ok(_) = res {
|
||||
assert!(true);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ip6() {
|
||||
let key = [0x6b794087697b9180, 0x0c149aa303534b02];
|
||||
let ip_src = IpAddr::V6(Ipv6Addr::new(
|
||||
0xe50f, 0xe521, 0x70a2, 0xa3b3, 0x2135, 0x52d9, 0x6a0d, 0xe215,
|
||||
));
|
||||
let ip_dst = IpAddr::V6(Ipv6Addr::new(
|
||||
0xc2eb, 0x33cf, 0x2c15, 0x4f7a, 0x7085, 0x492c, 0x2dbc, 0xf35b,
|
||||
));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let res = generate(&client_info, &key);
|
||||
if let Ok(_) = res {
|
||||
assert!(true);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clientinfo() {
|
||||
let key = [0x0b1a8621b0caf88d, 0x677cc071dab41639];
|
||||
let err = Err(io::ErrorKind::InvalidInput);
|
||||
/* all ok */
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let mut client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let res = generate(&client_info, &key);
|
||||
if let Ok(_) = res {
|
||||
assert!(true);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
/* ip src is None */
|
||||
client_info.ip.src = None;
|
||||
let res = generate(&client_info, &key);
|
||||
assert_eq!(res.map_err(|e| e.kind()), err);
|
||||
client_info.ip.src = Some(ip_src);
|
||||
/* ip dst is None */
|
||||
client_info.ip.dst = None;
|
||||
let res = generate(&client_info, &key);
|
||||
assert_eq!(res.map_err(|e| e.kind()), err);
|
||||
client_info.ip.dst = Some(ip_dst);
|
||||
/* port src is None */
|
||||
client_info.port.src = None;
|
||||
let res = generate(&client_info, &key);
|
||||
assert_eq!(res.map_err(|e| e.kind()), err);
|
||||
client_info.port.src = Some(tcp_sport);
|
||||
/* port dst is None */
|
||||
client_info.port.dst = None;
|
||||
let res = generate(&client_info, &key);
|
||||
assert_eq!(res.map_err(|e| e.kind()), err);
|
||||
client_info.port.dst = Some(tcp_dport);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key() {
|
||||
/* reference */
|
||||
let ref_key = [0x1e9219e0b0e0b44c, 0x9e460bcddf4eaac9];
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let ref_cookie = generate(&client_info, &ref_key).unwrap();
|
||||
assert!(_check(&client_info, ref_cookie, &ref_key));
|
||||
/* change key */
|
||||
let key = [0xc98a8cb8579004d4, 0x8b53a2735381ded4];
|
||||
let cookie = generate(&client_info, &key).unwrap();
|
||||
assert_ne!(ref_key, key);
|
||||
assert_ne!(cookie, ref_cookie);
|
||||
assert!(_check(&client_info, cookie, &key));
|
||||
assert!(!_check(&client_info, ref_cookie, &key));
|
||||
assert!(!_check(&client_info, cookie, &ref_key));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ip4_src() {
|
||||
let key = [0x77b781aaeca4f0d1, 0x7481d7251789d247];
|
||||
/* reference */
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let mut client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let ref_cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, ref_cookie, &key));
|
||||
client_info.ip.src = Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 1)));
|
||||
let cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, cookie, &key));
|
||||
assert!(!_check(&client_info, ref_cookie, &key));
|
||||
assert_ne!(cookie, ref_cookie);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ip4_dst() {
|
||||
let key = [0xe2ada0ff90978791, 0xb18586de261db429];
|
||||
/* reference */
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(3, 3, 3, 3));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 80;
|
||||
let mut client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let ref_cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, ref_cookie, &key));
|
||||
client_info.ip.dst = Some(IpAddr::V4(Ipv4Addr::new(4, 4, 3, 3)));
|
||||
let cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, cookie, &key));
|
||||
assert!(!_check(&client_info, ref_cookie, &key));
|
||||
assert_ne!(cookie, ref_cookie);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tcp_src() {
|
||||
let key = [0xda0e06f5916b0a24, 0x754a8c2f23106b5f];
|
||||
/* reference */
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(3, 4, 3, 4));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 443;
|
||||
let mut client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let ref_cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, ref_cookie, &key));
|
||||
client_info.port.src = Some(12345);
|
||||
let cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, cookie, &key));
|
||||
assert!(!_check(&client_info, ref_cookie, &key));
|
||||
assert_ne!(cookie, ref_cookie);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tcp_dst() {
|
||||
let key = [0x85fa7e3f1cd254b7, 0xcfce5e92a7bb7595];
|
||||
/* reference */
|
||||
let ip_src = IpAddr::V4(Ipv4Addr::new(200, 210, 220, 230));
|
||||
let ip_dst = IpAddr::V4(Ipv4Addr::new(172, 48, 14, 103));
|
||||
let tcp_sport = 65000;
|
||||
let tcp_dport = 443;
|
||||
let mut client_info = ClientInfo {
|
||||
mac: ClientInfoSrcDst {
|
||||
src: None,
|
||||
dst: None,
|
||||
},
|
||||
ip: ClientInfoSrcDst {
|
||||
src: Some(ip_src),
|
||||
dst: Some(ip_dst),
|
||||
},
|
||||
transport: None,
|
||||
port: ClientInfoSrcDst {
|
||||
src: Some(tcp_sport),
|
||||
dst: Some(tcp_dport),
|
||||
},
|
||||
cookie: None,
|
||||
};
|
||||
let ref_cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, ref_cookie, &key));
|
||||
client_info.port.dst = Some(80);
|
||||
let cookie = generate(&client_info, &key).unwrap();
|
||||
assert!(_check(&client_info, cookie, &key));
|
||||
assert!(!_check(&client_info, ref_cookie, &key));
|
||||
assert_ne!(cookie, ref_cookie);
|
||||
}
|
||||
}
|
3
src/utils/mod.rs
Normal file
3
src/utils/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod parsers;
|
||||
|
||||
pub use parsers::IpAddrParser;
|
249
src/utils/parsers.rs
Normal file
249
src/utils/parsers.rs
Normal file
|
@ -0,0 +1,249 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::File;
|
||||
use std::io::BufRead;
|
||||
use std::io::BufReader;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use log::*;
|
||||
use pcap_file::pcap::{Packet, PcapReader};
|
||||
use pnet::packet::{
|
||||
ethernet::{EtherTypes, EthernetPacket},
|
||||
ipv4::Ipv4Packet,
|
||||
ipv6::Ipv6Packet,
|
||||
Packet as Pkt,
|
||||
};
|
||||
|
||||
/* Generic IP packet (either IPv4 or IPv6) */
|
||||
pub enum IpPacket<'a> {
|
||||
V4(Ipv4Packet<'a>),
|
||||
V6(Ipv6Packet<'a>),
|
||||
}
|
||||
|
||||
/* Get source or dest. IP address from a packet (IPv4 or IPv6) */
|
||||
impl<'a> IpPacket<'a> {
|
||||
// Macro ?
|
||||
pub fn src(&self) -> IpAddr {
|
||||
match self {
|
||||
IpPacket::V4(p) => IpAddr::V4(p.get_source()),
|
||||
IpPacket::V6(p) => IpAddr::V6(p.get_source()),
|
||||
}
|
||||
}
|
||||
pub fn dst(&self) -> IpAddr {
|
||||
match self {
|
||||
IpPacket::V4(p) => IpAddr::V4(p.get_destination()),
|
||||
IpPacket::V6(p) => IpAddr::V6(p.get_destination()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IpAddrParser {
|
||||
fn extract_ip_addresses_with_count(
|
||||
self,
|
||||
blacklist: Option<HashSet<IpAddr>>,
|
||||
) -> HashMap<IpAddr, u32>;
|
||||
fn extract_ip_addresses_only(self, blacklist: Option<HashSet<IpAddr>>) -> HashSet<IpAddr>;
|
||||
}
|
||||
|
||||
/* Parse IP addresses from a text file */
|
||||
impl IpAddrParser for File {
|
||||
fn extract_ip_addresses_with_count(
|
||||
self,
|
||||
blacklist: Option<HashSet<IpAddr>>,
|
||||
) -> HashMap<IpAddr, u32> {
|
||||
let mut ip_addresses = HashMap::new();
|
||||
let buf = BufReader::new(self);
|
||||
for (i, line) in buf.lines().enumerate() {
|
||||
let entry: Vec<&str> = match &line {
|
||||
Ok(l) => l.split('\t').collect(),
|
||||
Err(e) => {
|
||||
warn!("cannot read line {} - {}", i, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
/* Should never occur */
|
||||
if entry.is_empty() {
|
||||
warn!("cannot parse line: {}", line.expect("error reading line"));
|
||||
continue;
|
||||
}
|
||||
let ip: IpAddr;
|
||||
if let Ok(val) = entry[0].parse::<Ipv4Addr>() {
|
||||
ip = IpAddr::V4(val);
|
||||
} else if let Ok(val) = entry[0].parse::<Ipv6Addr>() {
|
||||
ip = IpAddr::V6(val);
|
||||
} else {
|
||||
warn!(
|
||||
"cannot parse IP address from line: {}",
|
||||
line.expect("error reading line")
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if let Some(ref b) = blacklist {
|
||||
if b.contains(&ip) {
|
||||
info!("[blacklist] ignoring {}", &ip);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let ip_entry = ip_addresses.entry(ip).or_insert(0);
|
||||
if entry.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if let Ok(count) = entry[1].parse::<u32>() {
|
||||
*ip_entry += count;
|
||||
}
|
||||
}
|
||||
ip_addresses
|
||||
}
|
||||
|
||||
fn extract_ip_addresses_only(self, blacklist: Option<HashSet<IpAddr>>) -> HashSet<IpAddr> {
|
||||
let mut ip_addresses = HashSet::new();
|
||||
let buf = BufReader::new(self);
|
||||
for (i, line) in buf.lines().enumerate() {
|
||||
let entry: Vec<&str> = match &line {
|
||||
Ok(l) => l.split('\t').collect(),
|
||||
Err(e) => {
|
||||
warn!("cannot read line {} - {}", i, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
/* Should never occur */
|
||||
if entry.is_empty() {
|
||||
warn!("cannot parse line: {}", line.expect("error reading line"));
|
||||
continue;
|
||||
}
|
||||
let ip: IpAddr;
|
||||
if let Ok(val) = entry[0].parse::<Ipv4Addr>() {
|
||||
ip = IpAddr::V4(val);
|
||||
} else if let Ok(val) = entry[0].parse::<Ipv6Addr>() {
|
||||
ip = IpAddr::V6(val);
|
||||
} else {
|
||||
warn!(
|
||||
"cannot parse IP address from line: {}",
|
||||
line.expect("error reading line")
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if let Some(ref b) = blacklist {
|
||||
if b.contains(&ip) {
|
||||
info!("[blacklist] ignoring {}", &ip);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ip_addresses.insert(ip);
|
||||
}
|
||||
ip_addresses
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the IP address of source and dest. from an IP packet.
|
||||
* works with both IPv4 and IPv6 packets/addresses */
|
||||
fn extract_ip(pkt: Packet) -> Option<(IpAddr, IpAddr)> {
|
||||
let eth = EthernetPacket::new(&pkt.data).expect("error parsing Ethernet packet");
|
||||
let payload = eth.payload();
|
||||
let ip = match eth.get_ethertype() {
|
||||
EtherTypes::Ipv4 => match Ipv4Packet::new(payload) {
|
||||
Some(p) => IpPacket::V4(p),
|
||||
None => {
|
||||
warn!("error parsing IPv4 packet - {:?}", pkt);
|
||||
return None;
|
||||
}
|
||||
},
|
||||
EtherTypes::Ipv6 => match Ipv6Packet::new(payload) {
|
||||
Some(p) => IpPacket::V6(p),
|
||||
None => {
|
||||
warn!("error parsing IPv6 packet - {:?}", pkt);
|
||||
return None;
|
||||
}
|
||||
},
|
||||
EtherTypes::Arp => {
|
||||
return None;
|
||||
}
|
||||
t => {
|
||||
warn!("unknown layer 2: {}", t);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some((ip.src(), ip.dst()))
|
||||
}
|
||||
|
||||
impl IpAddrParser for PcapReader<std::fs::File> {
|
||||
/* Extract IP addresses (v4 and v6) from a capture and count occurrences of
|
||||
* each. */
|
||||
fn extract_ip_addresses_with_count(
|
||||
self: PcapReader<std::fs::File>,
|
||||
blacklist: Option<HashSet<IpAddr>>,
|
||||
) -> HashMap<IpAddr, u32> {
|
||||
let mut ip_addresses = HashMap::new();
|
||||
// pcap.map(fn) , map_Ok
|
||||
// .iter, into_iter
|
||||
for pkt in self {
|
||||
match pkt {
|
||||
Ok(pkt) => {
|
||||
// map_Some map_None
|
||||
if let Some((s, d)) = extract_ip(pkt) {
|
||||
match blacklist {
|
||||
Some(ref b) if b.contains(&s) => {
|
||||
info!("[blacklist] ignoring {}", &s);
|
||||
}
|
||||
_ => {
|
||||
let ip = ip_addresses.entry(s).or_insert(0);
|
||||
*ip += 1;
|
||||
}
|
||||
}
|
||||
match blacklist {
|
||||
Some(ref b) if b.contains(&d) => {
|
||||
info!("[blacklist] ignoring {}", &d);
|
||||
}
|
||||
_ => {
|
||||
let ip = ip_addresses.entry(d).or_insert(0);
|
||||
*ip += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error reading packet - {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
ip_addresses
|
||||
}
|
||||
fn extract_ip_addresses_only(
|
||||
self: PcapReader<std::fs::File>,
|
||||
blacklist: Option<HashSet<IpAddr>>,
|
||||
) -> HashSet<IpAddr> {
|
||||
let mut ip_addresses = HashSet::new();
|
||||
// pcap.map(fn) , map_Ok
|
||||
// .iter, into_iter
|
||||
for pkt in self {
|
||||
match pkt {
|
||||
Ok(pkt) => {
|
||||
// map_Some map_None
|
||||
if let Some((s, d)) = extract_ip(pkt) {
|
||||
match blacklist {
|
||||
Some(ref b) if b.contains(&s) => {
|
||||
info!("[blacklist] ignoring {}", &s);
|
||||
}
|
||||
_ => {
|
||||
ip_addresses.insert(s);
|
||||
}
|
||||
}
|
||||
match blacklist {
|
||||
Some(ref b) if b.contains(&d) => {
|
||||
info!("[blacklist] ignoring {}", &d);
|
||||
}
|
||||
_ => {
|
||||
ip_addresses.insert(d);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error reading packet - {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
ip_addresses
|
||||
}
|
||||
}
|
0
test/res/.gitignore
vendored
Normal file
0
test/res/.gitignore
vendored
Normal file
15
test/src/__init__.py
Normal file
15
test/src/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# 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/>.
|
593
test/src/all.py
Normal file
593
test/src/all.py
Normal file
|
@ -0,0 +1,593 @@
|
|||
# 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/>.
|
||||
|
||||
from scapy.all import *
|
||||
import requests
|
||||
import requests.packages.urllib3.util.connection as urllib3_cn
|
||||
import logging
|
||||
|
||||
from .conf import *
|
||||
|
||||
fmt = logging.Formatter("%(levelname)s\t%(message)s")
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(fmt)
|
||||
ch.setLevel(logging.DEBUG)
|
||||
LOG = logging.getLogger(__name__)
|
||||
LOG.setLevel(logging.DEBUG)
|
||||
LOG.addHandler(ch)
|
||||
|
||||
tests = list()
|
||||
|
||||
# decorator to automatically add a function to tests
|
||||
def test(f):
|
||||
OK = "\033[1mOK\033[0m"
|
||||
KO = "\033[1m\033[1;%dmKO\033[0m" % 31
|
||||
global tests
|
||||
fname = f.__name__.ljust(50, '.')
|
||||
def w(iface):
|
||||
try:
|
||||
f(iface)
|
||||
LOG.info("{}{}".format(fname, OK))
|
||||
except AssertionError as e:
|
||||
LOG.info("{}{}: {}".format(fname, KO, e))
|
||||
tests.append(w)
|
||||
return w
|
||||
|
||||
def multicast(ip6):
|
||||
a, b = ip6.split(":")[-2:]
|
||||
mac = ["33", "33", "ff"]
|
||||
if len(a) == 4:
|
||||
mac.append(a[2:])
|
||||
else:
|
||||
mac.append("00")
|
||||
if len(b) >= 2:
|
||||
mac.append(b[:2])
|
||||
else:
|
||||
mac.append("00")
|
||||
if len(b) >= 4:
|
||||
mac.append(b[2:])
|
||||
else:
|
||||
mac.append("00")
|
||||
return ":".join(mac)
|
||||
|
||||
def check_ip_checksum(pkt):
|
||||
assert(IP in pkt), "no IP layer found"
|
||||
ip_pkt = pkt[IP]
|
||||
chksum = ip_pkt.chksum
|
||||
del ip_pkt.chksum
|
||||
assert(IP(raw(ip_pkt)).chksum == chksum), "bad IPv4 checksum"
|
||||
|
||||
def check_ipv6_checksum(pkt):
|
||||
assert(IPv6 in pkt), "no IP layer found"
|
||||
ip_pkt = pkt[IPv6]
|
||||
chksum = ip_pkt.chksum
|
||||
del ip_pkt.chksum
|
||||
assert(IPv6(raw(ip_pkt)).chksum == chksum), "bad IPv6 checksum"
|
||||
|
||||
@test
|
||||
def test_arp_req(iface):
|
||||
##### ARP #####
|
||||
arp_req = Ether()/ARP(psrc='192.0.0.2', pdst=IPV4_ADDR)
|
||||
arp_repl = iface.sr1(arp_req, timeout=1)
|
||||
assert(arp_repl is not None), "expecting answer, got nothing"
|
||||
assert(ARP in arp_repl), "no ARP layer found"
|
||||
arp_repl = arp_repl[ARP]
|
||||
# check answer
|
||||
## op is "is-at"
|
||||
assert(arp_repl.op == 2), "unexpected ARP op: {}".format(arp_repl.op)
|
||||
## answer for the requested IP
|
||||
assert(arp_repl.psrc == arp_req.pdst), "unexpected ARP psrc: {}".format(arp_repl.psrc)
|
||||
assert(arp_repl.pdst == arp_req.psrc), "unexpected ARP pdst: {}".format(arp_repl.pdst)
|
||||
## answer is expected MAC address
|
||||
assert(arp_repl.hwsrc == MAC_ADDR), "unexpected ARP hwsrc: {}".format(arp_repl.hwsrc)
|
||||
|
||||
@test
|
||||
def test_arp_req_other_ip(iface):
|
||||
##### ARP #####
|
||||
arp_req = Ether()/ARP(psrc='192.0.0.2', pdst='1.2.3.4')
|
||||
arp_repl = iface.sr1(arp_req, timeout=1)
|
||||
assert(arp_repl is None), "responding to ARP requests for other IP addresses"
|
||||
|
||||
@test
|
||||
def test_ipv4_req(iface):
|
||||
##### IP #####
|
||||
ip_req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR, id=0x1337)/ICMP(type=8, code=0)
|
||||
ip_repl = iface.sr1(ip_req, timeout=1)
|
||||
assert(ip_repl is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(ip_repl)
|
||||
assert(IP in ip_repl), "no IP layer in response"
|
||||
ip_repl = ip_repl[IP]
|
||||
assert(ip_repl.id == 0), "IP identification unexpected"
|
||||
|
||||
@test
|
||||
def test_eth_req_other_mac(iface):
|
||||
#### ETH ####
|
||||
ip_req = Ether(dst="00:00:00:11:11:11")/IP(dst=IPV4_ADDR)/ICMP(type=8, code=0)
|
||||
ip_repl = iface.sr1(ip_req, timeout=1)
|
||||
assert(ip_repl is None), "responding to other MAC addresses"
|
||||
|
||||
@test
|
||||
def test_ipv4_req_other_ip(iface):
|
||||
##### IP #####
|
||||
ip_req = Ether(dst=MAC_ADDR)/IP(dst="1.2.3.4")/ICMP(type=8, code=0)
|
||||
ip_repl = iface.sr1(ip_req, timeout=1)
|
||||
assert(ip_repl is None), "responding to other IP addresses"
|
||||
|
||||
@test
|
||||
def test_icmpv4_echo_req(iface):
|
||||
##### ICMPv4 #####
|
||||
icmp_req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/ICMP(type=8, code=0)/Raw("idrinkwaytoomuchcoffee")
|
||||
icmp_repl = iface.sr1(icmp_req, timeout=1)
|
||||
assert(icmp_repl is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(icmp_repl)
|
||||
assert(ICMP in icmp_repl)
|
||||
icmp_repl = icmp_repl[ICMP]
|
||||
# check answer
|
||||
## type is "echo-reply"
|
||||
assert(icmp_repl.type == 0)
|
||||
assert(icmp_repl.code == 0)
|
||||
## data is the same as sent
|
||||
assert(icmp_repl.load == icmp_req.load)
|
||||
|
||||
@test
|
||||
def test_icmpv6_neighbor_solicitation(iface):
|
||||
##### IPv6 Neighbor Solicitation #####
|
||||
for mac in ["ff:ff:ff:ff:ff:ff", "33:33:00:00:00:01", MAC_ADDR, multicast(IPV6_ADDR)]:
|
||||
nd_ns = Ether(dst=mac)/IPv6()/ICMPv6ND_NS(tgt=IPV6_ADDR)
|
||||
nd_na = iface.sr1(nd_ns, timeout=1)
|
||||
assert(nd_na is not None), "expecting answer, got nothing"
|
||||
assert(ICMPv6ND_NA in nd_na)
|
||||
nd_na = nd_na[ICMPv6ND_NA]
|
||||
# check answer content
|
||||
assert(nd_na.code == 0)
|
||||
assert(nd_na.R == 0)
|
||||
assert(nd_na.S == 1)
|
||||
assert(nd_na.O == 1)
|
||||
assert(nd_na.tgt == IPV6_ADDR)
|
||||
# check ND Option
|
||||
assert(nd_na.haslayer(ICMPv6NDOptDstLLAddr))
|
||||
assert(nd_na.getlayer(ICMPv6NDOptDstLLAddr).lladdr == MAC_ADDR)
|
||||
for mac in ["00:00:00:00:00:00", "33:33:33:00:00:01"]:
|
||||
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff")/IPv6()/ICMPv6ND_NS(tgt=IPV6_ADDR)
|
||||
nd_na = iface.sr1(nd_ns, timeout=1)
|
||||
assert(nd_na is not None), "expecting no answer, got one"
|
||||
|
||||
@test
|
||||
def test_icmpv6_neighbor_solicitation_other_ip(iface):
|
||||
##### IPv6 Neighbor Solicitation #####
|
||||
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff")/IPv6()/ICMPv6ND_NS(tgt="2020:4141:3030:2020::bdbd")
|
||||
nd_na = iface.sr1(nd_ns, timeout=1)
|
||||
assert(nd_na is None), "responding to ND_NS for other IP addresses"
|
||||
|
||||
@test
|
||||
def test_icmpv6_echo_req(iface):
|
||||
##### IPv6 Ping #####
|
||||
echo_req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/ICMPv6EchoRequest(data="waytoomanynapkins")
|
||||
echo_repl = iface.sr1(echo_req, timeout=1)
|
||||
assert(echo_repl is not None), "expecting answer, got nothing"
|
||||
assert(ICMPv6EchoReply in echo_repl)
|
||||
echo_repl = echo_repl[ICMPv6EchoReply]
|
||||
# check answer content
|
||||
assert(echo_repl.code == 0)
|
||||
assert(echo_repl.data == echo_req.data)
|
||||
|
||||
@test
|
||||
def test_tcp_syn(iface):
|
||||
##### SYN-ACK #####
|
||||
# test a list of ports, randomly generated once
|
||||
ports_to_test = [
|
||||
1152, 2003, 2193, 3709, 4054, 6605, 6737, 6875, 7320, 8898, 9513, 9738, 10623, 10723,
|
||||
11253, 12125, 12189, 12873, 14648, 14659, 16242, 16243, 17209, 17492, 17667, 17838,
|
||||
18081, 18682, 18790, 19124, 19288, 19558, 19628, 19789, 20093, 21014, 21459, 21740,
|
||||
24070, 24312, 24576, 26939, 27136, 27165, 27361, 29971, 31088, 33011, 33068, 34990,
|
||||
35093, 35958, 36626, 36789, 37130, 37238, 37256, 37697, 37890, 38958, 42131, 43864,
|
||||
44420, 44655, 44868, 45157, 46213, 46497, 46955, 49049, 49067, 49452, 49480, 50498,
|
||||
50945, 51181, 52890, 53301, 53407, 53417, 53980, 55827, 56483, 58552, 58713, 58836,
|
||||
59362, 59560, 60534, 60555, 60660, 61615, 62402, 62533, 62941, 63240, 63339, 63616,
|
||||
64380, 65438,
|
||||
]
|
||||
for p in ports_to_test:
|
||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", dport=p)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
|
||||
@test
|
||||
def test_ipv4_tcp_psh_ack(iface):
|
||||
##### PSH-ACK #####
|
||||
sport = 26695
|
||||
port = 445
|
||||
# send PSH-ACK first
|
||||
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port)/Raw("payload")
|
||||
syn_ack = iface.sr1(psh_ack, timeout=1)
|
||||
assert(syn_ack is None), "no answer expected, got one"
|
||||
# test the anti-injection mechanism
|
||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", dport=port)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", dport=port)
|
||||
# should fail because no ack given
|
||||
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port)
|
||||
ack = iface.sr1(psh_ack, timeout=1)
|
||||
assert(ack is None), "no answer expected, got one"
|
||||
# should get an answer this time
|
||||
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port, ack=syn_ack.seq + 1)
|
||||
ack = iface.sr1(psh_ack, timeout=1)
|
||||
assert(ack is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(ack)
|
||||
assert(TCP in ack)
|
||||
ack = ack[TCP]
|
||||
assert(ack.flags == "A")
|
||||
|
||||
@test
|
||||
def test_ipv6_tcp_psh_ack(iface):
|
||||
##### PSH-ACK #####
|
||||
sport = 26695
|
||||
port = 445
|
||||
# send PSH-ACK first
|
||||
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port)/Raw("payload")
|
||||
syn_ack = iface.sr1(psh_ack, timeout=1)
|
||||
assert(syn_ack is None), "no answer expected, got one"
|
||||
# test the anti-injection mechanism
|
||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", dport=port)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", dport=port)
|
||||
# should fail because no ack given
|
||||
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port)
|
||||
ack = iface.sr1(psh_ack, timeout=1)
|
||||
assert(ack is None), "no answer expected, got one"
|
||||
# should get an answer this time
|
||||
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port, ack=syn_ack.seq + 1)
|
||||
ack = iface.sr1(psh_ack, timeout=1)
|
||||
assert(ack is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(ack)
|
||||
assert(TCP in ack)
|
||||
ack = ack[TCP]
|
||||
assert(ack.flags == "A")
|
||||
|
||||
@test
|
||||
def test_ipv4_tcp_http(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
||||
_ = iface.sr1(ack, timeout=1)
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(resp)
|
||||
assert(TCP in resp)
|
||||
tcp = resp[TCP]
|
||||
assert(tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
||||
|
||||
@test
|
||||
def test_ipv6_tcp_http(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
||||
_ = iface.sr1(ack, timeout=1)
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(resp)
|
||||
assert(TCP in resp)
|
||||
tcp = resp[TCP]
|
||||
assert(tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
||||
|
||||
@test
|
||||
def test_ipv4_udp_http(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(resp)
|
||||
assert(UDP in resp)
|
||||
udp = resp[UDP]
|
||||
assert(udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
||||
|
||||
@test
|
||||
def test_ipv6_udp_http(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(resp)
|
||||
assert(UDP in resp)
|
||||
udp = resp[UDP]
|
||||
assert(udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
||||
|
||||
@test
|
||||
def test_ipv4_tcp_http_ko(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
||||
_ = iface.sr1(ack, timeout=1)
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(resp)
|
||||
assert(TCP in resp)
|
||||
assert("P" not in resp[TCP].flags)
|
||||
assert(len(resp[TCP].payload) == 0)
|
||||
|
||||
@test
|
||||
def test_ipv4_udp_http_ko(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is None), "expecting no answer, got one"
|
||||
|
||||
@test
|
||||
def test_ipv6_tcp_http_ko(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
||||
_ = iface.sr1(ack, timeout=1)
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(resp)
|
||||
assert(TCP in resp)
|
||||
assert("P" not in resp[TCP].flags)
|
||||
assert(len(resp[TCP].payload) == 0)
|
||||
|
||||
@test
|
||||
def test_ipv6_udp_http_ko(iface):
|
||||
sport = 24592
|
||||
dports = [80, 443, 5000, 53228]
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is None), "expecting no answer, got one"
|
||||
|
||||
@test
|
||||
def test_ipv4_udp_stun(iface):
|
||||
sports = [12345, 55555, 80, 43273]
|
||||
dports = [80, 800, 8000, 3478]
|
||||
payload = bytes.fromhex("000100002112a442000000000000000000000000")
|
||||
for sport in sports:
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(resp)
|
||||
assert(UDP in resp), "no UDP layer found"
|
||||
udp = resp[UDP]
|
||||
assert(udp.sport == dport), "unexpected UDP sport: {}".format(udp.sport)
|
||||
assert(udp.dport == sport), "unexpected UDP dport: {}".format(udp.dport)
|
||||
resp_payload = udp.payload.load
|
||||
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
|
||||
tid = resp_payload[8:20]
|
||||
data = resp_payload[20:]
|
||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||
assert(length == 12), "expected length 12, got {}".format(length)
|
||||
assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
|
||||
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"
|
||||
|
||||
@test
|
||||
def test_ipv6_udp_stun(iface):
|
||||
sports = [12345, 55555, 80, 43273]
|
||||
dports = [80, 800, 8000, 3478]
|
||||
payload = bytes.fromhex("000100002112a442000000000000000000000000")
|
||||
for sport in sports:
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(resp)
|
||||
assert(UDP in resp)
|
||||
udp = resp[UDP]
|
||||
assert(udp.sport == dport)
|
||||
assert(udp.dport == sport)
|
||||
resp_payload = udp.payload.load
|
||||
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
|
||||
tid = resp_payload[8:20]
|
||||
data = resp_payload[20:]
|
||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||
assert(length == 24), "expected length 24, got {}".format(length)
|
||||
assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
|
||||
assert(tid == b'\x00' * 12), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
|
||||
assert(data == bytes.fromhex("000100140002") + struct.pack(">H", sport) + bytes.fromhex("00000000" * 4)), "unexpected data: {}".format(data)
|
||||
|
||||
@test
|
||||
def test_ipv4_udp_stun_change_port(iface):
|
||||
sports = [12345, 55555, 80, 43273]
|
||||
dports = [80, 800, 8000, 3478, 65535]
|
||||
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
|
||||
for sport in sports:
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(resp)
|
||||
assert(UDP in resp), "no UDP layer found"
|
||||
udp = resp[UDP]
|
||||
assert(udp.sport == (dport + 1) % 2**16), "expected answer from UDP/{}, got it from UDP/{}".format((dport + 1) % 2**16, udp.sport)
|
||||
assert(udp.dport == sport), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
|
||||
resp_payload = udp.payload.load
|
||||
type_, length = struct.unpack(">HH", resp_payload[:4])
|
||||
tid = resp_payload[4:20]
|
||||
data = resp_payload[20:]
|
||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||
assert(length == 12), "expected length 12, got {}".format(length)
|
||||
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"
|
||||
|
||||
@test
|
||||
def test_ipv6_udp_stun_change_port(iface):
|
||||
sports = [12345, 55555, 80, 43273]
|
||||
dports = [80, 800, 8000, 3478, 65535]
|
||||
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
|
||||
for sport in sports:
|
||||
for dport in dports:
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(resp)
|
||||
assert(UDP in resp), "expecting UDP layer in answer, got nothing"
|
||||
udp = resp[UDP]
|
||||
assert(udp.sport == (dport + 1) % 2**16), "expected answer from UDP/{}, got it from UDP/{}".format((dport + 1) % 2**16, udp.sport)
|
||||
assert(udp.dport == sport), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
|
||||
resp_payload = udp.payload.load
|
||||
type_, length = struct.unpack(">HH", resp_payload[:4])
|
||||
tid = resp_payload[4:20]
|
||||
data = resp_payload[20:]
|
||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||
assert(length == 24), "expected length 12, got {}".format(length)
|
||||
assert(tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c")), "expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
|
||||
assert(data == bytes.fromhex("000100140002") + struct.pack(">H", sport) + bytes.fromhex("00000000" * 4))
|
||||
|
||||
@test
|
||||
def test_ipv4_tcp_ssh(iface):
|
||||
sport = 37183
|
||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||
for i, dport in enumerate(dports):
|
||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
||||
_ = iface.sr1(ack, timeout=1)
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(resp)
|
||||
assert(TCP in resp)
|
||||
tcp = resp[TCP]
|
||||
assert("A" in tcp.flags), "expecting ACK flag, not set (%r)" % tcp.flags
|
||||
assert("P" in tcp.flags), "expecting PSH flag, not set (%r)" % tcp.flags
|
||||
assert(len(tcp.payload) > 0), "expecting payload, got none"
|
||||
assert(tcp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % tcp.payload.load
|
||||
assert(tcp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % tcp.payload.load
|
||||
|
||||
@test
|
||||
def test_ipv4_udp_ssh(iface):
|
||||
sport = 37183
|
||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||
for i, dport in enumerate(dports):
|
||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ip_checksum(resp)
|
||||
assert(UDP in resp)
|
||||
udp = resp[UDP]
|
||||
assert(len(udp.payload) > 0), "expecting payload, got none"
|
||||
assert(udp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % udp.payload.load
|
||||
assert(udp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % udp.payload.load
|
||||
|
||||
@test
|
||||
def test_ipv6_tcp_ssh(iface):
|
||||
sport = 37183
|
||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||
for i, dport in enumerate(dports):
|
||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
||||
syn_ack = iface.sr1(syn, timeout=1)
|
||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(syn_ack)
|
||||
assert(TCP in syn_ack)
|
||||
syn_ack = syn_ack[TCP]
|
||||
assert(syn_ack.flags == "SA")
|
||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
||||
_ = iface.sr1(ack, timeout=1)
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(resp)
|
||||
assert(TCP in resp)
|
||||
tcp = resp[TCP]
|
||||
assert("A" in tcp.flags), "expecting ACK flag, not set (%r)" % tcp.flags
|
||||
assert("P" in tcp.flags), "expecting PSH flag, not set (%r)" % tcp.flags
|
||||
assert(len(tcp.payload) > 0), "expecting payload, got none"
|
||||
assert(tcp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % tcp.payload.load
|
||||
assert(tcp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % tcp.payload.load
|
||||
|
||||
@test
|
||||
def test_ipv6_udp_ssh(iface):
|
||||
sport = 37183
|
||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||
for i, dport in enumerate(dports):
|
||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
||||
resp = iface.sr1(req, timeout=1)
|
||||
assert(resp is not None), "expecting answer, got nothing"
|
||||
check_ipv6_checksum(resp)
|
||||
assert(UDP in resp)
|
||||
udp = resp[UDP]
|
||||
assert(len(udp.payload) > 0), "expecting payload, got none"
|
||||
assert(udp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % udp.payload.load
|
||||
assert(udp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % udp.payload.load
|
||||
|
||||
def test_all(iface):
|
||||
global tests
|
||||
# execute tests
|
||||
for t in tests:
|
||||
t(iface)
|
20
test/src/conf.py
Normal file
20
test/src/conf.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# 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/>.
|
||||
|
||||
IPV4_ADDR = "192.0.0.1"
|
||||
IPV6_ADDR = "2001:41d0::ab32:bdb8"
|
||||
MAC_ADDR = "52:1c:4e:c2:a4:1f"
|
||||
OUTDIR = "test/res/"
|
78
test/test_masscanned.py
Executable file
78
test/test_masscanned.py
Executable file
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# 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/>.
|
||||
|
||||
from scapy.all import *
|
||||
from time import sleep
|
||||
from tempfile import _get_candidate_names as gen_tmp_filename
|
||||
from tempfile import gettempdir
|
||||
import subprocess
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
from src.all import test_all
|
||||
from src.conf import *
|
||||
|
||||
# if args in CLI, they are passed to masscanned
|
||||
if len(sys.argv) > 1:
|
||||
args = " ".join(sys.argv[1:])
|
||||
else:
|
||||
args = ""
|
||||
|
||||
fmt = logging.Formatter("%(levelname)s\t%(message)s")
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(fmt)
|
||||
ch.setLevel(logging.INFO)
|
||||
LOG = logging.getLogger(__name__)
|
||||
LOG.setLevel(logging.INFO)
|
||||
LOG.addHandler(ch)
|
||||
|
||||
conf.iface = 'tap0'
|
||||
conf.verb = 0
|
||||
|
||||
# prepare configuration file for masscanned
|
||||
ipfile = os.path.join(gettempdir(), next(gen_tmp_filename()))
|
||||
with open(ipfile, "w") as f:
|
||||
f.write("{}\n".format(IPV4_ADDR))
|
||||
f.write("{}\n".format(IPV6_ADDR))
|
||||
|
||||
# create test interface
|
||||
tap = TunTapInterface(resolve_iface(conf.iface))
|
||||
|
||||
# set interface
|
||||
subprocess.run("ip a a dev {} 192.0.0.2".format(conf.iface), shell=True)
|
||||
subprocess.run("ip link set {} up".format(conf.iface), shell=True)
|
||||
|
||||
# start capture
|
||||
tcpdump = subprocess.Popen("tcpdump -enli {} -w {}".format(conf.iface, os.path.join(OUTDIR, "test_capture.pcap")), shell=True,
|
||||
stdin=None, stdout=None, stderr=None, close_fds=True)
|
||||
# run masscanned
|
||||
masscanned = subprocess.Popen("RUST_BACKTRACE=1 ./target/debug/masscanned -vvvvv -i {} -f {} -a {} {}".format(conf.iface, ipfile, MAC_ADDR, args), shell=True,
|
||||
stdin=None, stdout=open("test/res/masscanned.stdout", "w"), stderr=open("test/res/masscanned.stderr", "w"), close_fds=True)
|
||||
sleep(1)
|
||||
|
||||
try:
|
||||
test_all(tap)
|
||||
except AssertionError:
|
||||
pass
|
||||
|
||||
# terminate masscanned
|
||||
masscanned.kill()
|
||||
# terminate capture
|
||||
sleep(2)
|
||||
tcpdump.kill()
|
Loading…
Add table
Add a link
Reference in a new issue