1 //
2 // Copyright (c) 2010-2018 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using Antmicro.Renode.Core;
10 using Antmicro.Renode.Core.Structure;
11 using Antmicro.Renode.Peripherals.Network;
12 using System.Linq;
13 using Antmicro.Renode.Network;
14 using Antmicro.Renode.Logging;
15 using System.Collections.Generic;
16 using Antmicro.Renode.Exceptions;
17 using Antmicro.Renode.Peripherals;
18 using Antmicro.Renode.Time;
19 
20 namespace Antmicro.Renode.Tools.Network
21 {
22     public static class SwitchExtensions
23     {
CreateSwitch(this Emulation emulation, string name)24         public static void CreateSwitch(this Emulation emulation, string name)
25         {
26             emulation.ExternalsManager.AddExternal(new Switch(), name);
27         }
28     }
29 
30     public class Switch : IExternal, IHasOwnLife, IConnectable<IMACInterface>, INetworkLogSwitch
31     {
AttachTo(IMACInterface iface)32         public void AttachTo(IMACInterface iface)
33         {
34             AttachTo(iface, null);
35         }
36 
AttachTo(IMACInterface iface, IMachine machine)37         public void AttachTo(IMACInterface iface, IMachine machine)
38         {
39             lock(innerLock)
40             {
41                 if(ifaces.Any(x => x.Interface == iface))
42                 {
43                     throw new RecoverableException("Cannot attach to the provided MAC interface as it is already registered in this switch.");
44                 }
45 
46                 var ifaceDescriptor = new InterfaceDescriptor
47                 {
48                     Interface = iface,
49                     Delegate = f => ForwardToReceiver(f, iface)
50                 };
51 
52                 //  this is to handle TAPInterfaces that are not peripherals
53                 if(iface is IPeripheral peripheralInterface)
54                 {
55                     ifaceDescriptor.Machine = machine ?? peripheralInterface.GetMachine();
56                 }
57                 iface.FrameReady += ifaceDescriptor.Delegate;
58                 ifaces.Add(ifaceDescriptor);
59                 this.Log(LogLevel.Info, "Interface {0} attached", iface.MAC);
60             }
61         }
62 
DetachFrom(IMACInterface iface)63         public void DetachFrom(IMACInterface iface)
64         {
65             lock(innerLock)
66             {
67                 var descriptor = ifaces.SingleOrDefault(x => x.Interface == iface);
68                 if(descriptor == null)
69                 {
70                     this.Log(LogLevel.Warning, "Detaching mac interface that is currently not attached: {0}", iface.MAC);
71                     return;
72                 }
73 
74                 ifaces.Remove(descriptor);
75                 iface.FrameReady -= descriptor.Delegate;
76                 foreach(var m in macMapping.Where(x => x.Value == iface).ToArray())
77                 {
78                     macMapping.Remove(m.Key);
79                 }
80                 this.Log(LogLevel.Info, "Interface {0} detached", iface.MAC);
81             }
82         }
83 
EnablePromiscuousMode(IMACInterface iface)84         public void EnablePromiscuousMode(IMACInterface iface)
85         {
86             lock(innerLock)
87             {
88                 var descriptor = ifaces.SingleOrDefault(x => x.Interface == iface);
89                 if(descriptor == null)
90                 {
91                     throw new RecoverableException("The interface is not registered, you must connect it in order to change promiscuous mode settings");
92                 }
93                 descriptor.PromiscuousMode = true;
94                 this.Log(LogLevel.Info, "Promiscuous mode enabled for interace {0}", iface.MAC);
95             }
96         }
97 
DisablePromiscuousMode(IMACInterface iface)98         public void DisablePromiscuousMode(IMACInterface iface)
99         {
100             lock(innerLock)
101             {
102                 var descriptor = ifaces.SingleOrDefault(x => x.Interface == iface);
103                 if(descriptor == null)
104                 {
105                     throw new RecoverableException("The interface is not registered, you must connect it in order to change promiscuous mode settings");
106                 }
107                 if(!descriptor.PromiscuousMode)
108                 {
109                     throw new RecoverableException("The interface is not in promiscuous mode");
110                 }
111                 descriptor.PromiscuousMode = false;
112                 this.Log(LogLevel.Info, "Promiscuous mode disabled for interace {0}", iface.MAC);
113             }
114         }
115 
Start()116         public void Start()
117         {
118             Resume();
119         }
120 
Pause()121         public void Pause()
122         {
123             started = false;
124         }
125 
Resume()126         public void Resume()
127         {
128             started = true;
129         }
130 
131         public bool IsPaused => !started;
132 
133         public event Action<IExternal, IMACInterface, IMACInterface, byte[]> FrameTransmitted;
134         public event Action<IExternal, IMACInterface, byte[]> FrameProcessed;
135 
ForwardToReceiver(EthernetFrame frame, IMACInterface sender)136         private void ForwardToReceiver(EthernetFrame frame, IMACInterface sender)
137         {
138             this.Log(LogLevel.Noisy, "Received frame from interface {0}", sender.MAC);
139 
140             FrameProcessed?.Invoke(this, sender, frame.Bytes);
141 
142             if(!started)
143             {
144                 return;
145             }
146             lock(innerLock)
147             {
148                 var interestingIfaces = macMapping.TryGetValue(frame.DestinationMAC, out var destIface)
149                     ? ifaces.Where(x => (x.PromiscuousMode && x.Interface != sender) || x.Interface == destIface)
150                     : ifaces.Where(x => x.Interface != sender);
151 
152                 if(!TimeDomainsManager.Instance.TryGetVirtualTimeStamp(out var vts))
153                 {
154                     // it happens when sending from tap interface
155                     vts = new TimeStamp(default(TimeInterval), EmulationManager.ExternalWorld);
156                 }
157 
158                 foreach(var iface in interestingIfaces)
159                 {
160                     this.Log(LogLevel.Noisy, "Forwarding frame to interface {0}", iface.Interface.MAC);
161 
162                     if(iface.Machine == null)
163                     {
164                         iface.Interface.ReceiveFrame(frame.Clone());
165                         continue;
166                     }
167 
168                     iface.Machine.HandleTimeDomainEvent(iface.Interface.ReceiveFrame, frame.Clone(), vts, () =>
169                     {
170                         FrameTransmitted?.Invoke(this, sender, iface.Interface, frame.Bytes);
171                     });
172                 }
173             }
174 
175             // at the same we will potentially add current MAC address assigned to the source
176             lock(innerLock)
177             {
178                 macMapping[frame.SourceMAC] = sender;
179             }
180         }
181 
182         private bool started = true;
183 
184         private readonly object innerLock = new object();
185         private readonly HashSet<InterfaceDescriptor> ifaces = new HashSet<InterfaceDescriptor>();
186         private readonly Dictionary<MACAddress, IMACInterface> macMapping = new Dictionary<MACAddress, IMACInterface>();
187 
188         private class InterfaceDescriptor
189         {
190             public IMachine Machine;
191             public IMACInterface Interface;
192             public bool PromiscuousMode;
193             public Action<EthernetFrame> Delegate;
194 
GetHashCode()195             public override int GetHashCode()
196             {
197                 return Interface.GetHashCode();
198             }
199 
Equals(object obj)200             public override bool Equals(object obj)
201             {
202                 var objAsInterfaceDescriptor = obj as InterfaceDescriptor;
203                 return objAsInterfaceDescriptor != null && Interface.Equals(objAsInterfaceDescriptor.Interface);
204             }
205         }
206     }
207 }
208 
209