| | 1 | | using System.Net; |
| | 2 | | using DnsClient; |
| | 3 | | using DnsClient.Protocol; |
| | 4 | |
|
| | 5 | | namespace NLightning.Infrastructure.Protocol.Services; |
| | 6 | |
|
| | 7 | | public static class DnsSeedClient |
| | 8 | | { |
| 0 | 9 | | public record NodeRecord(byte[] Pubkey, IPEndPoint Endpoint); |
| | 10 | |
|
| | 11 | | /// <summary> |
| | 12 | | /// Find Nodes from DNS seed domains |
| | 13 | | /// </summary> |
| | 14 | | /// <param name="nodeCount">Records to return</param> |
| | 15 | | /// <param name="seeds">List of seed domains</param> |
| | 16 | | /// <param name="ipV6">Return IPv6 endpoints</param> |
| | 17 | | /// <param name="useTcp">Use TCP Only</param> |
| | 18 | | /// <param name="nameServers">Provide your own nameservers to override system</param> |
| | 19 | | /// <returns></returns> |
| | 20 | | public static List<NodeRecord> FindNodes(int nodeCount, List<string> seeds, bool ipV6 = false, bool useTcp = false, |
| | 21 | | { |
| 0 | 22 | | var opts = nameServers.Length != 0 ? new LookupClientOptions(nameServers) : new LookupClientOptions(); |
| 0 | 23 | | opts.UseTcpOnly = useTcp; |
| 0 | 24 | | var client = new LookupClient(opts); |
| 0 | 25 | | var list = new List<NodeRecord>(); |
| 0 | 26 | | foreach (var dnsSeed in seeds) |
| | 27 | | { |
| 0 | 28 | | if (list.Count < nodeCount) |
| | 29 | | { |
| | 30 | | try |
| | 31 | | { |
| 0 | 32 | | var srvResult = client.Query(dnsSeed, QueryType.SRV); |
| 0 | 33 | | if (srvResult.HasError) |
| 0 | 34 | | continue; |
| | 35 | |
|
| 0 | 36 | | var srvShuffled = srvResult.Answers.OrderBy(_ => Guid.NewGuid()).ToList(); |
| | 37 | |
|
| 0 | 38 | | foreach (var srv in srvShuffled.SrvRecords()) |
| | 39 | | { |
| 0 | 40 | | if (list.Count >= nodeCount) |
| | 41 | | { |
| | 42 | | continue; |
| | 43 | | } |
| | 44 | |
|
| 0 | 45 | | var result = client.Query(srv.Target, ipV6 ? QueryType.AAAA : QueryType.A); |
| | 46 | |
|
| 0 | 47 | | if (result.Answers.Count <= 0) |
| | 48 | | { |
| | 49 | | continue; |
| | 50 | | } |
| | 51 | |
|
| 0 | 52 | | var publicKey = GetPublicKey(srv); |
| 0 | 53 | | var ip = GetIp(result.Answers[0]); |
| | 54 | |
|
| 0 | 55 | | if (ip != "0.0.0.0" && ip != "[::0]") |
| | 56 | | { |
| 0 | 57 | | list.Add(new NodeRecord(publicKey, new IPEndPoint(IPAddress.Parse(ip), srv.Port))); |
| | 58 | | } |
| | 59 | | } |
| 0 | 60 | | } |
| 0 | 61 | | catch |
| | 62 | | { |
| 0 | 63 | | } |
| | 64 | | } |
| | 65 | | else |
| | 66 | | { |
| | 67 | | break; |
| | 68 | | } |
| | 69 | | } |
| | 70 | |
|
| 0 | 71 | | return list; |
| | 72 | | } |
| | 73 | |
|
| | 74 | | private static string GetIp(DnsResourceRecord answer) |
| | 75 | | { |
| 0 | 76 | | if (answer is ARecord record) |
| | 77 | | { |
| 0 | 78 | | return record.Address.ToString(); |
| | 79 | | } |
| | 80 | |
|
| 0 | 81 | | return $"[{((AaaaRecord)answer).Address}]"; |
| | 82 | | } |
| | 83 | |
|
| | 84 | | private static byte[] GetPublicKey(SrvRecord srv) |
| | 85 | | { |
| 0 | 86 | | var bech32 = srv.Target.Value.Split('.').First(); |
| 0 | 87 | | var bech32Encoder = NBitcoin.DataEncoders.Encoders.Bech32("ln"); |
| 0 | 88 | | var bech32Data5Bits = bech32Encoder.DecodeDataRaw(bech32, out _); |
| 0 | 89 | | var bech32Data8Bits = ConvertBits(bech32Data5Bits, 5, 8, false); |
| 0 | 90 | | return bech32Data8Bits; |
| | 91 | | } |
| | 92 | |
|
| | 93 | | /* |
| | 94 | | * The following method was copied from NBitcoin |
| | 95 | | * https://github.com/MetacoSA/NBitcoin/blob/23beaaab48f2038dca24a6020e71cee0b14cd55f/NBitcoin/DataEncoders/Bech32En |
| | 96 | | */ |
| | 97 | | private static byte[] ConvertBits(IEnumerable<byte> data, int fromBits, int toBits, bool pad = true) |
| | 98 | | { |
| 0 | 99 | | var num1 = 0; |
| 0 | 100 | | var num2 = 0; |
| 0 | 101 | | var num3 = (1 << toBits) - 1; |
| 0 | 102 | | var byteList = new List<byte>(); |
| 0 | 103 | | foreach (var num4 in data) |
| | 104 | | { |
| 0 | 105 | | if (num4 >> fromBits > 0) |
| 0 | 106 | | throw new FormatException("Invalid Bech32 string"); |
| 0 | 107 | | num1 = num1 << fromBits | num4; |
| 0 | 108 | | num2 += fromBits; |
| 0 | 109 | | while (num2 >= toBits) |
| | 110 | | { |
| 0 | 111 | | num2 -= toBits; |
| 0 | 112 | | byteList.Add((byte)(num1 >> num2 & num3)); |
| | 113 | | } |
| | 114 | | } |
| | 115 | |
|
| 0 | 116 | | if (pad) |
| | 117 | | { |
| 0 | 118 | | if (num2 > 0) |
| 0 | 119 | | byteList.Add((byte)(num1 << toBits - num2 & num3)); |
| | 120 | | } |
| 0 | 121 | | else if (num2 >= fromBits || (byte)(num1 << toBits - num2 & num3) != 0) |
| 0 | 122 | | throw new FormatException("Invalid Bech32 string"); |
| | 123 | |
|
| 0 | 124 | | return [.. byteList]; |
| | 125 | | } |
| | 126 | | } |