Skip to content

Commit

Permalink
Merge pull request #4 from gkc/socket-authenticator-option
Browse files Browse the repository at this point in the history
feat: Socket authenticators and data transformers
  • Loading branch information
gkc authored Jan 22, 2024
2 parents 396ffc0 + b6e0490 commit c2efc86
Show file tree
Hide file tree
Showing 13 changed files with 1,442 additions and 417 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/unit_tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: unit_tests

permissions:
contents: read

on:
workflow_dispatch:
push:
branches:
- trunk

pull_request:
branches:
- trunk

jobs:
unit_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: dart-lang/setup-dart@ca7e6fee45ffbd82b555a7ebfc236d2c86439f5b # v1.6.1
- name: dart pub get
run: dart pub get
- name: dart analyze
run: dart analyze
- name: dart test
run: dart test
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 2.0.0
- Added support for requiring client sockets to be authenticated in some
app-defined way before they will be connected to the other side
- Added support for app-defined data transformers which can be used to
transform the data while sending from A to B, and vice versa. Useful for
adding traffic encryption, for example.
- Refactored for readability
- Multiple breaking changes to improve API readability
- More documentation
- More tests

## 1.0.11
- Added close function to SocketConnector

Expand Down
96 changes: 62 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,85 @@
<h1><a href="https://atsign.com#gh-light-mode-only"><img width=250px
src="https://atsign.com/wp-content/uploads/2022/05/atsign-logo-horizontal-color2022.svg#gh-light-mode-only"
alt="The Atsign Foundation"></a>
<a href="https://atsign.com#gh-dark-mode-only"><img width=250px
src="https://atsign.com/wp-content/uploads/2023/08/atsign-logo-horizontal-reverse2022-Color.svg#gh-dark-mode-only"
alt="The Atsign Foundation"></a></h1>
<h1>
<a href="https://atsign.com#gh-light-mode-only">
<img width=250px
src="https://atsign.com/wp-content/uploads/2022/05/atsign-logo-horizontal-color2022.svg#gh-light-mode-only"
alt="The Atsign Foundation">
</a>
<a href="https://atsign.com#gh-dark-mode-only">
<img width=250px
src="https://atsign.com/wp-content/uploads/2023/08/atsign-logo-horizontal-reverse2022-Color.svg#gh-dark-mode-only"
alt="The Atsign Foundation">
</a>
</h1>

# Socket Connector

Connect two TCP sockets and optionally display the traffic.

## Features

TCP Sockets come in two flavours a server and a client, you have to be one or
the other. If you want to join two clients or two servers this package
includes all the tools you need. to connect servers to servers and clients to
clients. Why would you need this type of service? To create a rendezvous
service that two clients can connect to for example or to join two servers
with a shared client. Also included is a client to server, this acts as a
simple TCP proxy and as with all the services you can optionally set the
verbose flag and the readable (ascii) characters that are being transmitted
and received will be displayed.
TCP Sockets come in two flavours - a server and a client; you have to be one or
the other. If you want to join two clients or two servers this package includes
all the tools you need to connect servers to servers and clients to clients.
Why would you need this type of service? To create a rendezvous service that two
clients can connect to for example or to join two servers with a shared client.
Also included is a client to server, this acts as a simple TCP proxy and as with
all the services you can optionally set the verbose flag in order to see
more info about what is happening, and the logTraffic flag in order to see the
readable (ascii) characters that are being transmitted and received.

## Getting started

dart pub add socket_connector

## Usage

The following code will open two server sockets and connect them and display
any traffic that goes between the sockets. You can test this using ncat to
connect to the two listening ports in two sessions. You will see what is typed
in one window appear in the other plus see the data on at the dart program.
The following code will open two server sockets and connect them and display any
traffic that goes between the sockets. You can test this using ncat to connect
to the two listening ports in two sessions. You will see what is typed in one
window appear in the other plus see the data on at the dart program.

[![asciicast](https://asciinema.org/a/cglnKVtH16DPwWfqGJXgPMCKn.svg)](https://asciinema.org/a/cglnKVtH16DPwWfqGJXgPMCKn)
```dart
// Once running use ncat to check the sockets
SocketConnector socketConnector = await SocketConnector.serverToServer(
addressA: InternetAddress.anyIPv4,
addressB: InternetAddress.anyIPv4,
portA: 9000,
portB: 8000,
verbose: true,
logTraffic: true,
);
print('Sender Port: ${socketConnector.sideAPort}'
' Receiver Port: ${socketConnector.sideBPort}');
```

`ncat localhost 8000`

`ncat localhost 9000`

```dart
SocketConnector socketStream = await SocketConnector.serverToServer(
serverAddressA: InternetAddress.anyIPv4,
serverAddressB: InternetAddress.anyIPv4,
serverPortA: 9000,
serverPortB: 8000,
verbose: true);
print(
'Sender Port: ${socketStream.senderPort().toString()} Receiver Port: ${socketStream.receiverPort().toString()}');
}
```
[![asciicast](https://asciinema.org/a/cglnKVtH16DPwWfqGJXgPMCKn.svg)](https://asciinema.org/a/cglnKVtH16DPwWfqGJXgPMCKn)


## Additional information

TODO: All SocketConnectors only acceot a single session currently, so there
is one type of connection that is currently missing which is server to socket
which could allow separate sessions as clients connect.
Excerpted from the SocketConnector class's documentation:
```
/// Typical usage is via the [serverToServer], [serverToSocket],
/// [socketToSocket] and [socketToServer] methods which are different flavours
/// of the same functionality - to relay information from one socket to another.
///
/// - Upon creation, a [Timer] will be created for [timeout] duration. The
/// timer callback, when it executes, calls [close] if [connections]
// is empty
/// - When an established connection is closed, [close] will be called if
/// [connections] is empty
/// - New [Connection]s are added to [connections] when both
/// [pendingA] and [pendingB] have at least one entry
/// - When [verbose] is true, log messages will be logged to [logger]
/// - When [logTraffic] is true, socket traffic will be logged to [logger]
```

TODO: Currently only `SocketConnector.serverToServer` handles more than one
pair of sockets in a given session; code needs to be added to `serverToSocket`
and `socketToServer` so that when a new connection is received to the
serverSocket, then a new connection is opened to the address and port on the
other side.
22 changes: 7 additions & 15 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,10 @@

include: package:lints/recommended.yaml

# Uncomment the following section to specify additional rules.

# linter:
# rules:
# - camel_case_types

# analyzer:
# exclude:
# - path/to/excluded/files/**

# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints

# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
linter:
rules:
camel_case_types : true
unnecessary_string_interpolations : true
await_only_futures : true
unawaited_futures: true
depend_on_referenced_packages : false
32 changes: 17 additions & 15 deletions example/socket_connector_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ import 'package:socket_connector/socket_connector.dart';

void main() async {
// Once running use ncat to check the sockets
SocketConnector socketStream = await SocketConnector.serverToServer(
serverAddressA: InternetAddress.anyIPv4,
serverAddressB: InternetAddress.anyIPv4,
serverPortA: 9000,
serverPortB: 8000,
verbose: true);
print(
'Sender Port: ${socketStream.senderPort().toString()} Receiver Port: ${socketStream.receiverPort().toString()}');
SocketConnector socketConnector = await SocketConnector.serverToServer(
addressA: InternetAddress.anyIPv4,
addressB: InternetAddress.anyIPv4,
portA: 9000,
portB: 8000,
verbose: true,
logTraffic: true,
);
print('Sender Port: ${socketConnector.sideAPort}'
' Receiver Port: ${socketConnector.sideBPort}');

// Connects to ssh on port 22 on 192.168.1.149 to port 2000 on localhost
// 'ssh -p localhost' will transport you to 192.168.1.149's sshd server
InternetAddress? server = InternetAddress.tryParse('192.168.1.149');
SocketConnector socketStream1 = await SocketConnector.socketToServer(
socketAddress: server!,
socketPort: 22,
serverAddress: InternetAddress.anyIPv4,
receiverPort: 2000,
SocketConnector connector1 = await SocketConnector.socketToServer(
addressA: server!,
portA: 22,
addressB: InternetAddress.anyIPv4,
portB: 2000,
verbose: true);
print(
'Sender Port: ${socketStream1.senderPort().toString()} Receiver Port: ${socketStream1.receiverPort().toString()}');
print('Sender Port: ${connector1.sideAPort}'
' Receiver Port: ${connector1.sideBPort}');
}
111 changes: 111 additions & 0 deletions example/socket_connector_with_authenticator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:socket_connector/socket_connector.dart';

void main() async {
Duration demoConnectorTimeout = Duration(seconds: 30);

stdout.writeln("Select test scenario");
stdout.writeln("1.No side authenticated 0----0");
stdout.writeln("2.Both sides authenticated 0#----#0");
stdout.writeln("3.Only sender authenticated 0#----0");
stdout.writeln("4.Only receiver authenticated 0----#0");
stdout.write("> ");

String? option = stdin.readLineSync();
switch (option) {
case "1":
SocketConnector connector = await SocketConnector.serverToServer(
verbose: true,
timeout: demoConnectorTimeout,
);

stdout.writeln('You may now telnet to'
' Sender Port: ${connector.sideAPort}'
' Receiver Port: ${connector.sideBPort}');
stdout.writeln('There is no authentication on either side');

await connector.done;
break;

case "2":
SocketConnector connector = await SocketConnector.serverToServer(
verbose: true,
timeout: demoConnectorTimeout,
socketAuthVerifierA: goAuthVerifier,
socketAuthVerifierB: goAuthVerifier,
);

stdout.writeln('You may now telnet to'
' Sender Port: ${connector.sideAPort}'
' Receiver Port: ${connector.sideBPort}');
stdout.writeln('Both sides require authentication - to authenticate,'
' type \'go\' ');
await connector.done;
break;

case "3":
SocketConnector connector = await SocketConnector.serverToServer(
verbose: true,
timeout: demoConnectorTimeout,
socketAuthVerifierA: goAuthVerifier,
);

stdout.writeln('You may now telnet to'
' Sender Port: ${connector.sideAPort}'
' Receiver Port: ${connector.sideBPort}');
stdout.writeln(
'${connector.sideAPort} requires authentication - to authenticate,'
' type \'go\' ');

await connector.done;
break;

case "4":
SocketConnector connector = await SocketConnector.serverToServer(
verbose: true,
timeout: demoConnectorTimeout,
socketAuthVerifierB: goAuthVerifier,
);

stdout.writeln('You may now telnet to'
' Sender Port: ${connector.sideAPort}'
' Receiver Port: ${connector.sideBPort}');
stdout.writeln(
'${connector.sideBPort} requires authentication - to authenticate,'
' type \'go\' ');
await connector.done;
break;

default:
stderr.writeln("Enter 1, 2, 3 or 4");
exit(1);
}
exit(0);
}

Future<(bool, Stream<Uint8List>?)> goAuthVerifier(Socket socket) async {
Completer<(bool, Stream<Uint8List>?)> completer = Completer();
bool authenticated = false;
StreamController<Uint8List> sc = StreamController();
socket.listen((Uint8List data) {
if (authenticated) {
sc.add(data);
} else {
final message = String.fromCharCodes(data);

if (message.startsWith("go")) {
authenticated = true;
completer.complete((true, sc.stream));
}

if (message.startsWith("dontgo")) {
authenticated = false;
completer.complete((false, null));
}
}
}, onError: (error) => sc.addError(error), onDone: () => sc.close());
return completer.future;
}
30 changes: 30 additions & 0 deletions example/wait_for_connector_done_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'dart:io';
import 'dart:isolate';

import 'package:socket_connector/socket_connector.dart';

void main(List<String> arguments) async {
int portA = 8000;
int portB = 9000;
Future<void> connect() async {
SocketConnector connector = await SocketConnector.serverToServer(
addressA: InternetAddress.anyIPv4,
addressB: InternetAddress.anyIPv4,
portA: portA,
portB: portB,
verbose: true,
timeout: Duration(milliseconds: 500),
);
print('${DateTime.now()} | SocketConnector ready:'
' senderPort: ${connector.sideAPort}'
' receiverPort: ${connector.sideBPort}');
await connector.done;
print('${DateTime.now()} | SocketConnector done');
}

try {
await Isolate.run(connect);
} on FormatException catch (e) {
print(e.message);
}
}
Loading

0 comments on commit c2efc86

Please sign in to comment.