package org.red5.app.sip.registration; import local.net.KeepAliveSip; import org.zoolu.net.SocketAddress; import org.zoolu.sip.address.*; import org.zoolu.sip.provider.SipStack; import org.zoolu.sip.provider.SipProvider; import org.zoolu.sip.header.*; import org.zoolu.sip.message.*; import org.zoolu.sip.transaction.TransactionClient; import org.zoolu.sip.transaction.TransactionClientListener; import org.zoolu.sip.authentication.DigestAuthentication; import org.slf4j.Logger; import org.red5.logging.Red5LoggerFactory; import java.util.HashSet; import java.util.Set; import java.util.Vector; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * Register User Agent. It registers (one time or periodically) a contact * address with a registrar server. */ public class SipRegisterAgent implements TransactionClientListener { private static Logger log = Red5LoggerFactory.getLogger(SipRegisterAgent.class, "sip"); private final Executor exec = Executors.newSingleThreadExecutor(); private Runnable registerProcess; private volatile boolean continueRegistering = false; /** The CallerID and CSeq that should be used during REGISTER method */ private CallIdHeader registerCallID; private int registerCSeq; private static final int MAX_ATTEMPTS = 3; private Status agentStatus = Status.UNREGISTERED; private Request request; private enum Status { REGISTERING(0), REGISTERED(1), RENEWING(2), UNREGISTERING(3), UNREGISTERED(4); private final int state; Status(int state) {this.state = state;} private int getState() {return state;} } private enum Request { REGISTERING(0), RENEWING(1), UNREGISTERING(1); private final int request; Request(int request) {this.request = request;} private int getRequest() {return request;} } private Set<SipRegisterAgentListener> listeners = new HashSet<SipRegisterAgentListener>(); private SipProvider sipProvider; private NameAddress target; // User's URI with the fully qualified domain // name of the registrar server. private String username; private String realm; private String passwd; private String nextNonce; // Nonce for the next authentication. private String qop; // Qop for the next authentication. private NameAddress contact; // User's contact address. private int expireTime; // Expiration time. private int renewTime; private int origRenewTime; // Change by lior. private int minRenewTime = 20; private boolean lastRegFailed = false; // Changed by Lior. private boolean regInprocess = false; private int attempts; // Number of registration attempts. private KeepAliveSip keepAlive; // KeepAliveSip daemon. public SipRegisterAgent(SipProvider sipProvider, String targetUrl, String contactUrl) { init(sipProvider, targetUrl, contactUrl); } /** * Creates a new RegisterAgent with authentication credentials (i.e. * username, realm, and passwd). */ public SipRegisterAgent(SipProvider sipProvider, String targetUrl, String contactUrl, String username, String realm, String passwd) { init(sipProvider, targetUrl, contactUrl); // Authentication. this.username = username; this.realm = realm; this.passwd = passwd; } private void init(SipProvider sipProvider, String targetUrl, String contactUrl) { this.sipProvider = sipProvider; this.target = new NameAddress(targetUrl); this.contact = new NameAddress(contactUrl); // this.expire_time=SipStack.default_expires; this.expireTime = 600; // Changed by Lior. this.renewTime = 600; this.origRenewTime = this.renewTime; this.keepAlive = null; // Authentication. this.username = null; this.realm = null; this.passwd = null; this.nextNonce = null; this.qop = null; this.attempts = 0; this.minRenewTime = 20; this.registerCallID = null; this.registerCSeq = 0; } public void addListener(SipRegisterAgentListener listener) { listeners.add(listener); } public void removeListener(SipRegisterAgentListener listener) { listeners.remove(listener); } private boolean isPeriodicallyRegistering() { return continueRegistering; } private void register() { register(expireTime); } /** Registers with the registrar server for <i>expire_time</i> seconds. */ private void register(int expireTime) { if (agentStatus == Status.REGISTERED) { request = Request.RENEWING; } else { request = Request.REGISTERING; } attempts = 0; lastRegFailed = false; regInprocess = true; if (expireTime > 0) this.expireTime = expireTime; Message req = MessageFactory.createRegisterRequest(sipProvider, target, target, contact); /* * MY_FIX: registerCallID contains the CallerID randomly generated in * the first REGISTER method. It will be reused for all successive * REGISTER invocations */ if (this.registerCallID == null) { this.registerCallID = req.getCallIdHeader(); } else { req.setCallIdHeader(this.registerCallID); } /* * MY_FIX: the registerCSeq must be unique for a given CallerID */ this.registerCSeq++; req.setCSeqHeader(new CSeqHeader(this.registerCSeq, SipMethods.REGISTER)); req.setExpiresHeader(new ExpiresHeader(String.valueOf(expireTime))); if (nextNonce != null) { AuthorizationHeader authHeader = new AuthorizationHeader("Digest"); authHeader.addUsernameParam(username); authHeader.addRealmParam(realm); authHeader.addNonceParam(nextNonce); authHeader.addUriParam(req.getRequestLine().getAddress().toString()); authHeader.addQopParam(qop); String response = (new DigestAuthentication(SipMethods.REGISTER, authHeader, null, passwd)).getResponse(); authHeader.addResponseParam(response); req.setAuthorizationHeader(authHeader); } if (expireTime > 0) printLog("Registering contact " + contact + " (it expires in " + expireTime + " secs)"); else printLog("Unregistering contact " + contact); TransactionClient t = new TransactionClient(sipProvider, req, this); t.request(); } public void unregister() { if (isPeriodicallyRegistering()) { stopRegistering(); } unregisterWithServer(); sipProvider = null; } private void unregisterWithServer() { attempts = 0; request = Request.UNREGISTERING; Message req = MessageFactory.createRegisterRequest(sipProvider, target, target, null); req.setExpiresHeader(new ExpiresHeader(String.valueOf(0))); printLog("Unregistering all contacts"); TransactionClient t = new TransactionClient(sipProvider, req, this); t.request(); } /** * Periodically registers with the registrar server. * * @param expireTime * expiration time in seconds * @param renewTime * renew time in seconds */ private void loopRegister(int expireTime, int renewTime) { this.expireTime = expireTime; this.renewTime = renewTime; if (!isPeriodicallyRegistering()) { registerProcess = new Runnable() { public void run() { registerWithServer(); } }; exec.execute(registerProcess); } } /** * Periodically registers with the registrar server. * * @param expireTime * expiration time in seconds * @param renewTime * renew time in seconds * @param keepaliveTime * keep-alive packet rate (inter-arrival time) in milliseconds */ public void register(int expireTime, int renewTime, long keepaliveTime) { if (isPeriodicallyRegistering()) { stopRegistering(); } loopRegister(expireTime, renewTime); // Keep-alive. if (keepaliveTime > 0) { SipURL targetUrl = target.getAddress(); String targetHost = targetUrl.getHost(); int targePort = targetUrl.getPort(); if (targePort < 0) targePort = SipStack.default_port; SocketAddress socket = new SocketAddress(targetHost, targePort); keepAlive = new KeepAliveSip(sipProvider, socket, null, keepaliveTime); } } public void stopRegistering() { continueRegistering = false; if (keepAlive != null) keepAlive.halt(); } // ***************************** run() ***************************** /** Run method */ private void registerWithServer() { continueRegistering = true; try { while (continueRegistering) { register(); // Changed by Lior. long waitCnt = 0; while (regInprocess) { Thread.sleep(1000); waitCnt += 1000; } if (lastRegFailed) { printLog("Failed Registration stop try."); stopRegistering(); } else Thread.sleep(renewTime * 1000 - waitCnt); } } catch (Exception e) { printException(e); } continueRegistering = false; } // **************** Transaction callback functions ***************** /** Callback function called when client sends back a failure response. */ /** Callback function called when client sends back a provisional response. */ public void onTransProvisionalResponse(TransactionClient transaction, Message resp) { // do nothing... } /** Callback function called when client sends back a success response. */ public void onTransSuccessResponse(TransactionClient transaction, Message resp) { if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) { if (resp.hasAuthenticationInfoHeader()) { nextNonce = resp.getAuthenticationInfoHeader().getNextnonceParam(); } StatusLine status = resp.getStatusLine(); String result = status.getCode() + " " + status.getReason(); // Update the renew_time. // Changed by Lior. int expires = 0; if (resp.hasExpiresHeader()) { expires = resp.getExpiresHeader().getDeltaSeconds(); } else if (resp.hasContactHeader()) { // Look for the max expires - should be the latest. Vector contacts = resp.getContacts().getHeaders(); for (int i = 0; i < contacts.size(); i++) { int exp_i = (new ContactHeader((Header) contacts.elementAt(i))).getExpires(); if (exp_i / 2 > expires) expires = exp_i / 2; } } if (expires > 0 && expires < renewTime) { renewTime = expires; if (renewTime < minRenewTime) { printLog("Attempt to set renew time below min renew. Attempted=" + renewTime + " min=" + minRenewTime + "\r\nResponse=" + resp.toString()); renewTime = minRenewTime; } } else if (expires > origRenewTime) { printLog("Attempt to set renew time above original renew. Attempted=" + expires + " origrenew=" + origRenewTime + "\r\nResponse=" + resp.toString()); } printLog("Registration success: " + result); regInprocess = false; if (request == Request.REGISTERING) { printLog("Notifying listeners of REGISTRATION success"); agentStatus = Status.REGISTERED; notifyListenersOfRegistrationSuccess("REGISTERED"); } else if (request == Request.UNREGISTERING){ printLog("Notifying listeners of UNREGISTRATION success"); agentStatus = Status.UNREGISTERED; notifyListenersOfRegistrationSuccess("UNREGISTERED"); } else if (request == Request.RENEWING) { agentStatus = Status.REGISTERED; // Don't notify the listeners. printLog("NOT Notifying listeners of RENEW success"); } } } private void notifyListenersOfRegistrationSuccess(String result) { for (SipRegisterAgentListener listener : listeners) { listener.onRegistrationSuccess(result); } } /** Callback function called when client sends back a failure response. */ public void onTransFailureResponse(TransactionClient transaction, Message resp) { printLog("onTransFailureResponse start: "); if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) { StatusLine status = resp.getStatusLine(); int code = status.getCode(); if ((code == 401 && attempts < MAX_ATTEMPTS && resp.hasWwwAuthenticateHeader() && resp.getWwwAuthenticateHeader().getRealmParam().equalsIgnoreCase(realm)) || (code == 407 && attempts < MAX_ATTEMPTS && resp.hasProxyAuthenticateHeader() && resp.getProxyAuthenticateHeader().getRealmParam().equalsIgnoreCase(realm))) { printLog("onTransFailureResponse 401 or 407: "); attempts++; Message req = transaction.getRequestMessage(); req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber()); // * MY_FIX: registerCSeq counter must incremented. this.registerCSeq++; WwwAuthenticateHeader wwwAuthHeader; if (code == 401) wwwAuthHeader = resp.getWwwAuthenticateHeader(); else wwwAuthHeader = resp.getProxyAuthenticateHeader(); String qopOptions = wwwAuthHeader.getQopOptionsParam(); // qop=(qopOptions!=null)? "auth" : null; // Select a new branch - rfc3261 says should be new on each request. ViaHeader via = req.getViaHeader(); req.removeViaHeader(); via.setBranch(SipProvider.pickBranch()); req.addViaHeader(via); qop = (qopOptions != null) ? "auth" : null; DigestAuthentication digest = new DigestAuthentication(SipMethods.REGISTER, req.getRequestLine().getAddress().toString(), wwwAuthHeader, qop, null, username, passwd); AuthorizationHeader authHeader; if (code == 401) authHeader = digest.getAuthorizationHeader(); else authHeader = digest.getProxyAuthorizationHeader(); req.setAuthorizationHeader(authHeader); TransactionClient t = new TransactionClient(sipProvider, req, this); t.request(); } else { String result = code + " " + status.getReason(); lastRegFailed = true; regInprocess = false; printLog("Registration failure: " + result); notifyListenersOfRegistrationFailure(result); } } } private void notifyListenersOfRegistrationFailure(String result) { for (SipRegisterAgentListener listener : listeners) { listener.onRegistrationFailure(result); } } /** Callback function called when client expires timeout. */ public void onTransTimeout(TransactionClient transaction) { if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) { lastRegFailed = true; regInprocess = false; printLog("Registration failure: No response from server."); notifyListenersOfRegistrationFailure( "Timeout"); } } // ****************************** Logs ***************************** void printLog(String str) { System.out.println("RegisterAgent: " + str); } void printException(Exception e) { System.out.println("RegisterAgent Exception: " + e); } }