feat(relay): add SOFTWARE attribute (#8076)

Adding a `SOFTWARE` attribute is recommended by the spec and will allow
us to identify from client logs, which version of the relay we are
talking to.
This commit is contained in:
Thomas Eizinger
2025-02-11 03:34:38 +00:00
committed by GitHub
parent feb1ec5e17
commit c9b9fb0e6c
5 changed files with 56 additions and 43 deletions

View File

@@ -16,13 +16,24 @@ pub use server::{
CreatePermission, Refresh, Server,
};
pub use sleep::Sleep;
use stun_codec::rfc5389::attributes::Software;
pub use stun_codec::rfc8656::attributes::AddressFamily;
use std::{
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
sync::LazyLock,
};
pub const VERSION: Option<&str> = option_env!("GITHUB_SHA");
pub static SOFTWARE: LazyLock<Software> = LazyLock::new(|| {
Software::new(format!(
"firezone-relay; rev={}",
VERSION.map(|rev| &rev[..8]).unwrap_or("xxxx")
))
.expect("less than 128 chars")
});
/// Describes the IP stack of a relay server.
#[derive(Debug, Copy, Clone)]
pub enum IpStack {

View File

@@ -8,7 +8,7 @@ use firezone_logging::{err_with_src, sentry_layer};
use firezone_relay::sockets::Sockets;
use firezone_relay::{
sockets, AddressFamily, AllocationPort, ChannelData, ClientSocket, Command, IpStack,
PeerSocket, Server, Sleep,
PeerSocket, Server, Sleep, VERSION,
};
use firezone_telemetry::{Telemetry, RELAY_DSN};
use futures::{future, FutureExt};
@@ -106,7 +106,7 @@ fn main() {
if args.telemetry {
telemetry.start(
args.api_url.as_str(),
option_env!("GITHUB_SHA").unwrap_or("unknown"),
VERSION.unwrap_or("unknown"),
RELAY_DSN,
);
}

View File

@@ -8,7 +8,7 @@ pub use crate::server::client_message::{
use crate::auth::{self, AuthenticatedMessage, MessageIntegrityExt, Nonces, FIREZONE};
use crate::net_ext::IpAddrExt;
use crate::{ClientSocket, IpStack, PeerSocket};
use crate::{ClientSocket, IpStack, PeerSocket, SOFTWARE};
use anyhow::Result;
use bytecodec::EncodeExt;
use core::fmt;
@@ -453,11 +453,7 @@ where
#[tracing::instrument(level = "info", skip_all, fields(software = request.software().map(|s| field::display(s.description())), tid = %format_args!("{:X}", request.transaction_id().as_bytes().hex()), %sender))]
fn handle_binding_request(&mut self, request: &Binding, sender: ClientSocket) {
let mut message = Message::new(
MessageClass::SuccessResponse,
BINDING,
request.transaction_id(),
);
let mut message = success_response(BINDING, request.transaction_id());
message.add_attribute(XorMappedAddress::new(sender.0));
tracing::info!("Handled BINDING request");
@@ -530,11 +526,7 @@ where
maybe_second_relay_addr,
);
let mut message = Message::new(
MessageClass::SuccessResponse,
ALLOCATE,
request.transaction_id(),
);
let mut message = success_response(ALLOCATE, request.transaction_id());
let port = allocation.port;
@@ -959,6 +951,8 @@ where
}
fn send_message(&mut self, message: AuthenticatedMessage, recipient: ClientSocket) {
debug_assert!(message.get_attribute::<Software>().is_some());
let method = message.method();
let class = message.class();
let error_code = message.get_attribute::<ErrorCode>().map(|e| e.code());
@@ -1098,39 +1092,31 @@ fn make_error_response(
) -> (Message<Attribute>, String) {
let method = request.method();
let mut message = Message::new(
MessageClass::ErrorResponse,
method,
request.transaction_id(),
);
let attribute = error_code.into();
let reason = attribute.reason_phrase();
let msg = format!("{method} failed with {reason}");
message.add_attribute(attribute);
(message, msg)
(
error_response(method, request.transaction_id(), attribute),
msg,
)
}
fn refresh_success_response(
effective_lifetime: Lifetime,
transaction_id: TransactionId,
) -> Message<Attribute> {
let mut message = Message::new(MessageClass::SuccessResponse, REFRESH, transaction_id);
let mut message = success_response(REFRESH, transaction_id);
message.add_attribute(effective_lifetime);
message
}
fn channel_bind_success_response(transaction_id: TransactionId) -> Message<Attribute> {
Message::new(MessageClass::SuccessResponse, CHANNEL_BIND, transaction_id)
success_response(CHANNEL_BIND, transaction_id)
}
fn create_permission_success_response(transaction_id: TransactionId) -> Message<Attribute> {
Message::new(
MessageClass::SuccessResponse,
CREATE_PERMISSION,
transaction_id,
)
success_response(CREATE_PERMISSION, transaction_id)
}
/// Represents an allocation of a client.
@@ -1356,6 +1342,25 @@ stun_codec::define_attribute_enums!(
]
);
fn success_response(method: Method, id: TransactionId) -> Message<Attribute> {
let mut message = Message::new(MessageClass::SuccessResponse, method, id);
message.add_attribute(SOFTWARE.clone());
message
}
fn error_response(
method: Method,
transaction_id: TransactionId,
error_code: ErrorCode,
) -> Message<Attribute> {
let mut message = Message::new(MessageClass::ErrorResponse, method, transaction_id);
message.add_attribute(SOFTWARE.clone());
message.add_attribute(error_code);
message
}
fn earliest(left: Option<Instant>, right: Option<Instant>) -> Option<Instant> {
match (left, right) {
(None, None) => None,

View File

@@ -1,6 +1,6 @@
use crate::auth::{generate_password, split_username, systemtime_from_unix, FIREZONE};
use crate::server::channel_data::ChannelData;
use crate::server::UDP_TRANSPORT;
use crate::server::{error_response, UDP_TRANSPORT};
use crate::Attribute;
use anyhow::{Context, Result};
use bytecodec::DecodeExt;
@@ -17,7 +17,7 @@ use stun_codec::rfc5766::methods::{ALLOCATE, CHANNEL_BIND, CREATE_PERMISSION, RE
use stun_codec::rfc8656::attributes::{
AdditionalAddressFamily, AddressFamily, RequestedAddressFamily,
};
use stun_codec::{Message, MessageClass, Method, TransactionId};
use stun_codec::{Message, MessageClass, TransactionId};
use uuid::Uuid;
/// The maximum lifetime of an allocation.
@@ -615,17 +615,6 @@ fn bad_request(message: &Message<Attribute>) -> Message<Attribute> {
)
}
fn error_response(
method: Method,
transaction_id: TransactionId,
error_code: ErrorCode,
) -> Message<Attribute> {
let mut message = Message::new(MessageClass::ErrorResponse, method, transaction_id);
message.add_attribute(error_code);
message
}
#[derive(Debug)]
pub enum Error {
BadChannelData(io::Error),

View File

@@ -3,7 +3,7 @@
use bytecodec::{DecodeExt, EncodeExt};
use firezone_relay::{
AddressFamily, Allocate, AllocationPort, Attribute, Binding, ChannelBind, ChannelData,
ClientMessage, ClientSocket, Command, IpStack, PeerSocket, Refresh, Server,
ClientMessage, ClientSocket, Command, IpStack, PeerSocket, Refresh, Server, SOFTWARE,
};
use rand::rngs::mock::StepRng;
use secrecy::SecretString;
@@ -835,6 +835,7 @@ fn binding_response(
) -> Message<Attribute> {
let mut message =
Message::<Attribute>::new(MessageClass::SuccessResponse, BINDING, transaction_id);
message.add_attribute(SOFTWARE.clone());
message.add_attribute(XorMappedAddress::new(address.into()));
message
@@ -849,6 +850,7 @@ fn allocate_response(
) -> Message<Attribute> {
let mut message =
Message::<Attribute>::new(MessageClass::SuccessResponse, ALLOCATE, transaction_id);
message.add_attribute(SOFTWARE.clone());
message.add_attribute(XorRelayAddress::new(SocketAddr::new(
public_relay_addr.into(),
port,
@@ -865,6 +867,7 @@ fn unauthorized_allocate_response(
) -> Message<Attribute> {
let mut message =
Message::<Attribute>::new(MessageClass::ErrorResponse, ALLOCATE, transaction_id);
message.add_attribute(SOFTWARE.clone());
message.add_attribute(ErrorCode::from(Unauthorized));
message.add_attribute(Realm::new("firezone".to_owned()).unwrap());
message.add_attribute(Nonce::new(nonce.as_hyphenated().to_string()).unwrap());
@@ -875,13 +878,18 @@ fn unauthorized_allocate_response(
fn refresh_response(transaction_id: TransactionId, lifetime: Lifetime) -> Message<Attribute> {
let mut message =
Message::<Attribute>::new(MessageClass::SuccessResponse, REFRESH, transaction_id);
message.add_attribute(SOFTWARE.clone());
message.add_attribute(lifetime);
message
}
fn channel_bind_response(transaction_id: TransactionId) -> Message<Attribute> {
Message::<Attribute>::new(MessageClass::SuccessResponse, CHANNEL_BIND, transaction_id)
let mut message =
Message::<Attribute>::new(MessageClass::SuccessResponse, CHANNEL_BIND, transaction_id);
message.add_attribute(SOFTWARE.clone());
message
}
fn parse_message(message: &[u8]) -> Message<Attribute> {