blob: 9168017f370314f7d4116112efca22287f06136f [file] [log] [blame]
Ari Saha89831742015-06-26 10:31:48 -07001/*
2 * Copyright 2015 AT&T Foundry
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 */
16package org.onosproject.aaa;
17
18import com.google.common.base.Strings;
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
22import org.apache.felix.scr.annotations.Modified;
23import org.apache.felix.scr.annotations.Property;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.onlab.packet.Ethernet;
27import org.onlab.packet.IPv4;
28import org.onlab.packet.Ip4Address;
29import org.onlab.packet.IpAddress;
30import org.onlab.packet.MacAddress;
31import org.onlab.packet.UDP;
32import org.onlab.packet.VlanId;
33import org.onlab.util.Tools;
34import org.onosproject.aaa.packet.EAP;
35import org.onosproject.aaa.packet.EAPEthernet;
36import org.onosproject.aaa.packet.EAPOL;
37import org.onosproject.aaa.packet.RADIUS;
38import org.onosproject.aaa.packet.RADIUSAttribute;
39import org.onosproject.cfg.ComponentConfigService;
40import org.onosproject.core.ApplicationId;
41import org.onosproject.core.CoreService;
42import org.onosproject.net.ConnectPoint;
43import org.onosproject.net.DeviceId;
44import org.onosproject.net.Host;
45import org.onosproject.net.PortNumber;
46import org.onosproject.net.flow.DefaultTrafficSelector;
47import org.onosproject.net.flow.DefaultTrafficTreatment;
48import org.onosproject.net.flow.FlowRuleService;
49import org.onosproject.net.flow.TrafficSelector;
50import org.onosproject.net.flow.TrafficTreatment;
51import org.onosproject.net.host.HostService;
52import org.onosproject.net.intent.IntentService;
53import org.onosproject.net.packet.DefaultOutboundPacket;
54import org.onosproject.net.packet.InboundPacket;
55import org.onosproject.net.packet.OutboundPacket;
56import org.onosproject.net.packet.PacketContext;
57import org.onosproject.net.packet.PacketPriority;
58import org.onosproject.net.packet.PacketProcessor;
59import org.onosproject.net.packet.PacketService;
60import org.onosproject.net.topology.TopologyService;
61import org.onosproject.xosintegration.VoltTenantService;
62import org.osgi.service.component.ComponentContext;
63import org.slf4j.Logger;
64
65import java.net.InetAddress;
66import java.net.UnknownHostException;
67import java.nio.ByteBuffer;
68import java.util.Collections;
69import java.util.Dictionary;
70import java.util.HashMap;
71import java.util.Iterator;
72import java.util.Map;
73import java.util.Optional;
74import java.util.Set;
75
76import static org.slf4j.LoggerFactory.getLogger;
77
78
79/**
80 * AAA application for Onos.
81 */
82@Component(immediate = true)
83public class AAA {
84 // a list of our dependencies :
85 // to register with ONOS as an application - described next
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected CoreService coreService;
88
89 // topology information
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected TopologyService topologyService;
92
93 // to receive Packet-in events that we'll respond to
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected PacketService packetService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected FlowRuleService flowService;
99
100 // to submit/withdraw intents for traffic manipulation
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 protected IntentService intentService;
103
104 // end host information
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected HostService hostService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected VoltTenantService voltTenantService;
110
111
112 // for verbose output
113 private final Logger log = getLogger(getClass());
114
115 // our application-specific event handler
116 private ReactivePacketProcessor processor = new ReactivePacketProcessor();
117
118 // our unique identifier
119 private ApplicationId appId;
120
121 // Map of state machines. Each state machine is represented by an
122 // unique identifier on the switch: dpid + port number
123 Map stateMachineMap = null;
124
125 // RADIUS server IP address
126 private static final String DEFAULT_RADIUS_IP = "192.168.1.10";
127 // NAS IP address
128 private static final String DEFAULT_NAS_IP = "192.168.1.11";
129 // RADIUS uplink port
130 private static final int DEFAULT_RADIUS_UPLINK = 2;
131 // RADIUS server shared secret
132 private static final String DEFAULT_RADIUS_SECRET = "ONOSecret";
133 //RADIUS MAC address
134 private static final String RADIUS_MAC_ADDRESS = "00:00:00:00:01:10";
135 //NAS MAC address
136 private static final String NAS_MAC_ADDRESS = "00:00:00:00:10:01";
137 //Radius Switch Id
138 private static final String DEFAULT_RADIUS_SWITCH = "of:5e3e486e73000187";
139 //Radius Port Number
140 private static final String DEFAULT_RADIUS_PORT = "5";
141
142 @Property(name = "radiusIpAddress", value = DEFAULT_RADIUS_IP,
143 label = "RADIUS IP Address")
144 private String radiusIpAddress = DEFAULT_RADIUS_IP;
145
146 @Property(name = "nasIpAddress", value = DEFAULT_NAS_IP,
147 label = "NAP IP Address")
148 private String nasIpAddress = DEFAULT_NAS_IP;
149
150 @Property(name = "radiusMacAddress", value = RADIUS_MAC_ADDRESS,
151 label = "RADIUS MAC Address")
152 private String radiusMacAddress = RADIUS_MAC_ADDRESS;
153
154 @Property(name = "nasMacAddress", value = NAS_MAC_ADDRESS,
155 label = "NAP MAC Address")
156 private String nasMacAddress = NAS_MAC_ADDRESS;
157
158 @Property(name = "radiusSecret", value = DEFAULT_RADIUS_SECRET,
159 label = "RADIUS shared secret")
160 private String radiusSecret = DEFAULT_RADIUS_SECRET;
161
162 @Property(name = "radiusSwitchId", value = DEFAULT_RADIUS_SWITCH,
163 label = "Radius switch")
164 private String radiusSwitch = DEFAULT_RADIUS_SWITCH;
165
166 @Property(name = "radiusPortNumber", value = DEFAULT_RADIUS_PORT,
167 label = "Radius port")
168 private String radiusPort = DEFAULT_RADIUS_PORT;
169
170 // Parsed RADIUS server IP address
171 protected InetAddress parsedRadiusIpAddress;
172
173 // Parsed NAS IP address
174 protected InetAddress parsedNasIpAddress;
175
176 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
177 protected ComponentConfigService cfgService;
178
179 @Modified
180 public void modified(ComponentContext context) {
181 Dictionary<?, ?> properties = context.getProperties();
182
183 String s = Tools.get(properties, "radiusIpAddress");
184 try {
185 parsedRadiusIpAddress = InetAddress.getByName(s);
186 radiusIpAddress = Strings.isNullOrEmpty(s) ? DEFAULT_RADIUS_IP : s;
187 } catch (UnknownHostException e) {
188 log.error("Invalid RADIUS IP address specification: {}", s);
189 }
190 try {
191 s = Tools.get(properties, "nasIpAddress");
192 parsedNasIpAddress = InetAddress.getByName(s);
193 nasIpAddress = Strings.isNullOrEmpty(s) ? DEFAULT_NAS_IP : s;
194 } catch (UnknownHostException e) {
195 log.error("Invalid NAS IP address specification: {}", s);
196 }
197
198 s = Tools.get(properties, "radiusMacAddress");
199 radiusMacAddress = Strings.isNullOrEmpty(s) ? RADIUS_MAC_ADDRESS : s;
200
201 s = Tools.get(properties, "nasMacAddress");
202 nasMacAddress = Strings.isNullOrEmpty(s) ? NAS_MAC_ADDRESS : s;
203
204 s = Tools.get(properties, "radiusSecret");
205 radiusSecret = Strings.isNullOrEmpty(s) ? DEFAULT_RADIUS_SECRET : s;
206
207 s = Tools.get(properties, "radiusSwitchId");
208 radiusSwitch = Strings.isNullOrEmpty(s) ? DEFAULT_RADIUS_SWITCH : s;
209
210 s = Tools.get(properties, "radiusPortNumber");
211 radiusPort = Strings.isNullOrEmpty(s) ? DEFAULT_RADIUS_PORT : s;
212 }
213
214 @Activate
215 public void activate(ComponentContext context) {
216 cfgService.registerProperties(getClass());
217 modified(context);
218 // "org.onosproject.aaa" is the FQDN of our app
219 appId = coreService.registerApplication("org.onosproject.aaa");
220 // register our event handler
221 packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
222 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
223
224 selector.matchEthType(EAPEthernet.TYPE_PAE);
225 packetService.requestPackets(selector.build(),
226 PacketPriority.CONTROL, appId);
227
228 // Instantiate the map of the state machines
229 Map<String, StateMachine> stateMachines = new HashMap<String, StateMachine>();
230 stateMachineMap = Collections.synchronizedMap(stateMachines);
231
232 hostService.startMonitoringIp(IpAddress.valueOf(radiusIpAddress));
233
234 }
235
236 @Deactivate
237 public void deactivate() {
238 cfgService.unregisterProperties(getClass(), false);
239 // de-register and null our handler
240 packetService.removeProcessor(processor);
241 processor = null;
242 }
243
244 // our handler defined as a private inner class
245
246 /**
247 * Packet processor responsible for forwarding packets along their paths.
248 */
249 private class ReactivePacketProcessor implements PacketProcessor {
250 @Override
251 public void process(PacketContext context) {
252
253 // Extract the original Ethernet frame from the packet information
254 InboundPacket pkt = context.inPacket();
255 Ethernet ethPkt = pkt.parsed();
256 if (ethPkt == null) {
257 return;
258 }
259 //identify if incoming packet comes from supplicant (EAP) or RADIUS
260 switch (ethPkt.getEtherType()) {
261 case (short) 0x888e:
262 handleSupplicantPacket(ethPkt, context);
263 break;
264 case 0x800:
265 IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
266 Ip4Address srcIp = Ip4Address.valueOf(ipv4Packet.getSourceAddress());
267 Ip4Address radiusIp4Address = Ip4Address.valueOf(parsedRadiusIpAddress);
268 if (srcIp.equals(radiusIp4Address) && ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
269 // TODO: check for port as well when it's configurable
270 UDP udpPacket = (UDP) ipv4Packet.getPayload();
271 // RADIUS radiusPacket = (RADIUS) udpPacket.getPayload();
272 byte[] datagram = udpPacket.getPayload().serialize();
273 RADIUS radiusPacket = new RADIUS();
274 radiusPacket = (RADIUS) radiusPacket.deserialize(datagram, 0, datagram.length);
275 handleRadiusPacket(radiusPacket);
276 }
277 break;
278 default:
279 return;
280 }
281 }
282
283
284 /**
285 * Handle PAE packets (supplicant).
286 * @param ethPkt Ethernet packet coming from the supplicant.
287 */
288 private void handleSupplicantPacket(Ethernet ethPkt, PacketContext context) {
289 // Where does it come from?
290 MacAddress srcMAC = ethPkt.getSourceMAC();
291
292 DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
293 PortNumber portNumber = context.inPacket().receivedFrom().port();
294 String sessionId = deviceId.toString() + portNumber.toString();
295 StateMachine stateMachine = getStateMachine(sessionId);
296 //Reserialize the data of the eth packet into our EAPOL format
297 // this code will go once it is in the onos repository.
298 byte[] bullshit = ethPkt.getPayload().serialize();
299 EAPOL eapol = (EAPOL) new EAPOL().deserialize(bullshit, 0, bullshit.length);
300
301 switch (eapol.getEapolType()) {
302 case EAPOL.EAPOL_START:
303 try {
304 stateMachine.start();
305 stateMachine.supplicantConnectpoint = context.inPacket().receivedFrom();
306
307 //send an EAP Request/Identify to the supplicant
308 EAP eapPayload = new EAP(EAP.REQUEST, stateMachine.getIdentifier(), EAP.ATTR_IDENTITY, null);
309 Ethernet eth = EAPOL.buildEapolResponse(srcMAC, MacAddress.valueOf(1L),
310 ethPkt.getVlanID(), EAPOL.EAPOL_PACKET,
311 eapPayload);
312 stateMachine.supplicantAddress = srcMAC;
313 stateMachine.vlanId = ethPkt.getVlanID();
314
315 this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint);
316 } catch (StateMachineException e) {
317 e.printStackTrace();
318 }
319
320 break;
321 case EAPOL.EAPOL_PACKET:
322 //check if this is a Response/Idenfity or a Response/TLS
323 EAP eapPacket = (EAP) eapol.getPayload();
324
325 byte dataType = eapPacket.getDataType();
326 switch (dataType) {
327 case EAP.ATTR_IDENTITY:
328 try {
329 //request id access to RADIUS
330 RADIUS radiusPayload = new RADIUS(RADIUS.RADIUS_CODE_ACCESS_REQUEST,
331 eapPacket.getIdentifier());
332 radiusPayload.setIdentifier(stateMachine.getIdentifier());
333 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME,
334 eapPacket.getData());
335 stateMachine.setUsername(eapPacket.getData());
336 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
337 AAA.this.parsedNasIpAddress.getAddress());
338
339 radiusPayload.encapsulateMessage(eapPacket);
340
341 // set Request Authenticator in StateMachine
342 stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
343 radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
344 sendRadiusMessage(radiusPayload);
345
346 //change the state to "PENDING"
347 stateMachine.requestAccess();
348 } catch (StateMachineException e) {
349 e.printStackTrace();
350 }
351 break;
352 case EAP.ATTR_MD5:
353 //verify if the EAP identifier corresponds to the challenge identifier from the client state
354 //machine.
355 if (eapPacket.getIdentifier() == stateMachine.getChallengeIdentifier()) {
356 //send the RADIUS challenge response
357 RADIUS radiusPayload = new RADIUS(RADIUS.RADIUS_CODE_ACCESS_REQUEST,
358 eapPacket.getIdentifier());
359 radiusPayload.setIdentifier(stateMachine.getChallengeIdentifier());
360 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME,
361 stateMachine.getUsername());
362 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
363 AAA.this.parsedNasIpAddress.getAddress());
364
365 radiusPayload.encapsulateMessage(eapPacket);
366
367 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
368 stateMachine.getChallengeState());
369 radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
370 sendRadiusMessage(radiusPayload);
371 }
372 break;
373 case EAP.ATTR_TLS:
374 try {
375 //request id access to RADIUS
376 RADIUS radiusPayload = new RADIUS(RADIUS.RADIUS_CODE_ACCESS_REQUEST,
377 eapPacket.getIdentifier());
378 radiusPayload.setIdentifier(stateMachine.getIdentifier());
379 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME,
380 stateMachine.getUsername());
381 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
382 AAA.this.parsedNasIpAddress.getAddress());
383
384 radiusPayload.encapsulateMessage(eapPacket);
385
386 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
387 stateMachine.getChallengeState());
388 stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
389
390 radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
391
392 sendRadiusMessage(radiusPayload);
393 // TODO: this gets called on every fragment, should only be called at TLS-Start
394 stateMachine.requestAccess();
395 } catch (StateMachineException e) {
396 e.printStackTrace();
397 }
398 break;
399 default:
400 return;
401 }
402 break;
403 default:
404 return;
405 }
406 }
407
408 /**
409 * Handle RADIUS packets.
410 * @param radiusPacket RADIUS packet coming from the RADIUS server.
411 */
412 private void handleRadiusPacket(RADIUS radiusPacket) {
413 StateMachine stateMachine = getStateMachineById(radiusPacket.getIdentifier());
414 if (stateMachine == null) {
415 log.error("Invalid session identifier, exiting...");
416 return;
417 }
418
419 byte[] eapMessage = null;
420 EAP eapPayload = new EAP();
421 Ethernet eth = null;
422 switch (radiusPacket.getCode()) {
423 case RADIUS.RADIUS_CODE_ACCESS_CHALLENGE:
424 byte[] challengeState = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE).getValue();
425 eapPayload = radiusPacket.decapsulateMessage();
426 stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
427 eth = EAPOL.buildEapolResponse(stateMachine.supplicantAddress,
428 MacAddress.valueOf(1L), stateMachine.vlanId, EAPOL.EAPOL_PACKET, eapPayload);
429 this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint);
430 break;
431 case RADIUS.RADIUS_CODE_ACCESS_ACCEPT:
432 try {
433 //send an EAPOL - Success to the supplicant.
434 eapMessage =
435 radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE).getValue();
436 eapPayload = new EAP();
437 eapPayload = (EAP) eapPayload.deserialize(eapMessage, 0, eapMessage.length);
438 eth = EAPOL.buildEapolResponse(stateMachine.supplicantAddress,
439 MacAddress.valueOf(1L), stateMachine.vlanId, EAPOL.EAPOL_PACKET, eapPayload);
440 this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint);
441
442 stateMachine.authorizeAccess();
443 } catch (StateMachineException e) {
444 e.printStackTrace();
445 }
446 break;
447 case RADIUS.RADIUS_CODE_ACCESS_REJECT:
448 try {
449 stateMachine.denyAccess();
450 } catch (StateMachineException e) {
451 e.printStackTrace();
452 }
453 break;
454 default:
455 log.warn("Unknown RADIUS message received with code: {}", radiusPacket.getCode());
456 }
457 }
458
459 private StateMachine getStateMachineById(byte identifier) {
460 StateMachine stateMachine = null;
461 Set stateMachineSet = stateMachineMap.entrySet();
462
463 synchronized (stateMachineMap) {
464 Iterator itr = stateMachineSet.iterator();
465 while (itr.hasNext()) {
466 Map.Entry entry = (Map.Entry) itr.next();
467 stateMachine = (StateMachine) entry.getValue();
468 if (identifier == stateMachine.getIdentifier()) {
469 //the state machine has already been created for this session session
470 stateMachine = (StateMachine) entry.getValue();
471 break;
472 }
473 }
474 }
475
476 return stateMachine;
477 }
478
479 private StateMachine getStateMachine(String sessionId) {
480 StateMachine stateMachine = null;
481 Set stateMachineSet = stateMachineMap.entrySet();
482
483 synchronized (stateMachineMap) {
484 Iterator itr = stateMachineSet.iterator();
485 while (itr.hasNext()) {
486
487 Map.Entry entry = (Map.Entry) itr.next();
488 if (sessionId.equals(entry.getKey())) {
489 //the state machine has already been created for this session session
490 stateMachine = (StateMachine) entry.getValue();
491 break;
492 }
493 }
494 }
495
496 if (stateMachine == null) {
497 stateMachine = new StateMachine(sessionId, voltTenantService);
498 stateMachineMap.put(sessionId, stateMachine);
499 }
500
501 return stateMachine;
502 }
503
504 private void sendRadiusMessage(RADIUS radiusMessage) {
505 Set<Host> hosts = hostService.getHostsByIp(IpAddress.valueOf(radiusIpAddress));
506 Optional<Host> odst = hosts.stream().filter(h -> h.vlan().toShort() == VlanId.UNTAGGED).findFirst();
507
508 Host dst;
509 if (!odst.isPresent()) {
510 log.info("Radius server {} is not present", radiusIpAddress);
511 return;
512 } else {
513 dst = odst.get();
514 }
515
516 UDP udp = new UDP();
517 IPv4 ip4Packet = new IPv4();
518 Ethernet ethPkt = new Ethernet();
519 radiusMessage.setParent(udp);
520 udp.setDestinationPort((short) 1812);
521 udp.setSourcePort((short) 1812); // TODO: make this configurable
522 udp.setPayload(radiusMessage);
523 udp.setParent(ip4Packet);
524 ip4Packet.setSourceAddress(AAA.this.nasIpAddress);
525 ip4Packet.setDestinationAddress(AAA.this.radiusIpAddress);
526 ip4Packet.setProtocol(IPv4.PROTOCOL_UDP);
527 ip4Packet.setPayload(udp);
528 ip4Packet.setParent(ethPkt);
529 ethPkt.setDestinationMACAddress(radiusMacAddress);
530 ethPkt.setSourceMACAddress(nasMacAddress);
531 ethPkt.setEtherType(Ethernet.TYPE_IPV4);
532 ethPkt.setPayload(ip4Packet);
533
534 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
535 .setOutput(PortNumber.portNumber(Integer.parseInt(radiusPort))).build();
536 OutboundPacket packet = new DefaultOutboundPacket(DeviceId.deviceId(radiusSwitch),
537 treatment, ByteBuffer.wrap(ethPkt.serialize()));
538 packetService.emit(packet);
539
540 }
541
542
543 /**
544 * Send the ethernet packet to the supplicant.
545 * @param ethernetPkt the ethernet packet
546 */
547 private void sendPacketToSupplicant(Ethernet ethernetPkt, ConnectPoint connectPoint) {
548 TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
549 OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
550 treatment, ByteBuffer.wrap(ethernetPkt.serialize()));
551 packetService.emit(packet);
552 }
553
554 }
555
556}