Handling requests in python SocketServer - python

I have a code below that does the client-server communication properly.
The client:
# Client
import socket
import pickle
class Model:
def __init__(self, host, port):
self.port = port
self.host = host
def snd_query(self, query):
received_data = []
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((self.host, self.port))
sock.sendall(pickle.dumps(query))
while True:
packet = sock.recv(4096)
if not packet or packet == b'':
break
received_data.append(packet)
try:
content = pickle.loads(b"".join(received_data))
return content
except EOFError:
return None
and a server:
# Server.py
import socketserver
import pickle
import configparser
from data_manipulation import DataManipulation
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
db = DataManipulation("data.db")
request = pickle.loads(self.request.recv(1024))
if request['command'] == 'GET':
content = db.get_data()
#elif ...:
#... some other logic and database interactions
self.request.sendall(pickle.dumps((content)))
def main(HOST, PORT):
try:
my_server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
my_server.serve_forever()
except KeyboardInterrupt:
my_server.shutdown()
my_server.server_close()
if __name__ == "__main__":
config = configparser.ConfigParser()
config.read('../params.ini')
main(config['SERVER']['host'], int(config['SERVER']['port']))
In that form with every new handled request a database connection db instance is created.
I would like to avoid it. I would like to make db object ones and raise it with argument read from ini file. Obviously it is enough to read it once.
All examples that I found show simple echo server application. handle() doesn't do much, only prints some stuff.
I am not sure where is the correct place to call db object to have only one instance?
How to properly incorporate some advanced logic in handle() method?

Related

Paramiko. Reverse Forward Tunnel Question- Help Appreciated

So I've been working with the Paramiko Libary. I have a client and two servers Server A & Server B. The client connects to Server A, and then requests a reverse-forwarded tunnel to Server B, there is a lot more functionality to write into it but my problem at the moment is very fundamental and likely has a very simply answer I'm just somehow overlooking or not understanding.
What I am trying to do at this point is have Server A send some information to Server B every time it connects to it, which due to a timer on the client should after a connection is closed be each minute. (reconnecting each time)
I want to have Server A send, Server B some information every time it connects to it. Now my question relates to how I'd achieve that.
My first thought was to have the client send a command to Server A after the reverse tunnel is connected, I suspect and here my understanding may be wrong, thus why I'm checking here. The command (which is a string) will be forwarded by Server A to Server B, whilst I am looking for the response to that command to be sent Server B.
The other option as I see it is to have Server A push the data to Server B. But I don't know how to check for when a reverse-forwarded-tunnel is created, I could do it for any connection but then that seems inefficient, as the client will get some data, then the data will once again be sent as the reverse forward tunnel is created. (Again likely overlooking something simple here)
So I'm curious given my code's present state what could I do, could change that would let me check for when a reverse-forward-tunnel is made to Server B so I can send the data I want to send to it.
Thank you for taking the time to try and help me here, and yes I understand hardcoding in passwords etc is a bad idea for application security.
The code below is the client & server code (which again need some work but are getting there.)
client Code
import getpass
import os
import socket
import select
import sys
import threading
import paramiko
from paramiko import Channel
import schedule
import time
import string
import random
from optparse import OptionParser
IP = '127.0.0.1'
USER = 'user'
PASSWORD = 'CrabRave'
PORT = 900
REMOTE_PORT = 443
REMOTE_IP = ... ###Remote IP will go here.
def handler(chan, host, port):
sock = socket.socket()
try:
sock.connect((IP, PORT))
except Exception as e:
Timer()
def ssh_client(IP, PORT, USER, PASSWORD):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(IP, PORT, USER, PASSWORD)
ssh_session = client.get_transport().open_session()
def reverse_forward_tunnel(PORT, REMOTE_IP, REMOTE_PORT, transport):
transport.request_port_forward("", PORT)
while True:
chan = transport.accept(1000)
if chan is None:
continue
thr = threading.Thread(
target=handler, args=(chan, REMOTE_IP, REMOTE_PORT))
thr.setDaemon(True)
thr.start()
def Timer():
if Channel.is_active():
schedule.every(1).seconds.do(Timer)
else: schedule.every(1).minutes.do(main)
def main():
client = ssh_client
try:
ssh_client(IP, PORT, USER, PASSWORD)
except Exception as E:
Timer()
try:
reverse_forward_tunnel(PORT, REMOTE_IP, REMOTE_PORT, client.get_transport())
except KeyboardInterrupt:
Timer()
try: Timer()
except Exception as E:
Timer
if __name__ == '__main__':
main()
Server Code
from ABC import abstractmethod
from sys import platform
from Shell import Shell
from cmd import Cmd
from src.server_base import ServerBase
from src.ssh_server_interface import SshServerInterface
from src.shell import Shell
from src.ssh_server import SshServer
import base64
import os
import socket
import sys
import paramiko
import threading
import string
import random
my_key = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(100))
class Shell(Cmd):
use_rawinput=False
promt='My Shell> '
def __init__(self, stdin=None, stdout=None):
super(Shell, self).__init__(completkey='tab', stdin=stdin, stdout =stdout)
def print(self, value):
if self.stdout and not self.stdout.closed:
self.stdout.write(value)
self.stdout.flush()
def printline(self, value):
self.print(value + '\r\n')
def emptyline(self):
self.print('\r\n')
class ServerBase(ABC):
def __init__(self):
self._is_running = threading.Event()
self._socket = None
self.client_shell = None
self._listen_thread = None
def start(self, address='127.0.0.1', port=900, timeout=1):
if not self._is_running.is_set():
self._is_running.set()
self._socket(socket.AF_INET, socket.SOCK_STREAM)
if platform == "linux" or platform == "linux2":
self._socket.setsocketopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
self._socket.settimeout(timeout)
self._socket.bind((address, port))
self._listen_thread = threading.Thread(target=self._listen)
self._listen_thread.start()
def stop(self):
if self._is_running.is_set():
self._is_running.clear()
self._listen_thread.join()
self._socket.close()
def listen(self):
while self._is_running.is_set():
try:
self._socket.listen()
client, addr = self._socket.accept()
self.connection_function(client)
except socket.timeout:
pass
#abstractmethod
def connection_function(self, client):
pass
class SshServerInterface(paramiko.ServerInterface):
def check_channel_request(self, kind, chanid):
if kind == "session":
return paramiko.OPEN_SUCCEEDED
def check_auth_password(self, username: str, password: str) -> int:
if (username == "user") and (password == "CrabRave"):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def check_channel_pty_request(self, channel: Channel, term: bytes, width: int, height: int, pixelwidth: int, pixelheight: int, modes: bytes):
return True
def check_channel_shell_request(self, channel: Channel) -> bool:
return True
def check_channel_env_request(self, channel: Channel, name: bytes, value: bytes) -> bool:
return True
def check_port_forward_request(self, address: str, port: int) -> int:
return port
class SshServer(ServerBase):
def __init__(self, host_key_file, host_key_file_password=None):
super(SshServer, self).__init__()
self._host_key = paramiko.RSAKey.from_private_key_file(StringIO.StringIO(my_key))
def connection_function(self, client):
try:
session = paramiko.Transport(client)
session.add_server_key(self._host_key)
server = SshServerInterface()
try:
session.start_server(server=server)
except paramiko.SSHException:
return
channel = session.accept()
stdio = channel.makefile('rwU')
self.client = Shell(stdio, stdio)
self.client_shell.cmdloop()
session.close()
except:
pass
if __name__ == '__main__':
server = SshServer(my_key)
server.start()

How can I create a Python SSL Client/Server pair where only the server authenticates the client

I have been trying to get a simple Python SSL example working for a day now with no luck. I want to create an SSL server and SSL client. The server should authenticate the client. The Python docs are pretty light on examples for the SSL module, and in general I can't find many working examples. The code I am working with is as follows;
Client:
import socket
import ssl
class SSLClient:
def __init__(self, server_host, server_port, client_cert, client_key):
self.server_host = server_host
self.server_port = server_port
self._context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
self._context.load_cert_chain(client_cert, client_key)
self._sock = None
self._ssock = None
def __del__(self):
self.close()
def connect(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._ssock = self._context.wrap_socket(
self._sock, server_hostname=self.server_host
)
self._ssock.connect((self.server_host, self.server_port))
def send(self, msg):
self._ssock.send(msg.encode())
def close(self):
self._ssock.close()
Server:
import socket
import ssl
from threading import Thread
class SSLServer:
def __init__(self, host, port, cafile, chunk_size=1024):
self.host = host
self.port = port
self.chunk_size = chunk_size
self._context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
self._context.load_verify_locations(cafile)
self._ssock = None
def __del__(self):
self.close()
def connect(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
sock.bind((self.host, self.port))
sock.listen(5)
with self._context.wrap_socket(sock, server_side=True) as self._ssock:
conn, _ = self._ssock.accept()
while True:
data = conn.recv(self.chunk_size).decode()
print(data)
if data is None:
break
def close(self):
self._ssock.close()
class SSLServerThread(Thread):
def __init__(self, server):
super().__init__()
self._server = server
self.daemon = True
def run(self):
self._server.connect()
def stop(self):
self._server.close()
Test script:
import client, server
from os import path
from time import sleep
server_host = "localhost"
server_port = 11234
client_cert = path.join(path.dirname(__file__), "client.crt")
client_key = path.join(path.dirname(__file__), "client.key")
s = server.SSLServer(server_host, server_port, client_cert)
s_thread = server.SSLServerThread(s)
s_thread.start()
sleep(2)
c = client.SSLClient(server_host, server_port, client_cert, client_key)
c.connect()
c.send("This is a test message!")
c.close()
s.close()
I generated my client certificate and key using the following command:
openssl req -newkey rsa:2048 \
-x509 \
-sha256 \
-days 3650 \
-nodes \
-out client.crt \
-keyout client.key \
-subj "/C=UK/ST=Scotland/L=Glasgow/O=Company A/OU=Testing/CN=MyName"
The test script seems to start the server and allow the client to connect, but I am getting a BrokenPipeError when I try to send the test message.
Annoyingly I have been getting various different error messages as I go, so it's likely a combination of things. This is a simple example I created to try and get something working. On my more complex example I get "NO_SHARED_CIPHERS" when the client attempts to connect to the server. Annoyingly I can't see why this simple example seems to get further than the more complex one (i.e. the connection seems to be established successfully) even though they are set up almost identically.
I have uploaded a repo at git#github.com:stevengillies87/python-ssl-client-auth-example.git if anyone would like to test it.
I realised the first bug came from copy pasting and example and not realising how it differed from my code in its setup. It used socket.socket() to create the socket whereas my example used socket.create_connection(), which also connects the socket. This was the reason I was getting a BrokenPipeError. Now both my simple example and the actual code I am writing both have a consistent NO_SHARED_CIPHER error. I added a line to the source code to connect the client after the socket has been wrapped.
So, as expected it was a combination of things.
Before I added the SSL layer to my code it worked with TCP sockets. I was using socket.create_connection() in the client to create and connect a socket in one call. When I added SSL I continued to do this but because I was attempting to connect to an SSL server via a TCP socket I was getting a NO_SHARED_CIPHER error.
The solution to this problem was to only create the TCP socket with sock = socket.socket(), wrap it with ssock = ssl_context.wrap_context(sock) and then call connect on the SSL layer, ssock.connect((host, port)).
However, I was still getting a handshaking error on connection. I found this link, https://www.electricmonk.nl/log/2018/06/02/ssl-tls-client-certificate-verification-with-python-v3-4-sslcontext/, which provided a detailed example of how to create mutually authenticating SSL client/server. Crucially, the author pointed out that hostname used for server authentication must match the "common name" entered when creating the server.crt and server.key files. Previously I had just been using the same host that I was connecting to, "localhost" in this case. They also noted that the ssl_context verify mode should be set to verify_mode = ssl.CERT_REQUIRED for client auth.
Once the example worked I set about removing the client auth of the server. This was done by changing the client SSL context from ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) to ssl.SSLContext(). The client now does not require the server.crt file to connect successfully.
Frustratingly I still need to create server cert/key files and load them into the server using ssl_context.load_cert_chain(), even though I do not need the server to be authenticated. If I try to remove this step from the server I get a NO_SHARED_CIPHER error again. If anyone knows how I can avoid this then please let me know, or explain why it is necessary.
Working code below, and updated at the github link in the question.
Client:
import socket
import ssl
class SSLClient:
def __init__(
self, server_host, server_port, sni_hostname, client_cert, client_key,
):
self.server_host = server_host
self.server_port = server_port
self.sni_hostname = sni_hostname
self._context = ssl.SSLContext()
self._context.load_cert_chain(client_cert, client_key)
self._sock = None
self._ssock = None
def __del__(self):
self.close()
def connect(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._ssock = self._context.wrap_socket(self._sock,)
self._ssock.connect((self.server_host, self.server_port))
def send(self, msg):
self._ssock.send(msg.encode())
def close(self):
self._ssock.close()
Server:
import socket
import ssl
from threading import Thread
class SSLServer:
def __init__(
self, host, port, server_cert, server_key, client_cert, chunk_size=1024
):
self.host = host
self.port = port
self.chunk_size = chunk_size
self._context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
self._context.verify_mode = ssl.CERT_REQUIRED
self._context.load_cert_chain(server_cert, server_key)
self._context.load_verify_locations(client_cert)
def connect(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
sock.bind((self.host, self.port))
sock.listen(5)
while True:
conn, _ = sock.accept()
with self._context.wrap_socket(conn, server_side=True) as sconn:
self._recv(sconn)
def _recv(self, sock):
while True:
data = sock.recv(self.chunk_size)
if data:
print(data.decode())
else:
break
class SSLServerThread(Thread):
def __init__(self, server):
super().__init__()
self._server = server
self.daemon = True
def run(self):
self._server.connect()
Test:
import client, server
from os import path
from time import sleep
server_host = "127.0.0.1"
server_port = 35689
server_sni_hostname = "www.company-b.com"
client_cert = path.join(path.dirname(__file__), "client.crt")
client_key = path.join(path.dirname(__file__), "client.key")
server_cert = path.join(path.dirname(__file__), "server.crt")
server_key = path.join(path.dirname(__file__), "server.key")
s = server.SSLServer(server_host, server_port, server_cert, server_key, client_cert)
s_thread = server.SSLServerThread(s)
s_thread.start()
sleep(2)
c = client.SSLClient(
server_host, server_port, server_sni_hostname, client_cert, client_key
)
c.connect()
c.send("This is a test message!")
c.close()

Simulate a Long Network Request for Python Testing

I need to test a device update function. The function opens a socket on a host and sends a block of text.
The update can take up to 120 seconds. It returns a code for success/failure. To allow continued functioning of the program the update is launched in a thread.
I cannot control the response of the device. The simulation needs to be able to hold an open connection for at least 120 seconds.
It does not need to be safe or scalable since it will only be used for an integration test. The simplest solution is preferred. Pure python is best, but a docker is also acceptable.
I wrote this up based on rdas's pointer.
import json
import logging
import socket
import socketserver
import threading
import time
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
class LongRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
# Echo the back to the client
data = json.loads(self.request.recv(1024).decode())
t = 0
while t < data['delay']:
time.sleep(1)
print(".", end='')
t += 1
if t % 80 == 0:
print("\n")
print("\n")
self.request.send(b"ok")
class Server():
def __init__(self, host='localhost', port=0):
self.host = host
self.port = port
self.ip = None
self.server = None
def run(self):
address = (self.host, self.port) # let the kernel assign port if port=0
self.server = socketserver.TCPServer(address, LongRequestHandler)
self.ip, self.port = self.server.server_address # what port was assigned?
t = threading.Thread(target=self.server.serve_forever)
t.setDaemon(True) # don't hang on exit
t.start()
return True
def send_request(self, data: dict ):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.ip, self.port))
message = json.dumps(data).encode()
s.send(message)
response = s.recv(1024)
s.close()
return response
def __exit__(self):
self.server.shutdown()
self.server.socket.close()
if __name__ == '__main__':
# For simple testing and config example...
server = Server()
server.run()
# Send the data
d = dict(delay=5) # set delay here to desired
out = server.send_request(d)
print('Received: {!r}'.format(out))

How to close python asyncio transport?

I'm working on a project which uses python asyncio socket server. The problem is that the implementation of the server doesn't call .close() on the transport when the server stops. This seems to leave clients connected and causes crashes in other parts of the code.
Python documents say that transports need to be closed explicitly, but in this project I don't know where I can close them because there is no reference to the transports that are created for each client.
https://docs.python.org/3/library/asyncio-dev.html#close-transports-and-event-loops
Here is the code:
"""
Socket server forwarding request to internal server
"""
import logging
try:
# we prefer to use bundles asyncio version, otherwise fallback to trollius
import asyncio
except ImportError:
import trollius as asyncio
from opcua import ua
from opcua.server.uaprocessor import UaProcessor
logger = logging.getLogger(__name__)
class BinaryServer(object):
def __init__(self, internal_server, hostname, port):
self.logger = logging.getLogger(__name__)
self.hostname = hostname
self.port = port
self.iserver = internal_server
self.loop = internal_server.loop
self._server = None
self._policies = []
def set_policies(self, policies):
self._policies = policies
def start(self):
class OPCUAProtocol(asyncio.Protocol):
"""
instanciated for every connection
defined as internal class since it needs access
to the internal server object
FIXME: find another solution
"""
iserver = self.iserver
loop = self.loop
logger = self.logger
policies = self._policies
def connection_made(self, transport):
self.peername = transport.get_extra_info('peername')
self.logger.info('New connection from %s', self.peername)
self.transport = transport
self.processor = UaProcessor(self.iserver, self.transport)
self.processor.set_policies(self.policies)
self.data = b""
def connection_lost(self, ex):
self.logger.info('Lost connection from %s, %s', self.peername, ex)
self.transport.close()
self.processor.close()
def data_received(self, data):
logger.debug("received %s bytes from socket", len(data))
if self.data:
data = self.data + data
self.data = b""
self._process_data(data)
def _process_data(self, data):
buf = ua.utils.Buffer(data)
while True:
try:
backup_buf = buf.copy()
try:
hdr = ua.Header.from_string(buf)
except ua.utils.NotEnoughData:
logger.info("We did not receive enough data from client, waiting for more")
self.data = backup_buf.read(len(backup_buf))
return
if len(buf) < hdr.body_size:
logger.info("We did not receive enough data from client, waiting for more")
self.data = backup_buf.read(len(backup_buf))
return
ret = self.processor.process(hdr, buf)
if not ret:
logger.info("processor returned False, we close connection from %s", self.peername)
self.transport.close()
return
if len(buf) == 0:
return
except Exception:
logger.exception("Exception raised while parsing message from client, closing")
self.transport.close()
break
coro = self.loop.create_server(OPCUAProtocol, self.hostname, self.port)
self._server = self.loop.run_coro_and_wait(coro)
print('Listening on {}:{}'.format(self.hostname, self.port))
def stop(self):
self.logger.info("Closing asyncio socket server")
self.loop.call_soon(self._server.close)
self.loop.run_coro_and_wait(self._server.wait_closed())
As you can see when we call stop() on this server class the asyncio server calls it's close method. However if clients are connected the created transports never get closed.
The project repository is here https://github.com/FreeOpcUa/python-opcua/ , you can take a look at Issue 137.
What is the correct way to close the transport object?
I solve this by applying this approach:
#self.OPCUAServer - this is my opcua server
nodes = []
nodes.append(self.OPCUAServer.get_node("ns=0; s=Measurements")) #Adding two root nodes
nodes.append(self.OPCUAServer.get_node("ns=1; s=Calibrations")) #to the list
self.OPCUAServer.delete_nodes(nodes, True) # Recursively call delete_nodes with this list
self.OPCUAServer.stop()

Python Game Server

I'm completely lost trying to create a UDP server/client for my game in python. I'm new to the language and only have limited experience with networking. Right now, the server runs, but doesn't seem to be getting any messages from the client.
Server:
class GameServer:
class GameServerUDPHandler(socketserver.BaseRequestHandler):
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
print(data)
socket.sendto(data.upper(), self.client_address)
def __init__(self, port):
self.server = socketserver.UDPServer(("localhost", port), self.GameServerUDPHandler)
def start_server(self):
self.server.serve_forever(
Client:
import socket
import sys
class GameClient:
def __init__(self, port, host):
self.port = port
self.host = host
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def register(self):
self.socket.sendto(bytes("register\n", "utf-8"), (self.host, self.port))
self.numberID = int(self.socket.recv(1024))
print("Received: {}".format(self.numberID))
-Main/Start of program
import gameserver
import gameclient
if __name__ == "__main__":
server = gameserver.GameServer(1300)
server.start_server()
client = gameclient.GameClient(1300, "localhost")
client.register()
NOTE: I'm most likely to multiple things wrong and may be violating several best practices in the language. I really have no clue.
The problem is that some of these calls are blocking. In particular, the serve_forever() method will run forever, so you need to put that on a separate thread if you want the rest of your program to continue:
import threading
if __name__ == "__main__":
server = GameServer(1300)
server_thread = threading.Thread(target=lambda: server.start_server())
server_thread.start()
time.sleep(1) # Give it time to start up; not production quality code of course
client = GameClient(1300, "localhost")
client.register()
socket.recv() is also a blocking call but that might be okay in this case.
Seems like this library isn't asynchronous so your first call to serve_forever will not return and your client never gets started. You can create a new thread to launch the server on or split your client and server into seperate processes.

Categories

Resources