diff options
Diffstat (limited to 'src/core')
77 files changed, 4482 insertions, 3880 deletions
diff --git a/src/core/bpf/clat.bpf.c b/src/core/bpf/clat.bpf.c new file mode 100644 index 00000000..97d74a61 --- /dev/null +++ b/src/core/bpf/clat.bpf.c @@ -0,0 +1,1203 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2021 Toke Høiland-Jørgensen <toke@toke.dk> */ +/* Copyright 2025 Mary Strodl <mstrodl@csh.rit.edu> */ +/* Copyright 2026 Beniamino Galvani <bgalvani@redhat.com> */ + +/** + * This is an implementation of a CLAT in eBPF. BPF is a different environment + * than the rest of NetworkManager, and we don't have access to most of the + * C standard library, so some things might look a little different from what + * you're used to. + * + * Check out src/core/bpf/meson.build to see how this gets built. + **/ + +#include <linux/bpf.h> +#include <linux/icmp.h> +#include <linux/icmpv6.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <linux/pkt_cls.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/if_ether.h> +#include <stdbool.h> + +#include <bpf/bpf_endian.h> +#include <bpf/bpf_helpers.h> + +#include "clat.h" + +char _license[] SEC("license") = "GPL"; + +struct clat_config config; +struct clat_stats stats; + +#ifdef DEBUG +/* Note: when enabling debugging, you also need to add CAP_PERFMON + * to the CapabilityBoundingSet of the NM systemd unit. The messages + * will be printed to /sys/kernel/debug/tracing/trace_pipe */ +#define DBG(fmt, ...) \ + ({ \ + char ____fmt[] = "clat: " fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \ + }) +#else +#define DBG(fmt, ...) +#endif + +/* Macros to read the sk_buff data* pointers, preventing the compiler + * from generating a 32-bit register spill. */ +#define SKB_ACCESS_MEMBER_32(_skb, member) \ + ({ \ + void *ptr; \ + \ + asm volatile("%0 = *(u32 *)(%1 + %2)" \ + : "=r"(ptr) \ + : "r"(_skb), "i"(offsetof(struct __sk_buff, member))); \ + \ + ptr; \ + }) + +#define SKB_DATA(_skb) SKB_ACCESS_MEMBER_32(_skb, data) +#define SKB_DATA_END(_skb) SKB_ACCESS_MEMBER_32(_skb, data_end) + +struct icmpv6_pseudo { + struct in6_addr saddr; + struct in6_addr daddr; + __u32 len; + __u8 padding[3]; + __u8 nh; +} __attribute__((packed)); + +struct ip6_frag { + __u8 nexthdr; + __u8 reserved; + __u16 offset; + __u32 identification; +} __attribute__((packed)); + +#define L2_H_LEN(has_eth) (has_eth ? sizeof(struct ethhdr) : 0) +#define IP_H_LEN (sizeof(struct iphdr)) +#define IP6_H_LEN (sizeof(struct ipv6hdr)) +#define IP6_FRAG_H_LEN (sizeof(struct ip6_frag)) +#define ICMP_H_LEN (sizeof(struct icmphdr)) +#define ICMP6_H_LEN (sizeof(struct icmp6hdr)) + +#define ensure_header(header, skb, data, data_end, offset) \ + _ensure_header((void **) header, (skb), (data), (data_end), sizeof(**(header)), (offset)) + +/* + * Verifies that the header at offset @offset and with size @size can + * be accessed, and assigns the pointer to @header. In case the data + * is not available, the function tries to pull it. Note that all packet + * pointers must be refreshed after calling this function. + */ +static __always_inline bool +_ensure_header(void **header, + struct __sk_buff *skb, + void **data, + void **data_end, + unsigned size, + unsigned offset) +{ + if (*data + offset + size > *data_end) { + bpf_skb_pull_data(skb, offset + size); + *data = SKB_DATA(skb); + *data_end = SKB_DATA_END(skb); + } + + if (*data + offset + size > *data_end) + return false; + + *header = *data + offset; + return true; +} + +/* This function must be declared as inline because the BPF calling + * convention only supports up to 5 function arguments. */ +static __always_inline void +update_l4_checksum(struct __sk_buff *skb, + struct ipv6hdr *ip6h, + struct iphdr *iph, + bool has_eth, + bool v4to6, + bool is_inner, + bool is_v6_fragment, + __u32 *csum_diff) +{ + int flags = BPF_F_PSEUDO_HDR; + __u16 offset; + __u32 csum; + int ip_type; + + if (v4to6) { + void *from_ptr = &iph->saddr; + void *to_ptr = &ip6h->saddr; + + csum = bpf_csum_diff(from_ptr, 2 * sizeof(__u32), to_ptr, 2 * sizeof(struct in6_addr), 0); + offset = L2_H_LEN(has_eth) + IP_H_LEN; + ip_type = ip6h->nexthdr; + } else { + void *from_ptr = &ip6h->saddr; + void *to_ptr = &iph->saddr; + + csum = bpf_csum_diff(from_ptr, 2 * sizeof(struct in6_addr), to_ptr, 2 * sizeof(__u32), 0); + offset = L2_H_LEN(has_eth) + IP6_H_LEN; + ip_type = iph->protocol; + + if (is_inner) { + offset = offset + ICMP6_H_LEN + IP6_H_LEN; + } + } + + if (is_v6_fragment) { + offset += IP6_FRAG_H_LEN; + } + + switch (ip_type) { + case IPPROTO_TCP: + offset += offsetof(struct tcphdr, check); + break; + case IPPROTO_UDP: + offset += offsetof(struct udphdr, check); + flags |= BPF_F_MARK_MANGLED_0; + break; + default: + return; + } + + bpf_l4_csum_replace(skb, offset, 0, csum, flags); + + if (csum_diff) { + *csum_diff = bpf_csum_diff((__be32 *) &csum, sizeof(csum), 0, 0, *csum_diff); + } +} + +static __always_inline void +update_icmp_checksum(struct __sk_buff *skb, + const struct ipv6hdr *ip6h, + void *icmp_before, + void *icmp_after, + bool has_eth, + bool v4to6, + bool is_inner, + __u32 seed) +{ + struct icmpv6_pseudo ph = {.nh = IPPROTO_ICMPV6, .len = ip6h->payload_len}; + __u16 h_before; + __u16 h_after; + __u16 offset; + __u32 csum; + __u32 u_before; + __u32 u_after; + + __builtin_memcpy(&ph.saddr, &ip6h->saddr, sizeof(struct in6_addr)); + __builtin_memcpy(&ph.daddr, &ip6h->daddr, sizeof(struct in6_addr)); + + /* Do checksum update in two passes: first compute the incremental + * checksum update of the ICMPv6 pseudo header, update the checksum + * using bpf_l4_csum_replace(), and then do a separate update for the + * ICMP type and code (which is two consecutive bytes, so cast them to + * u16). The bpf_csum_diff() helper can be used to compute the + * incremental update of the full block, whereas the + * bpf_l4_csum_replace() helper can do the two-byte diff and update by + * itself. + */ + csum = bpf_csum_diff((__be32 *) &ph, + v4to6 ? 0 : sizeof(ph), + (__be32 *) &ph, + v4to6 ? sizeof(ph) : 0, + seed); + + if (v4to6) { + offset = L2_H_LEN(has_eth) + IP_H_LEN + 2; + } else { + offset = L2_H_LEN(has_eth) + IP6_H_LEN + 2; + if (is_inner) + offset += ICMP6_H_LEN + IP6_H_LEN; + } + + /* first two bytes of ICMP header, type and code */ + h_before = *(__u16 *) icmp_before; + h_after = *(__u16 *) icmp_after; + + /* last four bytes of ICMP header, the data union */ + u_before = *(__u32 *) (icmp_before + 4); + u_after = *(__u32 *) (icmp_after + 4); + + bpf_l4_csum_replace(skb, offset, 0, csum, BPF_F_PSEUDO_HDR); + bpf_l4_csum_replace(skb, offset, h_before, h_after, 2); + + if (u_before != u_after) + bpf_l4_csum_replace(skb, offset, u_before, u_after, 4); +} + +static __always_inline int +rewrite_icmp(struct __sk_buff *skb, const struct ipv6hdr *ip6h, bool has_eth) +{ + void *data_end = SKB_DATA_END(skb); + void *data = SKB_DATA(skb); + struct icmphdr icmp_buf; /* copy of the old ICMPv4 header */ + struct icmp6hdr icmp6_buf; /* buffer for the new ICMPv6 header */ + struct icmphdr *icmp; + struct icmp6hdr *icmp6; + __u32 mtu; + + if (!ensure_header(&icmp, skb, &data, &data_end, L2_H_LEN(has_eth) + IP_H_LEN)) + return -1; + + icmp_buf = *icmp; + icmp6 = (void *) icmp; + icmp6_buf = *icmp6; + + /* These translations are defined in RFC6145 section 4.2 */ + switch (icmp->type) { + case ICMP_ECHO: + icmp6_buf.icmp6_type = ICMPV6_ECHO_REQUEST; + break; + case ICMP_ECHOREPLY: + icmp6_buf.icmp6_type = ICMPV6_ECHO_REPLY; + break; + case ICMP_DEST_UNREACH: + icmp6_buf.icmp6_type = ICMPV6_DEST_UNREACH; + switch (icmp->code) { + case ICMP_NET_UNREACH: + case ICMP_HOST_UNREACH: + case ICMP_SR_FAILED: + case ICMP_NET_UNKNOWN: + case ICMP_HOST_UNKNOWN: + case ICMP_HOST_ISOLATED: + case ICMP_NET_UNR_TOS: + case ICMP_HOST_UNR_TOS: + icmp6_buf.icmp6_code = ICMPV6_NOROUTE; + break; + case ICMP_PROT_UNREACH: + icmp6_buf.icmp6_type = ICMPV6_PARAMPROB; + icmp6_buf.icmp6_code = ICMPV6_UNK_NEXTHDR; + icmp6_buf.icmp6_pointer = bpf_htonl(offsetof(struct ipv6hdr, nexthdr)); + break; + case ICMP_PORT_UNREACH: + icmp6_buf.icmp6_code = ICMPV6_PORT_UNREACH; + break; + case ICMP_FRAG_NEEDED: + icmp6_buf.icmp6_type = ICMPV6_PKT_TOOBIG; + icmp6_buf.icmp6_code = 0; + mtu = bpf_ntohs(icmp->un.frag.mtu) + 20; + /* RFC6145 section 6, "second approach" - should not be + * necessary, but might as well do this + */ + if (mtu < 1280) + mtu = 1280; + icmp6_buf.icmp6_mtu = bpf_htonl(mtu); + break; + case ICMP_NET_ANO: + case ICMP_HOST_ANO: + case ICMP_PKT_FILTERED: + case ICMP_PREC_CUTOFF: + icmp6_buf.icmp6_code = ICMPV6_ADM_PROHIBITED; + break; + default: + return -1; + } + break; + case ICMP_PARAMETERPROB: + if (icmp->code == 1) + return -1; + icmp6_buf.icmp6_type = ICMPV6_PARAMPROB; + icmp6_buf.icmp6_code = ICMPV6_HDR_FIELD; + /* The pointer field not defined in the Linux header. This + * translation is from Figure 3 of RFC6145. + */ + switch (icmp->un.reserved[0]) { + case 0: /* version/IHL */ + icmp6_buf.icmp6_pointer = 0; + break; + case 1: /* Type of Service */ + icmp6_buf.icmp6_pointer = bpf_htonl(1); + break; + case 2: /* Total length */ + case 3: + icmp6_buf.icmp6_pointer = bpf_htonl(4); + break; + case 8: /* Time to Live */ + icmp6_buf.icmp6_pointer = bpf_htonl(7); + break; + case 9: /* Protocol */ + icmp6_buf.icmp6_pointer = bpf_htonl(6); + break; + case 12: /* Source address */ + case 13: + case 14: + case 15: + icmp6_buf.icmp6_pointer = bpf_htonl(8); + break; + case 16: /* Destination address */ + case 17: + case 18: + case 19: + icmp6_buf.icmp6_pointer = bpf_htonl(24); + break; + default: + return -1; + } + break; + default: + return -1; + } + + *icmp6 = icmp6_buf; + update_icmp_checksum(skb, ip6h, &icmp_buf, icmp6, has_eth, true, false, 0); + + /* FIXME: also need to rewrite IP header embedded in ICMP error */ + + return 0; +} + +/* + * Convert an IPv4 address to the corresponding "IPv4-Embedded IPv6 Address" + * according to RFC 6052 2.2. + * + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------| + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |32| prefix |v4(32) | u | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |40| prefix |v4(24) | u |(8)| suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |48| prefix |v4(16) | u | (16) | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |56| prefix |(8)| u | v4(24) | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |64| prefix | u | v4(32) | suffix | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |96| prefix | v4(32) | + * +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + */ +static __always_inline bool +v4addr_to_v6(__be32 addr4, struct in6_addr *addr6, const struct in6_addr *pref64, int pref64_len) +{ + union { + __be32 a32; + __u8 a8[4]; + } u; + + u.a32 = addr4; + + addr6->s6_addr32[0] = 0; + addr6->s6_addr32[1] = 0; + addr6->s6_addr32[2] = 0; + addr6->s6_addr32[3] = 0; + + switch (pref64_len) { + case 96: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = pref64->s6_addr32[1]; + addr6->s6_addr32[2] = pref64->s6_addr32[2]; + addr6->s6_addr32[3] = addr4; + break; + case 64: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = pref64->s6_addr32[1]; + addr6->s6_addr[9] = u.a8[0]; + addr6->s6_addr[10] = u.a8[1]; + addr6->s6_addr[11] = u.a8[2]; + addr6->s6_addr[12] = u.a8[3]; + break; + case 56: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = pref64->s6_addr32[1]; + addr6->s6_addr[7] = u.a8[0]; + addr6->s6_addr[9] = u.a8[1]; + addr6->s6_addr[10] = u.a8[2]; + addr6->s6_addr[11] = u.a8[3]; + break; + case 48: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr16[2] = pref64->s6_addr16[2]; + addr6->s6_addr[6] = u.a8[0]; + addr6->s6_addr[7] = u.a8[1]; + addr6->s6_addr[9] = u.a8[2]; + addr6->s6_addr[10] = u.a8[3]; + break; + case 40: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr[4] = pref64->s6_addr[4]; + addr6->s6_addr[5] = u.a8[0]; + addr6->s6_addr[6] = u.a8[1]; + addr6->s6_addr[7] = u.a8[2]; + addr6->s6_addr[9] = u.a8[3]; + break; + case 32: + addr6->s6_addr32[0] = pref64->s6_addr32[0]; + addr6->s6_addr32[1] = addr4; + break; + default: + return false; + } + return true; +} + +/* + * Extract the IPv4 address @addr4 and the NAT64 prefix @pref64 from an IPv6 address, + * given the known prefix length @pref64_len. See the table above. + */ +static __always_inline bool +v6addr_to_v4(const struct in6_addr *addr6, int pref64_len, __be32 *addr4, struct in6_addr *pref64) +{ + union { + __be32 a32; + __u8 a8[4]; + } u; + + pref64->s6_addr32[0] = 0; + pref64->s6_addr32[1] = 0; + pref64->s6_addr32[2] = 0; + pref64->s6_addr32[3] = 0; + + switch (pref64_len) { + case 96: + u.a32 = addr6->s6_addr32[3]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr32[2] = addr6->s6_addr32[2]; + break; + case 64: + u.a8[0] = addr6->s6_addr[9]; + u.a8[1] = addr6->s6_addr[10]; + u.a8[2] = addr6->s6_addr[11]; + u.a8[3] = addr6->s6_addr[12]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + break; + case 56: + u.a8[0] = addr6->s6_addr[7]; + u.a8[1] = addr6->s6_addr[9]; + u.a8[2] = addr6->s6_addr[10]; + u.a8[3] = addr6->s6_addr[11]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr[7] = 0; + break; + case 48: + u.a8[0] = addr6->s6_addr[6]; + u.a8[1] = addr6->s6_addr[7]; + u.a8[2] = addr6->s6_addr[9]; + u.a8[3] = addr6->s6_addr[10]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr16[3] = 0; + break; + case 40: + u.a8[0] = addr6->s6_addr[5]; + u.a8[1] = addr6->s6_addr[6]; + u.a8[2] = addr6->s6_addr[7]; + u.a8[3] = addr6->s6_addr[9]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + pref64->s6_addr32[1] = addr6->s6_addr32[1]; + pref64->s6_addr16[3] = 0; + pref64->s6_addr[5] = 0; + + break; + case 32: + u.a32 = addr6->s6_addr32[1]; + + pref64->s6_addr32[0] = addr6->s6_addr32[0]; + break; + default: + return false; + } + + *addr4 = u.a32; + return true; +} + +/* ipv4 traffic in from application on this device, needs to be translated to v6 and sent to PLAT */ +static __always_inline int +clat_handle_v4(struct __sk_buff *skb, bool has_eth) +{ + int ret = TC_ACT_OK; + void *data_end = SKB_DATA_END(skb); + void *data = SKB_DATA(skb); + struct ipv6hdr *ip6h; + struct ipv6hdr dst_hdr = { + .version = 6, + }; + struct iphdr *iph; + struct ethhdr *eth; + + if (!ensure_header(&iph, skb, &data, &data_end, L2_H_LEN(has_eth))) + goto out; + + if (has_eth) { + eth = data; + if (eth->h_proto != bpf_htons(ETH_P_IP)) + goto out; + } + + if (iph->version != 4) + goto out; + + if (iph->saddr != config.local_v4.s_addr) + goto out; + + /* At this point we know the packet needs translation. If we can't + * rewrite it, it should be dropped. + */ + ret = TC_ACT_SHOT; + + /* we don't bother dealing with IP options or fragmented packets. The + * latter are identified by the 'frag_off' field having a value (either + * the MF bit, or the fragment offset, or both). However, this field also + * contains the "don't fragment" (DF) bit, which we ignore, so mask that + * out. The DF is the second-most-significant bit (as bit 0 is + * reserved). + */ + + if (iph->ihl != 5 || (iph->frag_off & ~bpf_htons(1 << 14))) { + DBG("v4: pkt src/dst %pI4/%pI4 has IP options or is fragmented, dropping\n", + &iph->saddr, + &iph->daddr); + goto out; + } + + if (!v4addr_to_v6(iph->daddr, &dst_hdr.daddr, &config.pref64, config.pref64_len)) + goto out; + + dst_hdr.saddr = config.local_v6; + dst_hdr.nexthdr = iph->protocol; + dst_hdr.hop_limit = iph->ttl; + /* weird definition in ipv6hdr */ + dst_hdr.priority = (iph->tos & 0x70) >> 4; + dst_hdr.flow_lbl[0] = iph->tos << 4; + dst_hdr.payload_len = bpf_htons(bpf_ntohs(iph->tot_len) - IP_H_LEN); + + DBG("v4: outgoing pkt to dst %pI4 (%pI6c)\n", &iph->daddr, &dst_hdr.daddr); + + switch (dst_hdr.nexthdr) { + case IPPROTO_ICMP: + if (rewrite_icmp(skb, &dst_hdr, has_eth)) + goto out; + dst_hdr.nexthdr = IPPROTO_ICMPV6; + break; + case IPPROTO_TCP: + case IPPROTO_UDP: + update_l4_checksum(skb, &dst_hdr, iph, has_eth, true, false, false, NULL); + break; + default: + break; + } + + if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IPV6), 0)) + goto out; + + data = SKB_DATA(skb); + data_end = SKB_DATA_END(skb); + + if (!ensure_header(&ip6h, skb, &data, &data_end, L2_H_LEN(has_eth))) + goto out; + + if (has_eth) { + eth = data; + eth->h_proto = bpf_htons(ETH_P_IPV6); + } + + *ip6h = dst_hdr; + + switch (dst_hdr.nexthdr) { + case IPPROTO_ICMPV6: + __sync_fetch_and_add(&stats.egress_icmp, 1); + break; + case IPPROTO_TCP: + __sync_fetch_and_add(&stats.egress_tcp, 1); + break; + case IPPROTO_UDP: + __sync_fetch_and_add(&stats.egress_udp, 1); + break; + default: + __sync_fetch_and_add(&stats.egress_other, 1); + break; + } + + ret = bpf_redirect(skb->ifindex, 0); +out: + if (ret == TC_ACT_SHOT) + __sync_fetch_and_add(&stats.egress_dropped, 1); + return ret; +} + +static __always_inline __u16 +csum_fold_helper(__u32 csum) +{ + __u32 sum; + sum = (csum >> 16) + (csum & 0xffff); + sum += (sum >> 16); + return ~sum; +} + +static __always_inline bool +v6addr_equal(const struct in6_addr *a, const struct in6_addr *b) +{ + int i; + + for (i = 0; i < 4; i++) { + if (a->s6_addr32[i] != b->s6_addr32[i]) + return false; + } + return true; +} + +static __always_inline void +translate_ipv6_header(const struct ipv6hdr *ip6, struct iphdr *ip, __be32 saddr, __be32 daddr) +{ + *ip = (struct iphdr) { + .version = 4, + .ihl = 5, + .tos = ip6->priority << 4 | (ip6->flow_lbl[0] >> 4), + .frag_off = bpf_htons(1 << 14), + .ttl = ip6->hop_limit, + .protocol = ip6->nexthdr == IPPROTO_ICMPV6 ? IPPROTO_ICMP : ip6->nexthdr, + .saddr = saddr, + .daddr = daddr, + .tot_len = bpf_htons(bpf_ntohs(ip6->payload_len) + IP_H_LEN), + }; + + ip->check = csum_fold_helper(bpf_csum_diff((__be32 *) ip, 0, (__be32 *) ip, IP_H_LEN, 0)); +} + +static __always_inline int +translate_icmpv6_header(const struct icmp6hdr *icmp6, struct icmphdr *icmp) +{ + /* These translations are defined in RFC6145 section 5.2 */ + switch (icmp6->icmp6_type) { + case ICMPV6_ECHO_REQUEST: + icmp->type = ICMP_ECHO; + break; + case ICMPV6_ECHO_REPLY: + icmp->type = ICMP_ECHOREPLY; + break; + case ICMPV6_DEST_UNREACH: + icmp->type = ICMP_DEST_UNREACH; + switch (icmp6->icmp6_code) { + case ICMPV6_NOROUTE: + case ICMPV6_NOT_NEIGHBOUR: + case ICMPV6_ADDR_UNREACH: + icmp->code = ICMP_HOST_UNREACH; + break; + case ICMPV6_ADM_PROHIBITED: + icmp->code = ICMP_HOST_ANO; + break; + case ICMPV6_PORT_UNREACH: + icmp->code = ICMP_PORT_UNREACH; + break; + default: + return -1; + } + break; + case ICMPV6_PKT_TOOBIG: + { + __u32 mtu; + + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_FRAG_NEEDED; + + mtu = bpf_ntohl(icmp6->icmp6_mtu) - 20; + if (mtu > 0xffff) + return -1; + icmp->un.frag.mtu = bpf_htons(mtu); + break; + } + case ICMPV6_TIME_EXCEED: + icmp->type = ICMP_TIME_EXCEEDED; + break; + case ICMPV6_PARAMPROB: + switch (icmp6->icmp6_code) { + case 0: + { + __u32 ptr; + + icmp->type = ICMP_PARAMETERPROB; + icmp->code = 0; + + ptr = bpf_ntohl(icmp6->icmp6_pointer); + /* Figure 6 in RFC6145 - using if statements b/c of + * range at the bottom + */ + if (ptr == 0 || ptr == 1) + icmp->un.reserved[0] = ptr; + else if (ptr == 4 || ptr == 5) + icmp->un.reserved[0] = 2; + else if (ptr == 6) + icmp->un.reserved[0] = 9; + else if (ptr == 7) + icmp->un.reserved[0] = 8; + else if (ptr >= 8 && ptr <= 23) + icmp->un.reserved[0] = 12; + else if (ptr >= 24 && ptr <= 39) + icmp->un.reserved[0] = 16; + else + return -1; + break; + } + case 1: + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_PROT_UNREACH; + break; + default: + return -1; + } + break; + default: + return -1; + } + + return 0; +} + +static __always_inline int +rewrite_icmpv6_inner(struct __sk_buff *skb, __u32 *csum_diff, bool has_eth) +{ + void *data_end = SKB_DATA_END(skb); + void *data = SKB_DATA(skb); + struct icmphdr *icmp; + struct icmp6hdr *icmp6; + struct icmphdr icmp_buf; /* buffer for the new ICMPv4 header */ + struct icmp6hdr icmp6_buf; /* copy of the old ICMPv6 header */ + + /* + * icmp6: v + * ------------------------------------------------------------------------- + * | Ethernet | IPv6 | ICMPv6 | IPv6 | ICMPv6 | ... + * ------------------------------------------------------------------------- + */ + + if (!ensure_header(&icmp6, + skb, + &data, + &data_end, + L2_H_LEN(has_eth) + 2 * IP6_H_LEN + ICMP6_H_LEN)) + return -1; + + icmp6_buf = *icmp6; + icmp = (void *) icmp6; + icmp_buf = *icmp; + + if (translate_icmpv6_header(icmp6, &icmp_buf)) + return -1; + + *icmp = icmp_buf; + update_icmp_checksum(skb, + (struct ipv6hdr *) (data + L2_H_LEN(has_eth)), + &icmp6_buf, + icmp, + has_eth, + false, + true, + 0); + + if (csum_diff) { + data_end = SKB_DATA_END(skb); + data = SKB_DATA(skb); + + if (!ensure_header(&icmp, + skb, + &data, + &data_end, + L2_H_LEN(has_eth) + 2 * IP6_H_LEN + ICMP6_H_LEN)) + return -1; + + /* Compute the checksum difference between the old ICMPv6 header and the new ICMPv4 one */ + *csum_diff = + bpf_csum_diff((__be32 *) &icmp6_buf, ICMP6_H_LEN, (__be32 *) &icmp6_buf, 0, *csum_diff); + *csum_diff = bpf_csum_diff((__be32 *) icmp, 0, (__be32 *) icmp, ICMP_H_LEN, *csum_diff); + } + return 0; +} + +static __always_inline int +rewrite_ipv6_inner(struct __sk_buff *skb, struct iphdr *dst_hdr, __u32 *csum_diff, bool has_eth) +{ + void *data_end = SKB_DATA_END(skb); + void *data = SKB_DATA(skb); + struct ipv6hdr *ip6h; + __be32 addr4; + struct in6_addr subnet_v6; + + /* + * ip6h: v + * ---------------------------------------------------------------- + * | Ethernet | IPv6 | ICMPv6 | IPv6 | ... + * ---------------------------------------------------------------- + */ + + if (!ensure_header(&ip6h, skb, &data, &data_end, L2_H_LEN(has_eth) + IP6_H_LEN + ICMP6_H_LEN)) + return -1; + + if (!v6addr_equal(&ip6h->saddr, &config.local_v6)) + return -1; + if (!v6addr_to_v4(&ip6h->daddr, config.pref64_len, &addr4, &subnet_v6)) + return -1; + if (!v6addr_equal(&subnet_v6, &config.pref64)) + return -1; + + translate_ipv6_header(ip6h, dst_hdr, config.local_v4.s_addr, addr4); + + if (csum_diff) { + /* Checksum difference between the old IPv6 header and the new IPv4 one */ + *csum_diff = bpf_csum_diff((__be32 *) ip6h, IP6_H_LEN, (__be32 *) ip6h, 0, *csum_diff); + + *csum_diff = bpf_csum_diff((__be32 *) dst_hdr, 0, (__be32 *) dst_hdr, IP_H_LEN, *csum_diff); + } + + switch (dst_hdr->protocol) { + case IPPROTO_ICMP: + if (rewrite_icmpv6_inner(skb, csum_diff, has_eth)) + return -1; + break; + case IPPROTO_TCP: + case IPPROTO_UDP: + update_l4_checksum(skb, ip6h, dst_hdr, has_eth, false, true, false, csum_diff); + break; + default: + break; + } + + return 0; +} + +static __always_inline int +rewrite_icmpv6(struct __sk_buff *skb, int *out_length_diff, bool has_eth) +{ + void *data_end = SKB_DATA_END(skb); + void *data = SKB_DATA(skb); + struct iphdr *ip; + struct icmp6hdr *icmp6; + struct icmphdr *icmp; + struct icmphdr icmp_buf; /* buffer for the new ICMPv4 header */ + struct icmp6hdr icmp6_buf; /* copy of the old ICMPv6 header */ + struct iphdr ip_in_buf; /* buffer for the new inner IPv4 header */ + __u32 csum_diff = 0; + + /* + * icmp6: v + * --------------------------------------------- + * | Ethernet | IPv6 | ICMPv6 | ... + * --------------------------------------------- + */ + + if (!ensure_header(&icmp6, skb, &data, &data_end, L2_H_LEN(has_eth) + IP6_H_LEN)) + return -1; + + icmp6_buf = *icmp6; + icmp = (void *) icmp6; + icmp_buf = *icmp; + + if (translate_icmpv6_header(icmp6, &icmp_buf)) + return -1; + + if (icmp6->icmp6_type >= 128) { + /* ICMPv6 non-error message: only translate the header */ + *icmp = icmp_buf; + update_icmp_checksum(skb, + (struct ipv6hdr *) (data + L2_H_LEN(has_eth)), + &icmp6_buf, + icmp, + has_eth, + false, + false, + 0); + return 0; + } + + /* ICMPv6 error messages: we need to rewrite the headers in the inner packet. + * Track in csum_diff the incremental changes to the checksum for the ICMPv4 + * header. */ + + if (rewrite_ipv6_inner(skb, &ip_in_buf, &csum_diff, has_eth)) + return -1; + + /* The inner IP header shrinks from 40 (IPv6) to 20 (IPv4) bytes; we need to move + * the L4 header and payload. BPF programs don't have an easy way to move a variable + * amount of packet data; use bpf_skb_adjust_room() which can add or remove data + * inside a packet. It doesn't support arbitrary offsets, but we can use BPF_ADJ_ROOM_NET + * to remove the bytes just after the L3 header, and rewrite the ICMP and the inner + * IP headers. + */ + if (bpf_skb_adjust_room(skb, (int) IP_H_LEN - (int) IP6_H_LEN, BPF_ADJ_ROOM_NET, 0)) + return -1; + + *out_length_diff = (int) IP_H_LEN - (int) IP6_H_LEN; + + data_end = SKB_DATA_END(skb); + data = SKB_DATA(skb); + + if (!ensure_header(&ip, skb, &data, &data_end, L2_H_LEN(has_eth) + IP6_H_LEN + ICMP_H_LEN)) + return -1; + + icmp = data + L2_H_LEN(has_eth) + IP6_H_LEN; + + /* Rewrite the ICMPv6 header with the translated ICMPv4 one */ + *icmp = icmp_buf; + /* Rewrite the inner IPv6 header with the translated IPv4 one */ + *ip = ip_in_buf; + + /* Update the ICMPv4 checksum according to all the changes in headers */ + update_icmp_checksum(skb, + (struct ipv6hdr *) (data + L2_H_LEN(has_eth)), + &icmp6_buf, + icmp, + has_eth, + false, + false, + csum_diff); + + return 0; +} + +/* ipv6 traffic from the PLAT, to be translated into ipv4 and sent to an application */ +static __always_inline int +clat_handle_v6(struct __sk_buff *skb, bool has_eth) +{ + int ret = TC_ACT_OK; + void *data_end = SKB_DATA_END(skb); + void *data = SKB_DATA(skb); + struct ethhdr *eth; + struct ipv6hdr *ip6h; + struct iphdr *iph; + struct iphdr dst_hdr; + struct in6_addr subnet_v6; + __be32 addr4; + int length_diff = 0; + bool fragmented = false; + + if (!ensure_header(&ip6h, skb, &data, &data_end, L2_H_LEN(has_eth))) + goto out; + + if (has_eth) { + eth = data; + if (eth->h_proto != bpf_htons(ETH_P_IPV6)) + goto out; + } + + if (ip6h->version != 6) + goto out; + + if (!v6addr_equal(&ip6h->daddr, &config.local_v6)) + goto out; + if (!v6addr_to_v4(&ip6h->saddr, config.pref64_len, &addr4, &subnet_v6)) + goto out; + if (!v6addr_equal(&subnet_v6, &config.pref64)) { + struct icmp6hdr *icmp6; + + /* Follow draft-ietf-v6ops-icmpext-xlat-v6only-source-01: + * + * "Whenever a translator translates an ICMPv6 Destination Unreachable, + * ICMPv6 Time Exceeded or ICMPv6 Packet Too Big ([RFC4443]) to the + * corresponding ICMPv4 ([RFC0792]) message, and the IPv6 source + * address in the outermost IPv6 header is untranslatable, the + * translator SHOULD use the dummy IPv4 address (192.0.0.8) as the IPv4 + * source address for the translated packet." + */ + if (ip6h->nexthdr != IPPROTO_ICMPV6) + goto out; + + if (!ensure_header(&icmp6, skb, &data, &data_end, L2_H_LEN(has_eth) + IP6_H_LEN)) + goto out; + + ip6h = data + L2_H_LEN(has_eth); + + if (icmp6->icmp6_type != ICMPV6_DEST_UNREACH && icmp6->icmp6_type != ICMPV6_TIME_EXCEED + && icmp6->icmp6_type != ICMPV6_PKT_TOOBIG) + goto out; + + DBG("v6: icmpv6 type %u from native address %pI6c, translating src to dummy ipv4\n", + icmp6->icmp6_type, + &ip6h->saddr); + + addr4 = __cpu_to_be32(INADDR_DUMMY); + } + + /* At this point we know the packet needs translation. If we can't + * rewrite it, it should be dropped. + */ + ret = TC_ACT_SHOT; + + if (ip6h->nexthdr == IPPROTO_TCP || ip6h->nexthdr == IPPROTO_UDP + || ip6h->nexthdr == IPPROTO_ICMPV6) { + translate_ipv6_header(ip6h, &dst_hdr, addr4, config.local_v4.s_addr); + DBG("v6: incoming pkt from src %pI6c (%pI4)\n", &ip6h->saddr, &addr4); + } else if (ip6h->nexthdr == IPPROTO_FRAGMENT) { + struct ip6_frag *frag; + int tot_len; + __u16 offset; + + if (!ensure_header(&frag, skb, &data, &data_end, L2_H_LEN(has_eth) + IP6_H_LEN)) + goto out; + + ip6h = data + L2_H_LEN(has_eth); + + /* Translate into an IPv4 fragmented packet, RFC 6145 5.1.1 */ + + tot_len = bpf_ntohs(ip6h->payload_len) + IP_H_LEN - IP6_FRAG_H_LEN; + + offset = bpf_ntohs(frag->offset); + offset = ((offset & 1) << 13) | /* More Fragments flag */ + (offset >> 3); /* Offset in 8-octet units */ + + dst_hdr = (struct iphdr) { + .version = 4, + .ihl = 5, + .id = bpf_htons(bpf_ntohl(frag->identification) & 0xffff), + .tos = ip6h->priority << 4 | (ip6h->flow_lbl[0] >> 4), + .frag_off = bpf_htons(offset), + .ttl = ip6h->hop_limit, + .protocol = frag->nexthdr == IPPROTO_ICMPV6 ? IPPROTO_ICMP : frag->nexthdr, + .saddr = addr4, + .daddr = config.local_v4.s_addr, + .tot_len = bpf_htons(tot_len), + }; + + dst_hdr.check = csum_fold_helper( + bpf_csum_diff((__be32 *) &dst_hdr, 0, (__be32 *) &dst_hdr, IP_H_LEN, 0)); + + fragmented = true; + + DBG("v6: incoming fragmented pkt from src %pI6c (%pI4), id 0x%x\n", + &ip6h->saddr, + &addr4, + bpf_ntohs(dst_hdr.id)); + } else { + DBG("v6: pkt src/dst %pI6c/%pI6c has nexthdr %u, dropping\n", &ip6h->saddr, &ip6h->daddr); + goto out; + } + + switch (dst_hdr.protocol) { + case IPPROTO_ICMP: + /* We can't update the checksum of ICMP fragmented packets: ICMPv4 doesn't use + * a pseudo header, while the ICMPv6 pseudo-header includes the total payload + * length, which is not known when parsing the first fragment. This makes it + * impossible for a stateless translator to compute the checksum delta. TCP and + * UDP don't have this problem because both the v4 and v6 pseudo-headers include + * the total length. */ + if (fragmented) + goto out; + + if (rewrite_icmpv6(skb, &length_diff, has_eth)) + goto out; + break; + case IPPROTO_TCP: + case IPPROTO_UDP: + /* Update the L4 headers only for non-fragmented packets or for the first + * fragment, which contains the L4 header. */ + if (!fragmented || (bpf_ntohs(dst_hdr.frag_off) & 0x1FFF) == 0) { + update_l4_checksum(skb, ip6h, &dst_hdr, has_eth, false, false, fragmented, NULL); + } + break; + default: + break; + } + + /* rewrite_icmpv6() can change the payload length when it rewrites the content of + * an ICMPv6 error packet. Update the length and the checksum. */ + if (length_diff != 0) { + data = SKB_DATA(skb); + data_end = SKB_DATA_END(skb); + + if (!ensure_header(&ip6h, skb, &data, &data_end, L2_H_LEN(has_eth))) + goto out; + + dst_hdr.tot_len = bpf_htons(bpf_ntohs(ip6h->payload_len) + length_diff + IP_H_LEN); + + dst_hdr.check = 0; + dst_hdr.check = csum_fold_helper( + bpf_csum_diff((__be32 *) &dst_hdr, 0, (__be32 *) &dst_hdr, IP_H_LEN, 0)); + } + + if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IP), 0)) + goto out; + + if (fragmented) { + /* Remove the IPv6 fragment header */ + if (bpf_skb_adjust_room(skb, -(__s32) IP6_FRAG_H_LEN, BPF_ADJ_ROOM_NET, 0)) + goto out; + } + + data = SKB_DATA(skb); + data_end = SKB_DATA_END(skb); + + if (!ensure_header(&iph, skb, &data, &data_end, L2_H_LEN(has_eth))) + goto out; + + if (has_eth) { + eth = data; + eth->h_proto = bpf_htons(ETH_P_IP); + } + + *iph = dst_hdr; + + if (fragmented) + __sync_fetch_and_add(&stats.ingress_fragment, 1); + switch (dst_hdr.protocol) { + case IPPROTO_ICMP: + __sync_fetch_and_add(&stats.ingress_icmp, 1); + break; + case IPPROTO_TCP: + __sync_fetch_and_add(&stats.ingress_tcp, 1); + break; + case IPPROTO_UDP: + __sync_fetch_and_add(&stats.ingress_udp, 1); + break; + default: + __sync_fetch_and_add(&stats.ingress_other, 1); + break; + } + + ret = bpf_redirect(skb->ifindex, BPF_F_INGRESS); +out: + if (ret == TC_ACT_SHOT) + __sync_fetch_and_add(&stats.ingress_dropped, 1); + return ret; +} + +/* Use separate entry points for interfaces with and without an + * Ethernet header. Since all functions are now marked as inline, + * the compiler is able to replace the value of the parametric + * L2_H_LEN() macros with an immediate constant. This avoids + * pointer arithmetic which is forbidden because we don't run with + * CAP_PERFMON. The loader attaches the right program pair based + * on the interface type. */ +SEC("tcx/egress") +int +nm_clat_egress_eth(struct __sk_buff *skb) +{ + return clat_handle_v4(skb, true); +} + +SEC("tcx/egress") +int +nm_clat_egress_rawip(struct __sk_buff *skb) +{ + return clat_handle_v4(skb, false); +} + +SEC("tcx/ingress") +int +nm_clat_ingress_eth(struct __sk_buff *skb) +{ + return clat_handle_v6(skb, true); +} + +SEC("tcx/ingress") +int +nm_clat_ingress_rawip(struct __sk_buff *skb) +{ + return clat_handle_v6(skb, false); +} diff --git a/src/core/bpf/clat.h b/src/core/bpf/clat.h new file mode 100644 index 00000000..4a8adba5 --- /dev/null +++ b/src/core/bpf/clat.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __NAT64_H__ +#define __NAT64_H__ + +#include <linux/in6.h> + +struct clat_config { + struct in6_addr local_v6; + struct in6_addr pref64; + struct in_addr local_v4; + unsigned pref64_len; +}; + +struct clat_stats { + /* egress: v4 to v6 */ + __u64 egress_tcp; + __u64 egress_udp; + __u64 egress_icmp; + __u64 egress_other; + __u64 egress_dropped; + /* ingress: v6 to v4 */ + __u64 ingress_tcp; + __u64 ingress_udp; + __u64 ingress_icmp; + __u64 ingress_other; + __u64 ingress_fragment; + __u64 ingress_dropped; +}; + +#endif diff --git a/src/core/bpf/meson.build b/src/core/bpf/meson.build new file mode 100644 index 00000000..39b978dd --- /dev/null +++ b/src/core/bpf/meson.build @@ -0,0 +1,239 @@ +# SPDX-License-Identifier: LGPL-2.1+ + +# Ripped from systemd: https://github.com/systemd/systemd/pull/20429 + +if not enable_clat + subdir_done() +endif + +bpf_compiler = get_option('bpf-compiler') +clang_found = false +clang_supports_bpf = false +bpf_gcc_found = false +bpftool_strip = false + +if bpf_compiler == 'clang' or bpf_compiler == 'auto' + # Support 'versioned' clang/llvm-strip binaries, as seen on Debian/Ubuntu + # (like clang-10/llvm-strip-10) + if meson.is_cross_build() or cc.get_id() != 'clang' or cc.cmd_array()[0].contains('afl-clang') or cc.cmd_array()[0].contains('hfuzz-clang') + r = find_program('clang', + version : '>= 10.0.0') + clang_found = r.found() + if clang_found + if meson.version().version_compare('>= 0.55') + clang = r.full_path() + else + clang = r.path() + endif + endif + else + clang_found = true + clang = cc.cmd_array() + endif + + if clang_found + # Check if 'clang -target bpf' is supported. + clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus', check : false).returncode() == 0 + endif +elif bpf_compiler == 'gcc' or bpf_compiler == 'auto' + bpf_gcc = find_program('bpf-gcc', + 'bpf-none-gcc', + 'bpf-unknown-none-gcc', + version : '>= 13.1.0') + bpf_gcc_found = bpf_gcc.found() +endif + +if bpf_compiler == 'auto' + if clang_supports_bpf and bpf_gcc_found + # Both supported, prefer the one matching our compiler: + if cc.get_id() == 'gcc' + bpf_compiler = 'gcc' + else + # Default to clang if we don't know this compiler + bpf_compiler = 'clang' + endif + elif clang_supports_bpf + bpf_compiler = 'clang' + elif bpf_gcc_found + bpf_compiler = 'clang' + endif +endif + +if clang_supports_bpf or bpf_gcc_found + # Debian installs this in /usr/sbin/ which is not in $PATH. + # We check for 'bpftool' first, honouring $PATH, and in /usr/sbin/ for Debian. + # We use 'bpftool gen object' subcommand for bpftool strip, it was added by d80b2fcbe0a023619e0fc73112f2a02c2662f6ab (v5.13). + bpftool = find_program('bpftool', + '/usr/sbin/bpftool', + required : bpf_compiler == 'gcc', + version : bpf_compiler == 'gcc' ? '>= 7.0.0' : '>= 5.13.0') + + if bpftool.found() + bpftool_strip = true + elif bpf_compiler == 'clang' + # We require the 'bpftool gen skeleton' subcommand, it was added by 985ead416df39d6fe8e89580cc1db6aa273e0175 (v5.6). + bpftool = find_program('bpftool', + '/usr/sbin/bpftool', + required : true, + version : '>= 5.6.0') + endif + + # We use `llvm-strip` as a fallback if `bpftool gen object` strip support is not available. + if not bpftool_strip and bpftool.found() and clang_supports_bpf + if not meson.is_cross_build() + llvm_strip_bin = run_command(clang, '--print-prog-name', 'llvm-strip', + check : true).stdout().strip() + else + llvm_strip_bin = 'llvm-strip' + endif + llvm_strip = find_program(llvm_strip_bin, + required : true, + version : '>= 10.0.0') + endif +else + error('clat support was enabled but couldn\'t find a suitable BPF compiler!') +endif + +bpf_clang_flags = [ + '-std=gnu17', + '-Wunused', + '-Wimplicit-fallthrough', + '-Wno-compare-distinct-pointer-types', + '-fno-stack-protector', + '-O2', + '-target', + 'bpf', + '-g', + '-c', +] + +bpf_gcc_flags = [ + '-std=gnu17', + '-Wunused', + '-Wimplicit-fallthrough', + '-fno-stack-protector', + '-fno-ssa-phiopt', + '-O2', + '-mcpu=v3', + '-mco-re', + '-gbtf', + '-c', +] + +clang_arch_flag = '-D__@0@__'.format(host_machine.cpu_family()) + +libbpf_include_dir = dependency('libbpf').get_variable(pkgconfig : 'includedir') + +# Generate defines that are appropriate to tell the compiler what architecture +# we're compiling for. By default we just map meson's cpu_family to __<cpu_family>__. +# This dictionary contains the exceptions where this doesn't work. +# +# C.f. https://mesonbuild.com/Reference-tables.html#cpu-families +# and src/basic/missing_syscall_def.h. +cpu_arch_defines = { + 'ppc' : ['-D__powerpc__', '-D__TARGET_ARCH_powerpc'], + 'ppc64' : ['-D__powerpc64__', '-D__TARGET_ARCH_powerpc', '-D_CALL_ELF=2'], + 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32', '-D__TARGET_ARCH_riscv'], + 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64', '-D__TARGET_ARCH_riscv'], + 'x86' : ['-D__i386__', '-D__TARGET_ARCH_x86'], + 's390x' : ['-D__s390__', '-D__s390x__', '-D__TARGET_ARCH_s390'], + + # For arm, assume hardware fp is available. + 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP', '-D__TARGET_ARCH_arm'], + 'loongarch64' : ['-D__loongarch__', '-D__loongarch_grlen=64', '-D__TARGET_ARCH_loongarch'] +} + +bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(), + ['-D__@0@__'.format(host_machine.cpu_family())]) +if bpf_compiler == 'gcc' + bpf_arch_flags += ['-m' + host_machine.endian() + '-endian'] +endif + +bpf_o_unstripped_cmd = [] +if bpf_compiler == 'clang' + bpf_o_unstripped_cmd += [ + clang, + bpf_clang_flags, + bpf_arch_flags, + ] +elif bpf_compiler == 'gcc' + bpf_o_unstripped_cmd += [ + bpf_gcc, + bpf_gcc_flags, + bpf_arch_flags, + ] +endif + +bpf_o_unstripped_cmd += ['-I.'] + +if cc.get_id() == 'gcc' or meson.is_cross_build() + if cc.get_id() != 'gcc' + warning('Cross compiler is not gcc. Guessing the target triplet for bpf likely fails.') + endif + target_triplet_cmd = run_command(cc.cmd_array(), '-print-multiarch', check: false) +else + # clang does not support -print-multiarch (D133170) and its -dump-machine + # does not match multiarch. Query gcc instead. + target_triplet_cmd = run_command('gcc', '-print-multiarch', check: false) +endif + +if target_triplet_cmd.returncode() == 0 + target_triplet = target_triplet_cmd.stdout().strip() + bpf_o_unstripped_cmd += [ + '-isystem', + '/usr/include/@0@'.format(target_triplet) + ] +endif + +bpf_o_unstripped_cmd += [ + '-idirafter', + libbpf_include_dir, + '@INPUT@', + '-o', + '@OUTPUT@' +] + +if bpftool_strip + bpf_o_cmd = [ + bpftool, + 'gen', + 'object', + '@OUTPUT@', + '@INPUT@' + ] +elif bpf_compiler == 'clang' + bpf_o_cmd = [ + llvm_strip, + '-g', + '@INPUT@', + '-o', + '@OUTPUT@' + ] +endif + +skel_h_cmd = [ + bpftool, + 'g', + 's', + '@INPUT@' +] + +clat_bpf_o_unstripped = custom_target( + 'clat.bpf.unstripped.o', + input : 'clat.bpf.c', + output : 'clat.bpf.unstripped.o', + command : bpf_o_unstripped_cmd) + +clat_bpf_o = custom_target( + 'clat.bpf.o', + input : clat_bpf_o_unstripped, + output : 'clat.bpf.o', + command : bpf_o_cmd) + +clat_skel_h = custom_target( + 'clat.skel.h', + input : clat_bpf_o, + output : 'clat.skel.h', + command : skel_h_cmd, + capture : true) + diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c index 39e68e96..06d41a19 100644 --- a/src/core/devices/nm-device-bond.c +++ b/src/core/devices/nm-device-bond.c @@ -52,11 +52,12 @@ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \ NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_USE_CARRIER, \ NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, \ - NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX + NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY -#define OPTIONS_REAPPLY_FULL \ - OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \ - NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_NS_IP6_TARGET +#define OPTIONS_REAPPLY_FULL \ + OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \ + NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_NS_IP6_TARGET, \ + NM_SETTING_BOND_OPTION_ARP_MISSED_MAX /*****************************************************************************/ @@ -501,6 +502,8 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p props->lp_interval_has = props->lp_interval != 1; props->tlb_dynamic_lb_has = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB); props->lacp_active_has = NM_IN_SET(props->mode, NM_BOND_MODE_8023AD); + props->arp_missed_max_has = + !NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB, NM_BOND_MODE_8023AD); } static void @@ -907,6 +910,8 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne set_bond_arp_ip_targets(device, s_bond); set_bond_attrs_or_default(device, s_bond, NM_MAKE_STRV(OPTIONS_REAPPLY_SUBSET)); + if (!NM_IN_SET(mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB, NM_BOND_MODE_8023AD)) + set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX); _balance_slb_setup(self, con_new); } diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c index 11f691de..b550b10a 100644 --- a/src/core/devices/nm-device-ethernet.c +++ b/src/core/devices/nm-device-ethernet.c @@ -1684,11 +1684,13 @@ complete_connection(NMDevice *device, con_peer_name = nm_setting_veth_get_peer(s_veth); if (con_peer_name) { - nm_utils_error_set(error, - NM_UTILS_ERROR_UNKNOWN, - "mismatching veth peer \"%s\"", - con_peer_name); - return FALSE; + if (!nm_streq(con_peer_name, peer_name)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "mismatching veth peer \"%s\"", + con_peer_name); + return FALSE; + } } else g_object_set(s_veth, NM_SETTING_VETH_PEER, peer_name, NULL); diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c index c5bcc91a..d3cbb663 100644 --- a/src/core/devices/nm-device-macvlan.c +++ b/src/core/devices/nm-device-macvlan.c @@ -468,7 +468,12 @@ static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = { NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("NoPromisc", "b", NM_DEVICE_MACVLAN_NO_PROMISC), - NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tab", "b", NM_DEVICE_MACVLAN_TAP), ), ), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tap", "b", NM_DEVICE_MACVLAN_TAP), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE( + "Tab", + "b", + NM_DEVICE_MACVLAN_TAP, + .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ), }; static void diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c index c4b9e234..8be29fe9 100644 --- a/src/core/devices/nm-device-veth.c +++ b/src/core/devices/nm-device-veth.c @@ -53,7 +53,7 @@ update_properties(NMDevice *device) nm_device_parent_set_ifindex(device, peer_ifindex); peer = nm_device_parent_get_device(device); - if (peer && NM_IS_DEVICE_VETH(peer) && nm_device_parent_get_ifindex(peer) <= 0) + if (peer && NM_IS_DEVICE_VETH(peer) && !nm_device_parent_get_device(peer)) update_properties(peer); } diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c index 4058287c..87844e6f 100644 --- a/src/core/devices/nm-device-vxlan.c +++ b/src/core/devices/nm-device-vxlan.c @@ -176,14 +176,14 @@ create_and_realize(NMDevice *device, if (str) { if (!nm_inet_parse_bin(AF_INET, str, NULL, &props.local) && !nm_inet_parse_bin(AF_INET6, str, NULL, &props.local6)) - return FALSE; + return nm_assert_unreachable_val(FALSE); } str = nm_setting_vxlan_get_remote(s_vxlan); if (str) { if (!nm_inet_parse_bin(AF_INET, str, NULL, &props.group) && !nm_inet_parse_bin(AF_INET6, str, NULL, &props.group6)) - return FALSE; + return nm_assert_unreachable_val(FALSE); } props.tos = nm_setting_vxlan_get_tos(s_vxlan); diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index a0cf1ee6..e3457e34 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -805,7 +805,7 @@ typedef struct _NMDevicePrivate { GVariant *ports_variant; /* Array of port devices D-Bus path */ char *prop_ip_iface; /* IP interface D-Bus property */ - GList *ping_operations; + CList ping_ops_lst_head; GSource *ping_timeout; } NMDevicePrivate; @@ -850,7 +850,6 @@ static const char *_activation_func_to_string(ActivationHandleFunc func); static void _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting); static void queued_state_clear(NMDevice *device); -static void ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data); static void nm_device_start_ip_check(NMDevice *self); static void realize_start_setup(NMDevice *self, const NMPlatformLink *plink, @@ -907,10 +906,11 @@ static void concheck_update_state(NMDevice *self, static void sriov_op_cb(GError *error, gpointer user_data); static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self); -static gboolean device_link_changed(gpointer user_data); -static gboolean _get_maybe_ipv6_disabled(NMDevice *self); -static void deactivate_ready(NMDevice *self, NMDeviceStateReason reason); -static void carrier_disconnected_action_cancel(NMDevice *self); +static gboolean device_link_changed(gpointer user_data); +static gboolean _get_maybe_ipv6_disabled(NMDevice *self); +static void deactivate_ready(NMDevice *self, NMDeviceStateReason reason); +static void carrier_disconnected_action_cancel(NMDevice *self); +static const char *nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family); /*****************************************************************************/ @@ -1294,7 +1294,7 @@ _prop_get_ipv6_dhcp_duid(NMDevice *self, gint64 time; guint32 timestamp; -#define EPOCH_DATETIME_THREE_YEARS (356 * 24 * 3600 * 3) +#define EPOCH_DATETIME_THREE_YEARS (365 * 24 * 3600 * 3) /* We want a variable time between the host_id timestamp and three years * before. Let's compute the time (in seconds) from 0 to 3 years; then we'll @@ -1524,6 +1524,50 @@ _prop_get_connection_dnssec(NMDevice *self, NMConnection *connection) NM_SETTING_CONNECTION_DNSSEC_DEFAULT); } +static NMSettingIp4ConfigClat +_prop_get_ipv4_clat(NMDevice *self, gboolean do_log) +{ + NMSettingIP4Config *s_ip4 = NULL; + NMSettingIp4ConfigClat clat; + const char *method; + + s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG); + if (!s_ip4) + return NM_SETTING_IP4_CONFIG_CLAT_NO; + + method = nm_device_get_effective_ip_config_method(self, AF_INET); + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + return NM_SETTING_IP4_CONFIG_CLAT_NO; + + clat = nm_setting_ip4_config_get_clat(s_ip4); + if (clat == NM_SETTING_IP4_CONFIG_CLAT_DEFAULT) { + clat = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv4.clat"), + self, + NM_SETTING_IP4_CONFIG_CLAT_NO, + NM_SETTING_IP4_CONFIG_CLAT_FORCE, + NM_SETTING_IP4_CONFIG_CLAT_NO); + } + + if (clat == NM_SETTING_IP4_CONFIG_CLAT_AUTO + && !nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + /* clat=auto enables CLAT only with method=auto */ + clat = NM_SETTING_IP4_CONFIG_CLAT_NO; + } + + if (!HAVE_CLAT + && NM_IN_SET(clat, NM_SETTING_IP4_CONFIG_CLAT_AUTO, NM_SETTING_IP4_CONFIG_CLAT_FORCE)) { + if (do_log) { + _NMLOG(clat == NM_SETTING_IP4_CONFIG_CLAT_FORCE ? LOGL_WARN : LOGL_TRACE, + LOGD_DEVICE, + "CLAT will not work because it is disabled at build time"); + } + clat = NM_SETTING_IP4_CONFIG_CLAT_NO; + } + + return clat; +} + static NMMptcpFlags _prop_get_connection_mptcp_flags(NMDevice *self, NMConnection *connection) { @@ -1922,26 +1966,43 @@ _prop_get_ipvx_may_fail_cached(NMDevice *self, int addr_family, NMTernary *cache } static gboolean -_prop_get_ipv4_dhcp_ipv6_only_preferred(NMDevice *self) +_prop_get_ipv4_dhcp_ipv6_only_preferred(NMDevice *self, gboolean *out_is_auto) { NMSettingIP4Config *s_ip4; NMSettingIP4DhcpIpv6OnlyPreferred ipv6_only; + NM_SET_OUT(out_is_auto, FALSE); + s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG); if (!s_ip4) return FALSE; ipv6_only = nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(s_ip4); - if (ipv6_only != NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT) - return ipv6_only; + if (ipv6_only == NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT) { + ipv6_only = nm_config_data_get_connection_default_int64( + NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("ipv4.dhcp-ipv6-only-preferred"), + self, + NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO, + NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO, + NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO); + } - return nm_config_data_get_connection_default_int64( - NM_CONFIG_GET_DATA, - NM_CON_DEFAULT("ipv4.dhcp-ipv6-only-preferred"), - self, - NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO, - NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES, - NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO); + if (NM_IN_SET(ipv6_only, + NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES, + NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO)) + return ipv6_only == NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES; + + /* auto */ + NM_SET_OUT(out_is_auto, TRUE); + + if (nm_streq0(nm_device_get_effective_ip_config_method(self, AF_INET6), + NM_SETTING_IP6_CONFIG_METHOD_AUTO) + && _prop_get_ipv4_clat(self, FALSE) != NM_SETTING_IP4_CONFIG_CLAT_NO) { + return TRUE; + } + + return FALSE; } /** @@ -3623,6 +3684,7 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co { NML3ConfigData *l3cd; int ifindex; + gs_free char *gw_warning = NULL; nm_assert(NM_IS_DEVICE(self)); nm_assert(!connection || NM_IS_CONNECTION(connection)); @@ -3642,6 +3704,11 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co nm_l3_config_data_set_dnssec(l3cd, _prop_get_connection_dnssec(self, connection)); nm_l3_config_data_set_ip6_privacy(l3cd, _prop_get_ipv6_ip6_privacy(self, connection)); nm_l3_config_data_set_mptcp_flags(l3cd, _prop_get_connection_mptcp_flags(self, connection)); + + gw_warning = nm_connection_get_unreachable_gateways_warning(connection, FALSE); + if (gw_warning) + _LOGW(LOGD_IP, "%s", gw_warning); + return l3cd; } @@ -4924,7 +4991,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N if (state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) { /* FIXME(l3cfg): MTU handling should be moved to l3cfg. */ if (l3cd) - priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu(l3cd); + priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu_ra(l3cd); _commit_mtu(self); } _dev_ipll4_check_fallback(self, l3cd); @@ -6345,6 +6412,14 @@ concheck_is_possible(NMDevice *self) if (priv->state == NM_DEVICE_STATE_UNKNOWN) return FALSE; + if (!nm_config_data_get_device_config_boolean_by_device( + NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_KEY_DEVICE_CHECK_CONNECTIVITY, + self, + TRUE, + TRUE)) + return FALSE; + return TRUE; } @@ -6365,8 +6440,10 @@ concheck_periodic_schedule_do(NMDevice *self, int addr_family, gint64 now_ns) goto out; } - if (!concheck_is_possible(self)) + if (!concheck_is_possible(self)) { + concheck_update_state(self, addr_family, NM_CONNECTIVITY_UNKNOWN, FALSE); goto out; + } nm_assert(now_ns > 0); nm_assert(priv->concheck_x[IS_IPv4].p_cur_interval > 0); @@ -6589,7 +6666,11 @@ concheck_update_interval(NMDevice *self, int addr_family, gboolean check_now) concheck_periodic_schedule_do(self, addr_family, 0); /* also update the fake connectivity state. */ - concheck_update_state(self, addr_family, NM_CONNECTIVITY_FAKE, TRUE); + if (concheck_is_possible(self)) + concheck_update_state(self, addr_family, NM_CONNECTIVITY_FAKE, TRUE); + else + concheck_update_state(self, addr_family, NM_CONNECTIVITY_UNKNOWN, FALSE); + return; } @@ -6618,6 +6699,7 @@ concheck_update_state(NMDevice *self, /* @state is a result of the connectivity check. We only expect a precise * number of possible values. */ nm_assert(NM_IN_SET(state, + NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_PORTAL, NM_CONNECTIVITY_FULL, @@ -6941,8 +7023,11 @@ nm_device_check_connectivity(NMDevice *self, NMDeviceConnectivityCallback callback, gpointer user_data) { - if (!concheck_is_possible(self)) + if (!concheck_is_possible(self)) { + concheck_update_state(self, AF_INET, NM_CONNECTIVITY_UNKNOWN, FALSE); + concheck_update_state(self, AF_INET6, NM_CONNECTIVITY_UNKNOWN, FALSE); return NULL; + } concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_CHECK_EXTERNAL); return concheck_start(self, addr_family, callback, user_data, FALSE); @@ -7149,7 +7234,9 @@ nm_device_controller_release_port(NMDevice *self, info = find_port_info(self, port); - if (info->port_state == PORT_STATE_ATTACHED) + if (!info) + port_state_str = "(not registered)"; + else if (info->port_state == PORT_STATE_ATTACHED) port_state_str = "(attached)"; else if (info->port_state == PORT_STATE_NOT_ATTACHED) port_state_str = "(not attached)"; @@ -7162,7 +7249,7 @@ nm_device_controller_release_port(NMDevice *self, "controller: release one port " NM_HASH_OBFUSCATE_PTR_FMT "/%s %s%s", NM_HASH_OBFUSCATE_PTR(port), nm_device_get_iface(port), - !info ? "(not registered)" : port_state_str, + port_state_str, release_type == RELEASE_PORT_TYPE_CONFIG_FORCE ? " (force-configure)" : (release_type == RELEASE_PORT_TYPE_CONFIG ? " (configure)" : "(no-config)")); @@ -7191,6 +7278,8 @@ nm_device_controller_release_port(NMDevice *self, if (ret == NM_TERNARY_DEFAULT) { port_priv->port_detach_count++; port_priv->port_detach_reason = reason; + } else { + g_object_unref(port); } } @@ -7798,6 +7887,12 @@ device_link_changed(gpointer user_data) NM_UNMANAGED_PLATFORM_INIT, NM_UNMAN_FLAG_OP_SET_MANAGED, nm_device_get_manage_reason_external(self)); + + /* Now that we got UDEV's announcement we need to check ignore-carrier again. + * This is because the permanent MAC might have been set or changed. If we don't + * recheck we would ignore rules matching by MAC address. */ + priv->ignore_carrier = + nm_config_data_get_ignore_carrier_by_device(nm_config_get_data(nm_config_get()), self); } _dev_unmanaged_check_external_down(self, FALSE, FALSE); @@ -8330,6 +8425,17 @@ config_changed(NMConfig *config, && !nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV)) device_init_static_sriov_num_vfs(self); } + + if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES) && concheck_is_possible(self)) { + /* restart (periodic) connectivity checks if they were previously disabled */ + if (!nm_config_data_get_device_config_boolean_by_device( + old_data, + NM_CONFIG_KEYFILE_KEY_DEVICE_CHECK_CONNECTIVITY, + self, + TRUE, + TRUE)) + nm_device_check_connectivity_update_interval(self); + } } static void @@ -8463,10 +8569,13 @@ realize_start_setup(NMDevice *self, nm_device_update_initial_hw_address(self); nm_device_update_permanent_hw_address(self, FALSE); - /* Note: initial hardware address must be read before calling get_ignore_carrier() */ + /* Note: initial hardware address must be read before calling get_ignore_carrier(). We'll + * need to recheck ignore_carrier again after UDEV's announcement, as the permanent MAC + * address may be set by UDEV. */ config = nm_config_get(); priv->ignore_carrier = nm_config_data_get_ignore_carrier_by_device(nm_config_get_data(config), self); + if (!priv->config_changed_id) { priv->config_changed_id = g_signal_connect(config, NM_CONFIG_SIGNAL_CONFIG_CHANGED, @@ -11440,6 +11549,8 @@ _dev_ipmanual_start(NMDevice *self) if (_prop_get_ipvx_routed_dns(self, AF_INET6) == NM_SETTING_IP_CONFIG_ROUTED_DNS_YES) { nm_l3_config_data_set_routed_dns(l3cd, AF_INET6, TRUE); } + + nm_l3_config_data_set_clat_config(l3cd, _prop_get_ipv4_clat(self, TRUE)); } if (!l3cd) { @@ -11748,8 +11859,9 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) gboolean hostname_is_fqdn; gboolean send_client_id; guint8 dscp; - gboolean dscp_explicit = FALSE; - gboolean ipv6_only_pref = FALSE; + gboolean dscp_explicit = FALSE; + gboolean ipv6_only_pref = FALSE; + gboolean ipv6_only_pref_auto = FALSE; client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr, &send_client_id); dscp = _prop_get_ipv4_dhcp_dscp(self, &dscp_explicit); @@ -11768,13 +11880,15 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip); } - if (_prop_get_ipv4_dhcp_ipv6_only_preferred(self)) { + if (_prop_get_ipv4_dhcp_ipv6_only_preferred(self, &ipv6_only_pref_auto)) { if (nm_streq0(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { _LOGI_ipdhcp( addr_family, "not requesting the \"IPv6-only preferred\" option because IPv6 is disabled"); } else { - _LOGD_ipdhcp(addr_family, "requesting the \"IPv6-only preferred\" option"); + _LOGD_ipdhcp(addr_family, + "requesting the \"IPv6-only preferred\" option (%s enabled)", + ipv6_only_pref_auto ? "automatically" : "explicitly"); ipv6_only_pref = TRUE; } } @@ -13585,13 +13699,21 @@ activate_stage3_ip_config(NMDevice *self) nm_device_get_ip_iface(self)); } - /* We currently will attach ports in the state change NM_DEVICE_STATE_IP_CONFIG above. - * Note that kernel changes the MTU of bond ports, so we want to commit the MTU - * afterwards! + /* + * Let's make sure MTU matches what is configured. The reason it's done at this + * precise location is twofold: + * + * (1) Attaching ports above might affect the MTU. + * + * We currently will attach ports in the state change NM_DEVICE_STATE_IP_CONFIG + * above. This might reset the MTU to something different from the bond controller + * and it might not be a working configuration. But it's what the user asked for. + * + * (2) When MTU is under 1280 IPv6 can not work. * - * This might reset the MTU to something different from the bond controller and - * it might not be a working configuration. But it's what the user asked for, so - * let's do it! */ + * Kernel will not expose sysctls, create or accept addresses that are needed for IPv6 + * configuration when the MTU is too small (under 1280). + */ _commit_mtu(self); if (!nm_device_managed_type_is_external(self) @@ -13600,12 +13722,6 @@ activate_stage3_ip_config(NMDevice *self) && !NM_IN_STRSET(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) { - /* Ensure the MTU makes sense. If it was below 1280 the kernel would not - * expose any ipv6 sysctls or allow presence of any addresses on the interface, - * including LL, which * would make it impossible to autoconfigure MTU to a - * correct value. */ - _commit_mtu(self); - /* Any method past this point requires an IPv6LL address. Use NM-controlled * IPv6LL if this is not an assumed connection, since assumed connections * will already have IPv6 set up. @@ -14583,6 +14699,12 @@ check_and_reapply_connection(NMDevice *self, reactivate_proxy_config(self); + /* Reapply may have changed the per-device L3 merge flags (ignore-auto-dns, + * ignore-auto-routes, never-default). Re-register the active l3cds so the + * refreshed flags reach l3cfg even for sources that are not restarted on + * reapply (e.g. DHCPv6 when the NDisc DHCP level is unchanged). */ + _dev_l3_register_l3cds(self, priv->l3cfg, TRUE, FALSE); + nm_device_l3cfg_commit( self, NM_FLAGS_HAS(reapply_flags, NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP) @@ -15588,35 +15710,15 @@ _dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_da /*****************************************************************************/ typedef struct { - NMLogDomain log_domain; - NMDevice *device; - gboolean ping_addresses_require_all; - GSource *watch; - GPid pid; - char *binary; - char *address; - guint deadline; -} PingOperation; - -static PingOperation * -ping_operation_new(NMDevice *self, - NMLogDomain log_domain, - const char *address, - const char *ping_binary, - guint ping_timeout, - gboolean ip_ping_addresses_require_all) -{ - PingOperation *ping_op = g_new0(PingOperation, 1); - - ping_op->device = self; - ping_op->log_domain = log_domain; - ping_op->address = g_strdup(address); - ping_op->binary = g_strdup(ping_binary); - ping_op->deadline = ping_timeout + 10; - ping_op->ping_addresses_require_all = ip_ping_addresses_require_all; + char *addr_str; + NMIPAddrTyped addr_bin; - return ping_op; -} + NMLogDomain log_domain; + NMDevice *device; + GCancellable *cancellable; + gboolean require_all; + CList ping_ops_lst; +} PingOperation; static void ip_check_pre_up(NMDevice *self) @@ -15640,188 +15742,154 @@ ip_check_pre_up(NMDevice *self) } static void -cleanup_ping_operation(PingOperation *ping_op) +ping_op_cleanup(PingOperation *ping_op) { - if (ping_op->watch) { - nm_clear_g_source_inst(&ping_op->watch); - } - - if (ping_op->pid) { - nm_utils_kill_child_async(ping_op->pid, - SIGTERM, - ping_op->log_domain, - "ping", - 1000, - NULL, - NULL); - ping_op->pid = 0; - } - - nm_clear_g_free(&ping_op->binary); - nm_clear_g_free(&ping_op->address); + nm_clear_g_cancellable(&ping_op->cancellable); + nm_clear_g_free(&ping_op->addr_str); + c_list_unlink_stale(&ping_op->ping_ops_lst); g_free(ping_op); } -static gboolean -spawn_ping_for_operation(NMDevice *self, PingOperation *ping_op) -{ - gs_free char *str_timeout = NULL; - gs_free char *tmp_str = NULL; - const char *args[] = {ping_op->binary, - "-I", - nm_device_get_ip_iface(self), - "-c", - "1", - "-w", - NULL, - ping_op->address, - NULL}; - gs_free_error GError *error = NULL; - gboolean ret; - - args[6] = str_timeout = g_strdup_printf("%u", ping_op->deadline); - - tmp_str = g_strjoinv(" ", (char **) args); - _LOGD(ping_op->log_domain, "ping: running '%s'", tmp_str); - - ret = g_spawn_async("/", - (char **) args, - NULL, - G_SPAWN_DO_NOT_REAP_CHILD, - NULL, - NULL, - &ping_op->pid, - &error); +static void +ping_cleanup(NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + PingOperation *ping_op; - if (ret) { - ping_op->watch = nm_g_child_watch_add_source(ping_op->pid, ip_check_ping_watch_cb, ping_op); - } else { - _LOGD(ping_op->log_domain, "ping: could not spawn %s: %s", ping_op->binary, error->message); + while ((ping_op = c_list_first_entry(&priv->ping_ops_lst_head, PingOperation, ping_ops_lst))) { + ping_op_cleanup(ping_op); } - return ret; + nm_clear_g_source_inst(&priv->ping_timeout); } -static gboolean -respawn_ping_cb(gpointer user_data) +static void +ping_host_cb(GObject *source, GAsyncResult *result, gpointer user_data) { - PingOperation *ping_op = (PingOperation *) user_data; - NMDevice *self = ping_op->device; - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMDevice *self; + NMDevicePrivate *priv; + PingOperation *ping_op = user_data; + gs_free_error GError *error = NULL; + gboolean success; + NMLogDomain log_domain; + gs_free char *addr_str = NULL; + + success = nm_utils_ping_host_finish(result, &error); + if (nm_utils_error_is_cancelled(error)) + return; - nm_clear_g_source_inst(&ping_op->watch); + self = NM_DEVICE(ping_op->device); + priv = NM_DEVICE_GET_PRIVATE(self); + log_domain = ping_op->log_domain; + addr_str = g_steal_pointer(&ping_op->addr_str); - if (!spawn_ping_for_operation(self, ping_op)) { - priv->ping_operations = g_list_remove(priv->ping_operations, ping_op); - cleanup_ping_operation(ping_op); + if (!success) { + /* it should never fail because we set an infinite timeout */ + nm_assert_not_reached(); + return; + } - if (g_list_length(priv->ping_operations) == 0) { - ip_check_pre_up(self); + if (ping_op->require_all) { + ping_op_cleanup(ping_op); + if (!c_list_is_empty(&priv->ping_ops_lst_head)) { + _LOGD(log_domain, + "ping: check on address %s succeeded, waiting for other addresses", + addr_str); + return; } } - return FALSE; + _LOGD(log_domain, "ping: check on address %s succeeded, continuing the activation", addr_str); + + ping_cleanup(self); + ip_check_pre_up(self); } static void -ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data) +ping_operation_start(NMDevice *self, + const char *addr_str, + NMIPAddrTyped *addr_bin, + gboolean require_all) { - PingOperation *ping_op = (PingOperation *) user_data; - NMDevice *self = ping_op->device; - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - gboolean success = FALSE; - - if (!ping_op->watch) - return; - - nm_clear_g_source_inst(&ping_op->watch); - ping_op->pid = 0; - - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) == 0) { - _LOGD(ping_op->log_domain, "ping: ping succeeded on %s", ping_op->address); - success = TRUE; - } else { - _LOGD(ping_op->log_domain, - "ping: ping failed with error code %d on %s", - WEXITSTATUS(status), - ping_op->address); - } + PingOperation *ping_op; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + NMIPAddrTyped addr_bin_local; + char buf[NM_INET_ADDRSTRLEN]; + int addr_family; + int ret; + + /* Exactly one of the two must be set */ + nm_assert(!!addr_str ^ !!addr_bin); + + /* Derive the string address from the binary and vice versa */ + if (addr_str) { + ret = nm_inet_parse_bin_full(AF_UNSPEC, + FALSE, + addr_str, + &addr_family, + &addr_bin_local.addr.addr_ptr); + nm_assert(ret); + addr_bin_local.addr_family = addr_family; + addr_bin = &addr_bin_local; } else { - _LOGD(ping_op->log_domain, - "ping: stopped unexpectedly with status %d on %s", - status, - ping_op->address); - } - - if (success) { - if (ping_op->ping_addresses_require_all) { - priv->ping_operations = g_list_remove(priv->ping_operations, ping_op); - if (g_list_length(priv->ping_operations) == 0) { - _LOGD(ping_op->log_domain, - "ping: ip-ping-addresses requires all, all ping checks on ip-ping-addresses " - "succeeded"); - if (priv->ping_timeout) - nm_clear_g_source_inst(&priv->ping_timeout); - ip_check_pre_up(self); - } - cleanup_ping_operation(ping_op); - } else { - nm_assert(priv->ping_operations); - - g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation); - priv->ping_operations = NULL; + nm_inet_ntop(addr_bin->addr_family, addr_bin->addr.addr_ptr, buf); + addr_str = buf; + } + + /* When pinging the gateway, the caller must ensure that the IP configuration is ready. + * For the ip-ping-addresses property, a valid connection always has may-fail=no for + * the families of all the target addresses. Thus, at this point the IP configuration + * must also be ready. */ + nm_assert(priv->ip_data_x[NM_IS_IPv4(addr_bin->addr_family)].state == NM_DEVICE_IP_STATE_READY); + + ping_op = g_new(PingOperation, 1); + *ping_op = (PingOperation) { + .device = self, + .cancellable = g_cancellable_new(), + .require_all = require_all, + .addr_bin = *addr_bin, + .addr_str = g_strdup(addr_str), + .log_domain = (addr_bin->addr_family == AF_INET) ? LOGD_IP4 : LOGD_IP6, + }; - if (priv->ping_timeout) - nm_clear_g_source_inst(&priv->ping_timeout); + /* Start the asynchronous ping operation */ + nm_utils_ping_host(ping_op->addr_bin, + nm_device_get_ip_ifindex(self), + 0, /* try forever */ + ping_op->cancellable, + ping_host_cb, + ping_op); - _LOGD(ping_op->log_domain, - "ping: ip-ping-addresses requires any, one ping check on ip-ping-addresses " - "succeeded"); - ip_check_pre_up(self); - } - } else { - /* If ping exited with an error it may have returned early, - * wait 1 second and restart it */ - ping_op->watch = nm_g_timeout_add_seconds_source(1, respawn_ping_cb, ping_op); - } + c_list_link_tail(&priv->ping_ops_lst_head, &ping_op->ping_ops_lst); } static gboolean ip_check_ping_timeout_cb(gpointer user_data) { - NMDevice *self = NM_DEVICE(user_data); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - - _LOGW(LOGD_DEVICE, "ping timeout: unreachable gateway or ip-ping-addresses"); - - if (priv->ping_operations) { - g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation); - priv->ping_operations = NULL; + NMDevice *self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + PingOperation *ping_op; + nm_auto_free_gstring GString *str = NULL; + + if (_LOGW_ENABLED(LOGD_DEVICE)) { + str = g_string_new(""); + c_list_for_each_entry (ping_op, &priv->ping_ops_lst_head, ping_ops_lst) { + if (str->len != 0) + g_string_append(str, ", "); + g_string_append(str, ping_op->addr_str); + } + _LOGW(LOGD_DEVICE, + "ping: the following addresses were not reachable within the timeout: %s", + str->str); } - if (priv->ping_timeout) - nm_clear_g_source_inst(&priv->ping_timeout); + ping_cleanup(self); ip_check_pre_up(self); return FALSE; } -static gboolean -start_ping(NMDevice *self, PingOperation *ping_op) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - - if (spawn_ping_for_operation(self, ping_op)) { - priv->ping_operations = g_list_append(priv->ping_operations, ping_op); - return TRUE; - } - - cleanup_ping_operation(ping_op); - return FALSE; -} - static void nm_device_start_ip_check(NMDevice *self) { @@ -15830,17 +15898,13 @@ nm_device_start_ip_check(NMDevice *self) NMSettingConnection *s_con; guint gw_ping_timeout = 0; guint ip_ping_timeout = 0; - const char *ping_binary = NULL; - char buf[NM_INET_ADDRSTRLEN]; - NMLogDomain log_domain = LOGD_IP4; - gboolean ip_ping_addresses_require_all; - gboolean ping_started = FALSE; + gboolean require_all; + NMIPAddrTyped addr_bin = {}; /* Shouldn't be any active ping here, since IP_CHECK happens after the * first IP method completes. Any subsequently completing IP method doesn't - * get checked. - */ - g_return_if_fail(priv->ping_operations == NULL); + * get checked. */ + g_return_if_fail(c_list_is_empty(&priv->ping_ops_lst_head)); g_return_if_fail(priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY || priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY); @@ -15849,100 +15913,71 @@ nm_device_start_ip_check(NMDevice *self) s_con = nm_connection_get_setting_connection(connection); g_assert(s_con); - gw_ping_timeout = nm_setting_connection_get_gateway_ping_timeout(s_con); - ip_ping_addresses_require_all = _prop_get_connection_ip_ping_addresses_require_all(self, s_con); - ip_ping_timeout = nm_setting_connection_get_ip_ping_timeout(s_con); + gw_ping_timeout = nm_setting_connection_get_gateway_ping_timeout(s_con); + ip_ping_timeout = nm_setting_connection_get_ip_ping_timeout(s_con); + require_all = _prop_get_connection_ip_ping_addresses_require_all(self, s_con); - buf[0] = '\0'; - if (gw_ping_timeout != 0 && ip_ping_timeout == 0) { + /* the timeouts are mutually exclusive */ + nm_assert(gw_ping_timeout == 0 || ip_ping_timeout == 0); + + if (gw_ping_timeout > 0) { const NMPObject *gw; const NML3ConfigData *l3cd; - _LOGD(LOGD_DEVICE, "starting ping gateway..."); - l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL; if (!l3cd) { /* pass */ } else if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY) { gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET); if (gw) { - nm_inet4_ntop(NMP_OBJECT_CAST_IP4_ROUTE(gw)->gateway, buf); - ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL); - log_domain = LOGD_IP4; + addr_bin.addr_family = AF_INET; + addr_bin.addr.addr4 = NMP_OBJECT_CAST_IP4_ROUTE(gw)->gateway; } } else if (priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY) { gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET6); if (gw) { - nm_inet6_ntop(&NMP_OBJECT_CAST_IP6_ROUTE(gw)->gateway, buf); - ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL); - log_domain = LOGD_IP6; + addr_bin.addr_family = AF_INET6; + addr_bin.addr.addr6 = NMP_OBJECT_CAST_IP6_ROUTE(gw)->gateway; } } - } - - if (buf[0]) { - PingOperation *ping_op = ping_operation_new(self, - log_domain, - buf, - ping_binary, - gw_ping_timeout, - ip_ping_addresses_require_all); - - if (start_ping(self, ping_op)) - ping_started = TRUE; - } - if (gw_ping_timeout == 0 && ip_ping_timeout != 0) { + if (addr_bin.addr_family != AF_UNSPEC) { + _LOGD(LOGD_DEVICE, + "starting ping on the IPv%c gateway with a %u seconds timeout", + nm_utils_addr_family_to_char(addr_bin.addr_family), + gw_ping_timeout); + ping_operation_start(self, NULL, &addr_bin, require_all); + } + } else if (ip_ping_timeout > 0) { const NML3ConfigData *l3cd; + GArray *ip_ping_addresses; + const char *const *strv; guint i; - GArray *ip_ping_addresses = _nm_setting_connection_get_ip_ping_addresses(s_con); - const char *const *strv = nm_strvarray_get_strv_notempty(ip_ping_addresses, NULL); - _LOGD(LOGD_DEVICE, "starting ping ip addresses..."); + ip_ping_addresses = _nm_setting_connection_get_ip_ping_addresses(s_con); + strv = nm_strvarray_get_strv_notnull(ip_ping_addresses, NULL); - l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL; + _LOGD(LOGD_DEVICE, + "starting ping on the ip-ping-addresses with a %u seconds timeout", + ip_ping_timeout); + l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL; if (l3cd) { for (i = 0; strv[i]; i++) { - const char *s = strv[i]; - struct in_addr ipv4_addr; - struct in6_addr ipv6_addr; - - if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY - && inet_pton(AF_INET, (const char *) s, &ipv4_addr)) { - ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL); - log_domain = LOGD_IP4; - } else if (priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY - && inet_pton(AF_INET6, (const char *) s, &ipv6_addr)) { - ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL); - log_domain = LOGD_IP6; - } else - continue; - - if (s[0]) { - PingOperation *ping_op = ping_operation_new(self, - log_domain, - s, - ping_binary, - ip_ping_timeout, - ip_ping_addresses_require_all); - - if (start_ping(self, ping_op)) - ping_started = TRUE; - } + ping_operation_start(self, strv[i], NULL, require_all); } } } - if (ping_started) { + if (c_list_is_empty(&priv->ping_ops_lst_head)) { + /* No ping operation in progress, advance to pre-up */ + ip_check_pre_up(self); + } else { priv->ping_timeout = - nm_g_timeout_add_seconds_source(gw_ping_timeout ? gw_ping_timeout : ip_ping_timeout, + nm_g_timeout_add_seconds_source(ip_ping_timeout > 0 ? ip_ping_timeout : gw_ping_timeout, ip_check_ping_timeout_cb, self); } - /* If no ping was started, just advance to pre_up. */ - else - ip_check_pre_up(self); } /*****************************************************************************/ @@ -17401,17 +17436,11 @@ _cancel_activation(NMDevice *self) _dispatcher_cleanup(self); - if (priv->ping_operations) { - g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation); - priv->ping_operations = NULL; - } - - if (priv->ping_timeout) - nm_clear_g_source_inst(&priv->ping_timeout); - _dev_ip_state_cleanup(self, AF_INET, FALSE); _dev_ip_state_cleanup(self, AF_INET6, FALSE); + ping_cleanup(self); + /* Break the activation chain */ activation_source_clear(self); } @@ -17968,6 +17997,14 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE); } break; + case NM_DEVICE_STATE_DEACTIVATING: + /* When deactivating, certain devices are removed/disconnected after the + * STATE_CHANGED signal is sent and before the DHCP release packet + * can be sent. To ensure the release packet is sent, we cleanup DHCP + * before the signal is emitted*/ + _dev_ipdhcpx_cleanup(self, AF_INET, TRUE, FALSE); + _dev_ipdhcpx_cleanup(self, AF_INET6, TRUE, FALSE); + break; case NM_DEVICE_STATE_DISCONNECTED: if (old_state > NM_DEVICE_STATE_DISCONNECTED) { /* Ensure devices that previously assumed a connection now have @@ -18017,6 +18054,7 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, (guint32) state, (guint32) old_state, (guint32) reason); + g_signal_emit(self, signals[STATE_CHANGED], 0, @@ -18167,13 +18205,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, break; } case NM_DEVICE_STATE_SECONDARIES: - if (priv->ping_operations) { - g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation); - priv->ping_operations = NULL; - } - if (priv->ping_timeout) - nm_clear_g_source_inst(&priv->ping_timeout); _LOGD(LOGD_DEVICE, "device entered SECONDARIES state"); + ping_cleanup(self); break; default: break; @@ -19153,14 +19186,14 @@ nm_device_get_supplicant_timeout(NMDevice *self) SUPPLICANT_DEFAULT_TIMEOUT); } -gboolean -nm_device_auth_retries_try_next(NMDevice *self) +static int +_device_get_auth_retries(NMDevice *self) { NMDevicePrivate *priv; NMSettingConnection *s_con; int auth_retries; - g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_DEVICE(self), 0); priv = NM_DEVICE_GET_PRIVATE(self); auth_retries = priv->auth_retries; @@ -19192,13 +19225,47 @@ nm_device_auth_retries_try_next(NMDevice *self) priv->auth_retries = auth_retries; } + return auth_retries; +} + +gboolean +nm_device_auth_retries_has_next(NMDevice *self) +{ + int auth_retries; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + auth_retries = _device_get_auth_retries(self); + + if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY) + return TRUE; + + if (auth_retries > 0) + return TRUE; + + return FALSE; +} + +gboolean +nm_device_auth_retries_try_next(NMDevice *self) +{ + NMDevicePrivate *priv; + int auth_retries; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + + priv = NM_DEVICE_GET_PRIVATE(self); + auth_retries = _device_get_auth_retries(self); + if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY) return TRUE; if (auth_retries <= 0) { nm_assert(auth_retries == 0); return FALSE; } + priv->auth_retries--; + return TRUE; } @@ -19838,6 +19905,8 @@ nm_device_init(NMDevice *self) priv->available_connections = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL); priv->ip6_saved_properties = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free); + c_list_init(&priv->ping_ops_lst_head); + priv->managed_type_ = NM_DEVICE_MANAGED_TYPE_EXTERNAL; /* If networking is already disabled at boot, we want to manage all devices * after re-enabling networking; hence, the initial state is MANAGED. */ diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h index 2f287953..c8069d7c 100644 --- a/src/core/devices/nm-device.h +++ b/src/core/devices/nm-device.h @@ -791,6 +791,7 @@ void nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_fr void nm_device_update_dynamic_ip_setup(NMDevice *self, const char *reason); guint nm_device_get_supplicant_timeout(NMDevice *self); +gboolean nm_device_auth_retries_has_next(NMDevice *self); gboolean nm_device_auth_retries_try_next(NMDevice *self); gboolean nm_device_hw_addr_get_cloned(NMDevice *self, diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c index fa6e2f9d..94b9a7d7 100644 --- a/src/core/devices/wifi/nm-device-iwd.c +++ b/src/core/devices/wifi/nm-device-iwd.c @@ -2270,6 +2270,37 @@ add_new: return NM_ACT_STAGE_RETURN_SUCCESS; } +static void +set_powersave(NMDevice *device) +{ + NMDeviceIwd *self = NM_DEVICE_IWD(device); + NMSettingWireless *s_wireless; + NMSettingWirelessPowersave val; + + s_wireless = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); + + g_return_if_fail(s_wireless); + + val = nm_setting_wireless_get_powersave(s_wireless); + if (val == NM_SETTING_WIRELESS_POWERSAVE_DEFAULT) { + val = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + "wifi.powersave", + device, + NM_SETTING_WIRELESS_POWERSAVE_IGNORE, + NM_SETTING_WIRELESS_POWERSAVE_ENABLE, + NM_SETTING_WIRELESS_POWERSAVE_IGNORE); + } + + _LOGT(LOGD_WIFI, "powersave is set to %u", (unsigned) val); + + if (val == NM_SETTING_WIRELESS_POWERSAVE_IGNORE) + return; + + nm_platform_wifi_set_powersave(nm_device_get_platform(device), + nm_device_get_ifindex(device), + val == NM_SETTING_WIRELESS_POWERSAVE_ENABLE); +} + static NMActStageReturn act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) { @@ -2297,6 +2328,8 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) goto out_fail; } + set_powersave(device); + /* With priv->iwd_autoconnect we have to let IWD handle retries for * infrastructure networks. IWD will not necessarily retry the same * network after a failure but it will likely go into an autoconnect diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c index b41ed5e1..b836c1e8 100644 --- a/src/core/devices/wifi/nm-device-wifi.c +++ b/src/core/devices/wifi/nm-device-wifi.c @@ -194,6 +194,9 @@ static void supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface, static void supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDeviceWifi *self); +static void supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface, + NMDeviceWifi *self); + static void periodic_update(NMDeviceWifi *self); static void ap_add_remove(NMDeviceWifi *self, @@ -631,6 +634,10 @@ supplicant_interface_acquire_cb(NMSupplicantManager *supplicant_manager, NM_SUPPLICANT_INTERFACE_PSK_MISMATCH, G_CALLBACK(supplicant_iface_notify_wpa_psk_mismatch_cb), self); + g_signal_connect(priv->sup_iface, + NM_SUPPLICANT_INTERFACE_SAE_MISMATCH, + G_CALLBACK(supplicant_iface_notify_wpa_sae_mismatch_cb), + self); _scan_notify_is_scanning(self); @@ -2191,21 +2198,16 @@ supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface, val_key = g_variant_lookup_value(credentials, "Key", G_VARIANT_TYPE_BYTESTRING); if (val_key) { - char psk[64]; + char psk[65]; array = g_variant_get_fixed_array(val_key, &psk_len, 1); - if (psk_len >= 8 && psk_len <= 63) { - memcpy(psk, array, psk_len); - psk[psk_len] = '\0'; - if (g_utf8_validate(psk, psk_len, NULL)) { - secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]", - NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, - NM_SETTING_WIRELESS_SECURITY_PSK, - psk); - g_variant_ref_sink(secrets); - } - } - if (!secrets) + if (nm_wifi_utils_wps_key_to_psk((const guint8 *) array, psk_len, &psk)) { + secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PSK, + psk); + g_variant_ref_sink(secrets); + } else _LOGW(LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK"); } @@ -2244,6 +2246,26 @@ wps_timeout_cb(gpointer user_data) return G_SOURCE_REMOVE; } +static gboolean +wifi_connection_is_new(NMDeviceWifi *self) +{ + NMDevice *device = NM_DEVICE(self); + NMActRequest *req; + NMSettingsConnection *connection; + guint64 timestamp = 0; + + req = nm_device_get_act_request(device); + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), TRUE); + + connection = nm_act_request_get_settings_connection(req); + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(connection), TRUE); + + if (nm_settings_connection_get_timestamp(connection, ×tamp) && timestamp != 0) + return FALSE; + + return TRUE; +} + static void wifi_secrets_get_secrets(NMDeviceWifi *self, const char *setting_name, @@ -2398,10 +2420,11 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi *self, NMSupplicantInterfaceState old_state, int disconnect_reason) { - NMDevice *device = NM_DEVICE(self); - NMActRequest *req; - const char *setting_name = NULL; - gboolean handled = FALSE; + NMDevice *device = NM_DEVICE(self); + NMActRequest *req; + const char *setting_name = NULL; + NMSecretAgentGetSecretsFlags secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION + | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; g_return_val_if_fail(new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE); @@ -2411,8 +2434,7 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi *self, req = nm_device_get_act_request(NM_DEVICE(self)); g_return_val_if_fail(req != NULL, FALSE); - if (need_new_8021x_secrets(self, old_state, &setting_name) - || need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) { + if (need_new_8021x_secrets(self, old_state, &setting_name)) { nm_act_request_clear_secrets(req); _LOGI(LOGD_DEVICE | LOGD_WIFI, @@ -2422,14 +2444,54 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi *self, nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); - wifi_secrets_get_secrets(self, - setting_name, - NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION - | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW); - handled = TRUE; + wifi_secrets_get_secrets(self, setting_name, secret_flags); + return TRUE; + } + + if (need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) { + nm_act_request_clear_secrets(req); + cleanup_association_attempt(self, TRUE); + + if (wifi_connection_is_new(self)) { + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) new connection disconnected during association, asking for " + "new key"); + nm_device_state_changed(device, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + wifi_secrets_get_secrets(self, setting_name, secret_flags); + return TRUE; + } + + if (!nm_device_auth_retries_try_next(device)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + return TRUE; + } + + if (nm_device_auth_retries_has_next(device)) { + secret_flags &= ~NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + _LOGI( + LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) disconnected during association, reauthenticating connection"); + } else { + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) disconnected during association, asking for new key"); + } + + nm_device_state_changed(device, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + wifi_secrets_get_secrets(self, setting_name, secret_flags); + + return TRUE; } - return handled; + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) disconnected during association, retrying connection"); + + return FALSE; } static gboolean @@ -2861,6 +2923,12 @@ supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDevi if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG) return; + if (!wifi_connection_is_new(self) && nm_device_auth_retries_has_next(device)) { + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) psk mismatch reported by supplicant, retrying connection"); + return; + } + _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) psk mismatch reported by supplicant, asking for new key"); @@ -2879,6 +2947,34 @@ supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDevi | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW); } +static void +supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface, NMDeviceWifi *self) +{ + NMDevice *device = NM_DEVICE(self); + NMActRequest *req; + const char *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + + if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG) + return; + + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi) SAE password mismatch reported by supplicant, asking for new key"); + + req = nm_device_get_act_request(NM_DEVICE(self)); + g_return_if_fail(req != NULL); + + nm_act_request_clear_secrets(req); + + cleanup_association_attempt(self, TRUE); + nm_device_state_changed(device, + NM_DEVICE_STATE_NEED_AUTH, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + wifi_secrets_get_secrets(self, + setting_name, + NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION + | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW); +} + /* * supplicant_connection_timeout_cb * @@ -3222,8 +3318,19 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) static void ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP *ap) { - guint32 a_freqs[] = {5180, 5200, 5220, 5745, 5765, 5785, 5805, 0}; - guint32 bg_freqs[] = {2412, 2437, 2462, 2472, 0}; + guint32 freqs_a[] = {5180, /* only U-NII-1 channels: non-DFS and available everywhere */ + 5200, + 5220, + 5240, + 0}; + guint32 freqs_bg[] = {2412, 2437, 2462, 2472, 0}; + guint32 freqs_6ghz[] = {5975, /* only U-NII-5 PSC channels, for better compatibility */ + 6055, + 6135, + 6215, + 6295, + 6375, + 0}; guint32 *rnd_freqs; guint rnd_freqs_len; NMDevice *device = NM_DEVICE(self); @@ -3234,7 +3341,7 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP guint l; nm_assert(ap); - nm_assert(NM_IN_STRSET(band, NULL, "a", "bg")); + nm_assert(NM_IN_STRSET(band, NULL, "a", "bg", "6GHz")); if (nm_wifi_ap_get_freq(ap)) return; @@ -3268,11 +3375,14 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP } if (nm_streq0(band, "a")) { - rnd_freqs = a_freqs; - rnd_freqs_len = G_N_ELEMENTS(a_freqs) - 1; + rnd_freqs = freqs_a; + rnd_freqs_len = G_N_ELEMENTS(freqs_a) - 1; + } else if (nm_streq0(band, "6GHz")) { + rnd_freqs = freqs_6ghz; + rnd_freqs_len = G_N_ELEMENTS(freqs_6ghz) - 1; } else { - rnd_freqs = bg_freqs; - rnd_freqs_len = G_N_ELEMENTS(bg_freqs) - 1; + rnd_freqs = freqs_bg; + rnd_freqs_len = G_N_ELEMENTS(freqs_bg) - 1; } /* shuffle the frequencies (inplace). The idea is to choose diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c index ceb954b7..3e758972 100644 --- a/src/core/devices/wifi/nm-wifi-ap.c +++ b/src/core/devices/wifi/nm-wifi-ap.c @@ -574,16 +574,6 @@ nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64 return str_buf; } -static guint -freq_to_band(guint32 freq) -{ - if (freq >= 4915 && freq <= 5825) - return 5; - else if (freq >= 2412 && freq <= 2484) - return 2; - return 0; -} - gboolean nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection) { @@ -631,12 +621,12 @@ nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection) band = nm_setting_wireless_get_band(s_wireless); if (band) { - guint ap_band = freq_to_band(priv->freq); + const char *ap_band = nm_wifi_freq_to_band_prop(priv->freq); - if (!strcmp(band, "a") && ap_band != 5) - return FALSE; - else if (!strcmp(band, "bg") && ap_band != 2) + if (!nm_streq(band, ap_band)) return FALSE; + + return TRUE; } channel = nm_setting_wireless_get_channel(s_wireless); diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c index 332352ab..2ee4ec2e 100644 --- a/src/core/devices/wifi/nm-wifi-utils.c +++ b/src/core/devices/wifi/nm-wifi-utils.c @@ -639,7 +639,7 @@ nm_wifi_utils_complete_connection(GBytes *ap_ssid, chan_valid = FALSE; } - band = nm_utils_wifi_freq_to_band(ap_freq); + band = nm_wifi_freq_to_band_prop(ap_freq); if (band) { g_object_set(s_wifi, NM_SETTING_WIRELESS_BAND, band, NULL); } else { @@ -890,6 +890,28 @@ nm_wifi_utils_is_manf_default_ssid(GBytes *ssid) return FALSE; } +/* Convert a WPS "Key" credential into a PSK string. The key is either an + * 8..63 character passphrase or a 64 character hexadecimal PSK. The actual + * WPA-PSK validity check is shared with nm_utils_wpa_psk_valid(). */ +gboolean +nm_wifi_utils_wps_key_to_psk(const guint8 *key, gsize key_len, char (*out_psk)[65]) +{ + if (key_len > 64) + return FALSE; + if (key_len < 64 && !g_utf8_validate((const char *) key, key_len, NULL)) + return FALSE; + + memcpy(*out_psk, key, key_len); + (*out_psk)[key_len] = '\0'; + + /* An embedded NUL would make nm_utils_wpa_psk_valid() see a truncated + * string, so reject it explicitly. */ + if (strlen(*out_psk) != key_len) + return FALSE; + + return nm_utils_wpa_psk_valid(*out_psk); +} + /* To be used for connections where the SSID has been validated before */ gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *connection, @@ -1929,3 +1951,19 @@ nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b) return a->source == b->source && a->sink == b->sink && a->port == b->port && a->has_audio == b->has_audio && a->has_uibc == b->has_uibc && a->has_cp == b->has_cp; } + +const char * +nm_wifi_freq_to_band_prop(guint32 freq) +{ + switch (nm_utils_wifi_freq_to_band(freq)) { + case NM_WIFI_BAND_2_4_GHZ: + return "bg"; + case NM_WIFI_BAND_5_GHZ: + return "a"; + case NM_WIFI_BAND_6_GHZ: + return "6GHz"; + default: + case NM_WIFI_BAND_UNKNOWN: + return NULL; + } +} diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h index 1d46a900..30920396 100644 --- a/src/core/devices/wifi/nm-wifi-utils.h +++ b/src/core/devices/wifi/nm-wifi-utils.h @@ -42,6 +42,8 @@ gboolean nm_wifi_utils_complete_connection(GBytes *ssid, gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid); +gboolean nm_wifi_utils_wps_key_to_psk(const guint8 *key, gsize key_len, char (*out_psk)[65]); + gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *connection, char **ssid, NMIwdNetworkSecurity *security); @@ -56,4 +58,6 @@ bool nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd); GBytes *nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd); bool nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b); +const char *nm_wifi_freq_to_band_prop(guint32 freq); + #endif /* __NM_WIFI_UTILS_H__ */ diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c index a52696ea..b50e2626 100644 --- a/src/core/devices/wifi/tests/test-devices-wifi.c +++ b/src/core/devices/wifi/tests/test-devices-wifi.c @@ -1459,6 +1459,50 @@ test_ssids_options_to_ptrarray(void) /*****************************************************************************/ +static void +test_wps_key_to_psk(void) +{ + static const struct { + const char *key; + gsize key_len; + gboolean valid; + } cases[] = { + {"12345678", 8, TRUE}, + {"supersecret123", 14, TRUE}, + {"caf\xc3\xa9_key", 9, TRUE}, + {"123456789012345678901234567890123456789012345678901234567890123", 63, TRUE}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 64, TRUE}, + {"0123456789ABCDEF0123456789abcdef0123456789abcdef0123456789abcdeF", 64, TRUE}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg", 64, FALSE}, + {"", 0, FALSE}, + {"1234567", 7, FALSE}, + {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0", 65, FALSE}, + {"abc\xff" + "def123", + 10, + FALSE}, + {"0123456789abcdef0123456789abcdef\0" + "23456789abcdef0123456789abcdef0", + 64, + FALSE}, + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS(cases); i++) { + char psk[65]; + gboolean ok; + + ok = nm_wifi_utils_wps_key_to_psk((const guint8 *) cases[i].key, cases[i].key_len, &psk); + g_assert_cmpint(ok, ==, cases[i].valid); + if (ok) { + g_assert_cmpmem(psk, cases[i].key_len, cases[i].key, cases[i].key_len); + g_assert_cmpint(psk[cases[i].key_len], ==, '\0'); + } + } +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -1605,5 +1649,7 @@ main(int argc, char **argv) g_test_add_func("/wifi/ssids_options_to_ptrarray", test_ssids_options_to_ptrarray); + g_test_add_func("/wifi/wps_key_to_psk", test_wps_key_to_psk); + return g_test_run(); } diff --git a/src/core/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c index 4a89f38e..b8438e3f 100644 --- a/src/core/devices/wwan/nm-modem-manager.c +++ b/src/core/devices/wwan/nm-modem-manager.c @@ -258,16 +258,7 @@ modm_handle_name_owner_changed(MMManager *modem_manager, GParamSpec *pspec, NMMo /* Available! */ g_free(name_owner); - /* Hack alert: GDBusObjectManagerClient won't signal neither 'object-added' - * nor 'object-removed' if it was created while there was no ModemManager in - * the bus. This hack avoids this issue until we get a GIO with the fix - * included... */ - modm_clear_manager(self); - modm_ensure_manager(self); - - /* Whenever GDBusObjectManagerClient is fixed, we can just do the following: - * modm_manager_available (self); - */ + modm_manager_available(self); } static void diff --git a/src/core/dhcp/README.next.md b/src/core/dhcp/README.next.md index 88fa6683..1e61e75a 100644 --- a/src/core/dhcp/README.next.md +++ b/src/core/dhcp/README.next.md @@ -54,17 +54,14 @@ complexity out of `NMDevice`. mean that it gave up. It will keep retrying, it's just that there is little hope of getting a new lease. This happens, when you try to run DHCP on a Layer3 link (WireGuard). There is little hope to succeed, but `NMDhcpClient` - (theoretically) will retry and may recover from this. Another example is when - we fail to start dhclient because it's not installed. In that case, we are not - optimistic to recover, however `NMDhcpDhclient` will retry (with backoff - timeout) and might still recover from this. For most cases, `NMDevice` will + (theoretically) will retry and may recover from this. For most cases, `NMDevice` will treat the no-lease cases the same, but in case of "bad" it might give up earlier. When a lease expires, that does not necessarily mean that we are now in a bad state. It might mean that the DHCP server is temporarily down, but we might recover from that easily. "bad" really means, something is wrong on our side -which prevents us from getting a lease. Also, imagine `dhclient` dies (we would +which prevents us from getting a lease. Also, imagine the DHCP client dies (we would try to restart, but assume that fails too), but we still have a valid lease, then possibly `NMDhcpClient` should still pretend all is good and we still have a lease until it expires. It may be we can recover before that happens. The @@ -88,8 +85,7 @@ optionally does ACD first, then configures the IP address first and calls different lease). With this, the above state "has a lease" has actually three flavors: "has a lease but not yet ACD probed" and "has a lease but accepted/declined" (but `NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED` gets only emitted -when we get the lease, not when we accept/decline it). With `dhclient`, when we -receive a lease, it means "has a lease but accepted" right away. +when we get the lease, not when we accept/decline it). - for IPv6 prefix delegation, there is also `needed_prefixes` and `NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED`. Currently `needed_prefixes` needs diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c index 18ad4024..c17e5544 100644 --- a/src/core/dhcp/nm-dhcp-client.c +++ b/src/core/dhcp/nm-dhcp-client.c @@ -266,9 +266,8 @@ nm_dhcp_client_create_options_dict(NMDhcpClient *self, gboolean static_keys) guint option = IS_IPv4 ? NM_DHCP_OPTION_DHCP4_CLIENT_ID : NM_DHCP_OPTION_DHCP6_CLIENT_ID; gs_free char *str = nm_dhcp_utils_duid_to_string(effective_client_id); - /* Note that for the nm-dhcp-helper based plugins (dhclient), the plugin - * may send the used client-id/DUID via the environment variables and - * overwrite them yet again. */ + /* Note that nm-dhcp-helper based plugins may send the used client-id/DUID + * via the environment variables and overwrite them yet again. */ nm_dhcp_option_take_option(options, static_keys, @@ -786,13 +785,12 @@ _nm_dhcp_client_notify(NMDhcpClient *self, NMDhcpClientEventType client_event_type, const NML3ConfigData *l3cd) { - NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); - GHashTable *options; - gboolean l3cd_changed; - NMOptionBool acd_state; - const int IS_IPv4 = NM_IS_IPv4(priv->config.addr_family); - nm_auto_unref_l3cd const NML3ConfigData *l3cd_merged = NULL; - char sbuf1[NM_HASH_OBFUSCATE_PTR_STR_BUF_SIZE]; + NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + GHashTable *options; + gboolean l3cd_changed; + NMOptionBool acd_state; + const int IS_IPv4 = NM_IS_IPv4(priv->config.addr_family); + char sbuf1[NM_HASH_OBFUSCATE_PTR_STR_BUF_SIZE]; nm_assert(NM_IN_SET(client_event_type, NM_DHCP_CLIENT_EVENT_TYPE_UNSPECIFIED, @@ -825,16 +823,6 @@ _nm_dhcp_client_notify(NMDhcpClient *self, if (client_event_type >= NM_DHCP_CLIENT_EVENT_TYPE_TIMEOUT) watch_cleanup(self); - if (!IS_IPv4 && l3cd) { - /* nm_dhcp_utils_merge_new_dhcp6_lease() relies on "life_starts" option - * for merging, which is only set by dhclient. Internal client never sets that, - * but it supports multiple IP addresses per lease. */ - if (nm_dhcp_utils_merge_new_dhcp6_lease(priv->l3cd_next, l3cd, &l3cd_merged)) { - _LOGD("lease merged with existing one"); - l3cd = nm_l3_config_data_seal(l3cd_merged); - } - } - if (l3cd) { nm_clear_g_source_inst(&priv->no_lease_timeout_source); } else @@ -1460,7 +1448,9 @@ nm_dhcp_client_schedule_ipv6_only_restart(NMDhcpClient *self, guint timeout) nm_assert(!priv->is_stopped); timeout = NM_MAX(priv->v4.ipv6_only_min_wait, timeout); - _LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds", timeout); + _LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds. Set " + "ipv4.dhcp-ipv6-only-preferred=no to force the use of IPv4 on this IPv6-mostly network", + timeout); nm_dhcp_client_stop(self, FALSE); nm_clear_g_source_inst(&priv->no_lease_timeout_source); @@ -1690,21 +1680,14 @@ maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant g_hash_table_insert(hash, g_strdup(key), str_value); - /* dhclient has no special labels for private dhcp options: it uses "unknown_xyz" - * labels for that. We need to identify those to alias them to our "private_xyz" - * format unused in the internal dchp plugins. - */ + /* "unknown_xyz" labels are aliased to our "private_xyz" format. */ if ((priv_opt_num = label_is_unknown_xyz(key)) > 0) { gs_free guint8 *check_val = NULL; char *hex_str = NULL; gsize len; - /* dhclient passes values from dhcp private options in its own "string" format: - * if the raw values are printable as ascii strings, it will pass the string - * representation; if the values are not printable as an ascii string, it will - * pass a string displaying the hex values (hex string). Try to enforce passing - * always an hex string, converting string representation if needed. - */ + /* Private options may arrive as printable ascii strings or as hex strings. + * Normalize to always use hex string format. */ check_val = nm_utils_hexstr2bin_alloc(str_value, FALSE, TRUE, ":", 0, &len); hex_str = nm_utils_bin2hexstr_full(check_val ?: (guint8 *) str_value, check_val ? len : strlen(str_value), diff --git a/src/core/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h index a7b6ae98..12f3a276 100644 --- a/src/core/dhcp/nm-dhcp-client.h +++ b/src/core/dhcp/nm-dhcp-client.h @@ -53,8 +53,8 @@ typedef enum _nm_packed { * As such, it's never officially in a non-recoverable state. * However, there are cases when it really looks like we won't * be able to get a lease. For example, if the underlying interface - * is layer 3 only, if we have no IPv6 link local address for a prolonged - * time, or if dhclient is not installed. + * is layer 3 only, or if we have no IPv6 link local address for a prolonged + * time. * But even these cases are potentially recoverable. This is only * a hint to the user (which they might ignore). * @@ -311,7 +311,6 @@ typedef struct { GType nm_dhcp_nettools_get_type(void); -extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient; extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd; extern const NMDhcpClientFactory _nm_dhcp_client_factory_internal; extern const NMDhcpClientFactory _nm_dhcp_client_factory_systemd; diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.c b/src/core/dhcp/nm-dhcp-dhclient-utils.c deleted file mode 100644 index 286f7aa1..00000000 --- a/src/core/dhcp/nm-dhcp-dhclient-utils.c +++ /dev/null @@ -1,763 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2011 Red Hat, Inc. - */ - -#include "src/core/nm-default-daemon.h" - -#include "nm-dhcp-dhclient-utils.h" - -#include <ctype.h> -#include <arpa/inet.h> -#include <net/if.h> -#include <linux/if_ether.h> - -#include "libnm-glib-aux/nm-dedup-multi.h" - -#include "nm-dhcp-utils.h" -#include "nm-utils.h" -#include "libnm-platform/nm-platform.h" -#include "NetworkManagerUtils.h" - -#define TIMEOUT_TAG "timeout " -#define RETRY_TAG "retry " -#define CLIENTID_TAG "send dhcp-client-identifier" - -#define HOSTNAME4_TAG "send host-name" -#define HOSTNAME4_FORMAT HOSTNAME4_TAG " \"%s\"; # added by NetworkManager" - -#define FQDN_TAG_PREFIX "send fqdn." -#define FQDN_TAG FQDN_TAG_PREFIX "fqdn" -#define FQDN_FORMAT FQDN_TAG " \"%s\"; # added by NetworkManager" - -#define ALSOREQ_TAG "also request " -#define REQ_TAG "request " - -#define MUDURLv4_DEF "option mudurl code 161 = text;\n" -#define MUDURLv4_FMT "send mudurl \"%s\";\n" - -#define MUDURLv6_DEF "option dhcp6.mudurl code 112 = text;\n" -#define MUDURLv6_FMT "send dhcp6.mudurl \"%s\";\n" - -static void -add_request(GPtrArray *array, const char *item) -{ - guint i; - - for (i = 0; i < array->len; i++) { - if (nm_streq(array->pdata[i], item)) - return; - } - g_ptr_array_add(array, g_strdup(item)); -} - -static gboolean -grab_request_options(GPtrArray *store, const char *line) -{ - gs_free const char **line_v = NULL; - gsize i; - - /* Grab each 'request' or 'also request' option and save for later */ - line_v = nm_strsplit_set(line, "\t ,"); - for (i = 0; line_v && line_v[i]; i++) { - const char *ss = nm_str_skip_leading_spaces(line_v[i]); - gsize l; - gboolean end = FALSE; - - if (!ss[0]) - continue; - if (ss[0] == ';') { - /* all done */ - return TRUE; - } - - if (!g_ascii_isalnum(ss[0])) - continue; - - l = strlen(ss); - - while (l > 0 && g_ascii_isspace(ss[l - 1])) { - ((char *) ss)[l - 1] = '\0'; - l--; - } - if (l > 0 && ss[l - 1] == ';') { - /* Remove the EOL marker */ - ((char *) ss)[l - 1] = '\0'; - end = TRUE; - } - - if (ss[0]) - add_request(store, ss); - - if (end) - return TRUE; - } - - return FALSE; -} - -static void -add_ip4_config(GString *str, - GBytes *client_id, - const char *hostname, - gboolean use_fqdn, - NMDhcpHostnameFlags hostname_flags) -{ - if (client_id) { - const char *p; - gsize l; - guint i; - - p = g_bytes_get_data(client_id, &l); - nm_assert(p); - - /* Allow type 0 (non-hardware address) to be represented as a string - * as long as all the characters are printable. - */ - for (i = 1; (p[0] == 0) && i < l; i++) { - if (!g_ascii_isprint(p[i]) || p[i] == '\\' || p[i] == '"') - break; - } - - g_string_append(str, CLIENTID_TAG " "); - if (l == 0) { - /* An empty value effectively unsets the client-id to avoid sending it */ - g_string_append(str, "\"\""); - } else if (i < l) { - /* Unprintable; convert to a hex string */ - for (i = 0; i < l; i++) { - if (i > 0) - g_string_append_c(str, ':'); - g_string_append_printf(str, "%02x", (guint8) p[i]); - } - } else { - /* Printable; just add to the line with type 0 */ - g_string_append_c(str, '"'); - g_string_append(str, "\\x00"); - g_string_append_len(str, p + 1, l - 1); - g_string_append_c(str, '"'); - } - g_string_append(str, "; # added by NetworkManager\n"); - } - - if (hostname) { - if (use_fqdn) { - g_string_append_printf(str, FQDN_FORMAT "\n", hostname); - - g_string_append_printf(str, - FQDN_TAG_PREFIX "encoded %s;\n", - (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED) ? "on" - : "off"); - - g_string_append_printf( - str, - FQDN_TAG_PREFIX "server-update %s;\n", - (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE) ? "on" : "off"); - - g_string_append_printf(str, - FQDN_TAG_PREFIX "no-client-update %s;\n", - (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE) ? "on" - : "off"); - } else - g_string_append_printf(str, HOSTNAME4_FORMAT "\n", hostname); - } - - g_string_append_c(str, '\n'); - - /* Define options for classless static routes */ - g_string_append( - str, - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"); - g_string_append(str, - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"); - /* Web Proxy Auto-Discovery option (bgo #368423) */ - g_string_append(str, "option wpad code 252 = string;\n"); - - g_string_append_c(str, '\n'); -} - -static void -add_hostname6(GString *str, const char *hostname, NMDhcpHostnameFlags hostname_flags) -{ - if (hostname) { - g_string_append_printf(str, FQDN_FORMAT "\n", hostname); - if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE) - g_string_append(str, FQDN_TAG_PREFIX "server-update on;\n"); - if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE) - g_string_append(str, FQDN_TAG_PREFIX "no-client-update on;\n"); - g_string_append_c(str, '\n'); - } -} - -static void -add_mud_url_config(GString *str, const char *mud_url, int addr_family) -{ - if (mud_url) { - if (addr_family == AF_INET) { - g_string_append(str, MUDURLv4_DEF); - g_string_append_printf(str, MUDURLv4_FMT, mud_url); - } else { - g_string_append(str, MUDURLv6_DEF); - g_string_append_printf(str, MUDURLv6_FMT, mud_url); - } - } -} - -static GBytes * -read_client_id(const char *str) -{ - gs_free char *s = NULL; - char *p; - int i = 0; - int j = 0; - gsize l; - - nm_assert(NM_STR_HAS_PREFIX(str, CLIENTID_TAG)); - str += NM_STRLEN(CLIENTID_TAG); - - if (!g_ascii_isspace(*str)) - return NULL; - while (g_ascii_isspace(*str)) - str++; - - if (*str == '"') { - /* Parse string literal with escape sequences */ - s = g_strdup(str + 1); - p = strrchr(s, '"'); - if (p) - *p = '\0'; - else - return NULL; - - if (!s[0]) - return NULL; - - while (s[i]) { - if (s[i] == '\\' && s[i + 1] == 'x' && g_ascii_isxdigit(s[i + 2]) - && g_ascii_isxdigit(s[i + 3])) { - s[j++] = (g_ascii_xdigit_value(s[i + 2]) << 4) + g_ascii_xdigit_value(s[i + 3]); - i += 4; - continue; - } - if (s[i] == '\\' && s[i + 1] >= '0' && s[i + 1] <= '7' && s[1 + 2] >= '0' - && s[i + 2] <= '7' && s[1 + 3] >= '0' && s[i + 3] <= '7') { - s[j++] = ((s[i + 1] - '0') << 6) + ((s[i + 2] - '0') << 3) + (s[i + 3] - '0'); - i += 4; - continue; - } - s[j++] = s[i++]; - } - return g_bytes_new_take(g_steal_pointer(&s), j); - } - - /* Otherwise, try to read a hexadecimal sequence */ - s = g_strdup(str); - g_strchomp(s); - l = strlen(s); - if (l > 0 && s[l - 1] == ';') - s[l - 1] = '\0'; - - return nm_utils_hexstr2bin(s); -} - -static gboolean -read_interface(const char *line, char *interface, guint size) -{ - gs_free char *dup = g_strdup(line + NM_STRLEN("interface")); - char *ptr = dup, *end; - - while (g_ascii_isspace(*ptr)) - ptr++; - - if (*ptr == '"') { - ptr++; - end = strchr(ptr, '"'); - if (!end) - return FALSE; - *end = '\0'; - } else { - end = strchr(ptr, ' '); - if (!end) - end = strchr(ptr, '{'); - if (!end) - return FALSE; - *end = '\0'; - } - - if (ptr[0] == '\0' || strlen(ptr) + 1 > size) - return FALSE; - - g_snprintf(interface, size, "%s", ptr); - - return TRUE; -} - -char * -nm_dhcp_dhclient_create_config(const char *interface, - int addr_family, - GBytes *client_id, - gboolean send_client_id, - const char *anycast_address, - const char *hostname, - guint32 timeout, - gboolean use_fqdn, - NMDhcpHostnameFlags hostname_flags, - const char *mud_url, - const char *const *reject_servers, - const char *orig_path, - const char *orig_contents, - GBytes **out_new_client_id) -{ - nm_auto_free_gstring GString *new_contents = NULL; - gs_unref_ptrarray GPtrArray *fqdn_opts = NULL; - gs_unref_ptrarray GPtrArray *reqs = NULL; - gboolean reset_reqlist = FALSE; - int i; - - g_return_val_if_fail(!anycast_address || nm_utils_hwaddr_valid(anycast_address, ETH_ALEN), - NULL); - g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NULL); - g_return_val_if_fail(!reject_servers || addr_family == AF_INET, NULL); - nm_assert(!out_new_client_id || !*out_new_client_id); - - new_contents = g_string_new(_("# Created by NetworkManager\n")); - reqs = g_ptr_array_new_full(5, g_free); - - if (orig_contents) { - gs_free const char **lines = NULL; - gsize line_i; - nm_auto_free_gstring GString *blocks_stack = NULL; - guint blocks_skip = 0; - gboolean in_alsoreq = FALSE; - gboolean in_req = FALSE; - char intf[IFNAMSIZ]; - - blocks_stack = g_string_new(NULL); - g_string_append_printf(new_contents, _("# Merged from %s\n\n"), orig_path); - intf[0] = '\0'; - - lines = nm_strsplit_set(orig_contents, "\n\r"); - for (line_i = 0; lines && lines[line_i]; line_i++) { - const char *line = nm_str_skip_leading_spaces(lines[line_i]); - const char *p; - - if (line[0] == '\0') - continue; - - g_strchomp((char *) line); - - p = line; - if (in_req) { - /* pass */ - } else if (strchr(p, '{')) { - if (NM_STR_HAS_PREFIX(p, "lease") || NM_STR_HAS_PREFIX(p, "alias") - || NM_STR_HAS_PREFIX(p, "interface") || NM_STR_HAS_PREFIX(p, "pseudo")) { - /* skip over these blocks, except 'interface' when it - * matches the current interface */ - blocks_skip++; - g_string_append_c(blocks_stack, 'b'); - if (!intf[0] && NM_STR_HAS_PREFIX(p, "interface")) { - if (read_interface(p, intf, sizeof(intf))) - continue; - } - } else { - /* allow other blocks (conditionals) */ - if (!strchr(p, '}')) /* '} else {' */ - g_string_append_c(blocks_stack, 'c'); - } - } else if (strchr(p, '}')) { - if (blocks_stack->len > 0) { - if (blocks_stack->str[blocks_stack->len - 1] == 'b') { - g_string_truncate(blocks_stack, blocks_stack->len - 1); - nm_assert(blocks_skip > 0); - blocks_skip--; - intf[0] = '\0'; - continue; - } - g_string_truncate(blocks_stack, blocks_stack->len - 1); - } - } - - if (blocks_skip > 0 && !intf[0]) - continue; - - if (intf[0] && !nm_streq(intf, interface)) - continue; - - /* Some timing parameters in dhclient should not be imported (timeout, retry). - * The retry parameter will be simply not used as we will exit on first failure. - * The timeout one instead may affect NetworkManager behavior: if the timeout - * elapses before dhcp-timeout dhclient will report failure and cause NM to - * fail the dhcp process before dhcp-timeout. So, always skip importing timeout - * as we will need to add one greater than dhcp-timeout. - */ - if (NM_STR_HAS_PREFIX(p, TIMEOUT_TAG) || NM_STR_HAS_PREFIX(p, RETRY_TAG)) - continue; - - if (NM_STR_HAS_PREFIX(p, CLIENTID_TAG)) { - /* Skip "dhcp-client-id" if the connection has defined a custom one or "none" */ - if (client_id || !send_client_id) - continue; - - /* Otherwise, capture and return the existing client id */ - if (out_new_client_id) - nm_clear_pointer(out_new_client_id, g_bytes_unref); - NM_SET_OUT(out_new_client_id, read_client_id(p)); - /* fall-through. We keep the line... */ - } - - /* Override config file hostname and use one from the connection */ - if (hostname) { - if (NM_STR_HAS_PREFIX(p, HOSTNAME4_TAG)) - continue; - if (NM_STR_HAS_PREFIX(p, FQDN_TAG)) - continue; - } - - /* To let user's FQDN options (except "fqdn.fqdn") override the - * default ones set by NM, add them later - */ - if (NM_STR_HAS_PREFIX(p, FQDN_TAG_PREFIX)) { - if (!fqdn_opts) - fqdn_opts = g_ptr_array_new_full(5, g_free); - g_ptr_array_add(fqdn_opts, g_strdup(p + NM_STRLEN(FQDN_TAG_PREFIX))); - continue; - } - - /* Ignore 'script' since we pass our own */ - if (g_str_has_prefix(p, "script ")) - continue; - - /* Check for "request" */ - if (NM_STR_HAS_PREFIX(p, REQ_TAG)) { - in_req = TRUE; - p += NM_STRLEN(REQ_TAG); - g_ptr_array_set_size(reqs, 0); - reset_reqlist = TRUE; - } - - /* Save all request options for later use */ - if (in_req) { - in_req = !grab_request_options(reqs, p); - continue; - } - - /* Check for "also require" */ - if (NM_STR_HAS_PREFIX(p, ALSOREQ_TAG)) { - in_alsoreq = TRUE; - p += NM_STRLEN(ALSOREQ_TAG); - } - - if (in_alsoreq) { - in_alsoreq = !grab_request_options(reqs, p); - continue; - } - - /* Existing configuration line is OK, add it to new configuration */ - g_string_append(new_contents, line); - g_string_append_c(new_contents, '\n'); - } - } else - g_string_append_c(new_contents, '\n'); - - /* ensure dhclient timeout is greater than dhcp-timeout: as dhclient timeout default value is - * 60 seconds, we need this only if dhcp-timeout is greater than 60. - */ - if (timeout >= 60) { - timeout = timeout < G_MAXINT32 ? timeout + 1 : G_MAXINT32; - g_string_append_printf(new_contents, "timeout %u;\n", timeout); - } - - add_mud_url_config(new_contents, mud_url, addr_family); - - if (reject_servers && reject_servers[0]) { - g_string_append(new_contents, "reject "); - for (i = 0; reject_servers[i]; i++) { - if (i != 0) - g_string_append(new_contents, ", "); - g_string_append(new_contents, reject_servers[i]); - } - g_string_append(new_contents, ";\n"); - } - - if (addr_family == AF_INET) { - nm_auto_unref_bytes GBytes *client_id_none = NULL; - client_id = send_client_id ? client_id : (client_id_none = g_bytes_new_static("", 0)); - add_ip4_config(new_contents, client_id, hostname, use_fqdn, hostname_flags); - add_request(reqs, "rfc3442-classless-static-routes"); - add_request(reqs, "ms-classless-static-routes"); - add_request(reqs, "static-routes"); - add_request(reqs, "wpad"); - add_request(reqs, "ntp-servers"); - add_request(reqs, "root-path"); - } else { - add_hostname6(new_contents, hostname, hostname_flags); - add_request(reqs, "dhcp6.name-servers"); - add_request(reqs, "dhcp6.domain-search"); - - /* FIXME: internal client does not support requesting client-id option. Does this even work? */ - add_request(reqs, "dhcp6.client-id"); - } - - if (reset_reqlist) - g_string_append(new_contents, "request; # override dhclient defaults\n"); - /* And add it to the dhclient configuration */ - for (i = 0; i < reqs->len; i++) - g_string_append_printf(new_contents, "also request %s;\n", (char *) reqs->pdata[i]); - - if (fqdn_opts) { - for (i = 0; i < fqdn_opts->len; i++) { - const char *t = fqdn_opts->pdata[i]; - - if (i == 0) - g_string_append_printf(new_contents, "\n# FQDN options from %s\n", orig_path); - g_string_append_printf(new_contents, FQDN_TAG_PREFIX "%s\n", t); - } - } - - g_string_append_c(new_contents, '\n'); - - if (anycast_address) { - g_string_append_printf(new_contents, - "interface \"%s\" {\n" - " initial-interval 1; \n" - " anycast-mac ethernet %s;\n" - "}\n", - interface, - anycast_address); - } - - return g_string_free(g_steal_pointer(&new_contents), FALSE); -} - -/* In the lease file, dhclient will write "option dhcp6.client-id $HEXSTR". This - * function does the same. */ -static char * -nm_dhcp_dhclient_escape_duid_as_hex(GBytes *duid) -{ - const guint8 *s; - gsize len; - - nm_assert(duid); - - s = g_bytes_get_data(duid, &len); - return nm_utils_bin2hexstr_fuller(s, len, ':', FALSE, FALSE, NULL); -} - -/* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */ -char * -nm_dhcp_dhclient_escape_duid(GBytes *duid) -{ - char *escaped; - const guint8 *s, *s0; - gsize len; - char *d; - - g_return_val_if_fail(duid, NULL); - - s0 = g_bytes_get_data(duid, &len); - s = s0; - - d = escaped = g_malloc((len * 4) + 1); - while (s < (s0 + len)) { - if (!g_ascii_isprint(*s)) { - *d++ = '\\'; - *d++ = '0' + ((*s >> 6) & 0x7); - *d++ = '0' + ((*s >> 3) & 0x7); - *d++ = '0' + (*s++ & 0x7); - } else if (*s == '"' || *s == '\'' || *s == '$' || *s == '`' || *s == '\\' || *s == '|' - || *s == '&') { - *d++ = '\\'; - *d++ = *s++; - } else - *d++ = *s++; - } - *d++ = '\0'; - return escaped; -} - -static gboolean -isoctal(const guint8 *p) -{ - return (p[0] >= '0' && p[0] <= '3' && p[1] >= '0' && p[1] <= '7' && p[2] >= '0' && p[2] <= '7'); -} - -GBytes * -nm_dhcp_dhclient_unescape_duid(const char *duid) -{ - GByteArray *unescaped; - const guint8 *p = (const guint8 *) duid; - guint i, len; - guint8 octal; - - /* FIXME: it's wrong to have an "unescape-duid" function. dhclient - * defines a file format with escaping. So we need a general unescape - * function that can handle dhclient syntax. */ - - len = strlen(duid); - unescaped = g_byte_array_sized_new(len); - for (i = 0; i < len; i++) { - if (p[i] == '\\') { - i++; - if (isdigit(p[i])) { - /* Octal escape sequence */ - if (i + 2 >= len || !isoctal(p + i)) - goto error; - octal = ((p[i] - '0') << 6) + ((p[i + 1] - '0') << 3) + (p[i + 2] - '0'); - g_byte_array_append(unescaped, &octal, 1); - i += 2; - } else { - /* FIXME: don't warn on untrusted data. Either signal an error, or accept - * it silently. */ - - /* One of ", ', $, `, \, |, or & */ - g_warn_if_fail(p[i] == '"' || p[i] == '\'' || p[i] == '$' || p[i] == '`' - || p[i] == '\\' || p[i] == '|' || p[i] == '&'); - g_byte_array_append(unescaped, &p[i], 1); - } - } else - g_byte_array_append(unescaped, &p[i], 1); - } - - return g_byte_array_free_to_bytes(unescaped); - -error: - g_byte_array_free(unescaped, TRUE); - return NULL; -} - -#define DEFAULT_DUID_PREFIX "default-duid \"" - -/* Beware: @error may be unset even if the function returns %NULL. */ -GBytes * -nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error) -{ - gs_free char *contents = NULL; - gs_free const char **contents_v = NULL; - gsize i; - - if (!g_file_test(leasefile, G_FILE_TEST_EXISTS)) - return NULL; - - if (!g_file_get_contents(leasefile, &contents, NULL, error)) - return NULL; - - contents_v = nm_strsplit_set(contents, "\n\r"); - for (i = 0; contents_v && contents_v[i]; i++) { - const char *p = nm_str_skip_leading_spaces(contents_v[i]); - GBytes *duid; - - if (!NM_STR_HAS_PREFIX(p, DEFAULT_DUID_PREFIX)) - continue; - - p += NM_STRLEN(DEFAULT_DUID_PREFIX); - - g_strchomp((char *) p); - - if (!NM_STR_HAS_SUFFIX(p, "\";")) - continue; - - ((char *) p)[strlen(p) - 2] = '\0'; - - duid = nm_dhcp_dhclient_unescape_duid(p); - if (duid) - return duid; - } - - return NULL; -} - -gboolean -nm_dhcp_dhclient_save_duid(const char *leasefile, - GBytes *duid, - gboolean enforce_duid, - GError **error) -{ - gs_free char *escaped_duid = NULL; - gs_free const char **lines = NULL; - nm_auto_free_gstring GString *s = NULL; - const char *const *iter; - gs_free char *conflicting_duid_line = NULL; - gs_free char *contents = NULL; - gsize contents_len = 0; - - g_return_val_if_fail(leasefile != NULL, FALSE); - - if (!duid) { - nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "missing duid"); - g_return_val_if_reached(FALSE); - } - - escaped_duid = nm_dhcp_dhclient_escape_duid(duid); - nm_assert(escaped_duid); - - if (g_file_test(leasefile, G_FILE_TEST_EXISTS)) { - if (!g_file_get_contents(leasefile, &contents, &contents_len, error)) { - g_prefix_error(error, "failed to read lease file %s: ", leasefile); - return FALSE; - } - - lines = nm_strsplit_set_with_empty(contents, "\n"); - } - - s = g_string_sized_new(contents_len + 50); - g_string_append_printf(s, DEFAULT_DUID_PREFIX "%s\";\n", escaped_duid); - - /* Preserve existing leasefile contents */ - if (lines) { - for (iter = lines; *iter; iter++) { - const char *str = *iter; - const char *l; - gboolean ends_with_r; - gsize l_len; - gsize prefix_len; - - l = nm_str_skip_leading_spaces(str); - l_len = strlen(l); - prefix_len = l - str; - - ends_with_r = l_len > 0 && l[l_len - 1u] == '\r'; - if (ends_with_r) { - ((char *) l)[--l_len] = '\0'; - } - - if (NM_STR_HAS_PREFIX(l, DEFAULT_DUID_PREFIX)) { - /* We always add our line on top. This line can be skipped. */ - continue; - } - - if (enforce_duid & NM_STR_HAS_PREFIX(l, "option dhcp6.client-id ")) { - /* we want to use our duid. Skip the per-lease client-id. */ - if (!conflicting_duid_line) { - gs_free char *duid_hex = nm_dhcp_dhclient_escape_duid_as_hex(duid); - - conflicting_duid_line = g_strdup_printf("option dhcp6.client-id %s;", duid_hex); - } - /* We adjust the duid line and set what we want. */ - l = conflicting_duid_line; - } - - g_string_append_len(s, str, prefix_len); - g_string_append(s, l); - if (ends_with_r) { - g_string_append_c(s, '\r'); - g_string_append_c(s, '\n'); - } else if ((iter[1]) != NULL) { - /* avoid to add an extra '\n' at the end of file */ - g_string_append_c(s, '\n'); - } - } - } - - if (contents && strlen(contents) == contents_len && nm_streq(contents, s->str)) { - /* The file is already as we want it. We are done. */ - return TRUE; - } - - if (!g_file_set_contents(leasefile, s->str, -1, error)) { - g_prefix_error(error, "failed to set DUID in lease file %s: ", leasefile); - return FALSE; - } - - return TRUE; -} diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.h b/src/core/dhcp/nm-dhcp-dhclient-utils.h deleted file mode 100644 index 34b26175..00000000 --- a/src/core/dhcp/nm-dhcp-dhclient-utils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2010 Red Hat, Inc. - */ - -#ifndef __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ -#define __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ - -#include "nm-setting-ip4-config.h" -#include "nm-setting-ip6-config.h" - -char *nm_dhcp_dhclient_create_config(const char *interface, - int addr_family, - GBytes *client_id, - gboolean send_client_id, - const char *anycast_addr, - const char *hostname, - guint32 timeout, - gboolean use_fqdn, - NMDhcpHostnameFlags hostname_flags, - const char *mud_url, - const char *const *reject_servers, - const char *orig_path, - const char *orig_contents, - GBytes **out_new_client_id); - -char *nm_dhcp_dhclient_escape_duid(GBytes *duid); - -GBytes *nm_dhcp_dhclient_unescape_duid(const char *duid); - -GBytes *nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error); - -gboolean nm_dhcp_dhclient_save_duid(const char *leasefile, - GBytes *duid, - gboolean enforce_duid, - GError **error); - -#endif /* __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ */ diff --git a/src/core/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c deleted file mode 100644 index 7e00599c..00000000 --- a/src/core/dhcp/nm-dhcp-dhclient.c +++ /dev/null @@ -1,741 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2005 - 2012 Red Hat, Inc. - */ - -#include <config.h> -#define __CONFIG_H__ - -#define _XOPEN_SOURCE -#include <time.h> -#undef _XOPEN_SOURCE - -#include "src/core/nm-default-daemon.h" - -#if WITH_DHCLIENT - -#include <stdlib.h> -#include <unistd.h> -#include <stdio.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <ctype.h> - -#include "libnm-glib-aux/nm-dedup-multi.h" - -#include "nm-utils.h" -#include "nm-dhcp-dhclient-utils.h" -#include "nm-dhcp-manager.h" -#include "NetworkManagerUtils.h" -#include "nm-dhcp-listener.h" -#include "nm-dhcp-client-logging.h" - -/*****************************************************************************/ - -static const char * -_addr_family_to_path_part(int addr_family) -{ - nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); - return (addr_family == AF_INET6) ? "6" : ""; -} - -/*****************************************************************************/ - -#define NM_TYPE_DHCP_DHCLIENT (nm_dhcp_dhclient_get_type()) -#define NM_DHCP_DHCLIENT(obj) \ - (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient)) -#define NM_DHCP_DHCLIENT_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass)) -#define NM_IS_DHCP_DHCLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCLIENT)) -#define NM_IS_DHCP_DHCLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCLIENT)) -#define NM_DHCP_DHCLIENT_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass)) - -typedef struct _NMDhcpDhclient NMDhcpDhclient; -typedef struct _NMDhcpDhclientClass NMDhcpDhclientClass; - -static GType nm_dhcp_dhclient_get_type(void); - -/*****************************************************************************/ - -typedef struct { - char *conf_file; - const char *def_leasefile; - char *lease_file; - char *pid_file; - NMDhcpListener *dhcp_listener; -} NMDhcpDhclientPrivate; - -struct _NMDhcpDhclient { - NMDhcpClient parent; - NMDhcpDhclientPrivate _priv; -}; - -struct _NMDhcpDhclientClass { - NMDhcpClientClass parent; -}; - -G_DEFINE_TYPE(NMDhcpDhclient, nm_dhcp_dhclient, NM_TYPE_DHCP_CLIENT) - -#define NM_DHCP_DHCLIENT_GET_PRIVATE(self) \ - _NM_GET_PRIVATE(self, NMDhcpDhclient, NM_IS_DHCP_DHCLIENT) - -/*****************************************************************************/ - -static GBytes *read_duid_from_lease(NMDhcpDhclient *self); - -/*****************************************************************************/ - -static const char * -nm_dhcp_dhclient_get_path(void) -{ - return nm_utils_find_helper("dhclient", DHCLIENT_PATH, NULL); -} - -/** - * get_dhclient_leasefile(): - * @addr_family: AF_INET or AF_INET6 - * @iface: the interface name of the device on which DHCP will be done - * @uuid: the connection UUID to which the returned lease should belong - * @out_preferred_path: on return, the "most preferred" leasefile path - * - * Returns the path of an existing leasefile (if any) for this interface and - * connection UUID. Also returns the "most preferred" leasefile path, which - * may be different than any found leasefile. - * - * Returns: an existing leasefile, or %NULL if no matching leasefile could be found - */ -static char * -get_dhclient_leasefile(int addr_family, - const char *iface, - const char *uuid, - char **out_preferred_path) -{ - gs_free char *path = NULL; - - if (nm_dhcp_utils_get_leasefile_path(addr_family, "dhclient", iface, uuid, &path)) { - NM_SET_OUT(out_preferred_path, g_strdup(path)); - return g_steal_pointer(&path); - } - - NM_SET_OUT(out_preferred_path, g_steal_pointer(&path)); - - /* If the leasefile we're looking for doesn't exist yet in the new location - * (eg, /var/lib/NetworkManager) then look in old locations to maintain - * backwards compatibility with external tools (like dracut) that put - * leasefiles there. - */ - - /* Old Debian, SUSE, and Mandriva location */ - g_free(path); - path = g_strdup_printf(LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease", - _addr_family_to_path_part(addr_family), - uuid, - iface); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return g_steal_pointer(&path); - - /* Old Red Hat and Fedora location */ - g_free(path); - path = g_strdup_printf(LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease", - _addr_family_to_path_part(addr_family), - uuid, - iface); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return g_steal_pointer(&path); - - /* Fail */ - return NULL; -} - -static char * -find_existing_config(NMDhcpDhclient *self, int addr_family, const char *iface, const char *uuid) -{ - char *path; - - /* NetworkManager-overridden configuration can be used to ship DHCP config - * with NetworkManager itself. It can be uuid-specific, device-specific - * or generic. - */ - if (uuid) { - path = g_strdup_printf(NMCONFDIR "/dhclient%s-%s.conf", - _addr_family_to_path_part(addr_family), - uuid); - _LOGD("looking for existing config %s", path); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return path; - g_free(path); - } - - path = g_strdup_printf(NMCONFDIR "/dhclient%s-%s.conf", - _addr_family_to_path_part(addr_family), - iface); - _LOGD("looking for existing config %s", path); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return path; - g_free(path); - - path = g_strdup_printf(NMCONFDIR "/dhclient%s.conf", _addr_family_to_path_part(addr_family)); - _LOGD("looking for existing config %s", path); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return path; - g_free(path); - - /* Distribution's dhclient configuration is used so that we can use - * configuration shipped with dhclient (if any). - * - * This replaces conditional compilation based on distribution name. Fedora - * and Debian store the configs in /etc/dhcp while upstream defaults to /etc - * which is then used by many other distributions. Some distributions - * (including Fedora) don't even provide a default configuration file. - */ - path = g_strdup_printf(SYSCONFDIR "/dhcp/dhclient%s-%s.conf", - _addr_family_to_path_part(addr_family), - iface); - _LOGD("looking for existing config %s", path); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return path; - g_free(path); - - path = g_strdup_printf(SYSCONFDIR "/dhclient%s-%s.conf", - _addr_family_to_path_part(addr_family), - iface); - _LOGD("looking for existing config %s", path); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return path; - g_free(path); - - path = - g_strdup_printf(SYSCONFDIR "/dhcp/dhclient%s.conf", _addr_family_to_path_part(addr_family)); - _LOGD("looking for existing config %s", path); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return path; - g_free(path); - - path = g_strdup_printf(SYSCONFDIR "/dhclient%s.conf", _addr_family_to_path_part(addr_family)); - _LOGD("looking for existing config %s", path); - if (g_file_test(path, G_FILE_TEST_EXISTS)) - return path; - g_free(path); - - return NULL; -} - -/* NM provides interface-specific options; thus the same dhclient config - * file cannot be used since DHCP transactions can happen in parallel. - * Since some distros don't have default per-interface dhclient config files, - * read their single config file and merge that into a custom per-interface - * config file along with the NM options. - */ -static char * -create_dhclient_config(NMDhcpDhclient *self, - int addr_family, - const char *iface, - const char *uuid, - GBytes *client_id, - gboolean send_client_id, - const char *anycast_address, - const char *hostname, - guint32 timeout, - gboolean use_fqdn, - NMDhcpHostnameFlags hostname_flags, - const char *mud_url, - const char *const *reject_servers, - GBytes **out_new_client_id) -{ - gs_free char *orig_path = NULL; - gs_free char *orig_content = NULL; - char *new_path = NULL; - gs_free char *new_content = NULL; - GError *error = NULL; - - g_return_val_if_fail(iface != NULL, NULL); - - new_path = g_strdup_printf(NMSTATEDIR "/dhclient%s-%s.conf", - _addr_family_to_path_part(addr_family), - iface); - _LOGD("creating composite dhclient config %s", new_path); - - orig_path = find_existing_config(self, addr_family, iface, uuid); - if (orig_path) - _LOGD("merging existing dhclient config %s", orig_path); - else - _LOGD("no existing dhclient configuration to merge"); - - if (orig_path && g_file_test(orig_path, G_FILE_TEST_EXISTS)) { - if (!g_file_get_contents(orig_path, &orig_content, NULL, &error)) { - _LOGW("error reading dhclient configuration %s: %s", orig_path, error->message); - g_error_free(error); - } - } - - new_content = nm_dhcp_dhclient_create_config(iface, - addr_family, - client_id, - send_client_id, - anycast_address, - hostname, - timeout, - use_fqdn, - hostname_flags, - mud_url, - reject_servers, - orig_path, - orig_content, - out_new_client_id); - nm_assert(new_content); - - if (!g_file_set_contents(new_path, new_content, -1, &error)) { - _LOGW("error creating dhclient configuration: %s", error->message); - g_error_free(error); - g_free(new_path); - return NULL; - } - - return new_path; -} - -static gboolean -dhclient_start(NMDhcpClient *client, - gboolean set_mode, - gboolean release, - gboolean set_duid, - pid_t *out_pid, - GError **error) -{ - NMDhcpDhclient *self = NM_DHCP_DHCLIENT(client); - NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); - gs_unref_ptrarray GPtrArray *argv = NULL; - pid_t pid; - gs_free_error GError *local = NULL; - const char *iface; - const char *uuid; - const char *system_bus_address; - const char *dhclient_path; - char *binary_name; - gs_free char *cmd_str = NULL; - gs_free char *pid_file = NULL; - gs_free char *system_bus_address_env = NULL; - gs_free char *preferred_leasefile_path = NULL; - int addr_family; - const NMDhcpClientConfig *client_config; - char pd_length_str[16]; - - g_return_val_if_fail(!priv->pid_file, FALSE); - client_config = nm_dhcp_client_get_config(client); - addr_family = client_config->addr_family; - - NM_SET_OUT(out_pid, 0); - - dhclient_path = nm_dhcp_dhclient_get_path(); - if (!dhclient_path) { - nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "dhclient binary not found"); - return FALSE; - } - - iface = client_config->iface; - uuid = client_config->uuid; - - pid_file = g_strdup_printf(NMRUNDIR "/dhclient%s-%s.pid", - _addr_family_to_path_part(addr_family), - iface); - - /* Kill any existing dhclient from the pidfile */ - binary_name = g_path_get_basename(dhclient_path); - nm_dhcp_client_stop_existing(pid_file, binary_name); - g_free(binary_name); - - if (release) { - /* release doesn't use the pidfile after killing an old client */ - nm_clear_g_free(&pid_file); - } - - g_free(priv->lease_file); - priv->lease_file = get_dhclient_leasefile(addr_family, iface, uuid, &preferred_leasefile_path); - nm_assert(preferred_leasefile_path); - if (!priv->lease_file) { - /* No existing leasefile, dhclient will create one at the preferred path */ - priv->lease_file = g_steal_pointer(&preferred_leasefile_path); - } else if (!nm_streq0(priv->lease_file, preferred_leasefile_path)) { - gs_unref_object GFile *src = g_file_new_for_path(priv->lease_file); - gs_unref_object GFile *dst = g_file_new_for_path(preferred_leasefile_path); - - /* Try to copy the existing leasefile to the preferred location */ - if (!g_file_copy(src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &local)) { - gs_free char *s_path = NULL; - gs_free char *d_path = NULL; - - /* Failure; just use the existing leasefile */ - _LOGW("failed to copy leasefile %s to %s: %s", - (s_path = g_file_get_path(src)), - (d_path = g_file_get_path(dst)), - local->message); - g_clear_error(&local); - } else { - /* Success; use the preferred leasefile path */ - g_free(priv->lease_file); - priv->lease_file = g_file_get_path(dst); - } - } - - /* Save the DUID to the leasefile dhclient will actually use */ - if (set_duid && addr_family == AF_INET6) { - if (!nm_dhcp_dhclient_save_duid(priv->lease_file, - nm_dhcp_client_get_effective_client_id(client), - client_config->v6.enforce_duid, - &local)) { - nm_utils_error_set(error, - NM_UTILS_ERROR_UNKNOWN, - "failed to save DUID to '%s': %s", - priv->lease_file, - local->message); - return FALSE; - } - } - - argv = g_ptr_array_new(); - g_ptr_array_add(argv, (gpointer) dhclient_path); - - g_ptr_array_add(argv, (gpointer) "-d"); - - /* Be quiet. dhclient logs to syslog anyway. And we duplicate the syslog - * to stderr in case of NM running with --debug. - */ - g_ptr_array_add(argv, (gpointer) "-q"); - - if (release) - g_ptr_array_add(argv, (gpointer) "-r"); - - if (!release && client_config->addr_family == AF_INET && client_config->v4.request_broadcast) { - g_ptr_array_add(argv, (gpointer) "-B"); - } - - if (addr_family == AF_INET6) { - guint prefixes = client_config->v6.needed_prefixes; - const char *mode_opt; - - g_ptr_array_add(argv, (gpointer) "-6"); - - if (!set_mode) - mode_opt = NULL; - else if (!client_config->v6.info_only) - mode_opt = "-N"; - else if (prefixes == 0) - mode_opt = "-S"; - else - mode_opt = NULL; - - if (mode_opt) - g_ptr_array_add(argv, (gpointer) mode_opt); - - if (prefixes > 0 && client_config->v6.pd_hint_length > 0) { - if (!IN6_IS_ADDR_UNSPECIFIED(&client_config->v6.pd_hint_addr)) { - _LOGW("dhclient only supports a length as prefix delegation hint, not a prefix"); - } - - nm_sprintf_buf(pd_length_str, "%u", client_config->v6.pd_hint_length); - g_ptr_array_add(argv, "--prefix-len-hint"); - g_ptr_array_add(argv, pd_length_str); - } - - while (prefixes--) - g_ptr_array_add(argv, (gpointer) "-P"); - } - g_ptr_array_add(argv, (gpointer) "-sf"); /* Set script file */ - g_ptr_array_add(argv, (gpointer) nm_dhcp_helper_path); - - if (pid_file) { - g_ptr_array_add(argv, (gpointer) "-pf"); /* Set pid file */ - g_ptr_array_add(argv, (gpointer) pid_file); - } - - g_ptr_array_add(argv, (gpointer) "-lf"); /* Set lease file */ - g_ptr_array_add(argv, (gpointer) priv->lease_file); - - if (priv->conf_file) { - g_ptr_array_add(argv, (gpointer) "-cf"); /* Set interface config file */ - g_ptr_array_add(argv, (gpointer) priv->conf_file); - } - - if (client_config->v4.dscp_explicit) { - _LOGW("dhclient does not support specifying a custom DSCP value; the TOS field will be set " - "to LOWDELAY (0x10)."); - } - - if (client_config->v4.ipv6_only_preferred) { - _LOGW("the dhclient backend does not support the \"IPv6-Only Preferred\" option; ignoring " - "it"); - } - - /* Usually the system bus address is well-known; but if it's supposed - * to be something else, we need to push it to dhclient, since dhclient - * sanitizes the environment it gives the action scripts. - */ - system_bus_address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); - if (system_bus_address) { - system_bus_address_env = g_strdup_printf("DBUS_SYSTEM_BUS_ADDRESS=%s", system_bus_address); - g_ptr_array_add(argv, (gpointer) "-e"); - g_ptr_array_add(argv, (gpointer) system_bus_address_env); - } - - g_ptr_array_add(argv, (gpointer) iface); - g_ptr_array_add(argv, NULL); - - _LOGD("running: %s", (cmd_str = g_strjoinv(" ", (char **) argv->pdata))); - - if (!g_spawn_async(NULL, - (char **) argv->pdata, - NULL, - G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL - | G_SPAWN_STDERR_TO_DEV_NULL, - nm_utils_setpgid, - NULL, - &pid, - &local)) { - nm_utils_error_set(error, - NM_UTILS_ERROR_UNKNOWN, - "dhclient failed to start: %s", - local->message); - return FALSE; - } - - _LOGI("dhclient started with pid %lld", (long long int) pid); - - if (!release) - nm_dhcp_client_watch_child(client, pid); - - priv->pid_file = g_steal_pointer(&pid_file); - - NM_SET_OUT(out_pid, pid); - return TRUE; -} - -static gboolean -ip4_start(NMDhcpClient *client, GError **error) -{ - NMDhcpDhclient *self = NM_DHCP_DHCLIENT(client); - NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); - gs_unref_bytes GBytes *new_client_id = NULL; - const NMDhcpClientConfig *client_config; - - client_config = nm_dhcp_client_get_config(client); - - nm_assert(client_config->addr_family == AF_INET); - - priv->conf_file = create_dhclient_config(self, - AF_INET, - client_config->iface, - client_config->uuid, - client_config->client_id, - client_config->v4.send_client_id, - client_config->anycast_address, - client_config->hostname, - client_config->timeout, - client_config->use_fqdn, - client_config->hostname_flags, - client_config->mud_url, - client_config->reject_servers, - &new_client_id); - if (!priv->conf_file) { - nm_utils_error_set_literal(error, - NM_UTILS_ERROR_UNKNOWN, - "error creating dhclient configuration file"); - return FALSE; - } - - /* Note that the effective-client-id for IPv4 here might be unknown/NULL. */ - nm_assert(!new_client_id || !client_config->client_id); - nm_dhcp_client_set_effective_client_id(client, client_config->client_id ?: new_client_id); - - return dhclient_start(client, FALSE, FALSE, FALSE, NULL, error); -} - -static gboolean -ip6_start(NMDhcpClient *client, const struct in6_addr *ll_addr, GError **error) -{ - NMDhcpDhclient *self = NM_DHCP_DHCLIENT(client); - NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); - const NMDhcpClientConfig *config; - gs_unref_bytes GBytes *effective_client_id = NULL; - - config = nm_dhcp_client_get_config(client); - - nm_assert(config->addr_family == AF_INET6); - - if (config->v6.iaid_explicit) - _LOGW("dhclient does not support specifying an IAID for DHCPv6, it will be ignored"); - - priv->conf_file = create_dhclient_config(self, - AF_INET6, - config->iface, - config->uuid, - NULL, - TRUE, - config->anycast_address, - config->hostname, - config->timeout, - TRUE, - config->hostname_flags, - config->mud_url, - NULL, - NULL); - if (!priv->conf_file) { - nm_utils_error_set_literal(error, - NM_UTILS_ERROR_UNKNOWN, - "error creating dhclient configuration file"); - return FALSE; - } - - nm_assert(config->client_id); - if (!config->v6.enforce_duid) - effective_client_id = read_duid_from_lease(self); - nm_dhcp_client_set_effective_client_id(client, effective_client_id ?: config->client_id); - - return dhclient_start(client, TRUE, FALSE, TRUE, NULL, error); -} - -static void -stop(NMDhcpClient *client, gboolean release) -{ - NMDhcpDhclient *self = NM_DHCP_DHCLIENT(client); - NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); - int errsv; - - NM_DHCP_CLIENT_CLASS(nm_dhcp_dhclient_parent_class)->stop(client, release); - - if (priv->conf_file) - if (remove(priv->conf_file) == -1) { - errsv = errno; - _LOGD("could not remove dhcp config file \"%s\": %d (%s)", - priv->conf_file, - errsv, - nm_strerror_native(errsv)); - } - if (priv->pid_file) { - if (remove(priv->pid_file) == -1) { - errsv = errno; - _LOGD("could not remove dhcp pid file \"%s\": %s (%d)", - priv->pid_file, - nm_strerror_native(errsv), - errsv); - } - nm_clear_g_free(&priv->pid_file); - } - - if (release) { - pid_t rpid = -1; - - if (dhclient_start(client, FALSE, TRUE, FALSE, &rpid, NULL)) { - /* Wait a few seconds for the release to happen */ - nm_dhcp_client_stop_pid(rpid, nm_dhcp_client_get_iface(client)); - } - } -} - -static GBytes * -read_duid_from_lease(NMDhcpDhclient *self) -{ - NMDhcpClient *client = NM_DHCP_CLIENT(self); - NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); - const NMDhcpClientConfig *client_config; - GBytes *duid = NULL; - gs_free char *leasefile = NULL; - GError *error = NULL; - - client_config = nm_dhcp_client_get_config(client); - - /* Look in interface-specific leasefile first for backwards compat */ - leasefile = get_dhclient_leasefile(AF_INET6, - nm_dhcp_client_get_iface(client), - client_config->uuid, - NULL); - if (leasefile) { - _LOGD("looking for DUID in '%s'", leasefile); - duid = nm_dhcp_dhclient_read_duid(leasefile, &error); - if (error) { - _LOGW("failed to read leasefile '%s': %s", leasefile, error->message); - g_clear_error(&error); - } - if (duid) - return duid; - } - - /* Otherwise, read the default machine-wide DUID */ - _LOGD("looking for default DUID in '%s'", priv->def_leasefile); - duid = nm_dhcp_dhclient_read_duid(priv->def_leasefile, &error); - if (error) { - _LOGW("failed to read leasefile '%s': %s", priv->def_leasefile, error->message); - g_clear_error(&error); - } - - return duid; -} - -/*****************************************************************************/ - -static void -nm_dhcp_dhclient_init(NMDhcpDhclient *self) -{ - static const char *const FILES[] = { - SYSCONFDIR "/dhclient6.leases", /* default */ - LOCALSTATEDIR "/lib/dhcp/dhclient6.leases", - LOCALSTATEDIR "/lib/dhclient/dhclient6.leases", - }; - NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(self); - int i; - - priv->def_leasefile = FILES[0]; - for (i = 0; i < G_N_ELEMENTS(FILES); i++) { - if (g_file_test(FILES[i], G_FILE_TEST_EXISTS)) { - priv->def_leasefile = FILES[i]; - break; - } - } - - priv->dhcp_listener = g_object_ref(nm_dhcp_listener_get()); - g_signal_connect(priv->dhcp_listener, - NM_DHCP_LISTENER_EVENT, - G_CALLBACK(nm_dhcp_client_handle_event), - self); -} - -static void -dispose(GObject *object) -{ - NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE(object); - - if (priv->dhcp_listener) { - g_signal_handlers_disconnect_by_func(priv->dhcp_listener, - G_CALLBACK(nm_dhcp_client_handle_event), - NM_DHCP_DHCLIENT(object)); - g_clear_object(&priv->dhcp_listener); - } - - nm_clear_g_free(&priv->pid_file); - nm_clear_g_free(&priv->conf_file); - nm_clear_g_free(&priv->lease_file); - - G_OBJECT_CLASS(nm_dhcp_dhclient_parent_class)->dispose(object); -} - -static void -nm_dhcp_dhclient_class_init(NMDhcpDhclientClass *dhclient_class) -{ - NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS(dhclient_class); - GObjectClass *object_class = G_OBJECT_CLASS(dhclient_class); - - object_class->dispose = dispose; - - client_class->ip4_start = ip4_start; - client_class->ip6_start = ip6_start; - client_class->stop = stop; -} - -const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = { - .name = "dhclient", - .get_type_4 = nm_dhcp_dhclient_get_type, - .get_type_6 = nm_dhcp_dhclient_get_type, - .get_path = nm_dhcp_dhclient_get_path, -}; - -#endif /* WITH_DHCLIENT */ diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c index 9e4cedf2..144e3cb0 100644 --- a/src/core/dhcp/nm-dhcp-helper.c +++ b/src/core/dhcp/nm-dhcp-helper.c @@ -205,8 +205,7 @@ do_notify: if (!NM_IN_STRSET(s_err, "org.freedesktop.DBus.Error.UnknownMethod")) { /* Some unexpected error. We treat that as a failure. In particular, - * the daemon will fail the request if ACD fails. This causes nm-dhcp-helper - * to fail, which in turn causes dhclient to send a DECLINE. */ + * the daemon will fail the request if ACD fails. */ _LOGW("failure to call notify: %s (try signal via Event)", error->message); success = FALSE; goto out; diff --git a/src/core/dhcp/nm-dhcp-listener.c b/src/core/dhcp/nm-dhcp-listener.c index 095131cc..cead1308 100644 --- a/src/core/dhcp/nm-dhcp-listener.c +++ b/src/core/dhcp/nm-dhcp-listener.c @@ -36,9 +36,6 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6] = { #endif &_nm_dhcp_client_factory_systemd, &_nm_dhcp_client_factory_nettools, -#if WITH_DHCLIENT - &_nm_dhcp_client_factory_dhclient, -#endif }; /*****************************************************************************/ diff --git a/src/core/dhcp/nm-dhcp-manager.c b/src/core/dhcp/nm-dhcp-manager.c index 68bb327b..9faee84e 100644 --- a/src/core/dhcp/nm-dhcp-manager.c +++ b/src/core/dhcp/nm-dhcp-manager.c @@ -91,11 +91,6 @@ static const NMDhcpClientFactory * _client_factory_available(const NMDhcpClientFactory *client_factory) { if (client_factory) { - if (nm_streq(client_factory->name, "dhclient")) { - _LOGW(AF_UNSPEC, - "attempting to used a deprecated DHCP client '%s' ", - client_factory->name); - } if (!client_factory->get_path || client_factory->get_path()) return client_factory; } @@ -205,30 +200,10 @@ nm_dhcp_manager_start_client(NMDhcpManager *self, NMDhcpClientConfig *config, GE client = g_object_new(gtype, NM_DHCP_CLIENT_CONFIG, config, NULL); - /* unfortunately, our implementations work differently per address-family regarding client-id/DUID. - * - * - for IPv4, the calling code may determine a client-id (from NM's connection profile). - * If present, it is taken. If not present, the DHCP plugin uses a plugin specific default. - * - for "internal" plugin, the default is just "mac". - * - for "dhclient", we try to get the configuration from dhclient's /etc/dhcp or fallback - * to whatever dhclient uses by default. - * We do it this way, because for dhclient the user may configure a default - * outside of NM, and we want to honor that. Worse, dhclient could be a wapper - * script where the wrapper script overwrites the client-id. We need to distinguish - * between: force a particular client-id and leave it unspecified to whatever dhclient - * wants. - * - * - for IPv6, the calling code always determines a client-id. It also specifies @enforce_duid, - * to determine whether the given client-id must be used. - * - for "internal" plugin @enforce_duid doesn't matter and the given client-id is - * always used. - * - for "dhclient", @enforce_duid FALSE means to first try to load the DUID from the - * lease file, and only otherwise fallback to the given client-id. - * - other plugins don't support DHCPv6. - * It's done this way, so that existing dhclient setups don't change behavior on upgrade. + /* - for IPv4, the calling code may determine a client-id (from NM's connection profile). + * If present, it is taken. If not present, the default is just "mac". * - * This difference is cumbersome and only exists because of "dhclient" which supports hacking the - * default outside of NetworkManager API. + * - for IPv6, the calling code always determines a client-id. */ if (!nm_dhcp_client_start(client, error)) @@ -289,8 +264,10 @@ nm_dhcp_manager_init(NMDhcpManager *self) NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY); client = client_free; if (client) { - client_factory = _client_factory_available(_client_factory_find_by_name(client)); + client_factory = _client_factory_find_by_name(client); if (!client_factory) + _LOGW(AF_UNSPEC, "init: unknown DHCP client '%s', ignoring", client); + else if (!(client_factory = _client_factory_available(client_factory))) _LOGW(AF_UNSPEC, "init: DHCP client '%s' not available", client); } if (!client_factory) { diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c index 27bb136b..ad9cb893 100644 --- a/src/core/dhcp/nm-dhcp-nettools.c +++ b/src/core/dhcp/nm-dhcp-nettools.c @@ -269,7 +269,7 @@ lease_parse_address(NMDhcpNettools *self /* for logging context only */, char str2[NM_INET_ADDRSTRLEN]; /* Some DHCP servers may not set the subnet-mask (issue#1037). - * Do the same as the dhclient plugin and use a default. */ + * Use a default. */ a_plen = nm_ip4_addr_get_default_prefix(a_address.s_addr); a_netmask = nm_ip4_addr_netmask_from_prefix(a_plen); _LOGT("missing subnet mask (option 1). Guess %s based on IP address %s", @@ -418,7 +418,6 @@ lease_parse_routes(NDhcp4ClientLease *lease, in_addr_t gateway; uint8_t plen; guint32 m; - gboolean has_router_from_classless = FALSE; gboolean has_classless = FALSE; guint32 default_route_metric_offset = 0; const guint8 *l_data; @@ -434,7 +433,7 @@ lease_parse_routes(NDhcp4ClientLease *lease, * We will however also parse one of the options into the "l3cd" for configuring routing. * Thereby we prefer 121 over 249 over 33. * - * Preferring 121 over 33 is defined by RFC 3443. + * Preferring 121 over 33 is defined by RFC 3442. * Preferring 121 over 249 over 33 is made up as it makes sense (the MS docs are not very clear). */ for (i = 0; i < 2; i++) { @@ -460,8 +459,7 @@ lease_parse_routes(NDhcp4ClientLease *lease, if (plen == 0) { /* if there are multiple default routes, we add them with differing * metrics. */ - m = default_route_metric_offset++; - has_router_from_classless = TRUE; + m = default_route_metric_offset++; } else m = 0; @@ -495,7 +493,7 @@ lease_parse_routes(NDhcp4ClientLease *lease, nm_str_buf_append_printf(sbuf, "%s/%d %s", dest_str, (int) plen, gateway_str); if (has_classless) { - /* RFC 3443: if the DHCP server returns both a Classless Static Routes + /* RFC 3442: if the DHCP server returns both a Classless Static Routes * option and a Static Routes option, the DHCP client MUST ignore the * Static Routes option. */ continue; @@ -539,13 +537,10 @@ lease_parse_routes(NDhcp4ClientLease *lease, continue; } - if (has_router_from_classless) { - /* If the DHCP server returns both a Classless Static Routes option and a - * Router option, the DHCP client MUST ignore the Router option [RFC 3442]. - * - * Be more lenient and ignore the Router option only if Classless Static - * Routes contain a default gateway (as other DHCP backends do). - */ + if (has_classless) { + /* RFC 3442: if the DHCP server returns both a Classless Static Routes + * option and a Router option, the DHCP client MUST ignore the Router + * option. */ continue; } @@ -1475,7 +1470,9 @@ ip4_start(NMDhcpClient *client, GError **error) } else { fqdn_len = strlen(client_config->hostname); if (fqdn_len > sizeof(buffer) - 3) { - nm_utils_error_set(error, r, "failed to set DHCP FQDN: name too long"); + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + "failed to set DHCP FQDN: name too long"); return FALSE; } memcpy(buffer + 3, client_config->hostname, fqdn_len); @@ -1530,7 +1527,8 @@ ip4_start(NMDhcpClient *client, GError **error) _LOGT("dhcp-client4: start " NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(priv->client)); - nm_dhcp_client_set_effective_client_id(client, effective_client_id); + if (effective_client_id) + nm_dhcp_client_set_effective_client_id(client, effective_client_id); return TRUE; } diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c index b570f7e5..547e730b 100644 --- a/src/core/dhcp/nm-dhcp-systemd.c +++ b/src/core/dhcp/nm-dhcp-systemd.c @@ -92,6 +92,7 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro const char *s; nm_auto_free_gstring GString *str = NULL; int num, i; + gboolean has_any_prefix_delegated = FALSE; nm_assert(lease); @@ -107,6 +108,28 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro NM_DHCP_OPTION_DHCP6_NM_IAID, nm_dhcp_iaid_to_hexstr(config->v6.iaid, iaid_buf)); + { + struct in6_addr prefix; + uint8_t prefix_len; + + nm_gstring_prepare(&str); + sd_dhcp6_lease_pd_iterator_reset(lease); + while (!sd_dhcp6_lease_get_pd_prefix(lease, &prefix, &prefix_len)) { + nm_gstring_add_space_delimiter(str); + nm_inet6_ntop(&prefix, addr_str); + g_string_append_printf(str, "%s/%u", addr_str, prefix_len); + sd_dhcp6_lease_pd_iterator_next(lease); + } + if (str->len > 0) { + nm_dhcp_option_add_option(options, + TRUE, + AF_INET6, + NM_DHCP_OPTION_DHCP6_IA_PD, + str->str); + has_any_prefix_delegated = TRUE; + } + } + if (!config->v6.info_only) { gboolean has_any_addresses = FALSE; uint64_t lft_pref; @@ -142,11 +165,11 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro str->str); } - if (!has_any_addresses) { + if (!has_any_addresses && !has_any_prefix_delegated) { g_set_error_literal(error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, - "no address received in managed mode"); + "no address or prefix delegation received in managed mode"); return NULL; } } @@ -166,27 +189,6 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro str->str); } - { - struct in6_addr prefix; - uint8_t prefix_len; - - nm_gstring_prepare(&str); - sd_dhcp6_lease_pd_iterator_reset(lease); - while (!sd_dhcp6_lease_get_pd_prefix(lease, &prefix, &prefix_len)) { - nm_gstring_add_space_delimiter(str); - nm_inet6_ntop(&prefix, addr_str); - g_string_append_printf(str, "%s/%u", addr_str, prefix_len); - sd_dhcp6_lease_pd_iterator_next(lease); - } - if (str->len > 0) { - nm_dhcp_option_add_option(options, - TRUE, - AF_INET6, - NM_DHCP_OPTION_DHCP6_IA_PD, - str->str); - } - } - num = sd_dhcp6_lease_get_domains(lease, &domains); if (num > 0) { nm_gstring_prepare(&str); diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c index 8a9bd58c..8c3001f2 100644 --- a/src/core/dhcp/nm-dhcp-utils.c +++ b/src/core/dhcp/nm-dhcp-utils.c @@ -827,64 +827,6 @@ nm_dhcp_utils_get_leasefile_path(int addr_family, return FALSE; } -gboolean -nm_dhcp_utils_merge_new_dhcp6_lease(const NML3ConfigData *l3cd_old, - const NML3ConfigData *l3cd_new, - const NML3ConfigData **out_l3cd_merged) -{ - nm_auto_unref_l3cd_init NML3ConfigData *l3cd_merged = NULL; - const NMPlatformIP6Address *addr; - NMDhcpLease *lease_old; - NMDhcpLease *lease_new; - NMDedupMultiIter iter; - const char *start; - const char *iaid; - - nm_assert(out_l3cd_merged && !*out_l3cd_merged); - - if (!l3cd_old) - return FALSE; - if (!l3cd_new) - return FALSE; - - lease_new = nm_l3_config_data_get_dhcp_lease(l3cd_new, AF_INET6); - if (!lease_new) - return FALSE; - - lease_old = nm_l3_config_data_get_dhcp_lease(l3cd_old, AF_INET6); - if (!lease_old) - return FALSE; - - start = nm_dhcp_lease_lookup_option(lease_new, "life_starts"); - if (!start) - return FALSE; - iaid = nm_dhcp_lease_lookup_option(lease_new, "iaid"); - if (!iaid) - return FALSE; - - if (!nm_streq0(start, nm_dhcp_lease_lookup_option(lease_old, "life_starts"))) - return FALSE; - if (!nm_streq0(iaid, nm_dhcp_lease_lookup_option(lease_old, "iaid"))) - return FALSE; - - /* If the server sends multiple IPv6 addresses, we receive a state - * changed event for each of them. Use the event ID to merge IPv6 - * addresses from the same transaction into a single configuration. - **/ - - l3cd_merged = nm_l3_config_data_new_clone(l3cd_old, 0); - - nm_l3_config_data_iter_ip6_address_for_each (&iter, l3cd_new, &addr) - nm_l3_config_data_add_address_6(l3cd_merged, addr); - - /* FIXME(l3cfg): Note that we keep the original NMDhcpLease. All we take from the new lease are the - * addresses. Maybe this is not right and we should merge the leases too?? */ - nm_l3_config_data_set_dhcp_lease(l3cd_merged, AF_INET6, lease_old); - - *out_l3cd_merged = nm_l3_config_data_ref_and_seal(g_steal_pointer(&l3cd_merged)); - return TRUE; -} - /*****************************************************************************/ void diff --git a/src/core/dhcp/nm-dhcp-utils.h b/src/core/dhcp/nm-dhcp-utils.h index 99898524..a2dde94e 100644 --- a/src/core/dhcp/nm-dhcp-utils.h +++ b/src/core/dhcp/nm-dhcp-utils.h @@ -33,12 +33,6 @@ gboolean nm_dhcp_utils_get_leasefile_path(int addr_family, const char *uuid, char **out_leasefile_path); -char *nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease); - -gboolean nm_dhcp_utils_merge_new_dhcp6_lease(const NML3ConfigData *l3cd_old, - const NML3ConfigData *l3cd_new, - const NML3ConfigData **out_l3cd_merged); - /*****************************************************************************/ static inline gboolean diff --git a/src/core/dhcp/tests/meson.build b/src/core/dhcp/tests/meson.build index e43c8cab..8b2a7b76 100644 --- a/src/core/dhcp/tests/meson.build +++ b/src/core/dhcp/tests/meson.build @@ -1,7 +1,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later test_units = [ - 'test-dhcp-dhclient', 'test-dhcp-utils', ] diff --git a/src/core/dhcp/tests/test-dhclient-commented-duid.leases b/src/core/dhcp/tests/test-dhclient-commented-duid.leases deleted file mode 100644 index 3e46ae7d..00000000 --- a/src/core/dhcp/tests/test-dhclient-commented-duid.leases +++ /dev/null @@ -1,2 +0,0 @@ -#default-duid "\000\001\000\001\030y\246\023`g \354Lp"; - diff --git a/src/core/dhcp/tests/test-dhclient-duid.leases b/src/core/dhcp/tests/test-dhclient-duid.leases deleted file mode 100644 index 229331d4..00000000 --- a/src/core/dhcp/tests/test-dhclient-duid.leases +++ /dev/null @@ -1,2 +0,0 @@ -default-duid "\000\001\000\001\030y\246\023`g \354Lp"; - diff --git a/src/core/dhcp/tests/test-dhcp-dhclient.c b/src/core/dhcp/tests/test-dhcp-dhclient.c deleted file mode 100644 index 6a7b7185..00000000 --- a/src/core/dhcp/tests/test-dhcp-dhclient.c +++ /dev/null @@ -1,1478 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2010 Red Hat, Inc. - */ - -#include "src/core/nm-default-daemon.h" - -#include <unistd.h> -#include <arpa/inet.h> -#include <linux/rtnetlink.h> - -#include "libnm-glib-aux/nm-dedup-multi.h" - -#include "NetworkManagerUtils.h" -#include "dhcp/nm-dhcp-dhclient-utils.h" -#include "dhcp/nm-dhcp-utils.h" -#include "nm-utils.h" -#include "libnm-platform/nm-platform.h" - -#include "nm-test-utils-core.h" - -#define TEST_DIR NM_BUILD_SRCDIR "/src/core/dhcp/tests" -#define TEST_MUDURL "https://example.com/mud.json" - -static void -test_config(const char *orig, - const char *expected, - int addr_family, - const char *hostname, - guint32 timeout, - gboolean use_fqdn, - NMDhcpHostnameFlags hostname_flags, - const char *dhcp_client_id, - GBytes *expected_new_client_id, - const char *iface, - const char *anycast_addr, - const char *mud_url) -{ - gs_free char *new = NULL; - gs_unref_bytes GBytes *client_id = NULL; - gs_unref_bytes GBytes *new_client_id = NULL; - gboolean send_client_id = TRUE; - - if (nm_streq0(dhcp_client_id, "none")) { - send_client_id = FALSE; - } else if (dhcp_client_id) { - client_id = nm_dhcp_utils_client_id_string_to_bytes(dhcp_client_id); - g_assert(client_id); - } - - new = nm_dhcp_dhclient_create_config(iface, - addr_family, - client_id, - send_client_id, - anycast_addr, - hostname, - timeout, - use_fqdn, - hostname_flags, - mud_url, - NULL, - "/path/to/dhclient.conf", - orig, - &new_client_id); - g_assert(new != NULL); - - if (!nm_streq(new, expected)) { - g_message("\n* OLD ---------------------------------\n" - "%s" - "\n- NEW -----------------------------------\n" - "%s" - "\n+ EXPECTED ++++++++++++++++++++++++++++++\n" - "%s" - "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", - orig, - new, - expected); - } - g_assert_cmpstr(new, ==, expected); - - if (expected_new_client_id) { - g_assert(new_client_id); - g_assert(g_bytes_equal(new_client_id, expected_new_client_id)); - } else - g_assert(new_client_id == NULL); -} - -/*****************************************************************************/ - -static const char *orig_missing_expected = - "# Created by NetworkManager\n" - "\n\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_orig_missing(void) -{ - test_config(NULL, - orig_missing_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *orig_missing_add_mud_url_expected = - "# Created by NetworkManager\n" - "\n" - "option mudurl code 161 = text;\n" - "send mudurl \"https://example.com/mud.json\";\n\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_orig_missing_add_mud_url(void) -{ - test_config(NULL, - orig_missing_add_mud_url_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - TEST_MUDURL); -} - -/*****************************************************************************/ - -static const char *override_client_id_orig = "send dhcp-client-identifier 00:30:04:20:7A:08;\n"; - -static const char *override_client_id_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send dhcp-client-identifier 11:22:33:44:55:66; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_override_client_id(void) -{ - test_config(override_client_id_orig, - override_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - "11:22:33:44:55:66", - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *quote_client_id_expected = - "# Created by NetworkManager\n" - "\n" - "send dhcp-client-identifier \"\\x00abcd\"; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_quote_client_id(void) -{ - test_config(NULL, - quote_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - "abcd", - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *quote_client_id_expected_2 = - "# Created by NetworkManager\n" - "\n" - "send dhcp-client-identifier 00:61:5c:62:63; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_quote_client_id_2(void) -{ - test_config(NULL, - quote_client_id_expected_2, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - "a\\bc", - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *hex_zero_client_id_expected = - "# Created by NetworkManager\n" - "\n" - "send dhcp-client-identifier 00:11:22:33; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_hex_zero_client_id(void) -{ - test_config(NULL, - hex_zero_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - "00:11:22:33", - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *ascii_client_id_expected = - "# Created by NetworkManager\n" - "\n" - "send dhcp-client-identifier \"\\x00qb:cd:ef:12:34:56\"; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_ascii_client_id(void) -{ - test_config(NULL, - ascii_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - "qb:cd:ef:12:34:56", - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *hex_single_client_id_expected = - "# Created by NetworkManager\n" - "\n" - "send dhcp-client-identifier ab:cd:0e:12:34:56; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_hex_single_client_id(void) -{ - test_config(NULL, - hex_single_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - "ab:cd:e:12:34:56", - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *existing_hex_client_id_orig = "send dhcp-client-identifier 10:30:04:20:7A:08;\n"; - -static const char *existing_hex_client_id_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send dhcp-client-identifier 10:30:04:20:7A:08;\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_existing_hex_client_id(void) -{ - gs_unref_bytes GBytes *new_client_id = NULL; - const guint8 bytes[] = {0x10, 0x30, 0x04, 0x20, 0x7A, 0x08}; - - new_client_id = g_bytes_new(bytes, sizeof(bytes)); - test_config(existing_hex_client_id_orig, - existing_hex_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - new_client_id, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *existing_escaped_client_id_orig = - "send dhcp-client-identifier \"\\044test\\xfe\";\n"; - -static const char *existing_escaped_client_id_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send dhcp-client-identifier \"\\044test\\xfe\";\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_existing_escaped_client_id(void) -{ - gs_unref_bytes GBytes *new_client_id = NULL; - - new_client_id = g_bytes_new("$test\xfe", 6); - test_config(existing_escaped_client_id_orig, - existing_escaped_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - new_client_id, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -#define EACID "qb:cd:ef:12:34:56" - -static const char *existing_ascii_client_id_orig = - "send dhcp-client-identifier \"\\x00" EACID "\";\n"; - -static const char *existing_ascii_client_id_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send dhcp-client-identifier \"\\x00" EACID "\";\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_existing_ascii_client_id(void) -{ - gs_unref_bytes GBytes *new_client_id = NULL; - char buf[NM_STRLEN(EACID) + 1] = {0}; - - memcpy(buf + 1, EACID, NM_STRLEN(EACID)); - new_client_id = g_bytes_new(buf, sizeof(buf)); - test_config(existing_ascii_client_id_orig, - existing_ascii_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - new_client_id, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *none_client_id_orig = "send dhcp-client-identifier 10:30:04:20:7A:08;\n"; - -static const char *none_client_id_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send dhcp-client-identifier \"\"; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_none_client_id(void) -{ - const char *connection_client_id = "none"; - gs_unref_bytes GBytes *expected_client_id = NULL; - - test_config(none_client_id_orig, - none_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - connection_client_id, - expected_client_id, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *missing_client_id_orig = ""; - -static const char *missing_client_id_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_missing_client_id(void) -{ - const char *connection_client_id = NULL; - gs_unref_bytes GBytes *expected_client_id = NULL; - - test_config(missing_client_id_orig, - missing_client_id_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - connection_client_id, - expected_client_id, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *fqdn_expected = - "# Created by NetworkManager\n" - "\n" - "send fqdn.fqdn \"foo.bar.com\"; # added by NetworkManager\n" - "send fqdn.encoded on;\n" - "send fqdn.server-update off;\n" - "send fqdn.no-client-update on;\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n\n"; - -static void -test_fqdn(void) -{ - test_config(NULL, - fqdn_expected, - AF_INET, - "foo.bar.com", - 0, - TRUE, - NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -static const char *fqdn_options_override_orig = - "\n" - "send fqdn.fqdn \"foobar.com\"\n" /* NM must ignore this ... */ - "send fqdn.encoded off;\n" /* ... and honor these */ - "send fqdn.server-update off;\n"; - -static const char *fqdn_options_override_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send fqdn.fqdn \"example2.com\"; # added by NetworkManager\n" - "send fqdn.encoded off;\n" - "send fqdn.server-update on;\n" - "send fqdn.no-client-update off;\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n" - "# FQDN options from /path/to/dhclient.conf\n" - "send fqdn.encoded off;\n" - "send fqdn.server-update off;\n\n"; - -static void -test_fqdn_options_override(void) -{ - test_config(fqdn_options_override_orig, - fqdn_options_override_expected, - AF_INET, - "example2.com", - 0, - NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE, - TRUE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *override_hostname_orig = "send host-name \"foobar\";\n"; - -static const char *override_hostname_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send host-name \"blahblah\"; # added by NetworkManager\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_override_hostname(void) -{ - test_config(override_hostname_orig, - override_hostname_expected, - AF_INET, - "blahblah", - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *override_hostname6_orig = "send fqdn.fqdn \"foobar\";\n"; - -static const char *override_hostname6_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send fqdn.fqdn \"blahblah.local\"; # added by NetworkManager\n" - "send fqdn.server-update on;\n" - "\n" - "also request dhcp6.name-servers;\n" - "also request dhcp6.domain-search;\n" - "also request dhcp6.client-id;\n" - "\n"; - -static void -test_override_hostname6(void) -{ - test_config(override_hostname6_orig, - override_hostname6_expected, - AF_INET6, - "blahblah.local", - 0, - TRUE, - NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *nonfqdn_hostname6_expected = - "# Created by NetworkManager\n" - "\n" - "send fqdn.fqdn \"blahblah\"; # added by NetworkManager\n" - "send fqdn.no-client-update on;\n" - "\n" - "also request dhcp6.name-servers;\n" - "also request dhcp6.domain-search;\n" - "also request dhcp6.client-id;\n" - "\n"; - -static void -test_nonfqdn_hostname6(void) -{ - /* Non-FQDN hostname can now be used with dhclient */ - test_config(NULL, - nonfqdn_hostname6_expected, - AF_INET6, - "blahblah", - 0, - TRUE, - NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *existing_alsoreq_orig = "also request something;\n" - "also request another-thing;\n"; - -static const char *existing_alsoreq_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request something;\n" - "also request another-thing;\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_existing_alsoreq(void) -{ - test_config(existing_alsoreq_orig, - existing_alsoreq_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *existing_req_orig = "request something;\n" - "also request some-other-thing;\n" - "request another-thing;\n" - "also request yet-another-thing;\n"; - -static const char *existing_req_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "request; # override dhclient defaults\n" - "also request another-thing;\n" - "also request yet-another-thing;\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_existing_req(void) -{ - test_config(existing_req_orig, - existing_req_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *existing_multiline_alsoreq_orig = - "also request something another-thing yet-another-thing\n" - " foobar baz blah;\n"; - -static const char *existing_multiline_alsoreq_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request something;\n" - "also request another-thing;\n" - "also request yet-another-thing;\n" - "also request foobar;\n" - "also request baz;\n" - "also request blah;\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_existing_multiline_alsoreq(void) -{ - test_config(existing_multiline_alsoreq_orig, - existing_multiline_alsoreq_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static void -test_one_duid(const char *escaped, const guint8 *unescaped, guint len) -{ - gs_unref_bytes GBytes *t1 = NULL; - gs_unref_bytes GBytes *t2 = NULL; - gs_free char *w = NULL; - - t1 = nm_dhcp_dhclient_unescape_duid(escaped); - g_assert(t1); - g_assert(nm_g_bytes_equal_mem(t1, unescaped, len)); - - t2 = g_bytes_new(unescaped, len); - w = nm_dhcp_dhclient_escape_duid(t2); - g_assert(w); - g_assert_cmpstr(escaped, ==, w); -} - -static void -test_duids(void) -{ - const guint8 test1_u[] = - {0x00, 0x01, 0x00, 0x01, 0x13, 0x6f, 0x13, 0x6e, 0x00, 0x22, 0xfa, 0x8c, 0xd6, 0xc2}; - const char *test1_s = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"; - - const guint8 test2_u[] = - {0x00, 0x01, 0x00, 0x01, 0x17, 0x57, 0xee, 0x39, 0x00, 0x23, 0x15, 0x08, 0x7E, 0xac}; - const char *test2_s = "\\000\\001\\000\\001\\027W\\3569\\000#\\025\\010~\\254"; - - const guint8 test3_u[] = - {0x00, 0x01, 0x00, 0x01, 0x17, 0x58, 0xe8, 0x58, 0x00, 0x23, 0x15, 0x08, 0x7e, 0xac}; - const char *test3_s = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254"; - - const guint8 test4_u[] = - {0x00, 0x01, 0x00, 0x01, 0x15, 0xd5, 0x31, 0x97, 0x00, 0x16, 0xeb, 0x04, 0x45, 0x18}; - const char *test4_s = "\\000\\001\\000\\001\\025\\3251\\227\\000\\026\\353\\004E\\030"; - - const char *bad_s = "\\000\\001\\000\\001\\425\\3251\\227\\000\\026\\353\\004E\\030"; - - test_one_duid(test1_s, test1_u, sizeof(test1_u)); - test_one_duid(test2_s, test2_u, sizeof(test2_u)); - test_one_duid(test3_s, test3_u, sizeof(test3_u)); - test_one_duid(test4_s, test4_u, sizeof(test4_u)); - - /* Invalid octal digit */ - g_assert(nm_dhcp_dhclient_unescape_duid(bad_s) == NULL); -} - -static void -test_read_duid_from_leasefile(void) -{ - const guint8 expected[] = - {0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6, 0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70}; - gs_unref_bytes GBytes *duid = NULL; - GError *error = NULL; - - duid = nm_dhcp_dhclient_read_duid(TEST_DIR "/test-dhclient-duid.leases", &error); - nmtst_assert_success(duid, error); - - g_assert(nm_g_bytes_equal_mem(duid, expected, G_N_ELEMENTS(expected))); -} - -static void -test_read_commented_duid_from_leasefile(void) -{ - GBytes *duid; - GError *error = NULL; - - duid = nm_dhcp_dhclient_read_duid(TEST_DIR "/test-dhclient-commented-duid.leases", &error); - g_assert_no_error(error); - g_assert(duid == NULL); -} - -/*****************************************************************************/ - -static void -_check_duid_impl(const guint8 *duid_bin, - gsize duid_len, - gboolean enforce_duid, - const char *old_content, - const char *new_content) -{ - gs_free_error GError *error = NULL; - gs_free char *contents = NULL; - gboolean success; - const char *path = NM_BUILD_BUILDDIR "/src/core/dhcp/tests/check-duid.lease"; - gs_unref_bytes GBytes *duid = NULL; - gsize contents_len; - - g_assert(duid_bin); - g_assert(duid_len > 0); - - if (!nm_str_is_empty(old_content) || nmtst_get_rand_bool()) { - success = g_file_set_contents(path, old_content ?: "", -1, &error); - nmtst_assert_success(success, error); - } else - nmtst_file_unlink_if_exists(path); - - duid = g_bytes_new(duid_bin, duid_len); - - success = nm_dhcp_dhclient_save_duid(path, duid, enforce_duid, &error); - nmtst_assert_success(success, error); - - success = g_file_get_contents(path, &contents, &contents_len, &error); - nmtst_assert_success(success, error); - g_assert(contents); - - nmtst_file_unlink(path); - - if (!nm_streq0(new_content, contents)) - g_error("FAILING:\n\nEXPECTED:\n%s\nACTUAL:\n%s\n\n", new_content, contents); - - g_assert_cmpstr(new_content, ==, contents); - g_assert_cmpint(contents_len, ==, strlen(contents)); -} - -#define _DUID(...) ((const guint8[]) {__VA_ARGS__}) - -#define _check_duid(duid, enforce_duid, old_content, new_content) \ - _check_duid_impl((duid), sizeof(duid), (enforce_duid), (old_content), (new_content)) - -static void -test_write_duid(void) -{ - _check_duid(_DUID(000, 001, 000, 001, 027, 'X', 0350, 'X', 0, '#', 025, 010, '~', 0254), - FALSE, - NULL, - "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"); - - _check_duid( - _DUID(000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302), - FALSE, - "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n", - "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n"); - - _check_duid(_DUID(000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302), - FALSE, - "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n", - "default-duid " - "\"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n#default-duid " - "\"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"); - _check_duid( - _DUID(000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302), - FALSE, - "### Commented old DUID ###\n#default-duid " - "\"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n", - "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n### " - "Commented old DUID ###\n#default-duid " - "\"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"); - - _check_duid( - _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xf), - FALSE, - "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:f;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\n}\n", - "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:f;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\n}\n"); - - _check_duid( - _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xf), - FALSE, - "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:f;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\r\n}\n", - "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:f;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\r\n}\n"); - - _check_duid( - _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xe), - FALSE, - "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:f;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\r\n}\n", - "default-duid \"\\252\\013\\314\\015\\356\\016\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:f;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\r\n}\n"); - - _check_duid( - _DUID(0xaa, 0xb, 0xcc, 0xd, 0xee, 0xe), - TRUE, - "default-duid \"\\252\\013\\314\\015\\356\\017\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:f;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\n}\n", - "default-duid \"\\252\\013\\314\\015\\356\\016\";\nlease6 {\n interface \"eth1\";\n " - " ia-na f1:ce:00:01 {\n starts 1671015678;\n renew 60;\n rebind 105;\n " - "iaaddr 192:168:121::1:112c {\n starts 1671015678;\n preferred-life 120;\n " - " max-life 120;\n }\n }\n option fqdn.encoded true;\n option " - "fqdn.server-update true;\n option fqdn.no-client-update false;\n option fqdn.fqdn " - "\"dff6de4fcb0f\";\n option fqdn.hostname \"dff6de4fcb0f\";\n option dhcp6.client-id " - "aa:b:cc:d:ee:e;\n option dhcp6.server-id 0:1:0:1:2b:2c:4d:1d:0:0:0:0:0:0;\n option " - "dhcp6.name-servers 192:168:121:0:ce0f:f1ff:fece:1;\n option dhcp6.fqdn " - "1:c:64:66:66:36:64:65:34:66:63:62:30:66;\n option dhcp6.status-code success " - "\"success\";\n}\n"); -} - -/*****************************************************************************/ - -static const char *interface1_orig = "interface \"eth0\" {\n" - "\talso request my-option;\n" - "\tinitial-delay 5;\n" - "}\n" - "interface \"eth1\" {\n" - "\talso request another-option;\n" - "\tinitial-delay 0;\n" - "}\n" - "\n" - "also request yet-another-option;\n"; - -static const char *interface1_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "initial-delay 5;\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "also request my-option;\n" - "also request yet-another-option;\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_interface1(void) -{ - test_config(interface1_orig, - interface1_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -static const char *interface2_orig = "interface eth0 {\n" - "\talso request my-option;\n" - "\tinitial-delay 5;\n" - " }\n" - "interface eth1 {\n" - "\tinitial-delay 0;\n" - "\trequest another-option;\n" - " } \n" - "\n" - "also request yet-another-option;\n"; - -static const char *interface2_expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "initial-delay 0;\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "request; # override dhclient defaults\n" - "also request another-option;\n" - "also request yet-another-option;\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - -static void -test_interface2(void) -{ - test_config(interface2_orig, - interface2_expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth1", - NULL, - NULL); -} - -static void -test_structured(void) -{ - gs_unref_bytes GBytes *new_client_id = NULL; - const guint8 bytes[] = "sad-and-useless"; - - static const char *const orig = - "interface \"eth0\" { \n" - " send host-name \"useless.example.com\";\n" - " hardware ethernet de:ad:80:86:ba:be;\n" - " send dhcp-client-identifier \"sad-and-useless\";\n" - " script \"/bin/useless\";\n" - " send dhcp-lease-time 8086;\n" - " request subnet-mask, broadcast-address, time-offset, routers,\n" - " domain-search, domain-name, host-name;\n" - " require subnet-mask;\n" - "} \n" - "\n" - " interface \"eth1\" { \n" - " send host-name \"sad.example.com\";\n" - " hardware ethernet de:ca:f6:66:ca:fe;\n" - " send dhcp-client-identifier \"useless-and-miserable\";\n" - " script \"/bin/miserable\";\n" - " send dhcp-lease-time 1337;\n" - " request subnet-mask, broadcast-address, time-offset, routers,\n" - " domain-search, domain-name, domain-name-servers, host-name;\n" - " require subnet-mask, domain-name-servers;\n" - " if not option domain-name = \"example.org\" {\n" - " prepend domain-name-servers 127.0.0.1;\n" - " } else {\n" - " prepend domain-name-servers 127.0.0.2;\n" - " } \n" - " } \n" - "\n" - "pseudo \"secondary\" \"eth0\" { \n" - " send dhcp-client-identifier \"sad-useless-and-secondary\";\n" - " script \"/bin/secondary\";\n" - " send host-name \"secondary.useless.example.com\";\n" - " send dhcp-lease-time 666;\n" - " request routers;\n" - " require routers;\n" - " } \n" - "\n" - " pseudo \"tertiary\" \"eth0\" { \n" - " send dhcp-client-identifier \"sad-useless-and-tertiary\";\n" - " script \"/bin/tertiary\";\n" - " send host-name \"tertiary.useless.example.com\";\n" - "} \n" - "\n" - " alias{ \n" - " interface \"eth0\";\n" - " fixed-address 192.0.2.1;\n" - " option subnet-mask 255.255.255.0;\n" - " } \n" - " lease { \n" - " interface \"eth0\";\n" - " fixed-address 192.0.2.2;\n" - " option subnet-mask 255.255.255.0;\n" - " } \n" - "if not option domain-name = \"example.org\" {\n" - " prepend domain-name-servers 127.0.0.1;\n" - " if not option domain-name = \"useless.example.com\" {\n" - " prepend domain-name-servers 127.0.0.2;\n" - " }\n" - "}\n"; - - static const char *const expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "send host-name \"useless.example.com\";\n" - "hardware ethernet de:ad:80:86:ba:be;\n" - "send dhcp-client-identifier \"sad-and-useless\";\n" - "send dhcp-lease-time 8086;\n" - "require subnet-mask;\n" - "if not option domain-name = \"example.org\" {\n" - "prepend domain-name-servers 127.0.0.1;\n" - "if not option domain-name = \"useless.example.com\" {\n" - "prepend domain-name-servers 127.0.0.2;\n" - "}\n" - "}\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "request; # override dhclient defaults\n" - "also request subnet-mask;\n" - "also request broadcast-address;\n" - "also request time-offset;\n" - "also request routers;\n" - "also request domain-search;\n" - "also request domain-name;\n" - "also request host-name;\n" - "also request rfc3442-classless-static-routes;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request ntp-servers;\n" - "also request root-path;\n" - "\n"; - - new_client_id = g_bytes_new(bytes, sizeof(bytes) - 1); - test_config(orig, - expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - new_client_id, - "eth0", - NULL, - NULL); -} - -static void -test_config_req_intf(void) -{ - static const char *const orig = "request subnet-mask, broadcast-address, routers,\n" - "\trfc3442-classless-static-routes,\n" - "\tinterface-mtu, host-name, domain-name, domain-search,\n" - "\tdomain-name-servers, nis-domain, nis-servers,\n" - "\tnds-context, nds-servers, nds-tree-name,\n" - "\tnetbios-name-servers, netbios-dd-server,\n" - "\tnetbios-node-type, netbios-scope, ntp-servers;\n" - ""; - static const char *const expected = - "# Created by NetworkManager\n" - "# Merged from /path/to/dhclient.conf\n" - "\n" - "\n" - "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n" - "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n" - "option wpad code 252 = string;\n" - "\n" - "request; # override dhclient defaults\n" - "also request subnet-mask;\n" - "also request broadcast-address;\n" - "also request routers;\n" - "also request rfc3442-classless-static-routes;\n" - "also request interface-mtu;\n" - "also request host-name;\n" - "also request domain-name;\n" - "also request domain-search;\n" - "also request domain-name-servers;\n" - "also request nis-domain;\n" - "also request nis-servers;\n" - "also request nds-context;\n" - "also request nds-servers;\n" - "also request nds-tree-name;\n" - "also request netbios-name-servers;\n" - "also request netbios-dd-server;\n" - "also request netbios-node-type;\n" - "also request netbios-scope;\n" - "also request ntp-servers;\n" - "also request ms-classless-static-routes;\n" - "also request static-routes;\n" - "also request wpad;\n" - "also request root-path;\n" - "\n"; - - test_config(orig, - expected, - AF_INET, - NULL, - 0, - FALSE, - NM_DHCP_HOSTNAME_FLAG_NONE, - NULL, - NULL, - "eth0", - NULL, - NULL); -} - -/*****************************************************************************/ - -NMTST_DEFINE(); - -int -main(int argc, char **argv) -{ - nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT"); - - g_test_add_func("/dhcp/dhclient/orig_missing", test_orig_missing); - g_test_add_func("/dhcp/dhclient/orig_missing_add_mud_url", test_orig_missing_add_mud_url); - g_test_add_func("/dhcp/dhclient/override_client_id", test_override_client_id); - g_test_add_func("/dhcp/dhclient/quote_client_id/1", test_quote_client_id); - g_test_add_func("/dhcp/dhclient/quote_client_id/2", test_quote_client_id_2); - g_test_add_func("/dhcp/dhclient/hex_zero_client_id", test_hex_zero_client_id); - g_test_add_func("/dhcp/dhclient/ascii_client_id", test_ascii_client_id); - g_test_add_func("/dhcp/dhclient/hex_single_client_id", test_hex_single_client_id); - g_test_add_func("/dhcp/dhclient/existing-hex-client-id", test_existing_hex_client_id); - g_test_add_func("/dhcp/dhclient/existing-client-id", test_existing_escaped_client_id); - g_test_add_func("/dhcp/dhclient/existing-ascii-client-id", test_existing_ascii_client_id); - g_test_add_func("/dhcp/dhclient/none-client-id", test_none_client_id); - g_test_add_func("/dhcp/dhclient/missing-client-id", test_missing_client_id); - g_test_add_func("/dhcp/dhclient/fqdn", test_fqdn); - g_test_add_func("/dhcp/dhclient/fqdn_options_override", test_fqdn_options_override); - g_test_add_func("/dhcp/dhclient/override_hostname", test_override_hostname); - g_test_add_func("/dhcp/dhclient/override_hostname6", test_override_hostname6); - g_test_add_func("/dhcp/dhclient/nonfqdn_hostname6", test_nonfqdn_hostname6); - g_test_add_func("/dhcp/dhclient/existing_req", test_existing_req); - g_test_add_func("/dhcp/dhclient/existing_alsoreq", test_existing_alsoreq); - g_test_add_func("/dhcp/dhclient/existing_multiline_alsoreq", test_existing_multiline_alsoreq); - g_test_add_func("/dhcp/dhclient/duids", test_duids); - g_test_add_func("/dhcp/dhclient/interface/1", test_interface1); - g_test_add_func("/dhcp/dhclient/interface/2", test_interface2); - g_test_add_func("/dhcp/dhclient/config/req_intf", test_config_req_intf); - g_test_add_func("/dhcp/dhclient/structured", test_structured); - - g_test_add_func("/dhcp/dhclient/read_duid_from_leasefile", test_read_duid_from_leasefile); - g_test_add_func("/dhcp/dhclient/read_commented_duid_from_leasefile", - test_read_commented_duid_from_leasefile); - - g_test_add_func("/dhcp/dhclient/test_write_duid", test_write_duid); - - return g_test_run(); -} diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index ec33c464..f1a19d01 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -371,7 +371,7 @@ _ASSERT_dns_config_ip_data(const NMDnsConfigIPData *ip_data) gboolean has_default = FALSE; gsize i; - for (i = 0; ip_data->domains.search && ip_data->domains.search; i++) { + for (i = 0; ip_data->domains.search && ip_data->domains.search[i]; i++) { const char *d = ip_data->domains.search[i]; d = nm_utils_parse_dns_domain(d, NULL); diff --git a/src/core/main.c b/src/core/main.c index 8d519c00..7299a91c 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -298,12 +298,6 @@ main(int argc, char *argv[]) _nm_utils_is_manager_process = TRUE; - /* Known to cause a possible deadlock upon GDBus initialization: - * https://bugzilla.gnome.org/show_bug.cgi?id=674885 */ - g_type_ensure(G_TYPE_SOCKET); - g_type_ensure(G_TYPE_DBUS_CONNECTION); - g_type_ensure(NM_TYPE_DBUS_MANAGER); - /* we determine a first-start (contrary to a restart during the same boot) * based on the existence of NM_CONFIG_DEVICE_STATE_DIR directory. */ config_cli = nm_config_cmd_line_options_new( @@ -328,6 +322,12 @@ main(int argc, char *argv[]) exit(result); } + /* Known to cause a possible deadlock upon GDBus initialization: + * https://bugzilla.gnome.org/show_bug.cgi?id=674885 */ + g_type_ensure(G_TYPE_SOCKET); + g_type_ensure(G_TYPE_DBUS_CONNECTION); + g_type_ensure(NM_TYPE_DBUS_MANAGER); + nm_main_utils_ensure_not_running_pidfile(global_opt.pidfile); nm_main_utils_ensure_statedir(); @@ -461,14 +461,8 @@ main(int argc, char *argv[]) /* the first access to State causes the file to be read (and possibly print a warning) */ nm_config_state_get(config); - nm_log_dbg(LOGD_CORE, - "WEXT support is %s", -#if HAVE_WEXT - "enabled" -#else - "disabled" -#endif - ); + nm_log_dbg(LOGD_CORE, "WEXT support is %s", HAVE_WEXT ? "enabled" : "disabled"); + nm_log_dbg(LOGD_CORE, "CLAT support is %s", HAVE_CLAT ? "enabled" : "disabled"); if (!_dbus_manager_init(config)) goto done_no_manager; diff --git a/src/core/meson.build b/src/core/meson.build index b1d7f2e7..8ff3d9f9 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -32,6 +32,15 @@ install_data( core_plugins = [] +subdir('bpf') + +base_sources_addon = [] +base_deps_addon = [] +if enable_clat + base_sources_addon += [clat_skel_h] + base_deps_addon += [libbpf] +endif + libNetworkManagerBase = static_library( 'NetworkManagerBase', sources: files( @@ -55,20 +64,20 @@ libNetworkManagerBase = static_library( 'nm-l3cfg.c', 'nm-bond-manager.c', 'nm-ip-config.c', - ), + ) + base_sources_addon, dependencies: [ core_default_dep, libnm_core_public_dep, + libndp_dep, libsystemd_dep, libudev_dep, - ], + ] + base_deps_addon, ) nm_deps = [ libnm_core_public_dep, core_default_dep, dl_dep, - libndp_dep, libudev_dep, logind_dep, ] @@ -119,8 +128,6 @@ libNetworkManager = static_library( 'devices/nm-device-wireguard.c', 'devices/nm-device-wpan.c', 'devices/nm-lldp-listener.c', - 'dhcp/nm-dhcp-dhclient.c', - 'dhcp/nm-dhcp-dhclient-utils.c', 'dhcp/nm-dhcp-dhcpcd.c', 'dhcp/nm-dhcp-listener.c', 'dns/nm-dns-dnsmasq.c', diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c index c19dcc91..69213da6 100644 --- a/src/core/ndisc/nm-lndp-ndisc.c +++ b/src/core/ndisc/nm-lndp-ndisc.c @@ -401,6 +401,30 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) } } +#if HAVE_CLAT + /* PREF64 */ + ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREF64) { + NMNDiscPref64 pref64; + + pref64 = (NMNDiscPref64) { + .prefix = *ndp_msg_opt_pref64_prefix(msg, offset), + .plen = ndp_msg_opt_pref64_prefix_length(msg, offset), + .gateway = gateway.address, + .gateway_preference = gateway.preference, + .expiry_msec = + _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_pref64_lifetime(msg, offset)), + .gateway_expiry_msec = gateway.expiry_msec, + }; + + /* libndp should only return lengths defined in RFC 8781 */ + nm_assert(NM_IN_SET(pref64.plen, 96, 64, 56, 48, 40, 32)); + + if (nm_ndisc_add_pref64(ndisc, &pref64, now_msec)) { + changed |= NM_NDISC_CONFIG_PREF64; + } + } +#endif + nm_ndisc_ra_received(ndisc, now_msec, changed); return 0; } @@ -463,7 +487,8 @@ send_ra(NMNDisc *ndisc, GError **error) struct in6_addr *addr; struct ndp_msg *msg; guint i; - nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE); + nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE); + gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); errsv = ndp_msg_new(&msg, NDP_MSG_RA); if (errsv) { @@ -507,13 +532,9 @@ send_ra(NMNDisc *ndisc, GError **error) prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK; prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; prefix->nd_opt_pi_valid_time = - htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, - address->expiry_msec, - TRUE)); + htonl(_nm_ndisc_lifetime_from_expiry(now_msec, address->expiry_msec, TRUE)); prefix->nd_opt_pi_preferred_time = - htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, - address->expiry_preferred_msec, - TRUE)); + htonl(_nm_ndisc_lifetime_from_expiry(now_msec, address->expiry_preferred_msec, TRUE)); prefix->nd_opt_pi_prefix.s6_addr32[0] = address->address.s6_addr32[0]; prefix->nd_opt_pi_prefix.s6_addr32[1] = address->address.s6_addr32[1]; prefix->nd_opt_pi_prefix.s6_addr32[2] = 0; diff --git a/src/core/ndisc/nm-ndisc-private.h b/src/core/ndisc/nm-ndisc-private.h index 1479e566..90ec032d 100644 --- a/src/core/ndisc/nm-ndisc-private.h +++ b/src/core/ndisc/nm-ndisc-private.h @@ -14,6 +14,7 @@ struct _NMNDiscDataInternal { NMNDiscData public; GArray *gateways; GArray *addresses; + GArray *pref64; GArray *routes; GArray *dns_servers; GArray *dns_domains; @@ -28,6 +29,7 @@ gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gi gboolean nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec); gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec); +gboolean nm_ndisc_add_pref64(NMNDisc *ndisc, const NMNDiscPref64 *new_item, gint64 now_msec); gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec); gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec); diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c index 1a2bf480..2056b5f8 100644 --- a/src/core/ndisc/nm-ndisc.c +++ b/src/core/ndisc/nm-ndisc.c @@ -34,6 +34,7 @@ #define _SIZE_MAX_ROUTES 1000u #define _SIZE_MAX_DNS_SERVERS 64u #define _SIZE_MAX_DNS_DOMAINS 64u +#define _SIZE_MAX_PREF64 8u /*****************************************************************************/ @@ -109,7 +110,8 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx, int ifindex, const NMNDiscData *rdata, NMSettingIP6ConfigPrivacy ip6_privacy, - NMUtilsIPv6IfaceId *token) + NMUtilsIPv6IfaceId *token, + const char *network_id) { nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; guint32 ifa_flags; @@ -211,13 +213,21 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx, for (i = 0; i < rdata->dns_domains_n; i++) nm_l3_config_data_add_search(l3cd, AF_INET6, rdata->dns_domains[i].domain); + if (rdata->pref64_n > 0) { + nm_l3_config_data_set_pref64(l3cd, rdata->pref64[0].prefix, rdata->pref64[0].plen); + } else { + nm_l3_config_data_set_pref64_valid(l3cd, FALSE); + } + nm_l3_config_data_set_ndisc_hop_limit(l3cd, rdata->hop_limit); nm_l3_config_data_set_ndisc_reachable_time_msec(l3cd, rdata->reachable_time_ms); nm_l3_config_data_set_ndisc_retrans_timer_msec(l3cd, rdata->retrans_timer_ms); - nm_l3_config_data_set_ip6_mtu(l3cd, rdata->mtu); + nm_l3_config_data_set_ip6_mtu_ra(l3cd, rdata->mtu); if (token) nm_l3_config_data_set_ip6_token(l3cd, *token); + if (network_id) + nm_l3_config_data_set_network_id(l3cd, network_id); return g_steal_pointer(&l3cd); } @@ -416,6 +426,7 @@ _data_complete(NMNDiscDataInternal *data) _SET(data, gateways); _SET(data, addresses); _SET(data, routes); + _SET(data, pref64); _SET(data, dns_servers); _SET(data, dns_domains); #undef _SET @@ -437,7 +448,8 @@ nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed) nm_l3cfg_get_ifindex(priv->config.l3cfg), rdata, priv->config.ip6_privacy, - priv->iid_is_token ? &priv->iid : NULL); + priv->iid_is_token ? &priv->iid : NULL, + priv->config.network_id); l3cd = nm_l3_config_data_seal(l3cd); if (!nm_l3_config_data_equal(priv->l3cd, l3cd)) @@ -761,6 +773,59 @@ nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec } gboolean +nm_ndisc_add_pref64(NMNDisc *ndisc, const NMNDiscPref64 *new_item, gint64 now_msec) +{ + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + guint i; + guint insert_idx = G_MAXUINT; + + for (i = 0; i < rdata->pref64->len;) { + NMNDiscPref64 *item = &nm_g_array_index(rdata->pref64, NMNDiscPref64, i); + + if (item->plen == new_item->plen && IN6_ARE_ADDR_EQUAL(&item->prefix, &new_item->prefix) + && IN6_ARE_ADDR_EQUAL(&item->gateway, &new_item->gateway)) { + if (new_item->expiry_msec <= now_msec) { + g_array_remove_index(rdata->pref64, i); + return TRUE; + } + + if (item->gateway_preference != new_item->gateway_preference) { + g_array_remove_index(rdata->pref64, i); + continue; + } + + item->gateway_expiry_msec = new_item->gateway_expiry_msec; + + if (item->expiry_msec == new_item->expiry_msec) + return FALSE; + + item->expiry_msec = new_item->expiry_msec; + return TRUE; + } + + /* Put before less preferable gateways. */ + if (_preference_to_priority(item->gateway_preference) + < _preference_to_priority(new_item->gateway_preference) + && insert_idx == G_MAXUINT) + insert_idx = i; + + i++; + } + + if (rdata->pref64->len >= _SIZE_MAX_PREF64) + return FALSE; + + if (new_item->expiry_msec <= now_msec) + return FALSE; + + g_array_insert_val(rdata->pref64, + insert_idx == G_MAXUINT ? rdata->pref64->len : insert_idx, + *new_item); + + return TRUE; +} + +gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec) { NMNDiscPrivate *priv; @@ -1059,7 +1124,8 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd) const NMPObject *obj; guint len; guint i; - gint32 fake_now = NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000; + gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + gint32 now = now_msec / 1000; nm_assert(NM_IS_NDISC(ndisc)); nm_assert(nm_ndisc_get_node_type(ndisc) == NM_NDISC_NODE_TYPE_ROUTER); @@ -1082,16 +1148,15 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd) lifetime = nmp_utils_lifetime_get(addr->timestamp, addr->lifetime, addr->preferred, - &fake_now, + &now, &preferred); if (!lifetime) continue; a = (NMNDiscAddress) { - .address = addr->address, - .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime), - .expiry_preferred_msec = - _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred), + .address = addr->address, + .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, lifetime), + .expiry_preferred_msec = _nm_ndisc_lifetime_to_expiry(now_msec, preferred), }; if (nm_ndisc_add_address(ndisc, &a, 0, FALSE)) @@ -1111,8 +1176,7 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd) n = (NMNDiscDNSServer) { .address = a.addr6, - .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, - NM_NDISC_ROUTER_LIFETIME), + .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, NM_NDISC_ROUTER_LIFETIME), }; if (nm_ndisc_add_dns_server(ndisc, &n, G_MININT64)) changed = TRUE; @@ -1127,8 +1191,7 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd) n = (NMNDiscDNSDomain) { .domain = (char *) strvarr[i], - .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, - NM_NDISC_ROUTER_LIFETIME), + .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, NM_NDISC_ROUTER_LIFETIME), }; if (nm_ndisc_add_dns_domain(ndisc, &n, G_MININT64)) changed = TRUE; @@ -1400,6 +1463,17 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed) nm_icmpv6_router_pref_to_string(route->preference, str_pref, sizeof(str_pref)), get_exp(str_exp, now_msec, route)); } + for (i = 0; i < rdata->pref64->len; i++) { + const NMNDiscPref64 *pref64 = &nm_g_array_index(rdata->pref64, NMNDiscPref64, i); + char addrstr2[NM_INET_ADDRSTRLEN]; + + _LOGD(" pref64 %s/%u via %s exp %s", + nm_inet6_ntop(&pref64->prefix, addrstr), + pref64->plen, + nm_inet6_ntop(&pref64->gateway, addrstr2), + get_exp(str_exp, now_msec, pref64)); + } + for (i = 0; i < rdata->dns_servers->len; i++) { const NMNDiscDNSServer *dns_server = &nm_g_array_index(rdata->dns_servers, NMNDiscDNSServer, i); @@ -1486,7 +1560,7 @@ clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint } if (i != j) { - *changed = NM_NDISC_CONFIG_ADDRESSES; + *changed |= NM_NDISC_CONFIG_ADDRESSES; g_array_set_size(rdata->addresses, j); } @@ -1520,11 +1594,50 @@ clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 g_array_set_size(rdata->routes, j); } - if (_array_set_size_max(rdata->gateways, _SIZE_MAX_ROUTES)) + if (_array_set_size_max(rdata->routes, _SIZE_MAX_ROUTES)) *changed |= NM_NDISC_CONFIG_ROUTES; } static void +clean_pref64(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec) +{ + NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; + NMNDiscPref64 *arr; + guint i; + guint j; + + if (rdata->pref64->len == 0) + return; + + arr = &nm_g_array_first(rdata->pref64, NMNDiscPref64); + + for (i = 0, j = 0; i < rdata->pref64->len; i++) { + if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec) + || !expiry_next(now_msec, + arr[i].gateway_expiry_msec, + next_msec)) { /* no gateway no party */ + if (i == 0) { + /* Emit the changed signal only when the first PREF64 expires, + * because only the first item is exported into the l3cd. Changes + * in other PREF64s are not relevant. */ + *changed |= NM_NDISC_CONFIG_PREF64; + } + continue; + } + + if (i != j) + arr[j] = arr[i]; + j++; + } + + if (i != j) { + g_array_set_size(rdata->pref64, j); + } + + _array_set_size_max(rdata->pref64, _SIZE_MAX_PREF64); +} + +static void clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec) { NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; @@ -1550,7 +1663,7 @@ clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gi g_array_set_size(rdata->dns_servers, j); } - if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_SERVERS)) + if (_array_set_size_max(rdata->dns_servers, _SIZE_MAX_DNS_SERVERS)) *changed |= NM_NDISC_CONFIG_DNS_SERVERS; } @@ -1580,12 +1693,12 @@ clean_dns_domains(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gi j++; } - if (i != 0) { + if (i != j) { *changed |= NM_NDISC_CONFIG_DNS_DOMAINS; g_array_set_size(rdata->dns_domains, j); } - if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_DOMAINS)) + if (_array_set_size_max(rdata->dns_domains, _SIZE_MAX_DNS_DOMAINS)) *changed |= NM_NDISC_CONFIG_DNS_DOMAINS; } @@ -1600,6 +1713,7 @@ check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed) clean_gateways(ndisc, now_msec, &changed, &next_msec); clean_addresses(ndisc, now_msec, &changed, &next_msec); clean_routes(ndisc, now_msec, &changed, &next_msec); + clean_pref64(ndisc, now_msec, &changed, &next_msec); clean_dns_servers(ndisc, now_msec, &changed, &next_msec); clean_dns_domains(ndisc, now_msec, &changed, &next_msec); @@ -1919,6 +2033,7 @@ nm_ndisc_init(NMNDisc *ndisc) rdata->gateways = g_array_new(FALSE, FALSE, sizeof(NMNDiscGateway)); rdata->addresses = g_array_new(FALSE, FALSE, sizeof(NMNDiscAddress)); rdata->routes = g_array_new(FALSE, FALSE, sizeof(NMNDiscRoute)); + rdata->pref64 = g_array_new(FALSE, FALSE, sizeof(NMNDiscPref64)); rdata->dns_servers = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSServer)); rdata->dns_domains = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSDomain)); g_array_set_clear_func(rdata->dns_domains, dns_domain_free); @@ -1951,6 +2066,7 @@ finalize(GObject *object) g_array_unref(rdata->gateways); g_array_unref(rdata->addresses); g_array_unref(rdata->routes); + g_array_unref(rdata->pref64); g_array_unref(rdata->dns_servers); g_array_unref(rdata->dns_domains); diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h index 8f1a12a2..cdd9a676 100644 --- a/src/core/ndisc/nm-ndisc.h +++ b/src/core/ndisc/nm-ndisc.h @@ -50,18 +50,6 @@ const char *nm_ndisc_dhcp_level_to_string(NMNDiscDHCPLevel level); * unit of it is milliseconds. But of course, infinity has not really a unit. */ #define NM_NDISC_EXPIRY_INFINITY G_MAXINT64 -/* in common cases, the expiry_msec tracks the timestamp in nm_utils_get_monotonic_timestamp_mses() - * timestamp when the item expires. - * - * When we configure an NMNDiscAddress to be announced via the router advertisement, - * then that address does not have a fixed expiry point in time, instead, the expiry - * really contains the lifetime from the moment when we send the router advertisement. - * In that case, the expiry_msec is more a "lifetime" that starts counting at timestamp - * zero. - * - * The unit is milliseconds (but of course, the timestamp is zero, so it doesn't really matter). */ -#define NM_NDISC_EXPIRY_BASE_TIMESTAMP ((gint64) 0) - static inline gint64 _nm_ndisc_lifetime_to_expiry(gint64 now_msec, guint32 lifetime) { @@ -119,6 +107,15 @@ typedef struct _NMNDiscRoute { bool duplicate : 1; } NMNDiscRoute; +typedef struct _NMNDiscPref64 { + struct in6_addr prefix; + struct in6_addr gateway; + gint64 expiry_msec; + gint64 gateway_expiry_msec; + NMIcmpv6RouterPref gateway_preference; + guint8 plen; +} NMNDiscPref64; + typedef struct { struct in6_addr address; gint64 expiry_msec; @@ -141,6 +138,7 @@ typedef enum { NM_NDISC_CONFIG_MTU = 1 << 7, NM_NDISC_CONFIG_REACHABLE_TIME = 1 << 8, NM_NDISC_CONFIG_RETRANS_TIMER = 1 << 9, + NM_NDISC_CONFIG_PREF64 = 1 << 10, } NMNDiscConfigMap; typedef enum { @@ -188,12 +186,14 @@ typedef struct { guint gateways_n; guint addresses_n; guint routes_n; + guint pref64_n; guint dns_servers_n; guint dns_domains_n; const NMNDiscGateway *gateways; const NMNDiscAddress *addresses; const NMNDiscRoute *routes; + const NMNDiscPref64 *pref64; const NMNDiscDNSServer *dns_servers; const NMNDiscDNSDomain *dns_domains; } NMNDiscData; @@ -282,6 +282,7 @@ struct _NML3ConfigData *nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_id int ifindex, const NMNDiscData *rdata, NMSettingIP6ConfigPrivacy ip6_privacy, - NMUtilsIPv6IfaceId *token); + NMUtilsIPv6IfaceId *token, + const char *network_id); #endif /* __NETWORKMANAGER_NDISC_H__ */ diff --git a/src/core/nm-config.c b/src/core/nm-config.c index d8bf2e3e..85e36ac8 100644 --- a/src/core/nm-config.c +++ b/src/core/nm-config.c @@ -892,6 +892,7 @@ static const ConfigGroup config_groups[] = { .is_prefix = TRUE, .keys = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT, NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER, + NM_CONFIG_KEYFILE_KEY_DEVICE_CHECK_CONNECTIVITY, NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED, NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS, NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION, @@ -1256,34 +1257,28 @@ read_base_config(GKeyFile *keyfile, return TRUE; } +/* We want to use GDir instead of GFile here to avoid loading GVFS modules and + * initalizing DBUS infra for communicating with GVFS. + * https://redhat.atlassian.net/browse/RHEL-140113 + */ static GPtrArray * _get_config_dir_files(const char *config_dir) { - GFile *dir; - GFileEnumerator *direnum; - GFileInfo *info; - GPtrArray *confs; - const char *name; - + GDir *dir; + GPtrArray *confs; + const char *name; g_return_val_if_fail(config_dir, NULL); - confs = g_ptr_array_new_with_free_func(g_free); if (!*config_dir) return confs; - - dir = g_file_new_for_path(config_dir); - direnum = g_file_enumerate_children(dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, NULL); - if (direnum) { - while ((info = g_file_enumerator_next_file(direnum, NULL, NULL))) { - name = g_file_info_get_name(info); + dir = g_dir_open(config_dir, 0, NULL); + if (dir) { + while ((name = g_dir_read_name(dir))) { if (NM_STR_HAS_SUFFIX(name, ".conf")) g_ptr_array_add(confs, g_strdup(name)); - g_object_unref(info); } - g_object_unref(direnum); + g_dir_close(dir); } - g_object_unref(dir); - g_ptr_array_sort(confs, nm_strcmp_p); return confs; } @@ -1339,8 +1334,7 @@ read_entire_config(const NMConfigCmdLineOptions *cli, run_config_dir = RUN_CONFIG_DIR; /* create a default configuration file. */ - keyfile = nm_config_create_keyfile(); - + keyfile = nm_config_create_keyfile(); system_confs = _get_config_dir_files(system_config_dir); confs = _get_config_dir_files(config_dir); run_confs = _get_config_dir_files(run_config_dir); @@ -3292,7 +3286,6 @@ init_sync(GInitable *initable, GCancellable *cancellable, GError **error) g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "unspecified error"); g_return_val_if_reached(FALSE); } - s = priv->cli.config_dir ?: "" DEFAULT_CONFIG_DIR; priv->config_dir = g_strdup(s[0] == '/' ? s : ""); @@ -3300,12 +3293,10 @@ init_sync(GInitable *initable, GCancellable *cancellable, GError **error) if (s[0] != '/' || nm_streq(s, priv->config_dir)) s = ""; priv->system_config_dir = g_strdup(s); - if (priv->cli.intern_config_file) priv->intern_config_file = g_strdup(priv->cli.intern_config_file); else priv->intern_config_file = g_strdup(DEFAULT_INTERN_CONFIG_FILE); - warnings = g_ptr_array_new_with_free_func(g_free); keyfile = read_entire_config(&priv->cli, diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index deac04e7..ee05bedd 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -21,6 +21,10 @@ #include <linux/if_infiniband.h> #include <net/if_arp.h> #include <net/ethernet.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> +#include <netinet/ip6.h> +#include <linux/if_packet.h> #include "libnm-glib-aux/nm-uuid.h" #include "libnm-platform/nmp-base.h" @@ -5003,6 +5007,469 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_activation_type_to_string, /*****************************************************************************/ typedef struct { + NMIPAddrTyped address; + char *addr_str; + GTask *task; + GSource *timeout_source; + GSource *retry_source; + GSource *input_source; + gulong cancellable_id; + int ifindex; + int socket; + guint16 seq; +} PingInfo; + +#define _NMLOG2_PREFIX_NAME "ping" +#define _NMLOG2_DOMAIN LOGD_CORE +#define _NMLOG2(level, info, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG2_DOMAIN))) { \ + PingInfo *_info = (info); \ + \ + _nm_log((level), \ + (_NMLOG2_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + _NMLOG2_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",if=%d,%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(_info), \ + _info->ifindex, \ + _info->addr_str _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +static void +ping_complete(PingInfo *info, GError *error) +{ + nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id); + + if (error && !nm_utils_error_is_cancelled(error)) { + _LOG2T(info, "terminated with error: %s", error->message); + } + + if (error) + g_task_return_error(info->task, error); + else + g_task_return_boolean(info->task, TRUE); + + nm_clear_g_source_inst(&info->timeout_source); + nm_clear_g_source_inst(&info->retry_source); + nm_clear_g_source_inst(&info->input_source); + nm_clear_g_free(&info->addr_str); + nm_clear_fd(&info->socket); + g_object_unref(info->task); + + g_free(info); +} + +static gboolean +ping_socket_data_cb(int fd, GIOCondition condition, gpointer user_data) +{ + PingInfo *info = user_data; + ssize_t len; + union { + struct icmphdr icmph; + struct icmp6_hdr icmp6h; + } pkt; + + len = recv(fd, &pkt, sizeof(pkt), 0); + + if (len < 0) + return G_SOURCE_CONTINUE; + + if (info->address.addr_family == AF_INET) { + if (len >= sizeof(struct icmphdr) && pkt.icmph.type == ICMP_ECHOREPLY) { + _LOG2T(info, "received echo-reply with seq %hu", ntohs(pkt.icmph.un.echo.sequence)); + ping_complete(info, NULL); + return G_SOURCE_CONTINUE; + } + } else { + if (len >= sizeof(struct icmp6_hdr) && pkt.icmp6h.icmp6_type == ICMP6_ECHO_REPLY) { + _LOG2T(info, "received echo-reply with seq %hu", ntohs(pkt.icmp6h.icmp6_seq)); + ping_complete(info, NULL); + return G_SOURCE_CONTINUE; + } + } + + return G_SOURCE_CONTINUE; +} + +static void +ping_send(PingInfo *info) +{ + const bool IS_IPv4 = NM_IS_IPv4(info->address.addr_family); + union { + struct sockaddr_in6 sa6; + struct sockaddr_in sa4; + } sa; + union { + struct icmphdr icmph; + struct icmp6_hdr icmp6h; + } pkt; + socklen_t sa_len; + size_t pkt_len; + nm_be32_t ifindex_be; + int errsv; + + info->seq++; + + if (info->socket < 0) { + info->socket = socket(info->address.addr_family, + SOCK_DGRAM | SOCK_CLOEXEC, + IS_IPv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6); + if (info->socket < 0) { + errsv = errno; + _LOG2T(info, "socket creation failed: %s", nm_strerror_native(errsv)); + /* Try again at the next iteration */ + return; + } + + memset(&sa, 0, sizeof(sa)); + if (IS_IPv4) { + sa.sa4.sin_family = AF_INET; + sa.sa4.sin_addr.s_addr = info->address.addr.addr4; + sa_len = sizeof(struct sockaddr_in); + } else { + sa.sa6.sin6_family = AF_INET6; + sa.sa6.sin6_addr = info->address.addr.addr6; + if (IN6_IS_ADDR_LINKLOCAL(&info->address.addr.addr6)) + sa.sa6.sin6_scope_id = info->ifindex; + sa_len = sizeof(struct sockaddr_in6); + } + + /* setsockopt(IP*_UNICAST_IF) must be called *before* connecting + * the socket, otherwise it doesn't have any effect */ + ifindex_be = htonl(info->ifindex); + if (setsockopt(info->socket, + IS_IPv4 ? IPPROTO_IP : IPPROTO_IPV6, + IS_IPv4 ? IP_UNICAST_IF : IPV6_UNICAST_IF, + &ifindex_be, + sizeof(ifindex_be))) { + errsv = errno; + _LOG2T(info, + "failed to bind the socket to the interface: %s", + nm_strerror_native(errsv)); + /* Try again at the next iteration */ + nm_clear_fd(&info->socket); + return; + } + + /* Connect the socket so that the kernel only delivers us packets + * coming from the given remote address */ + if (connect(info->socket, (struct sockaddr *) &sa, sa_len) < 0) { + errsv = errno; + _LOG2T(info, "failed to connect the socket: %s", nm_strerror_native(errsv)); + /* try again at the next iteration */ + nm_clear_fd(&info->socket); + return; + } + + info->input_source = nm_g_unix_fd_source_new(info->socket, + G_IO_IN, + G_PRIORITY_DEFAULT, + ping_socket_data_cb, + info, + NULL); + g_source_attach(info->input_source, g_task_get_context(info->task)); + } + + if (IS_IPv4) { + memset(&pkt.icmph, 0, sizeof(struct icmphdr)); + pkt.icmph.type = ICMP_ECHO; + pkt.icmph.un.echo.sequence = htons(info->seq); + pkt_len = sizeof(struct icmphdr); + } else { + memset(&pkt.icmp6h, 0, sizeof(struct icmp6_hdr)); + pkt.icmp6h.icmp6_type = ICMP6_ECHO_REQUEST; + pkt.icmp6h.icmp6_seq = htons(info->seq); + pkt_len = sizeof(struct icmp6_hdr); + } + /* The kernel will automatically set the ID ICMP field and filter + * incoming packets by the same ID */ + + if (send(info->socket, &pkt, pkt_len, 0) < 0) { + errsv = errno; + _LOG2T(info, "error sending echo-request #%u: %s", info->seq, nm_strerror_native(errsv)); + return; + } + + _LOG2T(info, "sent echo-request #%u", info->seq); +} + +static gboolean +ping_timeout_cb(gpointer user_data) +{ + PingInfo *info = user_data; + + _LOG2T(info, "timeout"); + + nm_clear_g_source_inst(&info->timeout_source); + ping_complete(info, g_error_new_literal(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "timeout")); + + return G_SOURCE_CONTINUE; +} + +static gboolean +ping_retry_cb(gpointer user_data) +{ + PingInfo *info = user_data; + + ping_send(info); + + return G_SOURCE_CONTINUE; +} + +static void +ping_cancelled(GObject *object, gpointer user_data) +{ + PingInfo *info = user_data; + GError *error = NULL; + + nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id); + nm_utils_error_set_cancelled(&error, FALSE, NULL); + ping_complete(info, error); +} + +void +nm_utils_ping_host(NMIPAddrTyped address, + int ifindex, + guint timeout_sec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer cb_data) +{ + PingInfo *info; + char buf[NM_INET_ADDRSTRLEN]; + gulong signal_id; + + nm_assert(ifindex > 0); + nm_assert(G_IS_CANCELLABLE(cancellable)); + nm_assert(callback); + nm_assert(cb_data); + + info = g_new0(PingInfo, 1); + info->address = address; + info->ifindex = ifindex; + info->task = nm_g_task_new(NULL, cancellable, nm_utils_ping_host, callback, cb_data); + info->socket = -1; + + nm_inet_ntop(address.addr_family, address.addr.addr_ptr, buf); + info->addr_str = g_strdup(buf); + + _LOG2T(info, "started"); + + if (timeout_sec > 0) { + info->timeout_source = nm_g_timeout_source_new_seconds(timeout_sec, + G_PRIORITY_DEFAULT, + ping_timeout_cb, + info, + NULL); + g_source_attach(info->timeout_source, g_task_get_context(info->task)); + } + + info->retry_source = + nm_g_timeout_source_new_seconds(1, G_PRIORITY_DEFAULT, ping_retry_cb, info, NULL); + g_source_attach(info->retry_source, g_task_get_context(info->task)); + + signal_id = g_cancellable_connect(cancellable, G_CALLBACK(ping_cancelled), info, NULL); + if (signal_id == 0) { + /* the callback was invoked synchronously, which destroyed @info. + * We must not touch it anymore. */ + return; + } + info->cancellable_id = signal_id; + + ping_send(info); +} + +gboolean +nm_utils_ping_host_finish(GAsyncResult *result, GError **error) +{ + GTask *task = G_TASK(result); + + nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_ping_host)); + + return g_task_propagate_boolean(task, error); +} + +/*****************************************************************************/ + +/* + * nm_utils_icmp6_checksum: + * @ip6_src: pointer to the IPv6 source address + * @data_len: length of the data + * @data: the data on which to compute the checksum + * + * Computes the ICMP6 checksum over @data (with length @data_len) and the IPv6 + * pseudo-header. @ip6_src points to the source address in the IPv6 header. + */ +uint16_t +nm_utils_icmp6_checksum(const void *ip6_src, size_t data_len, const void *data) +{ + uint32_t sum = 0; + const uint16_t *ptr; + const uint8_t *ptr8; + size_t i; + + /* Pseudo-header: source address */ + ptr = (const uint16_t *) ip6_src; + for (i = 0; i < 8; i++) + sum += *ptr++; + + /* Pseudo-header: destination address */ + for (i = 0; i < 8; i++) + sum += *ptr++; + + /* Pseudo-header: payload length */ + sum += htons(data_len); + + /* Pseudo-header: next header */ + sum += htons(IPPROTO_ICMPV6); + + /* ICMPv6 data */ + ptr = (const uint16_t *) data; + for (i = 0; i < data_len / 2; i++) + sum += ptr[i]; + + /* Handle odd byte */ + if (data_len % 2) { + ptr8 = &((const uint8_t *) data)[data_len - 1]; + sum += htons((guint16) (*ptr8) << 8); + } + + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return (uint16_t) ~sum; +} + +/* + * nm_utils_ipv6_dad_send: + * @addr: the target IPv6 address + * @ifindex: the interface index + * @arptype: the ARP hardware type of the interface (e.g. ARPHRD_ETHER, ARPHRD_NONE) + * + * Send an IPv6 Duplicate Address Detection (DAD) Neighbor Solicitation + * for the given address. + * + * Returns: %TRUE if the packet was sent successfully, %FALSE on error + */ +gboolean +nm_utils_ipv6_dad_send(const struct in6_addr *addr, int ifindex, int arptype) +{ + /* DAD packet: IPv6 header + ICMPv6 NS + nonce option (RFC 3971) */ + struct _nm_packed { + struct ip6_hdr ip6h; + struct nd_neighbor_solicit ns; + guint8 ns_opt_nr; + guint8 ns_opt_len; + guint8 ns_opt_nonce[6]; + } dad_pkt; + nm_auto_close int fd = -1; + int errsv; + char sbuf[NM_INET_ADDRSTRLEN]; + + nm_assert(addr); + nm_assert(ifindex > 0); + + /* IPv6 header */ + dad_pkt.ip6h = (struct ip6_hdr) { + .ip6_flow = htonl(6 << 28), /* version 6, tclass 0, flowlabel 0 */ + .ip6_plen = htons(sizeof(dad_pkt) - sizeof(struct ip6_hdr)), + .ip6_nxt = IPPROTO_ICMPV6, + .ip6_hlim = 255, + .ip6_src = IN6ADDR_ANY_INIT, + .ip6_dst.s6_addr = {0xff, + 0x02, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0x01, + 0xff, + addr->s6_addr[13], + addr->s6_addr[14], + addr->s6_addr[15]}, + }; + + /* ICMPv6 Neighbor Solicitation */ + dad_pkt.ns = (struct nd_neighbor_solicit) { + .nd_ns_type = ND_NEIGHBOR_SOLICIT, + .nd_ns_target = *addr, + }; + + /* Nonce option (RFC 3971) */ + dad_pkt.ns_opt_nr = 14; + dad_pkt.ns_opt_len = 1; /* in units of 8 bytes */ + nm_random_get_bytes(dad_pkt.ns_opt_nonce, sizeof(dad_pkt.ns_opt_nonce)); + + /* Compute the ICMPv6 checksum */ + dad_pkt.ns.nd_ns_cksum = nm_utils_icmp6_checksum(&dad_pkt.ip6h.ip6_src, + sizeof(dad_pkt) - sizeof(struct ip6_hdr), + &dad_pkt.ns); + + /* We need a ETH_P_IPV6 socket because we need to use a zero IPv6 source address */ + fd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6)); + if (fd < 0) { + errsv = errno; + nm_log_warn(LOGD_CORE, + "ipv6-dad: failed to create socket for %s: %s", + nm_inet6_ntop(addr, sbuf), + nm_strerror_native(errsv)); + return FALSE; + } + + /* Build link-layer destination address. For Ethernet, use the solicited-node + * multicast MAC address. For L3-only devices (ARPHRD_NONE, ARPHRD_RAWIP, etc.) + * there is no L2 header, so set sll_halen to 0. */ + { + struct sockaddr_ll dst_ll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IPV6), + .sll_ifindex = ifindex, + }; + + if (arptype == ARPHRD_ETHER) { + dst_ll.sll_halen = ETH_ALEN; + dst_ll.sll_addr[0] = 0x33; + dst_ll.sll_addr[1] = 0x33; + dst_ll.sll_addr[2] = 0xff; + dst_ll.sll_addr[3] = addr->s6_addr[13]; + dst_ll.sll_addr[4] = addr->s6_addr[14]; + dst_ll.sll_addr[5] = addr->s6_addr[15]; + } + + if (sendto(fd, &dad_pkt, sizeof(dad_pkt), 0, (struct sockaddr *) &dst_ll, sizeof(dst_ll)) + < 0) { + errsv = errno; + nm_log_warn(LOGD_CORE, + "ipv6-dad: failed to send DAD NS for %s: %s", + nm_inet6_ntop(addr, sbuf), + nm_strerror_native(errsv)); + return FALSE; + } + } + + nm_log_dbg(LOGD_CORE, + "ipv6-dad: sent DAD NS for %s on ifindex %d", + nm_inet6_ntop(addr, sbuf), + ifindex); + + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { GPid pid; GTask *task; gulong cancellable_id; @@ -5023,6 +5490,9 @@ typedef struct { gsize out_buffer_offset; } HelperInfo; +#undef _NMLOG2_PREFIX_NAME +#undef _NMLOG2_DOMAIN +#undef _NMLOG2 #define _NMLOG2_PREFIX_NAME "nm-daemon-helper" #define _NMLOG2_DOMAIN LOGD_CORE #define _NMLOG2(level, info, ...) \ diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index cccccae6..a1892b99 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -304,6 +304,7 @@ typedef enum { NM_UTILS_STABLE_TYPE_STABLE_ID = 1, NM_UTILS_STABLE_TYPE_GENERATED = 2, NM_UTILS_STABLE_TYPE_RANDOM = 3, + NM_UTILS_STABLE_TYPE_CLAT = 4, } NMUtilsStableType; #define NM_UTILS_STABLE_TYPE_NONE ((NMUtilsStableType) - 1) @@ -520,4 +521,19 @@ void nm_utils_read_private_files(const char *const *paths, gpointer cb_data); GHashTable *nm_utils_read_private_files_finish(GAsyncResult *result, GError **error); +/*****************************************************************************/ + +void nm_utils_ping_host(NMIPAddrTyped address, + int ifindex, + guint timeout_sec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer cb_data); + +gboolean nm_utils_ping_host_finish(GAsyncResult *result, GError **error); + +uint16_t nm_utils_icmp6_checksum(const void *ip6_src, size_t data_len, const void *data); + +gboolean nm_utils_ipv6_dad_send(const struct in6_addr *addr, int ifindex, int arptype); + #endif /* __NM_CORE_UTILS_H__ */ diff --git a/src/core/nm-ip-config.c b/src/core/nm-ip-config.c index 75a75b42..4616f2e4 100644 --- a/src/core/nm-ip-config.c +++ b/src/core/nm-ip-config.c @@ -26,6 +26,7 @@ GType nm_ip6_config_get_type(void); /*****************************************************************************/ #define NM_IP_CONFIG_ADDRESS_DATA "address-data" +#define NM_IP_CONFIG_CLAT_ADDRESS "clat-address" #define NM_IP_CONFIG_DNS_OPTIONS "dns-options" #define NM_IP_CONFIG_DNS_PRIORITY "dns-priority" #define NM_IP_CONFIG_DOMAINS "domains" @@ -41,6 +42,7 @@ NM_GOBJECT_PROPERTIES_DEFINE_FULL(_ip, NMIPConfig, PROP_IP_L3CFG, PROP_IP_ADDRESS_DATA, + PROP_IP_CLAT_ADDRESS, PROP_IP_GATEWAY, PROP_IP_ROUTE_DATA, PROP_IP_DOMAINS, @@ -164,6 +166,8 @@ get_property_ip(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec const int addr_family = nm_ip_config_get_addr_family(self); char **to_free = NULL; char sbuf_addr[NM_INET_ADDRSTRLEN]; + in_addr_t addr4; + struct in6_addr addr6; const char *const *strv; guint len; int v_i; @@ -218,6 +222,20 @@ get_property_ip(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec strv = nm_l3_config_data_get_dns_options(priv->l3cd, addr_family, &len); _value_set_variant_as(value, strv, len); break; + case PROP_IP_CLAT_ADDRESS: + if (nm_l3_config_data_get_clat_state(priv->l3cd, &addr6, NULL, NULL, &addr4)) { + if (addr_family == AF_INET) { + g_value_set_variant(value, + g_variant_new_string(nm_inet_ntop(AF_INET, &addr4, sbuf_addr))); + } else { + g_value_set_variant( + value, + g_variant_new_string(nm_inet_ntop(AF_INET6, &addr6, sbuf_addr))); + } + } else { + g_value_set_variant(value, nm_g_variant_singleton_s_empty()); + } + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -336,6 +354,13 @@ nm_ip_config_class_init(NMIPConfigClass *klass) G_VARIANT_TYPE("aa{sv}"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties_ip[PROP_IP_CLAT_ADDRESS] = + g_param_spec_variant(NM_IP_CONFIG_CLAT_ADDRESS, + "", + "", + G_VARIANT_TYPE("s"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties_ip[PROP_IP_GATEWAY] = g_param_spec_variant(NM_IP_CONFIG_GATEWAY, "", @@ -512,6 +537,9 @@ static const NMDBusInterfaceInfoExtended interface_info_ip4_config = { NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("AddressData", "aa{sv}", NM_IP_CONFIG_ADDRESS_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ClatAddress", + "s", + NM_IP_CONFIG_CLAT_ADDRESS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Gateway", "s", NM_IP_CONFIG_GATEWAY), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE( "Routes", @@ -614,6 +642,7 @@ nm_ip4_config_class_init(NMIP4ConfigClass *klass) /*****************************************************************************/ /* public */ +#define NM_IP6_CONFIG_CLAT_PREF64 "clat-pref64" #define NM_IP6_CONFIG_NAMESERVERS "nameservers" /* deprecated */ @@ -625,6 +654,7 @@ typedef struct _NMIP6ConfigClass NMIP6ConfigClass; NM_GOBJECT_PROPERTIES_DEFINE_FULL(_ip6, NMIP6Config, + PROP_IP6_CLAT_PREF64, PROP_IP6_NAMESERVERS, PROP_IP6_ADDRESSES, PROP_IP6_ROUTES, ); @@ -651,6 +681,12 @@ static const NMDBusInterfaceInfoExtended interface_info_ip6_config = { NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("AddressData", "aa{sv}", NM_IP_CONFIG_ADDRESS_DATA), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ClatAddress", + "s", + NM_IP_CONFIG_CLAT_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ClatPref64", + "s", + NM_IP6_CONFIG_CLAT_PREF64), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Gateway", "s", NM_IP_CONFIG_GATEWAY), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE( "Routes", @@ -682,11 +718,24 @@ get_property_ip6(GObject *object, guint prop_id, GValue *value, GParamSpec *pspe guint len; guint i; const char *const *strarr; + guint8 plen; + struct in6_addr addr6; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; switch (prop_id) { case PROP_IP6_ADDRESSES: g_value_set_variant(value, priv->v_addresses); break; + case PROP_IP6_CLAT_PREF64: + if (nm_l3_config_data_get_clat_state(priv->l3cd, NULL, &addr6, &plen, NULL)) { + nm_inet6_ntop(&addr6, sbuf); + g_value_set_variant(value, + g_variant_new_string( + nm_sprintf_bufa(NM_INET_ADDRSTRLEN + 32, "%s/%u", sbuf, plen))); + } else { + g_value_set_variant(value, nm_g_variant_singleton_s_empty()); + } + break; case PROP_IP6_ROUTES: g_value_set_variant(value, priv->v_routes); break; @@ -740,6 +789,13 @@ nm_ip6_config_class_init(NMIP6ConfigClass *klass) G_VARIANT_TYPE("a(ayuay)"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties_ip6[PROP_IP6_CLAT_PREF64] = + g_param_spec_variant(NM_IP6_CONFIG_CLAT_PREF64, + "", + "", + G_VARIANT_TYPE("s"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties_ip6[PROP_IP6_ROUTES] = g_param_spec_variant(NM_IP6_CONFIG_ROUTES, "", @@ -787,7 +843,7 @@ _handle_l3cd_changed(NMIPConfig *self, const NML3ConfigData *l3cd) const int IS_IPv4 = NM_IS_IPv4(addr_family); NMIPConfigPrivate *priv = NM_IP_CONFIG_GET_PRIVATE(self); nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; - GParamSpec *changed_params[8]; + GParamSpec *changed_params[10]; guint n_changed_params = 0; const char *const *strarr; const char *const *strarr_old; @@ -840,6 +896,59 @@ _handle_l3cd_changed(NMIPConfig *self, const NML3ConfigData *l3cd) } } + /* CLAT state */ + { + struct in6_addr clat_ip6; + struct in6_addr clat_pref64; + guint8 clat_pref64_plen; + in_addr_t clat_ip4; + gboolean clat_enabled; + struct in6_addr clat_ip6_old; + struct in6_addr clat_pref64_old; + guint8 clat_pref64_plen_old; + in_addr_t clat_ip4_old; + gboolean clat_enabled_old; + gboolean changed; + + clat_enabled_old = nm_l3_config_data_get_clat_state(l3cd_old, + &clat_ip6_old, + &clat_pref64_old, + &clat_pref64_plen_old, + &clat_ip4_old); + clat_enabled = nm_l3_config_data_get_clat_state(priv->l3cd, + &clat_ip6, + &clat_pref64, + &clat_pref64_plen, + &clat_ip4); + + /* CLAT address */ + if (clat_enabled != clat_enabled_old) { + changed = TRUE; + } else if (!clat_enabled) { + changed = FALSE; + } else if (IS_IPv4) { + changed = (clat_ip4 != clat_ip4_old); + } else { + changed = !IN6_ARE_ADDR_EQUAL(&clat_ip6, &clat_ip6_old); + } + if (changed) + changed_params[n_changed_params++] = obj_properties_ip[PROP_IP_CLAT_ADDRESS]; + + /* PREF64 */ + if (!IS_IPv4) { + if (clat_enabled != clat_enabled_old) { + changed = TRUE; + } else if (!clat_enabled) { + changed = FALSE; + } else { + changed = (clat_pref64_plen != clat_pref64_plen_old) + || (!IN6_ARE_ADDR_EQUAL(&clat_pref64, &clat_pref64_old)); + } + if (changed) + changed_params[n_changed_params++] = obj_properties_ip6[PROP_IP6_CLAT_PREF64]; + } + } + _notify_all(self, changed_params, n_changed_params); } diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 7500337e..849da7f4 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -50,6 +50,9 @@ struct _NML3ConfigData { const NMPObject *best_default_route_x[2]; }; + struct in6_addr pref64_prefix; + guint32 pref64_plen; + GArray *wins; GArray *nis_servers; @@ -122,6 +125,19 @@ struct _NML3ConfigData { NMSettingConnectionDnsOverTls dns_over_tls; NMSettingConnectionDnssec dnssec; NMUtilsIPv6IfaceId ip6_token; + NMRefString *network_id; + NMSettingIp4ConfigClat clat_config; /* this indicates the 'administrative' CLAT + * state, i.e. whether CLAT will be started + * once we receive a PREF64 */ + + /* The runtime CLAT state */ + struct { + struct in6_addr ip6; + struct in6_addr pref64; + in_addr_t ip4; + guint8 pref64_plen; + bool enabled; + } clat_state; NML3ConfigDatFlags flags; @@ -130,7 +146,8 @@ struct _NML3ConfigData { int ndisc_hop_limit_val; guint32 mtu; - guint32 ip6_mtu; + guint32 ip6_mtu_static; /* IPv6 MTU from the connection profile */ + guint32 ip6_mtu_ra; /* IPv6 MTU from Router Advertisement */ guint32 ndisc_reachable_time_msec_val; guint32 ndisc_retrans_timer_msec_val; @@ -167,6 +184,8 @@ struct _NML3ConfigData { bool routed_dns_4 : 1; bool routed_dns_6 : 1; + + bool pref64_valid : 1; }; /*****************************************************************************/ @@ -390,8 +409,11 @@ nm_l3_config_data_log(const NML3ConfigData *self, : "", !self->is_sealed ? ", not-sealed" : ""); - if (self->mtu != 0 || self->ip6_mtu != 0) { - _L("mtu: %u, ip6-mtu: %u", self->mtu, self->ip6_mtu); + if (self->mtu != 0 || self->ip6_mtu_static != 0 || self->ip6_mtu_ra != 0) { + _L("mtu: %u, ip6-mtu-static: %u, ip6-mtu-ra %u", + self->mtu, + self->ip6_mtu_static, + self->ip6_mtu_ra); } for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { @@ -519,6 +541,27 @@ nm_l3_config_data_log(const NML3ConfigData *self, _L("nis-domain: %s", self->nis_domain->str); } + if (!IS_IPv4) { + if (self->clat_config == NM_SETTING_IP4_CONFIG_CLAT_AUTO) + _L("clat-config: auto"); + else if (self->clat_config == NM_SETTING_IP4_CONFIG_CLAT_FORCE) + _L("clat-config: force"); + + if (self->clat_state.enabled) { + _L("clat-state: ip4=%s/32, pref64=%s/%u, ip6=%s/64", + nm_inet4_ntop(self->clat_state.ip4, sbuf + NM_INET_ADDRSTRLEN), + nm_inet6_ntop(&self->clat_state.pref64, sbuf), + self->clat_state.pref64_plen, + nm_inet6_ntop(&self->clat_state.ip6, sbuf_addr)); + } + } + + if (!IS_IPv4 && self->pref64_valid) { + _L("pref64_prefix: %s/%d", + nm_utils_inet6_ntop(&self->pref64_prefix, sbuf_addr), + self->pref64_plen); + } + if (self->dhcp_lease_x[IS_IPv4]) { gs_free NMUtilsNamedValue *options_free = NULL; NMUtilsNamedValue options_buffer[30]; @@ -603,6 +646,10 @@ nm_l3_config_data_log(const NML3ConfigData *self, nm_utils_inet6_interface_identifier_to_token(&self->ip6_token, sbuf_addr)); } + if (self->network_id) { + _L("network-id: %s", self->network_id->str); + } + if (self->metered != NM_TERNARY_DEFAULT) _L("metered: %s", self->metered ? "yes" : "no"); @@ -709,6 +756,7 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSourc .flags = NM_L3_CONFIG_DAT_FLAGS_NONE, .metered = NM_TERNARY_DEFAULT, .proxy_browser_only = NM_TERNARY_DEFAULT, + .clat_config = NM_SETTING_IP4_CONFIG_CLAT_NO, .proxy_method = NM_PROXY_CONFIG_METHOD_UNKNOWN, .route_table_sync_4 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE, .route_table_sync_6 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE, @@ -722,6 +770,7 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSourc .ndisc_retrans_timer_msec_set = FALSE, .allow_routes_without_address_4 = TRUE, .allow_routes_without_address_6 = TRUE, + .pref64_valid = FALSE, }; _idx_type_init(&self->idx_addresses_4, NMP_OBJECT_TYPE_IP4_ADDRESS); @@ -822,6 +871,7 @@ nm_l3_config_data_unref(const NML3ConfigData *self) nm_ref_string_unref(mutable->nis_domain); nm_ref_string_unref(mutable->proxy_pac_url); nm_ref_string_unref(mutable->proxy_pac_script); + nm_ref_string_unref(mutable->network_id); nm_g_slice_free(mutable); } @@ -1890,22 +1940,42 @@ nm_l3_config_data_set_mtu(NML3ConfigData *self, guint32 mtu) } guint32 -nm_l3_config_data_get_ip6_mtu(const NML3ConfigData *self) +nm_l3_config_data_get_ip6_mtu_static(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->ip6_mtu_static; +} + +gboolean +nm_l3_config_data_set_ip6_mtu_static(NML3ConfigData *self, guint32 ip6_mtu) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + if (self->ip6_mtu_static == ip6_mtu) + return FALSE; + + self->ip6_mtu_static = ip6_mtu; + return TRUE; +} + +guint32 +nm_l3_config_data_get_ip6_mtu_ra(const NML3ConfigData *self) { nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); - return self->ip6_mtu; + return self->ip6_mtu_ra; } gboolean -nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu) +nm_l3_config_data_set_ip6_mtu_ra(NML3ConfigData *self, guint32 ip6_mtu) { nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); - if (self->ip6_mtu == ip6_mtu) + if (self->ip6_mtu_ra == ip6_mtu) return FALSE; - self->ip6_mtu = ip6_mtu; + self->ip6_mtu_ra = ip6_mtu; return TRUE; } @@ -1957,6 +2027,132 @@ nm_l3_config_data_set_ip6_token(NML3ConfigData *self, NMUtilsIPv6IfaceId ipv6_to return TRUE; } +const char * +nm_l3_config_data_get_network_id(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return nm_ref_string_get_str(self->network_id); +} + +gboolean +nm_l3_config_data_set_network_id(NML3ConfigData *self, const char *value) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + return nm_ref_string_reset_str(&self->network_id, value); +} + +gboolean +nm_l3_config_data_set_clat_config(NML3ConfigData *self, NMSettingIp4ConfigClat val) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert(NM_IN_SET(val, + NM_SETTING_IP4_CONFIG_CLAT_NO, + NM_SETTING_IP4_CONFIG_CLAT_FORCE, + NM_SETTING_IP4_CONFIG_CLAT_AUTO)); + + if (self->clat_config == val) + return FALSE; + self->clat_config = val; + return TRUE; +} + +NMSettingIp4ConfigClat +nm_l3_config_data_get_clat_config(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->clat_config; +} + +gboolean +nm_l3_config_data_get_clat_state(const NML3ConfigData *self, + struct in6_addr *out_ip6, + struct in6_addr *out_pref64, + guint8 *out_pref64_plen, + in_addr_t *out_ip4) +{ + if (!self || !self->clat_state.enabled) + return FALSE; + NM_SET_OUT(out_ip6, self->clat_state.ip6); + NM_SET_OUT(out_pref64, self->clat_state.pref64); + NM_SET_OUT(out_pref64_plen, self->clat_state.pref64_plen); + NM_SET_OUT(out_ip4, self->clat_state.ip4); + return TRUE; +} + +void +nm_l3_config_data_set_clat_state(NML3ConfigData *self, + gboolean enabled, + const struct in6_addr *ip6, + const struct in6_addr *pref64, + guint8 pref64_plen, + in_addr_t ip4) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + + self->clat_state.enabled = enabled; + if (enabled) { + self->clat_state.ip6 = *ip6; + self->clat_state.pref64 = *pref64; + self->clat_state.pref64_plen = pref64_plen; + self->clat_state.ip4 = ip4; + } else { + self->clat_state.ip6 = in6addr_any; + self->clat_state.pref64 = in6addr_any; + self->clat_state.pref64_plen = 0; + self->clat_state.ip4 = 0; + } +} + +gboolean +nm_l3_config_data_set_pref64_valid(NML3ConfigData *self, gboolean val) +{ + if (self->pref64_valid == val) + return FALSE; + self->pref64_valid = val; + return TRUE; +} + +gboolean +nm_l3_config_data_get_pref64_valid(const NML3ConfigData *self) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + return self->pref64_valid; +} + +gboolean +nm_l3_config_data_get_pref64(const NML3ConfigData *self, + struct in6_addr *out_prefix, + guint32 *out_plen) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + if (!self->pref64_valid) + return FALSE; + NM_SET_OUT(out_prefix, self->pref64_prefix); + NM_SET_OUT(out_plen, self->pref64_plen); + return TRUE; +} + +gboolean +nm_l3_config_data_set_pref64(NML3ConfigData *self, struct in6_addr prefix, guint32 plen) +{ + if (self->pref64_valid) { + if (self->pref64_plen == plen + && nm_ip6_addr_same_prefix(&self->pref64_prefix, &prefix, plen)) { + return FALSE; + } + } else { + self->pref64_valid = TRUE; + } + self->pref64_prefix = prefix; + self->pref64_plen = plen; + return TRUE; +} + NMMptcpFlags nm_l3_config_data_get_mptcp_flags(const NML3ConfigData *self) { @@ -2294,8 +2490,8 @@ _dedup_multi_index_cmp(const NML3ConfigData *a, switch (obj_type) { case NMP_OBJECT_TYPE_IP4_ADDRESS: NM_CMP_DIRECT(obj_a->ip4_address.plen, obj_b->ip4_address.plen); - NM_CMP_DIRECT(obj_b->ip4_address.address, obj_b->ip4_address.address); - NM_CMP_DIRECT(obj_b->ip4_address.peer_address, obj_b->ip4_address.peer_address); + NM_CMP_DIRECT(obj_a->ip4_address.address, obj_b->ip4_address.address); + NM_CMP_DIRECT(obj_a->ip4_address.peer_address, obj_b->ip4_address.peer_address); break; case NMP_OBJECT_TYPE_IP6_ADDRESS: NM_CMP_DIRECT(obj_a->ip6_address.plen, obj_b->ip6_address.plen); @@ -2408,15 +2604,15 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a, const NMPObject *def_route_a = a->best_default_route_x[IS_IPv4]; const NMPObject *def_route_b = b->best_default_route_x[IS_IPv4]; - if (def_route_a != def_route_b) { - if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES)) { - NM_CMP_RETURN( - nmp_object_cmp_full(def_route_a, - def_route_b, - NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX) - ? NMP_OBJECT_CMP_FLAGS_NONE - : NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX)); - } else if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID)) { + if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES)) { + NM_CMP_RETURN(nmp_object_cmp_full(def_route_a, + def_route_b, + NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX) + ? NMP_OBJECT_CMP_FLAGS_NONE + : NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX)); + } else if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID)) { + NM_CMP_DIRECT(!!def_route_a, !!def_route_b); + if (def_route_a && def_route_b) { if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_IFINDEX)) { NM_CMP_DIRECT(def_route_a->obj_with_ifindex.ifindex, def_route_b->obj_with_ifindex.ifindex); @@ -2484,8 +2680,10 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a, if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_OTHER)) { NM_CMP_DIRECT(a->flags, b->flags); NM_CMP_DIRECT(a->ip6_token.id, b->ip6_token.id); + NM_CMP_DIRECT_REF_STRING(a->network_id, b->network_id); NM_CMP_DIRECT(a->mtu, b->mtu); - NM_CMP_DIRECT(a->ip6_mtu, b->ip6_mtu); + NM_CMP_DIRECT(a->ip6_mtu_static, b->ip6_mtu_static); + NM_CMP_DIRECT(a->ip6_mtu_ra, b->ip6_mtu_ra); NM_CMP_DIRECT_UNSAFE(a->metered, b->metered); NM_CMP_DIRECT_UNSAFE(a->proxy_browser_only, b->proxy_browser_only); NM_CMP_DIRECT_UNSAFE(a->proxy_method, b->proxy_method); @@ -2509,6 +2707,23 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a, NM_CMP_DIRECT_UNSAFE(a->routed_dns_4, b->routed_dns_4); NM_CMP_DIRECT_UNSAFE(a->routed_dns_6, b->routed_dns_6); + NM_CMP_DIRECT_UNSAFE(a->clat_config, b->clat_config); + + NM_CMP_DIRECT(!!a->clat_state.enabled, !!b->clat_state.enabled); + if (a->clat_state.enabled) { + NM_CMP_DIRECT_IN6ADDR(&a->clat_state.ip6, &b->clat_state.ip6); + NM_CMP_DIRECT_IN6ADDR(&a->clat_state.pref64, &b->clat_state.pref64); + NM_CMP_DIRECT(a->clat_state.pref64_plen, b->clat_state.pref64_plen); + NM_CMP_DIRECT(a->clat_state.ip4, b->clat_state.ip4); + } + + NM_CMP_DIRECT(!!a->pref64_valid, !!b->pref64_valid); + if (a->pref64_valid) { + NM_CMP_DIRECT(a->pref64_plen, b->pref64_plen); + NM_CMP_RETURN_DIRECT( + nm_ip6_addr_same_prefix_cmp(&a->pref64_prefix, &b->pref64_prefix, a->pref64_plen)); + } + NM_CMP_FIELD(a, b, source); } @@ -2524,8 +2739,10 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a, /*****************************************************************************/ -static const NMPObject * -_data_get_direct_route_for_host(const NML3ConfigData *self, int addr_family, gconstpointer host) +const NMPObject * +nm_l3_config_data_get_direct_route_for_host(const NML3ConfigData *self, + int addr_family, + gconstpointer host) { const int IS_IPv4 = NM_IS_IPv4(addr_family); const NMPObject *best_route_obj = NULL; @@ -2556,7 +2773,8 @@ _data_get_direct_route_for_host(const NML3ConfigData *self, int addr_family, gco if (!nm_ip_addr_same_prefix(addr_family, host, item->rx.network_ptr, item->rx.plen)) continue; - if (best_route && best_route->rx.metric <= item->rx.metric) + if (best_route && best_route->rx.plen == item->rx.plen + && best_route->rx.metric <= item->rx.metric) continue; best_route_obj = item_obj; @@ -2683,7 +2901,7 @@ nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_fam if (NM_FLAGS_HAS(route_src->rx.r_rtm_flags, (unsigned) RTNH_F_ONLINK)) continue; - if (_data_get_direct_route_for_host(self, addr_family, p_gateway)) + if (nm_l3_config_data_get_direct_route_for_host(self, addr_family, p_gateway)) continue; new_route = nmp_object_clone(obj_src, FALSE); @@ -3027,6 +3245,9 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co nm_l3_config_data_set_ip6_privacy( self, nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip))); + nm_l3_config_data_set_ip6_mtu_static( + self, + nm_setting_ip6_config_get_mtu(NM_SETTING_IP6_CONFIG(s_ip))); } } @@ -3503,6 +3724,9 @@ nm_l3_config_data_merge(NML3ConfigData *self, if (self->ip6_token.id == 0) self->ip6_token.id = src->ip6_token.id; + if (!self->network_id) + self->network_id = nm_ref_string_ref(src->network_id); + self->metered = NM_MAX((NMTernary) self->metered, (NMTernary) src->metered); if (self->proxy_method == NM_PROXY_CONFIG_METHOD_UNKNOWN) @@ -3541,8 +3765,11 @@ nm_l3_config_data_merge(NML3ConfigData *self, if (self->mtu == 0u) self->mtu = src->mtu; - if (self->ip6_mtu == 0u) - self->ip6_mtu = src->ip6_mtu; + if (self->ip6_mtu_static == 0u) + self->ip6_mtu_static = src->ip6_mtu_static; + + if (self->ip6_mtu_ra == 0u) + self->ip6_mtu_ra = src->ip6_mtu_ra; if (NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) { _nm_unused nm_auto_unref_dhcplease NMDhcpLease *dhcp_lease_6 = @@ -3564,6 +3791,24 @@ nm_l3_config_data_merge(NML3ConfigData *self, self->routed_dns_4 = TRUE; if (src->routed_dns_6) self->routed_dns_6 = TRUE; + + if (self->clat_config == NM_SETTING_IP4_CONFIG_CLAT_NO) { + /* 'no' always loses to 'force' and 'auto' */ + self->clat_config = src->clat_config; + } else if (src->clat_config == NM_SETTING_IP4_CONFIG_CLAT_FORCE) { + /* 'force' always takes precedence */ + self->clat_config = src->clat_config; + } + + if (!self->clat_state.enabled && src->clat_state.enabled) { + self->clat_state = src->clat_state; + } + + if (src->pref64_valid) { + self->pref64_prefix = src->pref64_prefix; + self->pref64_plen = src->pref64_plen; + self->pref64_valid = src->pref64_valid; + } } NML3ConfigData * diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index 4102b6e1..b76e11f9 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -5,6 +5,7 @@ #include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-setting-connection.h" +#include "nm-setting-ip4-config.h" #include "nm-setting-ip6-config.h" #include "libnm-platform/nm-platform.h" #include "libnm-platform/nmp-object.h" @@ -225,6 +226,10 @@ nm_l3_config_data_equal(const NML3ConfigData *a, const NML3ConfigData *b) /*****************************************************************************/ +const NMPObject *nm_l3_config_data_get_direct_route_for_host(const NML3ConfigData *self, + int addr_family, + gconstpointer host); + const NMDedupMultiIdxType *nm_l3_config_data_lookup_index(const NML3ConfigData *self, NMPObjectType obj_type); @@ -482,14 +487,49 @@ guint32 nm_l3_config_data_get_mtu(const NML3ConfigData *self); gboolean nm_l3_config_data_set_mtu(NML3ConfigData *self, guint32 mtu); -guint32 nm_l3_config_data_get_ip6_mtu(const NML3ConfigData *self); +guint32 nm_l3_config_data_get_ip6_mtu_static(const NML3ConfigData *self); + +gboolean nm_l3_config_data_set_ip6_mtu_static(NML3ConfigData *self, guint32 ip6_mtu); -gboolean nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu); +guint32 nm_l3_config_data_get_ip6_mtu_ra(const NML3ConfigData *self); + +gboolean nm_l3_config_data_set_ip6_mtu_ra(NML3ConfigData *self, guint32 ip6_mtu); NMUtilsIPv6IfaceId nm_l3_config_data_get_ip6_token(const NML3ConfigData *self); gboolean nm_l3_config_data_set_ip6_token(NML3ConfigData *self, NMUtilsIPv6IfaceId ipv6_token); +gboolean nm_l3_config_data_set_network_id(NML3ConfigData *self, const char *network_id); + +const char *nm_l3_config_data_get_network_id(const NML3ConfigData *self); + +gboolean nm_l3_config_data_set_clat_config(NML3ConfigData *self, NMSettingIp4ConfigClat val); + +NMSettingIp4ConfigClat nm_l3_config_data_get_clat_config(const NML3ConfigData *self); + +gboolean nm_l3_config_data_get_clat_state(const NML3ConfigData *self, + struct in6_addr *out_ip6, + struct in6_addr *out_pref64, + guint8 *out_pref64_plen, + in_addr_t *out_ip4); + +void nm_l3_config_data_set_clat_state(NML3ConfigData *self, + gboolean enabled, + const struct in6_addr *ip6, + const struct in6_addr *pref64, + guint8 pref64_plen, + in_addr_t ip4); + +gboolean nm_l3_config_data_set_pref64_valid(NML3ConfigData *self, gboolean val); + +gboolean nm_l3_config_data_get_pref64_valid(const NML3ConfigData *self); + +gboolean nm_l3_config_data_get_pref64(const NML3ConfigData *self, + struct in6_addr *out_prefix, + guint32 *out_plen); + +gboolean nm_l3_config_data_set_pref64(NML3ConfigData *self, struct in6_addr prefix, guint32 plen); + NMMptcpFlags nm_l3_config_data_get_mptcp_flags(const NML3ConfigData *self); gboolean nm_l3_config_data_set_mptcp_flags(NML3ConfigData *self, NMMptcpFlags mptcp_flags); diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 88a9c241..98a2a2a6 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -7,10 +7,15 @@ #include "libnm-std-aux/nm-linux-compat.h" #include <net/if.h> +#include <net/if_arp.h> #include "nm-compat-headers/linux/if_addr.h" #include <linux/if_ether.h> #include <linux/rtnetlink.h> #include <linux/fib_rules.h> +#if HAVE_CLAT +#include <bpf/libbpf.h> +#include <bpf/bpf.h> +#endif /* HAVE_CLAT */ #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-glib-aux/nm-prioq.h" @@ -22,6 +27,13 @@ #include "n-acd/src/n-acd.h" #include "nm-l3-ipv4ll.h" #include "nm-ip-config.h" +#include "nm-core-utils.h" +#if HAVE_CLAT +#include "bpf/clat.h" +NM_PRAGMA_WARNING_DISABLE("-Wcast-align") +#include "bpf/clat.skel.h" +NM_PRAGMA_WARNING_REENABLE +#endif /* HAVE_CLAT */ /*****************************************************************************/ @@ -289,6 +301,24 @@ typedef struct _NML3CfgPrivate { NMIPConfig *ipconfig_x[2]; }; +#if HAVE_CLAT + /* The reserved IPv4 address for CLAT in the 192.0.0.0/28 range */ + NMNetnsIPReservation *clat_address_4; + /* The IPv6 address for sending and receiving translated packets */ + NMPlatformIP6Address clat_address_6; + + /* The same addresses as above, but already committed previously */ + NMNetnsIPReservation *clat_address_4_committed; + NMPlatformIP6Address clat_address_6_committed; + + /* If NULL, the BPF program hasn't been loaded or attached */ + struct clat_bpf *clat_bpf; + struct bpf_link *clat_ingress_link; + struct bpf_link *clat_egress_link; + + int clat_socket; +#endif /* HAVE_CLAT */ + /* Whether we earlier configured MPTCP endpoints for the interface. */ union { struct { @@ -353,6 +383,9 @@ typedef struct _NML3CfgPrivate { bool rp_filter_handled : 1; bool rp_filter_set : 1; + + bool clat_address_6_valid : 1; + bool clat_address_6_committed_valid : 1; } NML3CfgPrivate; struct _NML3CfgClass { @@ -4107,6 +4140,291 @@ update_routes: } } +#if HAVE_CLAT +/** + * _clat_prefix_is_better: + * @best: current best candidate (or %NULL) + * @candidate: the new candidate prefix + * @nat64_pref: the NAT64 prefix + * + * Compare two SLAAC candidate prefixes to be used for CLAT, + * as recommended by draft-ietf-v6ops-claton Section 7. Apply + * rules 6 and 8 of the source address selection algorithm from + * RFC 6724, Section 5. + * + * Returns %TRUE if @candidate is better than @best. + */ +static gboolean +_clat_prefix_is_better(const NMPlatformIP6Address *best, + const NMPlatformIP6Address *candidate, + const struct in6_addr *nat64_pref) +{ + guint nat64_pref_label; + gboolean best_label_match; + gboolean cand_label_match; + guint best_prefix_len; + guint cand_prefix_len; + + if (!best) + return TRUE; + + /* Rule 6: prefer the address whose RFC 6724 label matches + * the label of the NAT64 prefix. */ + nat64_pref_label = nm_ip6_addr_rfc6724_label(nat64_pref); + best_label_match = nm_ip6_addr_rfc6724_label(&best->address) == nat64_pref_label; + cand_label_match = nm_ip6_addr_rfc6724_label(&candidate->address) == nat64_pref_label; + + if (cand_label_match && !best_label_match) + return TRUE; + else if (best_label_match && !cand_label_match) + return FALSE; + + /* Rule 8: longest matching prefix with the NAT64 prefix. */ + best_prefix_len = nm_ip6_addr_common_prefix_len(&best->address, nat64_pref); + cand_prefix_len = nm_ip6_addr_common_prefix_len(&candidate->address, nat64_pref); + if (cand_prefix_len != best_prefix_len) + return cand_prefix_len > best_prefix_len; + + return FALSE; +} +#endif /* HAVE_CLAT */ + +static void +_l3cfg_update_clat_config(NML3Cfg *self, + NML3ConfigData *l3cd, + const L3ConfigData **l3_config_datas_arr, + guint l3_config_datas_len) +{ +#if !HAVE_CLAT + return; +#else + struct in6_addr pref64; + guint32 pref64_plen; + gboolean clat_enabled = FALSE; + const NMPlatformIP4Route *ip4_route; + NMDedupMultiIter iter; + + switch (nm_l3_config_data_get_clat_config(l3cd)) { + case NM_SETTING_IP4_CONFIG_CLAT_FORCE: + clat_enabled = TRUE; + break; + case NM_SETTING_IP4_CONFIG_CLAT_NO: + clat_enabled = FALSE; + break; + case NM_SETTING_IP4_CONFIG_CLAT_AUTO: + clat_enabled = TRUE; + /* disable if there is a native IPv4 gateway */ + nm_l3_config_data_iter_ip4_route_for_each (&iter, l3cd, &ip4_route) { + if (ip4_route->network == INADDR_ANY && ip4_route->plen == 0 + && ip4_route->gateway != INADDR_ANY) { + clat_enabled = FALSE; + break; + } + } + break; + case NM_SETTING_IP4_CONFIG_CLAT_DEFAULT: + nm_assert_not_reached(); + clat_enabled = TRUE; + break; + } + + if (clat_enabled && nm_l3_config_data_get_pref64_valid(l3cd)) { + NMPlatformIPXRoute rx; + NMIPAddrTyped best_v6_gateway; + const NMPlatformIP6Route *best_v6_route; + const NMPlatformIP6Address *ip6_entry; + struct in6_addr ip6; + const char *network_id; + char buf[512]; + guint32 route4_metric = NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4; + guint i; + + /* If we have a valid NAT64 prefix, configure in kernel: + * + * - a CLAT IPv4 address (192.0.0.x) + * - a IPv4 default route via the best IPv6 gateway + * + * We also set clat_address_6 as an additional /64 IPv6 address + * determined according to https://www.rfc-editor.org/rfc/rfc6877#section-6.3 . + * This address is used for sending and receiving translated packets, + * but is not configured in kernel to avoid that it gets used by applications. + * Later in _l3_commit_pref64() we use IPV6_JOIN_ANYCAST to let the kernel + * handle ND for the address. + */ + + nm_l3_config_data_get_pref64(l3cd, &pref64, &pref64_plen); + network_id = nm_l3_config_data_get_network_id(l3cd); + + if (!self->priv.p->clat_address_6_valid && network_id) { + const NMPlatformIP6Address *best_prefix = NULL; + + /* Select the best SLAAC prefix for the CLAT address per + * draft-ietf-v6ops-claton-14 Section 7 */ + nm_l3_config_data_iter_ip6_address_for_each (&iter, l3cd, &ip6_entry) { + if (ip6_entry->addr_source == NM_IP_CONFIG_SOURCE_NDISC && ip6_entry->plen == 64) { + if (_clat_prefix_is_better(best_prefix, ip6_entry, &pref64)) + best_prefix = ip6_entry; + } + } + + if (best_prefix) { + ip6 = best_prefix->address; + + nm_utils_ipv6_addr_set_stable_privacy(NM_UTILS_STABLE_TYPE_CLAT, + &ip6, + nm_l3cfg_get_ifname(self, TRUE), + network_id, + 0); + self->priv.p->clat_address_6 = (NMPlatformIP6Address) { + .ifindex = self->priv.ifindex, + .address = ip6, + .peer_address = ip6, + .addr_source = NM_IP_CONFIG_SOURCE_CLAT, + .plen = best_prefix->plen, + }; + + _LOGT("clat: using IPv6 address %s", nm_inet6_ntop(&ip6, buf)); + + self->priv.p->clat_address_6_valid = TRUE; + } + } + + /* Don't get a v4 address if we have no v6 address (otherwise, we could + * potentially create broken v4 connectivity) */ + if (!self->priv.p->clat_address_6_valid) { + _LOGW("CLAT is currently only supported when SLAAC is in use."); + /* Deallocate the v4 address unless it's the committed one */ + if (self->priv.p->clat_address_4 != self->priv.p->clat_address_4_committed) { + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_reservation_release); + } else { + self->priv.p->clat_address_4 = NULL; + } + } else if (!self->priv.p->clat_address_4) { + /* We need a v4 /32 */ + self->priv.p->clat_address_4 = + nm_netns_ip_reservation_get(self->priv.netns, NM_NETNS_IP_RESERVATION_TYPE_CLAT); + } + + { + const NMPlatformIP4Route *r4; + guint32 metric = 0; + guint32 penalty = 0; + + /* Find the IPv4 metric for the CLAT default route. + * If there is another non-CLAT default route on the device, use the + * same metric + 1, so that native connectivity is always preferred. + * Otherwise, use the metric from the connection profile. + */ + + r4 = NMP_OBJECT_CAST_IP4_ROUTE(nm_l3_config_data_get_best_default_route(l3cd, AF_INET)); + + if (r4) { + route4_metric = nm_add_clamped_u32(r4->metric, 1u); + } else { + for (i = 0; i < l3_config_datas_len; i++) { + const L3ConfigData *l3cd_data = l3_config_datas_arr[i]; + + if (l3cd_data->default_route_metric_4 != NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4) { + metric = l3cd_data->default_route_metric_4; + } + if (l3cd_data->default_route_penalty_4 != 0) { + penalty = l3cd_data->default_route_penalty_4; + } + } + route4_metric = nm_add_clamped_u32(metric, penalty); + } + } + + if (self->priv.p->clat_address_4) { + best_v6_route = NMP_OBJECT_CAST_IP6_ROUTE( + nm_l3_config_data_get_direct_route_for_host(l3cd, AF_INET6, &pref64)); + if (!best_v6_route) { + best_v6_route = NMP_OBJECT_CAST_IP6_ROUTE( + nm_l3_config_data_get_best_default_route(l3cd, AF_INET6)); + } + if (best_v6_route) { + NMPlatformIP4Address addr = { + .ifindex = self->priv.ifindex, + .address = self->priv.p->clat_address_4->addr, + .peer_address = self->priv.p->clat_address_4->addr, + .addr_source = NM_IP_CONFIG_SOURCE_CLAT, + .plen = 32, + }; + const NMPlatformLink *pllink; + guint mtu = 0; + guint val = 0; + + best_v6_gateway.addr_family = AF_INET6; + best_v6_gateway.addr.addr6 = best_v6_route->gateway; + + /* Determine the IPv6 MTU of the interface. Unfortunately, + * the logic to set the MTU is in NMDevice and here we need + * some duplication to find the actual value. + * TODO: move the MTU handling into l3cfg. */ + + /* Get the link MTU */ + pllink = nm_l3cfg_get_pllink(self, TRUE); + if (pllink) + mtu = pllink->mtu; + if (mtu == 0) + mtu = 1500; + + /* Update it with the IPv6 MTU value from the connection + * or from RA */ + val = nm_l3_config_data_get_ip6_mtu_static(l3cd); + if (val == 0) { + val = nm_l3_config_data_get_ip6_mtu_ra(l3cd); + } + if (val != 0 && val < mtu) { + mtu = val; + } + if (mtu < 1280) + mtu = 1280; + + /* Leave 20 additional bytes for the ipv4 -> ipv6 header translation, + * plus 8 for a potential fragmentation extension header */ + mtu -= 28; + + rx.r4 = (NMPlatformIP4Route) { + .ifindex = self->priv.ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_CLAT, + .network = 0, /* default route */ + .plen = 0, + .table_coerced = nm_platform_route_table_coerce(RT_TABLE_MAIN), + .scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE), + .type_coerced = nm_platform_route_type_coerce(RTN_UNICAST), + .pref_src = self->priv.p->clat_address_4->addr, + .via = best_v6_gateway, + .metric = route4_metric, + .mtu = mtu, + }; + nm_platform_ip_route_normalize(AF_INET, &rx.rx); + if (!nm_l3_config_data_lookup_route(l3cd, AF_INET, &rx.rx)) { + nm_l3_config_data_add_route_4(l3cd, &rx.r4); + } + + _LOGT("clat: route %s", nm_platform_ip4_route_to_string(&rx.r4, buf, sizeof(buf))); + + nm_l3_config_data_add_address_4(l3cd, &addr); + } else { + _LOGW("Couldn't find a good ipv6 route! Unable to set up CLAT!"); + } + } + + if (self->priv.p->clat_address_4 && self->priv.p->clat_address_6_valid) { + nm_l3_config_data_set_clat_state(l3cd, + TRUE, + &self->priv.p->clat_address_6.address, + &pref64, + pref64_plen, + self->priv.p->clat_address_4->addr); + } else { + nm_l3_config_data_set_clat_state(l3cd, FALSE, NULL, NULL, 0, INADDR_ANY); + } + } +#endif /* HAVE_CLAT */ +} + static void _l3cfg_update_combined_config(NML3Cfg *self, gboolean to_commit, @@ -4220,6 +4538,8 @@ _l3cfg_update_combined_config(NML3Cfg *self, &hook_data); } + _l3cfg_update_clat_config(self, l3cd, l3_config_datas_arr, l3_config_datas_len); + if (self->priv.ifindex == NM_LOOPBACK_IFINDEX) { NMPlatformIPXAddress ax; NMPlatformIPXRoute rx; @@ -4280,6 +4600,18 @@ _l3cfg_update_combined_config(NML3Cfg *self, if (nm_l3_config_data_equal(l3cd, self->priv.p->combined_l3cd_merged)) goto out; +#if HAVE_CLAT + if (!l3cd) { + self->priv.p->clat_address_6_valid = FALSE; + /* Deallocate the v4 address unless it's the commited one */ + if (self->priv.p->clat_address_4 != self->priv.p->clat_address_4_committed) { + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_reservation_release); + } else { + self->priv.p->clat_address_4 = NULL; + } + } +#endif /* HAVE_CLAT */ + l3cd_old = g_steal_pointer(&self->priv.p->combined_l3cd_merged); self->priv.p->combined_l3cd_merged = nm_l3_config_data_seal(g_steal_pointer(&l3cd)); merged_changed = TRUE; @@ -5379,6 +5711,251 @@ _l3_commit_one(NML3Cfg *self, _failedobj_handle_routes(self, addr_family, routes_failed); } +#if HAVE_CLAT +static void +_l3_clat_destroy(NML3Cfg *self) +{ + char buf[100]; + int err; + + if (self->priv.p->clat_bpf) { + const struct clat_stats *s = &self->priv.p->clat_bpf->bss->stats; + + _LOGT("clat: stats:" + " egress (v4 to v6): tcp %" G_GUINT64_FORMAT ", udp %" G_GUINT64_FORMAT + ", icmp %" G_GUINT64_FORMAT ", other %" G_GUINT64_FORMAT + ", dropped %" G_GUINT64_FORMAT "; ingress (v6 to v4): tcp %" G_GUINT64_FORMAT + ", udp %" G_GUINT64_FORMAT ", icmp %" G_GUINT64_FORMAT ", other %" G_GUINT64_FORMAT + ", fragment %" G_GUINT64_FORMAT ", dropped %" G_GUINT64_FORMAT, + (guint64) s->egress_tcp, + (guint64) s->egress_udp, + (guint64) s->egress_icmp, + (guint64) s->egress_other, + (guint64) s->egress_dropped, + (guint64) s->ingress_tcp, + (guint64) s->ingress_udp, + (guint64) s->ingress_icmp, + (guint64) s->ingress_other, + (guint64) s->ingress_fragment, + (guint64) s->ingress_dropped); + } + + if (self->priv.p->clat_ingress_link) { + err = bpf_link__destroy(self->priv.p->clat_ingress_link); + if (err != 0) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGD("clat: failed to destroy the ingress link"); + } + self->priv.p->clat_ingress_link = NULL; + } + + if (self->priv.p->clat_egress_link) { + err = bpf_link__destroy(self->priv.p->clat_egress_link); + if (err != 0) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGD("clat: failed to destroy the egress link"); + } + self->priv.p->clat_egress_link = NULL; + } + + nm_clear_pointer(&self->priv.p->clat_bpf, clat_bpf__destroy); +} + +static void +_l3_commit_pref64(NML3Cfg *self, NML3CfgCommitType commit_type) +{ + int err = 0; + const NML3ConfigData *l3cd = self->priv.p->combined_l3cd_commited; + struct in6_addr _l3cd_pref64_inner; + const struct in6_addr *l3cd_pref64 = NULL; + guint32 l3cd_pref64_plen; + char buf[100]; + struct clat_config clat_config; + gboolean v6_changed; + const NMPlatformLink *pllink; + gboolean has_ethernet_header = FALSE; + + if (l3cd && nm_l3_config_data_get_pref64(l3cd, &_l3cd_pref64_inner, &l3cd_pref64_plen)) { + l3cd_pref64 = &_l3cd_pref64_inner; + } + + if (l3cd_pref64 && self->priv.p->clat_address_4 && self->priv.p->clat_address_6_valid) { + pllink = nm_l3cfg_get_pllink(self, TRUE); + if (!pllink) { + has_ethernet_header = TRUE; + } else { + switch (pllink->arptype) { + case ARPHRD_ETHER: + has_ethernet_header = TRUE; + break; + case ARPHRD_NONE: + case ARPHRD_PPP: + case ARPHRD_RAWIP: + has_ethernet_header = FALSE; + break; + default: + _LOGD("clat: unknown ARP type %u, assuming the interface uses no L2 header", + pllink->arptype); + has_ethernet_header = FALSE; + } + } + + if (!self->priv.p->clat_bpf) { + _LOGT("clat: attaching the BPF program"); + + self->priv.p->clat_bpf = clat_bpf__open(); + if (!self->priv.p->clat_bpf) { + libbpf_strerror(errno, buf, sizeof(buf)); + _LOGW("clat: failed to open the BPF program: %s", buf); + return; + } + + /* Only load the programs for the right L2 type */ + bpf_program__set_autoload(self->priv.p->clat_bpf->progs.nm_clat_ingress_eth, + has_ethernet_header); + bpf_program__set_autoload(self->priv.p->clat_bpf->progs.nm_clat_egress_eth, + has_ethernet_header); + bpf_program__set_autoload(self->priv.p->clat_bpf->progs.nm_clat_ingress_rawip, + !has_ethernet_header); + bpf_program__set_autoload(self->priv.p->clat_bpf->progs.nm_clat_egress_rawip, + !has_ethernet_header); + + if (clat_bpf__load(self->priv.p->clat_bpf)) { + libbpf_strerror(errno, buf, sizeof(buf)); + _LOGW("clat: failed to load the BPF program: %s", buf); + nm_clear_pointer(&self->priv.p->clat_bpf, clat_bpf__destroy); + return; + } + + self->priv.p->clat_ingress_link = bpf_program__attach_tcx( + has_ethernet_header ? self->priv.p->clat_bpf->progs.nm_clat_ingress_eth + : self->priv.p->clat_bpf->progs.nm_clat_ingress_rawip, + self->priv.ifindex, + NULL); + if (!self->priv.p->clat_ingress_link) { + libbpf_strerror(errno, buf, sizeof(buf)); + _LOGW("clat: failed to attach the ingress program: %s", buf); + return; + } + + self->priv.p->clat_egress_link = bpf_program__attach_tcx( + has_ethernet_header ? self->priv.p->clat_bpf->progs.nm_clat_egress_eth + : self->priv.p->clat_bpf->progs.nm_clat_egress_rawip, + self->priv.ifindex, + NULL); + if (!self->priv.p->clat_egress_link) { + libbpf_strerror(errno, buf, sizeof(buf)); + _LOGW("clat: failed to attach the egress program: %s", buf); + return; + } + + _LOGT("clat: program attached successfully"); + } + + /* Pass configuration to the BPF program */ + memset(&clat_config, 0, sizeof(clat_config)); + clat_config.local_v4.s_addr = self->priv.p->clat_address_4->addr; + clat_config.local_v6 = self->priv.p->clat_address_6.address; + clat_config.pref64 = *l3cd_pref64; + clat_config.pref64_len = l3cd_pref64_plen; + self->priv.p->clat_bpf->bss->config = clat_config; + + if (self->priv.p->clat_socket < 0) { + self->priv.p->clat_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (self->priv.p->clat_socket < 0) { + _LOGW("clat: couldn't create the socket: %s", nm_strerror_native(errno)); + } + } + if (self->priv.p->clat_socket >= 0) { + err = setsockopt(self->priv.p->clat_socket, + SOL_SOCKET, + SO_BINDTOIFINDEX, + &self->priv.ifindex, + sizeof(self->priv.ifindex)); + if (err < 0) { + _LOGW("clat: couldn't bind the socket: %s", nm_strerror_native(errno)); + } + } + + v6_changed = + (self->priv.p->clat_address_6_valid != self->priv.p->clat_address_6_committed_valid) + || (self->priv.p->clat_address_6_valid && self->priv.p->clat_address_6_committed_valid + && memcmp(&self->priv.p->clat_address_6.address, + &self->priv.p->clat_address_6_committed.address, + sizeof(self->priv.p->clat_address_6_committed.address))); + + if (self->priv.p->clat_socket > 0 && v6_changed) { + struct ipv6_mreq mreq = {.ipv6mr_interface = self->priv.ifindex}; + + if (self->priv.p->clat_address_6_committed_valid) { + mreq.ipv6mr_multiaddr = self->priv.p->clat_address_6_committed.address; + + err = setsockopt(self->priv.p->clat_socket, + SOL_IPV6, + IPV6_LEAVE_ANYCAST, + &mreq, + sizeof(mreq)); + if (err < 0) { + _LOGW("clat: couldn't leave the anycast group: %s", nm_strerror_native(errno)); + } + } + + if (self->priv.p->clat_address_6_valid) { + /* As per draft-ietf-v6ops-claton-14, hosts must perform duplicate + * addresses detection (DAD) on the generated CLAT IPv6 address. This is + * necessary not only to avoid address collisions but also because some + * networks drop traffic from addresses that have not done DAD. + * Since doing true DAD adds complexity, adopt the same approach as + * Android: start DAD by sending a neighbor solicitation and don't wait + * for any reply. This avoids the problem with dropped traffic; it + * doesn't help with collisions, but collisions are anyway very unlikely + * because the interface identifier is a random 64-bit value. + */ + nm_utils_ipv6_dad_send(&self->priv.p->clat_address_6.address, + self->priv.ifindex, + pllink ? pllink->arptype : ARPHRD_ETHER); + + mreq.ipv6mr_multiaddr = self->priv.p->clat_address_6.address; + + err = setsockopt(self->priv.p->clat_socket, + SOL_IPV6, + IPV6_JOIN_ANYCAST, + &mreq, + sizeof(mreq)); + if (err < 0) { + _LOGW("clat: couldn't join the anycast group: %s", nm_strerror_native(errno)); + } + } + } + } else { + if (self->priv.p->clat_bpf) { + _l3_clat_destroy(self); + } + + /* Committed will get cleaned up below */ + self->priv.p->clat_address_6_valid = FALSE; + + nm_clear_fd(&self->priv.p->clat_socket); + + /* Deallocate the v4 address. Committed address will get cleaned up below, + but we need to make sure there's no double-free */ + if (self->priv.p->clat_address_4 != self->priv.p->clat_address_4_committed) { + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_reservation_release); + } else { + self->priv.p->clat_address_4 = NULL; + } + } + + /* Record the new state */ + if (self->priv.p->clat_address_4_committed != self->priv.p->clat_address_4) { + nm_clear_pointer(&self->priv.p->clat_address_4_committed, nm_netns_ip_reservation_release); + self->priv.p->clat_address_4_committed = self->priv.p->clat_address_4; + } + self->priv.p->clat_address_6_committed = self->priv.p->clat_address_6; + self->priv.p->clat_address_6_committed_valid = self->priv.p->clat_address_6_valid; +} +#endif /* HAVE_CLAT */ + static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) { @@ -5461,6 +6038,10 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) _l3_acd_data_process_changes(self); +#if HAVE_CLAT + _l3_commit_pref64(self, commit_type); +#endif /* HAVE_CLAT */ + nm_assert(self->priv.p->commit_reentrant_count == 1); self->priv.p->commit_reentrant_count--; @@ -5842,6 +6423,10 @@ nm_l3cfg_init(NML3Cfg *self) { self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_L3CFG, NML3CfgPrivate); +#if HAVE_CLAT + self->priv.p->clat_socket = -1; +#endif /* HAVE_CLAT */ + c_list_init(&self->priv.p->acd_lst_head); c_list_init(&self->priv.p->acd_event_notify_lst_head); c_list_init(&self->priv.p->commit_type_lst_head); @@ -5950,6 +6535,14 @@ finalize(GObject *object) if (changed) nmp_global_tracker_sync_mptcp_addrs(self->priv.global_tracker, FALSE); +#if HAVE_CLAT + self->priv.p->clat_address_4_committed = NULL; + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_reservation_release); + nm_clear_fd(&self->priv.p->clat_socket); + if (self->priv.p->clat_bpf) { + _l3_clat_destroy(self); + } +#endif g_clear_object(&self->priv.netns); g_clear_object(&self->priv.platform); nm_clear_pointer(&self->priv.global_tracker, nmp_global_tracker_unref); diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 87dde2c3..7099cb8a 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -11,7 +11,6 @@ #include <fcntl.h> #include <limits.h> #include <stdlib.h> -#include <sys/sendfile.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -3585,34 +3584,6 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat } static gboolean -copy_lease(const char *src, const char *dst) -{ - nm_auto_close int src_fd = -1; - int dst_fd; - ssize_t res, size = SSIZE_MAX; - - src_fd = open(src, O_RDONLY | O_CLOEXEC); - if (src_fd < 0) - return FALSE; - - dst_fd = open(dst, O_CREAT | O_EXCL | O_CLOEXEC | O_WRONLY, 0644); - if (dst_fd < 0) - return FALSE; - - while ((res = sendfile(dst_fd, src_fd, NULL, size)) > 0) - size -= res; - - nm_close(dst_fd); - - if (res != 0) { - unlink(dst); - return FALSE; - } - - return TRUE; -} - -static gboolean recheck_assume_connection(NMManager *self, NMDevice *device) { NMSettingsConnection *sett_conn; @@ -3652,18 +3623,9 @@ recheck_assume_connection(NMManager *self, NMDevice *device) if (state == NM_DEVICE_STATE_UNMANAGED) { gs_free char *initramfs_lease = g_strdup_printf(RUNSTATEDIR "/initramfs/net.%s.lease", nm_device_get_iface(device)); - gs_free char *connection_lease = g_strdup_printf(NMRUNDIR "/dhclient-%s-%s.lease", - nm_settings_connection_get_uuid(sett_conn), - nm_device_get_iface(device)); - if (copy_lease(initramfs_lease, connection_lease)) { + if (g_file_test(initramfs_lease, G_FILE_TEST_EXISTS)) { unlink(initramfs_lease); - /* - * We've managed to steal the lease used by initramfs before it - * killed off the dhclient. We need to take ownership of the configured - * connection and act like the device was configured by us. - * Otherwise, the address would just expire. - */ _LOG2I(LOGD_DEVICE, device, "assume: taking over an initramfs-configured connection"); activation_type_assume = TRUE; @@ -3885,10 +3847,19 @@ _get_best_connectivity(NMManager *self, int addr_family) if (NM_IS_DEVICE_LOOPBACK(dev)) continue; - r = nm_device_get_best_default_route(dev, addr_family); - if (r) + r = nm_device_get_best_default_route(dev, addr_family); + state = nm_device_get_connectivity_state(dev, addr_family); + + if (r) { + /* If a default-route device is FULL, that is the global state: its + * route carries no penalty, so it outranks any non-FULL device (which + * gets +20000). Decide on the state directly, not the metric, which is + * stale here because the penalty commits to the route asynchronously + * after this recompute. */ + if (nm_connectivity_state_cmp(state, NM_CONNECTIVITY_FULL) >= 0) + return NM_CONNECTIVITY_FULL; metric = NMP_OBJECT_CAST_IP_ROUTE(r)->metric; - else { + } else { /* if all devices have no default-route, we still include the best * of all connectivity state of all the devices. */ metric = G_MAXINT64; @@ -3896,11 +3867,10 @@ _get_best_connectivity(NMManager *self, int addr_family) if (metric > best_metric) { /* we already have a default route with better metric. The connectivity state - * of this device is irreleavnt. */ + * of this device is irrelevant. */ continue; } - state = nm_device_get_connectivity_state(dev, addr_family); if (metric < best_metric) { /* this device has a better default route. It wins. */ best_metric = metric; @@ -8146,6 +8116,27 @@ nm_manager_start(NMManager *self, GError **error) return TRUE; } +static int +compare_device_remove_order(const CList *a, const CList *b, const void *user_data) +{ + NMDevice *dev_a = c_list_entry(a, NMDevice, devices_lst); + NMDevice *dev_b = c_list_entry(b, NMDevice, devices_lst); + + gboolean a_has_dhcp = + nm_device_get_dhcp_config(dev_a, AF_INET) || nm_device_get_dhcp_config(dev_a, AF_INET6); + gboolean b_has_dhcp = + nm_device_get_dhcp_config(dev_b, AF_INET) || nm_device_get_dhcp_config(dev_b, AF_INET6); + gboolean a_is_software = nm_device_is_software(dev_a); + gboolean b_is_software = nm_device_is_software(dev_b); + + /* priority: software AND dhcp first, then dhcp only + * then everything else,*/ + int a_score = a_has_dhcp ? (a_is_software ? 2 : 1) : 0; + int b_score = b_has_dhcp ? (b_is_software ? 2 : 1) : 0; + + return b_score - a_score; +} + void nm_manager_stop(NMManager *self) { @@ -8167,6 +8158,12 @@ nm_manager_stop(NMManager *self) nm_dbus_manager_stop(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self))); + /* When OVS internal interface or linux bridge holds DHCP, if we delete its + * physical interface first, then we cannot send out DHCP release request + * anymore. To fix that, we need to remove/deactivate software interfaces that + * holds DHCP config first. + */ + c_list_sort(&priv->devices_lst_head, compare_device_remove_order, NULL); while ((device = c_list_first_entry(&priv->devices_lst_head, NMDevice, devices_lst))) remove_device(self, device, TRUE); diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c index 0e8b15a7..65142f4f 100644 --- a/src/core/nm-netns.c +++ b/src/core/nm-netns.c @@ -576,8 +576,8 @@ notify_watcher: typedef struct { const char *name; guint32 start_addr; /* host byte order */ - guint prefix_len; - guint num_addrs; + guint range_plen; + guint addr_plen; gboolean allow_reuse; } IPReservationTypeDesc; @@ -585,11 +585,19 @@ static const IPReservationTypeDesc ip_reservation_types[_NM_NETNS_IP_RESERVATION [NM_NETNS_IP_RESERVATION_TYPE_SHARED4] = { .name = "shared-ip4", - .start_addr = 0x0a2a0001, /* 10.42.0.1 */ - .prefix_len = 24, - .num_addrs = 256, + .start_addr = 0x0a2a0001, /* 10.42.{0-255}.1/24 */ + .range_plen = 16, + .addr_plen = 24, .allow_reuse = TRUE, }, + [NM_NETNS_IP_RESERVATION_TYPE_CLAT] = + { + .name = "clat", + .start_addr = 0xc0000005, /* 192.0.0.{5-7,0-4}/32 */ + .range_plen = 29, + .addr_plen = 32, + .allow_reuse = FALSE, + }, }; NMNetnsIPReservation * @@ -615,13 +623,23 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type) g_object_ref(self); } else { guint32 count; + guint32 base_network; + guint32 host_mask; + guint32 increment; nm_assert(g_hash_table_size(*table) > 0); - nm_assert(desc->prefix_len > 0 && desc->prefix_len <= 32); + nm_assert(desc->range_plen < 32); + nm_assert(desc->addr_plen > 0 && desc->addr_plen <= 32); + nm_assert(desc->addr_plen > desc->range_plen); + + base_network = desc->start_addr & ~(0xFFFFFFFFu >> desc->range_plen); + host_mask = 0xFFFFFFFFu >> desc->range_plen; + increment = 1 << (32 - desc->addr_plen); count = 0u; for (;;) { - addr = htonl(desc->start_addr + (count << (32 - desc->prefix_len))); + addr = htonl(base_network + + ((base_network + (desc->start_addr + count * increment)) & host_mask)); res = g_hash_table_lookup(*table, &addr); if (!res) @@ -629,7 +647,7 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type) count++; - if (count >= desc->num_addrs) { + if (count >= 1 << (desc->addr_plen - desc->range_plen)) { if (!desc->allow_reuse) { _LOGE("%s: ran out of IP addresses", desc->name); return NULL; @@ -639,12 +657,12 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type) _LOGE("%s: ran out of IP addresses. Reuse %s/%u", desc->name, nm_inet4_ntop(res->addr, buf), - desc->prefix_len); + desc->addr_plen); } else { _LOGD("%s: reserved IP address %s/%u (duplicate)", desc->name, nm_inet4_ntop(res->addr, buf), - desc->prefix_len); + desc->addr_plen); } res->_ref_count++; return res; @@ -665,7 +683,7 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type) _LOGD("%s: reserved IP address %s/%u", desc->name, nm_inet4_ntop(res->addr, buf), - desc->prefix_len); + desc->addr_plen); return res; } @@ -697,7 +715,7 @@ nm_netns_ip_reservation_release(NMNetnsIPReservation *res) _LOGD("%s: release IP address reservation %s/%u (%d more references held)", desc->name, nm_inet4_ntop(res->addr, buf), - desc->prefix_len, + desc->addr_plen, res->_ref_count); return; } @@ -708,7 +726,7 @@ nm_netns_ip_reservation_release(NMNetnsIPReservation *res) _LOGD("%s: release IP address reservation %s/%u", desc->name, nm_inet4_ntop(res->addr, buf), - desc->prefix_len); + desc->addr_plen); if (g_hash_table_size(*table) == 0) { nm_clear_pointer(table, g_hash_table_unref); diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h index 5ddb852a..e32d5680 100644 --- a/src/core/nm-netns.h +++ b/src/core/nm-netns.h @@ -43,6 +43,7 @@ NML3Cfg *nm_netns_l3cfg_acquire(NMNetns *netns, int ifindex); typedef enum { NM_NETNS_IP_RESERVATION_TYPE_SHARED4, + NM_NETNS_IP_RESERVATION_TYPE_CLAT, _NM_NETNS_IP_RESERVATION_TYPE_NUM, } NMNetnsIPReservationType; diff --git a/src/core/nm-pacrunner-manager.c b/src/core/nm-pacrunner-manager.c index 12c3c9a1..9f4a294b 100644 --- a/src/core/nm-pacrunner-manager.c +++ b/src/core/nm-pacrunner-manager.c @@ -122,43 +122,6 @@ NM_AUTO_DEFINE_FCN0(NMPacrunnerConfId *, _nm_auto_unref_conf_id, conf_id_unref); /*****************************************************************************/ -static void -get_ip_domains(GPtrArray *domains, const NML3ConfigData *l3cd, int addr_family) -{ - NMDedupMultiIter ipconf_iter; - char *cidr; - guint num; - guint i; - char sbuf[NM_INET_ADDRSTRLEN]; - const NMPlatformIPAddress *address; - const NMPlatformIPRoute *route; - const char *const *strv; - - strv = nm_l3_config_data_get_searches(l3cd, addr_family, &num); - for (i = 0; i < num; i++) - g_ptr_array_add(domains, g_strdup(strv[i])); - - strv = nm_l3_config_data_get_domains(l3cd, addr_family, &num); - for (i = 0; i < num; i++) - g_ptr_array_add(domains, g_strdup(strv[i])); - - nm_l3_config_data_iter_ip_address_for_each (&ipconf_iter, l3cd, addr_family, &address) { - cidr = g_strdup_printf("%s/%u", - nm_inet_ntop(addr_family, address->address_ptr, sbuf), - address->plen); - g_ptr_array_add(domains, cidr); - } - - nm_l3_config_data_iter_ip_route_for_each (&ipconf_iter, l3cd, addr_family, &route) { - if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) - continue; - cidr = g_strdup_printf("%s/%u", - nm_inet_ntop(addr_family, route->network_ptr, sbuf), - route->plen); - g_ptr_array_add(domains, cidr); - } -} - static GVariant * _make_request_create_proxy_configuration(const char *iface, const NML3ConfigData *l3cd) { @@ -198,23 +161,6 @@ _make_request_create_proxy_configuration(const char *iface, const NML3ConfigData "BrowserOnly", g_variant_new_boolean(l3cd ? !!nm_l3_config_data_get_proxy_browser_only(l3cd) : FALSE)); - if (l3cd) { - gs_unref_ptrarray GPtrArray *domains = NULL; - - domains = g_ptr_array_new_with_free_func(g_free); - - get_ip_domains(domains, l3cd, AF_INET); - get_ip_domains(domains, l3cd, AF_INET6); - - if (domains->len > 0) { - g_variant_builder_add( - &builder, - "{sv}", - "Domains", - g_variant_new_strv((const char *const *) domains->pdata, domains->len)); - } - } - return g_variant_new("(a{sv})", &builder); } diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c index f7be1a9f..48c004bf 100644 --- a/src/core/nm-policy.c +++ b/src/core/nm-policy.c @@ -204,10 +204,10 @@ expire_ip6_delegations(NMPolicy *self) IP6PrefixDelegation *delegation = NULL; guint i; - for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { - delegation = &nm_g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i); + for (i = priv->ip6_prefix_delegations->len; i > 0; i--) { + delegation = &nm_g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i - 1); if (delegation->prefix.timestamp + delegation->prefix.lifetime < now) - g_array_remove_index_fast(priv->ip6_prefix_delegations, i); + g_array_remove_index(priv->ip6_prefix_delegations, i - 1); } } @@ -253,7 +253,7 @@ ip6_subnet_from_delegation(IP6PrefixDelegation *delegation, NMDevice *device) } /* Check for out-of-prefixes condition */ - num_subnets = 1 << (64 - delegation->prefix.plen); + num_subnets = (guint64) 1 << (64 - delegation->prefix.plen); if (nm_g_hash_table_size(delegation->map_subnet_id_to_ifindex) >= num_subnets) { _LOGD(LOGD_IP6, "ipv6-pd: no more prefixes in %s/%u", @@ -378,10 +378,10 @@ ip6_remove_device_prefix_delegations(NMPolicy *self, NMDevice *device) IP6PrefixDelegation *delegation = NULL; guint i; - for (i = 0; i < priv->ip6_prefix_delegations->len; i++) { - delegation = &nm_g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i); + for (i = priv->ip6_prefix_delegations->len; i > 0; i--) { + delegation = &nm_g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i - 1); if (delegation->device == device) - g_array_remove_index_fast(priv->ip6_prefix_delegations, i); + g_array_remove_index(priv->ip6_prefix_delegations, i - 1); } } diff --git a/src/core/ppp/nm-pppd-plugin.c b/src/core/ppp/nm-pppd-plugin.c index c8a866ec..0bf58a0c 100644 --- a/src/core/ppp/nm-pppd-plugin.c +++ b/src/core/ppp/nm-pppd-plugin.c @@ -198,7 +198,7 @@ nm_ip_up(void *data, int arg) g_variant_builder_add(&builder, "{sv}", NM_PPP_IP4_CONFIG_GATEWAY, - g_variant_new_uint32(peer_opts.ouraddr)); + g_variant_new_uint32(peer_opts.hisaddr)); } g_variant_builder_add(&builder, "{sv}", NM_PPP_IP4_CONFIG_PREFIX, g_variant_new_uint32(32)); diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c index 7ed3712b..c8f5f290 100644 --- a/src/core/settings/nm-settings-connection.c +++ b/src/core/settings/nm-settings-connection.c @@ -1866,6 +1866,8 @@ impl_settings_connection_update2(NMDBusObject *obj, g_variant_iter_init(&iter, args); while (g_variant_iter_next(&iter, "{&sv}", &args_name, &args_value)) { + gs_unref_variant GVariant *args_value_unref = args_value; + if (plugin_name == NULL && nm_streq(args_name, "plugin") && g_variant_is_of_type(args_value, G_VARIANT_TYPE_STRING)) { plugin_name = g_variant_dup_string(args_value, NULL); diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c index 702c53d5..3fe9c76a 100644 --- a/src/core/settings/nm-settings.c +++ b/src/core/settings/nm-settings.c @@ -2934,6 +2934,8 @@ impl_settings_add_connection2(NMDBusObject *obj, g_variant_iter_init(&iter, args); while (g_variant_iter_next(&iter, "{&sv}", &args_name, &args_value)) { + gs_unref_variant GVariant *args_value_unref = args_value; + if (plugin == NULL && nm_streq(args_name, "plugin") && g_variant_is_of_type(args_value, G_VARIANT_TYPE_STRING)) { plugin = g_variant_dup_string(args_value, NULL); diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c index 728dccac..6312154c 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -4401,7 +4401,7 @@ make_wireless_setting(shvarFile *ifcfg, GError **error) NMSettingWireless *s_wireless; const char *cvalue; char *value = NULL; - gint64 chan = 0; + guint64 chan = 0; NMSettingMacRandomization mac_randomization; NMSettingWirelessPowersave powersave = NM_SETTING_WIRELESS_POWERSAVE_DEFAULT; NMTernary ternary; @@ -4502,7 +4502,7 @@ make_wireless_setting(shvarFile *ifcfg, GError **error) value = svGetValueStr_cp(ifcfg, "CHANNEL"); if (value) { - chan = _nm_utils_ascii_str_to_int64(value, 10, 1, 196, 0); + chan = _nm_utils_ascii_str_to_int64(value, 10, 1, _NM_WIFI_CHANNEL_MAX, 0); if (chan == 0) { g_set_error(error, NM_SETTINGS_ERROR, @@ -4518,19 +4518,18 @@ make_wireless_setting(shvarFile *ifcfg, GError **error) value = svGetValueStr_cp(ifcfg, "BAND"); if (value) { - if (!strcmp(value, "a")) { - if (chan && chan <= 14) { - g_set_error(error, - NM_SETTINGS_ERROR, - NM_SETTINGS_ERROR_INVALID_CONNECTION, - "Band '%s' invalid for channel %u", - value, - (guint32) chan); - g_free(value); - goto error; - } - } else if (!strcmp(value, "bg")) { - if (chan && chan > 14) { + if (!NM_IN_STRSET(value, "a", "bg", "6GHz")) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_INVALID_CONNECTION, + "Band '%s' invalid", + value); + g_free(value); + goto error; + } + + if (chan) { + if (!nm_utils_wifi_is_channel_valid(chan, value)) { g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, @@ -4540,22 +4539,26 @@ make_wireless_setting(shvarFile *ifcfg, GError **error) g_free(value); goto error; } - } else { + } + + g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, value, NULL); + g_free(value); + } else if (chan > 0) { + if (chan > _NM_WIFI_CHANNEL_MAX_5GHZ) { g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, - "Invalid wireless band '%s'", - value); + "Setting channel without band is ambiguous and deprecated. Not supported " + "for 6GHz."); g_free(value); goto error; - } - g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, value, NULL); - g_free(value); - } else if (chan > 0) { - if (chan > 14) + } else if (chan > _NM_WIFI_CHANNEL_MAX_2GHZ) { + PARSE_WARNING( + "Setting channel without band is ambiguous and deprecated. Assuming band 'a'."); g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, "a", NULL); - else + } else { g_object_set(s_wireless, NM_SETTING_WIRELESS_BAND, "bg", NULL); + } } value = svGetValueStr_cp(ifcfg, "MTU"); diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c index 21908090..dc60fdf1 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c @@ -849,7 +849,7 @@ write_wireless_setting(NMConnection *connection, GBytes *ssid; const guint8 *ssid_data; gsize ssid_len; - const char *mode, *bssid; + const char *mode, *bssid, *band; const char *device_mac, *cloned_mac; guint32 mtu, chan, i; gboolean adhoc = FALSE, hex_ssid = FALSE; @@ -968,9 +968,11 @@ write_wireless_setting(NMConnection *connection, chan = nm_setting_wireless_get_channel(s_wireless); if (chan) { svSetValueInt64(ifcfg, "CHANNEL", chan); - } else { - /* Band only set if channel is not, since channel implies band */ - svSetValueStr(ifcfg, "BAND", nm_setting_wireless_get_band(s_wireless)); + } + + band = nm_setting_wireless_get_band(s_wireless); + if (band) { + svSetValueStr(ifcfg, "BAND", band); } bssid = nm_setting_wireless_get_bssid(s_wireless); @@ -3598,6 +3600,7 @@ do_write_construct(NMConnection *connection, } else route_ignore = FALSE; + /* Unsupported properties */ if ((s_ip4 = nm_connection_get_setting_ip4_config(connection))) { if (nm_setting_ip_config_get_dhcp_dscp(s_ip4)) { set_error_unsupported(error, @@ -3616,6 +3619,14 @@ do_write_construct(NMConnection *connection, FALSE); return FALSE; } + if (nm_setting_ip4_config_get_clat(NM_SETTING_IP4_CONFIG(s_ip4)) + != NM_SETTING_IP4_CONFIG_CLAT_DEFAULT) { + set_error_unsupported(error, + connection, + NM_SETTING_IP4_CONFIG_SETTING_NAME "." NM_SETTING_IP4_CONFIG_CLAT, + FALSE); + return FALSE; + } } write_ip4_setting(connection, diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected index caeaaff8..8199a9b4 100644 --- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected @@ -1,6 +1,7 @@ ESSID=MySSID MODE=Ap -CHANNEL=196 +CHANNEL=52 +BAND=a MAC_ADDRESS_RANDOMIZATION=default AP_ISOLATION=yes TYPE=Wireless diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_6ghz.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_6ghz.cexpected new file mode 100644 index 00000000..d8ffe48e --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_6ghz.cexpected @@ -0,0 +1,18 @@ +ESSID="Test SSID" +MODE=Managed +BAND=6GHz +MAC_ADDRESS_RANDOMIZATION=default +TYPE=Wireless +PROXY_METHOD=none +BROWSER_ONLY=no +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=default +NAME="Test Write Wi-Fi Band 6GHz" +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_a.cexpected index 7e3d4f02..90570ea3 100644 --- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_a.cexpected @@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes IPV6_DEFROUTE=yes IPV6_FAILURE_FATAL=no IPV6_ADDR_GEN_MODE=default -NAME="Test Write Wi-Fi Band A" +NAME="Test Write Wi-Fi Band A - 5GHz" UUID=${UUID} ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-6ghz b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-6ghz new file mode 100644 index 00000000..803ca19b --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-6ghz @@ -0,0 +1,13 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +NM_CONTROLLED=yes +BOOTPROTO=dhcp +ESSID=blahblah +BAND=6GHz +MODE=Managed +RATE=auto +ONBOOT=yes +USERCTL=yes +PEERDNS=yes +IPV6INIT=no diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-6ghz-channel-mismatch b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-6ghz-channel-mismatch new file mode 100644 index 00000000..26fc29fe --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-6ghz-channel-mismatch @@ -0,0 +1,9 @@ +TYPE=Wireless +DEVICE=eth2 +HWADDR=00:16:41:11:22:33 +BOOTPROTO=dhcp +ESSID=blahblah +CHANNEL=14 +BAND=6GHz +MODE=Managed + diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c index 2f3035ef..f5f80ed8 100644 --- a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c @@ -3991,7 +3991,7 @@ test_write_wifi_band_a(void) s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION); g_object_set(s_con, NM_SETTING_CONNECTION_ID, - "Test Write Wi-Fi Band A", + "Test Write Wi-Fi Band A - 5GHz", NM_SETTING_CONNECTION_UUID, nm_uuid_generate_random_str_a(), NM_SETTING_CONNECTION_TYPE, @@ -4012,7 +4012,7 @@ test_write_wifi_band_a(void) _writer_new_connec_exp(connection, TEST_SCRATCH_DIR, - TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_Band_A.cexpected", + TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_Band_a.cexpected", &testfile); f = _svOpenFile(testfile); @@ -4025,6 +4025,77 @@ test_write_wifi_band_a(void) } static void +test_read_wifi_band_6ghz(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + + connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-6ghz", + NULL, + TYPE_WIRELESS, + NULL); + + s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION); + g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con), + ==, + NM_SETTING_WIRELESS_SETTING_NAME); + + s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS); + g_assert_cmpstr(nm_setting_wireless_get_band(s_wifi), ==, "6GHz"); +} + +static void +test_write_wifi_band_6ghz(void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + shvarFile *f; + gs_unref_bytes GBytes *ssid = + nmtst_gbytes_from_arr(0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44); + + connection = nm_simple_connection_new(); + + s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Write Wi-Fi Band 6GHz", + NM_SETTING_CONNECTION_UUID, + nm_uuid_generate_random_str_a(), + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS); + g_object_set(s_wifi, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NM_SETTING_WIRELESS_BAND, + "6GHz", + NULL); + + nmtst_assert_connection_verifies(connection); + + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-Test_Write_WiFi_Band_6ghz.cexpected", + &testfile); + + f = _svOpenFile(testfile); + _svGetValue_check(f, "BAND", "6GHz"); + svCloseFile(f); + + reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL); + + nmtst_assert_connection_equals(connection, TRUE, reread, FALSE); +} + +static void test_write_wifi_ap_mode(void) { nmtst_auto_unlinkfile char *testfile = NULL; @@ -4055,7 +4126,7 @@ test_write_wifi_ap_mode(void) NM_SETTING_WIRELESS_BAND, "a", NM_SETTING_WIRELESS_CHANNEL, - (guint) 196, + (guint) 52, NM_SETTING_WIRELESS_AP_ISOLATION, NM_TERNARY_TRUE, NULL); @@ -4073,6 +4144,18 @@ test_write_wifi_ap_mode(void) } static void +test_read_wifi_band_6ghz_channel_mismatch(void) +{ + gs_free_error GError *error = NULL; + + _connection_from_file_fail(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-6ghz-channel-mismatch", + NULL, + TYPE_WIRELESS, + &error); + g_assert_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION); +} + +static void test_read_wifi_band_a_channel_mismatch(void) { gs_free_error GError *error = NULL; @@ -10680,6 +10763,9 @@ main(int argc, char **argv) test_read_wifi_band_a_channel_mismatch); g_test_add_func(TPATH "wifi/read-band-bg-channel-mismatch", test_read_wifi_band_bg_channel_mismatch); + g_test_add_func(TPATH "wifi/read-band-6ghz", test_read_wifi_band_6ghz); + g_test_add_func(TPATH "wifi/read-band-6ghz-channel-mismatch", + test_read_wifi_band_6ghz_channel_mismatch); g_test_add_func(TPATH "wifi/read-hidden", test_read_wifi_hidden); nmtst_add_test_func(TPATH "wifi/read-mac-random-always", @@ -10852,6 +10938,7 @@ main(int argc, char **argv) test_write_wifi_wpa_then_wep_with_perms); g_test_add_func(TPATH "wifi/write-hidden", test_write_wifi_hidden); g_test_add_func(TPATH "wifi/write-band-a", test_write_wifi_band_a); + g_test_add_func(TPATH "wifi/write-band-6ghz", test_write_wifi_band_6ghz); g_test_add_func(TPATH "wifi/write-ap-mode", test_write_wifi_ap_mode); g_test_add_func(TPATH "s390/read-qeth-static", test_read_wired_qeth_static); diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c index c7c88260..962f3d15 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c +++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c @@ -143,7 +143,9 @@ cert_writer(NMConnection *connection, vtable->setting_key, strrchr(new_path, '/') + 1); } else { - nm_log_warn(LOGD_SETTINGS, + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, "keyfile: %s.%s: failed to write certificate to file %s: %s", setting_name, vtable->setting_key, diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c index 233afe48..a0ffb483 100644 --- a/src/core/supplicant/nm-supplicant-config.c +++ b/src/core/supplicant/nm-supplicant-config.c @@ -373,14 +373,24 @@ nm_supplicant_config_get_blobs(NMSupplicantConfig *self) } static const char * -wifi_freqs_to_string(gboolean bg_band) +wifi_freqs_to_string(const char *band) { static const char *str_2ghz = NULL; static const char *str_5ghz = NULL; + static const char *str_6ghz = NULL; const char **f_p; const char *f; - f_p = bg_band ? &str_2ghz : &str_5ghz; + if (nm_streq0(band, "a")) + f_p = &str_5ghz; + else if (nm_streq0(band, "bg")) + f_p = &str_2ghz; + else if (nm_streq0(band, "6GHz")) + f_p = &str_6ghz; + else { + nm_assert_not_reached(); + return NULL; + } again: f = g_atomic_pointer_get(f_p); @@ -390,7 +400,13 @@ again: const guint *freqs; int i; - freqs = bg_band ? nm_utils_wifi_2ghz_freqs() : nm_utils_wifi_5ghz_freqs(); + if (f_p == &str_2ghz) + freqs = nm_utils_wifi_2ghz_freqs(); + else if (f_p == &str_5ghz) + freqs = nm_utils_wifi_5ghz_freqs(); + else + freqs = nm_utils_wifi_6ghz_freqs(); + for (i = 0; freqs[i]; i++) { if (i > 0) nm_str_buf_append_c(&strbuf, ' '); @@ -533,38 +549,47 @@ get_ap_params(guint freq, guint channel; guint center_channel = 0; - if (freq < 5000) { - /* the setting is not valid */ - nm_assert_not_reached(); - return; - } - /* Determine the center channel according to the table at * https://en.wikipedia.org/wiki/List_of_WLAN_channels */ - channel = (freq - 5000) / 5; - - if (channel >= 36 && channel <= 48) - center_channel = 42; - else if (channel >= 52 && channel <= 64) - center_channel = 58; - else if (channel >= 100 && channel <= 112) - center_channel = 106; - else if (channel >= 116 && channel <= 128) - center_channel = 122; - else if (channel >= 132 && channel <= 144) - center_channel = 138; - else if (channel >= 149 && channel <= 161) - center_channel = 155; - else if (channel >= 165 && channel <= 177) - center_channel = 171; - - if (center_channel) { + if (freq > 5950) { + /* 6 GHz */ + channel = (freq - 5950) / 5; + channel = ((channel - 1) / 16) * 16 + 7; + *out_ht40 = 1; *out_max_oper_chwidth = 1; - *out_center_freq = 5000 + 5 * center_channel; + *out_center_freq = 5950 + 5 * channel; + } else { + /* 5 GHz */ + if (freq < 5000) { + /* the setting is not valid */ + nm_assert_not_reached(); + return; + } + channel = (freq - 5000) / 5; + + if (channel >= 36 && channel <= 48) + center_channel = 42; + else if (channel >= 52 && channel <= 64) + center_channel = 58; + else if (channel >= 100 && channel <= 112) + center_channel = 106; + else if (channel >= 116 && channel <= 128) + center_channel = 122; + else if (channel >= 132 && channel <= 144) + center_channel = 138; + else if (channel >= 149 && channel <= 161) + center_channel = 155; + else if (channel >= 165 && channel <= 177) + center_channel = 171; + + if (center_channel) { + *out_ht40 = 1; + *out_max_oper_chwidth = 1; + *out_center_freq = 5000 + 5 * center_channel; + } } - return; } @@ -711,10 +736,7 @@ nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self, } else { const char *freqs = NULL; - if (nm_streq(band, "a")) - freqs = wifi_freqs_to_string(FALSE); - else if (nm_streq(band, "bg")) - freqs = wifi_freqs_to_string(TRUE); + freqs = wifi_freqs_to_string(band); if (freqs && !nm_supplicant_config_add_option(self, @@ -1575,8 +1597,14 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self, g_string_free(phase2, TRUE); /* PAC file */ - path = nm_setting_802_1x_get_pac_file(setting); - if (path) { + path = nm_setting_802_1x_get_pac_file(setting); + bytes = priv->private_user && path ? nm_g_hash_table_lookup(files, path) : NULL; + if (bytes) { + if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "pac_file", con_uuid, error)) + return FALSE; + } else if (path) { + /* Private connections cannot use paths */ + g_return_val_if_fail(!priv->private_user, FALSE); if (!add_string_val(self, path, "pac_file", FALSE, NULL, error)) return FALSE; } else { diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c index 5c60a7b6..4476c701 100644 --- a/src/core/supplicant/nm-supplicant-interface.c +++ b/src/core/supplicant/nm-supplicant-interface.c @@ -67,6 +67,7 @@ enum { GROUP_STARTED, /* a new Group (interface) was created */ GROUP_FINISHED, /* a Group (interface) has been finished */ PSK_MISMATCH, /* supplicant reported incorrect PSK */ + SAE_MISMATCH, /* supplicant reported incorrect SAE Password */ LAST_SIGNAL }; @@ -3237,6 +3238,11 @@ _signal_handle(NMSupplicantInterface *self, g_signal_emit(self, signals[PSK_MISMATCH], 0); return; } + + if (nm_streq(signal_name, "SaePasswordMismatch")) { + g_signal_emit(self, signals[SAE_MISMATCH], 0); + return; + } return; } @@ -3879,4 +3885,13 @@ nm_supplicant_interface_class_init(NMSupplicantInterfaceClass *klass) NULL, G_TYPE_NONE, 0); + signals[SAE_MISMATCH] = g_signal_new(NM_SUPPLICANT_INTERFACE_SAE_MISMATCH, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); } diff --git a/src/core/supplicant/nm-supplicant-interface.h b/src/core/supplicant/nm-supplicant-interface.h index 961de1b1..e77f5a42 100644 --- a/src/core/supplicant/nm-supplicant-interface.h +++ b/src/core/supplicant/nm-supplicant-interface.h @@ -87,6 +87,7 @@ typedef enum { #define NM_SUPPLICANT_INTERFACE_GROUP_STARTED "group-started" #define NM_SUPPLICANT_INTERFACE_GROUP_FINISHED "group-finished" #define NM_SUPPLICANT_INTERFACE_PSK_MISMATCH "wpa-psk-mismatch" +#define NM_SUPPLICANT_INTERFACE_SAE_MISMATCH "wpa-sae-password-mismatch" typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass; diff --git a/src/core/supplicant/nm-supplicant-settings-verify.c b/src/core/supplicant/nm-supplicant-settings-verify.c index cca53d82..a1f888ec 100644 --- a/src/core/supplicant/nm-supplicant-settings-verify.c +++ b/src/core/supplicant/nm-supplicant-settings-verify.c @@ -6,6 +6,7 @@ #include "src/core/nm-default-daemon.h" #include "nm-supplicant-settings-verify.h" +#include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include <stdio.h> #include <stdlib.h> @@ -71,7 +72,7 @@ static const struct Opt opt_table[] = { OPT_BYTES("engine_id", 0), OPT_INT("fragment_size", 1, 2000), OPT_KEYWORD("freq_list", NULL), - OPT_INT("frequency", 2412, 5825), + OPT_INT("frequency", _NM_WIFI_FREQ_MIN, _NM_WIFI_FREQ_MAX), OPT_KEYWORD("group", NM_MAKE_STRV("CCMP", "TKIP", "WEP104", "WEP40", "GCMP-256", )), OPT_INT("ht40", 0, 1), OPT_BYTES("identity", 0), diff --git a/src/core/tests/config/NetworkManager-warn.conf b/src/core/tests/config/NetworkManager-warn.conf index 80df7c52..f43f4cf1 100644 --- a/src/core/tests/config/NetworkManager-warn.conf +++ b/src/core/tests/config/NetworkManager-warn.conf @@ -1,5 +1,5 @@ [main] -dhcp=dhclient +dhcp=internal plugin=foo,bar,baz no-auto-default=11:11:11:11:11:11 rc-managed=unmanaged diff --git a/src/core/tests/config/NetworkManager.conf b/src/core/tests/config/NetworkManager.conf index ae9f3e46..7584a0f2 100644 --- a/src/core/tests/config/NetworkManager.conf +++ b/src/core/tests/config/NetworkManager.conf @@ -1,5 +1,5 @@ [main] -dhcp=dhclient +dhcp=internal plugins=foo,bar,baz no-auto-default=11:11:11:11:11:11 diff --git a/src/core/tests/config/test-config.c b/src/core/tests/config/test-config.c index b2f29821..c549bcaa 100644 --- a/src/core/tests/config/test-config.c +++ b/src/core/tests/config/test-config.c @@ -162,7 +162,7 @@ test_config_simple(void) g_assert_cmpstr(nm_config_data_get_config_main_file(nm_config_get_data_orig(config)), ==, TEST_DIR "/NetworkManager.conf"); - g_assert_cmpstr(_config_get_dhcp_client_a(config), ==, "dhclient"); + g_assert_cmpstr(_config_get_dhcp_client_a(config), ==, "internal"); g_assert_cmpstr(nm_config_get_log_level(config), ==, "INFO"); g_assert_cmpint(nm_config_data_get_connectivity_interval(nm_config_get_data_orig(config)), ==, @@ -297,7 +297,7 @@ test_config_override(void) g_assert_cmpstr(nm_config_data_get_config_main_file(nm_config_get_data_orig(config)), ==, TEST_DIR "/NetworkManager.conf"); - g_assert_cmpstr(_config_get_dhcp_client_a(config), ==, "dhclient"); + g_assert_cmpstr(_config_get_dhcp_client_a(config), ==, "internal"); g_assert_cmpstr(nm_config_get_log_level(config), ==, "INFO"); g_assert_cmpint(nm_config_data_get_connectivity_interval(nm_config_get_data_orig(config)), ==, diff --git a/src/core/tests/test-core.c b/src/core/tests/test-core.c index e08296c2..b8920558 100644 --- a/src/core/tests/test-core.c +++ b/src/core/tests/test-core.c @@ -7,6 +7,7 @@ #include <net/if.h> #include <byteswap.h> +#include <netinet/ip6.h> /* need math.h for isinf() and INFINITY. No need to link with -lm */ #include <math.h> @@ -19,6 +20,7 @@ #include "dns/nm-dns-manager.h" #include "nm-connectivity.h" #include "nm-firewall-utils.h" +#include "nm-l3-config-data.h" #include "nm-test-utils-core.h" @@ -2770,6 +2772,104 @@ test_nm_firewall_nft_stdio_mlag(void) "nm-mlag-bond0\012delete table netdev nm-mlag-bond0\012"); } +static void +test_icmp6_checksum(void) +{ + struct ip6_hdr ip6h = {}; + guint8 *data; + guint16 c; + + ip6h.ip6_src = NM_IN6ADDR_INIT(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + ip6h.ip6_dst = NM_IN6ADDR_INIT(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + data = (guint8[]) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + c = nm_utils_icmp6_checksum(&ip6h.ip6_src, 12, data); + g_assert_cmpint(c, ==, htons(0xffb9)); + + ip6h.ip6_src = NM_IN6ADDR_INIT(0xfe, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xc0, + 0x60, + 0x8c, + 0xaf, + 0xf6, + 0x9b, + 0xe4, + 0x1a); + ip6h.ip6_dst = NM_IN6ADDR_INIT(0x20, + 0x02, + 0xaa, + 0xaa, + 0x00, + 0x00, + 0x00, + 0x00, + 0x64, + 0xd4, + 0x29, + 0x32, + 0x35, + 0x85, + 0x7c, + 0x89); + data = (guint8[]) {0xdc, 0x74, 0x1a, 0xcc, 0xd3, 0x8e, 0xca, 0x34}; + c = nm_utils_icmp6_checksum(&ip6h.ip6_src, 8, data); + g_assert_cmpint(c, ==, htons(0x39af)); +} + +/*****************************************************************************/ + +static void +test_l3_config_data_cmp_default_routes(void) +{ + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new(); + nm_auto_unref_l3cd_init NML3ConfigData *a = NULL; + nm_auto_unref_l3cd_init NML3ConfigData *b = NULL; + const int IFINDEX = 1; + + a = nm_l3_config_data_new(multi_idx, IFINDEX, NM_IP_CONFIG_SOURCE_USER); + nm_l3_config_data_add_route_4( + a, + NM_PLATFORM_IP4_ROUTE_INIT(.ifindex = IFINDEX, + .network = 0, + .plen = 0, + .gateway = nmtst_inet4_from_string("192.168.1.1"), + .metric = 100)); + + b = nm_l3_config_data_new(multi_idx, IFINDEX, NM_IP_CONFIG_SOURCE_USER); + nm_l3_config_data_add_route_4( + b, + NM_PLATFORM_IP4_ROUTE_INIT(.ifindex = IFINDEX, + .network = 0, + .plen = 0, + .gateway = nmtst_inet4_from_string("192.168.1.1"), + .metric = 100, + .table_coerced = nm_platform_route_table_coerce(100))); + + nm_l3_config_data_seal(a); + nm_l3_config_data_seal(b); + + g_assert(nm_l3_config_data_get_best_default_route(a, AF_INET)); + g_assert(!nm_l3_config_data_get_best_default_route(b, AF_INET)); + + g_assert_cmpint(nm_l3_config_data_cmp_full(a, b, NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID), !=, 0); + g_assert_cmpint(nm_l3_config_data_cmp_full(b, a, NM_L3_CONFIG_CMP_FLAGS_ROUTES_ID), !=, 0); + + g_assert_cmpint(nm_l3_config_data_cmp_full(a, b, NM_L3_CONFIG_CMP_FLAGS_ROUTES), !=, 0); + g_assert_cmpint(nm_l3_config_data_cmp_full(b, a, NM_L3_CONFIG_CMP_FLAGS_ROUTES), !=, 0); + + g_assert_cmpint(nm_l3_config_data_cmp_full(a, b, NM_L3_CONFIG_CMP_FLAGS_ADDRESSES), ==, 0); + g_assert_cmpint(nm_l3_config_data_cmp_full(b, a, NM_L3_CONFIG_CMP_FLAGS_ADDRESSES), ==, 0); + + g_assert_cmpint(nm_l3_config_data_cmp_full(a, a, NM_L3_CONFIG_CMP_FLAGS_ALL), ==, 0); + g_assert_cmpint(nm_l3_config_data_cmp_full(b, b, NM_L3_CONFIG_CMP_FLAGS_ALL), ==, 0); +} + /*****************************************************************************/ NMTST_DEFINE(); @@ -2848,5 +2948,10 @@ main(int argc, char **argv) g_test_add_func("/core/test_nm_firewall_nft_stdio_mlag", test_nm_firewall_nft_stdio_mlag); + g_test_add_func("/core/general/test_icmp6_checksum", test_icmp6_checksum); + + g_test_add_func("/core/general/test_l3_config_data_cmp_default_routes", + test_l3_config_data_cmp_default_routes); + return g_test_run(); } diff --git a/src/core/tests/test-netns.c b/src/core/tests/test-netns.c index 26ecbcb8..7bcd3809 100644 --- a/src/core/tests/test-netns.c +++ b/src/core/tests/test-netns.c @@ -53,6 +53,44 @@ test_ip_reservation_shared4(void) } } +static void +test_ip_reservation_clat(void) +{ + gs_unref_object NMPlatform *platform = NULL; + gs_unref_object NMNetns *netns = NULL; + NMNetnsIPReservation *res[8]; + NMNetnsIPReservation *res1; + char buf[NM_INET_ADDRSTRLEN]; + guint i; + + platform = g_object_ref(NM_PLATFORM_GET); + netns = nm_netns_new(platform); + + /* Allocate addresses 192.0.0.{5,6,7,0,1,2,3,4} */ + for (i = 0; i < 8; i++) { + res[i] = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_CLAT); + g_snprintf(buf, sizeof(buf), "192.0.0.%u", (i + 5) % 8); + nmtst_assert_ip4_address(res[i]->addr, buf); + g_assert_cmpint(res[i]->_ref_count, ==, 1); + } + + /* Release an address and get it back */ + nm_netns_ip_reservation_release(res[2]); + res[2] = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_CLAT); + nmtst_assert_ip4_address(res[2]->addr, "192.0.0.7"); + + /* No reuse */ + NMTST_EXPECT_NM_ERROR("netns[*]: clat: ran out of IP addresses"); + res1 = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_CLAT); + g_test_assert_expected_messages(); + g_assert_null(res1); + + /* Release all */ + for (i = 0; i < 8; i++) { + nm_netns_ip_reservation_release(res[i]); + } +} + /*****************************************************************************/ NMTST_DEFINE(); @@ -64,6 +102,7 @@ main(int argc, char **argv) nm_linux_platform_setup(); g_test_add_func("/netns/ip_reservation/shared4", test_ip_reservation_shared4); + g_test_add_func("/netns/ip_reservation/clat", test_ip_reservation_clat); return g_test_run(); } diff --git a/src/core/tests/test-systemd.c b/src/core/tests/test-systemd.c index 1b0b7f65..09481a64 100644 --- a/src/core/tests/test-systemd.c +++ b/src/core/tests/test-systemd.c @@ -83,6 +83,42 @@ test_sd_event(void) /*****************************************************************************/ +static void +test_http_url_is_valid_https(void) +{ + /* CVE-2026-10805: connection.mud-url is pasted verbatim into the dhclient + * config inside a quoted string ("send mudurl \"%s\";"). This function + * gates the property at verify() time, so it must reject characters that + * break out of the quotes or inject config syntax. */ +#define _assert_valid(url) g_assert(nm_sd_http_url_is_valid_https("" url)) +#define _assert_invalid(url) g_assert(!nm_sd_http_url_is_valid_https("" url)) + + _assert_valid("https://example.com/mud.json"); + _assert_valid("https://example.com"); + _assert_valid("https://example.com/a?b=c&d=e#frag"); + _assert_valid("https://[2001:db8::1]/x"); + _assert_valid("https://user@example.com/~p/(a)*,;=+!$'"); + _assert_valid("https://user:pass@example.com/p%20q?x=%2F"); + + _assert_invalid("http://example.com"); + _assert_invalid("ftp://example.com"); + _assert_invalid("example.com"); + _assert_invalid(""); + _assert_invalid("https://"); + + _assert_invalid("https://example.com/\""); /* breaks out of the quoted string */ + _assert_invalid("https://example.com/\\"); /* escapes the following char */ + _assert_invalid("https://example.com/\n"); + _assert_invalid("https://example.com/\t"); + _assert_invalid("https://example.com/a\x01b"); + _assert_invalid("https://example.com/\xc3\xa4"); /* non-ASCII */ + +#undef _assert_valid +#undef _assert_invalid +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -91,6 +127,7 @@ main(int argc, char **argv) nmtst_init(&argc, &argv, TRUE); g_test_add_func("/systemd/sd-event", test_sd_event); + g_test_add_func("/systemd/http-url-is-valid-https", test_http_url_is_valid_https); return g_test_run(); } diff --git a/src/core/vpn/nm-vpn-connection.c b/src/core/vpn/nm-vpn-connection.c index 54478c53..4623af9c 100644 --- a/src/core/vpn/nm-vpn-connection.c +++ b/src/core/vpn/nm-vpn-connection.c @@ -2135,16 +2135,20 @@ _dbus_signal_ip_config_cb(NMVpnConnection *self, int addr_family, GVariant *dict IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN : NM_VPN_PLUGIN_IP6_CONFIG_DOMAIN, "&s", - &v_str)) + &v_str)) { nm_l3_config_data_add_domain(l3cd, addr_family, v_str); + nm_l3_config_data_add_search(l3cd, addr_family, v_str); + } if (g_variant_lookup(dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_DOMAINS : NM_VPN_PLUGIN_IP6_CONFIG_DOMAINS, "as", &var_iter)) { - while (g_variant_iter_next(var_iter, "&s", &v_str)) + while (g_variant_iter_next(var_iter, "&s", &v_str)) { nm_l3_config_data_add_domain(l3cd, addr_family, v_str); + nm_l3_config_data_add_search(l3cd, addr_family, v_str); + } g_variant_iter_free(var_iter); } diff --git a/src/core/vpn/nm-vpn-manager.c b/src/core/vpn/nm-vpn-manager.c index 6bf8edae..e49c6898 100644 --- a/src/core/vpn/nm-vpn-manager.c +++ b/src/core/vpn/nm-vpn-manager.c @@ -60,16 +60,21 @@ nm_vpn_manager_activate_connection(NMVpnManager *manager, NMVpnConnection *vpn, { NMVpnManagerPrivate *priv; NMVpnPluginInfo *plugin_info; + NMConnection *applied; const char *service_name; NMDevice *device; + const char *user; g_return_val_if_fail(NM_IS_VPN_MANAGER(manager), FALSE); g_return_val_if_fail(NM_IS_VPN_CONNECTION(vpn), FALSE); g_return_val_if_fail(!error || !*error, FALSE); - priv = NM_VPN_MANAGER_GET_PRIVATE(manager); - device = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(vpn)); - g_assert(device); + priv = NM_VPN_MANAGER_GET_PRIVATE(manager); + device = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(vpn)); + applied = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(vpn)); + nm_assert(device); + nm_assert(applied); + if (nm_device_get_state(device) != NM_DEVICE_STATE_ACTIVATED && nm_device_get_state(device) != NM_DEVICE_STATE_SECONDARIES) { g_set_error_literal(error, @@ -101,6 +106,30 @@ nm_vpn_manager_activate_connection(NMVpnManager *manager, NMVpnConnection *vpn, return FALSE; } + user = nm_utils_get_connection_first_permissions_user(applied); + if (user) { + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(applied); + nm_assert(s_con); + if (_nm_setting_connection_get_num_permissions_users(s_con) > 1) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE, + "private VPN connections with multiple users are not allowed."); + return FALSE; + } + + if (!nm_vpn_plugin_info_supports_safe_private_file_access(plugin_info)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE, + "The '%s' plugin doesn't support private connections.", + nm_vpn_plugin_info_get_name(plugin_info)); + return FALSE; + } + } + nm_vpn_connection_activate(vpn, plugin_info); if (!nm_vpn_plugin_info_supports_multiple(plugin_info)) { |