blob: c17736ed19df8239dda4b5dad1e165e46bd7908c [file] [log] [blame]
Satish Ke107e662015-09-21 19:00:17 +05301/*
2 * Copyright 2015 Open Networking Laboratory
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 org.onosproject.bgp.controller.impl;
18
Shashikanth VH6de20d32015-10-09 12:04:13 +053019import java.io.IOException;
20import java.net.InetSocketAddress;
21import java.net.SocketAddress;
22import java.nio.channels.ClosedChannelException;
23import java.util.Date;
24import java.util.List;
25import java.util.concurrent.RejectedExecutionException;
26
27import org.jboss.netty.channel.Channel;
28import org.jboss.netty.channel.ChannelHandlerContext;
29import org.jboss.netty.channel.ChannelStateEvent;
30import org.jboss.netty.channel.ExceptionEvent;
31import org.jboss.netty.channel.MessageEvent;
Satish Ke107e662015-09-21 19:00:17 +053032import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
Shashikanth VH6de20d32015-10-09 12:04:13 +053033import org.jboss.netty.handler.timeout.ReadTimeoutException;
34import org.jboss.netty.handler.timeout.ReadTimeoutHandler;
35import org.onlab.packet.IpAddress;
36import org.onosproject.bgp.controller.BGPCfg;
37import org.onosproject.bgp.controller.BGPId;
38import org.onosproject.bgp.controller.BGPPeer;
39import org.onosproject.bgp.controller.BGPPeerCfg;
40import org.onosproject.bgp.controller.impl.BGPControllerImpl.BGPPeerManager;
41import org.onosproject.bgpio.exceptions.BGPParseException;
42import org.onosproject.bgpio.protocol.BGPMessage;
43//import org.onosproject.bgpio.protocol.BGPOpenMsg;
44import org.onosproject.bgpio.protocol.BGPType;
45import org.onosproject.bgpio.protocol.BGPVersion;
46import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
Satish Ke107e662015-09-21 19:00:17 +053048
49/**
50 * Channel handler deals with the bgp peer connection and dispatches messages from peer to the appropriate locations.
51 */
52class BGPChannelHandler extends IdleStateAwareChannelHandler {
53
Shashikanth VH6de20d32015-10-09 12:04:13 +053054 private static final Logger log = LoggerFactory.getLogger(BGPChannelHandler.class);
55
56 static final int BGP_MAX_KEEPALIVE_INTERVAL = 3;
57 private BGPPeer bgpPeer;
58 private BGPId thisbgpId;
59 Channel channel;
60 private BGPKeepAliveTimer keepAliveTimer = null;
61 private short peerHoldTime = 0;
62 private short negotiatedHoldTime = 0;
63 private short peerAsNum;
64 private int peerIdentifier;
65 private BGPPacketStatsImpl bgpPacketStats;
66 static final int MAX_WRONG_COUNT_PACKET = 5;
67
68 // State needs to be volatile because the HandshakeTimeoutHandler
69 // needs to check if the handshake is complete
70 private volatile ChannelState state;
71
72 // When a bgp peer with a ip addresss is found (i.e we already have a
73 // connected peer with the same ip), the new peer is immediately
74 // disconnected. At that point netty callsback channelDisconnected() which
75 // proceeds to cleaup peer state - we need to ensure that it does not cleanup
76 // peer state for the older (still connected) peer
77 private volatile Boolean duplicateBGPIdFound;
78 // Indicates the bgp version used by this bgp peer
79 protected BGPVersion bgpVersion;
80 private BGPControllerImpl bgpControllerImpl;
81 private BGPPeerManager peerManager;
82 private InetSocketAddress inetAddress;
83 private IpAddress ipAddress;
84 private SocketAddress address;
85 private String peerAddr;
86 private BGPCfg bgpconfig;
87
Satish Ke107e662015-09-21 19:00:17 +053088 /**
89 * Create a new unconnected BGPChannelHandler.
90 *
91 * @param bgpCtrlImpl bgp controller implementation object
92 */
93 BGPChannelHandler(BGPControllerImpl bgpCtrlImpl) {
Shashikanth VH6de20d32015-10-09 12:04:13 +053094 this.bgpControllerImpl = bgpCtrlImpl;
95 this.peerManager = bgpCtrlImpl.getPeerManager();
96 this.state = ChannelState.IDLE;
97 this.duplicateBGPIdFound = Boolean.FALSE;
98 this.bgpPacketStats = new BGPPacketStatsImpl();
99 this.bgpconfig = bgpCtrlImpl.getConfig();
Satish Ke107e662015-09-21 19:00:17 +0530100 }
Shashikanth VH6de20d32015-10-09 12:04:13 +0530101
102 // To disconnect peer session.
103 public void disconnectPeer() {
104 bgpPeer.disconnectPeer();
105 }
106
107 // *************************
108 // Channel State Machine
109 // *************************
110
111 /**
112 * The state machine for handling the peer/channel state. All state transitions should happen from within the state
113 * machine (and not from other parts of the code)
114 */
115 enum ChannelState {
116 /**
117 * Initial state before channel is connected.
118 */
119 IDLE(false) {
120
121 },
122
123 OPENSENT(false) {
124 @Override
125 void processBGPMessage(BGPChannelHandler h, BGPMessage m) throws IOException, BGPParseException {
126 log.debug("message received in OPENSENT state");
127 // check for OPEN message
128 if (m.getType() != BGPType.OPEN) {
129 // When the message type is not keep alive message increment the wrong packet statistics
130 h.processUnknownMsg();
131 log.debug("Message is not OPEN message");
132 } else {
133 log.debug("Sending keep alive message in OPENSENT state");
134 h.bgpPacketStats.addInPacket();
135
136 // TODO: initialize openmessage BGPOpenMsg pOpenmsg = (BGPOpenMsg) m;
137 // TODO: initialize identifier from open messgae h.peerIdentifier = pOpenmsg.getBgpId();
138
139 // validate capabilities and open msg
140 if (h.openMsgValidation(h)) {
141 log.debug("Sending handshake OPEN message");
142
143 /*
144 * RFC 4271, section 4.2: Upon receipt of an OPEN message, a BGP speaker MUST calculate the
145 * value of the Hold Timer by using the smaller of its configured Hold Time and the Hold Time
146 * received in the OPEN message
147 */
148 // TODO: initialize holdtime from open message h.peerHoldTime = pOpenmsg.getHoldTime();
149 if (h.peerHoldTime < h.bgpconfig.getHoldTime()) {
150 h.channel.getPipeline().replace("holdTime",
151 "holdTime",
152 new ReadTimeoutHandler(BGPPipelineFactory.TIMER,
153 h.peerHoldTime));
154 }
155
156 log.info("Hold Time : " + h.peerHoldTime);
157
158 // TODO: get AS number for open message update AS number
159 }
160
161 // Send keepalive message to peer.
162 h.sendKeepAliveMessage();
163 h.bgpPacketStats.addOutPacket();
164 h.setState(OPENCONFIRM);
165 h.bgpconfig.setPeerConnState(h.peerAddr, BGPPeerCfg.State.OPENCONFIRM);
166 }
167 }
168 },
169
170 OPENWAIT(false) {
171 @Override
172 void processBGPMessage(BGPChannelHandler h, BGPMessage m) throws IOException, BGPParseException {
173 log.debug("Message received in OPEN WAIT State");
174
175 // check for open message
176 if (m.getType() != BGPType.OPEN) {
177 // When the message type is not open message increment the wrong packet statistics
178 h.processUnknownMsg();
179 log.debug("Message is not OPEN message");
180 } else {
181 h.bgpPacketStats.addInPacket();
182
183 // TODO: initialize open message BGPOpenMsg pOpenmsg = (BGPOpenMsg) m;
184
185 // Validate open message
186 if (h.openMsgValidation(h)) {
187 log.debug("Sending handshake OPEN message");
188
189 /*
190 * RFC 4271, section 4.2: Upon receipt of an OPEN message, a BGP speaker MUST calculate the
191 * value of the Hold Timer by using the smaller of its configured Hold Time and the Hold Time
192 * received in the OPEN message
193 */
194 // TODO: get hold time from open message h.peerHoldTime = pOpenmsg.getHoldTime();
195 if (h.peerHoldTime < h.bgpconfig.getHoldTime()) {
196 h.channel.getPipeline().replace("holdTime",
197 "holdTime",
198 new ReadTimeoutHandler(BGPPipelineFactory.TIMER,
199 h.peerHoldTime));
200 }
201
202 log.debug("Hold Time : " + h.peerHoldTime);
203
204 //TODO: update AS number form open messsage update AS number
205
206 h.sendHandshakeOpenMessage();
207 h.bgpPacketStats.addOutPacket();
208 h.setState(OPENCONFIRM);
209 }
210 }
211 }
212 },
213
214 OPENCONFIRM(false) {
215 @Override
216 void processBGPMessage(BGPChannelHandler h, BGPMessage m) throws IOException, BGPParseException {
217 log.debug("Message received in OPENCONFIRM state");
218 // check for keep alive message
219 if (m.getType() != BGPType.KEEP_ALIVE) {
220 // When the message type is not keep alive message handle the wrong packet
221 h.processUnknownMsg();
222 log.debug("Message is not KEEPALIVE message");
223 } else {
224
225 // Set the peer connected status
226 h.bgpPacketStats.addInPacket();
227 log.debug("Sending keep alive message in OPENCONFIRM state");
228
229 final InetSocketAddress inetAddress = (InetSocketAddress) h.address;
230 h.thisbgpId = BGPId.bgpId(IpAddress.valueOf(inetAddress.getAddress()));
231
232 h.bgpPeer = h.peerManager.getBGPPeerInstance(h.thisbgpId, h.bgpVersion, h.bgpPacketStats);
233 // set the status fo bgp as connected
234 h.bgpPeer.setConnected(true);
235 h.bgpPeer.setChannel(h.channel);
236
237 // set specific parameters to bgp peer
238 h.bgpPeer.setBgpPeerVersion(h.bgpVersion);
239 h.bgpPeer.setBgpPeerASNum(h.peerAsNum);
240 h.bgpPeer.setBgpPeerHoldTime(h.peerHoldTime);
241 h.bgpPeer.setBgpPeerIdentifier(h.peerIdentifier);
242
243 h.negotiatedHoldTime = (h.peerHoldTime < h.bgpconfig.getHoldTime()) ? h.peerHoldTime : h.bgpconfig
244 .getHoldTime();
245 h.bgpPeer.setNegotiatedHoldTime(h.negotiatedHoldTime);
246 /*
247 * RFC 4271, When an OPEN message is received, sends a KEEPALIVE message, If the negotiated hold
248 * time value is zero, then the HoldTimer and KeepaliveTimer are not started. A reasonable maximum
249 * time between KEEPALIVE messages would be one third of the Hold Time interval.
250 */
251 h.sendKeepAliveMessage();
252
253 if (h.negotiatedHoldTime != 0) {
254 h.keepAliveTimer
255 = new BGPKeepAliveTimer(h, (h.negotiatedHoldTime / BGP_MAX_KEEPALIVE_INTERVAL));
256 }
257
258 h.bgpPacketStats.addOutPacket();
259
260 // set the state handshake completion.
261 h.setHandshakeComplete(true);
262
263 if (!h.peerManager.addConnectedPeer(h.thisbgpId, h.bgpPeer)) {
264 /*
265 * RFC 4271, Section 6.8, Based on the value of the BGP identifier, a convention is established
266 * for detecting which BGP connection is to be preserved when a collision occurs. The convention
267 * is to compare the BGP Identifiers of the peers involved in the collision and to retain only
268 * the connection initiated by the BGP speaker with the higher-valued BGP Identifier..
269 */
270 // TODO: Connection collision handling.
271 disconnectDuplicate(h);
272 } else {
273 h.setState(ESTABLISHED);
274 h.bgpconfig.setPeerConnState(h.peerAddr, BGPPeerCfg.State.ESTABLISHED);
275 }
276 }
277 }
278 },
279
280 ESTABLISHED(true) {
281 @Override
282 void processBGPMessage(BGPChannelHandler h, BGPMessage m) throws IOException, BGPParseException {
283 log.debug("Message received in established state " + m.getType());
284 // dispatch the message
285 h.dispatchMessage(m);
286 }
287 };
288
289 private boolean handshakeComplete;
290
291 ChannelState(boolean handshakeComplete) {
292 this.handshakeComplete = handshakeComplete;
293 }
294
295 /**
296 * Is this a state in which the handshake has completed?
297 *
298 * @return true if the handshake is complete
299 */
300 public boolean isHandshakeComplete() {
301 return this.handshakeComplete;
302 }
303
304 /**
305 * Disconnect duplicate peer connection.
306 *
307 * @param h channel handler
308 */
309 protected void disconnectDuplicate(BGPChannelHandler h) {
310 log.error("Duplicated BGP IP or incompleted cleanup - " + "" + "disconnecting channel {}",
311 h.getPeerInfoString());
312 h.duplicateBGPIdFound = Boolean.TRUE;
313 h.channel.disconnect();
314 }
315
316 // set handshake completion status
317 public void setHandshakeComplete(boolean handshakeComplete) {
318 this.handshakeComplete = handshakeComplete;
319 }
320
321 void processBGPMessage(BGPChannelHandler bgpChannelHandler, BGPMessage pm)
322 throws IOException, BGPParseException {
323 // TODO Auto-generated method stub
324 log.debug("BGP message stub");
325 }
326
327 }
328
329 // *************************
330 // Channel handler methods
331 // *************************
332
333 @Override
334 public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
335
336 channel = e.getChannel();
337 log.info("BGP connected from {}", channel.getRemoteAddress());
338
339 address = channel.getRemoteAddress();
340 if (!(address instanceof InetSocketAddress)) {
341 throw new IOException("Invalid peer connection.");
342 }
343
344 // Connection should establish only if local ip and Autonomous system number is configured.
345 if (bgpconfig.getState() != BGPCfg.State.IP_AS_CONFIGURED) {
346 channel.close();
347 log.info("BGP local AS and router ID not configured");
348 return;
349 }
350
351 inetAddress = (InetSocketAddress) address;
352 ipAddress = IpAddress.valueOf(inetAddress.getAddress());
353 peerAddr = ipAddress.toString();
354
355 // if peer is not configured disconnect session
356 if (!bgpconfig.isPeerConfigured(peerAddr)) {
357 log.debug("Peer is not configured {}", peerAddr);
358 channel.close();
359 return;
360 }
361
362 // if connection is already established close channel
363 if (peerManager.isPeerConnected(peerAddr)) {
364 log.debug("Duplicate connection received, peer {}", peerAddr);
365 channel.close();
366 return;
367 }
368
369 if (null != channel.getPipeline().get("PassiveHandler")) {
370 log.info("BGP handle connection request from peer");
371 // Wait for open message from bgp peer
372 setState(ChannelState.OPENWAIT);
373 } else if (null != channel.getPipeline().get("ActiveHandler")) {
374 log.info("BGP handle connection response from peer");
375
376 sendHandshakeOpenMessage();
377 bgpPacketStats.addOutPacket();
378 setState(ChannelState.OPENSENT);
379 bgpconfig.setPeerConnState(peerAddr, BGPPeerCfg.State.OPENSENT);
380 }
381 }
382
383 @Override
384 public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
385
386 channel = e.getChannel();
387 log.info("BGP disconnected callback for bgp:{}. Cleaning up ...", getPeerInfoString());
388
389 address = channel.getRemoteAddress();
390 if (!(address instanceof InetSocketAddress)) {
391 throw new IOException("Invalid peer connection.");
392 }
393
394 inetAddress = (InetSocketAddress) address;
395 ipAddress = IpAddress.valueOf(inetAddress.getAddress());
396 peerAddr = ipAddress.toString();
397
398 if (thisbgpId != null) {
399 if (!duplicateBGPIdFound) {
400 // if the disconnected peer (on this ChannelHandler)
401 // was not one with a duplicate, it is safe to remove all
402 // state for it at the controller. Notice that if the disconnected
403 // peer was a duplicate-ip, calling the method below would clear
404 // all state for the original peer (with the same ip),
405 // which we obviously don't want.
406 log.debug("{}:removal called", getPeerInfoString());
407 if (bgpPeer != null) {
408 peerManager.removeConnectedPeer(thisbgpId);
409 }
410 } else {
411 // A duplicate was disconnected on this ChannelHandler,
412 // this is the same peer reconnecting, but the original state was
413 // not cleaned up - XXX check liveness of original ChannelHandler
414 log.debug("{}:duplicate found", getPeerInfoString());
415 duplicateBGPIdFound = Boolean.FALSE;
416 }
417
418 if (null != keepAliveTimer) {
419 keepAliveTimer.getKeepAliveTimer().cancel();
420 }
421 } else {
422 log.warn("No bgp ip in channelHandler registered for " + "disconnected peer {}", getPeerInfoString());
423 }
424 }
425
426 @Override
427 public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
428
429 log.info("[exceptionCaught]: " + e.toString());
430
431 if (e.getCause() instanceof ReadTimeoutException) {
432 if ((ChannelState.OPENWAIT == state) || (ChannelState.OPENSENT == state)) {
433
434 // When ReadTimeout timer is expired in OPENWAIT/OPENSENT state, it is considered
435 // TODO: Send notification
436 channel.close();
437 state = ChannelState.IDLE;
438 return;
439 } else if (ChannelState.OPENCONFIRM == state) {
440
441 // When ReadTimeout timer is expired in OPENCONFIRM state.
442 // TODO: Send Notification
443 channel.close();
444 state = ChannelState.IDLE;
445 return;
446 }
447 } else if (e.getCause() instanceof ClosedChannelException) {
448 log.debug("Channel for bgp {} already closed", getPeerInfoString());
449 } else if (e.getCause() instanceof IOException) {
450 log.error("Disconnecting peer {} due to IO Error: {}", getPeerInfoString(), e.getCause().getMessage());
451 if (log.isDebugEnabled()) {
452 // still print stack trace if debug is enabled
453 log.debug("StackTrace for previous Exception: ", e.getCause());
454 }
455 channel.close();
456 } else if (e.getCause() instanceof BGPParseException) {
457 // TODO: SEND NOTIFICATION
458 log.debug("BGP Parse Exception: ", e.getCause());
459 } else if (e.getCause() instanceof RejectedExecutionException) {
460 log.warn("Could not process message: queue full");
461 } else {
462 log.error("Error while processing message from peer " + getPeerInfoString() + "state " + this.state);
463 channel.close();
464 }
465 }
466
467 @Override
468 public String toString() {
469 return getPeerInfoString();
470 }
471
472 @Override
473 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
474 if (e.getMessage() instanceof List) {
475 @SuppressWarnings("Unchecked")
476 List<BGPMessage> msglist = (List<BGPMessage>) e.getMessage();
477 for (BGPMessage pm : msglist) {
478 // Do the actual packet processing
479 state.processBGPMessage(this, pm);
480 }
481 } else {
482 state.processBGPMessage(this, (BGPMessage) e.getMessage());
483 }
484 }
485
486 // *************************
487 // Channel utility methods
488 // *************************
489 /**
490 * Set handshake status.
491 *
492 * @param handshakeComplete handshake complete status
493 */
494 public void setHandshakeComplete(boolean handshakeComplete) {
495 this.state.setHandshakeComplete(handshakeComplete);
496 }
497
498 /**
499 * Is this a state in which the handshake has completed?
500 *
501 * @return true if the handshake is complete
502 */
503 public boolean isHandshakeComplete() {
504 return state.isHandshakeComplete();
505 }
506
507 /**
508 * To handle the BGP message.
509 *
510 * @param m BGP message
511 */
512 private void dispatchMessage(BGPMessage m) throws BGPParseException {
513 bgpPacketStats.addInPacket();
514 bgpControllerImpl.processBGPPacket(thisbgpId, m);
515 }
516
517 /**
518 * Return a string describing this peer based on the already available information (ip address and/or remote
519 * socket).
520 *
521 * @return display string
522 */
523 private String getPeerInfoString() {
524 if (bgpPeer != null) {
525 return bgpPeer.toString();
526 }
527 String channelString;
528 if (channel == null || channel.getRemoteAddress() == null) {
529 channelString = "?";
530 } else {
531 channelString = channel.getRemoteAddress().toString();
532 }
533 String bgpIpString;
534 // TODO: implement functionality to get bgp id string
535 bgpIpString = "?";
536 return String.format("[%s BGP-IP[%s]]", channelString, bgpIpString);
537 }
538
539 /**
540 * Update the channels state. Only called from the state machine. TODO: enforce restricted state transitions
541 *
542 * @param state
543 */
544 private void setState(ChannelState state) {
545 this.state = state;
546 }
547
548 /**
549 * get packet statistics.
550 *
551 * @return packet statistics
552 */
553 public BGPPacketStatsImpl getBgpPacketStats() {
554 return bgpPacketStats;
555 }
556
557 /**
558 * Send handshake open message to the peer.
559 *
560 * @throws IOException ,BGPParseException
561 */
562 private void sendHandshakeOpenMessage() throws IOException, BGPParseException {
563 // TODO: send open message.
564
565 }
566
567 /**
568 * Send keep alive message.
569 *
570 * @throws IOException when channel is disconnected
571 * @throws BGPParseException while building keep alive message
572 */
573 synchronized void sendKeepAliveMessage() throws IOException, BGPParseException {
574
575 // TODO: send keep alive message.
576 }
577
578 /**
579 * Send notification and close channel with peer.
580 */
581 private void sendErrNotificationAndCloseChannel() {
582 // TODO: send notification
583 channel.close();
584 }
585
586 /**
587 * Process unknown BGP message received.
588 *
589 * @throws BGPParseException when received invalid message
590 */
591 public void processUnknownMsg() throws BGPParseException {
592 log.debug("UNKNOWN message received");
593 Date now = null;
594 if (bgpPacketStats.wrongPacketCount() == 0) {
595 now = new Date();
596 bgpPacketStats.setTime(now.getTime());
597 bgpPacketStats.addWrongPacket();
598 sendErrNotificationAndCloseChannel();
599 }
600 if (bgpPacketStats.wrongPacketCount() > 1) {
601 Date lastest = new Date();
602 bgpPacketStats.addWrongPacket();
603 // converting to seconds
604 if (((lastest.getTime() - bgpPacketStats.getTime()) / 1000) > 60) {
605 now = lastest;
606 bgpPacketStats.setTime(now.getTime());
607 bgpPacketStats.resetWrongPacket();
608 bgpPacketStats.addWrongPacket();
609 } else if (((int) (lastest.getTime() - now.getTime()) / 1000) < 60) {
610 if (MAX_WRONG_COUNT_PACKET <= bgpPacketStats.wrongPacketCount()) {
611 // reset once wrong packet count reaches MAX_WRONG_COUNT_PACKET
612 bgpPacketStats.resetWrongPacket();
613 // max wrong packets received send error message and close the session
614 sendErrNotificationAndCloseChannel();
615 }
616 }
617 }
618 }
619
620 /**
621 * Open message validation.
622 *
623 * @param h channel handler
624 * @return true if validation succeed, otherwise false
625 * @throws BGPParseException when received invalid message
626 */
627 public boolean openMsgValidation(BGPChannelHandler h) throws BGPParseException {
628 // TODO: Open message validation.
629 return true;
630 }
631}