@ -32,10 +32,14 @@ class DeviceChannel : Object {
public signal void packet_received(Packet pkt);
private InetSocketAddress _isa = null;
private SocketConnection _conn = null;
private SocketConnection _sock_conn = null;
private TlsConnection _tls_conn = null;
private DataOutputStream _dout = null;
private DataInputStream _din = null;
private uint _srcid = 0;
private Socket _socket = null;
public TlsCertificate peer_certificate = null;
// channel encryption method
private Crypt _crypt = null;
@ -49,34 +53,7 @@ class DeviceChannel : Object {
debug("channel destroyed");
}
public async bool open() {
GLib.assert(this._isa != null);
debug("connect to %s:%u", _isa.address.to_string(), _isa.port);
var client = new SocketClient();
try {
_conn = yield client.connect_async(_isa);
} catch (Error e) {
//
warning("failed to connect to %s:%u: %s",
_isa.address.to_string(), _isa.port,
e.message);
// emit disconnected
return false;
}
debug("connected to %s:%u", _isa.address.to_string(), _isa.port);
// use data streams
_dout = new DataOutputStream(_conn.output_stream);
_din = new DataInputStream(_conn.input_stream);
// messages end with \n\n
_din.set_newline_type(DataStreamNewlineType.LF);
// setup socket monitoring
Socket sock = _conn.get_socket();
private void fixup_socket(Socket sock) {
#if 0
IPPROTO_TCP = 6, /* Transmission Control Protocol. */
@ -107,24 +84,149 @@ class DeviceChannel : Object {
// enable keepalive
sock.set_keepalive(true);
// prep source for monitoring events
var source = sock.create_source(IOCondition.IN);
}
private void replace_streams(InputStream input, OutputStream output) {
if (_dout != null) {
_dout.close();
}
_dout = new DataOutputStream(output);
if (_din != null) {
_din.close();
}
_din = new DataInputStream(input);
// messages end with \n\n
_din.set_newline_type(DataStreamNewlineType.LF);
}
private void monitor_events() {
var source = _socket.create_source(IOCondition.IN);
source.set_callback((src, cond) => {
return this._io_ready(cond);
});
// attach source
_srcid = source.attach(null);
}
private void unmonitor_events() {
if (_srcid > 0) {
Source.remove(_srcid);
_srcid = 0;
}
}
public async bool open() {
GLib.assert(this._isa != null);
debug("connect to %s:%u", _isa.address.to_string(), _isa.port);
var client = new SocketClient();
SocketConnection conn;
try {
conn = yield client.connect_async(_isa);
} catch (Error e) {
//
warning("failed to connect to %s:%u: %s",
_isa.address.to_string(), _isa.port,
e.message);
// emit disconnected
return false;
}
debug("connected to %s:%u", _isa.address.to_string(), _isa.port);
_socket = conn.get_socket();
// fixup socket keepalive
fixup_socket(_socket);
_sock_conn = conn;
// input/output streams will close underlying base stream when .close()
// is called on them, make sure that we pass Unix*Stream with which can
// skip closing the socket
replace_streams(new UnixInputStream(_socket.fd, false),
new UnixOutputStream(_socket.fd, false));
// start monitoring socket events
monitor_events();
return true;
}
/**
* secure:
* Switch channel to TLS mode
*
* When TLS was established, `peer_certificate` will store the remote client
* certificate. If `expected_peer` is null, the peer certificate will be
* accepted unconditionally during handshake and the caller must eventually
* decide if the client is to be trusted or not. However, if `expected_peer`
* was set, the received certificate and expected one will be compared
* during handshake and connection will be rejected if a mismatch is found.
*
* @param expected_peer the peer certificate we are expecting to see
* @return true if TLS negotiation was successful, false otherwise
*/
public async bool secure(TlsCertificate? expected_peer = null) {
GLib.assert(this._sock_conn != null);
// stop monitoring socket events
unmonitor_events();
var cert = Core.get_certificate();
// wrap with TLS
var tls_conn = TlsServerConnection.@new(_sock_conn, cert);
tls_conn.authentication_mode = TlsAuthenticationMode.REQUESTED;
tls_conn.accept_certificate.connect((peer_cert, errors) => {
info("accept certificate, flags: 0x%x", errors);
info("certificate:\n%s\n", peer_cert.certificate_pem);
this.peer_certificate = peer_cert;
if (expected_peer != null) {
if (DebugLog.Verbose) {
vdebug("verify certificate, expecting: %s, got: %s",
expected_peer.certificate_pem,
peer_cert.certificate_pem);
}
if (expected_peer.is_same(peer_cert)) {
return true;
} else {
warning("rejecting handshare, peer certificate mismatch, got:\n%s",
peer_cert.certificate_pem);
return false;
}
}
return true;
});
try {
info("attempt TLS handshake");
var res = yield tls_conn.handshake_async();
info("TLS handshare successful");
} catch (Error e) {
warning("TLS handshake failed: %s", e.message);
return false;
}
_tls_conn = tls_conn;
// data will now pass through TLS stream wrapper
replace_streams(_tls_conn.input_stream,
_tls_conn.output_stream);
// monitor socket events
monitor_events();
return true;
}
public void close() {
debug("closing connection");
if (_srcid > 0) {
Source.remove(_srcid);
_srcid = 0;
}
unmonitor_events();
try {
if (_din != null)
@ -139,14 +241,24 @@ class DeviceChannel : Object {
warning("failed to close data output: %s", e.message);
}
try {
if (_conn != null)
_conn.close();
if (_tls_conn != null)
_tls_conn.close();
} catch (Error e) {
warning("failed to close TLS connection: %s", e.message);
}
try {
if (_sock_conn != null)
_sock_conn.close();
} catch (Error e) {
warning("failed to close connection: %s", e.message);
}
_din = null;
_dout = null;
_conn = null;
_sock_conn = null;
_tls_conn = null;
_socket = null;
this.peer_certificate = null;
}
/**
@ -158,7 +270,9 @@ class DeviceChannel : Object {
public async void send(Packet pkt) {
string to_send = pkt.to_string() + "\n";
debug("send data: %s", to_send);
// _dout.put_string(data);
GLib.assert(_dout != null);
try {
_dout.put_string(to_send);
} catch (IOError e) {
@ -176,8 +290,11 @@ class DeviceChannel : Object {
public bool receive() {
size_t line_len;
string data = null;
// read line up to newline
GLib.assert(_din != null);
try {
// read line up to a newline
data = _din.read_upto("\n", -1, out line_len, null);
// expecting \n