2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2018-10-20 02:57:57 +03:00
/* AFS vlserver list management.
*
* Copyright ( C ) 2018 Red Hat , Inc . All Rights Reserved .
* Written by David Howells ( dhowells @ redhat . com )
*/
# include <linux/kernel.h>
# include <linux/slab.h>
# include "internal.h"
struct afs_vlserver * afs_alloc_vlserver ( const char * name , size_t name_len ,
unsigned short port )
{
struct afs_vlserver * vlserver ;
2023-10-30 14:53:16 +03:00
static atomic_t debug_ids ;
2018-10-20 02:57:57 +03:00
vlserver = kzalloc ( struct_size ( vlserver , name , name_len + 1 ) ,
GFP_KERNEL ) ;
if ( vlserver ) {
2022-07-06 12:52:14 +03:00
refcount_set ( & vlserver - > ref , 1 ) ;
2018-10-20 02:57:57 +03:00
rwlock_init ( & vlserver - > lock ) ;
2018-10-20 02:57:59 +03:00
init_waitqueue_head ( & vlserver - > probe_wq ) ;
spin_lock_init ( & vlserver - > probe_lock ) ;
2023-10-30 14:53:16 +03:00
vlserver - > debug_id = atomic_inc_return ( & debug_ids ) ;
2020-08-19 17:27:17 +03:00
vlserver - > rtt = UINT_MAX ;
2018-10-20 02:57:57 +03:00
vlserver - > name_len = name_len ;
2023-10-26 20:13:13 +03:00
vlserver - > service_id = VL_SERVICE ;
2018-10-20 02:57:57 +03:00
vlserver - > port = port ;
memcpy ( vlserver - > name , name , name_len ) ;
}
return vlserver ;
}
static void afs_vlserver_rcu ( struct rcu_head * rcu )
{
struct afs_vlserver * vlserver = container_of ( rcu , struct afs_vlserver , rcu ) ;
2023-10-19 15:59:03 +03:00
afs_put_addrlist ( rcu_access_pointer ( vlserver - > addresses ) ,
afs_alist_trace_put_vlserver ) ;
2018-10-20 02:57:57 +03:00
kfree_rcu ( vlserver , rcu ) ;
}
void afs_put_vlserver ( struct afs_net * net , struct afs_vlserver * vlserver )
{
2022-07-06 12:52:14 +03:00
if ( vlserver & &
refcount_dec_and_test ( & vlserver - > ref ) )
call_rcu ( & vlserver - > rcu , afs_vlserver_rcu ) ;
2018-10-20 02:57:57 +03:00
}
struct afs_vlserver_list * afs_alloc_vlserver_list ( unsigned int nr_servers )
{
struct afs_vlserver_list * vllist ;
vllist = kzalloc ( struct_size ( vllist , servers , nr_servers ) , GFP_KERNEL ) ;
if ( vllist ) {
2022-07-06 12:52:14 +03:00
refcount_set ( & vllist - > ref , 1 ) ;
2018-10-20 02:57:57 +03:00
rwlock_init ( & vllist - > lock ) ;
}
return vllist ;
}
void afs_put_vlserverlist ( struct afs_net * net , struct afs_vlserver_list * vllist )
{
if ( vllist ) {
2022-07-06 12:52:14 +03:00
if ( refcount_dec_and_test ( & vllist - > ref ) ) {
2018-10-20 02:57:57 +03:00
int i ;
for ( i = 0 ; i < vllist - > nr_servers ; i + + ) {
afs_put_vlserver ( net , vllist - > servers [ i ] . server ) ;
}
kfree_rcu ( vllist , rcu ) ;
}
}
}
static u16 afs_extract_le16 ( const u8 * * _b )
{
u16 val ;
val = ( u16 ) * ( * _b ) + + < < 0 ;
val | = ( u16 ) * ( * _b ) + + < < 8 ;
return val ;
}
/*
* Build a VL server address list from a DNS queried server list .
*/
rxrpc, afs: Allow afs to pin rxrpc_peer objects
Change rxrpc's API such that:
(1) A new function, rxrpc_kernel_lookup_peer(), is provided to look up an
rxrpc_peer record for a remote address and a corresponding function,
rxrpc_kernel_put_peer(), is provided to dispose of it again.
(2) When setting up a call, the rxrpc_peer object used during a call is
now passed in rather than being set up by rxrpc_connect_call(). For
afs, this meenat passing it to rxrpc_kernel_begin_call() rather than
the full address (the service ID then has to be passed in as a
separate parameter).
(3) A new function, rxrpc_kernel_remote_addr(), is added so that afs can
get a pointer to the transport address for display purposed, and
another, rxrpc_kernel_remote_srx(), to gain a pointer to the full
rxrpc address.
(4) The function to retrieve the RTT from a call, rxrpc_kernel_get_srtt(),
is then altered to take a peer. This now returns the RTT or -1 if
there are insufficient samples.
(5) Rename rxrpc_kernel_get_peer() to rxrpc_kernel_call_get_peer().
(6) Provide a new function, rxrpc_kernel_get_peer(), to get a ref on a
peer the caller already has.
This allows the afs filesystem to pin the rxrpc_peer records that it is
using, allowing faster lookups and pointer comparisons rather than
comparing sockaddr_rxrpc contents. It also makes it easier to get hold of
the RTT. The following changes are made to afs:
(1) The addr_list struct's addrs[] elements now hold a peer struct pointer
and a service ID rather than a sockaddr_rxrpc.
(2) When displaying the transport address, rxrpc_kernel_remote_addr() is
used.
(3) The port arg is removed from afs_alloc_addrlist() since it's always
overridden.
(4) afs_merge_fs_addr4() and afs_merge_fs_addr6() do peer lookup and may
now return an error that must be handled.
(5) afs_find_server() now takes a peer pointer to specify the address.
(6) afs_find_server(), afs_compare_fs_alists() and afs_merge_fs_addr[46]{}
now do peer pointer comparison rather than address comparison.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
2023-10-19 14:55:11 +03:00
static struct afs_addr_list * afs_extract_vl_addrs ( struct afs_net * net ,
const u8 * * _b , const u8 * end ,
2018-10-20 02:57:57 +03:00
u8 nr_addrs , u16 port )
{
struct afs_addr_list * alist ;
const u8 * b = * _b ;
int ret = - EINVAL ;
2023-10-26 20:13:13 +03:00
alist = afs_alloc_addrlist ( nr_addrs ) ;
2018-10-20 02:57:57 +03:00
if ( ! alist )
return ERR_PTR ( - ENOMEM ) ;
if ( nr_addrs = = 0 )
return alist ;
for ( ; nr_addrs > 0 & & end - b > = nr_addrs ; nr_addrs - - ) {
struct dns_server_list_v1_address hdr ;
__be32 x [ 4 ] ;
hdr . address_type = * b + + ;
switch ( hdr . address_type ) {
case DNS_ADDRESS_IS_IPV4 :
if ( end - b < 4 ) {
_leave ( " = -EINVAL [short inet] " ) ;
goto error ;
}
memcpy ( x , b , 4 ) ;
rxrpc, afs: Allow afs to pin rxrpc_peer objects
Change rxrpc's API such that:
(1) A new function, rxrpc_kernel_lookup_peer(), is provided to look up an
rxrpc_peer record for a remote address and a corresponding function,
rxrpc_kernel_put_peer(), is provided to dispose of it again.
(2) When setting up a call, the rxrpc_peer object used during a call is
now passed in rather than being set up by rxrpc_connect_call(). For
afs, this meenat passing it to rxrpc_kernel_begin_call() rather than
the full address (the service ID then has to be passed in as a
separate parameter).
(3) A new function, rxrpc_kernel_remote_addr(), is added so that afs can
get a pointer to the transport address for display purposed, and
another, rxrpc_kernel_remote_srx(), to gain a pointer to the full
rxrpc address.
(4) The function to retrieve the RTT from a call, rxrpc_kernel_get_srtt(),
is then altered to take a peer. This now returns the RTT or -1 if
there are insufficient samples.
(5) Rename rxrpc_kernel_get_peer() to rxrpc_kernel_call_get_peer().
(6) Provide a new function, rxrpc_kernel_get_peer(), to get a ref on a
peer the caller already has.
This allows the afs filesystem to pin the rxrpc_peer records that it is
using, allowing faster lookups and pointer comparisons rather than
comparing sockaddr_rxrpc contents. It also makes it easier to get hold of
the RTT. The following changes are made to afs:
(1) The addr_list struct's addrs[] elements now hold a peer struct pointer
and a service ID rather than a sockaddr_rxrpc.
(2) When displaying the transport address, rxrpc_kernel_remote_addr() is
used.
(3) The port arg is removed from afs_alloc_addrlist() since it's always
overridden.
(4) afs_merge_fs_addr4() and afs_merge_fs_addr6() do peer lookup and may
now return an error that must be handled.
(5) afs_find_server() now takes a peer pointer to specify the address.
(6) afs_find_server(), afs_compare_fs_alists() and afs_merge_fs_addr[46]{}
now do peer pointer comparison rather than address comparison.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
2023-10-19 14:55:11 +03:00
ret = afs_merge_fs_addr4 ( net , alist , x [ 0 ] , port ) ;
if ( ret < 0 )
goto error ;
2018-10-20 02:57:57 +03:00
b + = 4 ;
break ;
case DNS_ADDRESS_IS_IPV6 :
if ( end - b < 16 ) {
_leave ( " = -EINVAL [short inet6] " ) ;
goto error ;
}
memcpy ( x , b , 16 ) ;
rxrpc, afs: Allow afs to pin rxrpc_peer objects
Change rxrpc's API such that:
(1) A new function, rxrpc_kernel_lookup_peer(), is provided to look up an
rxrpc_peer record for a remote address and a corresponding function,
rxrpc_kernel_put_peer(), is provided to dispose of it again.
(2) When setting up a call, the rxrpc_peer object used during a call is
now passed in rather than being set up by rxrpc_connect_call(). For
afs, this meenat passing it to rxrpc_kernel_begin_call() rather than
the full address (the service ID then has to be passed in as a
separate parameter).
(3) A new function, rxrpc_kernel_remote_addr(), is added so that afs can
get a pointer to the transport address for display purposed, and
another, rxrpc_kernel_remote_srx(), to gain a pointer to the full
rxrpc address.
(4) The function to retrieve the RTT from a call, rxrpc_kernel_get_srtt(),
is then altered to take a peer. This now returns the RTT or -1 if
there are insufficient samples.
(5) Rename rxrpc_kernel_get_peer() to rxrpc_kernel_call_get_peer().
(6) Provide a new function, rxrpc_kernel_get_peer(), to get a ref on a
peer the caller already has.
This allows the afs filesystem to pin the rxrpc_peer records that it is
using, allowing faster lookups and pointer comparisons rather than
comparing sockaddr_rxrpc contents. It also makes it easier to get hold of
the RTT. The following changes are made to afs:
(1) The addr_list struct's addrs[] elements now hold a peer struct pointer
and a service ID rather than a sockaddr_rxrpc.
(2) When displaying the transport address, rxrpc_kernel_remote_addr() is
used.
(3) The port arg is removed from afs_alloc_addrlist() since it's always
overridden.
(4) afs_merge_fs_addr4() and afs_merge_fs_addr6() do peer lookup and may
now return an error that must be handled.
(5) afs_find_server() now takes a peer pointer to specify the address.
(6) afs_find_server(), afs_compare_fs_alists() and afs_merge_fs_addr[46]{}
now do peer pointer comparison rather than address comparison.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
2023-10-19 14:55:11 +03:00
ret = afs_merge_fs_addr6 ( net , alist , x , port ) ;
if ( ret < 0 )
goto error ;
2018-10-20 02:57:57 +03:00
b + = 16 ;
break ;
default :
_leave ( " = -EADDRNOTAVAIL [unknown af %u] " ,
hdr . address_type ) ;
ret = - EADDRNOTAVAIL ;
goto error ;
}
}
/* Start with IPv6 if available. */
if ( alist - > nr_ipv4 < alist - > nr_addrs )
2018-10-20 02:57:59 +03:00
alist - > preferred = alist - > nr_ipv4 ;
2018-10-20 02:57:57 +03:00
* _b = b ;
return alist ;
error :
* _b = b ;
2023-10-19 15:59:03 +03:00
afs_put_addrlist ( alist , afs_alist_trace_put_parse_error ) ;
2018-10-20 02:57:57 +03:00
return ERR_PTR ( ret ) ;
}
/*
* Build a VL server list from a DNS queried server list .
*/
struct afs_vlserver_list * afs_extract_vlserver_list ( struct afs_cell * cell ,
const void * buffer ,
size_t buffer_size )
{
const struct dns_server_list_v1_header * hdr = buffer ;
struct dns_server_list_v1_server bs ;
struct afs_vlserver_list * vllist , * previous ;
struct afs_addr_list * addrs ;
struct afs_vlserver * server ;
const u8 * b = buffer , * end = buffer + buffer_size ;
int ret = - ENOMEM , nr_servers , i , j ;
_enter ( " " ) ;
/* Check that it's a server list, v1 */
if ( end - b < sizeof ( * hdr ) | |
hdr - > hdr . content ! = DNS_PAYLOAD_IS_SERVER_LIST | |
hdr - > hdr . version ! = 1 ) {
pr_notice ( " kAFS: Got DNS record [%u,%u] len %zu \n " ,
hdr - > hdr . content , hdr - > hdr . version , end - b ) ;
ret = - EDESTADDRREQ ;
goto dump ;
}
nr_servers = hdr - > nr_servers ;
vllist = afs_alloc_vlserver_list ( nr_servers ) ;
if ( ! vllist )
return ERR_PTR ( - ENOMEM ) ;
vllist - > source = ( hdr - > source < NR__dns_record_source ) ?
hdr - > source : NR__dns_record_source ;
vllist - > status = ( hdr - > status < NR__dns_lookup_status ) ?
hdr - > status : NR__dns_lookup_status ;
read_lock ( & cell - > vl_servers_lock ) ;
previous = afs_get_vlserverlist (
rcu_dereference_protected ( cell - > vl_servers ,
lockdep_is_held ( & cell - > vl_servers_lock ) ) ) ;
read_unlock ( & cell - > vl_servers_lock ) ;
b + = sizeof ( * hdr ) ;
while ( end - b > = sizeof ( bs ) ) {
bs . name_len = afs_extract_le16 ( & b ) ;
bs . priority = afs_extract_le16 ( & b ) ;
bs . weight = afs_extract_le16 ( & b ) ;
bs . port = afs_extract_le16 ( & b ) ;
bs . source = * b + + ;
bs . status = * b + + ;
bs . protocol = * b + + ;
bs . nr_addrs = * b + + ;
_debug ( " extract %u %u %u %u %u %u %*.*s " ,
bs . name_len , bs . priority , bs . weight ,
bs . port , bs . protocol , bs . nr_addrs ,
bs . name_len , bs . name_len , b ) ;
if ( end - b < bs . name_len )
break ;
ret = - EPROTONOSUPPORT ;
if ( bs . protocol = = DNS_SERVER_PROTOCOL_UNSPECIFIED ) {
bs . protocol = DNS_SERVER_PROTOCOL_UDP ;
} else if ( bs . protocol ! = DNS_SERVER_PROTOCOL_UDP ) {
_leave ( " = [proto %u] " , bs . protocol ) ;
goto error ;
}
if ( bs . port = = 0 )
bs . port = AFS_VL_PORT ;
if ( bs . source > NR__dns_record_source )
bs . source = NR__dns_record_source ;
if ( bs . status > NR__dns_lookup_status )
bs . status = NR__dns_lookup_status ;
2019-05-07 17:30:34 +03:00
/* See if we can update an old server record */
2018-10-20 02:57:57 +03:00
server = NULL ;
2019-05-07 17:30:34 +03:00
for ( i = 0 ; i < previous - > nr_servers ; i + + ) {
struct afs_vlserver * p = previous - > servers [ i ] . server ;
if ( p - > name_len = = bs . name_len & &
p - > port = = bs . port & &
strncasecmp ( b , p - > name , bs . name_len ) = = 0 ) {
server = afs_get_vlserver ( p ) ;
break ;
2018-10-20 02:57:57 +03:00
}
}
if ( ! server ) {
ret = - ENOMEM ;
server = afs_alloc_vlserver ( b , bs . name_len , bs . port ) ;
if ( ! server )
goto error ;
}
b + = bs . name_len ;
/* Extract the addresses - note that we can't skip this as we
* have to advance the payload pointer .
*/
rxrpc, afs: Allow afs to pin rxrpc_peer objects
Change rxrpc's API such that:
(1) A new function, rxrpc_kernel_lookup_peer(), is provided to look up an
rxrpc_peer record for a remote address and a corresponding function,
rxrpc_kernel_put_peer(), is provided to dispose of it again.
(2) When setting up a call, the rxrpc_peer object used during a call is
now passed in rather than being set up by rxrpc_connect_call(). For
afs, this meenat passing it to rxrpc_kernel_begin_call() rather than
the full address (the service ID then has to be passed in as a
separate parameter).
(3) A new function, rxrpc_kernel_remote_addr(), is added so that afs can
get a pointer to the transport address for display purposed, and
another, rxrpc_kernel_remote_srx(), to gain a pointer to the full
rxrpc address.
(4) The function to retrieve the RTT from a call, rxrpc_kernel_get_srtt(),
is then altered to take a peer. This now returns the RTT or -1 if
there are insufficient samples.
(5) Rename rxrpc_kernel_get_peer() to rxrpc_kernel_call_get_peer().
(6) Provide a new function, rxrpc_kernel_get_peer(), to get a ref on a
peer the caller already has.
This allows the afs filesystem to pin the rxrpc_peer records that it is
using, allowing faster lookups and pointer comparisons rather than
comparing sockaddr_rxrpc contents. It also makes it easier to get hold of
the RTT. The following changes are made to afs:
(1) The addr_list struct's addrs[] elements now hold a peer struct pointer
and a service ID rather than a sockaddr_rxrpc.
(2) When displaying the transport address, rxrpc_kernel_remote_addr() is
used.
(3) The port arg is removed from afs_alloc_addrlist() since it's always
overridden.
(4) afs_merge_fs_addr4() and afs_merge_fs_addr6() do peer lookup and may
now return an error that must be handled.
(5) afs_find_server() now takes a peer pointer to specify the address.
(6) afs_find_server(), afs_compare_fs_alists() and afs_merge_fs_addr[46]{}
now do peer pointer comparison rather than address comparison.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
2023-10-19 14:55:11 +03:00
addrs = afs_extract_vl_addrs ( cell - > net , & b , end , bs . nr_addrs , bs . port ) ;
2018-10-20 02:57:57 +03:00
if ( IS_ERR ( addrs ) ) {
ret = PTR_ERR ( addrs ) ;
goto error_2 ;
}
if ( vllist - > nr_servers > = nr_servers ) {
_debug ( " skip %u >= %u " , vllist - > nr_servers , nr_servers ) ;
2023-10-19 15:59:03 +03:00
afs_put_addrlist ( addrs , afs_alist_trace_put_parse_empty ) ;
2018-10-20 02:57:57 +03:00
afs_put_vlserver ( cell - > net , server ) ;
continue ;
}
addrs - > source = bs . source ;
addrs - > status = bs . status ;
if ( addrs - > nr_addrs = = 0 ) {
2023-10-19 15:59:03 +03:00
afs_put_addrlist ( addrs , afs_alist_trace_put_parse_empty ) ;
2018-10-20 02:57:57 +03:00
if ( ! rcu_access_pointer ( server - > addresses ) ) {
afs_put_vlserver ( cell - > net , server ) ;
continue ;
}
} else {
struct afs_addr_list * old = addrs ;
write_lock ( & server - > lock ) ;
2019-09-24 01:28:28 +03:00
old = rcu_replace_pointer ( server - > addresses , old ,
lockdep_is_held ( & server - > lock ) ) ;
2018-10-20 02:57:57 +03:00
write_unlock ( & server - > lock ) ;
2023-10-19 15:59:03 +03:00
afs_put_addrlist ( old , afs_alist_trace_put_vlserver_old ) ;
2018-10-20 02:57:57 +03:00
}
/* TODO: Might want to check for duplicates */
/* Insertion-sort by priority and weight */
for ( j = 0 ; j < vllist - > nr_servers ; j + + ) {
if ( bs . priority < vllist - > servers [ j ] . priority )
break ; /* Lower preferable */
if ( bs . priority = = vllist - > servers [ j ] . priority & &
bs . weight > vllist - > servers [ j ] . weight )
break ; /* Higher preferable */
}
if ( j < vllist - > nr_servers ) {
memmove ( vllist - > servers + j + 1 ,
vllist - > servers + j ,
( vllist - > nr_servers - j ) * sizeof ( struct afs_vlserver_entry ) ) ;
}
2018-10-20 02:57:59 +03:00
clear_bit ( AFS_VLSERVER_FL_PROBED , & server - > flags ) ;
2018-10-20 02:57:57 +03:00
vllist - > servers [ j ] . priority = bs . priority ;
vllist - > servers [ j ] . weight = bs . weight ;
vllist - > servers [ j ] . server = server ;
vllist - > nr_servers + + ;
}
if ( b ! = end ) {
_debug ( " parse error %zd " , b - end ) ;
goto error ;
}
afs_put_vlserverlist ( cell - > net , previous ) ;
_leave ( " = ok [%u] " , vllist - > nr_servers ) ;
return vllist ;
error_2 :
afs_put_vlserver ( cell - > net , server ) ;
error :
afs_put_vlserverlist ( cell - > net , vllist ) ;
afs_put_vlserverlist ( cell - > net , previous ) ;
dump :
if ( ret ! = - ENOMEM ) {
printk ( KERN_DEBUG " DNS: at %zu \n " , ( const void * ) b - buffer ) ;
print_hex_dump_bytes ( " DNS: " , DUMP_PREFIX_NONE , buffer , buffer_size ) ;
}
return ERR_PTR ( ret ) ;
}