Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor greenlet handling and fix related bugs #493

Merged
merged 9 commits into from Nov 4, 2020

Conversation

srenfo
Copy link
Contributor

@srenfo srenfo commented May 12, 2020

This PR refactors all greenlet handling, especially in test code, and fixes all bugs that were exposed in the process.

I'm sorry that this is such a big PR but it's next to impossible to chop it into independent pieces. It may be best to view this commit by commit or as a side-by-side diff rather than a unified diff.

Highlights:

  • Refactor the debugging __main__s in the protocol servers into a single script in bin/start_protocol.py. Those mains would otherwise have been a major pain to keep up to date with these changes. The script should be able to start all protocol individually. It keeps the previous port choices where there were any, though that may be more confusing than necessary.
  • Refactor starting and stopping of servers and greenlets in protocol tests. This also includes databus init, paths to XML files, etc.
  • The test suite is much faster (by almost exactly 50% on my machine). Execution time of the TFTP tests alone went down from 12228ms to 119ms on my machine. That is not a typo. (I'll have to eat those words if it turns out the new code is broken.)
  • bin/conpot now waits for greenlets to shut down.
  • The startup delay of five seconds is gone.

There are new tests for the refactored utility code.

Bug fixes:

  • conpot_protocol tried to override some class properties but failed
  • Modbus and SNMP servers were never properly shut down
  • Modbus and IEC104 would not block in their start() method, contrary to all other protocols

Tests for these bugs are included. (All those tests would fail on the current master.)

Caveats:

  • There's a single TODO in the PR in greenlets.py. The assumption that the protocol's start() is fairly simple does not hold true for the HTTP server, so it is special cased there. That said, the new tests would/should catch such cases (they did for HTTP). It's hard to be certain.
  • Testing of the changes consisted of running the test suite and checking that bin/conpot still started up with the default template. No other functional testing was done. That's because the changes are focussed on test code.

@srenfo srenfo changed the title Rerfactor greenlet handling and fix related bugs Refactor greenlet handling and fix related bugs May 12, 2020
@srenfo
Copy link
Contributor Author

srenfo commented May 12, 2020

Let me know if you want me to remove parts of this or split it up in a certain way.

@xandfury
Copy link
Member

Thanks for your contribution 🙂 Would need some time to review this. I hope that's okay.

@srenfo
Copy link
Contributor Author

srenfo commented May 13, 2020

If this proves too unwieldy I can split it into multiple PRs that build on each other.

from gevent.server import StreamServer
import gevent
from conpot.helpers import chr_py3
import conpot.core as conpot_core
from conpot.protocols.kamstrup.meter_protocol import request_parser
from conpot.protocols.kamstrup.meter_protocol.command_responder import CommandResponder
from conpot.core.protocol_wrapper import conpot_protocol
# import logging as logger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave these comments here. It's super useful when debugging 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dislike commented out code. Commented out code tends to rot. It's clutter. It tends to break over time because it's not tested. If you notice it you don't know whether to maintain it or not: is it obsolete? Was it left there intentionally? Or unintentionally?

Thus I tend to remove commented out code. With a merge request of this size that was likely a bad idea because the change actually just adds clutter (hah) for the reviewer.

Do you really want to keep such code though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in this specific case, there was even a line below (former line 32) that was incompatible or at least redundant with the commented out code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xandfury how about adding this code into a debug function instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srenfo I don't think so.It works pretty well for me. I don't it's going to rot ~ unless it is generating code smell which it won't since it's commented. Again that's just my opinion. Others are free to disagree.

@glaslos Sure. That might be a better solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just so happens that I can illustrate my point. 😎 (This was not intentional but here we are.)

You may have noticed two rogue logging lines on every Conpot startup:

$ conpot -f --template default
WARNING:scapy.loading:Cannot read wireshark manuf database
WARNING:scapy.runtime:No route found for IPv6 destination :: (no default route?)

                       _
   ___ ___ ___ ___ ___| |_
  |  _| . |   | . | . |  _|
  |___|___|_|_|  _|___|_|
              |_|

  Version 0.6.0
  MushMush Foundation
2020-05-26 15:20:47,753 --force option specified. Using testing configuration
...

Those are (in part) caused by this line at the top of ipmi_server.py:

logger.basicConfig(stream=sys.stdout, level=logger.DEBUG)

which I heavily suspect was left in there as a debugging aid, maybe accidentally. (The second reason for those two lines is that BACpypes 0.17.0 adds a root-level logger in its debugging.py. You need both for those lines to appear.)

The offending line was removed in the PR, though I admit this was an unintentional change when I gobbled up all the debugging code.

Re: "debugging function" - that is the intended purpose of start_protocol.py! This seems to be a common misunderstanding throughout the discussion here. I'll try to address it in a separate comment below.

To move forward with the PR I will need directions though: do you want me to restore this code, yes or no?

@@ -110,16 +107,3 @@ def start(self, host, port):

def stop(self):
self.server.stop()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't impact the implementation. Some devs use it to debug the protocol locally. Super helpful. I think we can leave it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These bits were refactored into start_protocol.py in 3689964. The
next commit c4dad04 then uses that groundwork to test starting and stopping all protocols individually.

Some other changes in this MR would have made it necessary to rewrite these sections. I tried to do that in only one location that was used by both tests and start_protocol.py.

I can reinstate the separate sections (albeit rewritten) in lieu of start_protocol.py if you want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as in keeping the __main__ section and using start_protocol for initiating debugging?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to add here: with __main__ you can just go the file itself and starting the protocol with something like python3 <protcol_name>.py Again it's just useful for debugging the protocol locally.

It is not used anywhere for running the protocols which is done conpot executable. So totally in favour of keeping the __main__ sections.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start_protocol.py was added for exactly this purpose. It accepts a single command line parameter which identifies the protocol, i. e. start_protocol.py <protocol_name>.

Thus, instead of

python3 kamstrup_server.py

you do

python3 start_protocol.py kamstrup_meter

Advantages are that the startup code is consistently the same, i.e. the protocols are run the same way the tests and (more or less) bin/conpot run them, and that the code is in one place rather than scattered throughout.

Disadvantages are that the code is not as close to the protocols as before and that if you want to play around with the startup logic of one protocol it affects all of them. (The latter may be an advantage, given that you want consistency and all.)

I can remove start_protocols.py and restore the __main__ sections or keep it as is.

bin/conpot Outdated
@@ -16,7 +16,7 @@
# Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import gevent.monkey; gevent.monkey.patch_all()
from gevent import monkey; monkey.patch_all()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this change different from what it was previously? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way all monkey patching lines consistently use the same syntax. Before they differed from file to file.

Really it stems from my IDE preferring from foo import bar over import foo.bar.

It's a non-functional change so I can undo it if you want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinion on this one. If your IDE otherwise complains, keep it :)

Copy link
Member

@glaslos glaslos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work, much appreciated! Love the refactoring and centralization of all gevent code.
I think if you find an elegant solution for the debugging/testing questions from @xandfury , this is almost ready to go.
How did you end up working on Conpot?

bin/conpot Outdated
@@ -16,7 +16,7 @@
# Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import gevent.monkey; gevent.monkey.patch_all()
from gevent import monkey; monkey.patch_all()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinion on this one. If your IDE otherwise complains, keep it :)

from gevent.server import StreamServer
import gevent
from conpot.helpers import chr_py3
import conpot.core as conpot_core
from conpot.protocols.kamstrup.meter_protocol import request_parser
from conpot.protocols.kamstrup.meter_protocol.command_responder import CommandResponder
from conpot.core.protocol_wrapper import conpot_protocol
# import logging as logger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xandfury how about adding this code into a debug function instead?

@@ -110,16 +107,3 @@ def start(self, host, port):

def stop(self):
self.server.stop()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as in keeping the __main__ section and using start_protocol for initiating debugging?

@xandfury
Copy link
Member

Good job @srenfo 🎉 I'll be happy to merge your changes. I can live without logger comments. But I strongly believe that we should keep the __main__ sections where ever they were added.

@srenfo
Copy link
Contributor Author

srenfo commented May 19, 2020

Sorry, I'm rather busy at work right now. It may take another week or so but I'll come back to this. Ping me otherwise.

@srenfo
Copy link
Contributor Author

srenfo commented May 26, 2020

I appreciate the kind words @glaslos and @xandfury, thank you!

How did you end up working on Conpot?

Primarily, it's convenient if you want to quickly spin up a server of an ICS protocol. For example, we're currently doing research work on/with IEC104 and sometimes you just want a random IEC104 server to talk to. 😄 For a previous research project we actually had it deployed as a honeypot, though not much came from that unfortunately.

So after using it for some time it's only appropriate to give back here and there.

We have contributed a few small PRs before, we just haven't tagged ourselves as a group.

@glaslos glaslos requested a review from xandfury July 5, 2020 11:07
@glaslos
Copy link
Member

glaslos commented Jul 5, 2020

@xandfury any remaining concerns?

@xandfury
Copy link
Member

xandfury commented Jul 8, 2020

Hey @srenfo. The changes look good to me. Can you fix the conflicts so that we can merge?

@glaslos
Copy link
Member

glaslos commented Jul 9, 2020

@srenfo you probably want to run black over your branch before merging. Should be easier than fixing the merge conflicts.

@srenfo
Copy link
Contributor Author

srenfo commented Jul 20, 2020

@srenfo you probably want to run black over your branch before merging. Should be easier than fixing the merge conflicts.

Are you sure this is a good idea with master currently failing? (I haven't looked into why it's failing exactly.) I can hold off until that's fixed.

Alternatively you could revert the broken commits (or actually remove them from master with a force push), reopen #501 and do the fixes on the branch there. That would require eventually rebasing that branch of course if you wanted to merge this branch here in the meantime.

@glaslos
Copy link
Member

glaslos commented Oct 27, 2020

@srenfo if you give me permissions to change the PR I can help with resolving the conflicts

@glaslos glaslos self-requested a review October 28, 2020 14:15
@srenfo
Copy link
Contributor Author

srenfo commented Nov 2, 2020

@srenfo if you give me permissions to change the PR I can help with resolving the conflicts

The conflicts should be resolved.

There's a chance Travis will complain since master has changed since this was first opened. Fingers crossed. 🤞

@glaslos
Copy link
Member

glaslos commented Nov 2, 2020

hm, maybe merge master to get the git action to run?

@srenfo
Copy link
Contributor Author

srenfo commented Nov 2, 2020

Trying again... it's rebased on the current master, so a merge does nothing.

What's weird is that I've used this workflow before and it had always triggered the build (AFAICT).

I've also (re-)enabled "Allow edits and access to secrets by maintainers", though I believe it was the same before. Do you need me to do anything else?

@srenfo
Copy link
Contributor Author

srenfo commented Nov 2, 2020

I've even added an empty commit to try to force the build to trigger, but apparently it's just slow.

As of this writing, the CI build is queued: https://travis-ci.org/github/mushorg/conpot/pull_requests

You can see that the black check succeeded in my fork (i. e. the source of this PR): https://github.com/srenfo/conpot/runs/1343712255

@coveralls
Copy link

Pull Request Test Coverage Report for Build 1236

  • 71 of 72 (98.61%) changed or added relevant lines in 10 files are covered.
  • 85 unchanged lines in 8 files lost coverage.
  • Overall coverage increased (+0.3%) to 73.619%

Changes Missing Coverage Covered Lines Changed/Added Lines %
conpot/utils/greenlet.py 40 41 97.56%
Files with Coverage Reduction New Missed Lines %
conpot/core/protocol_wrapper.py 1 95.45%
conpot/protocols/s7comm/s7_server.py 2 64.66%
conpot/protocols/http/command_responder.py 3 60.82%
conpot/protocols/IEC104/IEC104_server.py 5 86.02%
conpot/protocols/kamstrup/management_protocol/kamstrup_management_server.py 8 83.64%
conpot/protocols/enip/enip_server.py 20 65.14%
conpot/protocols/ftp/ftp_base_handler.py 20 83.08%
conpot/protocols/ftp/ftp_handler.py 26 81.23%
Totals Coverage Status
Change from base Build 1233: 0.3%
Covered Lines: 5545
Relevant Lines: 7532

💛 - Coveralls

@glaslos
Copy link
Member

glaslos commented Nov 2, 2020

Maybe the new pricing policies are having an effect? https://blog.travis-ci.com/2020-11-02-travis-ci-new-billing

@glaslos glaslos merged commit 0c5f4e1 into mushorg:master Nov 4, 2020
@glaslos
Copy link
Member

glaslos commented Nov 4, 2020

Alright, let's get this merged. Travis seems a bit confused due to the force-push.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants