├── README.md └── utelnet └── utelnetserver.py /README.md: -------------------------------------------------------------------------------- 1 | # utelnetserver 2 | 3 | This is a simple implementation of a telnet server that will hook telnet clients up to the REPL. The telnet server and associated logic run in the background so you can use the REPL or run other scripts with it. A single client connection is supported at a time. 4 | 5 | __Updated to support MPY v1.1__. 6 | 7 | To get started with it just add the following to your `boot.py` 8 | 9 | import utelnetserver 10 | utelnetserver.start() 11 | 12 | on boot you should see something like the following: `Telnet server started on 192.168.2.119:23` 13 | 14 | I've only tested it with a telnet client on the mac, but with that it is as simple as: 15 | 16 | $ telnet 192.168.2.119 17 | Trying 192.168.2.119... 18 | Connected to esp_f4b4b3. 19 | Escape character is '^]'. 20 | 21 | >>> print("Hello!") 22 | Hello! 23 | 24 | ## Limitations 25 | - One telnet client at a time 26 | - No authentication support 27 | 28 | ## What is supported 29 | - Telnet server is callback based 30 | - Interact with REPL via a telnet client 31 | 32 | ## Other examples 33 | The utelnetserver module is pretty straightforward, offering `start(port=23)` and `stop()` so you can start/stop it as needed or run it on a port different than the typical port 23 for telnet. 34 | 35 | ## Future Work 36 | - Authentication support 37 | - More robust handling of telnet control characters 38 | - Won't restart after just a soft reboot due to https://github.com/micropython/micropython/issues/1896 39 | -------------------------------------------------------------------------------- /utelnet/utelnetserver.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import network 3 | import uos 4 | import errno 5 | from uio import IOBase 6 | 7 | last_client_socket = None 8 | server_socket = None 9 | 10 | # Provide necessary functions for dupterm and replace telnet control characters that come in. 11 | class TelnetWrapper(IOBase): 12 | def __init__(self, socket): 13 | self.socket = socket 14 | self.discard_count = 0 15 | 16 | def readinto(self, b): 17 | readbytes = 0 18 | for i in range(len(b)): 19 | try: 20 | byte = 0 21 | # discard telnet control characters and 22 | # null bytes 23 | while(byte == 0): 24 | byte = self.socket.recv(1)[0] 25 | if byte == 0xFF: 26 | self.discard_count = 2 27 | byte = 0 28 | elif self.discard_count > 0: 29 | self.discard_count -= 1 30 | byte = 0 31 | 32 | b[i] = byte 33 | 34 | readbytes += 1 35 | except (IndexError, OSError) as e: 36 | if type(e) == IndexError or len(e.args) > 0 and e.args[0] == errno.EAGAIN: 37 | if readbytes == 0: 38 | return None 39 | else: 40 | return readbytes 41 | else: 42 | raise 43 | return readbytes 44 | 45 | def write(self, data): 46 | # we need to write all the data but it's a non-blocking socket 47 | # so loop until it's all written eating EAGAIN exceptions 48 | while len(data) > 0: 49 | try: 50 | written_bytes = self.socket.write(data) 51 | data = data[written_bytes:] 52 | except OSError as e: 53 | if len(e.args) > 0 and e.args[0] == errno.EAGAIN: 54 | # can't write yet, try again 55 | pass 56 | else: 57 | # something else...propagate the exception 58 | raise 59 | 60 | def close(self): 61 | self.socket.close() 62 | 63 | # Attach new clients to dupterm and 64 | # send telnet control characters to disable line mode 65 | # and stop local echoing 66 | def accept_telnet_connect(telnet_server): 67 | global last_client_socket 68 | 69 | if last_client_socket: 70 | # close any previous clients 71 | uos.dupterm(None) 72 | last_client_socket.close() 73 | 74 | last_client_socket, remote_addr = telnet_server.accept() 75 | print("Telnet connection from:", remote_addr) 76 | last_client_socket.setblocking(False) 77 | # dupterm_notify() not available under MicroPython v1.1 78 | # last_client_socket.setsockopt(socket.SOL_SOCKET, 20, uos.dupterm_notify) 79 | 80 | last_client_socket.sendall(bytes([255, 252, 34])) # dont allow line mode 81 | last_client_socket.sendall(bytes([255, 251, 1])) # turn off local echo 82 | 83 | uos.dupterm(TelnetWrapper(last_client_socket)) 84 | 85 | def stop(): 86 | global server_socket, last_client_socket 87 | uos.dupterm(None) 88 | if server_socket: 89 | server_socket.close() 90 | if last_client_socket: 91 | last_client_socket.close() 92 | 93 | # start listening for telnet connections on port 23 94 | def start(port=23): 95 | stop() 96 | global server_socket 97 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 98 | server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 99 | 100 | ai = socket.getaddrinfo("0.0.0.0", port) 101 | addr = ai[0][4] 102 | 103 | server_socket.bind(addr) 104 | server_socket.listen(1) 105 | server_socket.setsockopt(socket.SOL_SOCKET, 20, accept_telnet_connect) 106 | 107 | for i in (network.AP_IF, network.STA_IF): 108 | wlan = network.WLAN(i) 109 | if wlan.active(): 110 | print("Telnet server started on {}:{}".format(wlan.ifconfig()[0], port)) 111 | --------------------------------------------------------------------------------