agronholm / cbor2

Python CBOR (de)serializer with extensive tag support
MIT License
226 stars 59 forks source link

Support of RFC9164 Semantic Tags for IP Addresses/Prefixes #232

Open kc2rxo opened 7 months ago

kc2rxo commented 7 months ago

Things to check first

Feature description

RFC9164 changes the semantic tags for IP Addresses and prefixes. See the RFC for specific details.

TLDR: Instead of 260 and 261 there is now semantic tags 52 (for IPv4) and 54 (for IPv6). Each tag supports a combination of full address and prefix forms. The originals are deprecated.

Use case

IANA has, per RFC9164, marked the original semantic tags as deprecated in favor of the RFC9164 tags.

While I am unsure of how much these new tags are used in the wild, I am currently proposing their use in various documents for the Drone Remote ID Protocol (DRIP). For some examples see this document, Section 9 and Appendix A.

agronholm commented 7 months ago

I think these new tags are a perfect fit for the ipaddress module.

kc2rxo commented 7 months ago

I've done a little bit of work on this, just to see how this could be done before integration and have come up with the following code:

import cbor2
import ipaddress

ip4a = ipaddress.IPv4Address("192.0.2.1")
ip4n = ipaddress.IPv4Network("192.0.2.0/24")
ip4n_pl = ip4n.prefixlen
ip4n_addr = ip4n.network_address.packed[:int(ip4n_pl / 8)]

ip4_addr = cbor2.CBORTag(52, ip4a.packed)
ip4_pfix = cbor2.CBORTag(52, [ip4n_pl, ip4n_addr])
ip4_iface = cbor2.CBORTag(52, [ip4a.packed, 24, 'eth0'])

print(ip4_addr)
print(ip4_pfix)
print(ip4_iface)

ip6a = ipaddress.IPv6Address("2001:0db8:1234:deed:beef:cafe:face:feed")
ip6n = ipaddress.IPv6Network("2001:db8:1234::/48")
ip6n_pl = ip6n.prefixlen
ip6n_addr = ip6n.network_address.packed[:int(ip6n_pl / 8)]

ip6_addr = cbor2.CBORTag(54, ip6a.packed)
ip6_pfix = cbor2.CBORTag(54, [ip6n_pl, ip6n_addr])
ip6_iface = cbor2.CBORTag(54, [ip6a.packed, 56, 'eth1'])

print(ip6_addr)
print(ip6_pfix)
print(ip6_iface)

It should be worth noting that the interface (ipX_iface) definition the zone element (final element) can be either a string, integer or not present at all. The order of the array elements dictates the form you are encoding/decoding.

On the topic of the optional zone identifier, how would this be handled? It looks like the scope_id of ipaddress.IPv6Address is what would be expected but I have never seen or used this function before. The ipaddress documentation doesn't even mention it for IPv4Address. Perhaps it could be ignored for now and only the address and prefix forms of RFC9164 are supported?

To match the examples in RFC9164 I had to extract the address manually from the CIDR notation and perform a slice of the packed address to remove excess value not required in CBOR, thus resulting in the ipX_addr values. This is probably bad practice but I do not see a clean way to get the value required from ipaddress objects.

Obviously the CBORTag's above would be used in a call of encode_semantic() resulting in the desired bytes for the new tags.