To compare these requirements to swift-endpoint:
Makes sense, and is what swift-endpoint is already doing.
- Create addresses from buffer (
[UInt8] ), or create from a Span derived from that buffer. Creating from Span is likely preferred. It is assumed that these forms are in network byte order.
swift-endpoint provides init?(parsing span: Span<UInt8>) and func serialize(into span: inout OutputSpan<UInt8>) -> Bool /*success or failure of serialization*/ on IPAddress types (For clarity, I'm intentionally showing the func definitions instead of just the func signatures).
These work with network byte-order bytes. To be clear, these are NOT for "presentation" parsing. Just for "network" parsing, (where "presentation" and "network" refer to what C APIs like inet_pton refer to, where "pton" is short for "presentation to network").
API naming bikesheddings are welcome and will likely happen later if/when the library is proposed in a formal pitch / proposal.
- Create addresses from the raw integer types that are used in the C
sockaddr structs — UInt32 for IPv4 and (UInt32, UInt32, UInt32, UInt32) for IPv6. Note that using these types can raise some byte ordering concerns.
For reference, sockaddr is defined as:
struct sockaddr {
sa_family_t sa_family; /* Address family */
char sa_data[]; /* Socket address */
};
Currently swift-endpoint doesn't expose any actual C types in APIs (or otherwise), which is by design, although we could make exemptions for certain widely-used types such as sockaddr.
This means there is no API in swift-endpoint to retrieve a sa_family_t for a given IP address type.
However, there are func withCString<Result>(_ body: (Span<CChar>) throws -> Result) rethrows -> Result and init?(cString: UnsafePointer<CChar>) to work with cStrings.
While this might look contradictory to what I just said above about "doesn't expose any actual C types", note that still no C types are exposed.
(UInt32, UInt32, UInt32, UInt32) initializer for IPv6 is not provided in swift-endpoint, as I saw no significance in having them. They could be provided though for completeness.
UInt8 (byte) and UInt16 (segment) initializers are provided, as well as UInt128 for the whole value.
- Create from a
String , to parse an address from a string. Note that parsing needs to handle subnets as well (see below).
swift-endpoint not only includes such parsing/encoding impls, it also provides those with what all the AI companies would like to call "frontier" performance.
For parsing subnets, you can use the parsing methods of the CIDR type:
The current CIDR impl behavior is as follows:
- Parses
[ip] into [ip]/32.
- Parses
[ip]/22 into the same [ip]/22 block.
- Parses
192.168.0.77/24 into stored properties of prefix == 192.168.00.77 and mask == 255.255.255.0. Notice the insignificant bits (.77) remain.
- For
Hashable/Equatable the insignificant bits of prefix are assumed as so.
- In
CustomStringConvertible (var description: String), prefix is printed as-is.
- For parsing, in
init?(textualRepresentation span: Span<UInt8>), prefix is taken as-is.
- You can use
myCIDR.networkAddress if you need the host bits masked off.
- You can use
myCIDR.prefixLength to retrieve the prefix length as an integer.
- Contains
func contains(_ other: IPAddressType) -> Bool.
- Also specify interface scope during initialization (for IPv6 link local, etc)
I have some stashed WIP work but this is currently not implemented in swift-endpoint as I forgot about it. Will be implemented later.
an EthernetAddress type
Layer 2 types are not included in swift-endpoint. Perhaps they could be included in a separate module, incase someone is working at layers below IP and would like to not pull in the IP types.
String description needs to be queryable.
String description of IPv4Address and CIDR<IPv4Address> are easily queryable.
String description of IPv6Address and CIDR<IPv6Address> is more complicated and one could call it not queryable for their usecase.
The IPv6 description follows RFC 5952 - A Recommendation for IPv6 Address Text Representation, which means [2001:db8::1] is printed as opposed to a queryable 2001:0db8:0000:0000:0000:0000:0000:0001.
IPv4: isBroadcast, isLoopback, isLinkLocal, isSiteLocal, isLocalGroup, isZeroNet, isMulticast, isInLoopbackRange, isDSLite, is6to4RelayAnycast, isPrivateUse, isSharedAddressSpace
IPv6: isLoopback, is6to4, isIPv4Mapped, isIPv4Mapped, isScopeLinkLocal, isMulticastLinkLocal, isMulticastInterfaceLocal, isScopeEmbedded, isSiteLocal, isUniqueLocal, isUnspecified, multicastScope, multicastFlags, isMulticast
Some of these are already included:
IPv4:
public var isLoopback: Bool { get }
public var isMulticast: Bool { get }
public var isLinkLocal: Bool { get }
public var isUnspecified: Bool { get }
public var isBroadcast: Bool { get }
public var isPrivate: Bool { get }
public var isShared: Bool { get }
public var isDocumentation: Bool { get }
IPv6:
public var isLoopback: Bool { get }
public var isMulticast: Bool { get }
public var isLinkLocalUnicast: Bool { get }
public var isUnspecified: Bool { get }
public var isUniqueLocal: Bool { get }
public var isDocumentation: Bool { get }
I'm not sure how far we should go and what should be our reference in terms of what CIDR blocks (and their corresponding IP properties) to add.
Additionally, there should be some type that indicates an address family, or an enum that holds either v4 or v6, etc.
AnyIPAddress sounds like would match this requirement. With the addition that we'll have to prepare for a possible v8 IP type as well.
As conveniences, provide static instances for theany,broadcast, andloopbackaddresses.
CIDR static methods are provided, but no such static methods in IP address types.
I'm open to adding such static properties, and defaulting to the first non-network address in the block, or the network address if block is only 1 IP (so 127.0.0.1 in 127.0.0.0/8, or ::1 in ::1/128).
CIDR -Like in swift-network-evolution ( swift-network-evolution/Sources/SwiftNetwork/Utilities/IPAddress+CIDR.swift at main · apple/swift-network-evolution · GitHub ), we’d want to include CIDR parsing support for both IPv4 and IPv6 addresses that can take a CIDR string and produce a network address paired with its subnet mask. Additionally, we’d want the ability to check whether a given address is contained within that subnet, and falls back to domain pattern matching when the pattern isn’t a CIDR string.
These all exist in swift-endpoint, and while I like the relative simplicity of the code in swift-network-evolution, I'd like to mention swift-endpoint implementations will certainly be more performant.
NAT64 -Similar to what is done in swift-network-evolution ( swift-network-evolution/Sources/SwiftNetwork/Utilities/NAT64.swift at main · apple/swift-network-evolution · GitHub ), we’d like to include a NAT64 Prefix type that holds an IPv6 address paired with its length, and have the ability to embed an IPv4 address into an IPv6 address and extract it back out using the specified NAT64Prefix. We’d utilize the queryable property requirements above to make sure we don’t synthesize addresses that aren’t meant to be synthesized.
NAT64 is not explicitly implemented in swift-endpoint. That could change.
However, swift-endpoint does work and parse ipv4-embedded ipv6 addresses, and already provides some conversion methods: IPv6Address.init(ipv4: IPv4Address), IPv4Address.init?(ipv6: IPv6Address).
Currently both assume "IPv4-Mapped IPv6 Address" where the trailing 32 bits of the IPv6 address is the value of the IPv4 address, and the 16 bits behind it are all set to on. Per RFC 4291 - IP Version 6 Addressing Architecture.
Perhaps the assumption for an exact "IPv4-mapped IPv6 address" should not be made and the library should be able to recognize more types of embedded IPv4 addresses in IPv6.
We assume that the port type will be struct that isHashableandSendable, and own its backing storage. The expected storage would be aUInt16. Ports have more complex byte-ordering considerations than addresses, since they need to be handled in packets in network byte order, but are usually dealt with in host byte order in applications.
Apart from API bikesheddings that one could have, this is the case in swift-endpoint (see Port.swift).
- Create from a
UInt16, with clear indication of byte ordering
- Create from a
String of the port, like "443" ; this has the nice side effect of avoiding byte ordering confusion.
Everything is assumed network-byte-order by default in swift-endpoint. Perhaps there should be more efforts to come with some kind of clear and explicit policy for byte-orderings to make sure users make as few mistakes as possible when holding the APIs.
I notice swift-endpoint's Port misses String conversion methods but they will be easy to implement. I'll take note to implement such methods.
What is the backing storage for addresses?
in swift-endpoint, the backing storages are UInt32 and UInt128 (UnsignedInteger128 to be exact) types. This allows for easy generic operations. Such as bitwise operations in CIDR blocks.
they are clumsy/inefficient forSpanaccess, introduce potential byte ordering confusion, and are harder to access on a per-octet level.
I agree with "clumsy", "harder" and "introduce potential byte ordering confusion". However, getting a span out of and integer is not an issue apart from the need to use closures.
For example:
withUnsafeBytes(of: myInteger.bigEndian) { pointer in
let span = pointer.span
}
swift-endpoint provides methods to get byte tuples or segments (UInt16s) out of an address.
Perhaps swift-endpoint should also provide withSpan functions as well.
Inline arrays automatically supportSpanaccess, provide per-octet access, and more correctly model the concept of an address. However, they aren’t available in older Swift versions, and don’t natively supportHashableyet.
Agree. But requiring a minimum Apple platforms of 26 would simply block adoption of this package in a lot of existing packages that strictly follow SemVer and currently support Apple platforms lower than 26 (very common on server side), if they don't want to release a new major version.
This will have to be decided on in the workgroup I'd assume and see which way we'd want to take.
swift-endpoint used to use InlineArray for the backing storage before I noticed that makes the library platforms requirements too high to adopt in other libraries.
For example, consider a model where there is anIPv6Addressstruct that owns the bytes for the address. There could be another non-escapable lifetime-constrained type,IPv6AddressVieworIPv6AddressPropertiesthat provides computed variables for properties about the address, and also offersHashableconformance. TheIPv6AddressViewcould be accessed on an ownedIPv6Address, or initialized from aSpanof 16 bytes.
It’s not clear if this model is necessary, but if we think it will be useful, it might be nice to put it in from the start.
If the address types use an storage of an integer type or an InlineArray, I'd say there will be no concern of needing view types. The types will be easily and cheaply copyable. As opposed to an impl that e.g. uses Data, where there is CoW and ARC concerns with the performance.
How should interface scopes be represented?
As mentioned, scopes are not currently implemented in swift-endpoint so I have no remarks regarding this right now.