Example userspace AF_RXRPC client usage
AF_RXRPC is implemented as a network protocol and, as such, is
accessible from userspace through the Linux socket interface. A
socket is opened and then multiple calls may be made over it using
the ancillary data provided to sendmsg()
and recvmsg()
to determine the call.
Prologue
First of all, an AF_RXRPC socket must be opened:
client = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
The second argument is the transport socket type (which must
be DGRAM
for the moment) and the third argument
specified the transport protocol (PF_INET
or PF_INET6
). This gets you a file descriptor that can
be used to perform RPC message passing. You can then set the
security on it:
int sec = RXRPC_SECURITY_AUTH;
ret = setsockopt(client, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL, &sec, sizeof(sec));
const char principal[] = "afs@GRAND.CENTRAL.ORG";
ret = setsockopt(client, SOL_RXRPC, RXRPC_SECURITY_KEY, principal, strlen(principal));
The first statement sets the minimum security level required to one
of checksum only (PLAIN
), authenticated packet only
(AUTH
) or full packet encryption (ENCRYPT
).
The second statement sets the name of the key to be used.
An address can then be bound to the local endpoint. This is optional for a client, but if a specific address and/or port is required for the local UDP transport, then this must be used.
memset(&my_addr, 0, sizeof(my_addr));
my_addr.my_addr_family = AF_RXRPC;
my_addr.my_addr_service = 0;
my_addr.transport_type = SOCK_DGRAM;
my_addr.transport_len = sizeof(my_addr.transport.sin);
my_addr.transport.sin.sin_family = AF_INET;
my_addr.transport.sin.sin_port = htons(7001);
memcpy(&my_addr.transport.sin.sin_addr, &local_addr, 4);
ret = bind(client, (struct sockaddr *)&my_addr, sizeof(my_addr));
For a service, .srx_service
is where the service ID on
which the caller wishes to listen is specified; this should
be 0
for a purely client socket.
It should be noted that client sockets opened separately may share a UDP port inside the kernel with each other, but sharing a UDP socket between a service and a client or between a pair of services is forbidden.
We then need to set up the address of the server to which we're going to try to talk:
peer_addr.peer_addr_family = AF_RXRPC;
peer_addr.peer_addr_service = 52;
peer_addr.transport_type = SOCK_DGRAM;
peer_addr.transport_len = sizeof(peer_addr.transport.sin);
peer_addr.transport.sin.sin_family = AF_INET;
peer_addr.transport.sin.sin_port = htons(7003);
memcpy(&peer_addr.transport.sin.sin_addr, &remote_addr, 4);
The .srx_service
member holds the service ID that we
wish to contact on the far port. .transport
holds the
transport socket parameters. The .transport_type
and .transport_family
must match the values given to
socket as type
and protocol
. .transport.sin
carries the
target UDP address.
Call Initiation and Transmission Phase
Next we need to load up the ancillary data into the control buffer
for sendmsg()
to use in call multiplexing:
ctrllen = 0;
RXRPC_ADD_CALLID(control, ctrllen, 0x12345);
This includes userspace's identifier for the call we're about to
launch (in this case 0x12345
). It must be included in
all subsequent sendmsg()
calls and it will be included
in the return from all subsequent recvmsg()
calls. It
is valid until recvmsg()
returns MSG_EOR
in
conjuction with this call ID. The call identifier is an integer of
size unsigned long
and may be used to hold a pointer if
that is convenient.
We then marshal the request data into a buffer:
static char vlname[] = "root.cell";
vllen = sizeof(vlname) - 1;
param[0] = htonl(VLGETENTRYBYNAME);
param[1] = htonl(vllen);
padding = 0;
And then we call sendmsg() to send the data, inserting suitable padding to round strings up to four-byte alignment:
iov[0].iov_len = sizeof(param);
iov[0].iov_base = param;
iov[1].iov_len = vllen;
iov[1].iov_base = vlname;
iov[2].iov_len = 4 - (vllen & 3);
iov[2].iov_base = &padding;
ioc = (iov[2].iov_len > 0) ? 3 : 2;
msg.msg_name = (struct sockaddr *)&peer_addr;
msg.msg_namelen = sizeof(peer_addr);
msg.msg_iov = iov;
msg.msg_iovlen = ioc;
msg.msg_control = control;
msg.msg_controllen = ctrllen;
msg.msg_flags = 0;
ret = sendmsg(client, &msg, 0);
Note that we pass MSG_MORE if we want to send more data. AF_RXRPC will collect the data into appropriately sized packets so that there is no wastage of sequence number space (there is a limit to the number of DATA packets that can be sent in each direction).
Reception Phase
We can listen for a response from the server:
iov[0].iov_base = buffer;
iov[0].iov_len = sizeof(buffer);
msg.msg_name = &rx_addr;
msg.msg_namelen = sizeof(rx_addr);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
msg.msg_flags = 0;
ret = recvmsg(client, &msg, 0);
This will return 0
or the number of bytes read into the
buffer if it successfully queried a call - even if that call failed -
and -ENODATA
if no calls were outstanding on a client
socket. If a call did fail, the control message buffer will hold
ancillary data indicating the reason. The ancillary data will also
hold the call identifier. It should be noted that if several calls
are in progress simultaneously upon a socket, consecutive calls
to recvmsg()
may return information from different
calls. It is possible, however, to specify MSG_PEEK
to
find out what the next call will be without altering the call state.
If there is more data to be read because the call has not yet reached
the end of its reception phase, then MSG_MORE
will be
returned.
If any terminal indication is given, then the recvmsg will have
indicated MSG_EOR
to indicate that the call identifier
is now released and can be reused.
The contents of the control buffer then need to be parsed:
struct cmsghdr *cmsg;
unsigned long call_identifier;
int abort_code, error;
enum {
call_ongoing,
call_remotely_aborted,
service_call_finally_acked,
call_network_error,
call_error,
call_busy,
} call_state = call_ongoing;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
void *p = CMSG_DATA(cmsg);
int n = cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg));
if (cmsg->cmsg_level == SOL_RXRPC) {
switch (cmsg->cmsg_type) {
case RXRPC_USER_CALL_ID:
memcpy(&call_identifier, p, sizeof(user_id));
continue;
case RXRPC_ABORT:
memcpy(&abort_code, p, sizeof(abort_code));
call_state = call_remotely_aborted;
continue;
case RXRPC_ACK:
call_state = service_call_finally_acked;
continue;
case RXRPC_NET_ERROR:
memcpy(&error, p, sizeof(error));
errno = error;
call_state = call_network_error;
continue;
case RXRPC_BUSY:
call_state = call_busy;
continue;
case RXRPC_LOCAL_ERROR:
memcpy(&error, p, sizeof(error));
errno = error;
call_state = call_error;
continue;
default:
break;
}
}
}
The above snippet parses the control buffer, extracting the call identifier along with the code of any abort received and any local or network error incurred. It also extracts the indication that the call was terminated because the server kept returning a busy packet and the indication that a service call received the final ACK from the remote client.
Aborting a Call
An active call may be aborted as long as recvmsg() hasn't yet
indicated that the call has been terminated by
flagging MSG_EOR
. This is done by specifying the call
identifier and the abort code in the control message:
ctrllen = 0;
RXRPC_ADD_CALLID(control, ctrllen, 0x12345);
RXRPC_ADD_ABORT(control, ctrllen, 0x6789);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = NULL;
msg.msg_iovlen = 0;
msg.msg_control = control;
msg.msg_controllen = ctrllen;
msg.msg_flags = 0;
ret = sendmsg(client, &msg, 0);
Note that this still has to be followed with a call
to recvmsg()
to find out how the call actually ended
(it's possible the call was aborted first from the other side, for
example).