blob: 5178b601a66ec61676b13b4d8f01f740fb1521be [file] [log] [blame]
Jerome Forissiercf4633b2021-08-09 14:54:21 +02001#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2014, Linaro Limited
5# Copyright (c) 2021, Huawei Technologies Co., Ltd
6#
7
8import argparse
9import os
10import select
11import socket
12import sys
13import termios
14
15handle_telnet = False
16cmd_bytes = bytearray()
17
18TELNET_IAC = 0xff
19TELNET_DO = 0xfd
20TELNET_WILL = 0xfb
21TELNET_SUPRESS_GO_AHEAD = 0x1
22
23
24def get_args():
25
26 parser = argparse.ArgumentParser(description='Starts a TCP server to be '
27 'used as a terminal (for QEMU or FVP). '
28 'When the server receives a connection '
29 'it puts the terminal in raw mode so '
30 'that control characters (Ctrl-C etc.) '
31 'are interpreted remotely. Only when the '
32 'peer has closed the connection the '
33 'terminal settings are restored.')
34 parser.add_argument('port', nargs=1, type=int,
35 help='local TCP port to listen on')
36 parser.add_argument('-t', '--telnet', action='store_true',
37 help='handle telnet commands (FVP)')
38 return parser.parse_args()
39
40
41def set_stty_noncanonical():
42
43 t = termios.tcgetattr(sys.stdin.fileno())
44 # iflag
45 t[0] = t[0] & ~termios.ICRNL
46 # lflag
47 t[3] = t[3] & ~(termios.ICANON | termios.ECHO | termios.ISIG)
48 t[6][termios.VMIN] = 1 # Character-at-a-time input
49 t[6][termios.VTIME] = 0 # with blocking
50 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, t)
51
52
53def handle_telnet_codes(fd, buf):
54
55 global handle_telnet
56 global cmd_bytes
57
58 if (not handle_telnet):
59 return
60
61 if (fd == -1):
62 cmd_bytes.clear()
63 return
64
65 # Iterate on a copy because buf is modified in the loop
66 for c in bytearray(buf):
67 if (len(cmd_bytes) or c == TELNET_IAC):
68 cmd_bytes.append(c)
69 del buf[0]
70 if (len(cmd_bytes) == 3):
71 if (cmd_bytes[1] == TELNET_DO):
72 cmd_bytes[1] = TELNET_WILL
73 elif (cmd_bytes[1] == TELNET_WILL):
74 if (cmd_bytes[2] == TELNET_SUPRESS_GO_AHEAD):
75 # We're done after responding to this
76 handle_telnet = False
77 cmd_bytes[1] = TELNET_DO
78 else:
79 # Unknown command, ignore it
80 cmd_bytes.clear()
81 if (len(cmd_bytes)):
82 os.write(fd, cmd_bytes)
83 cmd_bytes.clear()
84
85
86def serve_conn(conn):
87
88 fd = conn.fileno()
89 poll = select.poll()
90 poll.register(sys.stdin.fileno(), select.POLLIN)
91 poll.register(fd, select.POLLIN)
92 while (True):
93 for readyfd, _ in poll.poll():
94 try:
95 data = os.read(readyfd, 512)
96 if (len(data) == 0):
97 print('soc_term: read fd EOF')
98 return
99 buf = bytearray(data)
100 handle_telnet_codes(readyfd, buf)
101 if (readyfd == fd):
Jerome Forissier49f9eb22022-10-17 10:55:53 +0200102 to = sys.stdout.fileno()
Jerome Forissiercf4633b2021-08-09 14:54:21 +0200103 else:
104 to = fd
105 except ConnectionResetError:
106 print('soc_term: connection reset')
107 return
108 try:
109 # Python >= 3.5 handles EINTR internally so no loop required
110 os.write(to, buf)
111 except WriteErrorException:
112 print('soc_term: write error')
113 return
114
115
116def main():
117
118 global handle_telnet
119 args = get_args()
120 port = args.port[0]
121 sock = socket.socket()
122 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
123 sock.bind(('127.0.0.1', port))
124 sock.listen(5)
125 print(f'listening on port {port}')
126 if (args.telnet):
127 print('Handling telnet commands')
128 old_term = termios.tcgetattr(sys.stdin.fileno())
129 while True:
130 try:
131 conn, _ = sock.accept()
132 print(f'soc_term: accepted fd {conn.fileno()}')
133 handle_telnet = args.telnet
134 handle_telnet_codes(-1, bytearray()) # Reset internal state
135 set_stty_noncanonical()
136 serve_conn(conn)
137 conn.close()
138 except KeyboardInterrupt:
139 return
140 finally:
141 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old_term)
142
143
144if __name__ == "__main__":
145 main()