Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Clarifying socket binding strategies
#1
There have been a number of issues regarding the binding of sockets to local addresses over time, particularly in drivers. They are basically my fault because I did the wrong thing in some ancient driver, and that mistake got propagated by everyone who used it as a prototype for their own drivers.

This issue is mostly benign, however in the presence of multi-homed machines (machines with more than one network interface) and I think sometimes even the presence of virtual software interfaces, it becomes an issue. The reason being that if you get bounds to an interface that has no path to the target address, it won't work. Or, maybe even worse because it's not obvious. it'll take a long and indirect path to the target, possibly even off network and back in.

These issues have been dealt with within the C++ code, other than possibly a couple small ones that have been fixed for the 4.5.5 drop. All that is remaining is to get drivers straightened out. For all of the drivers I wrote, I've fixed these issues. For the shipped ones that I didn't write, I've tried my best to adjust them based on the new stuff in 4.5.5, but I might not have gotten it totally right. For non-shipped drivers, there may be some breakage after upgrading to 4.5.5, but that will indicate it's a driver that has an issue and needs to be looked at. The fixes are pretty simple.

So, anyway, I just wanted to clarify the situation, so that they can be fixed and hopefully future drivers won't have this issue. I'll update the docs as well after this drop goes out.

Effectively, there's just no reason to do local binding almost 99% of the time. That was the biggest problem, that my ancient driver had a local binding call in it and everyone copied that.


Stream Sockets (TCP/IP)

Most drivers use a TCP/IP socket, which never needs binding. Any CML binding for stream sockets has been removed. When you do a connect to the remote address, the system will automatically bind you to the right interface. So any drivers that have binding calls for stream sockets will fail to compile. All it needs to fix is to just remove the binding call.


Client Datagram Sockets (UDP)

The biggest mistake I made was assuming that client side datagram sockets have to be bound, but they don't. In fact it's sub-optimal to do it. If you don't bind it, then the system will find the correct interface for each target address you send to. Binding it locally prevents this. So generally you just want to not do any binding. How I missed this fact for 20 something years, I'll never know.

In the case of sending and receiving a response back, the receiving side will be told the address/port of the sender, and it can use this to send back the reply. Effectively once you do the first send, the socket is bound to the Any address, so that it can receive replies back from any target you send to, no matter what interface the msg went out on. The system will chose a port for you.

However, there was no other way to get the socket open other than a Bind call. The Bind call at the CML level was used to both open and bind at the same time. So, the appropriate fix for client side Datagram sockets is to replace any bind call with an Open call:

Code:
Method Open([In] SockProtos Proto, [In] IPAddrTypes AddrType);

This will do what the bind methods used to, but without the binding. You can send and receive on it just fine and it should work with any target addresses without any special concerns.

[INDENT]* Keep in mind that you cannot query the local end point info in this scenario, because it was never set. Some drivers will query the local end point it attached to and log it. This will cause an exception now because it's not bound until you do a send, and even then it will just get bound to the ANY address, which isn't particularly interesting to know.[/INDENT]


'Server' Datagram Sockets (UDP)

If you need to listen for incoming messages on a datagram socket, meaning unsolicited ones, then it's a server side socket. Generally, if you send a msg on a UDP socket, the target gets your address and uses that to send you back a reply. But if you need to listen for unsolicited msgs, then you need to bind so that you are listening on a specific port.

For this scenario, there is a new method:

Code:
Method BindListen
(
    In] SockProtos Proto
    , [In] IPAddrTypes AddrType
    , [In] Card4 PortNum
);

You just tell it the port you want to listen on. It will bind you to all network interfaces that support the indicated address type, so it will work for all network interfaces. That means it will also work both for local apps that happen to use the localhost address and remote ones that use the actual IP address. Note that this both opens and binds the socket, so no need to call Open() first.

[INDENT]* If you want to support both IPV4 and IPV6, then you will have to create two sockets, and set them each up for one of the IP versions. In both cases, they will be bound to any interfaces that support that requested type.[/INDENT]


Server Stream Sockets (UDP)

Server side streams sockets are not exposed via CML. Server side stream sockets require a listening thread that listens on all interfaces separately and gens up new server side sockets for incoming connections. This isn't expose to CML, so this is a non-issue for this particular topic.


Explicit Datagram Binding

In the very rare circumstances that you actually do need to bind a datagram socket to a particular interface, there are two strategies.

For a client side socket, i.e. this side is initiating contact, and you know the target address you will be talking to, then use BindRemote. This will get you bound to a non-ANY local address that is known to be appropriate for the target address. It asks the system what would be a good local address for this target and binds to that, so as long as you know the target and will be talking to a single target, this is quite safe to do. If you need a specific local port indicate that, else pass zero and the system will choose one. Note that this both opens and binds it, so you don't need to call Open first.

Code:
Method BindRemote([In] SockProtos Proto, [In] IPEndPoint ipepTar, [In] Card4 LocalPort)


The least desirable scheme is BindLocal. You provide an explicit local end point to bind to. The end point contains both the address and port to bind to. But you really shouldn't ever need to use this. Note that this both opens and binds it, so you don't need to call Open first.

Code:
Method BindLocal([In] SockProtos Proto, [In] IPEndPoint ipepLocal)


Summary

So, by following these simple rules, everything should work correctly, even in the presence of multi-homed systems and in the presence of local loopback addresses and network addresses. There might be a little bit of cleanup to do in some of the non-shipped drivers and maybe a couple of the shipped ones I might have not gotten exactly right (listed below), but they should be easy enough to fix up. Sorry for the inconvenience, but this should finally get rid of any of the weirdness related to this issue and make the product more robust, so it's worth the small amount of work required to make the changes.

The ones I wasn't completely sure about are:

SNMP, Insteon, Netmon

These all make a good bit of use of sockets. I think I did the right thing on them, but take a look at them to be sure.
Dean Roddey
Software Geek Extraordinaire
Reply
#2
I woke up in the middle of the night last night and had a thought about this (it's bad when you go to sleep only to be chased by a big hairy socket carrying a bloody binding.)

I suddenly wondered about my claim that the receiver of a datagram msg can just turn around and send back a reply to the caller. I thought, but wait, the client (sender) was never bound to a port, so how would the msg get back.

But I checked it with a test program, and the server side does receive a port, and the client does receive the reply when sent back to that address/port.

I don't know if the system does a bind for you based on the target address, or if it just pseudo binds the client for each outgoing msg. If it's the former then the same issue will arise in a multi-homed system in that if it's a one time lazy binding, and it gets attached to the network interface of the first target, then a subsequent send to a target not accessible from that automatically selected interface won't work.

I'll have to test that. I guess I can run two instances of the server, one that binds to the local host address and another to the real address, and have the client try to send to both and see what happens. If it gets responses back in both cases, and the servers see the incoming messages from the two separate respective interfaces, that would prove it works.
Dean Roddey
Software Geek Extraordinaire
Reply
#3
OK, it's all good. They are smarter than I was giving them credit for. It apparently does an implicit binding to the any address, effectively allowing any target to send back a message to the original sending address.

If you query local end point info on the client side before doing a send, you get an exception since it's not bound. If you do it after a send, you get back an address of 0.0.0.0, and a port chosen by the system. So it's effectively doing the same thing for you as calling our CML BindListen(0) call, with a zero port to let the system select one.

So, if you really want to make sure a client side datagram socket is bound before you start sending, you could always just call BindListen(0), and you will still be safe, and don't have to worry about it not getting bound until later. Though, it doesn't buy you much since you know the end point is going to be 0.0.0.0:x, where x is some system chosen port.

I will update the above post with this info.
Dean Roddey
Software Geek Extraordinaire
Reply
#4
It looks like both the Isy driver and Sage driver are tossing up the errors with 4.5.5.


Attached Files
.txt   CIDLogFileSnap.Txt (Size: 16.52 KB / Downloads: 5)
Reply
#5
Yeh, probably should have stuck with 4.5.4 for the short term. I'll get fixed versions of those posted. I think though that I have the latest ISY in the product now, in which case it would just be a matter of removing an old non-shipped (User mode) version that you have. I'll check that.
Dean Roddey
Software Geek Extraordinaire
Reply
#6
Yeh, the latest ISY is shipped now, so remove the old one and load the shipped one and it will be happy.

1. Remove the driver
2. Go to [cqc]\CQCData\DataServer\Manifests\User\ on the MS and remove the manifest from there. Recycle the MS.
3. Reload the driver, which should now be the shipped one.

I'll deal with the other drivers you had an issue with.
Dean Roddey
Software Geek Extraordinaire
Reply
#7
Looks like the same thing applies to the Sage drivers as well. The shipped versions are the most recent ones and they should be fixed.
Dean Roddey
Software Geek Extraordinaire
Reply
#8
Oh, wait, it's the Sage TV driver, not the server driver. I'll get a fixed version posted here in a minute.
Dean Roddey
Software Geek Extraordinaire
Reply
#9
OK, I posted a new player driver in the Sage thread. The SageServer driver is the same as the one shipped. But the Sage TV driver is not the same as the shipped one. I'm not sure why, but given that Sage is all but gone it's probably not worth putting a lot of time into figuring out.
Dean Roddey
Software Geek Extraordinaire
Reply
#10
I just worked out the issue with the ISY driver on another person's system. Is yours still not working? I'll get a drop up tomorrow that should fix it. It turned out to be a very special case.
Dean Roddey
Software Geek Extraordinaire
Reply


Possibly Related Threads...
Thread Author Replies Views Last Post
  CML Driver socket example? lleo 8 1,278 09-21-2016, 07:37 AM
Last Post: lleo

Forum Jump:


Users browsing this thread: 1 Guest(s)