JoelBender / bacpypes

BACpypes provides a BACnet application layer and network layer written in Python for daemons, scripting, and graphical interfaces.
MIT License
294 stars 130 forks source link

BACnet through VPN + NAT #380

Open DB-CL opened 3 years ago

DB-CL commented 3 years ago

Hi there !

Awesome library ! We used to work with other libraries and I must say this is a really good piece of work !

I have a use case I cannot get to work properly and I really would like your opinion about it. I'm not sure I'm getting everything correctly :)

Here is the scheme of my use case : VPN Bacnet

I have a server, linked to a VPN Router through a VPN L3 and this router has a leg into a network containing some BACnet BBMD. I do not manage the purple network and I just have the possibility to access a router inside it through this VPN. I also have the possibility to NAT some ports from the VPN interface of the router to the local interface connected to the purple network.

That mean I can have some port forwarding NAT like that : 10.0.0.30:47808 <=> 192.168.0.2:47808 so when I'm talking from my server (10.0.0.20 to 10.0.0.30 on 47808, then I'm actually talking to 192.168.0.2 on 47808).

We want our server to be a foreign device that can talk to every BBMD in the purple network, with only one port forwarding toward one BBMD accepting foreign devices registration.

When I'm using WhoIsIAmForeign.py it's working pretty well. I'm putting 10.0.0.30 as the BBMD IP and the query whois broadcasted through bvlc has an answer from every BBMD. Nice !

But things get complicated when I'm using Discovery.py. I've modified it to get it to work with foreign devices and I think I got the things right, but when I'm trying to read the properties from a deviceid that have been discovered, it uses the local IP to make the ReadProperty query (seen in Wireshark on the server) : 192.168.0.X. But from this side of the VPN this IP is not known and therefore the query fails.

This is the same with the ReadPropertyForeign.py script, it requires an IP to read a property and the only IP I can give to it is the IP of my VPN router : 10.0.0.30. It works but it can only query the BBMD directly pointed by the NAT forwarding, although I would like to be able to query every BACnet device through this BBMD.

Here are some additional thoughts and information :

Can someone give me some pointers from here ? What should I do to get a script like the Discovery.py to work in my case ?

Many thanks :)

JoelBender commented 3 years ago

When your server sends out a broadcast Who-Is as a foreign device it is actually sending a unicast Distribute-Broadcast-To-Network message to the BBMD which is broadcasting it locally. The source address of this request as received by the BBMD is the inside address of your tunnel 192.168.0.1 thanks to the NAT, so the broadcast request that goes out also has that same source address. All of the I-Am responses from the other BBMDs are being sent to that, and if you are receiving them, that means that (a) they are unicast and the VPN router is forwarding them to you (which you can see if they are Original-Unicast-NPDU messages) and/or (b) they are locally broadcast on the 192.168.0.0/24 network and the BBMD is forwarding them back to you as a Forwarded-NPDU.

You could very easily be getting both types of BVLL messages depending on how the BBMDs behave, but (a) might be blocked by your VPN router because the only messages it will forward back to you are from 192.168.0.2.

After registering as a foreign device, see if you can read the Foreign Device Table and confirm that the address the BBMD has for you is 192.168.0.1. I would expect that is the case, and it doesn't help your problem, just confirms what I think is going on!

After registering as a foreign device, read the routing tables (the WhoIsRouterForeign.py sample application can help) and you will probably see there are two routing table entries, one for the IPv4 side (the purple network) and one for the other side (MS/TP? ARCNET?) where the other BACnet devices live.

The problem for you is that the BBMD is there to just forward broadcast messages on the IPv4 side and only act as a BACnet router for the other directly connected devices. It assumes that if you are coming from the IPv4 side you can talk to the other IPv4 devices (the BBMDs) with regular unicast traffic which is being blocked by the VPN. (I think you already know this, so consider that confirmation.)

What you want is an IPv4-to-IPv4 router living on the purple network and could also support being a BBMD. In your copy of the standard take a careful look at Figure J-7 in J.7.5 BBMD Operation with Network Address Translation. There is a little tiny dotted line between NAT A and BR 1, and that it uses a different port (47809) from the usual BACnet devices on Network 2.

All of your messages from you VPN Router would be directed to port 47809 and include the DNET/DADR of the BBMD you want to talk to. So, let's say this router sees you on network 123 and the other BBMDs as network 456. You would send your request to the router as 456:192.168.0.3 (using BACpypes notation) and your source address would be presented to them as 123:192.168.0.1:47809.

You have some options: 1) configure the VPN tunnel to allow you to send to more than one IPv4 address 2) configure the BBMD at 192.168.0.2 to also listen for traffic on UDP port 47809 and assign it a different network number, and since this is already a router it might be an option 3) there is an IP2IPRouter.py sample application that you can use on a separate device at 192.168.0.6 or other internal IP address

This issue comes up often enough that BACnet gets the reputation of not being "IT-friendly" and the IT Working Group has since published BACnet/SC. It is, in a nutshell, replacing the traffic between you and the IPv4-to-IPv4 router with a websockets connection and data packets. I had put together a proposal called Streaming BACnet that was designed for this, but that didn't address the authentication and encryption issues because I was using it over an existing VPN tunnel like yours.

JoelBender commented 3 years ago

At one point in time I had a Cisco RV016 at the office and an RV180 at home, so I could configure 192.168.1.0/24 to be a peer network with 192.168.0.0/24 -- it was a good thing while it lasted, but it wasn't for long. With that particular device and firmware, the BGP traffic on the office network would kick it offline.

DB-CL commented 3 years ago

Hello Joel,

I've read from another post that you enjoy taking your time and answering like an Ent. As an old wizard myself, I enjoy reading responses from old talking trees. Do not change anything, this is amazing :)

I understand better the problem, and it actually confirms well what I thought it was. I hoped for something I missed, like a BVLL parameter to set on client side to encapsulate the BACnet payload but it looks like it's not. As an IT guy I can tell you that it's more than a reputation ^^ BACnet is really not IT friendly and it's giving us headaches.

I'll do the investigation with the BBMD table and I'll let you know about it but I think you are right.

configure the VPN tunnel to allow you to send to more than one IPv4 address

We are trying to do so but no luck at this moment. The purple network is really not friendly and I think it's also kicking our traffic when we try some routing wizardry

configure the BBMD at 192.168.0.2 to also listen for traffic on UDP port 47809 and assign it a different network number, and since this is already a router it might be an option

This is very interesting because I was not aware of that solution. I believe this is the most clean answer to my problem and the most BACnetish way to do it right ? Unfortunately I don't have access to this BBMD (I actually have access but I don't have the credentials and I'm not allowed to do anything ...)

there is an IP2IPRouter.py sample application that you can use on a separate device at 192.168.0.6 or other internal IP address

Same answer, I cannot have anything else than access to the VPN router. I can tweak the rules on that router pretty well but it cannot run any logic. And honestly, if I was allowed to put a *nix machine in the network, I would put an HTTP API wrapping your library and I would make some HTTP calls instead of BACnet calls ;)

In other words, as limited as I am, it looks like I have no solution for that problem. I'll continue to work on the network to see if we can tweak the packets.

Thank you very much for your answer, its really, really helpful.