Skip to main content

TLS server

Creating a TLS server is the same process as creating a TCP server, except that an extra TlsServerConfig is required. For more details about TLS support and the configuration options, check the TLS general information page.

There are two kinds of TLS server:

  • create_tls_with_authz (spawn_tls_server_task_with_authz in Rust) implements Modbus Security: each client certificate must carry a role, and your AuthorizationHandler decides which requests that role may make.
  • create_tls (spawn_tls_server_task in Rust) takes no AuthorizationHandler, doesn't require the role, and lets any authenticated client perform any Modbus operation.

Authorization handler​

The examples on this page use this AuthorizationHandler:

/// Allows any role to read, and only the "operator" role to write
struct ExampleAuthorizationHandler;

impl ExampleAuthorizationHandler {
fn authorize_write(&self, unit_id: UnitId, role: &str) -> Authorization {
if role == "operator" {
Authorization::Allow
} else {
tracing::info!("denied write to unit {} for role '{role}'", unit_id.value);
Authorization::Deny
}
}
}

impl AuthorizationHandler for ExampleAuthorizationHandler {
fn read_coils(&self, _unit_id: UnitId, _range: AddressRange, _role: &str) -> Authorization {
Authorization::Allow
}

fn read_discrete_inputs(
&self,
_unit_id: UnitId,
_range: AddressRange,
_role: &str,
) -> Authorization {
Authorization::Allow
}

fn read_holding_registers(
&self,
_unit_id: UnitId,
_range: AddressRange,
_role: &str,
) -> Authorization {
Authorization::Allow
}

fn read_input_registers(
&self,
_unit_id: UnitId,
_range: AddressRange,
_role: &str,
) -> Authorization {
Authorization::Allow
}

fn write_single_coil(&self, unit_id: UnitId, _idx: u16, role: &str) -> Authorization {
self.authorize_write(unit_id, role)
}

fn write_single_register(&self, unit_id: UnitId, _idx: u16, role: &str) -> Authorization {
self.authorize_write(unit_id, role)
}

fn write_multiple_coils(
&self,
unit_id: UnitId,
_range: AddressRange,
role: &str,
) -> Authorization {
self.authorize_write(unit_id, role)
}

fn write_multiple_registers(
&self,
unit_id: UnitId,
_range: AddressRange,
role: &str,
) -> Authorization {
self.authorize_write(unit_id, role)
}
}

Certificate chain configuration​

let handler = SimpleHandler::new(POINT_COUNT).wrap();

// map unit ids to a handler for processing requests
let map = ServerHandlerMap::single(UnitId::new(UNIT_ID), handler.clone());

let tls_config = TlsServerConfig::new(
Path::new("./certs/ca_chain/ca_cert.pem"),
Path::new("./certs/ca_chain/server_cert.pem"),
Path::new("./certs/ca_chain/server_key.pem"),
None, // no password
MinTlsVersion::V1_2,
CertificateMode::AuthorityBased,
)?;

let server = rodbus::server::spawn_tls_server_task_with_authz(
MAX_SESSIONS,
endpoint,
map,
ExampleAuthorizationHandler.wrap(),
tls_config,
AddressFilter::Any,
DecodeLevel::new(
AppDecodeLevel::DataHeaders,
FrameDecodeLevel::Nothing,
PhysDecodeLevel::Nothing,
),
)
.await?;

Self-signed certificate configuration​

Only the TlsServerConfig differs from the certificate chain example:

let tls_config = TlsServerConfig::new(
Path::new("./certs/self_signed/entity1_cert.pem"),
Path::new("./certs/self_signed/entity2_cert.pem"),
Path::new("./certs/self_signed/entity2_key.pem"),
None, // no password
MinTlsVersion::V1_2,
CertificateMode::SelfSigned,
)?;

let server = rodbus::server::spawn_tls_server_task_with_authz(
MAX_SESSIONS,
endpoint,
map,
ExampleAuthorizationHandler.wrap(),
tls_config,
AddressFilter::Any,
DecodeLevel::new(
AppDecodeLevel::DataHeaders,
FrameDecodeLevel::Nothing,
PhysDecodeLevel::Nothing,
),
)
.await?;