GithubHelp home page GithubHelp logo

Comments (9)

MichaCo avatar MichaCo commented on May 18, 2024 1

I used IEnumerable<...> everywhere, you may prefer arrays or other ... enumerables 😜 (hell, IEnumerable<...> may cause some issues).

Right, I don't like to use IEnumerable for those kind of things, especially because it gives kinda random results every time it gets iterated ;)

I think, for this library, it is enough to have the existing methods and just extend the returned ServiceHostEntry to contain the missing fields (this would be a non-breaking change)

I'd rather not implement any logic on top of that within this library and leave that up to consumers.
You could use it with the example you have there, or do something totally different ;)

from dnsclient.net.

MichaCo avatar MichaCo commented on May 18, 2024 1

Thanks for the reply ;)
That actually makes more sense, yup

from dnsclient.net.

MichaCo avatar MichaCo commented on May 18, 2024

Hi @RobThree,
Those methods are meant for convenience / to simplify the resolve part. HostEntry is a framework type, I added Port to it because services usually have one ^^
Priority, weight and ttl could be added to that as properties, too I guess.

That being said, the resolved result will already be ordered, you don't usually have to do that again.
A DNS Server usually decides to re-order results to give you some kind of load balancing (e.g. in case there are multiple endpoints for the same service). So, if you order it again, you'll actually loose that.

from dnsclient.net.

RobThree avatar RobThree commented on May 18, 2024

Those methods are meant for convenience / to simplify the resolve part.

That was clear to me πŸ˜‰

Priority, weight and ttl could be added to that as properties, too I guess.

That was, sort of, the intent of my question indeed.

That being said, the resolved result will already be ordered, you don't usually have to do that again.

I don't see any explicit ordering being done? Or I'm missing it?

A DNS Server usually decides to re-order results to give you some kind of load balancing (e.g. in case there are multiple endpoints for the same service).

The priority and weight are intended for clients to be able to determine which record to pick. Assume the following SRV:

prio 0 :   weight: 10   port: 9991  host: hostA.example.org
prio 10:   weight: 10   port: 9992  host: hostB.example.org
prio 10:   weight: 40   port: 9993  host: hostC.example.org
prio 10:   weight: 50   port: 9994  host: hostD.example.org
prio 20:   weight: 1    port: 9995  host: hostE.example.org
prio 20:   weight: 9    port: 9996  host: hostF.example.org

A client should first try host A (highest priority, only one in it's "group"). If host A fails, the client should pick one from hosts B, C or D with a 10% 'chance' of picking host B, 40% chance of picking host C and 50% chance of picking host D. If the picked host (either B, C or D) fails again, the next priority group should be used: hosts E and F. Again, the client should choose between E or F by using a 10% chance of picking E and 90% chance of F.

This is how the 'load balancing' works. The weights and priorities determine when, and how often, a host should be used (assuming the client respects the actual SRV record, ofcourse).

So you see, when using SRV records you will need the priority and weights (the TTL can be useful to 'have' to, but I can live without).

I can do the SRV query just fine, don't get me wrong; I simply use myLookup.Query("_service._tcp.example.com", QueryType.SRV) and that works fine. I just think it's odd that the ResolveService extensionmethod gets rid of the information required for clients to work properly.

I will shortly post a proposed implementation of the ResolveService extensionmethod which, I think, is better suited. Ofcourse, it's up to you to decide wether you like to use it or ignore it πŸ˜‰

from dnsclient.net.

MichaCo avatar MichaCo commented on May 18, 2024

I think the simplified implementation came mostly from me using this together with Consul for service discovery. Consul's DNS endpoint always returns filtered and weighted entries already.

But you are right, for other use-cases that information (prio/weight) is needed.
Sure, go ahead and send a PR ;)

from dnsclient.net.

RobThree avatar RobThree commented on May 18, 2024

Sorry, no PR (yet) but just to demonstrate my idea...

First, a little setup:

public class SrvRecord
{
    public string HostName { get; set; }
    public int Port { get; set; }
    public IPAddress[] AddressList { get; set; }
    public int Priority { get; set; }
    public int Weight { get; set; }
}

Assume this is an SRV record returned by a DNS server (close to what you currently have, except I didn't bother with ResourceRecordInfo and stuff... Remember, this is just to demonstrate.

Next, assume the SRV records as mentioned here. The ResolveService method could return a structure that does two things:

  1. Allows access to all returned records (all 6 SRV records in our example)
  2. Takes care of priorities/weights so the user doesn't have to (and potentially get it wrong)

So, let's make SrvResult (or whatever) class that does just that:

/// <summary>
/// Class to hold results from an SRV lookup
/// </summary>
public class SrvResult : IEnumerable<SrvRecord>
{
    // We need an RNG to pick "random" records
    private static readonly Random _rng = new Random();
    
    // We need to keep track of the SRV records this result represents
    private IEnumerable<SrvRecord> _records;

    /// <summary>
    /// Gets the groups of SRV records (grouped by priority)
    /// </summary>
    public IEnumerable<IEnumerable<SrvRecord>> Groups => _records.GroupBy(r => r.Priority)
        .OrderBy(g => g.Key).Select(g => g);

    /// <summary>
    /// Creates a new instance of SrvResult
    /// </summary>
    /// <param name="records">The SRV records this SrvResult represents</param>
    public SrvResult(IEnumerable<SrvRecord> records)
    {
        if (records is null || !records.Any())
            throw new ArgumentNullException(nameof(records));

        _records = records;
    }

    /// <summary>
    /// "Enumerates" SRV records
    /// </summary>
    /// <returns>Returns SRV records based on priorities and weights in an order you can
    /// immediately consume</returns>
    public IEnumerator<SrvRecord> GetEnumerator()
    {
        // We return exactly ONE record from each prioritygroup
        foreach (var group in Groups)
        {
            // Get all records from this group (same priority)
            var grouprecords = group.ToArray();
            // Determine total weight
            var totalweight = grouprecords.Sum(r => r.Weight);
            if (totalweight == 0)
            {
                // Return random record
                yield return grouprecords[_rng.Next(grouprecords.Length)];
            }
            else
            {
                // See http://www.perlmonks.org/?node_id=242751 for "magic" below
                // In short:
                //
                // 1. Pick a random number less than the total
                // 2. At each position of the array, subtract the weight at that position
                // 3. As soon as the result is negative, return that position
                var rnd_val = _rng.Next(totalweight);
                var i = -1;
                while (rnd_val >= 0)
                    rnd_val -= grouprecords[++i].Weight;

                //Return record based on weight
                yield return grouprecords[i];
            }
        }
    }

    /// <summary>
    /// Explicit interface implementation
    /// </summary>
    /// <returns>Returns SRV records based on priorities and weights in an order you can
    /// immediately consume</returns>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<SrvRecord>)this).GetEnumerator();
    }
}

And now, let's demonstrate:

private static void Main(string[] args)
{

    // Assume we have the following result from a lookup (so, we "fake" a result)
    var result = new SrvResult(new[]
    {
        new SrvRecord { Priority = 0, Weight = 10, Port = 9991, HostName = "hostA.example.org", AddressList = new[] { IPAddress.Parse("1.2.3.4") } },
        new SrvRecord { Priority = 10, Weight = 10, Port = 9992, HostName = "hostB.example.org", AddressList = new[] { IPAddress.Parse("1.2.3.5") } },
        new SrvRecord { Priority = 10, Weight = 40, Port = 9993, HostName = "hostC.example.org", AddressList = new[] { IPAddress.Parse("1.2.3.6") } },
        new SrvRecord { Priority = 10, Weight = 50, Port = 9994, HostName = "hostD.example.org", AddressList = new[] { IPAddress.Parse("1.2.3.7") } },
        new SrvRecord { Priority = 20, Weight = 1, Port = 9995, HostName = "hostE.example.org", AddressList = new[] { IPAddress.Parse("1.2.3.8") } },
        new SrvRecord { Priority = 20, Weight = 9, Port = 9996, HostName = "hostF.example.org", AddressList = new[] { IPAddress.Parse("1.2.3.9") } }
    });

    // Simple demonstration we can get to all individual records if we want to
    Console.WriteLine($"Result: {result.Groups.SelectMany(r => r).Count()} records total");

    // Simple demonstration to show records are returned as specified by their priority / weight
    // You may need to hit a key several times to see that the records returned will vary on each iteration
    while (true)
    {
        Console.WriteLine("Iterating the result:");

        // This is how a "client" could use the result to approach hosts in the desired
        // order without bothering with weights, priorities etc.
        foreach (var entry in result)
            Console.WriteLine($"{entry.HostName}:{entry.Port} ({string.Join(",", entry.AddressList.Select(a => a.ToString()))})");


        Console.WriteLine("Press any key to iterate again");
        Console.ReadKey();
    }
}

Every time the result is iterated you will see the hosts in the order specified by the SRV priorities and then, within a priority, based on the weights within that priority, a single host.

The SrvResult (meh name...) offers access to all SrvRecords should a client want access to the "raw data". Another property (that doesn't return an IEnumerable of IEnumerables...) could be added:

/// <summary>
/// Gets the SRV records (ordered by priority, then by hostname)
/// </summary>
public IEnumerable<SrvRecord> Records => _records.OrderBy(r => r.Priority)
        .ThenBy(r => r.HostName);

The SrvResult, however, also implements the IEnumerable<SrvRecord> interface. It can be iterated and hosts will be returned in order a client should approach them (should a previous host fail or whatever).

I used IEnumerable<...> everywhere, you may prefer arrays or other ... enumerables 😜 (hell, IEnumerable<...> may cause some issues).

Also, by the way, offtopic but still: I like your library a lot! Good work! πŸ‘ I noticed you are moving to DnsOptions to pass to the DnsLookup constructor; good idea! I'm totally on board with that! Looking forward to the next release that contains this change!

from dnsclient.net.

RobThree avatar RobThree commented on May 18, 2024

... especially because it gives kinda random results every time it gets iterated ;)

... which, in this particular case, is exactly what was intended πŸ˜‰

I think, for this library, it is enough to have the existing methods and just extend the returned ServiceHostEntry to contain the missing fields (this would be a non-breaking change)

Sure, that's an option too. Good enough for me πŸ‘

I'd rather not implement any logic on top of that within this library and leave that up to consumers.

If you want such logic in your library or not is, ofcourse, up to you. I'm fine with that. However, maybe another option would be to create a helper-class (or even extension method) that implements the logic 'outside' of the result itself.

You could use it with the example you have there, or do something totally different ;)

I know πŸ˜‰ I'm using the Query("...", QueryType.SRV) now and populate my own object with the results. That works fine. It's just that it's not simple to get the "picking" or "selection" of SRV records right; I've seen many plain wrong implementations on the web so offering the correct implementation in your library would be (IMHO, to be clear) a nice feature. But, again, it's up to you. Thanks for your feedback and thanks for (considering) adding the Priority and Weight "back" in. πŸ‘

from dnsclient.net.

MichaCo avatar MichaCo commented on May 18, 2024

Hey @RobThree
Just revisited this one again, I think the simplest solution is when I add the original SrvRecord to the ServiceHostEntry

    public class ServiceHostEntry : IPHostEntry
    {
        public int Port { get; set; }

        public SrvRecord SrvRecord { get; set; } <== NEW
    }

The SrvRecord has the weight and prio on it so you can further filter/sort the results after calling ResolveService.

What do you think?

from dnsclient.net.

RobThree avatar RobThree commented on May 18, 2024

What do you think?

It's been a while since I opened this issue. I'll need to dive back in the matter and (re)familiarize myself.

From what I see right now: A SRV lookup results in one or more ServiceHostEntry objects (or none); each ServiceHostEntry each having it's own AddressList[]....

Without looking up anything, only basing my opinion on information in this issue...

Wouldn't an SrvRecord property make it awkward to get to the weight and priority? I can get straight to the port and addresslist (which always will contain one, and only one, address, right?) but would have to use the SrvRecord property to get to the Priority and Weight?

Why not:

public class ServiceHostEntry : IPHostEntry
{
    public int Port { get; set; }

    public int Priority { get; set; }
    public int Weight { get; set; }
}

I may be way off base though; as said: it's been a while and I'd have to look longer/deeper. I will, but am currently a bit (too) busy.

from dnsclient.net.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.