blob: 79042112be58526fbdd1a9ef791b70eac7050828 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package bacnetip
import (
"fmt"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"math/rand"
)
type TrafficLogger interface {
Call(args Args)
}
type NetworkNode interface {
fmt.Stringer
setLan(lan *Network)
getName() string
setName(name string)
getAddress() *Address
isPromiscuous() bool
Response(args Args, kwArgs KWArgs) error
}
type Network struct {
name string
nodes []NetworkNode
broadcastAddress *Address
dropPercent float32
trafficLogger TrafficLogger
log zerolog.Logger
}
func NewNetwork(localLog zerolog.Logger, opts ...func(*Network)) *Network {
network := &Network{
log: localLog,
}
for _, opt := range opts {
opt(network)
}
return network
}
func WithNetworkName(name string) func(*Network) {
return func(n *Network) {
n.name = name
}
}
func WithNetworkBroadcastAddress(broadcastAddress *Address) func(*Network) {
return func(n *Network) {
n.broadcastAddress = broadcastAddress
}
}
func WithNetworkDropPercent(dropPercent float32) func(*Network) {
return func(n *Network) {
n.dropPercent = dropPercent
}
}
func WithNetworkTrafficLogger(trafficLogger TrafficLogger) func(*Network) {
return func(n *Network) {
n.trafficLogger = trafficLogger
}
}
// AddNode Add a node to this network, let the node know which network it's on.
func (n *Network) AddNode(node NetworkNode) {
n.log.Debug().Stringer("node", node).Msg("Adding node")
n.nodes = append(n.nodes, node)
node.setLan(n)
// update the node name
if node.getName() == "" {
node.setName(fmt.Sprintf("%s:%s", n.name, node.getAddress()))
}
}
// RemoveNode Remove a node from this network.
func (n *Network) RemoveNode(node NetworkNode) {
n.log.Debug().Stringer("node", node).Msg("Remove node")
for i, _node := range n.nodes {
if _node == node {
n.nodes = append(n.nodes[:i], n.nodes[i+1:]...)
}
}
node.setLan(nil)
}
// ProcessPDU Process a PDU by sending a copy to each node as dictated by the addressing and if a node is promiscuous.
func (n *Network) ProcessPDU(pdu PDU) error {
n.log.Debug().Stringer("pdu", pdu).Msg("processing pdu")
// if there is a traffic log call it with the network name and PDU
if tl := n.trafficLogger; tl != nil {
tl.Call(NewArgs(n.name, pdu))
}
// randomly drop a packet
if n.dropPercent != 0.0 {
if rand.Float32()*100 < n.dropPercent {
n.log.Trace().Msg("Dropping PDU")
return nil
}
}
if n.broadcastAddress != nil && pdu.GetPDUDestination().Equals(n.broadcastAddress) {
n.log.Trace().Msg("broadcast")
for _, node := range n.nodes {
if !pdu.GetPDUSource().Equals(node.getAddress()) {
n.log.Debug().Stringer("node", node).Msg("match")
if err := node.Response(NewArgs(pdu.DeepCopy()), NoKWArgs); err != nil {
n.log.Debug().Err(err).Msg("error processing PDU")
}
}
}
} else {
n.log.Debug().Msg("unicast")
for _, node := range n.nodes {
if node.isPromiscuous() || pdu.GetPDUDestination().Equals(node.getAddress()) {
n.log.Debug().Stringer("node", node).Msg("match")
if err := node.Response(NewArgs(pdu.DeepCopy()), NoKWArgs); err != nil {
n.log.Debug().Err(err).Msg("error processing PDU")
}
}
}
}
return nil
}
func (n *Network) String() string {
return fmt.Sprintf("<Network name=%s>", n.name)
}
// NodeNetworkReference allows Network and IPNetwork to be used from Node.
type NodeNetworkReference interface {
AddNode(node NetworkNode)
ProcessPDU(pdu PDU) error
}
type Node struct {
*Server
lan NodeNetworkReference
address *Address
name string
promiscuous bool
spoofing bool
// pass through args
argSid *int
log zerolog.Logger
}
func NewNode(localLog zerolog.Logger, addr *Address, lan NodeNetworkReference, opts ...func(*Node)) (*Node, error) {
n := &Node{
address: addr,
log: localLog,
}
for _, opt := range opts {
opt(n)
}
if n.name == "" {
n.log = n.log.With().Str("name", n.name).Logger()
}
var err error
n.Server, err = NewServer(localLog, n, func(server *Server) {
server.serverID = n.argSid
})
if err != nil {
return nil, errors.Wrap(err, "error creating server")
}
// bind to a lan if it was provided
if lan != nil {
n.bind(lan)
}
return n, nil
}
func WithNodeName(name string) func(*Node) {
return func(n *Node) {
n.name = name
}
}
func WithNodePromiscuous(promiscuous bool) func(*Node) {
return func(n *Node) {
n.promiscuous = promiscuous
}
}
func WithNodeSpoofing(spoofing bool) func(*Node) {
return func(n *Node) {
n.spoofing = spoofing
}
}
func WithNodeSid(sid int) func(*Node) {
return func(n *Node) {
n.argSid = &sid
}
}
func (n *Node) setLan(lan *Network) {
n.lan = lan
}
func (n *Node) getName() string {
return n.name
}
func (n *Node) setName(name string) {
n.name = name
}
func (n *Node) getAddress() *Address {
return n.address
}
func (n *Node) isPromiscuous() bool {
return n.promiscuous
}
func (n *Node) SetPromiscuous(promiscuous bool) {
n.promiscuous = promiscuous
}
func (n *Node) SetSpoofing(spoofing bool) {
n.spoofing = spoofing
}
func (n *Node) String() string {
return fmt.Sprintf("Node: %s(%v)", n.name, n.serverID)
}
func (n *Node) bind(lan NodeNetworkReference) {
n.log.Debug().Interface("lan", lan).Msg("binding lan")
lan.AddNode(n)
}
func (n *Node) Indication(args Args, kwargs KWArgs) error {
n.log.Debug().Stringer("args", args).Stringer("kwargs", kwargs).Msg("Indication")
// Make sure we are connected
if n.lan == nil {
return errors.New("unbound node")
}
// if the pduSource is unset, fill in our address, otherwise
// leave it alone to allow for simulated spoofing
pdu := args.Get0PDU()
if pduSource := pdu.GetPDUSource(); pduSource == nil {
pdu.SetPDUSource(n.address)
} else if !n.spoofing && !pduSource.Equals(n.address) {
return errors.Errorf("spoofing address conflict (pduSource: '%s', nodeAddress: '%s').", pduSource, n.address)
}
// actual network delivery is a zero-delay task
OneShotFunction(func(args Args, kwargs KWArgs) error {
pdu := args.Get0PDU()
return n.lan.ProcessPDU(pdu)
}, args, NoKWArgs)
return nil
}
// IPNetwork instances are Network objects where the addresses on the
//
// network are tuples that would be used for sockets like ('1.2.3.4', 5).
// The first node added to the network sets the broadcast address, like
// ('1.2.3.255', 5) and the other nodes must have the same tuple.
type IPNetwork struct {
*Network
}
func NewIPNetwork(localLog zerolog.Logger, opts ...func(*Network)) *IPNetwork {
return &IPNetwork{
Network: NewNetwork(localLog, opts...),
}
}
// AddNode Add a node to this network, let the node know which network it's on.
func (n *IPNetwork) AddNode(node NetworkNode) {
n.log.Debug().Stringer("node", node).Msg("Adding node")
ipNode := node.(*IPNode)
address, err := NewAddress(n.log, ipNode.addrBroadcastTuple)
if err != nil {
panic(err) // TODO: check that we do the right thing here. Originally the tuple gets assigned but that makes trouble downstream
}
// first node sets the broadcast tuple, other nodes much match
if len(n.nodes) == 0 {
n.broadcastAddress = address
} else if !address.Equals(n.broadcastAddress) {
panic("nodes must all have the same broadcast tuple")
}
// continue along
n.Network.AddNode(node)
}
// An IPNode is a Node where the address is an Address that has an address
//
// tuple and a broadcast tuple that would be used for socket communications.
type IPNode struct {
*Node
addrTuple *AddressTuple[string, uint16]
addrBroadcastTuple *AddressTuple[string, uint16]
}
func NewIPNode(localLog zerolog.Logger, addr *Address, lan *IPNetwork, opts ...func(*Node)) (*IPNode, error) {
i := &IPNode{
// save the address information
addrTuple: addr.AddrTuple,
addrBroadcastTuple: addr.AddrBroadcastTuple,
}
var err error
i.Node, err = NewNode(localLog, addr, nil, opts...)
if err != nil {
return nil, errors.Wrap(err, "error creating node")
}
i.bind(lan) // bind here otherwise we bind the contained node
return i, nil
}
func (n *IPNode) bind(lan NodeNetworkReference) { // This is used to preserve the type
n.log.Debug().Interface("lan", lan).Msg("binding lan")
lan.AddNode(n)
}
func (n *IPNode) String() string {
return fmt.Sprintf("IPNode(%v): %s, %v", n.Node, n.addrTuple, n.addrBroadcastTuple)
}
type IPRouterNode struct {
*Client
router *IPRouter
lan *IPNetwork
node *IPNode
addrMask *uint32
addrSubnet *uint32
// pass through args
argCid *int
log zerolog.Logger
}
func NewIPRouterNode(localLog zerolog.Logger, router *IPRouter, addr *Address, lan *IPNetwork, opts ...func(*IPRouterNode)) (*IPRouterNode, error) {
i := &IPRouterNode{
// save the references to the router for packets and the lan for debugging
router: router,
lan: lan,
log: localLog,
}
for _, opt := range opts {
opt(i)
}
var err error
i.Client, err = NewClient(localLog, i, func(client *Client) {
client.clientID = i.argCid
})
if err != nil {
return nil, errors.Wrap(err, "error building client")
}
// make ourselves an IPNode and bind to it
i.node, err = NewIPNode(localLog, addr, lan, WithNodePromiscuous(true), WithNodeSpoofing(true))
if err != nil {
return nil, errors.Wrap(err, "error building IPNode")
}
if err := Bind(localLog, i, i.node); err != nil {
return nil, errors.Wrap(err, "error binding IPNode")
}
// save our mask and subnet
i.addrMask = addr.AddrMask
i.addrSubnet = addr.AddrSubnet
return i, nil
}
func WithIPRouterNodeCid(cid int) func(*IPRouterNode) {
return func(n *IPRouterNode) {
n.argCid = &cid
}
}
func (n *IPRouterNode) Confirmation(args Args, kwargs KWArgs) error {
pdu := args.Get0PDU()
n.log.Debug().Stringer("pdu", pdu).Msg("confirmation")
n.router.ProcessPDU(n, pdu)
return nil
}
func (n *IPRouterNode) ProcessPDU(pdu PDU) error {
n.log.Debug().Stringer("pdu", pdu).Msg("ProcessPDU")
return n.Request(NewArgs(pdu), NoKWArgs)
}
func (n *IPRouterNode) String() string {
return fmt.Sprintf("IPRouterNode for %s", n.lan.name)
}
type IPRouter struct {
nodes []*IPRouterNode
log zerolog.Logger
}
func NewIPRouter(localLog zerolog.Logger) *IPRouter {
return &IPRouter{
log: localLog,
}
}
func (n *IPRouter) AddNetwork(addr *Address, lan *IPNetwork) {
n.log.Debug().Stringer("addr", addr).Stringer("lan", lan).Msg("adding network")
node, err := NewIPRouterNode(n.log, n, addr, lan)
if err != nil {
n.log.Error().Err(err).Msg("error creating IPRouterNode")
return
}
n.log.Debug().Stringer("node", node).Msg("node")
n.nodes = append(n.nodes, node)
}
func (n *IPRouter) ProcessPDU(node *IPRouterNode, pdu PDU) {
n.log.Debug().Stringer("node", node).Stringer("pdu", pdu).Msg("processing PDU")
// unpack the address part of the destination
addrstr := *pdu.GetPDUDestination().AddrIP //TODO: check if this is the right way here.
ipaddr := addrstr
n.log.Debug().Uint32("ipaddr", ipaddr).Msg("ipaddr")
// loop through the other nodes
for _, inode := range n.nodes {
if inode != node {
if ipaddr&*inode.addrMask == *inode.addrSubnet {
n.log.Debug().Stringer("inode", inode).Msg("inode")
if err := inode.ProcessPDU(pdu); err != nil {
n.log.Debug().Err(err).Msg("error processing inode")
}
}
}
}
}