Some proposed enhancements to `inet_dns`:
* guard to catch when there is more …than one `EDNS(0)` option present as [discussed](https://github.com/erlang/otp/pull/2959#issuecomment-759405956)
* support for `NOTIFY` and `UPDATE`
* `UPDATE` is a little more complicated as zero length data is now allowed and a new `NONE` class
* removed the `UPDATE{A,D,DA,M,MA}` to kick up a conversation if these should still exist
* includes the defines for `IXFR` and a few other rcodes
* Initial support for `TSIG` [suggested some time back that there would be interest to have this baked into `inet_dns.erl`](https://github.com/erlang/otp/pull/2959#issuecomment-759405956)
* ~~Not yet added support for `TSIG` over TCP as [described by RFC8945, section 5.3.1](https://datatracker.ietf.org/doc/html/rfc8945#section-5.3.1); this is on my list of things to resolve though~~ - this has now been added though mostly untested
I suspect none of this is particularly controversial but the `TSIG` API does need to be thrashed out.
I am submitting what I see as version zero (0) and finally worth sharing around. I really would prefer the focus be on the API as the implementation will need burning and redoing a few more times from scratch as we carve out the interface.
In the spirit of `inet_dns`, this is an undocumented API in that it expects the user to handle policy whilst it only sets out to handles the encoding/decoding/verification.
I tried a few interface/API iterations and found having settled on passing in `#dns_rec{}` ended up being the least offensive and made the most sense from a possible user perspective but this is my view only. Similarly I tried putting this all in `inet_dns.erl` but after a while the sign/verify functionality just felt more correct in its own module, again this is my view, but it did mean I had to export the name encode/decode routines from `inet_dns` so maybe this should be folded back into `inet_dns`.
Broken out of the commit so to hack out the conversation here, this is what it smells like (for a simple client expecting a single UDP response packet to its request):
```
Zone = "example.com".
Name = "cheese".
Request = inet_dns:make_msg([
{header,inet_dns:make_header([
{id,rand:uniform(65536) - 1},
{opcode,update}
])},
% RFC2136, section 2
{qdlist,[ % Zone
inet_dns:make_dns_query([
{domain,Zone},
{class,in},
{type,soa}
])
]},
{nslist,[ % Update
inet_dns:make_rr([
{domain,Name ++ "." ++ Zone},
{ttl,300},
{class,in},
{type,a},
{data,{192,0,2,1}}
])
]}
]).
% my thinking here is to be like crypto:mac_init as we need rolling state especially for TSIG over TCP
TSigState0 = inet_dns_tsig:init([{keys,[{"mykey","moocowmoocow"}]}]).
{ok,RequestSigned,TSigState1} = inet_dns_tsig:sign(Request, TSigState0).
RequestPkt = inet_dns:encode(RequestSigned).
{ok,Sock} = gen_udp:open(0, [binary,{active,false}]).
gen_udp:send(Sock, {{127,0,0,1},5354}, RequestPkt).
{ok,{_,_,ResponsePkt}} = gen_udp:recv(Sock, 0).
{ok,ResponseSigned} = inet_dns:decode(ResponsePkt).
% will return {error,...} if something is wrong
% like inet_dns:decode, will throw formerr if something is wrong with the packet...personally not a fan of this behaviour
{ok,TSigState2} = inet_dns_tsig:verify(ResponseSigned, TSigState1).
Response = inet_dns:msg(ResponseSigned).
```
Usage for a client (shown above and mostly tested):
1. `tsig:init/1`
* configuration `algs` really is only useful as a single item as we use the first algorithm (default: `sha256`) provided
* configuration `keys` really is only useful as a single item as we use the first name/secret entry
3. `tsig:sign/2` the request and send it
4. for each response packet (there can be one or typically for TCP more than one) call `tsig:verify/2`
5. ...remember to consider the gotchas/warnings in the comments at the top of `inet_dns_tsig.erl`
Usage for a server (not tested as I am still in the middle of building my DNS server...will get back to you...):
1. `tsig:init/1`
* configuration `algs` lists all the algorithms we are willing to support (default: only `sha256`, maybe the default for 'server' should be changed to all the algorithms)
* configuration `keys` lists all the name/secret combinations that are accepted
3. call `tsig:verify/2` on the request packet
4. construct the response and call `tsig:sign/2`, sending it to the client; repeat this until the response is completely sent