blob: a5246c0d89de20ba81222105d9906d1c08c744ba [file] [log] [blame]
Jonathan Hartc7ca35d2013-06-25 20:54:25 +12001package net.onrc.onos.ofcontroller.proxyarp;
2
3import java.io.IOException;
4import java.net.InetAddress;
5import java.net.UnknownHostException;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.Set;
12
13import net.floodlightcontroller.core.FloodlightContext;
14import net.floodlightcontroller.core.IFloodlightProviderService;
15import net.floodlightcontroller.core.IOFMessageListener;
16import net.floodlightcontroller.core.IOFSwitch;
17import net.floodlightcontroller.packet.ARP;
18import net.floodlightcontroller.packet.Ethernet;
19import net.floodlightcontroller.topology.ITopologyService;
20import net.floodlightcontroller.topology.NodePortTuple;
Jonathan Hart8ec133c2013-06-26 15:25:18 +120021import net.floodlightcontroller.util.MACAddress;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120022
23import org.openflow.protocol.OFMessage;
24import org.openflow.protocol.OFPacketIn;
25import org.openflow.protocol.OFPacketOut;
26import org.openflow.protocol.OFPort;
27import org.openflow.protocol.OFType;
28import org.openflow.protocol.action.OFAction;
29import org.openflow.protocol.action.OFActionOutput;
Jonathan Hart8ec133c2013-06-26 15:25:18 +120030import org.openflow.util.HexString;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120031import org.slf4j.Logger;
32import org.slf4j.LoggerFactory;
33
34public class ProxyArpManager implements IOFMessageListener {
35 private static Logger log = LoggerFactory.getLogger(ProxyArpManager.class);
36
37 private final long ARP_ENTRY_TIMEOUT = 600000; //ms (== 5 mins)
38
39 protected IFloodlightProviderService floodlightProvider;
40 protected ITopologyService topology;
41
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120042 protected Map<InetAddress, ArpTableEntry> arpTable;
43
44 public ProxyArpManager(IFloodlightProviderService floodlightProvider,
45 ITopologyService topology){
46 this.floodlightProvider = floodlightProvider;
47 this.topology = topology;
48
49 arpTable = new HashMap<InetAddress, ArpTableEntry>();
50 }
51
52 @Override
53 public String getName() {
54 return "ProxyArpManager";
55 }
56
57 @Override
58 public boolean isCallbackOrderingPrereq(OFType type, String name) {
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120059 return false;
60 }
61
62 @Override
63 public boolean isCallbackOrderingPostreq(OFType type, String name) {
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120064 return false;
65 }
66
67 @Override
68 public Command receive(
69 IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
70
71 if (msg.getType() != OFType.PACKET_IN){
72 return Command.CONTINUE;
73 }
74
75 OFPacketIn pi = (OFPacketIn) msg;
76
77 Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
78 IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
79
80 if (eth.getEtherType() == Ethernet.TYPE_ARP){
81
82
83 ARP arp = (ARP) eth.getPayload();
84
85 if (arp.getOpCode() == ARP.OP_REQUEST) {
Jonathan Hart8ec133c2013-06-26 15:25:18 +120086 log.debug("ARP request received for {}", bytesToStringAddr(arp.getTargetProtocolAddress()));
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120087
88 byte[] mac = lookupArpTable(arp.getTargetProtocolAddress());
89
90 if (mac == null){
91 //Mac address is not in our arp table.
92 //We need to flood the request out edge ports
93 Set<NodePortTuple> broadcastPorts = topology.getBroadcastDomainPorts();
94 log.debug("size {}", broadcastPorts.size());
95 for (NodePortTuple nodePort : broadcastPorts){
96 log.debug("Port {}", nodePort);
97 }
98 broadcastArpRequestOutEdge(pi, sw.getId(), pi.getInPort());
99 }
100 else {
101 //We know the address, so send a reply
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200102 log.debug("Sending reply of {}", MACAddress.valueOf(mac).toString());
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200103 sendArpReply(arp, pi, mac, sw);
104 }
105 }
106 else if (arp.getOpCode() == ARP.OP_REPLY) {
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200107 log.debug("ARP reply recieved for {}", bytesToStringAddr(arp.getSenderProtocolAddress()));
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200108
109 log.debug("arp table {}", arpTable.keySet());
110
111 updateArpTable(arp);
112 }
113 }
114
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200115 return Command.CONTINUE;
116 }
117
118 private synchronized byte[] lookupArpTable(byte[] ipAddress){
119 InetAddress addr;
120 try {
121 addr = InetAddress.getByAddress(ipAddress);
122 } catch (UnknownHostException e) {
123 log.warn("Unable to create InetAddress", e);
124 return null;
125 }
126
127 ArpTableEntry arpEntry = arpTable.get(addr);
128
129 if (arpEntry == null){
130 return null;
131 }
132
133 if (System.currentTimeMillis() - arpEntry.getTimeLastSeen()
134 > ARP_ENTRY_TIMEOUT){
135 //Entry has timed out so we'll remove it and return null
136 arpTable.remove(addr);
137 return null;
138 }
139
140 return arpEntry.getMacAddress();
141 }
142
143 private synchronized void updateArpTable(ARP arp){
144 InetAddress addr;
145 try {
146 addr = InetAddress.getByAddress(arp.getSenderProtocolAddress());
147 } catch (UnknownHostException e) {
148 log.warn("Unable to create InetAddress", e);
149 return;
150 }
151
152 ArpTableEntry arpEntry = arpTable.get(addr);
153
154 if (arpEntry != null
155 && arpEntry.getMacAddress() == arp.getSenderHardwareAddress()){
156 arpEntry.setTimeLastSeen(System.currentTimeMillis());
157 }
158 else {
159 arpTable.put(addr,
160 new ArpTableEntry(arp.getSenderHardwareAddress(),
161 System.currentTimeMillis()));
162 }
163 }
164
165 private void broadcastArpRequestOutEdge(OFPacketIn pi, long inSwitch, short inPort){
166 for (IOFSwitch sw : floodlightProvider.getSwitches().values()){
167 Collection<Short> enabledPorts = sw.getEnabledPortNumbers();
168 Set<Short> linkPorts = topology.getPortsWithLinks(sw.getId());
169
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200170 if (linkPorts == null){
171 //I think this means the switch isn't known to topology yet.
172 //Maybe it only just joined.
173 continue;
174 }
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200175
176 OFPacketOut po = new OFPacketOut();
177 po.setInPort(OFPort.OFPP_NONE)
178 .setBufferId(-1)
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200179 .setPacketData(pi.getPacketData());
180
181 List<OFAction> actions = new ArrayList<OFAction>();
182
183 for (short portNum : enabledPorts){
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200184 if (linkPorts.contains(portNum) ||
185 (sw.getId() == inSwitch && portNum == inPort)){
186 //If this port isn't an edge port or is the ingress port
187 //for the ARP, don't broadcast out it
188 continue;
189 }
190
191 actions.add(new OFActionOutput(portNum));
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200192 log.debug("Broadcasting out {}/{}", HexString.toHexString(sw.getId()), portNum);
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200193 }
194
195 po.setActions(actions);
196 short actionsLength = (short) (actions.size() * OFActionOutput.MINIMUM_LENGTH);
197 po.setActionsLength(actionsLength);
198 po.setLengthU(OFPacketOut.MINIMUM_LENGTH + actionsLength
199 + pi.getPacketData().length);
200
201 List<OFMessage> msgList = new ArrayList<OFMessage>();
202 msgList.add(po);
203
204 try {
205 sw.write(msgList, null);
206 sw.flush();
207 } catch (IOException e) {
208 log.error("Failure writing packet out to switch", e);
209 }
210 }
211 }
212
213 private void sendArpReply(ARP arpRequest, OFPacketIn pi, byte[] macRequested, IOFSwitch sw){
214 ARP arpReply = new ARP();
215 arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
216 .setProtocolType(ARP.PROTO_TYPE_IP)
217 .setHardwareAddressLength((byte)Ethernet.DATALAYER_ADDRESS_LENGTH)
218 .setProtocolAddressLength((byte)4) //can't find the constant anywhere
219 .setOpCode(ARP.OP_REPLY)
220 .setSenderHardwareAddress(macRequested)
221 .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
222 .setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
223 .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
224
225 Ethernet eth = new Ethernet();
226 eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
227 .setSourceMACAddress(macRequested)
228 .setEtherType(Ethernet.TYPE_ARP)
229 .setPayload(arpReply);
230
231 List<OFAction> actions = new ArrayList<OFAction>();
232 actions.add(new OFActionOutput(pi.getInPort()));
233
234 OFPacketOut po = new OFPacketOut();
235 po.setInPort(OFPort.OFPP_NONE)
236 .setBufferId(-1)
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200237 .setPacketData(eth.serialize())
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200238 .setActions(actions)
239 .setActionsLength((short)OFActionOutput.MINIMUM_LENGTH)
240 .setLengthU(OFPacketOut.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH
241 + po.getPacketData().length);
242
243 List<OFMessage> msgList = new ArrayList<OFMessage>();
244 msgList.add(po);
245
246 try {
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200247 log.debug("Sending ARP reply to {}/{}", HexString.toHexString(sw.getId()), pi.getInPort());
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200248 sw.write(msgList, null);
249 sw.flush();
250 } catch (IOException e) {
251 log.warn("Failure writing packet out to switch", e);
252 }
253 }
Jonathan Hartc824ad02013-07-03 15:58:45 +1200254
255 //TODO this should be put somewhere more central. I use it in BgpRoute as well.
256 //We need a HexString.toHexString() equivalent.
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200257 private String bytesToStringAddr(byte[] bytes){
258 InetAddress addr;
259 try {
260 addr = InetAddress.getByAddress(bytes);
261 } catch (UnknownHostException e) {
Jonathan Hartc824ad02013-07-03 15:58:45 +1200262 log.warn(" ", e);
Jonathan Hart8ec133c2013-06-26 15:25:18 +1200263 return "";
264 }
265 if (addr == null) return "";
266 else return addr.getHostAddress();
267 }
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200268}