Skip to content

Commit 6c7ee4c

Browse files
committed
feat: support tls sender
Signed-off-by: tison <[email protected]>
1 parent 1feefe8 commit 6c7ee4c

File tree

8 files changed

+176
-30
lines changed

8 files changed

+176
-30
lines changed

Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ all-features = true
3030
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
3131
rustdoc-args = ["--cfg", "docsrs"]
3232

33+
[features]
34+
native-tls = ["dep:native-tls"]
35+
3336
[dependencies]
3437
jiff = { version = "0.1.14" }
3538

39+
# Optional dependencies
40+
native-tls = { version = "0.2.12", optional = true }
41+
3642
[target.'cfg(unix)'.dependencies]
3743
cfg-if = { version = "1.0.0" }
3844
nix = { version = "0.29.0", features = ["hostname"] }
@@ -48,6 +54,12 @@ doc-scrape-examples = true
4854
name = "tcp_sender"
4955
path = "examples/tcp_sender.rs"
5056

57+
[[example]]
58+
doc-scrape-examples = true
59+
name = "tls_sender"
60+
path = "examples/tls_sender.rs"
61+
required-features = ["native-tls"]
62+
5163
[[example]]
5264
doc-scrape-examples = true
5365
name = "udp_sender"

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ This crate is built against the latest stable release, and its minimum supported
9494

9595
The policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if Fasyslog 1.0 requires Rust 1.20.0, then Fasyslog 1.0.z for all values of z will also require Rust 1.20.0 or newer. However, Fasyslog 1.y for y > 0 may require a newer minimum version of Rust.
9696

97+
## When to release a 1.0 version
98+
99+
I'm fine with the current API design and ready for a 1.0 release. Just leave a few months for feedback on the crate's usability. If you have any suggestions or feedback, please open an issue.
100+
101+
I'm going to release a 1.0 version as early as 2025-01.
102+
97103
## License
98104

99105
This project is licensed under [Apache License, Version 2.0](LICENSE).

examples/tls_sender.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2024 FastLabs Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use fasyslog::Severity;
16+
17+
fn main() {
18+
let mut sender = fasyslog::sender::tls_well_known("127.0.0.1").unwrap();
19+
let mut generator = names::Generator::default();
20+
for _ in 0..100 {
21+
let name = generator.next().unwrap();
22+
let message = format!("Hello, {name}!");
23+
sender
24+
.send_rfc5424(Severity::ERROR, None::<String>, Vec::new(), message)
25+
.unwrap();
26+
}
27+
sender.flush().unwrap();
28+
}

src/sender/internal.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ macro_rules! impl_syslog_sender_common {
2020
&mut self,
2121
severity: $crate::Severity,
2222
message: M,
23-
) -> io::Result<()> {
23+
) -> std::io::Result<()> {
2424
let message = self.context.format_rfc3164(severity, Some(message));
2525
self.send_formatted(message.to_string().as_bytes())
2626
}
@@ -32,7 +32,7 @@ macro_rules! impl_syslog_sender_common {
3232
msgid: Option<S>,
3333
elements: Vec<$crate::SDElement>,
3434
message: M,
35-
) -> io::Result<()> {
35+
) -> std::io::Result<()> {
3636
let message = self
3737
.context
3838
.format_rfc5424(severity, msgid, elements, Some(message));
@@ -43,3 +43,25 @@ macro_rules! impl_syslog_sender_common {
4343
}
4444

4545
pub(crate) use impl_syslog_sender_common;
46+
47+
macro_rules! impl_syslog_stream_send_formatted {
48+
($sender:ident) => {
49+
impl $sender {
50+
/// Send a formatted message to the stream.
51+
pub fn send_formatted(&mut self, message: &[u8]) -> std::io::Result<()> {
52+
use std::io::Write;
53+
self.writer.write_all(message)?;
54+
self.writer.write_all(self.postfix.as_bytes())?;
55+
Ok(())
56+
}
57+
58+
/// Flush the stream.
59+
pub fn flush(&mut self) -> std::io::Result<()> {
60+
use std::io::Write;
61+
self.writer.flush()
62+
}
63+
}
64+
};
65+
}
66+
67+
pub(crate) use impl_syslog_stream_send_formatted;

src/sender/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ mod unix;
2525
#[cfg(unix)]
2626
pub use unix::*;
2727

28+
#[cfg(feature = "native-tls")]
29+
mod native_tls;
30+
#[cfg(feature = "native-tls")]
31+
pub use native_tls::*;
32+
2833
mod tcp;
2934
pub use tcp::*;
3035

@@ -38,6 +43,8 @@ pub(crate) mod internal;
3843
pub enum SyslogSender {
3944
Tcp(TcpSender),
4045
Udp(UdpSender),
46+
#[cfg(feature = "native-tls")]
47+
Tls(TlsSender),
4148
#[cfg(unix)]
4249
UnixDatagram(UnixDatagramSender),
4350
#[cfg(unix)]
@@ -54,6 +61,8 @@ impl SyslogSender {
5461
match self {
5562
SyslogSender::Tcp(sender) => sender.send_rfc3164(severity, message),
5663
SyslogSender::Udp(sender) => sender.send_rfc3164(severity, message),
64+
#[cfg(feature = "native-tls")]
65+
SyslogSender::Tls(sender) => sender.send_rfc3164(severity, message),
5766
#[cfg(unix)]
5867
SyslogSender::UnixDatagram(sender) => sender.send_rfc3164(severity, message),
5968
#[cfg(unix)]
@@ -72,6 +81,8 @@ impl SyslogSender {
7281
match self {
7382
SyslogSender::Tcp(sender) => sender.send_rfc5424(severity, msgid, elements, message),
7483
SyslogSender::Udp(sender) => sender.send_rfc5424(severity, msgid, elements, message),
84+
#[cfg(feature = "native-tls")]
85+
SyslogSender::Tls(sender) => sender.send_rfc5424(severity, msgid, elements, message),
7586
#[cfg(unix)]
7687
SyslogSender::UnixDatagram(sender) => {
7788
sender.send_rfc5424(severity, msgid, elements, message)
@@ -88,6 +99,8 @@ impl SyslogSender {
8899
match self {
89100
SyslogSender::Tcp(sender) => sender.send_formatted(formatted),
90101
SyslogSender::Udp(sender) => sender.send_formatted(formatted),
102+
#[cfg(feature = "native-tls")]
103+
SyslogSender::Tls(sender) => sender.send_formatted(formatted),
91104
#[cfg(unix)]
92105
SyslogSender::UnixDatagram(sender) => sender.send_formatted(formatted),
93106
#[cfg(unix)]
@@ -108,6 +121,8 @@ impl SyslogSender {
108121
match self {
109122
SyslogSender::Tcp(sender) => sender.flush(),
110123
SyslogSender::Udp(_) => Ok(()),
124+
#[cfg(feature = "native-tls")]
125+
SyslogSender::Tls(sender) => sender.flush(),
111126
#[cfg(unix)]
112127
SyslogSender::UnixDatagram(_) => Ok(()),
113128
#[cfg(unix)]

src/sender/native_tls.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use std::borrow::Cow;
2+
use std::io;
3+
use std::io::BufWriter;
4+
use std::net::TcpStream;
5+
use std::net::ToSocketAddrs;
6+
7+
use native_tls::TlsStream;
8+
use native_tls::{TlsConnector, TlsConnectorBuilder};
9+
10+
use crate::format::SyslogContext;
11+
use crate::sender::internal::impl_syslog_sender_common;
12+
use crate::sender::internal::impl_syslog_stream_send_formatted;
13+
14+
/// Create a TLS sender that sends messages to the well-known port (6514).
15+
///
16+
/// See also [RFC-5425] §4.1 Port Assignment.
17+
///
18+
/// [RFC-5425]: https://datatracker.ietf.org/doc/html/rfc5425#section-4.1
19+
pub fn tls_well_known<S: AsRef<str>>(domain: S) -> io::Result<TlsSender> {
20+
let domain = domain.as_ref();
21+
tls(format!("{domain}:6514"), domain)
22+
}
23+
24+
/// Create a TLS sender that sends messages to the given address.
25+
pub fn tls<A: ToSocketAddrs, S: AsRef<str>>(addr: A, domain: S) -> io::Result<TlsSender> {
26+
tls_with(addr, domain, TlsConnector::builder())
27+
}
28+
29+
/// Create a TLS sender that sends messages to the given address with certificate builder.
30+
pub fn tls_with<A: ToSocketAddrs, S: AsRef<str>>(
31+
addr: A,
32+
domain: S,
33+
builder: TlsConnectorBuilder,
34+
) -> io::Result<TlsSender> {
35+
TlsSender::connect(addr, domain, builder)
36+
}
37+
38+
/// A syslog sender that sends messages to a TCP socket over TLS.
39+
#[derive(Debug)]
40+
pub struct TlsSender {
41+
writer: BufWriter<TlsStream<TcpStream>>,
42+
context: SyslogContext,
43+
postfix: Cow<'static, str>,
44+
}
45+
46+
impl TlsSender {
47+
/// Connect to a TCP socket over TLS at the given address.
48+
pub fn connect<A: ToSocketAddrs, S: AsRef<str>>(
49+
addr: A,
50+
domain: S,
51+
builder: TlsConnectorBuilder,
52+
) -> io::Result<Self> {
53+
let domain = domain.as_ref();
54+
let stream = TcpStream::connect(addr)?;
55+
let connector = builder.build().map_err(io::Error::other)?;
56+
let stream = connector
57+
.connect(domain, stream)
58+
.map_err(io::Error::other)?;
59+
Ok(Self {
60+
writer: BufWriter::new(stream),
61+
context: SyslogContext::default(),
62+
postfix: Cow::Borrowed("\r\n"),
63+
})
64+
}
65+
66+
/// Set the postfix when formatting Syslog message.
67+
///
68+
/// This is generally '\r\n' as defined in [RFC-6587] §3.4.2.
69+
///
70+
/// [RFC-6587]: https://datatracker.ietf.org/doc/html/rfc6587
71+
pub fn set_postfix(&mut self, postfix: impl Into<Cow<'static, str>>) {
72+
self.postfix = postfix.into();
73+
}
74+
75+
/// Set the context when formatting Syslog message.
76+
pub fn set_context(mut self, context: SyslogContext) {
77+
self.context = context;
78+
}
79+
80+
/// Mutate the context when formatting Syslog message.
81+
pub fn mut_context(&mut self) -> &mut SyslogContext {
82+
&mut self.context
83+
}
84+
}
85+
86+
impl_syslog_sender_common!(TlsSender);
87+
impl_syslog_stream_send_formatted!(TlsSender);

src/sender/tcp.rs

+2-14
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
use std::borrow::Cow;
1616
use std::io;
1717
use std::io::BufWriter;
18-
use std::io::Write;
1918
use std::net::TcpStream;
2019
use std::net::ToSocketAddrs;
2120

2221
use crate::format::SyslogContext;
2322
use crate::sender::internal::impl_syslog_sender_common;
23+
use crate::sender::internal::impl_syslog_stream_send_formatted;
2424

2525
/// Create a TCP sender that sends messages to the well-known port (601).
2626
///
@@ -73,19 +73,7 @@ impl TcpSender {
7373
pub fn mut_context(&mut self) -> &mut SyslogContext {
7474
&mut self.context
7575
}
76-
77-
/// Send a pre-formatted message.
78-
pub fn send_formatted(&mut self, formatted: &[u8]) -> io::Result<()> {
79-
self.writer.write_all(formatted)?;
80-
self.writer.write_all(self.postfix.as_bytes())?;
81-
Ok(())
82-
}
83-
84-
/// Flush the writer.
85-
pub fn flush(&mut self) -> io::Result<()> {
86-
use std::io::Write;
87-
self.writer.flush()
88-
}
8976
}
9077

9178
impl_syslog_sender_common!(TcpSender);
79+
impl_syslog_stream_send_formatted!(TcpSender);

src/sender/unix.rs

+2-14
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
use std::borrow::Cow;
1616
use std::io;
1717
use std::io::BufWriter;
18-
use std::io::Write;
1918
use std::os::unix::net::UnixDatagram;
2019
use std::os::unix::net::UnixStream;
2120
use std::path::Path;
2221

2322
use crate::format::SyslogContext;
2423
use crate::sender::internal::impl_syslog_sender_common;
24+
use crate::sender::internal::impl_syslog_stream_send_formatted;
2525
use crate::sender::SyslogSender;
2626

2727
/// Create a Unix datagram sender that sends messages to the given path.
@@ -141,19 +141,7 @@ impl UnixStreamSender {
141141
pub fn mut_context(&mut self) -> &mut SyslogContext {
142142
&mut self.context
143143
}
144-
145-
/// Send a pre-formatted message.
146-
pub fn send_formatted(&mut self, formatted: &[u8]) -> io::Result<()> {
147-
self.writer.write_all(formatted)?;
148-
self.writer.write_all(self.postfix.as_bytes())?;
149-
Ok(())
150-
}
151-
152-
/// Flush the writer.
153-
pub fn flush(&mut self) -> io::Result<()> {
154-
use std::io::Write;
155-
self.writer.flush()
156-
}
157144
}
158145

159146
impl_syslog_sender_common!(UnixStreamSender);
147+
impl_syslog_stream_send_formatted!(UnixStreamSender);

0 commit comments

Comments
 (0)