blob: fe092e88d83b3ba8afc1f91134fdac5d51107e1e [file] [log] [blame]
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -07001/*
2 * Copyright 2018-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package devices
18
19import (
20 "bytes"
Matteo Scandolo4a036262020-08-17 15:56:13 -070021 "encoding/hex"
Zdravko Bozakov681364d2019-11-10 14:28:46 +010022 "os/exec"
23
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070024 "github.com/google/gopacket"
25 "github.com/google/gopacket/pcap"
26 "github.com/looplab/fsm"
27 "github.com/opencord/bbsim/internal/bbsim/packetHandlers"
28 "github.com/opencord/bbsim/internal/bbsim/types"
29 log "github.com/sirupsen/logrus"
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070030)
31
32var (
33 nniLogger = log.WithFields(log.Fields{"module": "NNI"})
Matteo Scandolo40e067f2019-10-16 16:59:41 -070034 dhcpServerIp = "192.168.254.1"
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070035)
36
Matteo Scandolo27428702019-10-11 16:21:16 -070037type Executor interface {
38 Command(name string, arg ...string) Runnable
39}
40
41type DefaultExecutor struct{}
42
43func (d DefaultExecutor) Command(name string, arg ...string) Runnable {
44 return exec.Command(name, arg...)
45}
46
47type Runnable interface {
48 Run() error
49}
50
51var executor = DefaultExecutor{}
52
Matteo Scandolo86e8ce62019-10-11 12:03:10 -070053type NniPort struct {
54 // BBSIM Internals
Zdravko Bozakov681364d2019-11-10 14:28:46 +010055 ID uint32
56 nniVeth string
57 upstreamVeth string
Pragya Arya996a0892020-03-09 21:47:52 +053058 PacketCount uint64
Matteo Scandolo86e8ce62019-10-11 12:03:10 -070059
60 // PON Attributes
61 OperState *fsm.FSM
62 Type string
63}
64
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070065func CreateNNI(olt *OltDevice) (NniPort, error) {
66 nniPort := NniPort{
Zdravko Bozakov681364d2019-11-10 14:28:46 +010067 ID: uint32(0),
68 nniVeth: "nni",
69 upstreamVeth: "upstream",
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070070 OperState: getOperStateFSM(func(e *fsm.Event) {
71 oltLogger.Debugf("Changing NNI OperState from %s to %s", e.Src, e.Dst)
72 }),
73 Type: "nni",
74 }
Shrey Baid688b4242020-07-10 20:40:10 +053075 _ = createNNIPair(executor, olt, &nniPort)
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070076 return nniPort, nil
77}
78
79// sendNniPacket will send a packet out of the NNI interface.
80// We will send upstream only DHCP packets and drop anything else
Zdravko Bozakov681364d2019-11-10 14:28:46 +010081func (n *NniPort) sendNniPacket(packet gopacket.Packet) error {
Matteo Scandolo73c488d2019-11-01 14:44:22 -070082 isDhcp := packetHandlers.IsDhcpPacket(packet)
83 isLldp := packetHandlers.IsLldpPacket(packet)
84
Shrey Baid688b4242020-07-10 20:40:10 +053085 if !isDhcp && !isLldp {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070086 nniLogger.WithFields(log.Fields{
87 "packet": packet,
88 }).Trace("Dropping NNI packet as it's not DHCP")
Matteo Scandolo73c488d2019-11-01 14:44:22 -070089 return nil
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -070090 }
91
Matteo Scandolo73c488d2019-11-01 14:44:22 -070092 if isDhcp {
93 packet, err := packetHandlers.PopDoubleTag(packet)
94 if err != nil {
95 nniLogger.WithFields(log.Fields{
96 "packet": packet,
97 }).Errorf("Can't remove double tags from packet: %v", err)
98 return err
99 }
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700100
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100101 handle, err := getVethHandler(n.nniVeth)
Matteo Scandolo73c488d2019-11-01 14:44:22 -0700102 if err != nil {
103 return err
104 }
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700105
Matteo Scandolo73c488d2019-11-01 14:44:22 -0700106 err = handle.WritePacketData(packet.Data())
107 if err != nil {
108 nniLogger.WithFields(log.Fields{
109 "packet": packet,
110 }).Errorf("Failed to send packet out of the NNI: %s", err)
111 return err
112 }
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700113
Matteo Scandolo4a036262020-08-17 15:56:13 -0700114 nniLogger.WithFields(log.Fields{
115 "packet": hex.EncodeToString(packet.Data()),
116 }).Trace("Sent packet out of NNI")
Matteo Scandolo73c488d2019-11-01 14:44:22 -0700117 } else if isLldp {
118 // TODO rework this when BBSim supports data-plane packets
119 nniLogger.Trace("Received LLDP Packet, ignoring it")
120 }
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700121 return nil
122}
123
Matteo Scandolo27428702019-10-11 16:21:16 -0700124//createNNIBridge will create a veth bridge to fake the connection between the NNI port
125//and something upstream, in this case a DHCP server.
126//It is also responsible to start the DHCP server itself
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100127func createNNIPair(executor Executor, olt *OltDevice, nniPort *NniPort) error {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700128
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100129 if err := executor.Command("ip", "link", "add", nniPort.nniVeth, "type", "veth", "peer", "name", nniPort.upstreamVeth).Run(); err != nil {
130 nniLogger.Errorf("Couldn't create veth pair between %s and %s", nniPort.nniVeth, nniPort.upstreamVeth)
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700131 return err
132 }
133
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100134 if err := setVethUp(executor, nniPort.nniVeth); err != nil {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700135 return err
136 }
137
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100138 if err := setVethUp(executor, nniPort.upstreamVeth); err != nil {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700139 return err
140 }
141
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100142 // TODO should be moved out of this function in case there are multiple NNI interfaces.
143 // Only one DHCP server should be running and listening on all NNI interfaces
144 if err := startDHCPServer(nniPort.upstreamVeth, dhcpServerIp); err != nil {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700145 return err
146 }
147
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700148 return nil
149}
150
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100151// NewVethChan returns a new channel for receiving packets over the NNI interface
Matteo Scandolo401503a2019-12-11 14:48:14 -0800152func (n *NniPort) NewVethChan() (chan *types.PacketMsg, *pcap.Handle, error) {
153 ch, handle, err := listenOnVeth(n.nniVeth)
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100154 if err != nil {
Matteo Scandolo401503a2019-12-11 14:48:14 -0800155 return nil, nil, err
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100156 }
Matteo Scandolo401503a2019-12-11 14:48:14 -0800157 return ch, handle, err
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100158}
159
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700160// setVethUp is responsible to activate a virtual interface
Matteo Scandolo27428702019-10-11 16:21:16 -0700161func setVethUp(executor Executor, vethName string) error {
162 if err := executor.Command("ip", "link", "set", vethName, "up").Run(); err != nil {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700163 nniLogger.Errorf("Couldn't change interface %s state to up: %v", vethName, err)
164 return err
165 }
166 return nil
167}
168
Zdravko Bozakov681364d2019-11-10 14:28:46 +0100169var startDHCPServer = func(upstreamVeth string, dhcpServerIp string) error {
170 // TODO the DHCP server should support multiple interfaces
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700171 if err := exec.Command("ip", "addr", "add", dhcpServerIp, "dev", upstreamVeth).Run(); err != nil {
172 nniLogger.Errorf("Couldn't assing ip %s to interface %s: %v", dhcpServerIp, upstreamVeth, err)
173 return err
174 }
175
Matteo Scandolo27428702019-10-11 16:21:16 -0700176 if err := setVethUp(executor, upstreamVeth); err != nil {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700177 return err
178 }
179
180 dhcp := "/usr/local/bin/dhcpd"
181 conf := "/etc/dhcp/dhcpd.conf" // copied in the container from configs/dhcpd.conf
182 logfile := "/tmp/dhcplog"
183 var stderr bytes.Buffer
Matteo Scandolofe9ac252019-10-25 11:40:17 -0700184 cmd := exec.Command(dhcp, "-cf", conf, upstreamVeth, "-tf", logfile, "-4")
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700185 cmd.Stderr = &stderr
186 err := cmd.Run()
187 if err != nil {
188 nniLogger.Errorf("Fail to start DHCP Server: %s, %s", err, stderr.String())
189 return err
190 }
191 nniLogger.Info("Successfully activated DHCP Server")
192 return nil
193}
194
195func getVethHandler(vethName string) (*pcap.Handle, error) {
196 var (
197 device = vethName
198 snapshotLen int32 = 1518
199 promiscuous = false
200 timeout = pcap.BlockForever
201 )
202 handle, err := pcap.OpenLive(device, snapshotLen, promiscuous, timeout)
203 if err != nil {
204 nniLogger.Errorf("Can't retrieve handler for interface %s", vethName)
205 return nil, err
206 }
207 return handle, nil
208}
209
Matteo Scandolo401503a2019-12-11 14:48:14 -0800210var listenOnVeth = func(vethName string) (chan *types.PacketMsg, *pcap.Handle, error) {
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700211
212 handle, err := getVethHandler(vethName)
213 if err != nil {
Matteo Scandolo401503a2019-12-11 14:48:14 -0800214 return nil, nil, err
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700215 }
216
Matteo Scandoloe33447a2019-10-31 12:38:23 -0700217 channel := make(chan *types.PacketMsg, 1024)
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700218
219 go func() {
Matteo Scandolo401503a2019-12-11 14:48:14 -0800220 nniLogger.Info("Start listening on NNI for packets")
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700221 packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
222 for packet := range packetSource.Packets() {
223
224 if !packetHandlers.IsIncomingPacket(packet) {
225 nniLogger.Tracef("Ignoring packet as it's going out")
226 continue
227 }
228
229 nniLogger.WithFields(log.Fields{
230 "packet": packet.Dump(),
231 }).Tracef("Received packet on NNI Port")
232 pkt := types.PacketMsg{
233 Pkt: packet,
234 }
235 channel <- &pkt
236 }
Matteo Scandolo401503a2019-12-11 14:48:14 -0800237 nniLogger.Info("Stop listening on NNI for packets")
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700238 }()
239
Matteo Scandolo401503a2019-12-11 14:48:14 -0800240 return channel, handle, nil
Matteo Scandolo4b3fc7e2019-09-17 16:49:54 -0700241}