Network kit: use next IP if connection is refused

Hi, I’m just starting some work on the StreamRadio app, mostly for fun. I was facing a problem with a particular radio station, that sometimes would play flawlessly and others would not.

After digging in the code for a while and enabling debug messages (I don’t really know C++), I’ve found the domain name is resolving to two different IP’s. Both respond to ping, but only one of them is listening on port 7200, which the Icecast server uses for streaming.

Haiku only seems to use the first address received. So, if for some reason the DNS reply had the “wrong” server in first place, the radio wouldn’t play. Closing the app apparently deletes the DNS cache, so it’s a matter of trying until the DNS response points to the “good” server.

I’ve tried with VLC and Windows Media Player, both on Windows, and they somehow know if they get a “connection refused” with the first IP, they have to try with next one. I’m not sure if that logic it’s embedded in the apps or if the network implementation on Windows checks for connection on the specified port.

My question is, how should I approach this? I’ve seen the RequestCompleted method in the UrlProtocolListener class, I know I can override that method and check for success and do something about it, but if I start a new request it’s probably going to resolve to the same IP address, since it’s cached.

Is there a way to check for connection on a specified port and force using the next address using the network kit classes?

Thanks in advance!
Javier

EDIT: by the way, the stream is http://sa.mp3.icecast.magma.edge-access.net:7200/sc_rad30
If you try enough times with StreamRadio, closing and opening the app, it will fail eventually. Can’t remember if Clementine (for example) is having the same problem, but I guess being a ported app it might use a different resolver mechanism

It’s up to the application to decide what to do with the results of a DNS query. Determining if the server is listening on a port and then switching to the next record is definitely the responsibility of the application.

So it should be a matter of extending the application to detect that a server isn’t listening and then if there is another address record to try it… Clementine probably does handle this in the application or if it uses a library to support the stream type it could be in there where it is handled.

How an application handles these sorts of things is left to the application as they could want to do something like, ping each record and determine the closest server etc… before attempting to establish a connection… the possibilities are endless lol.

Probably GetNextAddress from NetworkAddressResolver should be called again if connection fails… not sure how the connection logic determines if the connection was good or not but that should give you a path to look at things. I’m sure someone else here will chime in with more…

You can create bug or enhancement tickets for StreamRadio here: https://github.com/HaikuArchives/Haiku-Radio/issues

Please open a bugreport at dev.haiku-os.org, there should be a way to make it working automatically. I think we need to tell the DNS server which port we are interested in.

Otherwise, you could do the DNS lookup yourself (using BNetworkAddress, I think?) and it should be possible to get both IP from that. (if not, mention that in the bugreport as well)

I’m with cb88, it’s up to the application - not the DNS server’s business at all, and Connect() won’t be able to implement that logic as standard application feature because DNS lookup is done too early, in its BNetworkAddress parameter’s constructor.

@cb88 if you do the DNS query directly then yes, I agree with you. The StreamRadio app, on the other hand, uses the HTTPRequest class (which derives from NetworkRequest, and uses NetworkAddress internally), and I agree with @PulkoMandy: that class should be able to do this magic automatically. It’s not just StreamRadio, almost every network app could benefit from this, as this is (correct me if I’m wrong) the expected behaviour. Apps with special needs, like the ones you pointed, will handle the DNS request inside the app.

Thanks for pointing me in the right direction. I’m dealing with my own coding limitations now, trying to extend the HTTPRequest and NetworkAddress classes.

@vidrep I’ve commented on an issue opened there a few days ago (I’ve found the fix for that, but I didn’t make a pull request or anything)

@PulkoMandy I’ll open a ticket, thanks for your help!

I was thinking about extending the BNetworkAddress class, creating a socket and trying to connect to the first DNS result, if the connection is refused, call GetNextAddress from NetworkAddressResolver again. But I’ll have to extend the HTTPRequest class so it uses my CustomNetworkAddress internally. Does that make any sense?

Anyway, I still think it would be nice for BNetworkAddress to behave that way by default.

Sounds like you should derive an icecastrequst from networkrequest… the problem is you probably can’t make a generic version of this as each protocol will have a different method of determining if the connection was successful etc…

BNetworkAddress is for addresses only. We really should not be doing connection attempts in it. Is there really no other relevant information in the DNS query?

If you need custom behavior about connecting to IPs, you may need to use BNetworkAddressResolver yourself in StreamRadio.

@waddlesplash I’ve tried with different online tools and sa.mp3.icecast.magma.edge-access.net have two A records: 201.212.9.57 and 201.212.9.58. The first one is listening on port 7200, while the second one is not. Other ports (80 for example) are open in the last one.

Depending on the DNS server used (or even between attempts), the records come in different order, and Haiku only uses the first one. I wonder if other apps (WebPositive for example) have a way to check for that and use the next record if the port is closed.

It’s probably not a good idea to point two different IP’s, with a different set of services, to the same domain name, but it’s the way it’s done, and the fact that both VLC and Windows Media Player on Windows handle that situation correctly makes me wonder what should the app do. At this point I could just use the IP instead of the domain name in StreamRadio, but it’s interesting to know anyway.

It looks to me like for HTTPRequest, the way to cope with an address like that is

  1. do the DNS ahead of time, however you like but in a way that gets you the entire A record, not just one IP.
  2. connect to the service port you want, to verify that it’s available, for each IP address until you get one.
  3. close that connection
  4. use the IP address that worked with BNetworkAddress, HTTPRequest, etc.

Wouldn’t even have to be compiled into the application, could be done in a wrapper.

It might be worth checking whether this is really the domain name you’re supposed to use for this service. They don’t seem to have set up PTR records, so I can’t guess whether other names might point to this same 201.212.9.57 address, but that’s sure what I would expect from a site, if they have different services on different hosts, is to provide an unambiguous name by which you can connect to them.

It is also possible that Windows sorts DNS query results for added determinism.

The domain name is correct. They have an HTML player on their website and using Chrome developer tools you end up finding the stream. I’ve tried the same on a different website that collects radios and their player also ends up pointing to that stream. So that’s correct at least.

Thanks for the steps given. More or less that was my idea (but as you’ve been pointing, is better to use BNetworkResolver directly instead of extending BNetworkAddress)

getaddrinfo() allows to specify a port/service when making a request, but I don’t know how this is used. The topic needs more research. I’m mostly AFK this weekend, but I’ll get back to this in the debugreport

A related blogpost
https://daniel.haxx.se/blog/2012/01/03/getaddrinfo-with-round-robin-dns-and-happy-eyeballs/

He’s wacked. getaddrinfo has been around a long time, and doesn’t present any problem for load balancing with IPv4 addresses. I just tried it to verify, with a load balanced host name, and after several trials I got both host orders. There may be an issue with IPv6, never tried it.

The service port supplied to getaddrinfo is basically irrelevant. It’s supplied as a string, and looked up per getservbyname() if it’s non-numeric, but a numeric string is just converted to a port number. There’s no attempt to connect, that would be crazy.

In my possibly eccentric view, the on-init DNS lookup in BNetworkAddress and BNetAddress makes it a lot harder to make this an “automatic” feature in URL lookups and the like, and frankly it sort of smells bad in principle. The lookup should happen in the code path where the address will be used, so failures and variable results will be relative to the correct time and call stack. It might be fun to add an alternative behavior depending on an optional flag. Network access routines that are prepared to go through a list of IPs would check for that in the BNetAddress/BNetworkAddress they get. If it’s there, the original domain name is stored in the object and the resolution can be done on the spot and all the IPs returned.

1 Like

Thank you all for your replies. Using your ideas I’ve just finished a small mod in StreamRadio that checks for connection on the port before resolving. If the port is closed, it tries with the next record.

It’s on https://github.com/jsteinaker/Haiku-Radio/

Please forgive the code quality, memory leaks or anything that might be there. I don’t really know C++ enough, but I’ve just tried the app and it works the way it should. Until you double click on an empty space on the main window and it crashes, but that’s a different story.

Suggestions are very welcome. Thanks again.

EDIT: the new code should probably belong to the Station class, because when you’re trying to add a new station, that class probes for connection using an internal code (it doesn’t use the StreamIo class at all) and it faces the same problem

2 Likes

AFAICT The way this has been done has the effect that it throws away any non-default URL scheme (the part before the colon in the URL), the host name and the port number. So if the remote service cares about the host name, for example (as it is entitled to do in HTTP), it will now mysteriously break.

Your instinct was correct originally, Haiku’s BHttpRequest ought to do this for you, and it doesn’t, so the really correct place to fix this from Haiku’s point of view is in BHttpRequest. There are plenty of shortcomings in that kit for anybody who wants a new hobby.

That’s a good point. The remote host can’t know what name was used to get the IP, there’s simply no way, but the client has to put the right name in the HTTP Host field. I haven’t looked at the code in question and I don’t know whether it even sends HTTP headers, but my wrapper idea could sure run into that problem with an HTTP client.

Good point indeed. Right now it just tries to open a socket on the specified IP and port, if the server replies, it uses that IP for the HTTP request. With the changes I did, the app is crashing on some systems (see https://github.com/HaikuArchives/Haiku-Radio/issues/8) so that would be priority, but then @tialaramex has very valid points (for example, I’m just prepending “http”, hardcoded, so SSL connections are not supported)