From 64d2d89542dba9ad841807d8f8aecda3a68307be Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 30 Jul 2024 23:04:38 +0100 Subject: [PATCH] test(connlib): add coverage for the Internet Resource (#6089) With the upcoming feature of full-route tunneling aka an "Internet Resource", we need to expand the reference state machine in `tunnel_test`. In particular, packets to non-resources will now be routed the gateway if we have previously activated the Internet resource. This is reasonably easy to model as we can see from the small diff. Because `connlib` doesn't actually support the Internet resource yet, the code snippet for where it is added to the list of all possible resources to sample from is commented out. --- rust/connlib/shared/src/callbacks.rs | 2 +- rust/connlib/shared/src/messages/client.rs | 14 +++- rust/connlib/shared/src/proptest.rs | 11 ++- .../tunnel/proptest-regressions/tests.txt | 1 + rust/connlib/tunnel/src/tests/assertions.rs | 3 + rust/connlib/tunnel/src/tests/reference.rs | 25 +++++-- rust/connlib/tunnel/src/tests/sim_client.rs | 70 ++++++++++++++++--- rust/connlib/tunnel/src/tests/strategies.rs | 25 +++++-- rust/connlib/tunnel/src/tests/stub_portal.rs | 33 ++++++++- 9 files changed, 160 insertions(+), 24 deletions(-) diff --git a/rust/connlib/shared/src/callbacks.rs b/rust/connlib/shared/src/callbacks.rs index 970680773..6c1df4f57 100644 --- a/rust/connlib/shared/src/callbacks.rs +++ b/rust/connlib/shared/src/callbacks.rs @@ -19,7 +19,7 @@ pub enum Status { pub enum ResourceDescription { Dns(ResourceDescriptionDns), Cidr(ResourceDescriptionCidr), - Internet(ResourceDescriptionCidr), + Internet(ResourceDescriptionInternet), } impl ResourceDescription { diff --git a/rust/connlib/shared/src/messages/client.rs b/rust/connlib/shared/src/messages/client.rs index 8792940ff..1576db569 100644 --- a/rust/connlib/shared/src/messages/client.rs +++ b/rust/connlib/shared/src/messages/client.rs @@ -80,6 +80,16 @@ pub struct ResourceDescriptionInternet { pub sites: Vec, } +impl ResourceDescriptionInternet { + pub fn with_status(self, status: Status) -> crate::callbacks::ResourceDescriptionInternet { + crate::callbacks::ResourceDescriptionInternet { + id: self.id, + sites: self.sites, + status, + } + } +} + #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialOrd, Ord)] pub struct Site { pub id: SiteId, @@ -193,7 +203,9 @@ impl ResourceDescription { ResourceDescription::Cidr(r) => { crate::callbacks::ResourceDescription::Cidr(r.with_status(status)) } - ResourceDescription::Internet(_) => todo!(), + ResourceDescription::Internet(r) => { + crate::callbacks::ResourceDescription::Internet(r.with_status(status)) + } } } } diff --git a/rust/connlib/shared/src/proptest.rs b/rust/connlib/shared/src/proptest.rs index e590d6f67..f38f9300f 100644 --- a/rust/connlib/shared/src/proptest.rs +++ b/rust/connlib/shared/src/proptest.rs @@ -1,5 +1,8 @@ use crate::messages::{ - client::{ResourceDescription, ResourceDescriptionCidr, ResourceDescriptionDns, Site, SiteId}, + client::{ + ResourceDescription, ResourceDescriptionCidr, ResourceDescriptionDns, + ResourceDescriptionInternet, Site, SiteId, + }, ClientId, GatewayId, RelayId, ResourceId, }; use ip_network::{IpNetwork, Ipv4Network, Ipv6Network}; @@ -72,6 +75,12 @@ pub fn cidr_resource( }) } +pub fn internet_resource( + sites: impl Strategy>, +) -> impl Strategy { + (resource_id(), sites).prop_map(move |(id, sites)| ResourceDescriptionInternet { id, sites }) +} + pub fn cidr_v4_resource(host_mask_bits: usize) -> impl Strategy { cidr_resource( any_ip4_network(host_mask_bits), diff --git a/rust/connlib/tunnel/proptest-regressions/tests.txt b/rust/connlib/tunnel/proptest-regressions/tests.txt index 55ba5aab9..f1a71404f 100644 --- a/rust/connlib/tunnel/proptest-regressions/tests.txt +++ b/rust/connlib/tunnel/proptest-regressions/tests.txt @@ -60,3 +60,4 @@ cc 547a1c29e31d0e03c3d9645dd7d644cf6ee24d4118333efae50c6a0d7e2dd1e6 # shrinks to cc eab6084243b6d361bfd3a055870249f30f07d2d9ae808cfe440d2a92910805d5 # shrinks to (initial_state, transitions, seen_counter) = (ReferenceState { client: Host { inner: RefClient { id: ClientId(00000000-0000-0000-0000-000000000000), key: PrivateKey("0000000000000000000000000000000000000000000000000000000000000000"), known_hosts: {}, tunnel_ip4: 100.64.0.1, tunnel_ip6: fd00:2021:1111::, system_dns_resolvers: [0.0.0.0], upstream_dns_resolvers: [IpPort(IpDnsServer { address: 1.127.231.135:53 }), IpPort(IpDnsServer { address: [14c2:6a79:7420:f25f:1ce4:ff9d:dcc6:da21]:53 }), IpPort(IpDnsServer { address: [::ffff:127.0.0.1]:53 }), IpPort(IpDnsServer { address: [7d2e:d5bf:b3c3:b614:262a:bf39:7e4:8d54]:53 })], cidr_resources: {}, dns_resources: {}, dns_records: {}, connected_cidr_resources: {}, connected_dns_resources: {}, expected_icmp_handshakes: {}, expected_dns_handshakes: [] }, ip4: Some(203.0.113.99), ip6: None, old_ports: {0}, default_port: 53282, allocated_ports: {(53282, V4)}, latency: 65ms }, gateways: {GatewayId(546aa1d9-8309-7d32-6030-ee68906ba7a8): Host { inner: RefGateway { key: PrivateKey("fee4bb73bb216c4dbb9723a4aa154a7f3c8e12dbeedcd80448b3267f1e0475d7") }, ip4: Some(203.0.113.78), ip6: Some(2001:db80::59), old_ports: {0}, default_port: 4725, allocated_ports: {(4725, V4), (4725, V6)}, latency: 40ms }}, relays: {RelayId(71f81a67-4384-7479-b327-c3ad4b198e8f): Host { inner: 18265579232023912380, ip4: Some(203.0.113.39), ip6: Some(2001:db80::48), old_ports: {0}, default_port: 3478, allocated_ports: {(3478, V4), (3478, V6)}, latency: 88ms }}, portal: StubPortal { gateways_by_site: {SiteId(03cd8b72-a91e-f818-040b-78989a12c378): {GatewayId(546aa1d9-8309-7d32-6030-ee68906ba7a8)}}, sites_by_resource: {ResourceId(57f2e154-c37d-eb23-fe7c-7def269fa25a): SiteId(03cd8b72-a91e-f818-040b-78989a12c378), ResourceId(45d30f88-fce6-5633-afc5-07b66a5ccd60): SiteId(03cd8b72-a91e-f818-040b-78989a12c378), ResourceId(2816ea38-4f4f-f031-5cc8-46c9beed5209): SiteId(03cd8b72-a91e-f818-040b-78989a12c378), ResourceId(09fa3de9-3baf-8686-ceed-8830ee880d27): SiteId(03cd8b72-a91e-f818-040b-78989a12c378)}, cidr_resources: {ResourceId(57f2e154-c37d-eb23-fe7c-7def269fa25a): ResourceDescriptionCidr { id: ResourceId(57f2e154-c37d-eb23-fe7c-7def269fa25a), address: V4(Ipv4Network { network_address: 221.62.86.128, netmask: 25 }), name: "ocfaaizvoe", address_description: None, sites: [Site { id: SiteId(03cd8b72-a91e-f818-040b-78989a12c378), name: "iqnjedkq" }] }, ResourceId(09fa3de9-3baf-8686-ceed-8830ee880d27): ResourceDescriptionCidr { id: ResourceId(09fa3de9-3baf-8686-ceed-8830ee880d27), address: V4(Ipv4Network { network_address: 179.138.125.137, netmask: 32 }), name: "bzhnddmrp", address_description: Some("ffzmchohy"), sites: [Site { id: SiteId(03cd8b72-a91e-f818-040b-78989a12c378), name: "iqnjedkq" }] }}, dns_resources: {ResourceId(2816ea38-4f4f-f031-5cc8-46c9beed5209): ResourceDescriptionDns { id: ResourceId(2816ea38-4f4f-f031-5cc8-46c9beed5209), address: "xfkekv.pgmda", name: "mojmr", address_description: None, sites: [Site { id: SiteId(03cd8b72-a91e-f818-040b-78989a12c378), name: "iqnjedkq" }] }, ResourceId(45d30f88-fce6-5633-afc5-07b66a5ccd60): ResourceDescriptionDns { id: ResourceId(45d30f88-fce6-5633-afc5-07b66a5ccd60), address: "?.lysnno.daua", name: "mzwo", address_description: Some("wvwnkcnnf"), sites: [Site { id: SiteId(03cd8b72-a91e-f818-040b-78989a12c378), name: "iqnjedkq" }] }}, gateway_selector: Selector { rng: TestRng { rng: ChaCha(ChaCha20Rng { rng: BlockRng { core: ChaChaXCore {}, result_len: 64, index: 64 } }) }, bias_increment: 0 } }, global_dns_records: {Name(cohkip.lysnno.daua.): {198.51.100.176, 2001:db80::b, 198.51.100.196, 198.51.100.22}, Name(hedlv.vlt.hsn.): {254.0.175.188, 127.0.0.1, 74.56.14.172, 111.228.2.198, 0.0.0.0}, Name(ejxryi.dznv.mul.): {::ffff:26.186.154.63}, Name(ncx.nzrgl.): {4.37.52.12, 251.168.201.227, ::ffff:176.120.121.186, ::ffff:70.62.117.245, ::ffff:194.247.205.134}, Name(xfkekv.pgmda.): {2001:db80::bd, 2001:db80::8, 198.51.100.223}}, drop_direct_client_traffic: true, network: RoutingTable { routes: {(V4(Ipv4Network { network_address: 203.0.113.39, netmask: 32 }), Relay(RelayId(71f81a67-4384-7479-b327-c3ad4b198e8f))), (V4(Ipv4Network { network_address: 203.0.113.78, netmask: 32 }), Gateway(GatewayId(546aa1d9-8309-7d32-6030-ee68906ba7a8))), (V4(Ipv4Network { network_address: 203.0.113.99, netmask: 32 }), Client(ClientId(00000000-0000-0000-0000-000000000000))), (V6(Ipv6Network { network_address: 2001:db80::48, netmask: 128 }), Relay(RelayId(71f81a67-4384-7479-b327-c3ad4b198e8f))), (V6(Ipv6Network { network_address: 2001:db80::59, netmask: 128 }), Gateway(GatewayId(546aa1d9-8309-7d32-6030-ee68906ba7a8)))} } }, [ActivateResource(Dns(ResourceDescriptionDns { id: ResourceId(45d30f88-fce6-5633-afc5-07b66a5ccd60), address: "?.lysnno.daua", name: "mzwo", address_description: Some("wvwnkcnnf"), sites: [Site { id: SiteId(03cd8b72-a91e-f818-040b-78989a12c378), name: "iqnjedkq" }] })), SendDnsQuery { domain: Name(cohkip.lysnno.daua.), r_type: AAAA, query_id: 54648, dns_server: 1.127.231.135:53 }, SendICMPPacketToDnsResource { src: fd00:2021:1111::, dst: Name(cohkip.lysnno.daua.), seq: 0, identifier: 0 }, SendICMPPacketToDnsResource { src: fd00:2021:1111::, dst: Name(cohkip.lysnno.daua.), seq: 0, identifier: 0 }], None) cc b72bd98dd2b6aa4d7fb2fb29000c7198ab3b6f93e043cf87fb207611a1d26838 # shrinks to (initial_state, transitions, seen_counter) = (ReferenceState { client: Host { inner: RefClient { id: ClientId(00000000-0000-0000-0000-000000000000), key: PrivateKey("0000000000000000000000000000000000000000000000000000000000000000"), known_hosts: {}, tunnel_ip4: 100.64.0.1, tunnel_ip6: fd00:2021:1111::, system_dns_resolvers: [::ffff:0.0.0.0], upstream_dns_resolvers: [], cidr_resources: {}, dns_resources: {}, dns_records: {}, connected_cidr_resources: {}, connected_dns_resources: {}, expected_icmp_handshakes: {}, expected_dns_handshakes: [] }, ip4: None, ip6: Some(2001:db80::), old_ports: {0}, default_port: 1, allocated_ports: {(1, V6)}, latency: 10ms }, gateways: {GatewayId(00000000-0000-0000-0000-0070596bacaa): Host { inner: RefGateway { key: PrivateKey("e04a93801603cb484f2be7f116c8a6ff2d114621e67c95e0ef8130e5dca04a05") }, ip4: Some(203.0.113.48), ip6: Some(2001:db80::d), old_ports: {0}, default_port: 33619, allocated_ports: {(33619, V6), (33619, V4)}, latency: 94ms }}, relays: {RelayId(82b81509-2e01-4310-af0c-1e2977a3055b): Host { inner: 4354275714811340498, ip4: Some(203.0.113.82), ip6: Some(2001:db80::40), old_ports: {0}, default_port: 3478, allocated_ports: {(3478, V4), (3478, V6)}, latency: 30ms }}, portal: StubPortal { gateways_by_site: {SiteId(68de952d-805b-b1b8-f7ce-e37910be6744): {GatewayId(00000000-0000-0000-0000-0070596bacaa)}}, sites_by_resource: {ResourceId(0b4f45d6-3b4c-19f3-917b-9bcbb1c548e4): SiteId(68de952d-805b-b1b8-f7ce-e37910be6744), ResourceId(5c5be57c-c89c-d331-2924-56b06cf55940): SiteId(2fe3bfba-5209-b8c7-eff7-0fa47212d60d), ResourceId(dd56bf56-e977-7526-3e9f-4e1992c03605): SiteId(2fe3bfba-5209-b8c7-eff7-0fa47212d60d)}, cidr_resources: {ResourceId(5c5be57c-c89c-d331-2924-56b06cf55940): ResourceDescriptionCidr { id: ResourceId(5c5be57c-c89c-d331-2924-56b06cf55940), address: V4(Ipv4Network { network_address: 127.0.0.0, netmask: 26 }), name: "groey", address_description: None, sites: [Site { id: SiteId(2fe3bfba-5209-b8c7-eff7-0fa47212d60d), name: "mstcnfiy" }] }, ResourceId(0b4f45d6-3b4c-19f3-917b-9bcbb1c548e4): ResourceDescriptionCidr { id: ResourceId(0b4f45d6-3b4c-19f3-917b-9bcbb1c548e4), address: V4(Ipv4Network { network_address: 127.0.0.0, netmask: 31 }), name: "vjjcjd", address_description: None, sites: [Site { id: SiteId(68de952d-805b-b1b8-f7ce-e37910be6744), name: "ikukl" }] }}, dns_resources: {ResourceId(dd56bf56-e977-7526-3e9f-4e1992c03605): ResourceDescriptionDns { id: ResourceId(dd56bf56-e977-7526-3e9f-4e1992c03605), address: "?.punzd.vdmfn.ipb", name: "jbzakvxoal", address_description: None, sites: [Site { id: SiteId(2fe3bfba-5209-b8c7-eff7-0fa47212d60d), name: "mstcnfiy" }] }}, gateway_selector: Selector { rng: TestRng { rng: ChaCha(ChaCha20Rng { rng: BlockRng { core: ChaChaXCore {}, result_len: 64, index: 64 } }) }, bias_increment: 0 } }, global_dns_records: {Name(aaa.punzd.vdmfn.ipb.): {198.51.100.15}, Name(cvyq.uhdtb.kvcid.): {127.0.0.1, 232.139.74.54, ::ffff:127.0.0.1}, Name(llmcod.nejw.): {3f1d:5290:e6c5:f8b4:544c:3874:17e4:d507, 9ebd:31bb:be04:ed50:5965:addf:1fef:958d, ::ffff:38.91.200.132, 5.97.63.64}}, drop_direct_client_traffic: true, network: RoutingTable { routes: {(V4(Ipv4Network { network_address: 203.0.113.48, netmask: 32 }), Gateway(GatewayId(00000000-0000-0000-0000-0070596bacaa))), (V4(Ipv4Network { network_address: 203.0.113.82, netmask: 32 }), Relay(RelayId(82b81509-2e01-4310-af0c-1e2977a3055b))), (V6(Ipv6Network { network_address: 2001:db80::, netmask: 128 }), Client(ClientId(00000000-0000-0000-0000-000000000000))), (V6(Ipv6Network { network_address: 2001:db80::d, netmask: 128 }), Gateway(GatewayId(00000000-0000-0000-0000-0070596bacaa))), (V6(Ipv6Network { network_address: 2001:db80::40, netmask: 128 }), Relay(RelayId(82b81509-2e01-4310-af0c-1e2977a3055b)))} } }, [ActivateResource(Cidr(ResourceDescriptionCidr { id: ResourceId(0b4f45d6-3b4c-19f3-917b-9bcbb1c548e4), address: V4(Ipv4Network { network_address: 127.0.0.0, netmask: 31 }), name: "vjjcjd", address_description: None, sites: [Site { id: SiteId(68de952d-805b-b1b8-f7ce-e37910be6744), name: "ikukl" }] })), SendICMPPacketToCidrResource { src: 100.64.0.1, dst: 127.0.0.0, seq: 0, identifier: 0 }, SendICMPPacketToCidrResource { src: 100.64.0.1, dst: 127.0.0.0, seq: 0, identifier: 0 }], None) cc 6f00b4fe571ce46735ae49e5729677e7f098b354ccf1449a14b77ee46a564c40 # shrinks to (initial_state, transitions, seen_counter) = (ReferenceState { client: Host { inner: RefClient { id: 0, key: PrivateKey("0000000000000000000000000000000000000000000000000000000000000000"), known_hosts: {}, tunnel_ip4: 100.64.0.1, tunnel_ip6: fd00:2021:1111::, system_dns_resolvers: [0.0.0.0], upstream_dns_resolvers: [], cidr_resources: {}, dns_resources: {}, dns_records: {}, connected_cidr_resources: {}, connected_dns_resources: {}, expected_icmp_handshakes: {}, expected_dns_handshakes: [] }, ip4: Some(203.0.113.4), ip6: None, old_ports: {0}, default_port: 1, allocated_ports: {(1, V4)}, latency: 10ms }, gateways: {2F8575348E1C955FC: Host { inner: RefGateway { key: PrivateKey("a7ddde752b29d54dd8490c1d09ff3c57cea567538ff3969880df030302ce0186") }, ip4: Some(203.0.113.3), ip6: Some(2001:db80::2), old_ports: {0}, default_port: 34528, allocated_ports: {(34528, V4), (34528, V6)}, latency: 614ms }}, relays: {3BFC7AFF2563AA02AE270F8C169E0582: Host { inner: 18328713063552352787, ip4: Some(203.0.113.86), ip6: Some(2001:db80::26), old_ports: {0}, default_port: 3478, allocated_ports: {(3478, V6), (3478, V4)}, latency: 795ms }}, portal: StubPortal { gateways_by_site: {5724BEB2110961ABB05F3F32AC10E0FF: {2F8575348E1C955FC}}, sites_by_resource: {FF519F2CB514A2C99CD5C1CB0CB82D04: 5724BEB2110961ABB05F3F32AC10E0FF, 5BA249689A1A8355B04834C18F4B535B: 5724BEB2110961ABB05F3F32AC10E0FF, C9675631BBFA99A4BCA5BF13DB5F48EC: 5724BEB2110961ABB05F3F32AC10E0FF, DA21A49524864F9DC41178CCCEE652F2: 5724BEB2110961ABB05F3F32AC10E0FF, FBF8B2918FA52D4A9DF542CE0AF991A2: 5724BEB2110961ABB05F3F32AC10E0FF}, cidr_resources: {DA21A49524864F9DC41178CCCEE652F2: ResourceDescriptionCidr { id: DA21A49524864F9DC41178CCCEE652F2, address: V6(Ipv6Network { network_address: ::ffff:39.157.87.248, netmask: 125 }), name: "lrmenlrqn", address_description: None, sites: [Site { id: 5724BEB2110961ABB05F3F32AC10E0FF, name: "fciscdj" }] }, 5BA249689A1A8355B04834C18F4B535B: ResourceDescriptionCidr { id: 5BA249689A1A8355B04834C18F4B535B, address: V6(Ipv6Network { network_address: ::ffff:40.236.238.234, netmask: 127 }), name: "jlpvfrmhx", address_description: None, sites: [Site { id: 5724BEB2110961ABB05F3F32AC10E0FF, name: "fciscdj" }] }, FBF8B2918FA52D4A9DF542CE0AF991A2: ResourceDescriptionCidr { id: FBF8B2918FA52D4A9DF542CE0AF991A2, address: V6(Ipv6Network { network_address: ::ffff:0.0.0.0, netmask: 127 }), name: "gzaggml", address_description: Some("qtamwxq"), sites: [Site { id: 5724BEB2110961ABB05F3F32AC10E0FF, name: "fciscdj" }] }}, dns_resources: {FF519F2CB514A2C99CD5C1CB0CB82D04: ResourceDescriptionDns { id: FF519F2CB514A2C99CD5C1CB0CB82D04, address: "?.fcr.baj", name: "qzvuasnzdp", address_description: Some("imsuq"), sites: [Site { id: 5724BEB2110961ABB05F3F32AC10E0FF, name: "fciscdj" }] }, C9675631BBFA99A4BCA5BF13DB5F48EC: ResourceDescriptionDns { id: C9675631BBFA99A4BCA5BF13DB5F48EC, address: "?.rivxqn.icm", name: "oikpvyh", address_description: None, sites: [Site { id: 5724BEB2110961ABB05F3F32AC10E0FF, name: "fciscdj" }] }}, gateway_selector: Selector { rng: TestRng { rng: ChaCha(ChaCha20Rng { rng: BlockRng { core: ChaChaXCore {}, result_len: 64, index: 64 } }) }, bias_increment: 0 } }, global_dns_records: {Name(aaa.fcr.baj.): {198.51.100.1}, Name(aaa.rivxqn.icm.): {198.51.100.1}}, drop_direct_client_traffic: true, network: RoutingTable { routes: {(V4(Ipv4Network { network_address: 203.0.113.3, netmask: 32 }), Gateway(2F8575348E1C955FC)), (V4(Ipv4Network { network_address: 203.0.113.4, netmask: 32 }), Client(0)), (V4(Ipv4Network { network_address: 203.0.113.86, netmask: 32 }), Relay(3BFC7AFF2563AA02AE270F8C169E0582)), (V6(Ipv6Network { network_address: 2001:db80::2, netmask: 128 }), Gateway(2F8575348E1C955FC)), (V6(Ipv6Network { network_address: 2001:db80::26, netmask: 128 }), Relay(3BFC7AFF2563AA02AE270F8C169E0582))} } }, [ActivateResource(Cidr(ResourceDescriptionCidr { id: DA21A49524864F9DC41178CCCEE652F2, address: V6(Ipv6Network { network_address: ::ffff:39.157.87.248, netmask: 125 }), name: "lrmenlrqn", address_description: None, sites: [Site { id: 5724BEB2110961ABB05F3F32AC10E0FF, name: "fciscdj" }] })), SendICMPPacketToCidrResource { src: fd00:2021:1111::, dst: ::ffff:39.157.87.248, seq: 0, identifier: 0 }, SendICMPPacketToCidrResource { src: fd00:2021:1111::, dst: ::ffff:39.157.87.248, seq: 0, identifier: 0 }], None) +cc ffad6338bc4f13a8bd70b8c47149cc9e246fbaafbb2ea5ef8dfec15d88dff13f # shrinks to (initial_state, transitions, seen_counter) = (ReferenceState { client: Host { inner: RefClient { id: 0, key: PrivateKey("0000000000000000000000000000000000000000000000000000000000000000"), known_hosts: {}, tunnel_ip4: 100.64.0.1, tunnel_ip6: fd00:2021:1111::, system_dns_resolvers: [0.0.0.0], upstream_dns_resolvers: [], internet_resource: None, cidr_resources: {}, dns_resources: {}, dns_records: {}, connected_internet_resources: false, connected_cidr_resources: {}, connected_dns_resources: {}, expected_icmp_handshakes: {}, expected_dns_handshakes: [] }, ip4: Some(203.0.113.1), ip6: Some(2001:db80::), old_ports: {0}, default_port: 1, allocated_ports: {(1, V6), (1, V4)}, latency: 10ms, inbox: BufferedTransmits { inner: [] } }, gateways: {70703E654B89BF37D9F665: Host { inner: RefGateway { key: PrivateKey("7e352e134c81e86246e97333743052a446a2b8bd61ada00eb9826c79c2e7fa7d") }, ip4: Some(203.0.113.24), ip6: Some(2001:db80::35), old_ports: {0}, default_port: 14062, allocated_ports: {(14062, V6), (14062, V4)}, latency: 44ms, inbox: BufferedTransmits { inner: [] } }}, relays: {FB6E92EEDC915C5FB99078A9F36D03FA: Host { inner: 945565326848173624, ip4: Some(203.0.113.51), ip6: Some(2001:db80::10), old_ports: {0}, default_port: 3478, allocated_ports: {(3478, V6), (3478, V4)}, latency: 16ms, inbox: BufferedTransmits { inner: [] } }}, portal: StubPortal { gateways_by_site: {68B4BC644DE5F0F249FFB3016558099C: {70703E654B89BF37D9F665}}, sites_by_resource: {E17A77901A12F5E1ACC7BAB87829629C: DAB747DEB17138948C05A10E4DA40C69, 1FB725D749FE974ECBB1B06514F9EFCF: 68B4BC644DE5F0F249FFB3016558099C, D87B3DCFB028B941319CC6B670BC3920: 68B4BC644DE5F0F249FFB3016558099C, 35AC75045C1DD15D321158CB273570E2: DAB747DEB17138948C05A10E4DA40C69, 6261B9E0DFC219EA3FEFE7CA795C4759: DAB747DEB17138948C05A10E4DA40C69, F3AFB1503FFB5275E247B40D338981D7: 68B4BC644DE5F0F249FFB3016558099C, 9CF5652148850FE73BF66F6B4929DFC0: 68B4BC644DE5F0F249FFB3016558099C, 609B5D37B3AF65B5452679AF48D43396: DAB747DEB17138948C05A10E4DA40C69}, cidr_resources: {D87B3DCFB028B941319CC6B670BC3920: ResourceDescriptionCidr { id: D87B3DCFB028B941319CC6B670BC3920, address: V6(Ipv6Network { network_address: 7cea:aeb8:76fb:cff7:ca36:7429:4919:dcc0, netmask: 122 }), name: "quaqav", address_description: None, sites: [Site { id: 68B4BC644DE5F0F249FFB3016558099C, name: "qfvlo" }] }, 9CF5652148850FE73BF66F6B4929DFC0: ResourceDescriptionCidr { id: 9CF5652148850FE73BF66F6B4929DFC0, address: V4(Ipv4Network { network_address: 127.0.0.0, netmask: 26 }), name: "dcyfvatbt", address_description: Some("hliktug"), sites: [Site { id: 68B4BC644DE5F0F249FFB3016558099C, name: "qfvlo" }] }, 6261B9E0DFC219EA3FEFE7CA795C4759: ResourceDescriptionCidr { id: 6261B9E0DFC219EA3FEFE7CA795C4759, address: V6(Ipv6Network { network_address: 985e:eec2:a3eb:b2b6:57ba:15e3:8d34:2e40, netmask: 124 }), name: "rdrelb", address_description: None, sites: [Site { id: DAB747DEB17138948C05A10E4DA40C69, name: "idxdtvzjsl" }] }, E17A77901A12F5E1ACC7BAB87829629C: ResourceDescriptionCidr { id: E17A77901A12F5E1ACC7BAB87829629C, address: V6(Ipv6Network { network_address: ::ffff:96.93.142.184, netmask: 126 }), name: "nhtzwf", address_description: None, sites: [Site { id: DAB747DEB17138948C05A10E4DA40C69, name: "idxdtvzjsl" }] }}, dns_resources: {F3AFB1503FFB5275E247B40D338981D7: ResourceDescriptionDns { id: F3AFB1503FFB5275E247B40D338981D7, address: "?.rfgc.emlk.ztx", name: "wnmphidrw", address_description: None, sites: [Site { id: 68B4BC644DE5F0F249FFB3016558099C, name: "qfvlo" }] }, 35AC75045C1DD15D321158CB273570E2: ResourceDescriptionDns { id: 35AC75045C1DD15D321158CB273570E2, address: "*.feluz.pygfb", name: "rpncz", address_description: Some("jwlrlwywdb"), sites: [Site { id: DAB747DEB17138948C05A10E4DA40C69, name: "idxdtvzjsl" }] }, 609B5D37B3AF65B5452679AF48D43396: ResourceDescriptionDns { id: 609B5D37B3AF65B5452679AF48D43396, address: "*.ruwzl.msjhe", name: "rezmo", address_description: None, sites: [Site { id: DAB747DEB17138948C05A10E4DA40C69, name: "idxdtvzjsl" }] }}, internet_resource: ResourceDescriptionInternet { id: 1FB725D749FE974ECBB1B06514F9EFCF, sites: [Site { id: 68B4BC644DE5F0F249FFB3016558099C, name: "qfvlo" }] }, gateway_selector: Selector { rng: TestRng { rng: ChaCha(ChaCha20Rng { rng: BlockRng { core: ChaChaXCore {}, result_len: 64, index: 64 } }) }, bias_increment: 0 } }, global_dns_records: {Name(aaa.ruwzl.msjhe.): {2001:db80::1}, Name(aaa.feluz.pygfb.): {198.51.100.1}, Name(aaa.rfgc.emlk.ztx.): {198.51.100.1}}, drop_direct_client_traffic: false, network: RoutingTable { routes: {(V4(Ipv4Network { network_address: 203.0.113.1, netmask: 32 }), Client(0)), (V4(Ipv4Network { network_address: 203.0.113.24, netmask: 32 }), Gateway(70703E654B89BF37D9F665)), (V4(Ipv4Network { network_address: 203.0.113.51, netmask: 32 }), Relay(FB6E92EEDC915C5FB99078A9F36D03FA)), (V6(Ipv6Network { network_address: 2001:db80::, netmask: 128 }), Client(0)), (V6(Ipv6Network { network_address: 2001:db80::10, netmask: 128 }), Relay(FB6E92EEDC915C5FB99078A9F36D03FA)), (V6(Ipv6Network { network_address: 2001:db80::35, netmask: 128 }), Gateway(70703E654B89BF37D9F665))} } }, [ActivateResource(Internet(ResourceDescriptionInternet { id: 1FB725D749FE974ECBB1B06514F9EFCF, sites: [Site { id: 68B4BC644DE5F0F249FFB3016558099C, name: "qfvlo" }] })), SendICMPPacketToNonResourceIp { src: 100.64.0.1, dst: 198.51.100.124, seq: 0, identifier: 0 }, SendICMPPacketToNonResourceIp { src: 100.64.0.1, dst: 198.51.100.124, seq: 0, identifier: 0 }], None) diff --git a/rust/connlib/tunnel/src/tests/assertions.rs b/rust/connlib/tunnel/src/tests/assertions.rs index 0965901f6..c3fe06b08 100644 --- a/rust/connlib/tunnel/src/tests/assertions.rs +++ b/rust/connlib/tunnel/src/tests/assertions.rs @@ -107,6 +107,9 @@ pub(crate) fn assert_icmp_packets_properties( &mut mapping, ) } + ResourceDst::Internet(resource_dst) => { + assert_destination_is_cdir_resource(gateway_received_request, resource_dst) + } } } } diff --git a/rust/connlib/tunnel/src/tests/reference.rs b/rust/connlib/tunnel/src/tests/reference.rs index f562831f5..ea73ecfd2 100644 --- a/rust/connlib/tunnel/src/tests/reference.rs +++ b/rust/connlib/tunnel/src/tests/reference.rs @@ -41,6 +41,7 @@ pub(crate) struct ReferenceState { #[derive(Debug, Clone)] pub(crate) enum ResourceDst { + Internet(IpAddr), Cidr(IpAddr), Dns(DomainName), } @@ -288,7 +289,9 @@ impl ReferenceStateMachine for ReferenceState { client::ResourceDescription::Cidr(r) => { client.cidr_resources.insert(r.address, r.clone()); } - client::ResourceDescription::Internet(_) => todo!("Unsupported"), + client::ResourceDescription::Internet(r) => { + client.internet_resource = Some(r.id) + } }); } Transition::DeactivateResource(id) => { @@ -333,8 +336,20 @@ impl ReferenceStateMachine for ReferenceState { .exec_mut(|client| client.expected_dns_handshakes.push_back(*query_id)); } }, - Transition::SendICMPPacketToNonResourceIp { .. } => { - // Packets to non-resources are dropped, no state change required. + Transition::SendICMPPacketToNonResourceIp { + src, + dst, + seq, + identifier, + } => { + state.client.exec_mut(|client| { + // If the Internet Resource is active, all packets are expected to be routed. + if client.internet_resource.is_some() { + client.on_icmp_packet_to_internet(*src, *dst, *seq, *identifier, |r| { + state.portal.gateway_for_resource(r).copied() + }) + } + }); } Transition::SendICMPPacketToCidrResource { src, @@ -447,10 +462,10 @@ impl ReferenceStateMachine for ReferenceState { .. } => { let ref_client = state.client.inner(); - let Some(resource) = ref_client.cidr_resource_by_ip(*dst) else { + let Some(rid) = ref_client.cidr_resource_by_ip(*dst) else { return false; }; - let Some(gateway) = state.portal.gateway_for_resource(resource.id) else { + let Some(gateway) = state.portal.gateway_for_resource(rid) else { return false; }; diff --git a/rust/connlib/tunnel/src/tests/sim_client.rs b/rust/connlib/tunnel/src/tests/sim_client.rs index b693838d7..110ce6c6a 100644 --- a/rust/connlib/tunnel/src/tests/sim_client.rs +++ b/rust/connlib/tunnel/src/tests/sim_client.rs @@ -225,6 +225,8 @@ pub struct RefClient { /// The upstream DNS resolvers configured in the portal. pub(crate) upstream_dns_resolvers: Vec, + pub(crate) internet_resource: Option, + /// The CIDR resources the client is aware of. #[derivative(Debug = "ignore")] pub(crate) cidr_resources: IpNetworkTable, @@ -239,6 +241,9 @@ pub struct RefClient { #[derivative(Debug = "ignore")] pub(crate) dns_records: BTreeMap>, + /// Whether we are connected to the gateway serving the Internet resource. + pub(crate) connected_internet_resources: bool, + /// The CIDR resources the client is connected to. #[derivative(Debug = "ignore")] pub(crate) connected_cidr_resources: HashSet, @@ -293,6 +298,43 @@ impl RefClient { } } + #[tracing::instrument(level = "debug", skip_all, fields(dst, resource))] + pub(crate) fn on_icmp_packet_to_internet( + &mut self, + src: IpAddr, + dst: IpAddr, + seq: u16, + identifier: u16, + gateway_by_resource: impl Fn(ResourceId) -> Option, + ) { + tracing::Span::current().record("dst", tracing::field::display(dst)); + + // Second, if we are not yet connected, check if we have a resource for this IP. + let Some(rid) = self.internet_resource else { + tracing::debug!("No internet resource"); + return; + }; + tracing::Span::current().record("resource", tracing::field::display(rid)); + + let Some(gateway) = gateway_by_resource(rid) else { + tracing::error!("No gateway for resource"); + return; + }; + + if self.connected_internet_resources && self.is_tunnel_ip(src) { + tracing::debug!("Connected to Internet resource, expecting packet to be routed"); + self.expected_icmp_handshakes + .entry(gateway) + .or_default() + .push_back((ResourceDst::Internet(dst), seq, identifier)); + return; + } + + // If we have a resource, the first packet will initiate a connection to the gateway. + tracing::debug!("Not connected to resource, expecting to trigger connection intent"); + self.connected_internet_resources = true; + } + #[tracing::instrument(level = "debug", skip_all, fields(dst, resource))] pub(crate) fn on_icmp_packet_to_cidr( &mut self, @@ -305,18 +347,18 @@ impl RefClient { tracing::Span::current().record("dst", tracing::field::display(dst)); // Second, if we are not yet connected, check if we have a resource for this IP. - let Some(resource) = self.cidr_resource_by_ip(dst) else { + let Some(rid) = self.cidr_resource_by_ip(dst) else { tracing::debug!("No resource corresponds to IP"); return; }; - tracing::Span::current().record("resource", tracing::field::display(resource.id)); + tracing::Span::current().record("resource", tracing::field::display(rid)); - let Some(gateway) = gateway_by_resource(resource.id) else { + let Some(gateway) = gateway_by_resource(rid) else { tracing::error!("No gateway for resource"); return; }; - if self.is_connected_to_cidr(resource.id) && self.is_tunnel_ip(src) { + if self.is_connected_to_cidr(rid) && self.is_tunnel_ip(src) { tracing::debug!("Connected to CIDR resource, expecting packet to be routed"); self.expected_icmp_handshakes .entry(gateway) @@ -327,7 +369,7 @@ impl RefClient { // If we have a resource, the first packet will initiate a connection to the gateway. tracing::debug!("Not connected to resource, expecting to trigger connection intent"); - self.connected_cidr_resources.insert(resource.id); + self.connected_cidr_resources.insert(rid); } #[tracing::instrument(level = "debug", skip_all, fields(dst, resource))] @@ -484,8 +526,8 @@ impl RefClient { .collect() } - pub(crate) fn cidr_resource_by_ip(&self, ip: IpAddr) -> Option<&ResourceDescriptionCidr> { - self.cidr_resources.longest_match(ip).map(|(_, r)| r) + pub(crate) fn cidr_resource_by_ip(&self, ip: IpAddr) -> Option { + self.cidr_resources.longest_match(ip).map(|(_, r)| r.id) } pub(crate) fn resolved_ip4_for_non_resources( @@ -541,7 +583,7 @@ impl RefClient { return None; } - self.cidr_resource_by_ip(dns_server).map(|r| r.id) + self.cidr_resource_by_ip(dns_server) } pub(crate) fn all_resource_ids(&self) -> Vec { @@ -556,7 +598,15 @@ impl RefClient { return true; } - self.cidr_resources.iter().any(|(_, r)| r.id == resource_id) + if self.cidr_resources.iter().any(|(_, r)| r.id == resource_id) { + return true; + } + + if self.internet_resource.is_some_and(|r| r == resource_id) { + return true; + } + + false } pub(crate) fn all_resources(&self) -> Vec { @@ -663,11 +713,13 @@ fn ref_client( tunnel_ip6, system_dns_resolvers, upstream_dns_resolvers, + internet_resource: Default::default(), cidr_resources: IpNetworkTable::new(), dns_resources: Default::default(), dns_records: Default::default(), connected_cidr_resources: Default::default(), connected_dns_resources: Default::default(), + connected_internet_resources: Default::default(), expected_icmp_handshakes: Default::default(), expected_dns_handshakes: Default::default(), }, diff --git a/rust/connlib/tunnel/src/tests/strategies.rs b/rust/connlib/tunnel/src/tests/strategies.rs index ecb859c36..385d1c98f 100644 --- a/rust/connlib/tunnel/src/tests/strategies.rs +++ b/rust/connlib/tunnel/src/tests/strategies.rs @@ -7,7 +7,10 @@ use super::{ use crate::client::{IPV4_RESOURCES, IPV6_RESOURCES}; use connlib_shared::{ messages::{ - client::{ResourceDescriptionCidr, ResourceDescriptionDns, Site, SiteId}, + client::{ + ResourceDescriptionCidr, ResourceDescriptionDns, ResourceDescriptionInternet, Site, + SiteId, + }, DnsServer, GatewayId, RelayId, }, proptest::{ @@ -123,19 +126,26 @@ pub(crate) fn gateways_and_portal() -> impl Strategy< prop_oneof![ non_wildcard_dns_resource(any_site(sites.clone())), star_wildcard_dns_resource(any_site(sites.clone())), - question_mark_wildcard_dns_resource(any_site(sites)), + question_mark_wildcard_dns_resource(any_site(sites.clone())), ], 1..5, ); + let internet_resource = internet_resource(any_site(sites)); let gateways = collection::hash_map(gateway_id(), (ref_gateway_host(), gateway_site), 1..=3); let gateway_selector = any::(); - (gateways, cidr_resources, dns_resources, gateway_selector) + ( + gateways, + cidr_resources, + dns_resources, + internet_resource, + gateway_selector, + ) }) .prop_flat_map( - |(gateways, cidr_resources, dns_resources, gateway_selector)| { + |(gateways, cidr_resources, dns_resources, internet_resource, gateway_selector)| { let (gateways, gateways_by_site) = gateways.into_iter().fold( ( BTreeMap::::default(), @@ -190,6 +200,7 @@ pub(crate) fn gateways_and_portal() -> impl Strategy< gateway_selector, cidr_resources, dns_resources, + internet_resource, ); (Just(gateways), Just(portal), dns_resource_records) @@ -224,6 +235,12 @@ fn cidr_resource_outside_reserved_ranges( .prop_filter("resource must not be in the documentation range because we use those for host addresses and DNS IPs", |r| !r.address.is_documentation()) } +fn internet_resource( + site: impl Strategy, +) -> impl Strategy { + connlib_shared::proptest::internet_resource(site.prop_map(|s| vec![s])) +} + fn non_wildcard_dns_resource( site: impl Strategy, ) -> impl Strategy { diff --git a/rust/connlib/tunnel/src/tests/stub_portal.rs b/rust/connlib/tunnel/src/tests/stub_portal.rs index bd4c3984f..75294c07d 100644 --- a/rust/connlib/tunnel/src/tests/stub_portal.rs +++ b/rust/connlib/tunnel/src/tests/stub_portal.rs @@ -3,6 +3,7 @@ use itertools::Itertools; use proptest::sample::Selector; use std::{ collections::{HashMap, HashSet}, + iter, net::IpAddr, }; @@ -16,6 +17,7 @@ pub(crate) struct StubPortal { sites_by_resource: HashMap, cidr_resources: HashMap, dns_resources: HashMap, + internet_resource: client::ResourceDescriptionInternet, #[derivative(Debug = "ignore")] gateway_selector: Selector, @@ -27,6 +29,7 @@ impl StubPortal { gateway_selector: Selector, cidr_resources: HashSet, dns_resources: HashSet, + internet_resource: client::ResourceDescriptionInternet, ) -> Self { let cidr_resources = cidr_resources .into_iter() @@ -57,13 +60,23 @@ impl StubPortal { .id, ) }); + let internet_site = iter::once(( + internet_resource.id, + internet_resource + .sites + .iter() + .exactly_one() + .expect("only single-site resources") + .id, + )); Self { gateways_by_site, gateway_selector, - sites_by_resource: HashMap::from_iter(cidr_sites.chain(dns_sites)), + sites_by_resource: HashMap::from_iter(cidr_sites.chain(dns_sites).chain(internet_site)), cidr_resources, dns_resources, + internet_resource, } } @@ -78,6 +91,10 @@ impl StubPortal { .cloned() .map(client::ResourceDescription::Dns), ) + // TODO: Enable once we actually implement the Internet resource + // .chain(iter::once(client::ResourceDescription::Internet( + // self.internet_resource.clone(), + // ))) .collect() } @@ -122,10 +139,16 @@ impl StubPortal { addresses: resolved_ips.clone(), }) }); + let internet_resource = Some(gateway::ResourceDescription::Internet( + gateway::ResourceDescriptionInternet { + id: self.internet_resource.id, + }, + )); cidr_resource .or(dns_resource) - .expect("resource to be a known CIDR or DNS resource") + .or(internet_resource) + .expect("resource to be a known CIDR, DNS or Internet resource") } pub(crate) fn gateway_for_resource(&self, rid: ResourceId) -> Option<&GatewayId> { @@ -139,7 +162,11 @@ impl StubPortal { .get(&rid) .and_then(|r| Some(r.sites.first()?.id)); - let sid = cidr_site.or(dns_site)?; + let internet_site = (self.internet_resource.id == rid) + .then(|| Some(self.internet_resource.sites.first()?.id)) + .flatten(); + + let sid = cidr_site.or(dns_site).or(internet_site)?; let gateways = self.gateways_by_site.get(&sid)?; let gid = self.gateway_selector.try_select(gateways)?;