2014-07-16 00:26:02 +02:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright 2014 Lennart Poettering
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
2014-07-16 20:15:47 +02:00
# include <netinet/tcp.h>
2014-07-29 14:24:02 +02:00
# include "missing.h"
2014-07-16 00:26:02 +02:00
# include "strv.h"
2014-07-16 20:15:47 +02:00
# include "socket-util.h"
2014-07-18 16:15:12 +02:00
# include "af-list.h"
2014-07-16 00:26:02 +02:00
# include "resolved-dns-domain.h"
# include "resolved-dns-scope.h"
# define SEND_TIMEOUT_USEC (2*USEC_PER_SEC)
2014-07-18 16:09:30 +02:00
int dns_scope_new ( Manager * m , DnsScope * * ret , Link * l , DnsProtocol protocol , int family ) {
2014-07-16 00:26:02 +02:00
DnsScope * s ;
assert ( m ) ;
assert ( ret ) ;
s = new0 ( DnsScope , 1 ) ;
if ( ! s )
return - ENOMEM ;
s - > manager = m ;
2014-07-18 12:34:02 +02:00
s - > link = l ;
s - > protocol = protocol ;
s - > family = family ;
2014-07-16 00:26:02 +02:00
LIST_PREPEND ( scopes , m - > dns_scopes , s ) ;
2014-07-18 12:34:02 +02:00
dns_scope_llmnr_membership ( s , true ) ;
2014-07-18 16:15:12 +02:00
log_debug ( " New scope on link %s, protocol %s, family %s " , l ? l - > name : " * " , dns_protocol_to_string ( protocol ) , family = = AF_UNSPEC ? " * " : af_to_name ( family ) ) ;
2014-07-18 12:34:02 +02:00
2014-07-16 00:26:02 +02:00
* ret = s ;
return 0 ;
}
DnsScope * dns_scope_free ( DnsScope * s ) {
2014-07-22 21:48:41 +02:00
DnsQueryTransaction * t ;
2014-07-16 00:26:02 +02:00
if ( ! s )
return NULL ;
2014-07-18 16:15:12 +02:00
log_debug ( " Removing scope on link %s, protocol %s, family %s " , s - > link ? s - > link - > name : " * " , dns_protocol_to_string ( s - > protocol ) , s - > family = = AF_UNSPEC ? " * " : af_to_name ( s - > family ) ) ;
2014-07-18 12:34:02 +02:00
dns_scope_llmnr_membership ( s , false ) ;
2014-07-22 21:48:41 +02:00
while ( ( t = s - > transactions ) ) {
/* Abort the transaction, but make sure it is not
* freed while we still look at it */
2014-07-16 00:26:02 +02:00
2014-07-22 21:48:41 +02:00
t - > block_gc + + ;
dns_query_transaction_complete ( t , DNS_QUERY_ABORTED ) ;
t - > block_gc - - ;
2014-07-16 00:26:02 +02:00
2014-07-22 21:48:41 +02:00
dns_query_transaction_free ( t ) ;
2014-07-16 00:26:02 +02:00
}
2014-07-17 19:38:37 +02:00
dns_cache_flush ( & s - > cache ) ;
2014-07-29 14:24:02 +02:00
dns_zone_flush ( & s - > zone ) ;
2014-07-17 19:38:37 +02:00
2014-07-16 00:26:02 +02:00
LIST_REMOVE ( scopes , s - > manager - > dns_scopes , s ) ;
strv_free ( s - > domains ) ;
free ( s ) ;
return NULL ;
}
DnsServer * dns_scope_get_server ( DnsScope * s ) {
assert ( s ) ;
2014-07-18 12:34:02 +02:00
if ( s - > protocol ! = DNS_PROTOCOL_DNS )
return NULL ;
2014-07-16 00:26:02 +02:00
if ( s - > link )
return link_get_dns_server ( s - > link ) ;
else
return manager_get_dns_server ( s - > manager ) ;
}
void dns_scope_next_dns_server ( DnsScope * s ) {
assert ( s ) ;
2014-07-18 12:34:02 +02:00
if ( s - > protocol ! = DNS_PROTOCOL_DNS )
return ;
2014-07-16 00:26:02 +02:00
if ( s - > link )
link_next_dns_server ( s - > link ) ;
else
manager_next_dns_server ( s - > manager ) ;
}
int dns_scope_send ( DnsScope * s , DnsPacket * p ) {
2014-07-18 12:34:02 +02:00
union in_addr_union addr ;
int ifindex = 0 , r ;
2014-07-18 16:09:30 +02:00
int family ;
2014-07-18 12:34:02 +02:00
uint16_t port ;
uint32_t mtu ;
int fd ;
2014-07-16 00:26:02 +02:00
assert ( s ) ;
assert ( p ) ;
2014-07-18 12:34:02 +02:00
assert ( p - > protocol = = s - > protocol ) ;
2014-07-16 20:15:47 +02:00
if ( s - > link ) {
2014-07-18 12:34:02 +02:00
mtu = s - > link - > mtu ;
2014-07-16 00:26:02 +02:00
ifindex = s - > link - > ifindex ;
2014-07-18 12:34:02 +02:00
} else
2014-07-17 01:13:22 +02:00
mtu = manager_find_mtu ( s - > manager ) ;
2014-07-16 00:26:02 +02:00
2014-07-18 12:34:02 +02:00
if ( s - > protocol = = DNS_PROTOCOL_DNS ) {
DnsServer * srv ;
2014-07-17 01:13:22 +02:00
2014-07-29 14:24:02 +02:00
if ( DNS_PACKET_QDCOUNT ( p ) > 1 )
return - ENOTSUP ;
2014-07-18 12:34:02 +02:00
srv = dns_scope_get_server ( s ) ;
if ( ! srv )
return - ESRCH ;
family = srv - > family ;
addr = srv - > address ;
port = 53 ;
if ( p - > size > DNS_PACKET_UNICAST_SIZE_MAX )
return - EMSGSIZE ;
if ( p - > size > mtu )
return - EMSGSIZE ;
if ( family = = AF_INET )
fd = manager_dns_ipv4_fd ( s - > manager ) ;
else if ( family = = AF_INET6 )
fd = manager_dns_ipv6_fd ( s - > manager ) ;
else
return - EAFNOSUPPORT ;
if ( fd < 0 )
return fd ;
} else if ( s - > protocol = = DNS_PROTOCOL_LLMNR ) {
if ( DNS_PACKET_QDCOUNT ( p ) > 1 )
return - ENOTSUP ;
family = s - > family ;
port = 5355 ;
if ( family = = AF_INET ) {
addr . in = LLMNR_MULTICAST_IPV4_ADDRESS ;
fd = manager_llmnr_ipv4_udp_fd ( s - > manager ) ;
} else if ( family = = AF_INET6 ) {
addr . in6 = LLMNR_MULTICAST_IPV6_ADDRESS ;
fd = manager_llmnr_ipv6_udp_fd ( s - > manager ) ;
} else
return - EAFNOSUPPORT ;
if ( fd < 0 )
return fd ;
} else
2014-07-16 00:26:02 +02:00
return - EAFNOSUPPORT ;
2014-07-18 12:34:02 +02:00
r = manager_send ( s - > manager , fd , ifindex , family , & addr , port , p ) ;
2014-07-16 00:26:02 +02:00
if ( r < 0 )
return r ;
return 1 ;
}
2014-07-29 14:24:02 +02:00
int dns_scope_tcp_socket ( DnsScope * s , int family , const union in_addr_union * address , uint16_t port ) {
2014-07-16 20:15:47 +02:00
_cleanup_close_ int fd = - 1 ;
union sockaddr_union sa = { } ;
socklen_t salen ;
2014-07-29 14:24:02 +02:00
static const int one = 1 ;
int ret , r ;
2014-07-16 20:15:47 +02:00
assert ( s ) ;
2014-07-29 14:24:02 +02:00
assert ( ( family = = AF_UNSPEC ) = = ! address ) ;
2014-07-16 20:15:47 +02:00
2014-07-29 14:24:02 +02:00
if ( family = = AF_UNSPEC ) {
DnsServer * srv ;
srv = dns_scope_get_server ( s ) ;
if ( ! srv )
return - ESRCH ;
sa . sa . sa_family = srv - > family ;
if ( srv - > family = = AF_INET ) {
sa . in . sin_port = htobe16 ( port ) ;
sa . in . sin_addr = srv - > address . in ;
salen = sizeof ( sa . in ) ;
} else if ( srv - > family = = AF_INET6 ) {
sa . in6 . sin6_port = htobe16 ( port ) ;
sa . in6 . sin6_addr = srv - > address . in6 ;
sa . in6 . sin6_scope_id = s - > link ? s - > link - > ifindex : 0 ;
salen = sizeof ( sa . in6 ) ;
} else
return - EAFNOSUPPORT ;
} else {
sa . sa . sa_family = family ;
if ( family = = AF_INET ) {
sa . in . sin_port = htobe16 ( port ) ;
sa . in . sin_addr = address - > in ;
salen = sizeof ( sa . in ) ;
} else if ( family = = AF_INET6 ) {
sa . in6 . sin6_port = htobe16 ( port ) ;
sa . in6 . sin6_addr = address - > in6 ;
sa . in6 . sin6_scope_id = s - > link ? s - > link - > ifindex : 0 ;
salen = sizeof ( sa . in6 ) ;
} else
return - EAFNOSUPPORT ;
}
2014-07-16 20:15:47 +02:00
2014-07-29 14:24:02 +02:00
fd = socket ( sa . sa . sa_family , SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK , 0 ) ;
2014-07-16 20:15:47 +02:00
if ( fd < 0 )
return - errno ;
2014-07-29 14:24:02 +02:00
r = setsockopt ( fd , IPPROTO_TCP , TCP_NODELAY , & one , sizeof ( one ) ) ;
if ( r < 0 )
return - errno ;
if ( s - > link ) {
uint32_t ifindex = htobe32 ( s - > link - > ifindex ) ;
if ( sa . sa . sa_family = = AF_INET ) {
r = setsockopt ( fd , IPPROTO_IP , IP_UNICAST_IF , & ifindex , sizeof ( ifindex ) ) ;
if ( r < 0 )
return - errno ;
} else if ( sa . sa . sa_family = = AF_INET6 ) {
r = setsockopt ( fd , IPPROTO_IPV6 , IPV6_UNICAST_IF , & ifindex , sizeof ( ifindex ) ) ;
if ( r < 0 )
return - errno ;
}
}
if ( s - > protocol = = DNS_PROTOCOL_LLMNR ) {
/* RFC 4795, section 2.5 suggests the TTL to be set to 1 */
if ( sa . sa . sa_family = = AF_INET ) {
r = setsockopt ( fd , IPPROTO_IP , IP_TTL , & one , sizeof ( one ) ) ;
if ( r < 0 )
return - errno ;
} else if ( sa . sa . sa_family = = AF_INET6 ) {
r = setsockopt ( fd , IPPROTO_IPV6 , IPV6_UNICAST_HOPS , & one , sizeof ( one ) ) ;
if ( r < 0 )
return - errno ;
}
}
2014-07-16 20:15:47 +02:00
r = connect ( fd , & sa . sa , salen ) ;
if ( r < 0 & & errno ! = EINPROGRESS )
return - errno ;
ret = fd ;
fd = - 1 ;
2014-07-29 14:24:02 +02:00
2014-07-16 20:15:47 +02:00
return ret ;
}
2014-07-18 12:34:02 +02:00
DnsScopeMatch dns_scope_good_domain ( DnsScope * s , const char * domain ) {
2014-07-16 00:26:02 +02:00
char * * i ;
assert ( s ) ;
assert ( domain ) ;
STRV_FOREACH ( i , s - > domains )
2014-07-22 21:48:41 +02:00
if ( dns_name_endswith ( domain , * i ) > 0 )
2014-07-16 00:26:02 +02:00
return DNS_SCOPE_YES ;
2014-07-22 21:48:41 +02:00
if ( dns_name_root ( domain ) ! = 0 )
2014-07-16 00:26:02 +02:00
return DNS_SCOPE_NO ;
2014-07-17 01:14:07 +02:00
if ( is_localhost ( domain ) )
return DNS_SCOPE_NO ;
2014-07-18 12:34:02 +02:00
if ( s - > protocol = = DNS_PROTOCOL_DNS ) {
2014-07-22 21:48:41 +02:00
if ( dns_name_endswith ( domain , " 254.169.in-addr.arpa " ) = = 0 & &
dns_name_endswith ( domain , " 0.8.e.f.ip6.arpa " ) = = 0 & &
dns_name_single_label ( domain ) = = 0 )
return DNS_SCOPE_MAYBE ;
2014-07-18 12:34:02 +02:00
2014-07-22 21:48:41 +02:00
return DNS_SCOPE_NO ;
2014-07-18 12:34:02 +02:00
}
if ( s - > protocol = = DNS_PROTOCOL_MDNS ) {
2014-07-22 21:48:41 +02:00
if ( dns_name_endswith ( domain , " 254.169.in-addr.arpa " ) > 0 | |
dns_name_endswith ( domain , " 0.8.e.f.ip6.arpa " ) > 0 | |
( dns_name_endswith ( domain , " local " ) > 0 & & dns_name_equal ( domain , " local " ) = = 0 ) )
2014-07-16 00:26:02 +02:00
return DNS_SCOPE_MAYBE ;
return DNS_SCOPE_NO ;
}
2014-07-18 12:34:02 +02:00
if ( s - > protocol = = DNS_PROTOCOL_LLMNR ) {
2014-07-29 19:49:45 +02:00
if ( dns_name_endswith ( domain , " in-addr.arpa " ) > 0 | |
dns_name_endswith ( domain , " ip6.arpa " ) > 0 | |
2014-07-22 21:48:41 +02:00
dns_name_single_label ( domain ) > 0 )
2014-07-18 12:34:02 +02:00
return DNS_SCOPE_MAYBE ;
2014-07-16 00:26:02 +02:00
2014-07-18 12:34:02 +02:00
return DNS_SCOPE_NO ;
2014-07-16 00:26:02 +02:00
}
2014-07-18 12:34:02 +02:00
assert_not_reached ( " Unknown scope protocol " ) ;
}
int dns_scope_good_key ( DnsScope * s , DnsResourceKey * key ) {
assert ( s ) ;
assert ( key ) ;
if ( s - > protocol = = DNS_PROTOCOL_DNS )
return true ;
/* On mDNS and LLMNR, send A and AAAA queries only on the
* respective scopes */
if ( s - > family = = AF_INET & & key - > class = = DNS_CLASS_IN & & key - > type = = DNS_TYPE_AAAA )
return false ;
if ( s - > family = = AF_INET6 & & key - > class = = DNS_CLASS_IN & & key - > type = = DNS_TYPE_A )
return false ;
return true ;
}
int dns_scope_llmnr_membership ( DnsScope * s , bool b ) {
int fd ;
if ( s - > family = = AF_INET ) {
struct ip_mreqn mreqn = {
. imr_multiaddr = LLMNR_MULTICAST_IPV4_ADDRESS ,
. imr_ifindex = s - > link - > ifindex ,
} ;
fd = manager_llmnr_ipv4_udp_fd ( s - > manager ) ;
if ( fd < 0 )
return fd ;
if ( setsockopt ( fd , IPPROTO_IP , b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP , & mreqn , sizeof ( mreqn ) ) < 0 )
return - errno ;
} else if ( s - > family = = AF_INET6 ) {
struct ipv6_mreq mreq = {
. ipv6mr_multiaddr = LLMNR_MULTICAST_IPV6_ADDRESS ,
. ipv6mr_interface = s - > link - > ifindex ,
} ;
fd = manager_llmnr_ipv6_udp_fd ( s - > manager ) ;
if ( fd < 0 )
return fd ;
if ( setsockopt ( fd , IPPROTO_IPV6 , b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP , & mreq , sizeof ( mreq ) ) < 0 )
return - errno ;
} else
return - EAFNOSUPPORT ;
return 0 ;
2014-07-16 00:26:02 +02:00
}
2014-07-29 14:24:02 +02:00
int dns_scope_good_dns_server ( DnsScope * s , int family , const union in_addr_union * address ) {
assert ( s ) ;
assert ( address ) ;
if ( s - > protocol ! = DNS_PROTOCOL_DNS )
return 1 ;
if ( s - > link )
return ! ! link_find_dns_server ( s - > link , family , address ) ;
else
return ! ! manager_find_dns_server ( s - > manager , family , address ) ;
}
static int dns_scope_make_reply_packet ( DnsScope * s , uint16_t id , int rcode , DnsQuestion * q , DnsAnswer * a , DnsPacket * * ret ) {
_cleanup_ ( dns_packet_unrefp ) DnsPacket * p = NULL ;
unsigned i ;
int r ;
assert ( s ) ;
if ( q - > n_keys < = 0 & & a - > n_rrs < = 0 )
return - EINVAL ;
r = dns_packet_new ( & p , s - > protocol , 0 ) ;
if ( r < 0 )
return r ;
DNS_PACKET_HEADER ( p ) - > id = id ;
DNS_PACKET_HEADER ( p ) - > flags = htobe16 ( DNS_PACKET_MAKE_FLAGS ( 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , rcode ) ) ;
if ( q ) {
for ( i = 0 ; i < q - > n_keys ; i + + ) {
r = dns_packet_append_key ( p , q - > keys [ i ] , NULL ) ;
if ( r < 0 )
return r ;
}
DNS_PACKET_HEADER ( p ) - > qdcount = htobe16 ( q - > n_keys ) ;
}
if ( a ) {
for ( i = 0 ; i < a - > n_rrs ; i + + ) {
r = dns_packet_append_rr ( p , a - > rrs [ i ] , NULL ) ;
if ( r < 0 )
return r ;
}
DNS_PACKET_HEADER ( p ) - > ancount = htobe16 ( a - > n_rrs ) ;
}
* ret = p ;
p = NULL ;
return 0 ;
}
void dns_scope_process_query ( DnsScope * s , DnsStream * stream , DnsPacket * p ) {
_cleanup_ ( dns_packet_unrefp ) DnsPacket * reply = NULL ;
_cleanup_ ( dns_answer_unrefp ) DnsAnswer * answer = NULL ;
int r , fd ;
assert ( s ) ;
assert ( p ) ;
if ( p - > protocol ! = DNS_PROTOCOL_LLMNR )
return ;
r = dns_packet_extract ( p ) ;
if ( r < 0 ) {
log_debug ( " Failed to extract resources from incoming packet: %s " , strerror ( - r ) ) ;
return ;
}
r = dns_zone_lookup ( & s - > zone , p - > question , & answer ) ;
if ( r < 0 ) {
log_debug ( " Failed to lookup key: %s " , strerror ( - r ) ) ;
return ;
}
if ( r = = 0 )
return ;
r = dns_scope_make_reply_packet ( s , DNS_PACKET_ID ( p ) , DNS_RCODE_SUCCESS , p - > question , answer , & reply ) ;
if ( r < 0 ) {
log_debug ( " Failed to build reply packet: %s " , strerror ( - r ) ) ;
return ;
}
if ( stream )
r = dns_stream_write_packet ( stream , reply ) ;
else {
if ( p - > family = = AF_INET )
fd = manager_llmnr_ipv4_udp_fd ( s - > manager ) ;
else if ( p - > family = = AF_INET6 )
fd = manager_llmnr_ipv6_udp_fd ( s - > manager ) ;
else {
log_debug ( " Unknown protocol " ) ;
return ;
}
if ( fd < 0 ) {
log_debug ( " Failed to get reply socket: %s " , strerror ( - fd ) ) ;
return ;
}
r = manager_send ( s - > manager , fd , p - > ifindex , p - > family , & p - > sender , p - > sender_port , reply ) ;
}
if ( r < 0 ) {
log_debug ( " Failed to send reply packet: %s " , strerror ( - r ) ) ;
return ;
}
}