Coder Social home page Coder Social logo

sz-po / go-distributed-kvm-switch Goto Github PK

View Code? Open in Web Editor NEW
3.0 1.0 0.0 28 KB

The Distributed KVM Switch (dKVMs) is a framework designed to manage input and output streams from keyboards, mice, displays, speakers, and microphones in a distributed manner, with the ultimate goal of creating a platform-independent solution similar to hardware KVM switches.

Go 100.00%
golang dkvms virtual-audio virtual-camera virtual-display virtual-keyboard virtual-microphone virtual-mouse kvm-switch

go-distributed-kvm-switch's Introduction

Distributed KVM Switch

About the project

The Distributed KVM Switch (dKVMs) is a framework that aims to manage input and output streams from keyboards, mice, displays, speakers, and microphones in a distributed manner. The final objective of the project is to create a platform-independent distributed KVM switch, similar to the hardware one.

Current state

The project is currently in its initial stages, with ongoing development and potential features yet to be implemented, such as supported devices. Check the "issues" page to track progress and to report any issues.

Supported platforms

The goal of this project is to provide support for three major platforms, namely MacOS, Windows, and Linux. To use this project, an agent must be installed, which requires root/administrator privileges.

However, in a corporate environment, it may be challenging to use the project since root/administrator permissions are not always available for a user account. To overcome this limitation, we are planning to introduce an agentless mode that will operate through Raspberry Pi as an agent. You can find more details about this in the section below.

MacOS

TBD

Windows

TBD

Linux

TBD

Raspberry Pi

TBD

Project architecture

Overview

The project involves two important concepts that work together to ensure proper functioning - the kernel and the devices. The kernel is responsible for managing various devices and processing streams of data that originate from these devices. Essentially, the kernel starts by reading the configuration file and then proceeds to start the devices. Each device is an operating system process and is controlled by the kernel through a wire protocol over stdin/stdout pipes. The devices produce events that contain data to be processed, such as video frames, keystrokes or mouse movements. The kernel is responsible for routing the events to other devices as required.

While the kernel and devices may seem complex, their functionality is relatively straightforward. If you require more information, please refer to the section below for additional details.

Kernel

The kernel has several responsibilities, and each of then is handled by separate module.

device-manager

The kernel has a major responsibility of managing devices, which is done through the device manager module. This module is responsible for setting the desired state of each device, as defined in the configuration file after startup. Since a device is just an OS process, the device manager module also starts and stops the device processes.

In addition, the device manager module monitors the current state of the device process and restarts it if it is not running. When a device process completes with an exit code other than 0, it is restarted with exponential backoff. This module also attaches wire protocol to the device pipes (stdin and stdout) and connects handlers from other modules to the wire protocol.

Furthermore, the device manager module connects a log handler to the stderr pipe, which captures all log messages and processes them in the same way as logs from the kernel.

Like several other modules in the kernel, the device manager module exposes services that can be utilized by the device. This service provides APIs for querying the state of the devices. It also emits events when the device state changes, and devices can subscribe to these events.

memory-manager

Modern devices often produce large amounts of data, which can be quite challenging to handle. For instance, to capture a raw frame of Full HD display, the device responsible for streaming would need to emit 60 buffers with frames, producing about 355 MB/s of data. This data would then need to be passed to a device that displays it on the screen, or compressed and passed over a network to another kernel, where it can be decompressed and then displayed on the screen. This can result in a lot of data shuffling.

One might think that sharing pointers instead of copying data could solve this problem. However, due to the nature of the device (separate process) and modern operating systems (virtual memory), it is not possible to share a pointer to memory from one process to another. Every process has its own linear memory space, which cannot be easily shared with another process.

Fortunately, operating systems allow us to request shared memory space, which is typically exposed as a file descriptor that can be easily mapped inside the process as a place in virtual memory space. This enables us to pass data between different processes. However, this shared memory is just a simple, plain large buffer; therefore, we need to bring some management to it. This is where the memory manager comes in handy.

The memory manager is responsible for allocating buffers in shared memory space, locking them, retaining, releasing, and so on. It's like a regular memory manager in an operating system, but only for shared memory space. Like other modules in the kernel, it exposes services that can be utilized by the device. Therefore, when a device needs to pass data to another device, it should first request the memory manager to allocate a buffer in shared memory space, write data to that buffer, and then pass only the buffer descriptor to the outgoing event.

Device

As you may have read before, the device is essentially an OS process. You might be wondering why a device is not just simple code that can be used by the kernel? That approach would be much simpler rather than creating separate processes, managing them, their configurations, wire protocol, and so on. Why is this separation level used? It seems to complicate things. It is worth understanding why devices are separate processes.

Devices typically use different internal and public OS APIs. Sometimes, these APIs work unpredictably and cause the calling process to hang or crash. Unfortunately, exception handlers can't catch those crashes because these errors are often segmentation faults or other low-level violations that cause the process to be killed. To avoid crashing the whole project, it is better to move the device logic to a separate process. If that separate process crashes, the device manager will restart that process, and the device will be initialized again.

Another reason for this separation is that calling OS kernel APIs (e.g. to install a keyboard hook or access screen capturing service) usually requires being done from the main thread of the caller process. Combining main thread access to MacOS APIs and MS APIs, in the same way, would be super hard. It is better to allow the use of native GCD or another native event loop mechanism instead of combining different OS APIs.

SDK

TBD

Supported devices

TBD

go-distributed-kvm-switch's People

Contributors

sz-po avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

go-distributed-kvm-switch's Issues

Create device initialization function with hooks and abstraction layers in SDK

The purpose of this task is to create a function in the SDK that simplifies device initialization for developers. The goal is to abstract away the underlying processes, such as reading configurations from environment variables, installing the logger, starting the wire protocol, starting RPC, starting the event dispatcher, and connecting all these components.

Moreover, the function should support the ability to attach hooks to the start and stop processes, and install event handlers and RPC services with ease.

Ideally, the device developer should have a straightforward interface to work with. The example code below illustrates how it should look:

func main() {
  device.Serve[Config](
    device.WithBeforeStartFn(func(ctx device.Context) {
      ctx.GetConfig() // returns kernel provided config as a struct

      ctx.DeviceManager() // example - returns RPC of some kernel service
      ctx.MemoryManager() // example - returns RPC of some kernel service

      ctx.EventDispatcher().Dispatch(event) // example - uses device event dispatcher to send an event to kernel
    }), 
    device.WithAfterStartFn(func(...) {}), 

    device.WithBeforeStopFn(func(...) {}), 
    device.WithAfterStopFn(func(...) {}),

    device.WithRPCService(),

    device.WithEventListener(),
  )
}

Finally, the readme should include usage examples for all the above cases, as well as for events and RPCs.

Implement simple device manager

The goal of this task is to develop a solution that can initiate device processes, monitor their status, and terminate them as required. The device manager should have the following capabilities:

  • Importing device specifications from the structure
  • Starting the device process
  • Detecting the process's end and restarting it if the exit-code is other than 0. If the error persists, an exponential backoff should be implemented.
  • Triggering the end of the process by sending SIGTERM, waiting for a grace period, and then sending SIGKILL.

Furthermore, the structure describing the process should have a field that allows its value (also a structure) to be transferred as the device configuration. The configuration structure is unknown during compilation but should be passed as environment variables to the device process, prefixed with DKVMS_DEVICE_CONFIG_. For instance, the structure shown below expressed in YAML format:

display:
   id: foo

should be encoded as DKVMS_DEVICE_CONFIG_DISPLAY_ID.

Please ensure that appropriate information on how this mechanism works is provided in the readme.

Implement wire protocol for byte-level message transmission between devices and the kernel

The goal of this task is to establish a simple protocol at the byte transmission level.

Firstly, we need to define a message format. JSON messages should be used, which must contain the message type (in string format) and its content as a sequence of bytes.

Secondly, we need to create a mechanism that allows us to connect to io.Reader and attach handlers to it that can respond to a specific type of message. This mechanism should assume that each message is separated by a newline character.

Thirdly, we need to create a mechanism to connect to io.Writer, which provides an abstraction for sending messages. Each message must be separated by a newline character. Since JSON cannot be formatted, any newlines must be correctly converted using escape sequences.

It is important to include a description of the wire format in the readme file.

Implement device configuration reader in SDK

The purpose of this task is to add the ability to read the device process configuration to the SDK. The kernel passes device configuration through environment variables that are prefixed with DKVMS_DEVICE_CONFIG_.

To complete this task:

  • create a public method in the SDK that will allow you to load the entire configuration into the structure,
  • add usage example in readme

Create project skeleton.

A basic project structure should be created.

It can be considered completed if:

  • there are .gitignore files
  • kernel and device dummy executables can be built
  • go.mod is configured correctly

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.