diff mbox series

[ovs-dev,07/11] ovn-northd-ddlog: Define in_addr, in6_addr, eth_addr in ddlog code.

Message ID 20210304041012.4128938-8-blp@ovn.org
State Deferred
Headers show
Series ovn-northd-ddlog improvements | expand

Commit Message

Ben Pfaff March 4, 2021, 4:10 a.m. UTC
All of these were defined as opaque "extern" types in the ddlog code.
I have found that they are easier to work with and understand if
the ddlog code can look into their implementations.  This commit
re-defines them in terms of proper ddlog types.  It also renames
many of the functions that operate on them so that they can be used
in an object-like form, as well as defining many of these functions
in ddlog code rather than as externs.

This code refactoring shouldn't change ovn-northd-ddlog behavior.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 northd/ipam.dl       |  64 +++++-----
 northd/lrouter.dl    |   4 +-
 northd/lswitch.dl    |   7 +-
 northd/multicast.dl  |   2 +-
 northd/ovn.dl        | 281 +++++++++++++++++++++--------------------
 northd/ovn.rs        | 290 +++++++++++++------------------------------
 northd/ovn_northd.dl | 116 +++++++++--------
 7 files changed, 320 insertions(+), 444 deletions(-)
diff mbox series

Patch

diff --git a/northd/ipam.dl b/northd/ipam.dl
index 1349a933eba0..589126f81288 100644
--- a/northd/ipam.dl
+++ b/northd/ipam.dl
@@ -30,7 +30,7 @@  import ovn
 import lswitch
 import lrouter
 
-function mAC_ADDR_SPACE(): bit<64>  = 64'hffffff
+function mAC_ADDR_SPACE(): bit<48>  = 48'hffffff
 
 /*
  * IPv4 dynamic address allocation.
@@ -112,10 +112,10 @@  SwitchIPv4ReservedAddress(.lswitch = ls._uuid,
                 Right{ranges} -> {
                     for (rng in ranges) {
                         (var ip_start, var ip_end) = rng;
-                        var start = iptohl(ip_start);
+                        var start = ip_start.a;
                         var end = match (ip_end) {
                             None -> start,
-                            Some{ip} -> iptohl(ip)
+                            Some{ip} -> ip.a
                         };
                         start = max(start_ipv4, start);
                         end = min(start_ipv4 + total_ipv4s - 1, end);
@@ -147,7 +147,7 @@  SwitchIPv4ReservedAddress(.lswitch = ls._uuid,
     var addrs = {
         var addrs = set_empty();
         for (addr in lport_addrs.ipv4_addrs) {
-            var addr_host_endian = iptohl(addr.addr);
+            var addr_host_endian = addr.addr.a;
             if (addr_host_endian >= start_ipv4 and addr_host_endian < start_ipv4 + total_ipv4s) {
                 addrs.insert(addr_host_endian)
             } else ()
@@ -166,7 +166,7 @@  SwitchIPv4ReservedAddress(.lswitch = ls._uuid,
     var addrs = {
         var addrs = set_empty();
         for (addr in rport.networks.ipv4_addrs) {
-            var addr_host_endian = iptohl(addr.addr);
+            var addr_host_endian = addr.addr.a;
             if (addr_host_endian >= start_ipv4 and addr_host_endian < start_ipv4 + total_ipv4s) {
                 addrs.insert(addr_host_endian)
             } else ()
@@ -177,7 +177,7 @@  SwitchIPv4ReservedAddress(.lswitch = ls._uuid,
 
 /* Add reserved address group (5) */
 SwitchIPv4ReservedAddress(.lswitch = sw.ls._uuid,
-                          .addr    = iptohl(ip_addr)) :-
+                          .addr    = ip_addr.a) :-
     &SwitchPort(.sw = &sw, .lsp = lsp, .static_dynamic_ipv4 = Some{ip_addr}).
 
 /* Aggregate all reserved addresses for each switch. */
@@ -236,7 +236,7 @@  SwitchPortAllocatedIPv4DynAddress(lsport, dyn_addr) :-
                             need_addr.push(deref(port).lsp._uuid)
                         },
                         Some{addr} -> {
-                            var haddr = iptohl(addr.addr);
+                            var haddr = addr.addr.a;
                             if (haddr < start_ipv4 or haddr >= start_ipv4 + total_ipv4s) {
                                 need_addr.push(deref(port).lsp._uuid)
                             } else if (used_addrs.contains(haddr)) {
@@ -257,7 +257,7 @@  SwitchPortAllocatedIPv4DynAddress(lsport, dyn_addr) :-
     },
     var port_address = FlatMap(dyn_addresses),
     (var lsport, var dyn_addr_bits) = port_address,
-    var dyn_addr = dyn_addr_bits.map(hltoip).
+    var dyn_addr = dyn_addr_bits.map(|x| InAddr{x}).
 
 /* Compute new dynamic IPv4 address assignment:
  * - port does not need dynamic IP - use static_dynamic_ip if any
@@ -278,7 +278,7 @@  SwitchPortNewIPv4DynAddress(lsp._uuid, ip_addr) :-
                 match (sw.subnet) {
                     None -> { None },
                     Some{(_, _, start_ipv4, total_ipv4s)} -> {
-                        var haddr = iptohl(addr);
+                        var haddr = addr.a;
                         if (haddr < start_ipv4 or haddr >= start_ipv4 + total_ipv4s) {
                             /* new static ip is not valid */
                             None
@@ -298,24 +298,20 @@  SwitchPortNewIPv4DynAddress(lsport, addr) :-
  * Dynamic MAC address allocation.
  */
 
-function get_mac_prefix(options: Map<string,string>, uuid: uuid) : bit<64> =
+function get_mac_prefix(options: Map<string,string>, uuid: uuid) : bit<48> =
 {
-    var existing_prefix = match (map_get(options, "mac_prefix")) {
-        Some{prefix} -> scan_eth_addr_prefix(prefix),
-        None -> None
-    };
-    match (existing_prefix) {
-        Some{prefix} -> prefix,
-        None -> pseudorandom_mac(uuid, 16'h1234) & 64'hffffff000000
+    match (map_get(options, "mac_prefix").and_then(scan_eth_addr_prefix)) {
+        Some{prefix} -> prefix.ha,
+        None -> eth_addr_pseudorandom(uuid, 16'h1234).ha & 48'hffffff000000
     }
 }
-function put_mac_prefix(options: Map<string,string>, mac_prefix: bit<64>)
+function put_mac_prefix(options: Map<string,string>, mac_prefix: bit<48>)
     : Map<string,string> =
 {
     map_insert_imm(options, "mac_prefix",
-                   string_substr(to_string(eth_addr_from_uint64(mac_prefix)), 0, 8))
+                   string_substr(to_string(EthAddr{mac_prefix}), 0, 8))
 }
-relation MacPrefix(mac_prefix: bit<64>)
+relation MacPrefix(mac_prefix: bit<48>)
 MacPrefix(get_mac_prefix(options, uuid)) :-
     nb::NB_Global(._uuid = uuid, .options = options).
 
@@ -324,24 +320,24 @@  MacPrefix(get_mac_prefix(options, uuid)) :-
  * (2) static MAC component of "dynamic" `lsp.addresses`.
  * (3) addresses associated with router ports peered with the switch.
  *
- * Addresses are kept in 64-bit host-endian format.
+ * Addresses are kept in host-endian format.
  */
-relation ReservedMACAddress(addr: bit<64>)
+relation ReservedMACAddress(addr: bit<48>)
 
 /* Add reserved address group (1). */
-ReservedMACAddress(.addr = eth_addr_to_uint64(lport_addrs.ea)) :-
+ReservedMACAddress(.addr = lport_addrs.ea.ha) :-
     SwitchPortStaticAddresses(.addrs = lport_addrs).
 
 /* Add reserved address group (2). */
-ReservedMACAddress(.addr = eth_addr_to_uint64(mac_addr)) :-
+ReservedMACAddress(.addr = mac_addr.ha) :-
     &SwitchPort(.lsp = lsp, .static_dynamic_mac = Some{mac_addr}).
 
 /* Add reserved address group (3). */
-ReservedMACAddress(.addr = eth_addr_to_uint64(rport.networks.ea)) :-
+ReservedMACAddress(.addr = rport.networks.ea.ha) :-
     &SwitchPort(.peer = Some{&rport}).
 
 /* Aggregate all reserved MAC addresses. */
-relation ReservedMACAddresses(addrs: Set<bit<64>>)
+relation ReservedMACAddresses(addrs: Set<bit<48>>)
 
 ReservedMACAddresses(addrs) :-
     ReservedMACAddress(addr),
@@ -360,7 +356,7 @@  ReservedMACAddresses(set_empty()) :-
  * Case 2: needs dynamic MAC, has dynamic MAC, has existing dynamic MAC with the right prefix
  * needs dynamic MAC, does not have fixed dynamic MAC, doesn't have existing dynamic MAC with correct prefix
  */
-relation SwitchPortAllocatedMACDynAddress(lsport: uuid, dyn_addr: bit<64>)
+relation SwitchPortAllocatedMACDynAddress(lsport: uuid, dyn_addr: bit<48>)
 
 SwitchPortAllocatedMACDynAddress(lsport, dyn_addr),
 SwitchPortDuplicateMACAddress(dup_addrs) :-
@@ -380,8 +376,8 @@  SwitchPortDuplicateMACAddress(dup_addrs) :-
                 None       -> Some { mac_prefix | 1 },
                 Some{addr} -> {
                     /* The tentative MAC's suffix will be in the interval (1, 0xfffffe). */
-                    var mac_suffix: bit<24> = iptohl(addr)[23:0] % ((mAC_ADDR_SPACE() - 1)[23:0]) + 1;
-                    Some{ mac_prefix | (40'd0 ++ mac_suffix) }
+                    var mac_suffix: bit<24> = addr.a[23:0] % ((mAC_ADDR_SPACE() - 1)[23:0]) + 1;
+                    Some{ mac_prefix | (24'd0 ++ mac_suffix) }
                 }
             };
             match (port.dynamic_address) {
@@ -390,7 +386,7 @@  SwitchPortDuplicateMACAddress(dup_addrs) :-
                     need_addr.push((port.lsp._uuid, hint))
                 },
                 Some{dynaddr} -> {
-                    var haddr = eth_addr_to_uint64(dynaddr.ea);
+                    var haddr = dynaddr.ea.ha;
                     if ((haddr ^ mac_prefix) >> 24 != 0) {
                         /* existing dynamic address is no longer valid */
                         need_addr.push((port.lsp._uuid, hint))
@@ -413,7 +409,7 @@  SwitchPortDuplicateMACAddress(dup_addrs) :-
         var res_strs = vec_empty();
         for (x in res) {
             (var uuid, var addr) = x;
-            res_strs.push("${uuid2str(uuid)}: ${eth_addr_from_uint64(addr)}")
+            res_strs.push("${uuid2str(uuid)}: ${EthAddr{addr}}")
         };
         (res, dup_addrs)
     },
@@ -449,7 +445,7 @@  SwitchPortNewMACDynAddress(lsp._uuid, mac_addr) :-
         }
     }.
 
-SwitchPortNewMACDynAddress(lsport, Some{eth_addr_from_uint64(addr)}) :-
+SwitchPortNewMACDynAddress(lsport, Some{EthAddr{addr}}) :-
     SwitchPortAllocatedMACDynAddress(lsport, addr).
 
 SwitchPortNewMACDynAddress(lsp._uuid, addr) :-
@@ -462,7 +458,7 @@  SwitchPortNewMACDynAddress(lsp._uuid, addr) :-
 
 /*
  * Dynamic IPv6 address allocation.
- * `needs_dynamic_ipv6address` -> in6_generate_eui64(mac, ipv6_prefix)
+ * `needs_dynamic_ipv6address` -> mac.to_ipv6_eui64(ipv6_prefix)
  */
 relation SwitchPortNewDynamicAddress(port: Ref<SwitchPort>, address: Option<lport_addresses>)
 
@@ -479,7 +475,7 @@  SwitchPortNewDynamicAddress(port, lport_address) :-
     SwitchPortNewIPv4DynAddress(lsp._uuid, opt_ip4_addr),
     var ip6_addr = match ((static_dynamic_ipv6, needs_dynamic_ipv6address, sw.ipv6_prefix)) {
         (Some{ipv6}, _, _) -> " ${ipv6}",
-        (_, true, Some{prefix}) -> " ${in6_generate_eui64(mac_addr, prefix)}",
+        (_, true, Some{prefix}) -> " ${mac_addr.to_ipv6_eui64(prefix)}",
         _ -> ""
     },
     var ip4_addr = match (opt_ip4_addr) {
diff --git a/northd/lrouter.dl b/northd/lrouter.dl
index 7bcacfe2aeb6..7786ef137fc6 100644
--- a/northd/lrouter.dl
+++ b/northd/lrouter.dl
@@ -655,7 +655,7 @@  function find_lrp_member_ip(networks: lport_addresses, ip: v46_ip): Option<v46_i
     match (ip) {
         IPv4{ip4} -> {
             for (na in networks.ipv4_addrs) {
-                if (ip_same_network((na.addr, ip4), ipv4_netaddr_mask(na))) {
+                if ((na.addr, ip4).same_network(na.netmask())) {
                     /* There should be only 1 interface that matches the
                      * supplied IP.  Otherwise, it's a configuration error,
                      * because subnets of a router's interfaces should NOT
@@ -667,7 +667,7 @@  function find_lrp_member_ip(networks: lport_addresses, ip: v46_ip): Option<v46_i
         },
         IPv6{ip6} -> {
             for (na in networks.ipv6_addrs) {
-                if (ipv6_same_network((na.addr, ip6), ipv6_netaddr_mask(na))) {
+                if ((na.addr, ip6).same_network(na.netmask())) {
                     /* There should be only 1 interface that matches the
                      * supplied IP.  Otherwise, it's a configuration error,
                      * because subnets of a router's interfaces should NOT
diff --git a/northd/lswitch.dl b/northd/lswitch.dl
index 5fcb3871c341..47c497e0cff7 100644
--- a/northd/lswitch.dl
+++ b/northd/lswitch.dl
@@ -242,12 +242,11 @@  function ipv6_parse_prefix(s: string): Option<in6_addr> {
                         None
                     },
                     Right{(subnet, mask)} -> {
-                        if (ip_count_cidr_bits(mask) == Some{32}
-                            or not ip_is_cidr(mask)) {
+                        if (mask.cidr_bits() == Some{32} or not mask.is_cidr()) {
                             warn("bad 'subnet' ${subnet_str}");
                             None
                         } else {
-                            Some{(subnet, mask, (iptohl(subnet) & iptohl(mask)) + 1, ~iptohl(mask))}
+                            Some{(subnet, mask, (subnet.a & mask.a) + 1, ~mask.a)}
                         }
                     }
                 }
@@ -732,7 +731,7 @@  function get_svc_monitor_mac(options: Map<string,string>, uuid: uuid)
     };
     match (existing_mac) {
         Some{mac} -> mac,
-        None -> eth_addr_from_uint64(pseudorandom_mac(uuid, 'h5678))
+        None -> eth_addr_pseudorandom(uuid, 'h5678)
     }
 }
 function put_svc_monitor_mac(options: Map<string,string>,
diff --git a/northd/multicast.dl b/northd/multicast.dl
index f3989e7899d2..990203bffe25 100644
--- a/northd/multicast.dl
+++ b/northd/multicast.dl
@@ -224,7 +224,7 @@  IgmpRouterGroupPort(address, rtr_port.router, rtr_port.lrp._uuid) :-
      * (RFC 4291 2.7).
      */
     match (ipv6_parse(address)) {
-        Some{ipv6} -> ipv6_is_routable_multicast(ipv6),
+        Some{ipv6} -> ipv6.is_routable_multicast(),
         None -> true
     },
     var flood_port = FlatMap(sw_flood_ports),
diff --git a/northd/ovn.dl b/northd/ovn.dl
index 52fe3d82be33..f23ea3b9e1b1 100644
--- a/northd/ovn.dl
+++ b/northd/ovn.dl
@@ -23,90 +23,69 @@  function is_enabled(s: Option<bool>): bool = {
 /*
  * Ethernet addresses
  */
-extern type eth_addr
+typedef eth_addr = EthAddr {
+    ha: bit<48>                 // In host byte order, e.g. ha[40] is the multicast bit.
+}
 
-extern function eth_addr_zero(): eth_addr
-extern function eth_addr2string(addr: eth_addr): string
 function to_string(addr: eth_addr): string {
     eth_addr2string(addr)
 }
-extern function scan_eth_addr(s: string): Option<eth_addr>
-extern function scan_eth_addr_prefix(s: string): Option<bit<64>>
 extern function eth_addr_from_string(s: string): Option<eth_addr>
-extern function eth_addr_to_uint64(ea: eth_addr): bit<64>
-extern function eth_addr_from_uint64(x: bit<64>): eth_addr
-extern function eth_addr_mark_random(ea: eth_addr): eth_addr
+extern function scan_eth_addr(s: string): Option<eth_addr>
+extern function scan_eth_addr_prefix(s: string): Option<eth_addr>
+function eth_addr_zero(): eth_addr { EthAddr{0} }
+function eth_addr_pseudorandom(seed: uuid, variant: bit<16>) : eth_addr {
+    EthAddr{hash64(seed ++ variant) as bit<48>}.mark_random()
+}
+function mark_random(ea: eth_addr): eth_addr { EthAddr{ea.ha & ~(1 << 40) | (1 << 41)} }
 
-function pseudorandom_mac(seed: uuid, variant: bit<16>) : bit<64> = {
-    eth_addr_to_uint64(eth_addr_mark_random(eth_addr_from_uint64(hash64(seed ++ variant))))
+function to_eui64(ea: eth_addr): bit<64> {
+    var ha = ea.ha as u64;
+    (((ha & 64'hffffff000000) << 16) | 64'hfffe000000 | (ha & 64'hffffff)) ^ (1 << 57)
 }
 
+extern function eth_addr2string(addr: eth_addr): string
+
 /*
  * IPv4 addresses
  */
 
-extern type in_addr
-
-function to_string(ip: in_addr): string = {
-    var x = iptohl(ip);
-    "${x >> 24}.${(x >> 16) & 'hff}.${(x >> 8) & 'hff}.${x & 'hff}"
-}
-
-function ip_is_cidr(netmask: in_addr): bool {
-    var x = ~iptohl(netmask);
-    (x & (x + 1)) == 0
-}
-function ip_is_local_multicast(ip: in_addr): bool {
-    (iptohl(ip) & 32'hffffff00) == 32'he0000000
-}
-
-function ip_create_mask(plen: bit<32>): in_addr {
-    hltoip((64'h00000000ffffffff << (32 - plen))[31:0])
-}
-
-function ip_bitxor(a: in_addr, b: in_addr): in_addr {
-    hltoip(iptohl(a) ^ iptohl(b))
-}
-
-function ip_bitand(a: in_addr, b: in_addr): in_addr {
-    hltoip(iptohl(a) & iptohl(b))
-}
-
-function ip_network(addr: in_addr, mask: in_addr): in_addr {
-    hltoip(iptohl(addr) & iptohl(mask))
-}
-
-function ip_host(addr: in_addr, mask: in_addr): in_addr {
-    hltoip(iptohl(addr) & ~iptohl(mask))
+typedef in_addr = InAddr {
+    a: bit<32>                  // In host byte order.
 }
 
-function ip_host_is_zero(addr: in_addr, mask: in_addr): bool {
-    ip_is_zero(ip_host(addr, mask))
-}
+extern function ip_parse(s: string): Option<in_addr>
+extern function ip_parse_masked(s: string): Either<string/*err*/, (in_addr/*host_ip*/, in_addr/*mask*/)>
+extern function ip_parse_cidr(s: string): Either<string/*err*/, (in_addr/*ip*/, bit<32>/*plen*/)>
+extern function scan_static_dynamic_ip(s: string): Option<in_addr>
+function ip_create_mask(plen: bit<32>): in_addr { InAddr{(64'hffffffff << (32 - plen))[31:0]} }
 
-function ip_is_zero(a: in_addr): bool {
-    iptohl(a) == 0
+function to_string(ip: in_addr): string = {
+    "${ip.a >> 24}.${(ip.a >> 16) & 'hff}.${(ip.a >> 8) & 'hff}.${ip.a & 'hff}"
 }
 
-function ip_bcast(addr: in_addr, mask: in_addr): in_addr {
-   hltoip(iptohl(addr) | ~iptohl(mask))
+function is_cidr(netmask: in_addr): bool { var x = ~netmask.a; (x & (x + 1)) == 0 }
+function is_local_multicast(ip: in_addr): bool { (ip.a & 32'hffffff00) == 32'he0000000 }
+function is_zero(a: in_addr): bool { a.a == 0 }
+function is_all_ones(a: in_addr): bool { a.a == 32'hffffffff }
+function cidr_bits(ip: in_addr): Option<bit<8>> {
+    if (ip.is_cidr()) {
+        Some{32 - ip.a.trailing_zeros() as u8}
+    } else {
+        None
+    }
 }
 
-extern function ip_parse(s: string): Option<in_addr>
-extern function ip_parse_masked(s: string): Either<string/*err*/, (in_addr/*host_ip*/, in_addr/*mask*/)>
-extern function ip_parse_cidr(s: string): Either<string/*err*/, (in_addr/*ip*/, bit<32>/*plen*/)>
-extern function ip_count_cidr_bits(ip: in_addr): Option<bit<8>>
+function network(addr: in_addr, mask: in_addr): in_addr { InAddr{addr.a & mask.a} }
+function host(addr: in_addr, mask: in_addr): in_addr { InAddr{addr.a & ~mask.a} }
+function bcast(addr: in_addr, mask: in_addr): in_addr { InAddr{addr.a | ~mask.a} }
 
 /* True if both 'ips' are in the same network as defined by netmask 'mask',
  * false otherwise. */
-function ip_same_network(ips: (in_addr, in_addr), mask: in_addr): bool {
-    ((iptohl(ips.0) ^ iptohl(ips.1)) & iptohl(mask)) == 0
+function same_network(ips: (in_addr, in_addr), mask: in_addr): bool {
+    ((ips.0.a ^ ips.1.a) & mask.a) == 0
 }
 
-extern function iptohl(addr: in_addr): bit<32>
-extern function hltoip(addr: bit<32>): in_addr
-extern function scan_static_dynamic_ip(s: string): Option<in_addr>
-
 /*
  * parse IPv4 address list of the form:
  * "10.0.0.4 10.0.0.10 10.0.0.20..10.0.0.50 10.0.0.100..10.0.0.110"
@@ -116,48 +95,79 @@  extern function parse_ip_list(ips: string): Either<string, Vec<(in_addr, Option<
 /*
  * IPv6 addresses
  */
-extern type in6_addr
-
-extern function in6_generate_lla(ea: eth_addr): in6_addr
-extern function in6_generate_eui64(ea: eth_addr, prefix: in6_addr): in6_addr
-extern function in6_is_lla(addr: in6_addr): bool
-extern function in6_addr_solicited_node(ip6: in6_addr): in6_addr
+typedef in6_addr = In6Addr {
+    aaaa: bit<128>              // In host byte order.
+}
 
-extern function ipv6_string_mapped(addr: in6_addr): string
-extern function ipv6_parse_masked(s: string): Either<string/*err*/, (in6_addr/*ip*/, in6_addr/*mask*/)>
 extern function ipv6_parse(s: string): Option<in6_addr>
+extern function ipv6_parse_masked(s: string): Either<string/*err*/, (in6_addr/*ip*/, in6_addr/*mask*/)>
 extern function ipv6_parse_cidr(s: string): Either<string/*err*/, (in6_addr/*ip*/, bit<32>/*plen*/)>
-extern function ipv6_bitxor(a: in6_addr, b: in6_addr): in6_addr
-extern function ipv6_bitand(a: in6_addr, b: in6_addr): in6_addr
-extern function ipv6_bitnot(a: in6_addr): in6_addr
-extern function ipv6_create_mask(mask: bit<32>): in6_addr
-extern function ipv6_is_zero(a: in6_addr): bool
-extern function ipv6_is_routable_multicast(a: in6_addr): bool
-extern function ipv6_is_all_hosts(a: in6_addr): bool
 
-function ipv6_network(addr: in6_addr, mask: in6_addr): in6_addr {
-    ipv6_bitand(addr, mask)
+// Return IPv6 link local address for the given 'ea'.
+function to_ipv6_lla(ea: eth_addr): in6_addr {
+    In6Addr{(128'hfe80 << 112) | (ea.to_eui64() as u128)}
+}
+
+// Returns IPv6 EUI64 address for 'ea' with the given 'prefix'.
+function to_ipv6_eui64(ea: eth_addr, prefix: in6_addr): in6_addr {
+    In6Addr{(prefix.aaaa & ~128'hffffffffffffffff) | (ea.to_eui64() as u128)}
+}
+
+function ipv6_create_mask(plen: bit<32>): in6_addr {
+    if (plen == 0) {
+        In6Addr{0}
+    } else {
+        var shift = max(0, 128 - plen);
+        In6Addr{128'hffffffffffffffffffffffffffffffff << shift}
+    }
 }
 
-function ipv6_host(addr: in6_addr, mask: in6_addr): in6_addr {
-    ipv6_bitand(addr, ipv6_bitnot(mask))
+function is_zero(a: in6_addr): bool { a.aaaa == 0 }
+function is_all_ones(a: in6_addr): bool { a.aaaa == 128'hffffffffffffffffffffffffffffffff }
+function is_lla(a: in6_addr): bool { (a.aaaa >> 64) == 128'hfe80000000000000 }
+function is_all_hosts(a: in6_addr): bool { a.aaaa == 128'hff020000000000000000000000000001 }
+function is_cidr(netmask: in6_addr): bool { var x = ~netmask.aaaa; (x & (x + 1)) == 0 }
+function is_multicast(a: in6_addr): bool { (a.aaaa >> 120) == 128'hff }
+function is_routable_multicast(a: in6_addr): bool {
+    a.is_multicast() and match ((a.aaaa >> 112) as u8 & 8'hf) {
+        0 -> false,
+        1 -> false,
+        2 -> false,
+        3 -> false,
+        15 -> false,
+        _ -> true
+    }
+}
+
+extern function string_mapped(addr: in6_addr): string
+
+function network(addr: in6_addr, mask: in6_addr): in6_addr { In6Addr{addr.aaaa & mask.aaaa} }
+function host(addr: in6_addr, mask: in6_addr): in6_addr { In6Addr{addr.aaaa & ~mask.aaaa} }
+function solicited_node(ip6: in6_addr): in6_addr {
+    In6Addr{(ip6.aaaa & 128'hffffff) | 128'hff02_0000_0000_0000_0000_0001_ff00_0000}
 }
 
 /* True if both 'ips' are in the same network as defined by netmask 'mask',
  * false otherwise. */
-function ipv6_same_network(ips: (in6_addr, in6_addr), mask: in6_addr): bool {
-    ipv6_network(ips.0, mask) == ipv6_network(ips.1, mask)
+function same_network(ips: (in6_addr, in6_addr), mask: in6_addr): bool {
+    ips.0.network(mask) == ips.1.network(mask)
 }
 
-extern function ipv6_multicast_to_ethernet(ip6: in6_addr): eth_addr
-extern function ipv6_is_cidr(ip6: in6_addr): bool
-extern function ipv6_count_cidr_bits(ip6: in6_addr): Option<bit<8>>
+function multicast_to_ethernet(ip6: in6_addr): eth_addr {
+    EthAddr{48'h333300000000 | (ip6.aaaa as bit<48> & 48'hffffffff)}
+}
 
-extern function inet6_ntop(addr: in6_addr): string
-function to_string(addr: in6_addr): string = {
-    inet6_ntop(addr)
+function cidr_bits(ip6: in6_addr): Option<bit<8>> {
+    if (ip6.is_cidr()) {
+        Some{128 - ip6.aaaa.trailing_zeros() as u8}
+    } else {
+        None
+    }
 }
 
+function to_string(addr: in6_addr): string { inet6_ntop(addr) }
+extern function inet6_ntop(addr: in6_addr): string
+
 /*
  * IPv4 | IPv6 addresses
  */
@@ -210,70 +220,62 @@  function to_bracketed_string(ip46: v46_ip) : string = {
     }
 }
 
-function ip46_get_network(ip46: v46_ip, plen: bit<32>) : v46_ip {
+function network(ip46: v46_ip, plen: bit<32>) : v46_ip {
     match (ip46) {
-        IPv4{ipv4} -> IPv4{ip_bitand(ipv4, ip_create_mask(plen))},
-        IPv6{ipv6} -> IPv6{ipv6_bitand(ipv6, ipv6_create_mask(plen))}
+        IPv4{ipv4} -> IPv4{InAddr{ipv4.a & ip_create_mask(plen).a}},
+        IPv6{ipv6} -> IPv6{In6Addr{ipv6.aaaa & ipv6_create_mask(plen).aaaa}}
     }
 }
 
-function ip46_is_all_ones(ip46: v46_ip) : bool {
+function is_all_ones(ip46: v46_ip) : bool {
     match (ip46) {
-        IPv4{ipv4} -> ipv4 == ip_create_mask(32),
-        IPv6{ipv6} -> ipv6 == ipv6_create_mask(128)
+        IPv4{ipv4} -> ipv4.is_all_ones(),
+        IPv6{ipv6} -> ipv6.is_all_ones()
     }
 }
 
-function ip46_count_cidr_bits(ip46: v46_ip) : Option<bit<8>> {
+function cidr_bits(ip46: v46_ip) : Option<bit<8>> {
     match (ip46) {
-        IPv4{ipv4} -> ip_count_cidr_bits(ipv4),
-        IPv6{ipv6} -> ipv6_count_cidr_bits(ipv6)
+        IPv4{ipv4} -> ipv4.cidr_bits(),
+        IPv6{ipv6} -> ipv6.cidr_bits()
     }
 }
 
-function ip46_ipX(ip46: v46_ip) : string {
+function ipX(ip46: v46_ip) : string {
     match (ip46) {
         IPv4{_} -> "ip4",
         IPv6{_} -> "ip6"
     }
 }
 
-function ip46_xxreg(ip46: v46_ip) : string {
+function xxreg(ip46: v46_ip) : string {
     match (ip46) {
         IPv4{_} -> "",
         IPv6{_} -> "xx"
     }
 }
 
+/*
+ * CIDR-masked IPv4 address
+ */
+
 typedef ipv4_netaddr = IPV4NetAddr {
     addr: in_addr,             /* 192.168.10.123 */
     plen: bit<32>              /* CIDR Prefix: 24. */
 }
 
-/* Returns the netmask. */
-function ipv4_netaddr_mask(na: ipv4_netaddr): in_addr {
-    ip_create_mask(na.plen)
-}
-
-/* Returns the broadcast address. */
-function ipv4_netaddr_bcast(na: ipv4_netaddr): in_addr {
-    ip_bcast(na.addr, ipv4_netaddr_mask(na))
-}
-
-/* Returns the network (with the host bits zeroed). */
-function ipv4_netaddr_network(na: ipv4_netaddr): in_addr {
-    ip_network(na.addr, ipv4_netaddr_mask(na))
-}
+function netmask(na: ipv4_netaddr): in_addr { ip_create_mask(na.plen) }
+function bcast(na: ipv4_netaddr): in_addr { na.addr.bcast(na.netmask()) }
 
-/* Returns the host (with the network bits zeroed). */
-function ipv4_netaddr_host(na: ipv4_netaddr): in_addr {
-    ip_host(na.addr, ipv4_netaddr_mask(na))
-}
+/* Returns the network (with the host bits zeroed)
+ * or the host (with the network bits zeroed). */
+function network(na: ipv4_netaddr): in_addr { na.addr.network(na.netmask()) }
+function host(na: ipv4_netaddr): in_addr { na.addr.host(na.netmask()) }
 
 /* Match on the host, if the host part is nonzero, or on the network
  * otherwise. */
-function ipv4_netaddr_match_host_or_network(na: ipv4_netaddr): string {
-    if (na.plen < 32 and ip_is_zero(ipv4_netaddr_host(na))) {
+function match_host_or_network(na: ipv4_netaddr): string {
+    if (na.plen < 32 and na.host().is_zero()) {
         "${na.addr}/${na.plen}"
     } else {
         "${na.addr}"
@@ -281,51 +283,48 @@  function ipv4_netaddr_match_host_or_network(na: ipv4_netaddr): string {
 }
 
 /* Match on the network. */
-function ipv4_netaddr_match_network(na: ipv4_netaddr): string {
+function match_network(na: ipv4_netaddr): string {
     if (na.plen < 32) {
-        "${ipv4_netaddr_network(na)}/${na.plen}"
+        "${na.network()}/${na.plen}"
     } else {
         "${na.addr}"
     }
 }
 
+/*
+ * CIDR-masked IPv6 address
+ */
+
 typedef ipv6_netaddr = IPV6NetAddr {
     addr: in6_addr,          /* fc00::1 */
     plen: bit<32>            /* CIDR Prefix: 64 */
 }
 
-/* Returns the netmask. */
-function ipv6_netaddr_mask(na: ipv6_netaddr): in6_addr {
-    ipv6_create_mask(na.plen)
-}
+function netmask(na: ipv6_netaddr): in6_addr { ipv6_create_mask(na.plen) }
 
-/* Returns the network (with the host bits zeroed). */
-function ipv6_netaddr_network(na: ipv6_netaddr): in6_addr {
-    ipv6_network(na.addr, ipv6_netaddr_mask(na))
-}
+/* Returns the network (with the host bits zeroed).
+ * or the host (with the network bits zeroed). */
+function network(na: ipv6_netaddr): in6_addr { na.addr.network(na.netmask()) }
+function host(na: ipv6_netaddr): in6_addr { na.addr.host(na.netmask()) }
 
-/* Returns the host (with the network bits zeroed). */
-function ipv6_netaddr_host(na: ipv6_netaddr): in6_addr {
-    ipv6_host(na.addr, ipv6_netaddr_mask(na))
-}
+function solicited_node(na: ipv6_netaddr): in6_addr { na.addr.solicited_node() }
 
-function ipv6_netaddr_solicited_node(na: ipv6_netaddr): in6_addr {
-    in6_addr_solicited_node(na.addr)
-}
-
-function ipv6_netaddr_is_lla(na: ipv6_netaddr): bool {
-    return in6_is_lla(ipv6_netaddr_network(na))
-}
+function is_lla(na: ipv6_netaddr): bool { na.network().is_lla() }
 
 /* Match on the network. */
-function ipv6_netaddr_match_network(na: ipv6_netaddr): string {
+function match_network(na: ipv6_netaddr): string {
     if (na.plen < 128) {
-        "${ipv6_netaddr_network(na)}/${na.plen}"
+        "${na.network()}/${na.plen}"
     } else {
         "${na.addr}"
     }
 }
 
+/*
+ * Set of addresses associated with a logical port.
+ *
+ * A logical port always has one Ethernet address, plus any number of IPv4 and IPv6 addresses.
+ */
 typedef lport_addresses = LPortAddress {
     ea: eth_addr,
     ipv4_addrs: Vec<ipv4_netaddr>,
diff --git a/northd/ovn.rs b/northd/ovn.rs
index c4842bdbc424..d44f83bc7557 100644
--- a/northd/ovn.rs
+++ b/northd/ovn.rs
@@ -59,144 +59,77 @@  const AF_INET6: usize = 10;
 
 #[repr(C)]
 #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Serialize, Deserialize, Debug, IntoRecord, Mutator)]
-pub struct eth_addr {
+pub struct eth_addr_c {
     x: [u8; ETH_ADDR_SIZE]
 }
 
-pub fn eth_addr_zero() -> eth_addr {
-    eth_addr { x: [0; ETH_ADDR_SIZE] }
+impl eth_addr_c {
+    pub fn from_ddlog(d: &eth_addr) -> Self {
+        eth_addr_c {
+            x: [(d.ha >> 40) as u8,
+                (d.ha >> 32) as u8,
+                (d.ha >> 24) as u8,
+                (d.ha >> 16) as u8,
+                (d.ha >> 8) as u8,
+                d.ha as u8]
+        }
+    }
+    pub fn to_ddlog(&self) -> eth_addr {
+        let ea0 = u16::from_be_bytes([self.x[0], self.x[1]]) as u64;
+        let ea1 = u16::from_be_bytes([self.x[2], self.x[3]]) as u64;
+        let ea2 = u16::from_be_bytes([self.x[4], self.x[5]]) as u64;
+        eth_addr { ha: (ea0 << 32) | (ea1 << 16) | ea2 }
+    }
 }
 
 pub fn eth_addr2string(addr: &eth_addr) -> String {
+    let c = eth_addr_c::from_ddlog(addr);
     format!("{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
-            addr.x[0], addr.x[1], addr.x[2], addr.x[3], addr.x[4], addr.x[5])
+            c.x[0], c.x[1], c.x[2], c.x[3], c.x[4], c.x[5])
 }
 
 pub fn eth_addr_from_string(s: &String) -> ddlog_std::Option<eth_addr> {
-    let mut ea: eth_addr = Default::default();
+    let mut ea: eth_addr_c = Default::default();
     unsafe {
-        if ovs::eth_addr_from_string(string2cstr(s).as_ptr(), &mut ea as *mut eth_addr) {
-            ddlog_std::Option::Some{x: ea}
+        if ovs::eth_addr_from_string(string2cstr(s).as_ptr(), &mut ea as *mut eth_addr_c) {
+            ddlog_std::Option::Some{x: ea.to_ddlog()}
         } else {
             ddlog_std::Option::None
         }
     }
 }
 
-pub fn eth_addr_from_uint64(x: &u64) -> eth_addr {
-    let mut ea: eth_addr = Default::default();
-    unsafe {
-        ovs::eth_addr_from_uint64(*x as libc::uint64_t, &mut ea as *mut eth_addr);
-        ea
-    }
-}
-
-pub fn eth_addr_mark_random(ea: &eth_addr) -> eth_addr {
-    unsafe {
-        let mut ea_new = ea.clone();
-        ovs::eth_addr_mark_random(&mut ea_new as *mut eth_addr);
-        ea_new
-    }
-}
-
-pub fn eth_addr_to_uint64(ea: &eth_addr) -> u64 {
-    unsafe {
-        ovs::eth_addr_to_uint64(ea.clone()) as u64
-    }
-}
-
-
-impl FromRecord for eth_addr {
-    fn from_record(val: &record::Record) -> Result<Self, String> {
-        Ok(eth_addr{x: <[u8; ETH_ADDR_SIZE]>::from_record(val)?})
-    }
-}
-
 #[repr(C)]
-#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Serialize, Deserialize, Debug, IntoRecord, Mutator)]
-pub struct in6_addr {
-    x: [u8; IN6_ADDR_SIZE]
-}
-
-pub const in6addr_any: in6_addr = in6_addr{x: [0; IN6_ADDR_SIZE]};
-pub const in6addr_all_hosts: in6_addr = in6_addr{x: [
-    0xff,0x02,0x00,0x00,0x00,0x00,0x00,0x00,
-    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 ]};
-
-impl FromRecord for in6_addr {
-    fn from_record(val: &record::Record) -> Result<Self, String> {
-        Ok(in6_addr{x: <[u8; IN6_ADDR_SIZE]>::from_record(val)?})
-    }
-}
-
-pub fn in6_generate_lla(ea: &eth_addr) -> in6_addr {
-    let mut addr: in6_addr = Default::default();
-    unsafe {ovs::in6_generate_lla(ea.clone(), &mut addr as *mut in6_addr)};
-    addr
-}
-
-pub fn in6_generate_eui64(ea: &eth_addr, prefix: &in6_addr) -> in6_addr {
-    let mut addr: in6_addr = Default::default();
-    unsafe {ovs::in6_generate_eui64(ea.clone(),
-                                    prefix as *const in6_addr,
-                                    &mut addr as *mut in6_addr)};
-    addr
+struct in6_addr_c {
+    bytes: [u8; 16]
 }
 
-pub fn in6_is_lla(addr: &in6_addr) -> bool {
-    unsafe {ovs::in6_is_lla(addr as *const in6_addr)}
-}
-
-pub fn in6_addr_solicited_node(ip6: &in6_addr) -> in6_addr
-{
-    let mut res: in6_addr = Default::default();
-    unsafe {
-        ovs::in6_addr_solicited_node(&mut res as *mut in6_addr, ip6 as *const in6_addr);
-    }
-    res
-}
-
-pub fn ipv6_bitand(a: &in6_addr, b: &in6_addr) -> in6_addr {
-    unsafe {
-        ovs::ipv6_addr_bitand(a as *const in6_addr, b as *const in6_addr)
+impl Default for in6_addr_c {
+    fn default() -> Self {
+        in6_addr_c {
+            bytes: [0; 16]
+        }
     }
 }
 
-pub fn ipv6_bitxor(a: &in6_addr, b: &in6_addr) -> in6_addr {
-    unsafe {
-        ovs::ipv6_addr_bitxor(a as *const in6_addr, b as *const in6_addr)
+impl in6_addr_c {
+    pub fn from_ddlog(d: &in6_addr) -> Self {
+        in6_addr_c{bytes: d.aaaa.to_be_bytes()}
     }
-}
-
-pub fn ipv6_bitnot(a: &in6_addr) -> in6_addr {
-    let mut result: in6_addr = Default::default();
-    for i in 0..16 {
-        result.x[i] = !a.x[i]
+    pub fn to_ddlog(&self) -> in6_addr {
+        in6_addr{aaaa: u128::from_be_bytes(self.bytes)}
     }
-    result
 }
 
-pub fn ipv6_string_mapped(addr: &in6_addr) -> String {
+pub fn string_mapped(addr: &in6_addr) -> String {
+    let addr = in6_addr_c::from_ddlog(addr);
     let mut addr_str = [0 as i8; INET6_ADDRSTRLEN];
     unsafe {
-        ovs::ipv6_string_mapped(&mut addr_str[0] as *mut raw::c_char, addr as *const in6_addr);
+        ovs::ipv6_string_mapped(&mut addr_str[0] as *mut raw::c_char, &addr as *const in6_addr_c);
         cstr2string(&addr_str as *const raw::c_char)
     }
 }
 
-pub fn ipv6_is_zero(addr: &in6_addr) -> bool {
-    *addr == in6addr_any
-}
-
-pub fn ipv6_count_cidr_bits(ip6: &in6_addr) -> ddlog_std::Option<u8> {
-    unsafe {
-        match (ipv6_is_cidr(ip6)) {
-            true => ddlog_std::Option::Some{x: ovs::ipv6_count_cidr_bits(ip6 as *const in6_addr) as u8},
-            false => ddlog_std::Option::None
-        }
-    }
-}
-
 pub fn json_string_escape(s: &String) -> String {
     let mut ds = ovs_ds::new();
     unsafe {
@@ -263,15 +196,15 @@  pub fn ovn_internal_version() -> String {
 pub fn ipv6_parse_masked(s: &String) -> ddlog_std::Either<String, ddlog_std::tuple2<in6_addr, in6_addr>>
 {
     unsafe {
-        let mut ip: in6_addr = Default::default();
-        let mut mask: in6_addr = Default::default();
-        let err = ovs::ipv6_parse_masked(string2cstr(s).as_ptr(), &mut ip as *mut in6_addr, &mut mask as *mut in6_addr);
+        let mut ip: in6_addr_c = Default::default();
+        let mut mask: in6_addr_c = Default::default();
+        let err = ovs::ipv6_parse_masked(string2cstr(s).as_ptr(), &mut ip as *mut in6_addr_c, &mut mask as *mut in6_addr_c);
         if (err != ptr::null_mut()) {
             let errstr = cstr2string(err);
             free(err as *mut raw::c_void);
             ddlog_std::Either::Left{l: errstr}
         } else {
-            ddlog_std::Either::Right{r: ddlog_std::tuple2(ip, mask)}
+            ddlog_std::Either::Right{r: ddlog_std::tuple2(ip.to_ddlog(), mask.to_ddlog())}
         }
     }
 }
@@ -279,15 +212,15 @@  pub fn ipv6_parse_masked(s: &String) -> ddlog_std::Either<String, ddlog_std::tup
 pub fn ipv6_parse_cidr(s: &String) -> ddlog_std::Either<String, ddlog_std::tuple2<in6_addr, u32>>
 {
     unsafe {
-        let mut ip: in6_addr = Default::default();
+        let mut ip: in6_addr_c = Default::default();
         let mut plen: raw::c_uint = 0;
-        let err = ovs::ipv6_parse_cidr(string2cstr(s).as_ptr(), &mut ip as *mut in6_addr, &mut plen as *mut raw::c_uint);
+        let err = ovs::ipv6_parse_cidr(string2cstr(s).as_ptr(), &mut ip as *mut in6_addr_c, &mut plen as *mut raw::c_uint);
         if (err != ptr::null_mut()) {
             let errstr = cstr2string(err);
             free(err as *mut raw::c_void);
             ddlog_std::Either::Left{l: errstr}
         } else {
-            ddlog_std::Either::Right{r: ddlog_std::tuple2(ip, plen as u32)}
+            ddlog_std::Either::Right{r: ddlog_std::tuple2(ip.to_ddlog(), plen as u32)}
         }
     }
 }
@@ -295,54 +228,25 @@  pub fn ipv6_parse_cidr(s: &String) -> ddlog_std::Either<String, ddlog_std::tuple
 pub fn ipv6_parse(s: &String) -> ddlog_std::Option<in6_addr>
 {
     unsafe {
-        let mut ip: in6_addr = Default::default();
-        let res = ovs::ipv6_parse(string2cstr(s).as_ptr(), &mut ip as *mut in6_addr);
+        let mut ip: in6_addr_c = Default::default();
+        let res = ovs::ipv6_parse(string2cstr(s).as_ptr(), &mut ip as *mut in6_addr_c);
         if (res) {
-            ddlog_std::Option::Some{x: ip}
+            ddlog_std::Option::Some{x: ip.to_ddlog()}
         } else {
             ddlog_std::Option::None
         }
     }
 }
 
-pub fn ipv6_create_mask(mask: &u32) -> in6_addr
-{
-    unsafe {ovs::ipv6_create_mask(*mask as raw::c_uint)}
-}
-
-
-pub fn ipv6_is_routable_multicast(a: &in6_addr) -> bool
-{
-    unsafe{ovn_c::ipv6_addr_is_routable_multicast(a as *const in6_addr)}
-}
-
-pub fn ipv6_is_all_hosts(a: &in6_addr) -> bool
-{
-    return *a == in6addr_all_hosts;
-}
-
-pub fn ipv6_is_cidr(a: &in6_addr) -> bool
-{
-    unsafe{ovs::ipv6_is_cidr(a as *const in6_addr)}
-}
-
-pub fn ipv6_multicast_to_ethernet(ip6: &in6_addr) -> eth_addr
-{
-    let mut eth: eth_addr = Default::default();
-    unsafe{
-        ovs::ipv6_multicast_to_ethernet(&mut eth as *mut eth_addr, ip6 as *const in6_addr);
-    }
-    eth
-}
-
-pub type in_addr = u32;
 pub type ovs_be32 = u32;
 
-pub fn iptohl(addr: &in_addr) -> u32 {
-    ddlog_std::ntohl(addr)
-}
-pub fn hltoip(addr: &u32) -> in_addr {
-    ddlog_std::htonl(addr)
+impl in_addr {
+    pub fn from_be32(nl: ovs_be32) -> in_addr {
+        in_addr{a: ddlog_std::ntohl(&nl)}
+    }
+    pub fn to_be32(&self) -> ovs_be32 {
+        ddlog_std::htonl(&self.a)
+    }
 }
 
 pub fn ip_parse_masked(s: &String) -> ddlog_std::Either<String, ddlog_std::tuple2<in_addr, in_addr>>
@@ -356,7 +260,8 @@  pub fn ip_parse_masked(s: &String) -> ddlog_std::Either<String, ddlog_std::tuple
             free(err as *mut raw::c_void);
             ddlog_std::Either::Left{l: errstr}
         } else {
-            ddlog_std::Either::Right{r: ddlog_std::tuple2(ip, mask)}
+            ddlog_std::Either::Right{r: ddlog_std::tuple2(in_addr::from_be32(ip),
+                                                          in_addr::from_be32(mask))}
         }
     }
 }
@@ -372,7 +277,7 @@  pub fn ip_parse_cidr(s: &String) -> ddlog_std::Either<String, ddlog_std::tuple2<
             free(err as *mut raw::c_void);
             ddlog_std::Either::Left{l: errstr}
         } else {
-            ddlog_std::Either::Right{r: ddlog_std::tuple2(ip, plen as u32)}
+            ddlog_std::Either::Right{r: ddlog_std::tuple2(in_addr::from_be32(ip), plen as u32)}
         }
     }
 }
@@ -382,22 +287,13 @@  pub fn ip_parse(s: &String) -> ddlog_std::Option<in_addr>
     unsafe {
         let mut ip: ovs_be32 = 0;
         if (ovs::ip_parse(string2cstr(s).as_ptr(), &mut ip as *mut ovs_be32)) {
-            ddlog_std::Option::Some{x:ip}
+            ddlog_std::Option::Some{x: in_addr::from_be32(ip)}
         } else {
             ddlog_std::Option::None
         }
     }
 }
 
-pub fn ip_count_cidr_bits(address: &in_addr) -> ddlog_std::Option<u8> {
-    unsafe {
-        match (ip_is_cidr(address)) {
-            true => ddlog_std::Option::Some{x: ovs::ip_count_cidr_bits(*address) as u8},
-            false => ddlog_std::Option::None
-        }
-    }
-}
-
 pub fn is_dynamic_lsp_address(address: &String) -> bool {
     unsafe {
         ovn_c::is_dynamic_lsp_address(string2cstr(address).as_ptr())
@@ -414,21 +310,21 @@  pub fn split_addresses(addresses: &String) -> ddlog_std::tuple2<ddlog_std::Set<S
 }
 
 pub fn scan_eth_addr(s: &String) -> ddlog_std::Option<eth_addr> {
-    let mut ea = eth_addr_zero();
+    let mut ea: eth_addr_c = Default::default();
     unsafe {
         if ovs::ovs_scan(string2cstr(s).as_ptr(), b"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\0".as_ptr() as *const raw::c_char,
                          &mut ea.x[0] as *mut u8, &mut ea.x[1] as *mut u8,
                          &mut ea.x[2] as *mut u8, &mut ea.x[3] as *mut u8,
                          &mut ea.x[4] as *mut u8, &mut ea.x[5] as *mut u8)
         {
-            ddlog_std::Option::Some{x: ea}
+            ddlog_std::Option::Some{x: ea.to_ddlog()}
         } else {
             ddlog_std::Option::None
         }
     }
 }
 
-pub fn scan_eth_addr_prefix(s: &String) -> ddlog_std::Option<u64> {
+pub fn scan_eth_addr_prefix(s: &String) -> ddlog_std::Option<eth_addr> {
     let mut b2: u8 = 0;
     let mut b1: u8 = 0;
     let mut b0: u8 = 0;
@@ -436,7 +332,7 @@  pub fn scan_eth_addr_prefix(s: &String) -> ddlog_std::Option<u64> {
         if ovs::ovs_scan(string2cstr(s).as_ptr(), b"%hhx:%hhx:%hhx\0".as_ptr() as *const raw::c_char,
                          &mut b2 as *mut u8, &mut b1 as *mut u8, &mut b0 as *mut u8)
         {
-            ddlog_std::Option::Some{x: ((b2 as u64) << 40) | ((b1 as u64) << 32) | ((b0 as u64) << 24) }
+            ddlog_std::Option::Some{x: eth_addr{ha: ((b2 as u64) << 40) | ((b1 as u64) << 32) | ((b0 as u64) << 24)} }
         } else {
             ddlog_std::Option::None
         }
@@ -457,7 +353,11 @@  pub fn scan_static_dynamic_ip(s: &String) -> ddlog_std::Option<in_addr> {
                          &mut ip3 as *mut u8,
                          &mut n) && s.len() == (n as usize)
         {
-            ddlog_std::Option::Some{x: ddlog_std::htonl(&(((ip0 as u32) << 24)  | ((ip1 as u32) << 16) | ((ip2 as u32) << 8) | (ip3 as u32)))}
+            let a0 = (ip0 as u32) << 24;
+            let a1 = (ip1 as u32) << 16;
+            let a2 = (ip2 as u32) << 8;
+            let a3 = ip3 as u32;
+            ddlog_std::Option::Some{x: in_addr{a: a0 | a1 | a2 | a3}}
         } else {
             ddlog_std::Option::None
         }
@@ -513,9 +413,10 @@  pub fn str_to_uint(s: &String, base: &u16) -> ddlog_std::Option<u64> {
 }
 
 pub fn inet6_ntop(addr: &in6_addr) -> String {
+    let addr_c = in6_addr_c::from_ddlog(addr);
     let mut buf = [0 as i8; INET6_ADDRSTRLEN];
     unsafe {
-        let res = inet_ntop(AF_INET6 as raw::c_int, addr as *const in6_addr as *const raw::c_void,
+        let res = inet_ntop(AF_INET6 as raw::c_int, &addr_c as *const in6_addr_c as *const raw::c_void,
                             &mut buf[0] as *mut raw::c_char, INET6_ADDRSTRLEN as libc::socklen_t);
         if res == ptr::null() {
             warn(&format!("inet_ntop({:?}) failed", *addr));
@@ -611,9 +512,9 @@  impl Default for ipv4_netaddr_c {
 }
 
 impl ipv4_netaddr_c {
-    pub unsafe fn to_ddlog(&self) -> ipv4_netaddr {
+    pub fn to_ddlog(&self) -> ipv4_netaddr {
         ipv4_netaddr{
-            addr:       self.addr,
+            addr:       in_addr::from_be32(self.addr),
             plen:       self.plen,
         }
     }
@@ -621,10 +522,10 @@  impl ipv4_netaddr_c {
 
 #[repr(C)]
 struct ipv6_netaddr_c {
-    addr:       in6_addr,     /* fc00::1 */
-    mask:       in6_addr,     /* ffff:ffff:ffff:ffff:: */
-    sn_addr:    in6_addr,     /* ff02:1:ff00::1 */
-    network:    in6_addr,     /* fc00:: */
+    addr:       in6_addr_c,     /* fc00::1 */
+    mask:       in6_addr_c,     /* ffff:ffff:ffff:ffff:: */
+    sn_addr:    in6_addr_c,     /* ff02:1:ff00::1 */
+    network:    in6_addr_c,     /* fc00:: */
     plen:       raw::c_uint,      /* CIDR Prefix: 64 */
 
     addr_s:     [raw::c_char; INET6_ADDRSTRLEN + 1],    /* "fc00::1" */
@@ -650,7 +551,7 @@  impl Default for ipv6_netaddr_c {
 impl ipv6_netaddr_c {
     pub unsafe fn to_ddlog(&self) -> ipv6_netaddr {
         ipv6_netaddr{
-            addr:       self.addr.clone(),
+            addr:       in6_addr_c::to_ddlog(&self.addr),
             plen:       self.plen
         }
     }
@@ -661,7 +562,7 @@  impl ipv6_netaddr_c {
 #[repr(C)]
 struct lport_addresses_c {
     ea_s:           [raw::c_char; ETH_ADDR_STRLEN + 1],
-    ea:             eth_addr,
+    ea:             eth_addr_c,
     n_ipv4_addrs:   libc::size_t,
     ipv4_addrs:     *mut ipv4_netaddr_c,
     n_ipv6_addrs:   libc::size_t,
@@ -692,7 +593,7 @@  impl lport_addresses_c {
             ipv6_addrs.push((&*self.ipv6_addrs.offset(i as isize)).to_ddlog())
         }
         let res = lport_addresses {
-            ea:         self.ea.clone(),
+            ea:         self.ea.to_ddlog(),
             ipv4_addrs: ipv4_addrs,
             ipv6_addrs: ipv6_addrs
         };
@@ -713,7 +614,7 @@  mod ovn_c {
     use ::libc;
     use super::lport_addresses_c;
     use super::ovs_svec;
-    use super::in6_addr;
+    use super::in6_addr_c;
 
     #[link(name = "ovn")]
     extern "C" {
@@ -727,7 +628,6 @@  mod ovn_c {
         pub fn split_addresses(addresses: *const raw::c_char, ip4_addrs: *mut ovs_svec, ipv6_addrs: *mut ovs_svec);
         pub fn ip_address_and_port_from_lb_key(key: *const raw::c_char, ip_address: *mut *mut raw::c_char,
                                                port: *mut libc::uint16_t, addr_family: *mut raw::c_int);
-        pub fn ipv6_addr_is_routable_multicast(ip: *const in6_addr) -> bool;
         pub fn ovn_get_internal_version() -> *mut raw::c_char;
     }
 }
@@ -735,40 +635,24 @@  mod ovn_c {
 mod ovs {
     use ::std::os::raw;
     use ::libc;
-    use super::in6_addr;
+    use super::in6_addr_c;
     use super::ovs_be32;
     use super::ovs_ds;
-    use super::eth_addr;
+    use super::eth_addr_c;
     use super::ovs_svec;
 
     /* functions imported from libopenvswitch */
     #[link(name = "openvswitch")]
     extern "C" {
         // lib/packets.h
-        pub fn ipv6_string_mapped(addr_str: *mut raw::c_char, addr: *const in6_addr) -> *const raw::c_char;
-        pub fn ipv6_parse_masked(s: *const raw::c_char, ip: *mut in6_addr, mask: *mut in6_addr) -> *mut raw::c_char;
-        pub fn ipv6_parse_cidr(s: *const raw::c_char, ip: *mut in6_addr, plen: *mut raw::c_uint) -> *mut raw::c_char;
-        pub fn ipv6_parse(s: *const raw::c_char, ip: *mut in6_addr) -> bool;
-        pub fn ipv6_mask_is_any(mask: *const in6_addr) -> bool;
-        pub fn ipv6_count_cidr_bits(mask: *const in6_addr) -> raw::c_int;
-        pub fn ipv6_is_cidr(mask: *const in6_addr) -> bool;
-        pub fn ipv6_addr_bitxor(a: *const in6_addr, b: *const in6_addr) -> in6_addr;
-        pub fn ipv6_addr_bitand(a: *const in6_addr, b: *const in6_addr) -> in6_addr;
-        pub fn ipv6_create_mask(mask: raw::c_uint) -> in6_addr;
-        pub fn ipv6_is_zero(a: *const in6_addr) -> bool;
-        pub fn ipv6_multicast_to_ethernet(eth: *mut eth_addr, ip6: *const in6_addr);
+        pub fn ipv6_string_mapped(addr_str: *mut raw::c_char, addr: *const in6_addr_c) -> *const raw::c_char;
+        pub fn ipv6_parse_masked(s: *const raw::c_char, ip: *mut in6_addr_c, mask: *mut in6_addr_c) -> *mut raw::c_char;
+        pub fn ipv6_parse_cidr(s: *const raw::c_char, ip: *mut in6_addr_c, plen: *mut raw::c_uint) -> *mut raw::c_char;
+        pub fn ipv6_parse(s: *const raw::c_char, ip: *mut in6_addr_c) -> bool;
         pub fn ip_parse_masked(s: *const raw::c_char, ip: *mut ovs_be32, mask: *mut ovs_be32) -> *mut raw::c_char;
         pub fn ip_parse_cidr(s: *const raw::c_char, ip: *mut ovs_be32, plen: *mut raw::c_uint) -> *mut raw::c_char;
         pub fn ip_parse(s: *const raw::c_char, ip: *mut ovs_be32) -> bool;
-        pub fn ip_count_cidr_bits(mask: ovs_be32) -> raw::c_int;
-        pub fn eth_addr_from_string(s: *const raw::c_char, ea: *mut eth_addr) -> bool;
-        pub fn eth_addr_to_uint64(ea: eth_addr) -> libc::uint64_t;
-        pub fn eth_addr_from_uint64(x: libc::uint64_t, ea: *mut eth_addr);
-        pub fn eth_addr_mark_random(ea: *mut eth_addr);
-        pub fn in6_generate_eui64(ea: eth_addr, prefix: *const in6_addr, lla: *mut in6_addr);
-        pub fn in6_generate_lla(ea: eth_addr, lla: *mut in6_addr);
-        pub fn in6_is_lla(addr: *const in6_addr) -> bool;
-        pub fn in6_addr_solicited_node(addr: *mut in6_addr, ip6: *const in6_addr);
+        pub fn eth_addr_from_string(s: *const raw::c_char, ea: *mut eth_addr_c) -> bool;
 
         // include/openvswitch/json.h
         pub fn json_string_escape(str: *const raw::c_char, out: *mut ovs_ds);
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 4b4775a9d3bf..13bbe17c81da 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -1731,14 +1731,14 @@  function build_port_security_ipv6_flow(
     var ip6_addrs = vec_empty();
 
     /* Allow link-local address. */
-    ip6_addrs.push(ipv6_string_mapped(in6_generate_lla(ea)));
+    ip6_addrs.push(ea.to_ipv6_lla().string_mapped());
 
     /* Allow ip6.dst=ff00::/8 for multicast packets */
     if (pipeline == Egress) {
         ip6_addrs.push("ff00::/8")
     };
     for (addr in ipv6_addrs) {
-        ip6_addrs.push(ipv6_netaddr_match_network(addr))
+        ip6_addrs.push(addr.match_network())
     };
 
     var dir = if (pipeline == Ingress) { "src" } else { "dst" };
@@ -1755,12 +1755,10 @@  function build_port_security_ipv6_nd_flow(
     if (ipv6_addrs.is_empty()) {
         __match ++ "))"
     } else {
-        var ip6_str = ipv6_string_mapped(in6_generate_lla(ea));
-        __match = __match ++ " && (nd.target == ${ip6_str}";
+        __match = __match ++ " && (nd.target == ${ea.to_ipv6_lla()}";
 
         for(addr in ipv6_addrs) {
-            ip6_str = ipv6_netaddr_match_network(addr);
-            __match = __match ++ " || nd.target == ${ip6_str}"
+            __match = __match ++ " || nd.target == ${addr.match_network()}"
         };
         __match ++ ")))"
     }
@@ -1984,7 +1982,7 @@  function build_empty_lb_event_flow(key: string, lb: Ref<nb::Load_Balancer>,
     };
 
     var __match = vec_with_capacity(2);
-    __match.push("${ip46_ipX(ip)}.dst == ${ip}");
+    __match.push("${ip.ipX()}.dst == ${ip}");
     if (port != 0) {
         __match.push("${protocol}.dst == ${port}");
     };
@@ -2604,7 +2602,7 @@  for (SwitchPortDHCPv6Options(.port = &SwitchPort{.lsp = lsp, .sw = &sw},
      if lsp.__type != "external") {
     Some{var server_mac} = options.get("server_id") in
     Some{var ea} = eth_addr_from_string(server_mac) in
-    var server_ip = ipv6_string_mapped(in6_generate_lla(ea)) in
+    var server_ip = ea.to_ipv6_lla() in
     /* Get the link local IP of the DHCPv6 server from the
      * server MAC. */
     var has_stateful = sw.has_stateful_acl or sw.has_lb_vip in
@@ -3070,7 +3068,7 @@  for (SwitchPortPSAddresses(.port = &port@SwitchPort{.sw = &sw}, .ps_addrs = ps)
                  * address.  If zero, the host is allowed to use any
                  * address in the subnet.
                  */
-                addrs.push(ipv4_netaddr_match_host_or_network(addr))
+                addrs.push(addr.match_host_or_network())
             };
             addrs
         } in
@@ -3154,7 +3152,7 @@  for (SwitchPortPSAddresses(.port = &port@SwitchPort{.sw = &sw}, .ps_addrs = ps)
                 if (not ps.ipv4_addrs.is_empty()) {
                     var spas = vec_empty();
                     for (addr in ps.ipv4_addrs) {
-                        spas.push(ipv4_netaddr_match_host_or_network(addr))
+                        spas.push(addr.match_host_or_network())
                     };
                     prefix ++ " && arp.spa == {${spas.join(\", \")}}"
                 } else {
@@ -3316,7 +3314,7 @@  for (SwitchPortIPv6Address(.port = &SwitchPort{.lsp = lsp, .json_name = json_nam
         (lsp_is_up(lsp) or lsp.__type == "router" or lsp.__type == "localport") and
         lsp.__type != "external" and lsp.__type != "virtual")
 {
-    var __match = "nd_ns && ip6.dst == {${addr.addr}, ${ipv6_netaddr_solicited_node(addr)}} && nd.target == ${addr.addr}" in
+    var __match = "nd_ns && ip6.dst == {${addr.addr}, ${addr.solicited_node()}} && nd.target == ${addr.addr}" in
     var actions = "${if (lsp.__type == \"router\") \"nd_na_router\" else \"nd_na\"} { "
                   "eth.src = ${ea}; "
                   "ip6.src = ${addr.addr}; "
@@ -3392,7 +3390,7 @@  function build_dhcpv4_action(
             None
         },
         Right{(var host_ip, var mask)} -> {
-            if (not ip_same_network((offer_ip, host_ip), mask)) {
+            if (not (offer_ip, host_ip).same_network(mask)) {
                /* the offer ip of the logical port doesn't belong to the cidr
                 * defined in the DHCPv4 options.
                 */
@@ -3451,7 +3449,7 @@  function build_dhcpv6_action(
             None
         },
         Right{(var host_ip, var mask)} -> {
-            if (not ipv6_same_network((offer_ip, host_ip), mask)) {
+            if (not (offer_ip, host_ip).same_network(mask)) {
                 /* offer_ip doesn't belongs to the cidr defined in lport's DHCPv6
                  * options.*/
                 //warn("ip does not belong to cidr");
@@ -3471,8 +3469,8 @@  function build_dhcpv6_action(
                             },
                             Some{ea} -> {
                                 /* Get the link local IP of the DHCPv6 server from the server MAC. */
-                                var server_ip = ipv6_string_mapped(in6_generate_lla(ea));
-                                var ia_addr = ipv6_string_mapped(offer_ip);
+                                var server_ip = ea.to_ipv6_lla().string_mapped();
+                                var ia_addr = offer_ip.string_mapped();
                                 var options = vec_empty();
 
                                 /* Check whether the dhcpv6 options should be configured as stateful.
@@ -3862,10 +3860,10 @@  for (IgmpSwitchMulticastGroup(.address = address, .switch = &sw)) {
      */
     Some{var ip} = ip46_parse(address) in
     (var skip_address) = match (ip) {
-        IPv4{ipv4} -> ip_is_local_multicast(ipv4),
-        IPv6{ipv6} -> ipv6_is_all_hosts(ipv6)
+        IPv4{ipv4} -> ipv4.is_local_multicast(),
+        IPv6{ipv6} -> ipv6.is_all_hosts()
     } in
-    var ipX = ip46_ipX(ip) in
+    var ipX = ip.ipX() in
     for (SwitchMcastFloodRelayPorts(&sw, relay_ports) if not skip_address) {
         for (SwitchMcastFloodPorts(&sw, flood_ports)) {
             var flood_relay = not relay_ports.is_empty() in
@@ -3933,7 +3931,7 @@  Flow(.logical_datapath = sp.sw.ls._uuid,
      .__match          = ("inport == ${json_string_escape(localnet_port.1)} && "
                           "eth.src == ${lp_addr.ea} && "
                           "!is_chassis_resident(${sp.json_name}) && "
-                          "nd_ns && ip6.dst == {${rp_addr.addr}, ${ipv6_netaddr_solicited_node(rp_addr)}} && "
+                          "nd_ns && ip6.dst == {${rp_addr.addr}, ${rp_addr.solicited_node()}} && "
                           "nd.target == ${rp_addr.addr}"),
      .actions          = "drop;",
      .external_ids     = stage_hint(sp.lsp._uuid)) :-
@@ -4004,14 +4002,14 @@  function lrouter_port_ip_reachable(rp: Ref<RouterPort>, addr: v46_ip): bool {
     match (addr) {
         IPv4{ipv4} -> {
             for (na in rp.networks.ipv4_addrs) {
-                if (ip_same_network((ipv4, na.addr), ipv4_netaddr_mask(na))) {
+                if ((ipv4, na.addr).same_network(na.netmask())) {
                     return true
                 }
             }
         },
         IPv6{ipv6} -> {
             for (na in rp.networks.ipv6_addrs) {
-                if (ipv6_same_network((ipv6, na.addr), ipv6_netaddr_mask(na))) {
+                if ((ipv6, na.addr).same_network(na.netmask())) {
                     return true
                 }
             }
@@ -4356,9 +4354,9 @@  for (SwitchPortPSAddresses(.port = &SwitchPort{.lsp = lsp, .json_name = json_nam
                  * address.  If zero, the host is allowed to use any
                  * address in the subnet.
                  */
-                addrs.push(ipv4_netaddr_match_host_or_network(addr));
-                if (addr.plen < 32 and not ip_is_zero(ipv4_netaddr_host(addr))) {
-                    addrs.push("${ipv4_netaddr_bcast(addr)}")
+                addrs.push(addr.match_host_or_network());
+                if (addr.plen < 32 and not addr.host().is_zero()) {
+                    addrs.push("${addr.bcast()}")
                 }
             };
             addrs
@@ -4578,7 +4576,7 @@  for (RouterPortNetworksIPv4Addr(rp@&RouterPort{.router = router}, addr)) {
     var rLNR = rEGBIT_LOOKUP_NEIGHBOR_RESULT() in
     var rLNIR = rEGBIT_LOOKUP_NEIGHBOR_IP_RESULT() in
     var match0 = "inport == ${rp.json_name} && "
-                 "arp.spa == ${ipv4_netaddr_match_network(addr)}" in
+                 "arp.spa == ${addr.match_network()}" in
     var match1 = "arp.op == 1" ++ chassis_residence in
     var learn_from_arp_request = router.learn_from_arp_request in {
        if (not learn_from_arp_request) {
@@ -4701,7 +4699,7 @@  function format_v4_networks(networks: lport_addresses, add_bcast: bool): string
     for (addr in networks.ipv4_addrs) {
         addrs.push("${addr.addr}");
         if (add_bcast) {
-            addrs.push("${ipv4_netaddr_bcast(addr)}")
+            addrs.push("${addr.bcast()}")
         } else ()
     };
     if (addrs.len() == 1) {
@@ -4971,7 +4969,7 @@  Flow(.logical_datapath = lr.lr._uuid,
             None -> ()
         };
         if (sn_ip) {
-            clauses.push("ip6.dst == {${ip}, ${in6_addr_solicited_node(ip)}}")
+            clauses.push("ip6.dst == {${ip}, ${ip.solicited_node()}}")
         };
         clauses.push("nd_ns && nd.target == ${ip}");
         clauses.append(extra_match.to_vec());
@@ -5018,7 +5016,7 @@  for (RouterPortNetworksIPv4Addr(.port = &RouterPort{.lrp = lrp,
      * IP address. */
     for (AddChassisResidentCheck(lrp._uuid, add_chassis_resident_check)) {
         var __match =
-            "arp.spa == ${ipv4_netaddr_match_network(addr)}" ++
+            "arp.spa == ${addr.match_network()}" ++
             if (add_chassis_resident_check) {
                 " && is_chassis_resident(${router.redirect_port_name})"
             } else "" in
@@ -5295,10 +5293,10 @@  for (RouterPortNetworksIPv6Addr(.port = &RouterPort{.router = &router,
                                                     .json_name = json_name},
                                 .addr = addr)
      /* skip link-local address */
-     if (not ipv6_netaddr_is_lla(addr)))
+     if (not addr.is_lla()))
 {
     var __match = "inport == ${json_name} && ip6 && "
-                  "ip6.src == ${ipv6_netaddr_match_network(addr)} && "
+                  "ip6.src == ${addr.match_network()} && "
                   "ip.ttl == {0, 1} && !ip.later_frag" in
     var actions = "icmp6 {"
                   "eth.dst <-> eth.src; "
@@ -5396,7 +5394,7 @@  function lrouter_nat_add_ext_ip_match(
                 false -> {
                     /* S_ROUTER_OUT_SNAT uses priority (mask + 1 + 128 + 1) */
                     var is_gw_router = router.l3dgw_port == None;
-                    var mask_1bits = ip46_count_cidr_bits(mask).unwrap_or(8'd0) as integer;
+                    var mask_1bits = mask.cidr_bits().unwrap_or(8'd0) as integer;
                     mask_1bits + 2 + { if (not is_gw_router) 128 else 0 }
                 }
             };
@@ -5435,7 +5433,7 @@  Flow(.logical_datapath = logical_router,
                                 .ips = ips,
                                 .context = context),
     var ip = FlatMap(ips),
-    var ipX = ip46_ipX(ip).
+    var ipX = ip.ipX().
 
 /* Higher priority rules to force SNAT with the router port ip.
  * This only takes effect when the packet has already been
@@ -5488,12 +5486,12 @@  for (r in &Router(.lr = lr,
      if l3dgw_port.is_some() or is_gateway)
 {
     for (LogicalRouterNAT(.lr = lr._uuid, .nat = nat)) {
-        var ipX = ip46_ipX(nat.external_ip) in
-        var xx = ip46_xxreg(nat.external_ip) in
+        var ipX = nat.external_ip.ipX() in
+        var xx = nat.external_ip.xxreg() in
         /* Check the validity of nat->logical_ip. 'logical_ip' can
          * be a subnet when the type is "snat". */
         Some{(_, var mask)} = ip46_parse_masked(nat.nat.logical_ip) in
-        true == match ((ip46_is_all_ones(mask), nat.nat.__type)) {
+        true == match ((mask.is_all_ones(), nat.nat.__type)) {
             (_, "snat") -> true,
             (false, _) -> {
                 warn("bad ip ${nat.nat.logical_ip} for dnat in router ${uuid2str(lr._uuid)}");
@@ -5725,7 +5723,7 @@  for (r in &Router(.lr = lr,
                     } else {
                         "ct_snat(${ip_and_ports});"
                     } in
-                    Some{var plen} = ip46_count_cidr_bits(mask) in
+                    Some{var plen} = mask.cidr_bits() in
                     Flow(.logical_datapath = lr._uuid,
                          .stage            = s_ROUTER_OUT_SNAT(),
                          .priority         = plen as bit<64> + 1,
@@ -5761,7 +5759,7 @@  for (r in &Router(.lr = lr,
                     /* The priority here is calculated such that the
                      * nat->logical_ip with the longest mask gets a higher
                      * priority. */
-                    Some{var plen} = ip46_count_cidr_bits(mask) in
+                    Some{var plen} = mask.cidr_bits() in
                     var priority = (plen as bit<64>) + 1 in
                     var centralized_boost = if (mac == None) 128 else 0 in
                     Flow(.logical_datapath = lr._uuid,
@@ -5934,7 +5932,7 @@  for (RouterLBVIP(
 
     /* vip contains IP:port or just IP. */
     Some{(var ip_address, var port)} = ip_address_and_port_from_lb_key(vip) in
-    var ipX = ip46_ipX(ip_address) in
+    var ipX = ip_address.ipX() in
     var proto = match (lb.protocol) {
         Some{proto} -> proto,
         _ -> "tcp"
@@ -6038,7 +6036,7 @@  for (RouterLBVIP(
             } in
             not conds.is_empty() in
             var undnat_match =
-                "${ip46_ipX(ip_address)} && (" ++ conds.join(" || ") ++
+                "${ip_address.ipX()} && (" ++ conds.join(" || ") ++
                 ") && outport == ${json_string_escape(gwport.name)} && "
                 "is_chassis_resident(${redirect_port_name})" in
             var action =
@@ -6123,11 +6121,11 @@  function copy_ra_to_sb(port: RouterPort, address_mode: string): Map<string, stri
     };
 
     var prefixes = vec_empty();
-    for (addrs in port.networks.ipv6_addrs) {
-        if (ipv6_netaddr_is_lla(addrs)) {
-            options.insert("ipv6_ra_src_addr", "${addrs.addr}")
+    for (addr in port.networks.ipv6_addrs) {
+        if (addr.is_lla()) {
+            options.insert("ipv6_ra_src_addr", "${addr.addr}")
         } else {
-            prefixes.push(ipv6_netaddr_match_network(addrs))
+            prefixes.push(addr.match_network())
         }
     };
     match (port.sb_options.get("ipv6_ra_pd_list")) {
@@ -6191,8 +6189,8 @@  for (&RouterPort[port@RouterPort{.lrp = lrp@nb::Logical_Router_Port{.peer = None
                 var add_rs_response_flow = false;
                 var prefix = "";
                 for (addr in networks.ipv6_addrs) {
-                    if (not ipv6_netaddr_is_lla(addr)) {
-                        prefix = prefix ++ ", prefix = ${ipv6_netaddr_match_network(addr)}";
+                    if (not addr.is_lla()) {
+                        prefix = prefix ++ ", prefix = ${addr.match_network()}";
                         add_rs_response_flow = true
                     } else ()
                 };
@@ -6230,7 +6228,7 @@  for (&RouterPort[port@RouterPort{.lrp = lrp@nb::Logical_Router_Port{.peer = None
 
             var __match = "inport == ${json_name} && ip6.dst == ff02::2 && "
                           "nd_ra && ${rEGBIT_ND_RA_OPTS_RESULT()}" in
-            var ip6_str = ipv6_string_mapped(in6_generate_lla(networks.ea)) in
+            var ip6_str = networks.ea.to_ipv6_lla().string_mapped() in
             var actions = "eth.dst = eth.src; eth.src = ${networks.ea}; "
                           "ip6.dst = ip6.src; ip6.src = ${ip6_str}; "
                           "outport = inport; flags.loopback = 1; "
@@ -6275,7 +6273,7 @@  relation Route(key:         route_key,       // matching criteria
 
 function build_route_match(key: route_key) : (string, bit<32>) =
 {
-    var ipX = ip46_ipX(key.ip_prefix);
+    var ipX = key.ip_prefix.ipX();
 
     /* The priority here is calculated to implement longest-prefix-match
      * routing. */
@@ -6284,7 +6282,7 @@  function build_route_match(key: route_key) : (string, bit<32>) =
         DstIp -> ("dst", (key.plen * 2) + 1)
     };
 
-    var network = ip46_get_network(key.ip_prefix, key.plen);
+    var network = key.ip_prefix.network(key.plen);
     var __match = "${ipX}.${dir} == ${network}/${key.plen}";
 
     (__match, priority)
@@ -6294,11 +6292,11 @@  for (Route(.port        = port,
            .src_ip      = src_ip,
            .gateway     = gateway))
 {
-    var ipX = ip46_ipX(key.ip_prefix) in
-    var xx = ip46_xxreg(key.ip_prefix) in
+    var ipX = key.ip_prefix.ipX() in
+    var xx = key.ip_prefix.xxreg() in
     /* IPv6 link-local addresses must be scoped to the local router port. */
     var inport_match = match (key.ip_prefix) {
-        IPv6{prefix} -> if (in6_is_lla(prefix)) {
+        IPv6{prefix} -> if (prefix.is_lla()) {
                             "inport == ${port.json_name} && "
                         } else "",
         _ -> ""
@@ -6432,7 +6430,7 @@  Flow(.logical_datapath = router.lr._uuid,
     EcmpGroup(group_id, router, key, dsts, _, _),
     var member_id_and_dst = FlatMap(numbered_vec(dsts)),
     (var member_id, var dst) = member_id_and_dst,
-    var xx = ip46_xxreg(dst.nexthop),
+    var xx = dst.nexthop.xxreg(),
     var __match = "${rEG_ECMP_GROUP_ID()} == ${group_id} && "
                   "${rEG_ECMP_MEMBER_ID()} == ${member_id}",
     var actions = "${xx}${rEG_NEXT_HOP()} = ${dst.nexthop}; "
@@ -6512,7 +6510,7 @@  Flow(.logical_datapath = router.lr._uuid,
      .external_ids = map_empty()) :-
     EcmpSymmetricReply(router, dst, route_match, tunkey),
     var ecmp_reply = "ct.rpl && ct_label.ecmp_reply_port == ${tunkey}",
-    var xx = ip46_xxreg(dst.nexthop).
+    var xx = dst.nexthop.xxreg().
 
 
 /* IP Multicast lookup. Here we set the output port, adjust TTL and advance
@@ -6545,7 +6543,7 @@  for (IgmpRouterMulticastGroup(address, &rtr, ports)) {
             }
         } in
         Some{var ip} = ip46_parse(address) in
-        var ipX = ip46_ipX(ip) in
+        var ipX = ip.ipX() in
         UniqueFlow[Flow{.logical_datapath = rtr.lr._uuid,
                         .stage            = s_ROUTER_IN_IP_ROUTING(),
                         .priority         = 500,
@@ -6646,7 +6644,7 @@  Flow(.logical_datapath = r.lr._uuid,
                  " priority %"PRId64" nexthop %s",
                  rule->priority, rule->nexthop);
     */
-    var xx = ip46_xxreg(src_ip),
+    var xx = src_ip.xxreg(),
     var actions = (pkt_mark_policy(policy.options) ++
                    "${xx}${rEG_NEXT_HOP()} = ${nexthop}; "
                    "${xx}${rEG_SRC()} = ${src_ip}; "
@@ -6697,7 +6695,7 @@  Flow(.logical_datapath = r.lr._uuid,
     Some{var nexthop} = ip46_parse(nexthop_s),
     out_port in &RouterPort(.router = r),
     Some{var src_ip} = find_lrp_member_ip(out_port.networks, nexthop), // or warn
-    var xx = ip46_xxreg(src_ip),
+    var xx = src_ip.xxreg(),
     var actions = (pkt_mark_policy(policy.options) ++
                    "${xx}${rEG_NEXT_HOP()} = ${nexthop}; "
                    "${xx}${rEG_SRC()} = ${src_ip}; "
@@ -7161,9 +7159,9 @@  Flow(.logical_datapath = router.lr._uuid,
     IPv6{var gw_ip6} = dst.nexthop,
     var __match = "eth.dst == 00:00:00:00:00:00 && "
                   "ip6 && xx${rEG_NEXT_HOP()} == ${dst.nexthop}",
-    var sn_addr = in6_addr_solicited_node(gw_ip6),
-    var eth_dst = ipv6_multicast_to_ethernet(sn_addr),
-    var sn_addr_s = ipv6_string_mapped(sn_addr),
+    var sn_addr = gw_ip6.solicited_node(),
+    var eth_dst = sn_addr.multicast_to_ethernet(),
+    var sn_addr_s = sn_addr.string_mapped(),
     var actions = "nd_ns { "
                   "eth.dst = ${eth_dst}; "
                   "ip6.dst = ${sn_addr_s}; "