KSP-KOS / KOS

Fully programmable autopilot mod for KSP. Originally By Nivekk
Other
691 stars 229 forks source link

Optional Telnet Server for KoS Terminal #157

Closed IronGremlin closed 9 years ago

IronGremlin commented 10 years ago

It would be awesome to telnet into a KoS terminal from another computer over your network.

The 'killer application' of this feature would be for using KoS in conjunction with KSP 'LARP' set-ups, see also the mod "Go At Throttle Up."

I propose allowing the pilot to flag a particular part as 'active' via a right-click context menu option (ideally this should be 'action group'-able) as a solution to potential 'multiple CPUs one vessel' conflicts - This also fits really nicely with the GATU style game play, where use of KoS scripts would require two way communication between mission control and the pilot.

Ideally said telnet server should be configurable via plain text files, allowing user/pass authentication, port number specification, and the ability to outright disable the telnet server altogether. Also, perhaps obviously, the server should default to 'inactive' on any part until the player activates it.

Dunbaratu commented 10 years ago

I'd had my hands in the terminal code a lot when looking into the line wrap issues, and I think it would actually be quite messy to support both the in-game terminal and a telnet stream with the same code because of the differences in how cursor position and sub-buffers is handled between the two techniques. Supporting a streamed interface would probably mean changing the code to issue escape codes to control the terminal, and then re-implementing the in-game terminal to itself also be controlled by said escape sequences like a real serial cable terminal would be.

ghost commented 10 years ago

ugh telnet, security, .... please don't. at least ship kOS with that thing OFF by default if you ever do this.

Dunbaratu commented 10 years ago

Well, at most it would only open you up to someone messing with your game. It's not like it would be a telnet to a shell prompt. And even then it would only be a vulnerability if your home network has telnet port forwarded, which you'd have to go out of your way to do. But at any rate, the exact same idea could be implemented using an SSH server instead. I don't think any of us is interested in writing a telnet server from the ground up, so any solution would involve using a third-party library to implement the server, and at that point it may as well be an SSH library instead of a telnet library.

If it was on a unix server, I wouldn't even use an ssh library. I'd just run the ssh server daemon that it's safe to assume a unix server comes with, and speak to it using a pseudo-tty file. But I don't know Windows well enough to know how to do a similar setup.

But at any rate, the change from the current system that directly manipulates the terminal's screenbuffer into a system that operates on something like an ANSI escape sequence would be the first hurdle to overcome. Without that, trying to have a remote stream'ed terminal is an idea that's dead in the water before it even starts.

ghost commented 10 years ago

"only messing with the game" until someone finds an exploitable overflow to get out of KSP. It happens all the time, really - especially so with non-network code that suddenly gets network enabled by something.

And while I'm not too concerned about myself, the "local network" on a university campus / in a students home ( [...] ) can be pretty large and anonymous.

Anyway, its way too early to get agitated about this ;). The idea from IronGremlin generally is a good one.

IronGremlin commented 10 years ago

With regard to security -

I was actually assuming that the telnet server simply wouldn't be serving telnet until the player activated it manually from within KSP - When a CPU is flagged as active, the telnet server is now listening on the configured port, when a vessel is unloaded, or the terminal gets flagged as inactive, it closes any open sessions and stops listening.

Additionally, you could limit the number of sessions to 1, with authentication required.

Manual gate-keeping is not a good security solution, but I think considering the extremely limited application, it could prove sufficient.

pjf commented 10 years ago

"only messing with the game" until someone finds an exploitable overflow to get out of KSP.

This. I agree with this so hard.

I actually like the idea of a telnet (or ssh) connection into kOS. However I think it must:

Bonus points if there's a colourblind friendly status indicator that signals (a) that remote connections are enabled, and (b) a remote connection is active.

For what it's worth, I also like the idea of exposing a REST (or other) API, because then I can plug all sorts of things into kOS/KSP relatively easily, but that may be beyond the scope of this ticket.

Dunbaratu commented 10 years ago

Overflow exploits are easy in a language like C that allows pointers and stores things in memory buffers using them. They're a lot harder in a language like Java or C# that only lets you use references, and doesn't allow strings to overflow.

But it's all moot because using an ssh library is no more work than a telnet library, so there's no reason to bother using just a telnet library.

SolarLiner commented 10 years ago

I've been waiting so hard for this, in fact I already tried this with GATU so I have a fork ready on my computer, with a page already created that have a line input, a output block and an information readout that displays the CPU name/number, status of the terminal (if active and if script running). For this a "simple" interface where we would type commands like on the regular terminal would be more than enough, and maybe triggering events through scripts (like "RaiseEvent("Out of fuel", LEVEL_DANGER)").

Now in case you don't already know about it there is PuTTY that is the most robust and lightweight telnet/ssh client on the internet.

This telnet feature needs to happen.

Dunbaratu commented 10 years ago

I'm aware of PuTTY. While it's the most lightweight ssh/telnet client that has its own terminal emulator and all the associated GUI code that goes with that, It is not the most lightweight ssh/telnet client on the internet. Command-line ssh/telnet clients tend to be lighter-weight because they just handle the data stream and that's it. They don't also need to be terminal emulators like PuTTY has to be. They're meant to be run from prompts that are already inside other terminal emulator programs.

At any rate the biggest problem is NOT piping the stream through a server. It's editing the terminal handler that's there right now so it actually operates on a stream in the first place, which it currently doesn't. When you execute PRINT "blarg" AT (x,y), it's not issuing a set of escape characters that move the cursor to x,y. It's just directly manipulating the screenbuffer's contents at that location. That style of terminal control is not how serial stream terminals (like PuTTY) work.

If this idea was seriously pursued, it would first require that we re-write the terminal emulator to implement a subset of escape codes from a known common terminal type, like ANSI or VT220, and then access the terminal through those codes rather than directly. Then and only then could we talk about piping that stream of characters through a stream protocol like ssh or telnet so that a client like PuTTY can see the same thing the in-game terminal can.

I'm not saying it's impossible, or that it's a bad idea.. just that its not as easy as people are making it out to be.

The reason it's easy to pipe a lot of common internet programs through ssh or telnet is because they're already designed to operate on a serial stream. The kOS terminal isn't. Maybe it should be. It's something to consider for one of the future goals of enabling user input (which would also have to be streamable for the terminal to work over a socket).

SolarLiner commented 10 years ago

I didn't even think about PRINT AT. Indeed that is a problem, and a temporary solution might be to disable those and revert to the default behaviour of PRINT. Or maybe manipulate rows and columns is easy enough…

And for PuTTY, being under a megabyte its for me pretty lightweight enough to not search for alternatives. And I didn't really want to say that its the most lightweight, but it definitely is one good.

Anyway I'm really looking forward to remotely use kOS, good luck ! :)

Dunbaratu commented 10 years ago

To impress upon people the magnitude of the problem, here's another example: You know when you up-arrow to get the previous command back and it lets you arrow-around across a command that spans multiple lines of the screen? That's all being handled by telling the terminal to hold a sub-buffer of text superimposed on top of its normal screen-buffer, and then once every (your framerate here) times per second it tells the terminal to redraw all the characters in its screen buffers, including the sub-buffers superimposed on top of the main buffer.

The current design really is not stream-friendly at all. I'm not a fan of the current design, and it might be nice to change it, but don't expect it to happen quickly.

It might be worth visiting the idea as part of a later implementation of keyboard input, since it might be better to change the terminal to a streaming interface at that point and then make a pseudo-stream out of the KSP inputs to feed to the kOS mod.

SolarLiner commented 10 years ago

I guess too you can do a parallel implementation with a sort of "Standard Output" that handles "normal" output text (ie: not the PRINT AT nor the command history, which should be only on the ingame terminal) and then deal with special cases. I mean, I do not know the internals of the code but before the graphics layer is added, its like 95% of pure text stream right?

marianoapp commented 10 years ago

The current design makes easy to overlap and merge different text buffers into a single output, like superimposing the RT2 delay progressbar on top of whatever you have on the screen. If you want to support a telnet connection all you have to do is create a new interpreter class that inherits from the main interpreter that opens the socket and handle the telnet protocol. When you receive a control sequence that says move cursor, you parse it and call the base class MoveCursor method. I'm not really familiar with the telnet protocol but you can override any of the ScreenBuffer class methods to retrieve the console text and convert it into a telnet-friendly format.

Dunbaratu commented 10 years ago

What makes it messy is that streamed escape codes tend to be a write-only operation. You can't relay on being able to query the terminal and ask it to tell you what's in its screenbuffer - instead you have to track on the server's side the entire state of what the server has written to the terminal so far and make sure the server's mental picture of the screen buffer actually matches the commands you've sent.

It's not impossible. Just work.

What it would really take to set it up properly is to sever our own terminal entirely from any entanglement with the other kOS classes, and turn it into an object that can only be communicated with via a stream of characters that contain control codes, and route everything we do (print, print at, reading the keyboard) through that streamed interface. If we accomplish that with our own terminal, then it would be possible to hook up that same stream to a protocol talking to a remote terminal emulator. We'd just have to be sure that the control codes we implement happen to match a subset of some known common brand of terminal (and therefore would be usable by most standard terminal emulators like PuTTY).

goblin commented 9 years ago

How about a simpler thing... Don't add support for the whole terminal madness just yet. I'd just add a telnet server (maybe in a separate thread), and add a new simple command like PRINT TO TELNET that outputs a line to the connected telnet client. And then read and execute all commands entered via telnet as if they were written on the terminal prompt.

This way it'd be rather usable from various specialized scripts and wouldn't need changes to the way the in-game terminal works (which is quite nice btw).

Or maybe even just read data from the terminal and let the KerboScript program decide what to do with it entirely, e.g.:

SET var TO TELNET:READLINE.
EXEC var.
TELNET:PRINTLINE "Hello".

3 new small commands would make all the difference ;-) Read line from telnet, print line to telnet, and the ability to execute a command from a string.

Re security, I don't think this is a problem at all. Obviously ship it with telnet off by default, and any user who enables it should know the risks. The burden of firewalling the port or piping it via an ssh tunnel for encryption would be on the user. One nice configurable feature would be the bind address so the user can only have it on 127.0.0.1, for example. And that's it. :-)

goblin commented 9 years ago

Oh, just thought that maybe also a command to check if there's a line ready to read, so as to allow for a non-blocking read inside the kOS script. ;-)

goblin commented 9 years ago

I've started some preliminary work on a telnet CLIENT. I thought it'd be simpler to start with - it allows a kOS script to connect to a remote telnet server. Non-perfect code is here:

https://github.com/goblin/KOS/compare/telnet

Please let me know if you'd like a pull request at this stage...

Usage goes something like this:

SET t TO TELNET().
SET t:DEST TO "127.0.0.1".
SET t:PORT TO 31337.
PRINT t:CONNECT.
SET t:SEND TO "Hello world".
IF t:HASDATA {
  SET r TO t:RECEIVE.
}.
// and if you're adventurous, this should execute the command we've just received:
SET t:EXEC TO r.

There are no config options to enable/disable this little trojan yet.

RECEIVE will block if there's nothing immediately available to read. I thought it'd be a nice feature if you want the game to hang until the telnet server has something to say. If someone doesn't like it, they can check if there's data available to read with HASDATA prior to calling RECEIVE.

Similarly CONNECT and SEND will block, hopefully not for long as the user should have the telnet server set up and ready ;-)

Two issues: (1) EXEC doesn't really belong in TELNET, should be moved somewhere else; (2) there's probably a better way of calling functions on an object than via SET, I've just not yet figured it out.

Dunbaratu commented 9 years ago

I'm not sure I like adding this approach because it may preclude doing it the full, right way next time.

If you've looked into this I'd like some advice, though - have you been able to find ANY implementation of a C# library that does a telnet server or ssh server? I've only been able to find clients, not servers, which is frustrating. It seems to come from the presumption that C# means Windows, and the presumption that Windows means clients.

I'm thinking that the only way to do it will have to be telnet rather than ssh purely because a telnet server is so bog-simple that we can do it without the help of a library. It's really nothing more than just a raw TCP socket attached to a pseudo-TTY.

goblin commented 9 years ago

I'm also not convinced this is the right approach, but a full-blown telnet/ssh server also has some issues... You naturally have to deal with the issue of multiple clients connecting to it. I'm not sure how that'd work in the case of a on-board computer, especially given that all the script variables are global... so would all clients have access to all other clients' variables? Or do you think giving them separate "scopes" (or CPUs maybe here) would make sense? If so, what about steering, who takes precedence? And what about PRINTs from the script, should they go to all clients, some of them, none of them?

I think the client approach is a little more lightweight and kinda dodges those issues, as it can be clearly seen from within the script what goes on with the telnet connection. And by assigning telnet() to multiple variables, you could deal with multiple connections too.

Of course this is not exactly what the OP intended, and I'd be happy to hear how this feature could be implemented properly.

Regarding C# libraries, I've not done very deep searches, but on the surface it seems exactly like you say. I couldn't find ANYTHING that implements a server, and simple client libraries like MinimalTelnet are actually too minimal - I couldn't find a way to implement HASDATA with them. So, like you say, I decided to stick with a raw TCP socket cause telnet is not much different from it anyway. I've not really looked into ssh as it's even more complicated, but I definitely didn't see a server there (there was a few client libs though).

BTW, I also don't quite like how async sockets are done in .NET - they always seem to create a new thread, which IMO is a little overkill and it also introduces a whole heap of synchronization difficulties (and as a C# n00b I don't really want to touch that yet). Ideally though this telnet client I wrote would be using async, as it seems better-suited in this scenario.

Could you recommend a way to call methods on an object/structure (like this new TELNET object I introduced) properly? Currently the syntax SET t:SEND TO "datatosend" works, but feels rather wrong...

And it's fine if you don't want to do it my way, I'll keep it on my branch in my fork :-) I really just wanted something simple so I could talk to a remote computer from the game (which also will allow me cheaper LOG statements to a remote machine), I appreciate it may not be the correct way to do things in the mainstream code.

Dunbaratu commented 9 years ago

Threading also creates a problem as KSP mods are not entirely "full citizens" as far as OS applications go. There's a few things they're disallowed from doing, and one of them is launching a subthread. They have to perform all their work inside the callback hooks called by the main program (KSP) and if they attempt to launch a thread, it throws an exception because they don't have that privilege.

There is a thing called a "coroutine" which is basically like a thread, but one that is managed and executed by the Unity engine and adheres to the callback cycle of Update and FixedUpdate. We may be able to use that, although I haven't looked into how they work yet.

But in any case if any sort of a server is to be implemented, it would need to deal with getting data asynchronously and buffering it up between Update()s.

I guess the main problem I have with what you're proposing isn't really that it's a client instead of a server, but rather that it's a secondary system besides the terminal itself. What I want to eventually push for is a system where the kos script talks to stdin/stdout and does not care one iota whether that i/o stream is going to the terminal to over a socket. Basically, I'm looking for the same command-line flexibility that you get on a full blown UNIX system - if you can do it at the console you can do it remotely because programs that deal with stdin/stdout don't have to be written to take into account the fact that the tty might be remoted.

And that sort of means that the FIRST step is to actually divorce the terminal from the rest of kOS so that kOS talks to it ONLY over a streamed interface and that's it.

If it ends up having to reverse things so that kOS is the client and the server is elsewhere, well, that may be. But it's a sort of secondary issue. The primary one is that I'd like the PRINT command to not care if it's printing to a telnet or to the local terminal. That means no special code INSIDE of kerboscript for it.

I held off on implementing this for a long while because on two occasions we had other people who expressed an interest in doing it and I backed off to let them, but in both cases we haven't heard from them since.

As for the server/client issue - it may be that what we end up having to do is put the server outside KSP as a standalone tool application, and have it be both a server accepting outside connections AND a server accepting connections from kOS, and it's only purpose would be to connect the two clients to each other, copying bytes back and forth between them. That way kOS could be a client AND the user's terminal could be a client.

goblin commented 9 years ago

Not using threads might be a problem - after looking at the so-called "Async" examples for sockets on MSDN, it appears to me that they always use threads, by design: http://msdn.microsoft.com/en-us/library/bbx2eya8(v=vs.110).aspx "Asynchronous sockets use multiple threads from the system thread pool to process network connections."

Yeah, streaming behaviour of PRINT would be nice, but it sounds like too big a task for me, unfortunately.

Regarding the standalone application to bridge the clients together - it crossed my mind too. I think socat would be a good candidate for this.

goblin commented 9 years ago

OTOH, I think one could use a synchronous socket in an asynchronous way, by calling .Poll() or .Select() (like I did with HASDATA).

Dunbaratu commented 9 years ago

I've been working on this for 2 weeks and realized just now that I forgot to assign it to myself.

erendrake commented 9 years ago

@Dunbaratu you also forgot to close it now that you are done :)