Skip to content

Commit 15e5d29

Browse files
Merge pull request #11652 from RaimoNiskanen/raimo/kernel/inet-parse-addresses-optimizations/OTP-20357
Optimize `inet_parse` functions for IPv6 and non-strict IPv4 addresses. Update the behaviour of the parse functions to raise an `error` exception for argument type violations. Previously there was special handling of basic type violations to return `{error, einval}`, but for example non-`char()` elements in a `list()` (i.e. not a `string()`) caused an exception. Now all type violations are handled in the same way, as in not handled by the code, resulting in an `error` exception. Update the documentation of `inet:parse_`*`/1`to describe the formats that are parsed in detail. Add a note in the documentation about the changed exception behaviour above. Also document what `inet:ntoa/1` outputs.
2 parents 6fda90e + d2cf4ec commit 15e5d29

3 files changed

Lines changed: 820 additions & 465 deletions

File tree

‎lib/kernel/src/inet.erl‎

Lines changed: 250 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,19 @@ Add the following directive to the module:
376376
-type ip_address() :: ip4_address() | ip6_address().
377377

378378
-doc """
379-
An IP address in text form, as accepted by the `parse_*` functions.
379+
An IP address in text form, as accepted by the
380+
[`parse_`*](`parse_address/1`) functions.
380381

381382
Either a [character list](`t:string/0`) such as `"192.168.0.1"`,
382383
or a `t:binary/0` such as `~"192.168.0.1"`.
383384

384-
*Since OTP @OTP-20357@* a binary is accepted; before that only a list.
385+
> #### Note {: .info }
386+
>
387+
> *Since OTP @OTP-20357@* a binary is accepted; before that only a list.
388+
>
389+
> With that change, the `parse_`* functions mostly raise an error exception
390+
> for an argument that is not according to this type. Before this,
391+
> common bad arguments were handled and returned `{error, einval}`.
385392
""".
386393
-type address_string() :: string() | binary().
387394

@@ -2937,7 +2944,41 @@ net_getservbyname(Name, Protocol) when is_atom(Protocol) ->
29372944
net:getservbyname(Name, Protocol).
29382945

29392946

2940-
-doc "Parse an `t:ip_address/0` to an IPv4 or IPv6 address string.".
2947+
-doc """
2948+
Convert an `t:ip_address/0` to an IPv4 or IPv6 address string.
2949+
2950+
The returned string, if parsed with `parse_address/1`
2951+
or `parse_strict_address/1` will return the same `IpAddress`.
2952+
2953+
An `t:ip4_address/0` returns a string in dot-decimal notation,
2954+
as described for `parse_ipv4strict_address/1`.
2955+
2956+
An `t:ip6_address/0` returns a string in the format
2957+
described for `parse_ipv6strict_address/1`.
2958+
2959+
The first of the longest sequence of consecutive zeros
2960+
in the `t:ip6_address/0` is compressed with `"::"`.
2961+
2962+
IPv4-Mapped and IPv4-Compatible IPv6 addresses are converted
2963+
to an address string with dot-decimal notation suffix.
2964+
2965+
A link-local unicast or multicast address that has a non-zero
2966+
second word (an embedded interface index), outputs that interface index
2967+
in a scope id suffix (a `"%"` char followed by a decimal integer string).
2968+
2969+
Examples:
2970+
2971+
``` text
2972+
{127,0,0,1} -> "127.0.0.1"
2973+
{0,0,0,0,0,0,0,0} -> "::"
2974+
{8193,3512,0,0,8,2048,8204,16762} -> "2001:db8::8:800:200c:417a"
2975+
{0,0,0,0,0,65535,32512,1} -> "::ffff:127.0.0.1"
2976+
{0,0,0,0,0,0,32512,1} -> "::127.0.0.1"
2977+
{0,0,0,0,0,1,32512,1} -> "::1:7f00:1"
2978+
{65282,0,0,0,0,0,0,17} -> "ff02::11"
2979+
{65152,3,0,0,0,0,0,1} -> "fe80::1%3"
2980+
```
2981+
""".
29412982
-doc(#{since => <<"OTP R16B02">>}).
29422983
-spec ntoa(IpAddress) -> Address | {error, einval} when
29432984
Address :: string(),
@@ -2948,8 +2989,82 @@ ntoa(Addr) ->
29482989
-doc """
29492990
Parse (relaxed) an IPv4 address string to an `t:ip4_address/0`.
29502991

2951-
Accepts a short form IPv4 address string (less than 4 fields)
2952-
such as `"127.1"` or `"0x7f000001"`.
2992+
The allowed formats are according to the documentation of
2993+
the libc function inet_aton(), the so called numbers-and-dots notation,
2994+
a fairly flexible format.
2995+
2996+
In short, the string shall have 1..4 dot separated fields.
2997+
2998+
Each field can be one of:
2999+
* A decimal number with at most ten digits, starting with one digit
3000+
`"1"`..`"9"`, and followed by decimal digits `"0"`..`"9"`.
3001+
* The decimal number `"0"`, also as according to
3002+
the following octal number rule:
3003+
* An octal number starting with `"0"`, followed by
3004+
at most eleven octal digits `"0"`..`"7"`.
3005+
* A hexadecimal number prefixed by `"0x"` or `"0X"`,
3006+
followed by at most eight hexadecimal digits
3007+
`"0"`..`"9"`, `"a"`..`"f"` or `"A"`..`"F"`.
3008+
3009+
Note that decimal numbers cannot be written with leading zeros,
3010+
since that means octal numbers.
3011+
3012+
If there are 4 fields, **A.B.C.D**, then all are bytes assigned in
3013+
left-to-right order. This corresponds to a legacy Class C
3014+
network address, for example `"192.168.0.17"`.
3015+
3016+
If there are 3 fields, **A.B.C**, then **A** and **B** are the two
3017+
initial bytes, and **C** is the 16 bit big endian value that follows.
3018+
This corresponds to a legacy Class B network address,
3019+
for example `"172.16.4711"`, which is equivalent to `"172.16.18.103"`.
3020+
3021+
If there are 2 fields, **A.B**, then **A** is the initial byte,
3022+
and **B** is the 24 bit big endian value that follows.
3023+
This corresponds to a legacy Class A network address,
3024+
for example `"10.174711"`, which is equivalent to `"10.2.170.119"`.
3025+
3026+
If there is only 1 field it is a 32 bit big endian value.
3027+
3028+
In this notation all these strings represent the
3029+
`t:ip4_address/0` `{127,0,0,1}`, where the first one is in
3030+
dot-decimal notation as accepted by `parse_ipv4strict_address/1`
3031+
and output by `ntoa/1`, which is a stricter subset of the
3032+
numbers-and-dots notation parsed by this function.
3033+
``` text
3034+
"127.0.0.1"
3035+
"127.0.1" % Class B 127.0.0.1
3036+
"127.1" % Class C 127.0.0.1
3037+
"0x7f000001" % Hexadecimal 32-bit
3038+
"0X7f.0.0.1" % Hexadecimal first byte
3039+
"0177.0.0.1" % Octal first byte
3040+
"0177.0000.0000.0001" % Leading zeros in octal fields
3041+
"0x7f.0x00.0x00.0x01" % Leading zeros in hexadecimal fields
3042+
```
3043+
3044+
The description above implies that leading and trailing characters
3045+
are not allowed, and fields are not allowed to be empty.
3046+
So for example the following strings return `{error, einval}`:
3047+
3048+
``` text
3049+
" 127.0.0.1" % Leading whitespace
3050+
"127.0.0.1\n" % Trailing whitespace
3051+
"127.0.0." % Empty field
3052+
".0.0.0" % Empty field
3053+
"127..0.1" % Empty field
3054+
"x7f.0.0.1" % Malformed prefix
3055+
"127.0.0.x1" % Malformed prefix
3056+
"127.0.0.256" % Byte overflow
3057+
"127.0.65536" % 16-bit word overflow
3058+
"127.0x01000000" % 24-bit word overflow
3059+
```
3060+
3061+
> #### Note {: .info }
3062+
>
3063+
> *Since OTP @OTP-20357@* a binary is accepted; before that only a list.
3064+
>
3065+
> With that change, this function mostly raises an error exception
3066+
> for an argument that is of an invalid type. Before this,
3067+
> common bad arguments were handled and returned `{error, einval}`.
29533068
""".
29543069
-doc(#{since => <<"OTP R16B">>}).
29553070
-spec parse_ipv4_address(Address) ->
@@ -2962,8 +3077,25 @@ parse_ipv4_address(Addr) ->
29623077
-doc """
29633078
Parse (relaxed) an IPv6 address string to an `t:ip6_address/0`.
29643079

2965-
Also accepts a (relaxed) IPv4 address string like `parse_ipv4_address/1`
2966-
and returns an IPv4-mapped IPv6 address.
3080+
"Relaxed" in this case means that if `Address` is
3081+
a "relaxed" IPv4 address string as accepted by `parse_ipv4_address/1`,
3082+
this function returns an IPv4-mapped IPv6 address.
3083+
3084+
Example: `"127.0.0.1"` gives `{0,0,0,0,0,65535,32512,1}`,
3085+
as does `"::ffff:127.0.0.1"`
3086+
3087+
Otherwise `Address` has to be an IPv6 address string
3088+
as accepted by `parse_ipv6strict_address/1`.
3089+
3090+
Example: `"2001::1"` gives `{8193,0,0,0,0,0,0,1}`.
3091+
3092+
> #### Note {: .info }
3093+
>
3094+
> *Since OTP @OTP-20357@* a binary is accepted; before that only a list.
3095+
>
3096+
> With that change, this function mostly raises an error exception
3097+
> for an argument that is of an invalid type. Before this,
3098+
> common bad arguments were handled and returned `{error, einval}`.
29673099
""".
29683100
-doc(#{since => <<"OTP R16B">>}).
29693101
-spec parse_ipv6_address(Address) ->
@@ -2976,8 +3108,41 @@ parse_ipv6_address(Addr) ->
29763108
-doc """
29773109
Parse an IPv4 address string to an `t:ip4_address/0`.
29783110

2979-
Requires an IPv4 address string containing four fields,
2980-
that is; _not_ a short form address string.
3111+
The allowed format is according to the documentation of
3112+
the libc function inet_pton(), the so called dot-decimal notation,
3113+
without leading zeros.
3114+
3115+
The string shall have 4 dot separated fields with decimal numbers
3116+
of 1..3 digits in the range 0..255. Leading zeros are not allowed,
3117+
which rules out the ambiguity whether a leading zero could mean octal notation.
3118+
3119+
These are some address strings that are successfully parsed:
3120+
3121+
``` text
3122+
"0.0.0.0"
3123+
"127.0.0.1"
3124+
"255.255.255.255"
3125+
```
3126+
3127+
As for `parse_ipv4_address/1` leading and trailing characters
3128+
are not allowed.
3129+
3130+
Here are some examples of strings that return `{error, einval}`:
3131+
3132+
``` text
3133+
" 0.0.0.0" % Leading whitespace
3134+
"0.0.0.0\n" % Trailing whitespace
3135+
"127.000.000.001" % Leading zeros
3136+
"127.0.0.256" % Byte overflow
3137+
```
3138+
3139+
> #### Note {: .info }
3140+
>
3141+
> *Since OTP @OTP-20357@* a binary is accepted; before that only a list.
3142+
>
3143+
> With that change, this function mostly raises an error exception
3144+
> for an argument that is of an invalid type. Before this,
3145+
> common bad arguments were handled and returned `{error, einval}`.
29813146
""".
29823147
-doc(#{since => <<"OTP R16B">>}).
29833148
-spec parse_ipv4strict_address(Address) ->
@@ -2990,9 +3155,51 @@ parse_ipv4strict_address(Addr) ->
29903155
-doc """
29913156
Parse an IPv6 address string to an `t:ip6_address/0`.
29923157

2993-
_Doesn't_ accept an IPv4 address string. An IPv6 address string, though,
2994-
allows an IPv4 tail like this: `"::127.0.0.1"`
2995-
(which is the same as `"::7f00:0001"`).
3158+
Accepted address strings are those according to RFC 4291 section 2.2,
3159+
IP Version 6 Addressing Architecture - Text Representation of Addresses.
3160+
3161+
In short, and address string is 8 colon separated 1..4 hexadecimal
3162+
digits fields where leading zeros are allowed. One double colon
3163+
can be used to fill out the address with one or more zero fields.
3164+
The double colon is also allowed at the start and the end of the string.
3165+
3166+
Both the letters `"a"`..`"f"` and `"A"`..`"F"` are allowed
3167+
in the hexadecimal fields.
3168+
3169+
The last two fields can be expressed with IPv4 dot-decimal notation
3170+
as accepted by `parse_ipv4strict_address/1`.
3171+
3172+
An address can have a suffix denoting the IPv6 scope id. It is
3173+
a `"%"` character followed by a non-empty string. If that string
3174+
is a decimal integer (leading zeros are allowed), the address
3175+
has to be a link-local unicast or multicast IPv6 address,
3176+
and then the decimal integer is embedded into the second field
3177+
of the address, which is supposed to be zero. This is the
3178+
FreeBSD approach of embedding a link index into a link-local IPv6 address.
3179+
Strings that are not decimal integers are currently ignored.
3180+
3181+
Examples:
3182+
3183+
``` text
3184+
"2001:DB8:0:0:8:800:200C:417A"
3185+
"2001:db8::8:800:200c:417a"
3186+
"ff02:0:0:0:0:0:0:1010"
3187+
"ff02::1010"
3188+
"2000::"
3189+
"::"
3190+
"::1"
3191+
"::127.0.0.1"
3192+
"::ffff:192.168.0.53"
3193+
"fe80::1%3"
3194+
```
3195+
3196+
> #### Note {: .info }
3197+
>
3198+
> *Since OTP @OTP-20357@* a binary is accepted; before that only a list.
3199+
>
3200+
> With that change, this function mostly raises an error exception
3201+
> for an argument that is of an invalid type. Before this,
3202+
> common bad arguments were handled and returned `{error, einval}`.
29963203
""".
29973204
-doc(#{since => <<"OTP R16B">>}).
29983205
-spec parse_ipv6strict_address(Address) ->
@@ -3005,10 +3212,22 @@ parse_ipv6strict_address(Addr) ->
30053212
-doc """
30063213
Parse an IP address string to an `t:ip_address/0`.
30073214

3008-
Returns an `t:ip4_address/0` or an `t:ip6_address/0` depending
3009-
on which parsing that succeeds.
3215+
First tries to parse the `Address` string with `parse_ipv4_address/1`
3216+
and if that returns `{error, einval}` with `parse_ipv6strict_address/1`.
30103217

3011-
Accepts a short form IPv4 address string like `parse_ipv4_address/1`.
3218+
Hence this function accepts either an IPv4 address string in
3219+
numbers-and-dots notation, or an IPv6 address string.
3220+
3221+
Returns an `t:ip4_address/0` or an `t:ip6_address/0`
3222+
depending on which parsing succeeds.
3223+
3224+
> #### Note {: .info }
3225+
>
3226+
> *Since OTP @OTP-20357@* a binary is accepted; before that only a list.
3227+
>
3228+
> With that change, this function mostly raises an error exception
3229+
> for an argument that is of an invalid type. Before this,
3230+
> common bad arguments were handled and returned `{error, einval}`.
30123231
""".
30133232
-doc(#{since => <<"OTP R16B">>}).
30143233
-spec parse_address(Address) ->
@@ -3035,7 +3254,22 @@ parse_address(Addr, inet6) ->
30353254
-doc """
30363255
Parse an IP address string to an `t:ip_address/0`.
30373256

3038-
Like `parse_address/1` but _doesn't_ accept a short form IPv4 address string.
3257+
First tries to parse the `Address` string with `parse_ipv4strict_address/1`
3258+
and if that returns `{error, einval}` with `parse_ipv6strict_address/1`.
3259+
3260+
Hence this function accepts either an IPv4 address string in
3261+
dot-decimal notation, or an IPv6 address string.
3262+
3263+
Returns an `t:ip4_address/0` or an `t:ip6_address/0`
3264+
depending on which parsing succeeds.
3265+
3266+
> #### Note {: .info }
3267+
>
3268+
> *Since OTP @OTP-20357@* a binary is accepted; before that only a list.
3269+
>
3270+
> With that change, this function mostly raises an error exception
3271+
> for an argument that is of an invalid type. Before this,
3272+
> common bad arguments were handled and returned `{error, einval}`.
30393273
""".
30403274
-doc(#{since => <<"OTP R16B">>}).
30413275
-spec parse_strict_address(Address) ->

0 commit comments

Comments
 (0)