/** * ysipchan.cpp * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Sip Channel * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2006 Null Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include using namespace TelEngine; namespace { // anonymous class YateSIPListener; // Base class for listeners (need binding) class YateSIPPartyHolder; // A SIPParty holder class YateSIPTransport; // SIP transport: keeps a socket, read/send data class YateSIPUDPTransport; // UDP transport class YateSIPTCPTransport; // TCP/TLS transport class YateSIPTransportWorker; // A transport worker class YateSIPTCPListener; // A TCP listener class YateUDPParty; // A SIP UDP party class YateTCPParty; // A SIP TCP/TLS party class YateSIPEngine; // The SIP engine class YateSIPLine; // A line class YateSIPEndPoint; // Endpoint processor class SIPDriver; #define EXPIRES_MIN 60 #define EXPIRES_DEF 600 #define EXPIRES_MAX 3600 // TCP transport idle values in seconds // Outgoing: interval to send keep alive // Incoming: interval allowed to stay with refcounter=1 and no data received/sent #define TCP_IDLE_MIN 32 #define TCP_IDLE_DEF 120 #define TCP_IDLE_MAX 600 // Maximum allowed value for bind retry interval in milliseconds // 1 minute #define BIND_RETRY_MAX 60000 static TokenDict dict_errors[] = { { "incomplete", 484 }, { "noroute", 404 }, { "noroute", 604 }, { "noconn", 503 }, { "noconn", 408 }, { "noauth", 401 }, { "nomedia", 415 }, { "nocall", 481 }, { "busy", 486 }, { "busy", 600 }, { "noanswer", 487 }, { "rejected", 406 }, { "rejected", 606 }, { "forbidden", 403 }, { "forbidden", 603 }, { "offline", 404 }, { "congestion", 480 }, { "unallocated", 410 }, { "failure", 500 }, { "pending", 491 }, { "looping", 483 }, { "timeout", 408 }, { "timeout", 504 }, { "service-not-implemented", 501 }, { "unimplemented", 501 }, { "service-unavailable", 503 }, { "noresource", 503 }, { "interworking", 500 }, { "interworking", 400 }, { "invalid-message", 400 }, { "protocol-error", 400 }, { 0, 0 }, }; static const char s_dtmfs[] = "0123456789*#ABCDF"; static TokenDict info_signals[] = { { "*", 10 }, { "#", 11 }, { "A", 12 }, { "B", 13 }, { "C", 14 }, { "D", 15 }, { 0, 0 }, }; // Protocol definition class ProtocolHolder { public: enum Protocol { Unknown = 0, Udp, Tcp, Tls }; inline ProtocolHolder(int p) : m_proto(p) {} // Retrieve protocol inline int protocol() const { return m_proto; } inline const char* protoName(bool upperCase = true) const { return lookupProtoName(protocol(),upperCase); } static inline const char* lookupProtoName(int proto, bool upperCase = true) { return lookup(proto,upperCase ? s_protoUC : s_protoLC); } static inline int lookupProto(const char* name, bool upperCase = true, int def = Unknown) { return lookup(name,upperCase ? s_protoUC : s_protoLC,def); } static inline int lookupProtoAny(const String& name, int def = Unknown) { String tmp = name; return lookupProto(tmp.toLower(),false,def); } static const TokenDict s_protoLC[]; // Lower case proto name static const TokenDict s_protoUC[]; // Upper case proto name protected: int m_proto; private: ProtocolHolder() {} // No default }; // A SIP party holder class YateSIPPartyHolder : public ProtocolHolder { public: inline YateSIPPartyHolder(Mutex* mutex = 0) : ProtocolHolder(Udp), m_party(0), m_partyMutex(mutex), m_transLocalPort(0), m_transRemotePort(0) {} virtual ~YateSIPPartyHolder() { setParty(); } // Retrieve a referrenced pointer to the held party inline SIPParty* party() { Lock lock(m_partyMutex); return (m_party && m_party->ref()) ? m_party : 0; } // Retrieve the transport from party YateSIPTransport* transport(bool ref = false); // Check if a transport is used by our party inline bool isTransport(YateSIPTransport* trans) { return trans == transport(); } // Set the held party. Referrence it before void setParty(SIPParty* party = 0); // Set the party of a non answer message. Return true on success bool setSipParty(SIPMessage* message, const YateSIPLine* line = 0, bool useEp = false, const char* host = 0, int port = 0) const; // (Re)Build party. Return true on success bool buildParty(bool force = true); // Change party and its transport if the parameter list contains a transport // Set force to true to try building a party anyway // Return true on success bool setParty(const NamedList& params, bool force, const String& prefix = String::empty(), const String& defRemoteAddr = String::empty(), int defRemotePort = 0); // Transport status changed notification virtual void transportChangedStatus(int stat, const String& reason) {} protected: // Change a parameter, notify descendents bool change(String& dest, const String& src); bool change(int& dest, int src); // Changing notification for descendents virtual void changing(); // Update transport type. Return true if changed bool updateProto(const NamedList& params, const String& prefix = String::empty()); // Update transport remote addr/port. Return true if changed bool updateRemoteAddr(const NamedList& params, const String& prefix = String::empty(), const String& defRemoteAddr = String::empty(), int defRemotePort = 0); // Update transport local addr/port. Return true if changed bool updateLocalAddr(const NamedList& params, const String& prefix = String::empty()); // Update RTP local address void setRtpLocalAddr(String& addr, Message* m = 0); SIPParty* m_party; // Held party Mutex* m_partyMutex; // Mutex protecting the party pointer // Data used to (re)build the transport String m_transId; String m_transLocalAddr; int m_transLocalPort; String m_transRemoteAddr; int m_transRemotePort; }; // Base class for listeners (need binding) class YateSIPListener { public: YateSIPListener(const String& addr = String::empty(), int port = 0); inline const String& address() const { return m_address; } inline int port() const { return m_port; } // Check bind now flag bool bindNow(Mutex* mutex); // Check if address would change bool addrWouldChange(Mutex* mutex, bool udp, const String& addr, int port); // Set addr/port and bind flag. Return the bind flag bool setAddr(const String& addr, int port); // Initialize a socket // Set m_addr Socket* initSocket(int proto, const String& name, SocketAddr& addr, Mutex* mutex, int backLogBuffer, bool forceBind, String& reason); protected: unsigned int m_bindInterval; // Interval to try binding u_int64_t m_nextBind; // Next time to bind bool m_bind; // Re-bind flag String m_address; // Address to bind int m_port; // Port to bind }; // SIP transport: keeps a socket, read data from it, send data through it class YateSIPTransport : public Mutex, public RefObject, public ProtocolHolder { YCLASS(YateSIPTransport,RefObject); YNOCOPY(YateSIPTransport); friend class SIPDriver; friend class YateSIPEndPoint; friend class YateSIPTransportWorker; public: enum Status { Idle = 0, Connected, Terminating, Terminated }; // (Re)Initialize the transport bool init(const NamedList& params, const NamedList& defs, bool first, Thread::Priority prio = Thread::Normal); // Retrieve status inline int status() const { return m_status; } // Check if valid (connected) inline bool valid() const { return status() == Connected; } // Retrieve local address for this transport // This method is not thread safe for outgoing TCP inline const SocketAddr& local() const { return m_local; } // Retrieve remote address for this transport // This method is not thread safe for outgoing TCP inline const SocketAddr& remote() const { return m_remote; } // Safely retrieve RTP local address inline void rtpAddr(String& buf) { Lock lock(this); buf = m_rtpLocalAddr; } // Print sent messages to output void printSendMsg(const SIPMessage* msg, const SocketAddr* addr = 0); // Print received messages to output // For TCP transports the function will assume 'buf' is not null terminated void printRecvMsg(const char* buf, int len); // Add transport data yate message void fillMessage(Message& msg, bool addRoute = false); // Transport descendents virtual YateSIPUDPTransport* udpTransport() { return 0; } virtual YateSIPTCPTransport* tcpTransport() { return 0; } // Stop the worker. Change status void terminate(const char* reason = 0); // Process data (read/send). // Return 0 to continue processing, positive to sleep (usec), // negative to terminate and destroy virtual int process() = 0; // Retrieve the transport id virtual const String& toString() const; // Reset and delete a socket static void resetSocket(Socket*& sock, int linger); // Status names static inline const char* statusName(int stat, const char* defVal = "Unknown") { return lookup(stat,s_statusName,defVal); } static const TokenDict s_statusName[]; protected: YateSIPTransport(int proto, const String& id, Socket* sock, int stat = Connected); virtual void destroyed(); // Status changed notification for descendents virtual void statusChanged() {} // Change transport status. Notify it void changeStatus(int stat); // Handle received messages, set party, add to engine // Consume the message void receiveMsg(SIPMessage*& msg); // Print socket read error to output void printReadError(); // Print socket write error to output void printWriteError(int res, unsigned int len); // Set m_protoAddr from local/remote ip/port or reset it void setProtoAddr(bool set); String m_id; // Transport id int m_status; // Transport status unsigned int m_statusChgTime; // Last status changed time (seconds) String m_reason; // Termination reason Socket* m_sock; // The socket unsigned int m_maxpkt; // Max receive packet length DataBlock m_buffer; // Read buffer SocketAddr m_local; // Local ip/port SocketAddr m_remote; // Remote ip/port String m_rtpLocalAddr; // RTP local address YateSIPTransportWorker* m_worker; // Transport worker bool m_initialized; // Flag reset when initializing by the module and set in init() String m_protoAddr; // Proto + addr: used for debug (send/recv msg) private: YateSIPTransport() : ProtocolHolder(Udp) {} // No default constructor }; // UDP transport class YateSIPUDPTransport : public YateSIPTransport, public YateSIPListener { YCLASS(YateSIPUDPTransport,YateSIPTransport); friend class YateSIPTransport; public: YateSIPUDPTransport(const String& id); inline bool isDefault() const { return m_default; } virtual YateSIPUDPTransport* udpTransport() { return this; } // (Re)Initialize the transport bool init(const NamedList& params, const NamedList& defs, bool first, Thread::Priority prio = Thread::Normal); // Send data void send(const void* data, unsigned int len, const SocketAddr& addr); // Process data (read) virtual int process(); protected: bool m_default; bool m_forceBind; int m_bufferReq; }; // TCP/TLS transport class YateSIPTCPTransport : public YateSIPTransport { YCLASS(YateSIPTCPTransport,YateSIPTransport); friend class YateTCPParty; public: // Build an outgoing transport YateSIPTCPTransport(bool tls, const String& laddr, const String& raddr, int rport); // Build an incoming transport YateSIPTCPTransport(Socket* sock, bool tls); inline bool outgoing() const { return m_outgoing; } inline bool tls() const { return protocol() == Tls; } inline const String& remoteAddr() const { return m_remoteAddr; } inline int remotePort() const { return m_remotePort; } // Safely return a reference to party YateTCPParty* getParty(); virtual YateSIPTCPTransport* tcpTransport() { return this; } // (Re)Initialize the transport bool init(const NamedList& params, bool first, Thread::Priority prio = Thread::Normal); // Set flow timer flag and idle interval (in seconds) // Reset idle timeout void setFlowTimer(bool on, unsigned int interval); // Send an event void send(SIPEvent* event); // Process data (read/send) virtual int process(); protected: virtual void destroyed(); // Status changed notification virtual void statusChanged(); // Reset transport's party void resetParty(YateTCPParty* party, bool set); // Connect an outgoing transport. Terminate the socket before it // Return: 1: OK, 0: retry connect, -1: stop the transport int connect(u_int64_t connToutUs = 60000000); // Send pending messages or keepalive, return false on failure bool sendPending(const Time& time, bool& sent); // Read data bool readData(const Time& time, bool& read); // Reset socket and connection related data void resetConnection(Socket* sock = 0); // Set transport idle timeout void setIdleTimeout(u_int64_t time = Time::now()); // Send keep alive (or response to keep alive) bool sendKeepAlive(bool request); // Method called on buffer overflow. // Reset connection. Return false bool overflow(unsigned int msglen); bool m_outgoing; // Direction YateTCPParty* m_party; // Transport party ObjList m_queue; // Pending message queue int m_sent; // Sent bytes from first message in queue // -1 to dequeue a new message and print it unsigned int m_idleInterval; // Incoming: interval allowed to stay with a reference // counter=1 without receiving any data // Outgoing: keep alive interval u_int64_t m_idleTimeout; // Idle timeout: check state or send keep alive bool m_flowTimer; // Flow timer flag (RFC5626) bool m_keepAlivePending; // Pending keep alive response SIPMessage* m_msg; // Partially received SIP message (expecting body) String m_sipBuffer; // Accumulated read data unsigned int m_sipBufOffs; // Offset in sip buffer for partial sip message unsigned int m_contentLen; // Expected content length for partial sip message // Outgoing (re-connect info) String m_remoteAddr; // Remote party address int m_remotePort; // Remote port String m_localAddr; // Optional local addrress to bind to unsigned int m_connectRetry; // Number of re-connect u_int64_t m_nextConnect; // Interval to try ro re-connect }; // Transport worker class YateSIPTransportWorker : public Thread { friend class YateSIPTransport; public: YateSIPTransportWorker(YateSIPTransport* trans, Thread::Priority prio); ~YateSIPTransportWorker(); virtual void run(); private: void cleanupTransport(bool final, bool terminate = false); YateSIPTransport* m_transport; }; class YateSIPTCPListener : public Thread, public String, public ProtocolHolder, public YateSIPListener { friend class SIPDriver; friend class YateSIPEndPoint; public: YateSIPTCPListener(int proto, const String& name, const NamedList& params); ~YateSIPTCPListener(); void init(const NamedList& params, bool first); inline bool tls() const { return protocol() == Tls; } inline bool listening() const { return m_socket != 0; } inline void setReason(const char* reason) { if (!reason) return; Lock lck(m_mutex); m_reason = reason; } virtual void run(); private: // Close the socket. Remove from endpoint list void cleanup(bool final); // Reset socket void stopListening(const char* reason = 0, int level = DebugNote); Mutex m_mutex; // Mutex protecting transport parameters and bind ip/port String m_reason; // Last error (state change) string bool m_sslContextChanged; // SSL context changed flag bool m_transParamsChanged; // Transport parameters changed flag Socket* m_socket; // The socket unsigned int m_backlog; // Pending connections queue length String m_sslContext; // SSL/TLS context NamedList m_transParams; // Parameters for created transports bool m_initialized; // Flag reset when initializing by the module and set in init() }; class YateUDPParty : public SIPParty { public: YateUDPParty(YateSIPUDPTransport* trans, const SocketAddr& addr, int* localPort = 0, const char* localAddr = 0); ~YateUDPParty(); inline const SocketAddr& addr() const { return m_addr; } virtual void transmit(SIPEvent* event); virtual const char* getProtoName() const; virtual bool setParty(const URI& uri); virtual void* getTransport(); // Get an object from this one virtual void* getObject(const String& name) const; protected: YateSIPUDPTransport* m_transport; SocketAddr m_addr; }; class YateTCPParty : public SIPParty { public: YateTCPParty(YateSIPTCPTransport* trans); ~YateTCPParty(); virtual void transmit(SIPEvent* event); virtual const char* getProtoName() const; virtual bool setParty(const URI& uri); virtual void* getTransport(); // Get an object from this one virtual void* getObject(const String& name) const; // Update party local/remote addr/port from transport void updateAddrs(); protected: virtual void destroyed(); YateSIPTCPTransport* m_transport; }; class SipHandler; class YateSIPEngine : public SIPEngine { public: YateSIPEngine(YateSIPEndPoint* ep); // Initialize the engine void initialize(NamedList* params); virtual bool buildParty(SIPMessage* message); virtual bool checkUser(const String& username, const String& realm, const String& nonce, const String& method, const String& uri, const String& response, const SIPMessage* message, GenObject* userData); virtual SIPTransaction* forkInvite(SIPMessage* answer, SIPTransaction* trans); // Transport status changed notification void transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason); // Check if the engine has an active transaction using a given transport bool hasActiveTransaction(YateSIPTransport* trans); // Check if the engine has pending transactions bool hasInitialTransaction(); // Clear transactions inline void clearTransactions() { Lock lck(this); m_transList.clear(); } inline bool prack() const { return m_prack; } inline bool info() const { return m_info; } private: static bool copyAuthParams(NamedList* dest, const NamedList& src, bool ok = true); YateSIPEndPoint* m_ep; bool m_prack; bool m_info; bool m_fork; }; class YateSIPLine : public String, public Mutex, public YateSIPPartyHolder { YCLASS(YateSIPLine,String) public: YateSIPLine(const String& name); virtual ~YateSIPLine(); void setupAuth(SIPMessage* msg) const; SIPMessage* buildRegister(int expires) const; void login(); void logout(bool sendLogout = true, const char* reason = 0); bool process(SIPEvent* ev); void timer(const Time& when); bool update(const Message& msg); // Transport status changed notification virtual void transportChangedStatus(int stat, const String& reason); inline const String& getLocalAddr() const { return m_localAddr; } inline const String& getPartyAddr() const { return m_partyAddr ? m_partyAddr : m_transRemoteAddr; } inline int getLocalPort() const { return m_localPort; } inline int getPartyPort() const { return m_partyPort ? m_partyPort : m_transRemotePort; } inline bool localDetect() const { return m_localDetect; } inline const String& getFullName() const { return m_display; } inline const String& getUserName() const { return m_username; } inline const String& getAuthName() const { return m_authname ? m_authname : m_username; } inline const String& regDomain() const { return m_registrar ? m_registrar : m_transRemoteAddr; } inline const String& domain() const { return m_domain ? m_domain : regDomain(); } inline const char* domain(const char* defDomain) const { return m_domain ? m_domain.c_str() : (TelEngine::null(defDomain) ? regDomain().c_str() : defDomain); } inline bool valid() const { return m_valid; } inline bool marked() const { return m_marked; } inline void marked(bool mark) { m_marked = mark; } private: void clearTransaction(); void detectLocal(const SIPMessage* msg); void keepalive(); void setValid(bool valid, const char* reason = 0); virtual void changing(); String m_registrar; String m_username; String m_authname; String m_password; String m_domain; String m_display; u_int64_t m_resend; u_int64_t m_keepalive; int m_interval; int m_alive; int m_flags; SIPTransaction* m_tr; bool m_marked; bool m_valid; String m_callid; String m_localAddr; String m_partyAddr; int m_localPort; int m_partyPort; bool m_localDetect; bool m_keepTcpOffline; // Don't reset party when offline }; class YateSIPEndPoint : public Thread { friend class SIPDriver; friend class YateSIPTCPListener; public: YateSIPEndPoint(Thread::Priority prio = Thread::Normal, unsigned int partyMutexCount = 5); ~YateSIPEndPoint(); bool Init(void); void run(void); bool incoming(SIPEvent* e, SIPTransaction* t); void invite(SIPEvent* e, SIPTransaction* t); void regReq(SIPEvent* e, SIPTransaction* t); void regRun(const SIPMessage* message, SIPTransaction* t); void options(SIPEvent* e, SIPTransaction* t); bool generic(SIPEvent* e, SIPTransaction* t); bool buildParty(SIPMessage* message, const char* host = 0, int port = 0, const YateSIPLine* line = 0); inline void addTcpTransport(YateSIPTCPTransport* trans) { if (!trans) return; Lock lock(m_mutex); m_transports.append(trans)->setDelete(false); } // Retrieve the default transport. Return a referrenced object inline YateSIPUDPTransport* defTransport() { Lock lock(m_mutex); return (m_defTransport && m_defTransport->ref()) ? m_defTransport : 0; } // (re)set default UDP transport void updateDefUdpTransport(); // Retrieve a transport by name (name can be a prefix). // Return a referrenced object YateSIPTransport* findTransport(const String& name); // Retrieve an UDP transport. Return a referrenced object YateSIPUDPTransport* findUdpTransport(const String& name); // Retrieve an UDP transport by addr/port. Return a referrenced object YateSIPUDPTransport* findUdpTransport(const String& addr, int port); // Build or delete an UDP transport (re-init existing). Start the thread bool setupUdpTransport(const String& name, bool enabled, const NamedList& params, const NamedList& defs = NamedList::empty(), const char* reason = 0); // Delete an UDP transport bool removeUdpTransport(const String& name, const char* reason); // Remove a transport from list without deleting it. Notify termination. // Return true if found bool removeTransport(YateSIPTransport* trans, bool updDef = true); // Clear all transports void clearUdpTransports(const char* reason); // Transport status changed notification void transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason); // Build or delete a TCP listener. Start the thread bool setupListener(int proto, const String& name, bool enabled, const NamedList& params); // Remove a listener from list without deleting it. Return true if found bool removeListener(YateSIPTCPListener* listener); // Remove a listener from list. Remove all if name is empty. Wait for termination void cancelListener(const String& name = String::empty(), const char* reason = 0); // This method is called by the driver when start/end initializing void initializing(bool start); inline YateSIPEngine* engine() const { return m_engine; } inline void incFailedAuths() { m_failedAuths++; } inline unsigned int failedAuths() { unsigned int tmp = m_failedAuths; m_failedAuths = 0; return tmp; } inline unsigned int timedOutTrs() { unsigned int tmp = m_timedOutTrs; m_timedOutTrs = 0; return tmp; } inline unsigned int timedOutByes() { unsigned int tmp = m_timedOutByes; m_timedOutByes = 0; return tmp; } MutexPool m_partyMutexPool; // SIPParty mutex pool // Check if data is allowed to be read from socket(s) and processed static bool canRead(); static int s_evCount; private: YateSIPEngine *m_engine; Mutex m_mutex; // Protect transports and listeners ObjList m_transports; // All transports (non UDP are not owned) YateSIPUDPTransport* m_defTransport; // Default transport (pointer to object in m_transports) ObjList m_listeners; // Listeners list unsigned int m_failedAuths; unsigned int m_timedOutTrs; unsigned int m_timedOutByes; }; // Handle transfer requests // Respond to the enclosed transaction class YateSIPRefer : public Thread { public: YateSIPRefer(const String& transferorID, const String& transferredID, Driver* transferredDrv, Message* msg, SIPMessage* sipNotify, SIPTransaction* transaction); virtual void run(void); virtual void cleanup(void) { release(true); } private: // Respond the transaction and deref() it void setTrResponse(int code); // Set transaction response. Send the notification message. Notify the // connection and release other objects void release(bool fromCleanup = false); String m_transferorID; // Transferor channel's id String m_transferredID; // Transferred channel's id Driver* m_transferredDrv; // Transferred driver's pointer Message* m_msg; // 'call.route' message SIPMessage* m_sipNotify; // NOTIFY message to send the result int m_notifyCode; // The result to send with NOTIFY SIPTransaction* m_transaction; // The transaction to respond to int m_rspCode; // The transaction response }; class YateSIPRegister : public Thread { public: inline YateSIPRegister(YateSIPEndPoint* ep, SIPMessage* message, SIPTransaction* t) : Thread("YSIP Register"), m_ep(ep), m_msg(message), m_tr(t) { } virtual void run() { m_ep->regRun(m_msg,m_tr); } private: YateSIPEndPoint* m_ep; RefPointer m_msg; RefPointer m_tr; }; class YateSIPConnection : public Channel, public SDPSession, public YateSIPPartyHolder { friend class SipHandler; YCLASS(YateSIPConnection,Channel) public: enum { Incoming = 0, Outgoing = 1, Ringing = 2, Established = 3, Cleared = 4, }; enum { ReinviteNone, ReinvitePending, ReinviteRequest, ReinviteReceived, }; YateSIPConnection(SIPEvent* ev, SIPTransaction* tr); YateSIPConnection(Message& msg, const String& uri, const char* target = 0); ~YateSIPConnection(); virtual void destroyed(); virtual void complete(Message& msg, bool minimal=false) const; virtual void disconnected(bool final, const char *reason); virtual bool msgProgress(Message& msg); virtual bool msgRinging(Message& msg); virtual bool msgAnswered(Message& msg); virtual bool msgTone(Message& msg, const char* tone); virtual bool msgText(Message& msg, const char* text); virtual bool msgDrop(Message& msg, const char* reason); virtual bool msgUpdate(Message& msg); virtual bool callRouted(Message& msg); virtual void callAccept(Message& msg); virtual void callRejected(const char* error, const char* reason, const Message* msg); void startRouter(); bool process(SIPEvent* ev); bool checkUser(SIPTransaction* t, bool refuse = true); void doBye(SIPTransaction* t); void doCancel(SIPTransaction* t); bool doInfo(SIPTransaction* t); void doRefer(SIPTransaction* t); void reInvite(SIPTransaction* t); void hangup(); inline const SIPDialog& dialog() const { return m_dialog; } inline void setStatus(const char *stat, int state = -1) { status(stat); if (state >= 0) m_state = state; } inline void setReason(const char* str = "Request Terminated", int code = 487) { m_reason = str; m_reasonCode = code; } inline SIPTransaction* getTransaction() const { return m_tr; } inline const String& callid() const { return m_callid; } inline const String& user() const { return m_user; } inline int getPort() const { return m_port; } inline const String& getLine() const { return m_line; } inline void referTerminated() { m_referring = false; } inline bool isDialog(const String& callid, const String& fromTag, const String& toTag) const { return callid == m_dialog && m_dialog.fromTag(isOutgoing()) == fromTag && m_dialog.toTag(isOutgoing()) == toTag; } // Build and add a callid parameter to a list static inline void addCallId(NamedList& nl, const String& dialog, const String& fromTag, const String& toTag) { String tmp; tmp << "sip/" << dialog << "/" << fromTag << "/" << toTag; nl.addParam("callid",tmp); } protected: virtual Message* buildChanRtp(RefObject* context); MimeSdpBody* createProvisionalSDP(Message& msg); virtual void mediaChanged(const SDPMedia& media); virtual void endDisconnect(const Message& msg, bool handled); private: virtual void statusParams(String& str); void clearTransaction(); void detachTransaction2(); void startPendingUpdate(); bool processTransaction2(SIPEvent* ev, const SIPMessage* msg, int code); SIPMessage* createDlgMsg(const char* method, const char* uri = 0); void emitUpdate(); bool emitPRACK(const SIPMessage* msg); bool startClientReInvite(Message& msg); // Build the 'call.route' and NOTIFY messages needed by the transfer thread bool initTransfer(Message*& msg, SIPMessage*& sipNotify, const SIPMessage* sipRefer, const MimeHeaderLine* refHdr, const URI& uri, const MimeHeaderLine* replaces); // Decode an application/isup body into 'msg' if configured to do so // The message's name and user data are restored before exiting, regardless the result // Return true if an ISUP message was succesfully decoded bool decodeIsupBody(Message& msg, MimeBody* body); // Build the body of a SIP message from an engine message // Encode an ISUP message from parameters received in msg if enabled to process them // Build a multipart/mixed body if more then one body is going to be sent MimeBody* buildSIPBody(Message& msg, MimeSdpBody* sdp = 0); // Build the body of a hangup SIP message MimeBody* buildSIPBody(); SIPTransaction* m_tr; SIPTransaction* m_tr2; // are we already hung up? bool m_hungup; // should we send a BYE? bool m_byebye; // should we CANCEL? bool m_cancel; int m_state; String m_reason; int m_reasonCode; String m_callid; // SIP dialog of this call, used for re-INVITE or BYE SIPDialog m_dialog; // remote URI as we send in dialog messages URI m_uri; String m_domain; String m_user; String m_line; int m_port; Message* m_route; ObjList* m_routes; bool m_authBye; bool m_inband; bool m_info; // REFER already running bool m_referring; // reINVITE requested or in progress int m_reInviting; // sequence number of last transmitted PRACK int m_lastRseq; }; class YateSIPGenerate : public GenObject { YCLASS(YateSIPGenerate,GenObject) public: YateSIPGenerate(SIPMessage* m); virtual ~YateSIPGenerate(); bool process(SIPEvent* ev); bool busy() const { return m_tr != 0; } int code() const { return m_code; } private: void clearTransaction(); SIPTransaction* m_tr; int m_code; }; class UserHandler : public MessageHandler { public: UserHandler() : MessageHandler("user.login",150) { } virtual bool received(Message &msg); }; class SipHandler : public MessageHandler { public: SipHandler() : MessageHandler("xsip.generate",110) { } virtual bool received(Message &msg); }; // Proxy class used to transport a data buffer or a socket // The object doesn't own its data class RefObjectProxy : public RefObject { public: inline RefObjectProxy() : m_data(0), m_socket(0) {} inline RefObjectProxy(DataBlock* data) : m_data(data), m_socket(0) {} inline RefObjectProxy(Socket** sock) : m_data(0), m_socket(sock) {} virtual void* getObject(const String& name) const { if (name == YSTRING("DataBlock")) return m_data; if (name == YSTRING("Socket*")) return (void*)m_socket; return RefObject::getObject(name); } private: DataBlock* m_data; Socket** m_socket; }; class SIPDriver : public Driver { public: enum Relay { Stop = Private, }; SIPDriver(); ~SIPDriver(); virtual void initialize(); virtual bool hasLine(const String& line) const; virtual bool msgExecute(Message& msg, String& dest); virtual bool received(Message& msg, int id); inline YateSIPEndPoint* ep() const { return m_endpoint; } inline SDPParser& parser() { return m_parser; } YateSIPConnection* findCall(const String& callid, bool incRef = false); YateSIPConnection* findDialog(const SIPDialog& dialog, bool incRef = false); YateSIPConnection* findDialog(const String& dialog, const String& fromTag, const String& toTag, bool incRef = false); YateSIPLine* findLine(const String& line) const; YateSIPLine* findLine(const String& addr, int port, const String& user = String::empty()); // Drop channels belonging using a given transport // Return the number of disconnected channels unsigned int transportTerminated(YateSIPTransport* trans); bool validLine(const String& line); bool commandComplete(Message& msg, const String& partLine, const String& partWord); void msgStatus(Message& msg); // Build and dispatch a socket.ssl message bool socketSsl(Socket** sock, bool server, const String& context = String::empty()); protected: virtual void genUpdate(Message& msg); private: // Add status methods void msgStatusAccounts(Message& msg); void msgStatusTransports(Message& msg, bool showUdp, bool showTcp, bool showTls); void msgStatusListener(Message& msg); void msgStatusTransport(Message& msg, const String& id); SDPParser m_parser; YateSIPEndPoint *m_endpoint; }; static SIPDriver plugin; static ObjList s_lines; static Configuration s_cfg; static Mutex s_globalMutex(true,"SIPGlobal"); // Protect globals (don't use the plugin to avoid deadlocks) static unsigned int s_engineStop = 0; // engine.stop message counter static bool s_engineHalt = false; // engine.halt received static unsigned int s_bindRetryMs = 500; // Listeners bind retry interval static String s_realm = "Yate"; static int s_floodEvents = 20; static int s_maxForwards = 20; static int s_nat_refresh = 25; static bool s_privacy = false; static bool s_auto_nat = true; static bool s_progress = false; static bool s_inband = false; static bool s_info = false; static bool s_start_rtp = false; static bool s_ack_required = true; static bool s_1xx_formats = true; static bool s_auth_register = true; static bool s_reg_async = true; static bool s_multi_ringing = false; static bool s_refresh_nosdp = true; static bool s_ignoreVia = true; // Ignore Via headers and send answer back to the source static bool s_sipt_isup = false; // Control the application/isup body processing static bool s_printMsg = true; // Print sent/received SIP messages to output static u_int64_t s_waitActiveUdpTrans = 1000000; // Time to wait for active UDP transactions // to complete when a listener is disabled static unsigned int s_tcpConnectRetry = 3; // Number of TCP connect attempts static u_int64_t s_tcpConnectInterval = 1000000; // The interval to attempt tcp connect static unsigned int s_tcpIdle = TCP_IDLE_DEF; // TCP transport idle interval static unsigned int s_tcpMaxpkt = 1500; // Maximum packet to accept on TCP connections static String s_tcpOutRtpip; // RTP ip for outgoing tcp/tls transports (protected by plugin mutex) static bool s_lineKeepTcpOffline = true; // Lines: keep TCP transports when offline static String s_sslCertFile; // File containing the SSL client certificate to present if requested by the server static String s_sslKeyFile; // File containing the key of the SSL client certificate static int s_expires_min = EXPIRES_MIN; static int s_expires_def = EXPIRES_DEF; static int s_expires_max = EXPIRES_MAX; static String s_statusCmd = "status"; int YateSIPEndPoint::s_evCount = 0; // Lower case proto name const TokenDict ProtocolHolder::s_protoLC[] = { { "udp", Udp}, { "tcp", Tcp}, { "tls", Tls}, { 0, 0 }, }; // Upper case proto name const TokenDict ProtocolHolder::s_protoUC[] = { { "UDP", Udp}, { "TCP", Tcp}, { "TLS", Tls}, { 0, 0 }, }; // Transport status names const TokenDict YateSIPTransport::s_statusName[] = { { "Idle", Idle}, { "Connected", Connected}, { "Terminating", Terminating}, { "Terminated", Terminated}, { 0, 0 }, }; // Generate a transport id index when needed static inline unsigned int getTransIndex() { static unsigned int s_index = 0; Lock lck(plugin); if (!++s_index) ++s_index; return s_index; } // Add a socket error to a buffer static inline void addSockError(String& buf, Socket& sock, const char* sep = " ") { String tmp; Thread::errorString(tmp,sock.error()); buf << sep << tmp << " (" << sock.error() << ")"; } // Return default udp/tcp/tls port static inline int sipPort(bool noTls) { return noTls ? 5060 : 5061; } // Return valid tcp idle interval static unsigned int tcpIdleInterval(int val) { int min = TCP_IDLE_MIN; // Adjust minimum value to engine INVITE timeout if (plugin.ep()) min = (int)(plugin.ep()->engine()->getTimer('B',true) / 1000000) * 3 / 2; if (val >= min && val <= TCP_IDLE_MAX) return val; return (val < min) ? min : TCP_IDLE_MAX; } // Return a valid maxpkt static unsigned int getMaxpkt(int val, int defVal) { if (val >= 524 && val <= 65528) return val; if (val <= 0) return defVal; if (val > 65528) return 65528; return 524; } // Skip tabs, spaces, CR and LF from buffer start // Return true if the buffer changed static bool skipSpaces(String& buf, bool crlf = true) { unsigned int i = 0; if (crlf) { for (; i < buf.length(); i++) if (buf[i] != '\r' && buf[i] != '\n' && buf[i] != ' ' && buf[i] != '\t') break; } else for (; i < buf.length(); i++) if (buf[i] != ' ' && buf[i] != '\t') break; if (!i) return false; buf = buf.substr(i); return true; } // Find an empty line in a buffer // Return the position past it or buffer length + 1 if not found // NOTE: returned value may be buffer length static unsigned int getEmptyLine(const String& buf) { int count = 0; unsigned int i = 0; for (; count < 2 && i < buf.length(); i++) { if (buf[i] == '\r') { i++; if (i < buf.length() && buf[i] == '\n') count++; else count = 0; } else if (buf[i] == '\n') count++; else count = 0; } return (count == 2) ? i : buf.length() + 1; } // Fill a buffer with message method/code for debug purposes static inline void getMsgLine(String& buf, const SIPMessage* msg) { if (!msg) return; if (msg->isAnswer()) buf << "code " << msg->code; else buf << "'" << msg->method << " " << msg->uri << "'"; } // Reset transport timeout from expires static void resetTransportIdle(const SIPMessage* msg, int interval) { if (!msg || interval <= 0) return; YateSIPTCPTransport* tcp = YOBJECT(YateSIPTCPTransport,msg->getParty()); if (!tcp) return; const MimeHeaderLine* hl = msg->getHeader("Flow-Timer"); int val = hl ? hl->toInteger() : 0; bool on = (val > 0 && val < interval); if (on) { // Wait extra time for incoming transports interval = tcp->outgoing() ? val : val + 20; } tcp->setFlowTimer(on,interval); } // Check if an IPv4 address belongs to one of the non-routable blocks static bool isPrivateAddr(const String& host) { if (host.startsWith("192.168.") || host.startsWith("169.254.") || host.startsWith("10.")) return true; String s(host); if (!s.startSkip("172.",false)) return false; int i = 0; s >> i; return (i >= 16) && (i <= 31) && s.startsWith("."); } // Check if an address is an 'all interfaces' one // This works for IPv4 addresses only static inline bool isAllIfacesAddr(const String& addr) { return !addr || addr == YSTRING("0.0.0.0"); } // Check if there may be a NAT between an address embedded in the protocol // and an address obtained from the network layer static bool isNatBetween(const String& embAddr, const String& netAddr) { return isPrivateAddr(embAddr) && !isPrivateAddr(netAddr); } // List of headers we may not want to handle generically static const char* s_filterHeaders[] = { "from", "to", 0 }; // List of critical headers we surely don't want to handle generically static const char* s_rejectHeaders[] = { "via", "route", "record-route", "call-id", "cseq", "max-forwards", "content-length", "www-authenticate", "proxy-authenticate", "authorization", "proxy-authorization", 0 }; // Check if a string matches one member of a static list static bool matchAny(const String& name, const char** strs) { for (; *strs; strs++) if (name == *strs) return true; return false; } // Copy headers from SIP message to Yate message static void copySipHeaders(NamedList& msg, const SIPMessage& sip, bool filter = true) { const ObjList* l = sip.header.skipNull(); for (; l; l = l->skipNext()) { const MimeHeaderLine* t = static_cast(l->get()); String name(t->name()); name.toLower(); if (matchAny(name,s_rejectHeaders)) continue; if (filter && matchAny(name,s_filterHeaders)) continue; String tmp(*t); const ObjList* p = t->params().skipNull(); for (; p; p = p->skipNext()) { NamedString* s = static_cast(p->get()); tmp << ";" << s->name(); if (!s->null()) tmp << "=" << *s; } msg.addParam("sip_"+name,tmp); } } // Copy headers from Yate message to SIP message static void copySipHeaders(SIPMessage& sip, const NamedList& msg, const char* prefix = "osip_") { prefix = msg.getValue(YSTRING("osip-prefix"),prefix); if (!prefix) return; unsigned int n = msg.length(); for (unsigned int i = 0; i < n; i++) { NamedString* str = msg.getParam(i); if (!str) continue; String name(str->name()); if (!name.startSkip(prefix,false)) continue; if (name.trimBlanks().null()) continue; sip.addHeader(name,*str); } } // Copy privacy related information from SIP message to Yate message static void copyPrivacy(Message& msg, const SIPMessage& sip) { bool anonip = (sip.getHeaderValue("Anonymity") &= "ipaddr"); const MimeHeaderLine* hl = sip.getHeader("Remote-Party-ID"); const MimeHeaderLine* pr = sip.getHeader("Privacy"); if (!(anonip || hl || pr)) return; const NamedString* p = hl ? hl->getParam("screen") : 0; if (p) msg.setParam("screened",*p); if (pr && (*pr &= "none")) { msg.setParam("privacy",String::boolText(false)); return; } bool privname = false; bool privuri = false; String priv; if (anonip) priv.append("addr",","); p = hl ? hl->getParam("privacy") : 0; if (p) { if ((*p &= "full") || (*p &= "full-network")) privname = privuri = true; else if ((*p &= "name") || (*p &= "name-network")) privname = true; else if ((*p &= "uri") || (*p &= "uri-network")) privuri = true; } if (pr) { if ((*pr &= "user") || pr->getParam("user")) privname = true; if ((*pr &= "header") || pr->getParam("header")) privuri = true; } if (privname) priv.append("name",","); if (privuri) priv.append("uri",","); if (pr) { if ((*pr &= "session") || pr->getParam("session")) priv.append("session",","); if ((*pr &= "critical") || pr->getParam("critical")) priv.append("critical",","); } if (priv) msg.setParam("privacy",priv); if (hl) { URI uri(*hl); const char* tmp = uri.getDescription(); if (tmp) msg.setParam("privacy_callername",tmp); tmp = uri.getUser(); if (tmp) msg.setParam("privacy_caller",tmp); tmp = uri.getHost(); if (tmp) msg.setParam("privacy_domain",tmp); const String* str = hl->getParam("party"); if (!TelEngine::null(str)) msg.setParam("remote_party",*str); str = hl->getParam("id-type"); if (!TelEngine::null(str)) msg.setParam("remote_id_type",*str); } } // Copy privacy related information from Yate message to SIP message static void copyPrivacy(SIPMessage& sip, const Message& msg) { String screened(msg.getValue(YSTRING("screened"))); String privacy(msg.getValue(YSTRING("privacy"))); if (screened.null() && privacy.null()) return; bool screen = screened.toBoolean(); bool anonip = (privacy.find("addr") >= 0); bool privname = (privacy.find("name") >= 0); bool privuri = (privacy.find("uri") >= 0); String rfc3323; // allow for a simple "privacy=yes" or similar if (privacy.toBoolean(false)) privname = privuri = true; // "privacy=no" is translated to RFC 3323 "none" else if (!privacy.toBoolean(true)) rfc3323 = "none"; if (anonip) sip.setHeader("Anonymity","ipaddr"); if (screen || privname || privuri) { const char* caller = msg.getValue(YSTRING("privacy_caller"),msg.getValue(YSTRING("caller"))); if (!caller) caller = "anonymous"; const char* domain = msg.getValue(YSTRING("privacy_domain"),msg.getValue(YSTRING("domain"))); if (!domain) domain = "domain"; String tmp = msg.getValue(YSTRING("privacy_callername"),msg.getValue(YSTRING("callername"),caller)); if (tmp) { MimeHeaderLine::addQuotes(tmp); tmp += " "; } tmp << ""; MimeHeaderLine* hl = new MimeHeaderLine("Remote-Party-ID",tmp); if (screen) hl->setParam("screen","yes"); if (privname && privuri) hl->setParam("privacy","full"); else if (privname) hl->setParam("privacy","name"); else if (privuri) hl->setParam("privacy","uri"); else hl->setParam("privacy","none"); const char* str = msg.getValue(YSTRING("remote_party")); if (str) hl->setParam("party",str); str = msg.getValue(YSTRING("remote_id_type")); if (str) hl->setParam("id-type",str); sip.addHeader(hl); } if (rfc3323.null()) { if (privname) rfc3323.append("user",";"); if (privuri) rfc3323.append("header",";"); if (privacy.find("session") >= 0) rfc3323.append("session",";"); if (rfc3323 && (privacy.find("critical") >= 0)) rfc3323.append("critical",";"); } if (rfc3323) sip.addHeader("Privacy",rfc3323); } // Check if the given body have the given type // Find the given type inside multiparts static inline MimeBody* getOneBody(MimeBody* body, const char* type) { return body ? body->getFirst(type) : 0; } // Check if the given body is a SDP one or find an enclosed SDP body // if it is a multipart static inline MimeSdpBody* getSdpBody(MimeBody* body) { if (!body) return 0; return static_cast(body->isSDP() ? body : body->getFirst("application/sdp")); } // Add a mime body parameter to a list of parameters // Remove quotes, trim blanks and convert to lower case before adding // Return false if the parameter wasn't added inline bool addBodyParam(NamedList& nl, const char* param, MimeBody* body, const char* bodyParam) { const NamedString* ns = body ? body->getParam(bodyParam) : 0; if (!ns) return false; String p = *ns; MimeHeaderLine::delQuotes(p); p.trimBlanks(); if (p.null()) return false; p.toLower(); nl.addParam(param,p); return true; } // Decode an application/isup body into 'msg' if configured to do so // The message's name and user data are restored before exiting, regardless the result // Return true if an ISUP message was succesfully decoded static bool doDecodeIsupBody(const DebugEnabler* debug, Message& msg, MimeBody* body) { if (!s_sipt_isup) return false; // Get a valid application/isup body MimeBinaryBody* isup = static_cast(getOneBody(body,"application/isup")); if (!isup) return false; // Remember the message's name and user data and fill parameters String name = msg; RefObject* userdata = msg.userData(); if (userdata) userdata->ref(); msg = "isup.decode"; msg.addParam("message-prefix","isup."); addBodyParam(msg,"isup.protocol-type",isup,"version"); addBodyParam(msg,"isup.protocol-basetype",isup,"base"); msg.addParam(new NamedPointer("rawdata",new DataBlock(isup->body()))); bool ok = Engine::dispatch(msg); // Clear added params and restore message if (!ok) { Debug(debug,DebugMild,"%s failed error='%s'", msg.c_str(),msg.getValue(YSTRING("error"))); msg.clearParam(YSTRING("error")); } msg.clearParam(YSTRING("rawdata")); msg = name; msg.userData(userdata); TelEngine::destruct(userdata); return ok; } // Build the body of a SIP message from an engine message // Encode an ISUP message from parameters received in msg if enabled to process them // Build a multipart/mixed body if more then one body is going to be sent static MimeBody* doBuildSIPBody(const DebugEnabler* debug, Message& msg, MimeSdpBody* sdp) { MimeBinaryBody* isup = 0; // Build isup while (s_sipt_isup) { String prefix = msg.getValue(YSTRING("message-prefix")); if (!msg.getParam(prefix + "message-type")) break; // Remember the message's name and user data String name = msg; RefObject* userdata = msg.userData(); if (userdata) userdata->ref(); DataBlock* data = 0; msg = "isup.encode"; if (Engine::dispatch(msg)) { NamedString* ns = msg.getParam(YSTRING("rawdata")); if (ns) { NamedPointer* np = static_cast(ns->getObject(YSTRING("NamedPointer"))); if (np) data = static_cast(np->userObject(YSTRING("DataBlock"))); } } if (data && data->length()) { isup = new MimeBinaryBody("application/isup",(const char*)data->data(),data->length()); isup->setParam("version",msg.getValue(prefix + "protocol-type")); const char* s = msg.getValue(prefix + "protocol-basetype"); if (s) isup->setParam("base",s); MimeHeaderLine* line = new MimeHeaderLine("Content-Disposition","signal"); line->setParam("handling","optional"); isup->appendHdr(line); } else { Debug(debug,DebugMild,"%s failed error='%s'", msg.c_str(),msg.getValue(YSTRING("error"))); msg.clearParam(YSTRING("error")); } // Restore message msg = name; msg.userData(userdata); TelEngine::destruct(userdata); break; } if (!isup) return sdp; if (!sdp) return isup; // Build multipart MimeMultipartBody* body = new MimeMultipartBody; body->appendBody(sdp); body->appendBody(isup); return body; } // Find an URI parameter separator. Accept '?' or '&' static inline int findURIParamSep(const String& str, int start) { if (start < 0) return -1; for (int i = start; i < (int)str.length(); i++) if (str[i] == '?' || str[i] == '&') return i; return -1; } bool YateSIPPartyHolder::change(String& dest, const String& src) { if (dest == src) return false; changing(); dest = src; return true; } bool YateSIPPartyHolder::change(int& dest, int src) { if (dest == src) return false; changing(); dest = src; return true; } void YateSIPPartyHolder::changing() { } // Check if a transport is used by our party YateSIPTransport* YateSIPPartyHolder::transport(bool ref) { Lock lock(m_partyMutex); YateSIPTransport* trans = 0; if (m_party) trans = static_cast(m_party->getTransport()); return (trans && (!ref || trans->ref())) ? trans : 0; } // Set the held party. Referrence it before void YateSIPPartyHolder::setParty(SIPParty* party) { Lock lck(m_partyMutex); if (party == m_party) return; if (party && !party->ref()) party = 0; if (party != m_party) DDebug(&plugin,DebugAll,"YateSIPPartyHolder set party (%p) [%p]",party,this); SIPParty* tmp = m_party; m_party = party; lck.drop(); TelEngine::destruct(tmp); } // Set the party of a non answer message bool YateSIPPartyHolder::setSipParty(SIPMessage* message, const YateSIPLine* line, bool useEp, const char* host, int port) const { if (!message || message->isAnswer()) return false; Lock lck(m_partyMutex); if (!m_party) { lck.drop(); if (useEp && plugin.ep()) plugin.ep()->buildParty(message,host,port,line); return 0 != message->getParty(); } message->setParty(m_party); lck.drop(); if (line) line->setupAuth(message); return true; } // Change party and its transport. Return true on success bool YateSIPPartyHolder::buildParty(bool force) { XDebug(&plugin,DebugAll,"YateSIPPartyHolder::buildParty(%s,%s,%d,%s,%d) force=%u [%p]", protoName(),m_transLocalAddr.c_str(),m_transLocalPort, m_transRemoteAddr.c_str(),m_transRemotePort,force,this); if (!force) { Lock lock(m_partyMutex); if (m_party) return true; } YateSIPTCPTransport* tcpTrans = 0; YateSIPUDPTransport* udpTrans = 0; bool initTcp = false; if (m_transId) { YateSIPTransport* trans = 0; if (plugin.ep()) trans = plugin.ep()->findTransport(m_transId); if (trans) { tcpTrans = trans->tcpTransport(); if (!tcpTrans) { udpTrans = trans->udpTransport(); if (!udpTrans) TelEngine::destruct(trans); } } } if (!(tcpTrans || udpTrans)) { if (protocol() == Udp) { SocketAddr addr(AF_INET); if (plugin.ep()) { if (!m_transLocalAddr) udpTrans = plugin.ep()->defTransport(); else { addr.host(m_transLocalAddr); udpTrans = plugin.ep()->findUdpTransport(addr.host(),m_transLocalPort); } } } else { initTcp = true; bool tls = (protocol() == Tls); if (tls || protocol() == Tcp) tcpTrans = new YateSIPTCPTransport(tls,m_transLocalAddr, m_transRemoteAddr,m_transRemotePort); else Debug(DebugStub,"YateSIPPartyHolder::buildParty() transport %s not implemented", protoName()); } } SIPParty* p = 0; if (udpTrans) { SocketAddr addr(AF_INET); addr.host(m_transRemoteAddr); addr.port(m_transRemotePort); p = new YateUDPParty(udpTrans,addr); } else if (tcpTrans) { p = tcpTrans->getParty(); if (!p) p = new YateTCPParty(tcpTrans); } setParty(p); TelEngine::destruct(p); if (tcpTrans && initTcp) { // TODO: handle other params: maxpkt, thread prio tcpTrans->init(NamedList::empty(),true); } TelEngine::destruct(udpTrans); TelEngine::destruct(tcpTrans); return m_party != 0; } // Change party and its transport. Return true on success bool YateSIPPartyHolder::setParty(const NamedList& params, bool force, const String& prefix, const String& defRemoteAddr, int defRemotePort) { const String& transId = params[prefix + "connection_id"]; if (!(force || transId || params.getParam(prefix + "ip_transport") || params.getBoolValue(prefix + "ip_transport_tcp"))) { setParty(); return false; } if (change(m_transId,transId)) Debug(&plugin,DebugAll,"YateSIPPartyHolder transport id changed to '%s' [%p]", m_transId.c_str(),this); updateProto(params,prefix); updateRemoteAddr(params,prefix,defRemoteAddr,defRemotePort); updateLocalAddr(params,prefix); return buildParty(); } // Update transport type. Return true if changed bool YateSIPPartyHolder::updateProto(const NamedList& params, const String& prefix) { int proto = lookupProtoAny(params[prefix + "ip_transport"]); if (proto == Unknown) { if (!params.getBoolValue(prefix + "ip_transport_tcp")) { // Check transport id prefix if (m_transId.startsWith("tcp:",false)) proto = Tcp; else if (m_transId.startsWith("tls:",false)) proto = Tls; else proto = Udp; } else if (!params.getBoolValue(prefix + "ip_transport_tls")) proto = Tcp; else proto = Tls; } bool chg = change(m_proto,proto); if (chg) Debug(&plugin,DebugAll,"YateSIPPartyHolder transport proto changed to '%s' [%p]", protoName(),this); return chg; } // Update transport remote addr/port. Return true if changed bool YateSIPPartyHolder::updateRemoteAddr(const NamedList& params, const String& prefix, const String& defRemoteAddr, int defRemotePort) { const char* addr = params.getValue(prefix + "ip_transport_remoteip",defRemoteAddr); int port = params.getIntValue(prefix + "ip_transport_remoteport",defRemotePort); if (port <= 0) port = sipPort(protocol() != Tls); bool chg = change(m_transRemoteAddr,addr); chg = change(m_transRemotePort,port) || chg; if (chg) Debug(&plugin,DebugAll,"YateSIPPartyHolder remote addr changed to '%s:%d' [%p]", m_transRemoteAddr.c_str(),m_transRemotePort,this); return chg; } // Update transport local addr/port. Return true if changed bool YateSIPPartyHolder::updateLocalAddr(const NamedList& params, const String& prefix) { bool chg = change(m_transLocalAddr,params[prefix + "ip_transport_localip"]); int port = params.getIntValue(prefix + "ip_transport_localport"); chg = change(m_transLocalPort,port) || chg; if (chg) Debug(&plugin,DebugAll,"YateSIPPartyHolder local addr changed to '%s:%d' [%p]", m_transLocalAddr.c_str(),m_transLocalPort,this); return chg; } // Update RTP local address void YateSIPPartyHolder::setRtpLocalAddr(String& addr, Message* m) { addr.clear(); if (m) addr = m->getValue(YSTRING("rtp_localip")); if (!addr && m_party) { Lock lock(m_partyMutex); YateSIPTransport* t = YOBJECT(YateSIPTransport,m_party); if (t && !t->ref()) t = 0; lock.drop(); if (t) t->rtpAddr(addr); TelEngine::destruct(t); } DDebug(&plugin,DebugAll,"YateSIPPartyHolder rtp local addr is '%s' [%p]", addr.c_str(),this); } YateSIPListener::YateSIPListener(const String& addr, int port) : m_bindInterval(0), m_nextBind(0), m_bind(true), m_address(addr), m_port(port) { } // Check bind now flag bool YateSIPListener::bindNow(Mutex* mutex) { if (!m_bind) return false; Lock lck(mutex); bool old = m_bind; m_bind = false; return old; } // Check if address would change bool YateSIPListener::addrWouldChange(Mutex* mutex, bool udp, const String& addr, int port) { SocketAddr existing(udp ? AF_INET : PF_INET); Lock lck(mutex); existing.host(m_address); existing.port(m_port); lck.drop(); SocketAddr newAddr(udp ? AF_INET : PF_INET); newAddr.host(addr); newAddr.port(port); return existing != newAddr; } // Set addr/port and bind flag. Return the bind flag bool YateSIPListener::setAddr(const String& addr, int port) { if (m_address != addr) { m_bind = true; m_address = addr; } if (m_port != port) { m_bind = true; m_port = port; } return m_bind; } // Initialize a socket Socket* YateSIPListener::initSocket(int proto, const String& name, SocketAddr& lAddr, Mutex* mutex, int backLogBuffer, bool forceBind, String& reason) { reason = ""; Lock lck(mutex); String addr = m_address; int port = m_port; lck.drop(); bool udp = (proto == ProtocolHolder::Udp); const char* type = ProtocolHolder::lookupProtoName(proto); Debug(&plugin,DebugAll,"Listener(%s,'%s') initializing socket addr='%s:%d'", type,name.c_str(),addr.c_str(),port); Socket* sock = 0; if (udp) sock = new Socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); else sock = new Socket(PF_INET,SOCK_STREAM); // Use a while() to break to the end while (true) { if (!sock->valid()) { reason = "Create socket failed"; break; } if (!udp) sock->setReuse(); #ifdef SO_RCVBUF // Set UDP buffer size if (udp && backLogBuffer > 0) { int buflen = backLogBuffer; if (buflen < 4096) buflen = 4096; if (sock->setOption(SOL_SOCKET,SO_RCVBUF,&buflen,sizeof(buflen))) { buflen = 0; socklen_t sz = sizeof(buflen); if (sock->getOption(SOL_SOCKET,SO_RCVBUF,&buflen,&sz)) Debug(&plugin,DebugNote,"Listener(%s,'%s') buffer size is %d (requested %d)", type,name.c_str(),buflen,backLogBuffer); else Debug(&plugin,DebugWarn, "Listener(%s,'%s') could not get UDP buffer size (requested %d)", type,name.c_str(),backLogBuffer); } else Debug(&plugin,DebugWarn,"Listener(%s,'%s') could not set buffer size %d", type,name.c_str(),buflen); } #endif // Bind the socket lAddr.host(addr); lAddr.port(port); bool ok = sock->bind(lAddr); if (!ok && forceBind) { String error; Thread::errorString(error,sock->error()); Debug(&plugin,DebugWarn, "Listener(%s,'%s') unable to bind on '%s:%d' - trying a random port. %d '%s'", type,name.c_str(),lAddr.host().c_str(),lAddr.port(),sock->error(),error.c_str()); lAddr.port(0); ok = sock->bind(lAddr); if (ok && !sock->getSockName(lAddr)) { reason = "Failed to retrieve bind address"; break; } } if (!ok) { reason = "Bind failed"; break; } if (!sock->setBlocking(false)) { reason = "Set non blocking mode failed"; break; } if (!udp && !sock->listen(backLogBuffer)) { reason = "Listen failed"; break; } break; } if (!reason) { Debug(&plugin,DebugInfo,"Listener(%s,'%s') started on '%s:%d'", type,name.c_str(),lAddr.host().safe(),lAddr.port()); m_nextBind = 0; m_bindInterval = 0; return sock; } String s; Thread::errorString(s,sock->error()); Debug(&plugin,DebugWarn,"Listener(%s,'%s') %s. %d: '%s'", type,name.c_str(),reason.c_str(),sock->error(),s.c_str()); if (!m_bindInterval) m_bindInterval = s_bindRetryMs; else if (m_bindInterval < BIND_RETRY_MAX) m_bindInterval *= 2; m_nextBind = Time::now() + m_bindInterval * 1000; YateSIPTransport::resetSocket(sock,0); return 0; } YateSIPTransport::YateSIPTransport(int proto, const String& id, Socket* sock, int stat) : Mutex(true,"YateSIPTransport"), ProtocolHolder(proto), m_id(id), m_status(stat), m_statusChgTime(Time::secNow()), m_sock(sock), m_maxpkt(1500), m_local(proto == Udp ? AF_INET : PF_INET), m_remote(proto == Udp ? AF_INET : PF_INET), m_worker(0), m_initialized(false) { Debug(&plugin,DebugAll,"Transport(%s) created [%p]",m_id.c_str(),this); } // Initialize transport bool YateSIPTransport::init(const NamedList& params, const NamedList& defs, bool first, Thread::Priority prio) { static String s_maxPkt = "maxpkt"; lock(); m_initialized = true; m_rtpLocalAddr.clear(); if (udpTransport()) { int v = params.getIntValue(s_maxPkt,defs.getIntValue(s_maxPkt,m_maxpkt)); m_maxpkt = getMaxpkt(v,1500); // Set RTP addr from bind address if the transport was not built from // 'general' section if (params != YSTRING("general") && !isAllIfacesAddr(m_local.host())) m_rtpLocalAddr = m_local.host(); } else { m_maxpkt = getMaxpkt(params.getIntValue(YSTRING("tcp_maxpkt"),m_maxpkt),m_maxpkt); // Override rtp ip for tcp outgoing if (tcpTransport() && tcpTransport()->outgoing()) { Lock lck(s_globalMutex); m_rtpLocalAddr = s_tcpOutRtpip; } } m_rtpLocalAddr = params.getValue(YSTRING("rtp_localip"),m_rtpLocalAddr); unlock(); // Done if not first if (!first) return true; if (m_sock) { m_sock->getSockName(m_local); m_sock->getPeerName(m_remote); } m_worker = new YateSIPTransportWorker(this,prio); if (m_worker->startup()) return true; Debug(&plugin,DebugWarn,"Transport(%s) failed to start worker thread [%p]",m_id.c_str(),this); m_reason = "Failed to start worker"; m_worker = 0; return false; } void YateSIPTransport::printSendMsg(const SIPMessage* msg, const SocketAddr* addr) { if (!msg) return; if (!plugin.debugAt(DebugInfo)) return; String raddr; if (addr) raddr << addr->host() << ":" << addr->port(); else raddr << m_remote.host() << ":" << m_remote.port(); if (!plugin.filterDebug(raddr)) return; String tmp; getMsgLine(tmp,msg); if (addr) raddr = " to " + raddr; else raddr.clear(); String buf((char*)msg->getBuffer().data(),msg->getBuffer().length()); Debug(&plugin,DebugInfo,"'%s' sending %s %p%s [%p]\r\n------\r\n%s------", m_protoAddr.c_str(),tmp.c_str(),msg,raddr.safe(),this,buf.c_str()); } // Print received messages to output void YateSIPTransport::printRecvMsg(const char* buf, int len) { if (!buf) return; if (!plugin.debugAt(DebugInfo)) return; String raddr; raddr << m_remote.host() << ":" << m_remote.port(); if (!plugin.filterDebug(raddr)) return; String tmp; if (udpTransport()) raddr = " from " + raddr; else { raddr.clear(); tmp.assign(buf,len); buf = tmp; } Debug(&plugin,DebugInfo, "'%s' received %d bytes SIP message%s [%p]\r\n------\r\n%s------", m_protoAddr.c_str(),len,raddr.safe(),this,buf); } // Add transport data yate message void YateSIPTransport::fillMessage(Message& msg, bool addRoute) { msg.setParam("connection_id",toString()); msg.setParam("connection_reliable",String::boolText(0 != tcpTransport())); if (addRoute) { msg.setParam("route_params","oconnection_id"); msg.setParam("oconnection_id",toString()); } } // Stop the worker. Remove from global list void YateSIPTransport::terminate(const char* reason) { XDebug(&plugin,DebugInfo,"YateSIPTransport::terminate(%s) [%p]",reason,this); changeStatus(Terminating); if (m_worker) { bool wait = false; lock(); if (m_worker) { if (Thread::current() != m_worker) wait = true; else m_worker->m_transport = 0; m_worker->cancel(); } unlock(); if (wait) { unsigned int n = 500; while (m_worker && n--) Thread::idle(); if (m_worker) Debug(&plugin,DebugFail,"Transport(%s) terminating with worker running [%p]", m_id.c_str(),this); } } if (!TelEngine::null(reason)) { Lock lock(this); if (!m_reason) m_reason = reason; } changeStatus(Terminated); } const String& YateSIPTransport::toString() const { return m_id; } // Reset and delete a socket void YateSIPTransport::resetSocket(Socket*& sock, int linger) { if (!sock) return; sock->setLinger(linger); delete sock; sock = 0; } void YateSIPTransport::destroyed() { terminate("Destroyed"); resetSocket(m_sock,-1); Debug(&plugin,DebugAll,"Transport(%s) destroyed [%p]",m_id.c_str(),this); RefObject::destroyed(); } // Change transport status. Notify it void YateSIPTransport::changeStatus(int stat) { Lock lock(this); if (stat == m_status || m_status == Terminated) return; unsigned int t = Time::secNow(); DDebug(&plugin,DebugAll,"Transport(%s) changed status old=%s new=%s statustime=%u [%p]", m_id.c_str(),statusName(m_status),statusName(stat),t - m_statusChgTime,this); m_statusChgTime = t; m_status = stat; String reason; if (m_status == Terminated) { reason = m_reason; m_reason.clear(); } lock.drop(); statusChanged(); if (plugin.ep()) plugin.ep()->transportChangedStatus(this,m_status,reason); } // Handle received messages, set party, add to engine void YateSIPTransport::receiveMsg(SIPMessage*& msg) { if (!msg) return; if (!msg->isAnswer()) { SIPParty* party = 0; YateSIPUDPTransport* udp = udpTransport(); YateSIPTCPTransport* tcp = tcpTransport(); if (udp) { URI uri(msg->uri); YateSIPLine* line = plugin.findLine(m_remote.host(),m_remote.port(),uri.getUser()); const char* host = 0; int port = -1; if (line && line->getLocalPort()) { host = line->getLocalAddr(); port = line->getLocalPort(); } if (!host) host = m_local.host(); if (port <= 0) port = m_local.port(); party = new YateUDPParty(udp,m_remote,&port,host); } else if (tcp) { party = tcp->getParty(); if (!party) { party = new YateTCPParty(tcp); DDebug(&plugin,DebugAll, "Transport(%s) built tcp party (%p) for received message (%p) [%p]", m_id.c_str(),party,msg,this); } } if (party) { msg->setParty(party); TelEngine::destruct(party); } } if (plugin.ep() && plugin.ep()->engine()) plugin.ep()->engine()->addMessage(msg); TelEngine::destruct(msg); } // Print socket read error to output void YateSIPTransport::printReadError() { if (m_sock->canRetry()) return; m_reason = "Socket read error:"; addSockError(m_reason,*m_sock); Debug(&plugin,DebugWarn,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this); } // Print socket write error to output void YateSIPTransport::printWriteError(int res, unsigned int len) { if (res == (int)len) { XDebug(&plugin,DebugAll,"Transport(%s) sent %u bytes [%p]", m_id.c_str(),len,this); return; } if (res >= 0) { Debug(&plugin,DebugAll,"Transport(%s) sent %d/%u [%p]",m_id.c_str(),res,len,this); return; } if (m_sock->canRetry()) return; m_reason = "Socket send error:"; addSockError(m_reason,*m_sock); Debug(&plugin,DebugWarn,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this); } // Set m_protoAddr from local/remote ip/port or reset it void YateSIPTransport::setProtoAddr(bool set) { Lock lck(this); if (!set) { m_protoAddr = ""; return; } m_protoAddr << protoName(false) << ":" << m_local.host() << ":" << m_local.port(); if (!udpTransport()) m_protoAddr << "-" << m_remote.host() << ":" << m_remote.port(); } YateSIPUDPTransport::YateSIPUDPTransport(const String& id) : YateSIPTransport(Udp,id,0,Idle), m_default(false), m_forceBind(true), m_bufferReq(0) { } // (Re)Initialize the transport bool YateSIPUDPTransport::init(const NamedList& params, const NamedList& defs, bool first, Thread::Priority prio) { bool ok = YateSIPTransport::init(params,defs,first,prio); m_default = params.getBoolValue("default",toString() == YSTRING("general")); m_forceBind = params.getBoolValue("udp_force_bind",true); m_bufferReq = params.getIntValue("buffer",defs.getIntValue("buffer")); if (first) setAddr(params.getValue("addr","0.0.0.0"),params.getIntValue("port",5060)); Debug(&plugin,DebugAll, "Transport(%s) initialized addr='%s:%d' default=%s maxpkt=%u rtp_localip=%s [%p]", m_id.c_str(),m_address.c_str(),m_port,String::boolText(m_default),m_maxpkt, m_rtpLocalAddr.c_str(),this); return ok; } // Send data void YateSIPUDPTransport::send(const void* data, unsigned int len, const SocketAddr& addr) { if (!m_sock) return; Lock lck(this); if (!m_sock) return; int sent = m_sock->sendTo(data,len,addr); printWriteError(sent,len); } // Process data (read/send). // Return 0 to continue processing, positive to sleep (usec), // negative to terminate and destroy int YateSIPUDPTransport::process() { bool force = bindNow(this); if (force || !m_sock) { if (m_sock) { changeStatus(Idle); Lock lck(this); YateSIPTransport::resetSocket(m_sock,-1); m_local.host(""); m_local.port(0); setProtoAddr(false); } if (!force && m_nextBind > Time::now()) return Thread::idleUsec(); String reason; SocketAddr addr(PF_INET); Socket* sock = initSocket(ProtocolHolder::Udp,toString(),addr,this, m_bufferReq,m_forceBind,reason); if (sock) { lock(); m_sock = sock; m_local = addr; unlock(); setProtoAddr(true); changeStatus(Connected); } else { changeStatus(Idle); Lock lck(this); m_reason = reason; } if (!m_sock) return Thread::idleUsec(); } int& evc = YateSIPEndPoint::s_evCount; // Do nothing if the endpoint is flooded with events or terminating if (!(YateSIPEndPoint::canRead() || ((evc & 3) == 0))) return Thread::idleUsec(); int retVal = 0; // Check if we can read (select is available) // Wait up to the platform idle time if we had no events in last run if (m_sock->canSelect()) { bool ok = false; if (m_sock->select(&ok,0,0,Thread::idleUsec())) { if (!ok) return 0; } else { // Select failed if (m_sock->canRetry()) return Thread::idleUsec(); String tmp; Thread::errorString(tmp,m_sock->error()); Debug(&plugin,DebugWarn,"Transport(%s) select failed: %d '%s' [%p]", m_id.c_str(),m_sock->error(),tmp.c_str(),this); return Thread::idleUsec(); } } else retVal = Thread::idleUsec(); // We can read the data m_buffer.resize(m_maxpkt); int res = m_sock->recvFrom((void*)m_buffer.data(),m_buffer.length() - 1,m_remote); if (res <= 0) { printReadError(); return retVal; } if (res < 72) { DDebug(&plugin,DebugInfo, "Transport(%s) received short SIP message of %d bytes from %s:%d [%p]", m_id.c_str(),res,m_remote.host().c_str(),m_remote.port(),this); return 0; } char* b = (char*)m_buffer.data(); b[res] = 0; if (s_printMsg) printRecvMsg(b,res); SIPMessage* msg = SIPMessage::fromParsing(0,b,res); receiveMsg(msg); return 0; } // Outgoing YateSIPTCPTransport::YateSIPTCPTransport(bool tls, const String& laddr, const String& raddr, int rport) : YateSIPTransport(tls ? Tls : Tcp,String::empty(),0,Idle), m_outgoing(true), m_party(0), m_sent(-1), m_idleInterval(TCP_IDLE_DEF), m_idleTimeout(0), m_flowTimer(false), m_keepAlivePending(false), m_msg(0), m_sipBufOffs(0), m_contentLen(0), m_remoteAddr(raddr), m_remotePort(rport), m_localAddr(laddr), m_connectRetry(s_tcpConnectRetry), m_nextConnect(0) { m_maxpkt = s_tcpMaxpkt; if (m_remotePort <= 0) m_remotePort = sipPort(protocol() != Tls); m_id << (tls ? "tls:" : "tcp:") << getTransIndex() << "-" << raddr << ":" << m_remotePort; if (plugin.ep()) plugin.ep()->addTcpTransport(this); } // Incoming YateSIPTCPTransport::YateSIPTCPTransport(Socket* sock, bool tls) : YateSIPTransport(tls ? Tls : Tcp,String::empty(),sock,sock ? Connected : Idle), m_outgoing(false), m_party(0), m_sent(-1), m_idleInterval(TCP_IDLE_DEF), m_idleTimeout(0), m_flowTimer(false), m_keepAlivePending(false), m_msg(0), m_sipBufOffs(0), m_contentLen(0), m_remotePort(0), m_connectRetry(0), m_nextConnect(0) { m_maxpkt = s_tcpMaxpkt; m_id << (tls ? "tls:" : "tcp:"); if (m_sock) { m_sock->getSockName(m_local); m_sock->getPeerName(m_remote); m_id << m_local.host() << ":" << m_local.port(); m_id << "-" << m_remote.host() << ":" << m_remote.port();; setProtoAddr(true); } else m_id << getTransIndex(); if (plugin.ep()) plugin.ep()->addTcpTransport(this); } YateTCPParty* YateSIPTCPTransport::getParty() { Lock lock(this); return (m_party && m_party->ref()) ? m_party : 0; } // (Re)Initialize the transport bool YateSIPTCPTransport::init(const NamedList& params, bool first, Thread::Priority prio) { bool ok = YateSIPTransport::init(params,NamedList::empty(),first,prio); m_idleInterval = tcpIdleInterval(params.getIntValue(YSTRING("tcp_idle"),s_tcpIdle)); setIdleTimeout(); Debug(&plugin,DebugAll, "Transport(%s) initialized maxpkt=%u rtp_localip=%s tcp_idle=%u [%p]", m_id.c_str(),m_maxpkt,m_rtpLocalAddr.c_str(),m_idleInterval,this); return ok; } // Set flow timer flag and idle interval (in seconds) // Reset idle timeout void YateSIPTCPTransport::setFlowTimer(bool on, unsigned int interval) { Lock lock(this); m_flowTimer = on; if (m_flowTimer || m_outgoing || (!m_outgoing && m_idleInterval < interval)) m_idleInterval = interval; Debug(&plugin,DebugInfo,"Transport(%s) flow timer is '%s' idle interval is %u seconds [%p]", m_id.c_str(),String::boolText(m_flowTimer),m_idleInterval,this); setIdleTimeout(); } // Send data void YateSIPTCPTransport::send(SIPEvent* event) { SIPMessage* msg = event->getMessage(); if (!msg || s_engineHalt) return; Lock lock(this); if (m_status == Terminated) return; if (m_queue.find(msg) || !msg->ref()) return; m_queue.append(msg); #ifdef XDEBUG String tmp; getMsgLine(tmp,msg); Debug(&plugin,DebugAll,"Transport(%s) enqueued (%p,%s) [%p]", m_id.c_str(),msg,tmp.c_str(),this); #endif } // Process data (read/send) int YateSIPTCPTransport::process() { if (s_engineHalt) { // Stop processing Lock lck(this); // Last chance to send pending data ObjList* first = m_queue.skipNull(); if (first && m_sock && m_sock->valid()) { DataBlock buf; for (ObjList* o = first; o && buf.length() < 4096; o = o->skipNext()) { SIPMessage* msg = static_cast(o->get()); if (s_printMsg && (o != first || m_sent < 0)) printSendMsg(msg); if (o != first || m_sent <= 0) buf += msg->getBuffer(); else { int remaining = msg->getBuffer().length() - m_sent; if (remaining > 0) buf.assign(((char*)msg->getBuffer().data()) + m_sent,remaining); } } if (buf.length()) { DDebug(&plugin,DebugAll,"Transport(%s) sending last %u bytes [%p]", m_id.c_str(),buf.length(),this); m_sock->writeData(buf.data(),buf.length()); } } m_queue.clear(); // Terminate now incoming with no reference // Remember: the worker is referencing us if (!m_outgoing && refcount() == 2) return -1; return 2000; } if (!(m_sock && m_sock->valid())) { if (!m_outgoing) return -1; if (!m_connectRetry || s_engineStop) return -1; int retVal = Thread::idleUsec(); if (m_nextConnect > Time::now()) return retVal; int conn = connect(); if (conn > 0) { m_connectRetry = s_tcpConnectRetry; m_nextConnect = 0; setIdleTimeout(); } else if (!conn) { m_connectRetry--; DDebug(&plugin,DebugAll,"Transport(%s) connect retry is %u [%p]", toString().c_str(),m_connectRetry,this); if (m_connectRetry) m_nextConnect = Time::now() + s_tcpConnectInterval; else retVal = -1; } else { m_connectRetry = 0; retVal = -1; } return retVal; } Time time; bool sent = false; // Send pending data/keepalive if (!sendPending(time,sent)) { resetConnection(); return m_outgoing ? 0 : -1; } // Read data bool read = false; if (!readData(time,read)) { resetConnection(); return m_outgoing ? 0 : -1; } // Idle incoming with refcount=2 (the worker is referencing us): terminate if (!m_outgoing && m_idleTimeout < time) { if (refcount() == 2) { m_reason = "Connection idle timeout"; Debug(&plugin,DebugInfo,"Transport(%s) idle [%p]",m_id.c_str(),this); return -1; } setIdleTimeout(time); } return read ? 0 : Thread::idleUsec(); } void YateSIPTCPTransport::destroyed() { TelEngine::destruct(m_msg); YateSIPTransport::destroyed(); } // Status changed notification for descendents void YateSIPTCPTransport::statusChanged() { Lock lock(this); if (m_status == Terminated) { // Remove messages now: they keep a party who is keeping a reference to us m_queue.clear(); m_sent = -1; } } // Reset transport's party void YateSIPTCPTransport::resetParty(YateTCPParty* party, bool set) { if (!party) return; Lock lock(this); if (!m_party) { if (!set) { Debug(&plugin,DebugNote, "Transport(%s) party (%p) trying to reset empty [%p]", m_id.c_str(),party,this); return; } } else if (set || m_party != party) { int level = DebugNote; #ifdef DEBUG if (set && m_party != party) level = DebugFail; #endif Debug(&plugin,level,"Transport(%s) party (%p) trying to %sset (%p) [%p]", m_id.c_str(),party,set ? "" : "re",m_party,this); return; } m_party = set ? party : 0; DDebug(&plugin,DebugAll,"Transport(%s) party changed to (%p) [%p]", m_id.c_str(),m_party,this); } // Connect an outgoing transport. Terminate the socket before it int YateSIPTCPTransport::connect(u_int64_t connToutUs) { resetConnection(); Socket* sock = new Socket(PF_INET,SOCK_STREAM); int retVal = -1; m_reason.clear(); // Use a while() to break to the end while (true) { if (!m_remoteAddr) { m_reason = "Empty remote address"; break; } bool ok = true; // Bind to local ip SocketAddr lip(PF_INET); if (m_localAddr) { lip.host(m_localAddr); if (lip.host()) { ok = sock->bind(lip); if (!ok) { m_reason << "Failed to bind on '" << lip.host() << "' (" << m_localAddr << "). "; addSockError(m_reason,*sock); } } else m_reason << "Invalid local address '" << m_localAddr << "'"; } if (!ok) break; // Allow connect retry retVal = 0; ok = false; SocketAddr a(PF_INET); a.host(m_remoteAddr); a.port(m_remotePort); if (!a.host()) { m_reason << "Failed to resolve '" << m_remoteAddr << "'"; break; } // Use async connect if (connToutUs && !(sock->canSelect() && sock->setBlocking(false))) { connToutUs = 0; if (sock->canSelect()) { String tmp; addSockError(tmp,*sock); Debug(&plugin,DebugInfo, "Transport(%s) using sync connect (async set failed).%s [%p]", m_id.c_str(),tmp.c_str(),this); } else Debug(&plugin,DebugInfo, "Transport(%s) using sync connect (select() not available) [%p]", m_id.c_str(),this); } u_int64_t start = connToutUs ? Time::now() : 0; unsigned int intervals = 0; if (start) { intervals = (unsigned int)(connToutUs / Thread::idleUsec()); // Make sure we wait for at least 1 timeout interval if (!intervals) intervals = 1; } String domain; if (a.host() != m_remoteAddr) domain << " (" << m_remoteAddr << ")"; Debug(&plugin,DebugAll, "Transport(%s) attempt to connect to '%s:%d'%s localip=%s [%p]", m_id.c_str(),a.host().c_str(),a.port(),domain.safe(), lip.host().safe(),this); ok = (0 != sock->connect(a)); bool timeout = false; bool stop = false; // Async connect in progress if (!ok && sock->inProgress()) { bool done = false; bool event = false; while (intervals && !(done || event || stop)) { if (!sock->select(0,&done,&event,Thread::idleUsec())) break; intervals--; stop = Thread::check(false) || Engine::exiting(); } timeout = !intervals && !(done || event); if (!stop && sock && !sock->error() && (done || event) && sock->updateError()) ok = !sock->error(); } if (ok) { // TLS? if (tls() && !plugin.socketSsl(&sock,false)) { m_reason = "SSL not available locally"; retVal = -1; ok = false; } if (ok) { if (!Thread::check(false)) retVal = 1; else { m_reason = "Cancelled"; retVal = -1; } } } else if (!stop) { m_reason << "Failed to connect to '" << a.host() << ":" << a.port() << "'"; m_reason << domain; if (timeout) m_reason << " . Connect timeout"; else addSockError(m_reason,*sock); } break; } if (retVal > 0) resetConnection(sock); else { int level = DebugWarn; if (!m_reason) { if (Thread::check(false) || Engine::exiting()) { level = DebugInfo; m_reason = "Connect cancelled"; } else m_reason = "Connect failed"; } Debug(&plugin,level,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this); resetSocket(sock,0); } return retVal; } // Send pending messages, return false on failure bool YateSIPTCPTransport::sendPending(const Time& time, bool& sent) { sent = false; if (!m_sock) return false; int attempts = 3; while (attempts--) { Lock lock(this); ObjList* o = m_queue.skipNull(); SIPMessage* msg = o ? static_cast(o->get()) : 0; if (msg && m_sent < 0) { m_sent = 0; XDebug(&plugin,DebugAll,"Transport(%s) dequeued (%p) [%p]", m_id.c_str(),msg,this); if (s_printMsg) printSendMsg(msg); } else if (!msg) { m_sent = -1; break; } const DataBlock& buf = msg->getBuffer(); sent = true; int len = buf.length(); if (len > m_sent) { char* b = (char*)(buf.data()); len -= m_sent; int wr = m_sock->writeData(b + m_sent,len); printWriteError(wr,len); if (wr > 0) { m_sent += wr; // Outgoing: reset keep alive timer if (m_outgoing) setIdleTimeout(time); } else if (wr && !m_sock->canRetry()) return false; } if (m_sent >= (int)buf.length()) { #ifdef DEBUG String tmp; getMsgLine(tmp,msg); Debug(&plugin,DebugAll,"Transport(%s) sent (%p,%s) [%p]", m_id.c_str(),msg,tmp.c_str(),this); #endif o->remove(); m_sent = -1; if (m_keepAlivePending) { m_keepAlivePending = false; if (!sendKeepAlive(false)) return false; } continue; } break; } // Keep alive? if (m_outgoing && !sent && m_idleTimeout <= time) { if (sendKeepAlive(true)) { sent = true; setIdleTimeout(time); } else return false; } return true; } // Read data bool YateSIPTCPTransport::readData(const Time& time, bool& read) { read = false; m_buffer.resize(m_maxpkt); int res = m_sock->readData((void*)m_buffer.data(),m_buffer.length() - 1); if (res < 0) { printReadError(); return m_sock->canRetry(); } if (!res) { m_reason = "Network down"; Debug(&plugin,DebugNote,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this); return false; } read = true; char* b = (char*)m_buffer.data(); b[res] = 0; XDebug(&plugin,DebugAll,"%s current buffer '%s' read %d: %s [%p]", m_id.c_str(),m_sipBuffer.c_str(),res,b,this); m_sipBuffer << b; if (!m_msg) { // Always skip blanks before message start skipSpaces(m_sipBuffer,false); if (!m_outgoing && m_sipBuffer.startSkip("\r\n\r\n")) { // RFC5626: send CR/LF in response now or after the next message lock(); m_keepAlivePending = (0 != m_queue.skipNull()); unlock(); if (!(m_keepAlivePending || sendKeepAlive(false))) return false; } } while (m_sipBuffer.length() && m_sipBuffer.length() >= 72) { if (!m_msg) { m_sipBufOffs = 0; m_contentLen = 0; // Skip spaces from message start: it might be keep alive if (skipSpaces(m_sipBuffer) && m_sipBuffer.length() < 72) break; // Find an empty line m_sipBufOffs = getEmptyLine(m_sipBuffer); if (m_sipBufOffs > m_sipBuffer.length()) { m_sipBufOffs = 0; if (m_sipBuffer.length() <= m_maxpkt) break; return overflow(m_sipBuffer.length()); } if (m_sipBufOffs > m_maxpkt) return overflow(m_sipBufOffs); // Parse the message headers m_msg = SIPMessage::fromParsing(0,m_sipBuffer,m_sipBufOffs,&m_contentLen); if (!m_msg) { m_reason = "Received invalid message"; String tmp(m_sipBuffer,m_sipBufOffs); Debug(&plugin,DebugNote, "'%s' got invalid message [%p]\r\n------\r\n%s------", m_id.c_str(),this,tmp.c_str()); return false; } // Check now if expected message length exceeds maxpkt unsigned int expected = m_sipBufOffs + m_contentLen; if (expected > m_maxpkt) return overflow(expected); } // Expecting message body ? if (m_contentLen) { if (m_sipBufOffs + m_contentLen > m_sipBuffer.length()) break; m_msg->buildBody(m_sipBuffer + m_sipBufOffs,m_contentLen); m_sipBufOffs += m_contentLen; m_contentLen = 0; } if (s_printMsg) printRecvMsg(m_sipBuffer,m_sipBufOffs); SIPMessage* msg = m_msg; m_msg = 0; receiveMsg(msg); m_sipBuffer = m_sipBuffer.substr(m_sipBufOffs); m_sipBufOffs = 0; } // Got data: reset timeout for incoming and connection check for all if (!m_outgoing) setIdleTimeout(time); return true; } // Reset socket and status void YateSIPTCPTransport::resetConnection(Socket* sock) { Lock lck(this); DDebug(&plugin,DebugAll,"Transport(%s) resetting connection sock=%p [%p]", m_id.c_str(),sock,this); // Reset send/recv data TelEngine::destruct(m_msg); m_sent = -1; m_sipBuffer.clear(); m_sipBufOffs = 0; m_contentLen = 0; m_keepAlivePending = false; m_flowTimer = false; setProtoAddr(false); // Reset socket and addresses if (m_sock) { resetSocket(m_sock,-1); m_local.clear(); m_remote.clear(); } m_sock = sock; if (m_sock) { m_sock->getSockName(m_local); m_sock->getPeerName(m_remote); setProtoAddr(true); Debug(&plugin,DebugAll,"Transport(%s) connected local=%s:%d remote=%s:%d [%p]", m_id.c_str(),m_local.host().c_str(),m_local.port(), m_remote.host().c_str(),m_remote.port(),this); } // Update party local/remote ip/port if (m_party) m_party->updateAddrs(); lck.drop(); changeStatus(m_sock ? Connected : Idle); } void YateSIPTCPTransport::setIdleTimeout(u_int64_t time) { m_idleTimeout = time + (u_int64_t)m_idleInterval * 1000000; XDebug(&plugin,DebugAll,"Transport(%s) set idle timeout to %u [%p]", m_id.c_str(),(unsigned int)(m_idleTimeout / 1000000),this); } bool YateSIPTCPTransport::sendKeepAlive(bool request) { XDebug(&plugin,DebugAll,"Transport(%s) sending keep alive%s [%p]", m_id.c_str(),request ? "" : " response",this); unsigned int len = request ? 4 : 2; int wr = m_sock->writeData("\r\n\r\n",len); printWriteError(wr,len); return wr >= 0 || m_sock->canRetry(); } // Method called on buffer overflow. // Reset connection. Return 0 for outgoing -1 otherwise bool YateSIPTCPTransport::overflow(unsigned int msglen) { m_reason = "Buffer overflow (message too long)"; Debug(&plugin,DebugNote,"'%s' %s len=%u maxpkt=%u [%p]", m_id.c_str(),m_reason.c_str(),msglen,m_maxpkt,this); resetConnection(); return false; } YateSIPTransportWorker::YateSIPTransportWorker(YateSIPTransport* trans, Thread::Priority prio) : Thread("YSIP Worker",prio), m_transport(trans) { XDebug(&plugin,DebugAll,"YateSIPTransportWorker(%p,%s) [%p]", trans,trans ? trans->toString().c_str() : "",this); } YateSIPTransportWorker::~YateSIPTransportWorker() { cleanupTransport(true); } void YateSIPTransportWorker::run() { if (!m_transport) return; DDebug(&plugin,DebugAll,"YateSIPTransportWorker (%p) '%s' started [%p]", m_transport,m_transport->toString().c_str(),this); while (true) { if (Thread::check(false)) break; // Keep the transport alive while calling its method RefPointer trans = m_transport; int n = trans ? trans->process() : -1; trans = 0; if (n > 0) Thread::usleep(n); else if (n < 0) break; } DDebug(&plugin,DebugAll,"YateSIPTransportWorker terminated [%p]",this); cleanupTransport(false,!Thread::check(false)); } void YateSIPTransportWorker::cleanupTransport(bool final, bool terminate) { // Reset now transport data: the thread might be cancelled from // YateSIPTransport::destroyed() if (m_transport) { Lock lock(m_transport); m_transport->m_worker = 0; } RefPointer trans = m_transport; m_transport = 0; #ifdef XDEBUG Debugger debug(DebugAll,"YateSIPTransportWorker::cleanupTransport()", " final=%u terminate=%u transport=%p [%p]", final,terminate,(YateSIPTransport*)trans,this); #endif if (!trans) return; if (final) Debug(DebugWarn,"YateSIPTransportWorker abnormally terminated! [%p]",this); YateSIPTCPTransport* tcp = trans->tcpTransport(); if (tcp && tcp->outgoing()) tcp = 0; if (terminate) trans->terminate(); // Deref incoming TCP if (tcp) tcp->deref(); trans = 0; } YateSIPTCPListener::YateSIPTCPListener(int proto, const String& name, const NamedList& params) : Thread("YSIP Listener",Thread::priority(params.getValue("thread"))), String(name), ProtocolHolder(proto), m_mutex(true,"YSIPListener"), m_sslContextChanged(true), m_transParamsChanged(true), m_socket(0), m_backlog(5), m_transParams(params), m_initialized(false) { init(params,true); } YateSIPTCPListener::~YateSIPTCPListener() { cleanup(true); } // Init data void YateSIPTCPListener::init(const NamedList& params, bool first) { m_initialized = true; String addr = params.getValue("addr","0.0.0.0"); int port = params.getIntValue("port"); port = (port > 0 ? port : sipPort(!tls())); String rtpLip; SocketAddr lAddr(PF_INET); lAddr.host(addr); if (!isAllIfacesAddr(lAddr.host())) rtpLip = lAddr.host(); rtpLip = params.getValue("rtp_localip",rtpLip); String sslContext; m_mutex.lock(); if (tls()) { sslContext = params.getValue("sslcontext"); m_sslContextChanged = first || m_sslContextChanged || (sslContext != m_sslContext); m_sslContext = sslContext; if (!m_sslContext) Debug(&plugin,DebugConf,"Listener(%s,'%s') ssl context is empty [%p]", protoName(),c_str(),this); } m_backlog = params.getIntValue("backlog",5,0); m_bind = setAddr(addr,port) || first; m_transParamsChanged = m_transParamsChanged || first; if (rtpLip != m_transParams["rtp_localip"]) { m_transParamsChanged = true; m_transParams.setParam("rtp_localip",rtpLip); } m_mutex.unlock(); Debug(&plugin,DebugAll, "Listener(%s,'%s') initialized addr='%s' port=%d sslcontext='%s' rtp_localip='%s' [%p]", protoName(),c_str(),addr.c_str(),port,sslContext.safe(),rtpLip.c_str(),this); } void YateSIPTCPListener::run() { DDebug(&plugin,DebugAll,"Listener(%s,'%s') start running [%p]", protoName(),c_str(),this); SocketAddr lAddr(PF_INET); NamedList transParams(""); String sslContext; while (true) { if (Thread::check(false)) break; if (m_sslContextChanged || m_transParamsChanged) { Lock lock(m_mutex); if (m_sslContextChanged) { sslContext = m_sslContext; if (tls() && !sslContext) m_reason = "Empty ssl context"; } if (m_transParamsChanged) transParams = m_transParams; m_sslContextChanged = false; m_transParamsChanged = false; } if (tls() && !sslContext) { stopListening(); Thread::msleep(3 * Thread::idleMsec()); continue; } bool force = bindNow(&m_mutex); if (force || !m_socket) { if (m_socket) stopListening("Address changed",DebugInfo); if (!force && m_nextBind > Time::now()) { Thread::idle(); continue; } String reason; Socket* sock = initSocket(protocol(),toString(),lAddr,&m_mutex,m_backlog,false,reason); Lock lck(m_mutex); m_socket = sock; m_reason = reason; if (!m_socket) continue; } SocketAddr addr(PF_INET); Socket* sock = m_socket->accept(addr); if (!sock) { Thread::idle(); continue; } Debug(&plugin,DebugAll,"Listener(%s,'%s') '%s:%d' got conn from '%s:%d' [%p]", protoName(),c_str(),lAddr.host().safe(),lAddr.port(), addr.host().c_str(),addr.port(),this); if (!sock->setBlocking(false)) { String tmp; Thread::errorString(tmp,sock->error()); Debug(&plugin,DebugAll, "Listener(%s,'%s') '%s:%d' failed to set non-blocking mode for '%s:%d'. %d '%s' [%p]", protoName(),c_str(),lAddr.host().safe(),lAddr.port(), addr.host().c_str(),addr.port(),sock->error(),tmp.c_str(),this); delete sock; Thread::idle(); continue; } if (!tls() || plugin.socketSsl(&sock,true,sslContext)) { YateSIPTCPTransport* trans = new YateSIPTCPTransport(sock,tls()); if (!trans->init(transParams,true)) TelEngine::destruct(trans); } else { if (tls()) Debug(&plugin,DebugWarn,"Listener(%s,'%s') failed to start SSL [%p]", protoName(),c_str(),this); delete sock; } } cleanup(false); } // Close the socket. Remove from endpoint list void YateSIPTCPListener::cleanup(bool final) { if (plugin.ep()) plugin.ep()->removeListener(this); if (final) { if (!m_socket) DDebug(&plugin,DebugInfo,"Listener(%s,'%s') terminated [%p]",protoName(),c_str(),this); else Debug(&plugin,DebugWarn,"Listener(%s,'%s') abnormally terminated [%p]",protoName(),c_str(),this); } m_mutex.lock(); const char* reason = 0; if (!m_reason) reason = "Terminated"; m_mutex.unlock(); stopListening(reason,DebugInfo); } // Reset socket void YateSIPTCPListener::stopListening(const char* reason, int level) { if (!m_socket) return; Lock lck(m_mutex); if (!m_socket) return; if (!reason) reason = m_reason; Debug(&plugin,level,"Listener(%s,'%s') stop listening reason='%s' [%p]", protoName(),c_str(),reason,this); YateSIPTransport::resetSocket(m_socket,0); } YateUDPParty::YateUDPParty(YateSIPUDPTransport* trans, const SocketAddr& addr, int* localPort, const char* localAddr) : m_transport(0), m_addr(addr) { if (plugin.ep()) m_mutex = plugin.ep()->m_partyMutexPool.mutex(this); if (trans && trans->ref()) m_transport = trans; if (!localPort) { if (m_transport) { m_localPort = m_transport->local().port(); m_local = m_transport->local().host(); } } else { m_localPort = *localPort; m_local = localAddr; } m_party = m_addr.host(); m_partyPort = m_addr.port(); if (isAllIfacesAddr(m_local)) { SocketAddr laddr; if (laddr.local(addr)) m_local = laddr.host(); else m_local = "localhost"; } DDebug(&plugin,DebugAll,"YateUDPParty local %s:%d party %s:%d transport=%p [%p]", m_local.c_str(),m_localPort,m_party.c_str(),m_partyPort,m_transport,this); } YateUDPParty::~YateUDPParty() { DDebug(&plugin,DebugAll,"YateUDPParty::~YateUDPParty() transport=%p [%p]", m_transport,this); TelEngine::destruct(m_transport); } void YateUDPParty::transmit(SIPEvent* event) { const SIPMessage* msg = event->getMessage(); if (!msg) return; if (m_transport) { Lock lck(m_transport); if (s_printMsg) m_transport->printSendMsg(msg,&m_addr); m_transport->send(msg->getBuffer().data(),msg->getBuffer().length(),m_addr); return; } String tmp; getMsgLine(tmp,msg); Debug(&plugin,DebugWarn,"No transport to send %s to %s:%d", tmp.c_str(),m_addr.host().c_str(),m_addr.port()); } const char* YateUDPParty::getProtoName() const { return "UDP"; } bool YateUDPParty::setParty(const URI& uri) { Lock lock(m_mutex); if (m_partyPort && m_party && s_ignoreVia) return true; if (uri.getHost().null()) return false; int port = uri.getPort(); if (port <= 0) port = 5060; if (!m_addr.host(uri.getHost())) { Debug(&plugin,DebugWarn,"Could not resolve UDP party name '%s' [%p]", uri.getHost().safe(),this); return false; } m_addr.port(port); m_party = uri.getHost(); m_partyPort = port; DDebug(&plugin,DebugInfo,"New UDP party is %s:%d (%s:%d) [%p]", m_party.c_str(),m_partyPort, m_addr.host().c_str(),m_addr.port(), this); return true; } void* YateUDPParty::getTransport() { return m_transport; } // Get an object from this one void* YateUDPParty::getObject(const String& name) const { if (name == YSTRING("YateUDPParty")) return (void*)this; if (name == YSTRING("YateSIPUDPTransport") || name == YSTRING("YateSIPTransport")) return m_transport; return SIPParty::getObject(name); } YateTCPParty::YateTCPParty(YateSIPTCPTransport* trans) : SIPParty(true), m_transport(0) { if (plugin.ep()) m_mutex = plugin.ep()->m_partyMutexPool.mutex(this); if (trans && trans->ref()) { m_transport = trans; trans->resetParty(this,true); } updateAddrs(); DDebug(&plugin,DebugAll,"YateTCPParty local %s:%d party %s:%d transport=%p [%p]", m_local.c_str(),m_localPort,m_party.c_str(),m_partyPort,m_transport,this); } YateTCPParty::~YateTCPParty() { XDebug(&plugin,DebugAll,"YateTCPParty::~YateTCPParty() transport=%p [%p]", m_transport,this); TelEngine::destruct(m_transport); } void YateTCPParty::transmit(SIPEvent* event) { const SIPMessage* msg = event->getMessage(); if (!msg) return; if (m_transport) { m_transport->send(event); return; } String tmp; getMsgLine(tmp,msg); Debug(&plugin,DebugWarn,"YateTCPParty no transport to send %s [%p]", tmp.c_str(),this); } const char* YateTCPParty::getProtoName() const { if (m_transport) return m_transport->protoName(); return "TCP"; } bool YateTCPParty::setParty(const URI& uri) { Lock lock(m_mutex); if (m_partyPort && m_party && s_ignoreVia) return true; Debug(&plugin,DebugWarn,"YateTCPParty::setParty(%s) not implemented [%p]", uri.safe(),this); return false; } void* YateTCPParty::getTransport() { return m_transport; } // Get an object from this one void* YateTCPParty::getObject(const String& name) const { if (name == YSTRING("YateTCPParty")) return (void*)this; if (name == YSTRING("YateSIPTCPTransport") || name == YSTRING("YateSIPTransport")) return m_transport; return SIPParty::getObject(name); } void YateTCPParty::updateAddrs() { if (!m_transport) return; m_transport->lock(); String laddr = m_transport->local().host(); int lport = m_transport->local().port(); String raddr = m_transport->remote().host(); int rport = m_transport->remote().port(); SocketAddr remote(PF_INET); if (raddr) remote = m_transport->remote(); else { remote.host(m_transport->remoteAddr()); remote.port(m_transport->remotePort()); raddr = remote.host(); rport = remote.port(); } m_transport->unlock(); if (!laddr) { SocketAddr addr; if (addr.local(remote)) laddr = addr.host(); else laddr = "localhost"; } if (lport <= 0) lport = sipPort(!m_transport->tls()); setAddr(laddr,lport,true); setAddr(raddr,rport,false); } void YateTCPParty::destroyed() { DDebug(&plugin,DebugAll,"YateTCPParty::destroyed() transport=%p [%p]", m_transport,this); if (m_transport) { m_transport->resetParty(this,false); TelEngine::destruct(m_transport); } SIPParty::destroyed(); } YateSIPEngine::YateSIPEngine(YateSIPEndPoint* ep) : SIPEngine(s_cfg.getValue("general","useragent")), m_ep(ep), m_prack(false), m_info(false) { addAllowed("INVITE"); addAllowed("BYE"); addAllowed("CANCEL"); if (s_cfg.getBoolValue("general","registrar",!Engine::clientMode())) addAllowed("REGISTER"); if (s_cfg.getBoolValue("general","transfer",!Engine::clientMode())) addAllowed("REFER"); if (s_cfg.getBoolValue("general","options",true)) addAllowed("OPTIONS"); m_prack = s_cfg.getBoolValue("general","prack"); if (m_prack) addAllowed("PRACK"); m_info = s_cfg.getBoolValue("general","info",true); if (m_info) addAllowed("INFO"); lazyTrying(s_cfg.getBoolValue("general","lazy100",false)); m_fork = s_cfg.getBoolValue("general","fork",true); m_flags = s_cfg.getIntValue("general","flags",m_flags); NamedList *l = s_cfg.getSection("methods"); if (l) { unsigned int len = l->length(); for (unsigned int i=0; igetParam(i); if (!n) continue; String meth(n->name()); meth.toUpper(); addAllowed(meth); } } initialize(s_cfg.getSection("general")); } // Initialize the engine void YateSIPEngine::initialize(NamedList* params) { NamedList dummy(""); if (!params) params = &dummy; m_reqTransCount = params->getIntValue("sip_req_trans_count",4,2,10,false); m_rspTransCount = params->getIntValue("sip_rsp_trans_count",5,2,10,false); DDebug(this,DebugAll,"Initialized sip_req_trans_count=%d sip_rsp_trans_count=%d", m_reqTransCount,m_rspTransCount); } SIPTransaction* YateSIPEngine::forkInvite(SIPMessage* answer, SIPTransaction* trans) { if (m_fork && trans->isActive() && (answer->code/100) == 2) { Debug(this,DebugNote,"Changing early dialog tag because of forked 2xx"); trans->setDialogTag(answer->getParamValue("To","tag")); trans->processMessage(answer); return trans; } return SIPEngine::forkInvite(answer,trans); } // Transport status changed notification void YateSIPEngine::transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason) { if (!(trans && stat == YateSIPTransport::Terminated)) return; // Clear transactions Lock lock(this); for (ObjList* l = m_transList.skipNull(); l; l = l->skipNext()) { SIPTransaction* t = static_cast(l->get()); if (t->initialMessage() && t->initialMessage()->getParty() && trans == t->initialMessage()->getParty()->getTransport()) { bool active = t->isActive(); Debug(this,active ? DebugInfo : DebugAll, "Clearing %stransaction (%p) transport terminated reason=%s", active ? "active " : "",t,reason.c_str()); t->setCleared(); } } } // Check if the engine has an active transaction using a given transport bool YateSIPEngine::hasActiveTransaction(YateSIPTransport* trans) { if (!trans) return false; Lock lock(this); for (ObjList* l = m_transList.skipNull(); l; l = l->skipNext()) { SIPTransaction* t = static_cast(l->get()); if (t->isActive() && t->initialMessage() && t->initialMessage()->getParty() && trans == t->initialMessage()->getParty()->getTransport() ) return true; } return false; } // Check if the engine has pending transactions bool YateSIPEngine::hasInitialTransaction() { Lock lock(this); for (ObjList* l = m_transList.skipNull(); l; l = l->skipNext()) { SIPTransaction* t = static_cast(l->get()); if (t->getState() == SIPTransaction::Initial) return true; } return false; } bool YateSIPEngine::buildParty(SIPMessage* message) { return m_ep->buildParty(message); } bool YateSIPEngine::copyAuthParams(NamedList* dest, const NamedList& src, bool ok) { // we added those and we want to exclude them from copy static TokenDict exclude[] = { { "protocol", 1 }, // purposely copy the username and realm { "nonce", 1 }, { "method", 1 }, { "uri", 1 }, { "response", 1 }, { "ip_host", 1 }, { "ip_port", 1 }, { "address", 1 }, { "billid", 1 }, { 0, 0 }, }; if (!dest) return ok; unsigned int n = src.length(); for (unsigned int i = 0; i < n; i++) { NamedString* s = src.getParam(i); if (!s) continue; String name = s->name(); if (name.startSkip("authfail_",false) == ok) continue; if (name.toInteger(exclude,0)) continue; dest->setParam(name,*s); } return ok; } bool YateSIPEngine::checkUser(const String& username, const String& realm, const String& nonce, const String& method, const String& uri, const String& response, const SIPMessage* message, GenObject* userData) { NamedList* params = YOBJECT(NamedList,userData); Message m("user.auth"); m.addParam("protocol","sip"); if (username) { m.addParam("username",username); m.addParam("realm",realm); m.addParam("nonce",nonce); m.addParam("response",response); } m.addParam("method",method); m.addParam("uri",uri); if (message) { String raddr; int rport = 0; message->getParty()->getAddr(raddr,rport,false); String port(rport); m.addParam("ip_host",raddr); m.addParam("ip_port",port); m.addParam("ip_transport",message->getParty()->getProtoName()); if (raddr) m.addParam("address",raddr + ":" + port); // a dialogless INVITE could create a new call m.addParam("newcall",String::boolText((message->method == YSTRING("INVITE")) && !message->getParam("To","tag"))); const MimeHeaderLine* hl = message->getHeader("From"); if (hl) { URI from(*hl); m.addParam("domain",from.getHost()); } } if (params) { m.copyParam(*params,"caller"); m.copyParam(*params,"called"); m.copyParam(*params,"billid"); } if (!Engine::dispatch(m)) return copyAuthParams(params,m,false); // empty password returned means authentication succeeded if (m.retValue().null()) return copyAuthParams(params,m); // check for refusals if (m.retValue() == "-") { if (params) { const char* err = m.getValue(YSTRING("error")); if (err) params->setParam("error",err); err = m.getValue(YSTRING("reason")); if (err) params->setParam("reason",err); } return copyAuthParams(params,m,false); } // password works only with username if (!username) return copyAuthParams(params,m,false); String res; buildAuth(username,realm,m.retValue(),nonce,method,uri,res); if (res == response) return copyAuthParams(params,m); // if the URI included some parameters retry after stripping them off int sc = uri.find(';'); bool ok = false; if (sc >= 0) { buildAuth(username,realm,m.retValue(),nonce,method,uri.substr(0,sc),res); ok = (res == response) && copyAuthParams(params,m); } if (!ok && !response.null()) { DDebug(&plugin,DebugNote,"Failed authentication for username='%s'",username.c_str()); m_ep->incFailedAuths(); plugin.changed(); Message* fail = new Message(m); *fail = "user.authfail"; fail->retValue().clear(); Engine::enqueue(fail); } return ok || copyAuthParams(params,m,false); } YateSIPEndPoint::YateSIPEndPoint(Thread::Priority prio, unsigned int partyMutexCount) : Thread("YSIP EndPoint",prio), m_partyMutexPool(partyMutexCount,true,"SIPParty"), m_engine(0), m_mutex(true,"YateSIPEndPoint"), m_defTransport(0), m_failedAuths(0),m_timedOutTrs(0), m_timedOutByes(0) { Debug(&plugin,DebugAll,"YateSIPEndPoint::YateSIPEndPoint(%s) [%p]", Thread::priority(prio),this); } YateSIPEndPoint::~YateSIPEndPoint() { Debug(&plugin,DebugAll,"YateSIPEndPoint::~YateSIPEndPoint() [%p]",this); plugin.channels().clear(); s_lines.clear(); if (m_engine) { // send any pending events while (m_engine->process()) ; delete m_engine; m_engine = 0; } m_defTransport = 0; } bool YateSIPEndPoint::buildParty(SIPMessage* message, const char* host, int port, const YateSIPLine* line) { if (message->isAnswer()) return false; DDebug(&plugin,DebugAll,"YateSIPEndPoint::buildParty(%p,'%s',%d,%p)", message,host,port,line); if (line && line->setSipParty(message,line)) return true; // Find transport YateSIPUDPTransport* trans = defTransport(); if (!trans && line && line->getLocalAddr()) trans = findUdpTransport(line->getLocalAddr(),line->getLocalPort()); if (!trans) return false; // Build an udp party URI uri(message->uri); if (line) { if (!host) host = line->getPartyAddr(); if (port <= 0) port = line->getPartyPort(); line->setupAuth(message); } if (!host) { host = uri.getHost().safe(); if (port <= 0) port = uri.getPort(); } if (port <= 0) port = 5060; SocketAddr addr(AF_INET); if (!addr.host(host)) { TelEngine::destruct(trans); Debug(&plugin,DebugWarn,"Error resolving name '%s'",host); return false; } addr.port(port); DDebug(&plugin,DebugAll,"built addr: %s:%d",addr.host().c_str(),addr.port()); YateUDPParty* party = new YateUDPParty(trans,addr); TelEngine::destruct(trans); message->setParty(party); TelEngine::destruct(party); return true; } // (re)set default UDP transport void YateSIPEndPoint::updateDefUdpTransport() { static const String s_general("general"); Lock lock(m_mutex); m_defTransport = 0; // Find a non-general default transport for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) { YateSIPTransport* t = static_cast(o->get()); m_defTransport = t->udpTransport(); if (m_defTransport && m_defTransport->toString() != s_general && m_defTransport->isDefault()) break; m_defTransport = 0; } if (!m_defTransport) { m_defTransport = findUdpTransport(s_general); if (m_defTransport) m_defTransport->deref(); } if (m_defTransport) Debug(&plugin,DebugInfo,"Default UDP transport is '%s'", m_defTransport->toString().c_str()); else if (!Engine::exiting()) Debug(&plugin,DebugNote,"Default UDP transport not set"); } // Retrieve a transport by name. Return referrenced object YateSIPTransport* YateSIPEndPoint::findTransport(const String& name) { if (!name) return 0; Lock lock(m_mutex); ObjList* o = m_transports.find(name); YateSIPTransport* t = o ? static_cast(o->get()) : 0; return (t && t->ref()) ? t : 0; } // Retrieve an UDP transport by name. Return referrenced object YateSIPUDPTransport* YateSIPEndPoint::findUdpTransport(const String& name) { YateSIPTransport* t = findTransport(name); YateSIPUDPTransport* trans = t ? t->udpTransport() : 0; if (!trans) TelEngine::destruct(t); return trans; } // Retrieve an UDP transport by addr/port. Return referrenced object YateSIPUDPTransport* YateSIPEndPoint::findUdpTransport(const String& addr, int port) { Lock lock(m_mutex); for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) { YateSIPTransport* t = static_cast(o->get()); if (!t->udpTransport()) continue; Lock lck(t); if (t->local().port() == port && t->local().host() == addr) return t->ref() ? static_cast(t) : 0; } return 0; } // Build an UDP transport (re-init existing). Start the thread bool YateSIPEndPoint::setupUdpTransport(const String& name, bool enabled, const NamedList& params, const NamedList& defs, const char* reason) { if (!name) return false; YateSIPUDPTransport* rd = findUdpTransport(name); if (rd) { const char* addr = params.getValue("addr","0.0.0.0"); int port = params.getIntValue("port",5060); bool stop = enabled && !rd->addrWouldChange(rd,true,addr,port); if (stop) rd->init(params,defs,false); else { removeUdpTransport(name,enabled ? "Address changed" : (reason ? reason : "Disabled")); rd->deref(); } TelEngine::destruct(rd); if (stop) return true; } if (!enabled) return true; Lock lock(m_mutex); const char* s = params.getValue("thread",defs.getValue("thread")); rd = new YateSIPUDPTransport(name); rd->init(params,defs,true,Thread::priority(s)); m_transports.append(rd); return true; } // Delete an UDP transport bool YateSIPEndPoint::removeUdpTransport(const String& name, const char* reason) { XDebug(&plugin,DebugAll,"YateSIPEndPoint::removeUdpTransport(%s,%s)",name.c_str(),reason); if (!name) return false; YateSIPUDPTransport* rd = findUdpTransport(name); if (!rd) return false; Debug(&plugin,DebugAll,"Removing udp transport '%s': %s",name.c_str(),reason); removeTransport(rd,false); // Terminate channels, disconnect lines plugin.transportTerminated(rd); // Notify lines for (ObjList* ol = s_lines.skipNull(); ol; ol = ol->skipNext()) { YateSIPLine* line = static_cast(ol->get()); if (line->isTransport(rd)) line->transportChangedStatus(YateSIPTransport::Terminated,reason); } // Wait for active transactions to terminate if (!Engine::exiting()) { unsigned int intervals = (unsigned int)(s_waitActiveUdpTrans / Thread::idleUsec()); if (!intervals) intervals = 1; while (intervals > 0 && !Engine::exiting() && m_engine->hasActiveTransaction(rd)) { Thread::idle(); intervals--; } if (!intervals) Debug(&plugin,DebugNote, "Removing udp transport '%s' with active transactions using it", name.c_str()); } rd->terminate(reason); TelEngine::destruct(rd); return true; } // Remove a transport from list bool YateSIPEndPoint::removeTransport(YateSIPTransport* trans, bool updDef) { if (!trans) return false; Lock lock(m_mutex); if (!m_transports.remove(trans,false)) return false; Debug(&plugin,DebugAll,"Removed transport (%p,'%s')",trans,trans->toString().c_str()); if (trans == m_defTransport) { Debug(&plugin,DebugInfo,"Reset default UDP transport"); m_defTransport = 0; if (updDef) updateDefUdpTransport(); } return true; } // Remove all transports void YateSIPEndPoint::clearUdpTransports(const char* reason) { #ifdef DEBUG Debugger debug(DebugAll,"YateSIPEndPoint::clearUdpTransports()"); #else Debug(&plugin,DebugAll,"Clearing udp transports reason=%s",reason); #endif while (true) { Lock lock(m_mutex); RefPointer trans; for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) { trans = YOBJECT(YateSIPUDPTransport,o->get()); if (trans) break; } if (!trans) break; lock.drop(); removeTransport(trans); trans->terminate(reason); trans->deref(); trans = 0; } } // TCP transport status changed notification void YateSIPEndPoint::transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason) { if (!trans) return; DDebug(&plugin,DebugAll,"YateSIPEndPoint::transportChangedStatus(%p,%s,%s)", trans,YateSIPTransport::statusName(stat),reason.c_str()); // Remove from list if (stat == YateSIPTCPTransport::Terminated) removeTransport(trans); // Notify lines for (ObjList* ol = s_lines.skipNull(); ol; ol = ol->skipNext()) { YateSIPLine* line = static_cast(ol->get()); if (line->isTransport(trans)) line->transportChangedStatus(stat,reason); } // Notify transactions in engine if (m_engine) m_engine->transportChangedStatus(trans,stat,reason); if (stat != YateSIPTCPTransport::Terminated) return; // Notify unregister if (!Engine::exiting()) { Message* m = new Message("user.unregister"); m->addParam("connection_id",trans->toString()); Engine::enqueue(m); } // Notify channels plugin.transportTerminated(trans); } // Build or delete a TCP listener. Start the thread bool YateSIPEndPoint::setupListener(int proto, const String& name, bool enabled, const NamedList& params) { if (!name) return false; Lock lock(m_mutex); ObjList* o = m_listeners.find(name); if (o) { YateSIPTCPListener* l = static_cast(o->get()); if (enabled) { if (l->protocol() == proto) l->init(params,false); else { lock.drop(); cancelListener(name,"Type changed"); return setupListener(proto,name,enabled,params); } } else { lock.drop(); cancelListener(name,"Disabled"); } return true; } if (!enabled) return true; // Build it YateSIPTCPListener* listener = new YateSIPTCPListener(proto,name,params); if (listener->startup()) { m_listeners.append(listener); DDebug(&plugin,DebugAll,"Added listener %p '%s'",listener,listener->toString().c_str()); return true; } Debug(&plugin,DebugWarn,"Failed to start listener thread type=%s name='%s'", ProtocolHolder::lookupProtoName(proto),name.c_str()); return false; } // Remove a listener from list without deleting it. Return true if found bool YateSIPEndPoint::removeListener(YateSIPTCPListener* listener) { if (!listener) return false; Lock lock(m_mutex); if (!m_listeners.remove(listener,false)) return false; DDebug(&plugin,DebugAll,"Removed listener (%p,'%s')",listener,listener->toString().c_str()); return true; } // Remove a listener from list. Remove all if name is empty. Wait for termination void YateSIPEndPoint::cancelListener(const String& name, const char* reason) { m_mutex.lock(); bool wait = false; for (ObjList* o = m_listeners.skipNull(); o; o = o->skipNext()) { YateSIPTCPListener* l = static_cast(o->get()); if (name && name != l->toString()) continue; wait = true; Debug(&plugin,DebugAll,"Stopping listener (%p,'%s') reason=%s", l,l->toString().c_str(),reason); l->setReason(reason); l->cancel(); if (name) break; } m_mutex.unlock(); if (!wait) return; while (true) { Thread::idle(); Lock lck(m_mutex); ObjList* o = !name ? m_listeners.skipNull() : m_listeners.find(name); if (!o) break; } if (!name) Debug(&plugin,DebugAll,"Stopped all listeners"); else Debug(&plugin,DebugAll,"Stopped listener '%s'",name.c_str()); } // This method is called by the driver when start/end initializing // start==true: Reset initialized flag for listeners and UDP transports // start==false: Terminate not initialized listeners and UDP transports void YateSIPEndPoint::initializing(bool start) { ObjList rmListener; ObjList rmUdpTrans; m_mutex.lock(); for (ObjList* o = m_listeners.skipNull(); o; o = o->skipNext()) { YateSIPTCPListener* l = static_cast(o->get()); if (start) l->m_initialized = false; else if (!l->m_initialized) rmListener.append(new String(l->toString())); } for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) { YateSIPTCPTransport* t = static_cast(o->get()); if (start) t->m_initialized = false; else if (!t->m_initialized && t->udpTransport()) rmUdpTrans.append(new String(t->toString())); } m_mutex.unlock(); if (start) return; for (ObjList* o = rmListener.skipNull(); o; o = o->skipNext()) { String* name = static_cast(o->get()); Debug(&plugin,DebugNote,"Stopping deleted listener '%s'",name->c_str()); cancelListener(*name,"Deleted"); } for (ObjList* o = rmUdpTrans.skipNull(); o; o = o->skipNext()) removeUdpTransport(o->get()->toString(),"Deleted"); } bool YateSIPEndPoint::Init() { m_engine = new YateSIPEngine(this); m_engine->debugChain(&plugin); return true; } // Check if data is allowed to be read from socket(s) and processed bool YateSIPEndPoint::canRead() { return s_floodEvents <= 1 || (s_evCount < s_floodEvents) || Engine::exiting(); } void YateSIPEndPoint::run() { for (;;) { if (!canRead()) { if (s_evCount == s_floodEvents) Debug(&plugin,DebugMild,"Flood detected: %d handled events",s_evCount); else if ((s_evCount % s_floodEvents) == 0) Debug(&plugin,DebugWarn,"Severe flood detected: %d events",s_evCount); } SIPEvent* e = m_engine->getEvent(); if (e) s_evCount++; else s_evCount = 0; // hack: use a loop so we can use break and continue for (; e; m_engine->processEvent(e),e = 0) { SIPTransaction* t = e->getTransaction(); if (!t) continue; plugin.lock(); if (t->isOutgoing() && t->getResponseCode() == 408) { if (t->getMethod() == YSTRING("BYE")) { DDebug(&plugin,DebugInfo,"BYE for transaction %p has timed out",t); m_timedOutByes++; plugin.changed(); } if (e->getState() == SIPTransaction::Cleared && e->getUserData()) { DDebug(&plugin,DebugInfo,"Transaction %p has timed out",t); m_timedOutTrs++; plugin.changed(); } } GenObject* obj = static_cast(t->getUserData()); RefPointer conn = YOBJECT(YateSIPConnection,obj); YateSIPLine* line = YOBJECT(YateSIPLine,obj); YateSIPGenerate* gen = YOBJECT(YateSIPGenerate,obj); plugin.unlock(); if (conn) { if (conn->process(e)) { delete e; break; } else continue; } if (line) { if (line->process(e)) { delete e; break; } else continue; } if (gen) { if (gen->process(e)) { delete e; break; } else continue; } if ((e->getState() == SIPTransaction::Trying) && !e->isOutgoing() && incoming(e,e->getTransaction())) { delete e; break; } } if (s_evCount || s_engineHalt) Thread::check(); else Thread::usleep(Thread::idleUsec()); } } bool YateSIPEndPoint::incoming(SIPEvent* e, SIPTransaction* t) { if (t->isInvite()) invite(e,t); else if (t->getMethod() == YSTRING("BYE")) { YateSIPConnection* conn = plugin.findCall(t->getCallID(),true); if (conn) { conn->doBye(t); conn->deref(); } else t->setResponse(481); } else if (t->getMethod() == YSTRING("CANCEL")) { YateSIPConnection* conn = plugin.findCall(t->getCallID(),true); if (conn) { conn->doCancel(t); conn->deref(); } else t->setResponse(481); } else if (t->getMethod() == YSTRING("INFO")) { YateSIPConnection* conn = plugin.findCall(t->getCallID(),true); bool done = false; if (conn) { done = conn->doInfo(t); conn->deref(); if (!done) done = generic(e,t); } else if (t->getDialogTag()) { done = true; t->setResponse(481); } else done = generic(e,t); if (!done) t->setResponse(415); } else if (t->getMethod() == YSTRING("REGISTER")) regReq(e,t); else if (t->getMethod() == YSTRING("OPTIONS")) options(e,t); else if (t->getMethod() == YSTRING("REFER")) { YateSIPConnection* conn = plugin.findCall(t->getCallID(),true); if (conn) { conn->doRefer(t); conn->deref(); } else t->setResponse(481); } else return generic(e,t); return true; } void YateSIPEndPoint::invite(SIPEvent* e, SIPTransaction* t) { if (!plugin.canAccept()) { Debug(&plugin,DebugWarn,"Refusing new SIP call, full or exiting"); t->setResponse(480); return; } if (e->getMessage()->getParam("To","tag")) { SIPDialog dlg(*e->getMessage()); YateSIPConnection* conn = plugin.findDialog(dlg,true); if (conn) { conn->reInvite(t); conn->deref(); } else { Debug(&plugin,DebugWarn,"Got re-INVITE for missing dialog"); t->setResponse(481); } return; } YateSIPConnection* conn = new YateSIPConnection(e,t); conn->initChan(); conn->startRouter(); } void YateSIPEndPoint::regReq(SIPEvent* e, SIPTransaction* t) { if (Engine::exiting()) { Debug(&plugin,DebugWarn,"Dropping request, engine is exiting"); t->setResponse(500, "Server Shutting Down"); return; } if (s_reg_async) { YateSIPRegister* reg = new YateSIPRegister(this,e->getMessage(),t); if (reg->startup()) return; Debug(&plugin,DebugWarn,"Failed to start register thread"); delete reg; } regRun(e->getMessage(),t); } void YateSIPEndPoint::regRun(const SIPMessage* message, SIPTransaction* t) { const MimeHeaderLine* hl = message->getHeader("Contact"); if (!hl) { t->setResponse(400); return; } Message msg("user.register"); msg.addParam("sip_uri",t->getURI()); msg.addParam("sip_callid",t->getCallID()); String user; int age = t->authUser(user,false,&msg); DDebug(&plugin,DebugAll,"User '%s' age %d",user.c_str(),age); if (((age < 0) || (age > 10)) && s_auth_register) { Lock lck(s_globalMutex); t->requestAuth(s_realm,"",age >= 0); return; } // TODO: track registrations, allow deregistering all if (*hl == "*") { t->setResponse(200); return; } URI addr(*hl); if (user.null()) user = addr.getUser(); msg.setParam("username",user); msg.setParam("number",addr.getUser()); msg.setParam("driver","sip"); String data(addr); String raddr; int rport = 0; message->getParty()->getAddr(raddr,rport,false); bool nat = isNatBetween(addr.getHost(),raddr); if (!nat) { int port = addr.getPort(); if (!port) port = 5060; nat = (rport != port) && msg.getBoolValue(YSTRING("nat_port_support"),true); } bool natChanged = false; if (msg.getBoolValue(YSTRING("nat_support"),s_auto_nat && nat)) { Debug(&plugin,DebugInfo,"Registration NAT detected: private '%s:%d' public '%s:%d'", addr.getHost().c_str(),addr.getPort(),raddr.c_str(),rport); String tmp(addr.getHost()); if (addr.getPort()) tmp << ":" << addr.getPort(); msg.addParam("reg_nat_addr",tmp); int pos = data.find(tmp); if (pos >= 0) { int len = tmp.length(); tmp.clear(); tmp << data.substr(0,pos) << raddr << ":" << rport << data.substr(pos + len); data = tmp; natChanged = true; } } msg.setParam("data","sip/" + data); msg.setParam("ip_host",raddr); msg.setParam("ip_port",String(rport)); msg.setParam("ip_transport",message->getParty()->getProtoName()); bool dereg = false; String tmp(message->getHeader("Expires")); if (tmp.null()) tmp = hl->getParam("expires"); int expires = tmp.toInteger(-1); if (expires < 0) expires = s_expires_def; if (expires > s_expires_max) expires = s_expires_max; if (expires && (expires < s_expires_min)) { tmp = s_expires_min; SIPMessage* r = new SIPMessage(t->initialMessage(),423); r->addHeader("Min-Expires",tmp); t->setResponse(r); r->deref(); return; } tmp = expires; msg.setParam("expires",tmp); if (!expires) { msg = "user.unregister"; dereg = true; } else msg.setParam("sip_to",addr); hl = message->getHeader("User-Agent"); if (hl) msg.setParam("device",*hl); // Add transport if registering if (expires && message->getParty()) { YateSIPTransport* trans = static_cast(message->getParty()->getTransport()); if (trans) trans->fillMessage(msg,true); } // Always OK deregistration attempts if (Engine::dispatch(msg) || dereg) { if (dereg) { t->setResponse(200); Debug(&plugin,DebugNote,"Unregistered user '%s'",user.c_str()); } else { tmp = msg.getValue(YSTRING("expires"),tmp); if (tmp.null()) tmp = expires; SIPMessage* r = new SIPMessage(t->initialMessage(),200); r->addHeader("Expires",tmp); MimeHeaderLine* contact = new MimeHeaderLine("Contact","<" + addr + ">"); contact->setParam("expires",tmp); r->addHeader(contact); if (natChanged) { if (s_nat_refresh > 0) r->addHeader("P-NAT-Refresh",String(s_nat_refresh)); r->addHeader("X-Real-Contact",data); } if (t->initialMessage() && t->initialMessage()->getParty() && t->initialMessage()->getParty()->isReliable()) { const String& ftValue = msg[YSTRING("xsip_flow-timer")]; int flowTimer = ftValue.toInteger(); if (flowTimer > 10 && flowTimer <= 120) r->addHeader(new MimeHeaderLine("Flow-Timer",ftValue)); } // Reset transport timeout resetTransportIdle(r,tmp.toInteger()); t->setResponse(r); r->deref(); Debug(&plugin,DebugNote,"Registered user '%s' expires in %s s%s", user.c_str(),tmp.c_str(),natChanged ? " (NAT)" : ""); } } else t->setResponse(404); } void YateSIPEndPoint::options(SIPEvent* e, SIPTransaction* t) { const MimeHeaderLine* acpt = e->getMessage()->getHeader("Accept"); if (acpt) { if (*acpt != YSTRING("application/sdp")) { t->setResponse(415); return; } } t->setResponse(200); } bool YateSIPEndPoint::generic(SIPEvent* e, SIPTransaction* t) { String meth(t->getMethod()); meth.toLower(); String user; const String* auth = s_cfg.getKey("methods",meth); if (!auth) return false; Message m("sip." + meth); const SIPMessage* message = e->getMessage(); String host; int portNum = 0; message->getParty()->getAddr(host,portNum,false); URI uri(message->uri); YateSIPLine* line = plugin.findLine(host,portNum,uri.getUser()); if (line) { // message comes from line we have registered to if (user.null()) user = line->getUserName(); m.addParam("domain",line->domain()); m.addParam("in_line",*line); } else if (auth->toBoolean(true)) { int age = t->authUser(user,false,&m); DDebug(&plugin,DebugAll,"User '%s' age %d",user.c_str(),age); if ((age < 0) || (age > 10)) { Lock lck(s_globalMutex); t->requestAuth(s_realm,"",age >= 0); return true; } } // Add transport info YateSIPTransport* trans = YOBJECT(YateSIPTransport,message->getParty()); if (trans) trans->fillMessage(m); if (message->getParam("To","tag")) { SIPDialog dlg(*message); YateSIPConnection* conn = plugin.findDialog(dlg,true); if (conn) { m.userData(conn); conn->complete(m); conn->deref(); } } m.addParam("username",user,false); m.addParam("called",uri.getUser(),false); uri = message->getHeader("From"); uri.parse(); m.addParam("caller",uri.getUser(),false); m.addParam("callername",uri.getDescription(),false); String tmp(message->getHeaderValue("Max-Forwards")); int maxf = tmp.toInteger(s_maxForwards); if (maxf > s_maxForwards) maxf = s_maxForwards; tmp = maxf-1; m.addParam("antiloop",tmp); String port(portNum); m.addParam("address",host + ":" + port); m.addParam("ip_host",host); m.addParam("ip_port",port); m.addParam("ip_transport",message->getParty()->getProtoName()); m.addParam("sip_uri",t->getURI()); m.addParam("sip_callid",t->getCallID()); // establish the dialog here so user code will have the dialog tag handy t->setDialogTag(); m.addParam("xsip_dlgtag",t->getDialogTag()); copySipHeaders(m,*message,false); doDecodeIsupBody(&plugin,m,message->body); // add the body if it's a string one MimeStringBody* strBody = YOBJECT(MimeStringBody,message->body); if (strBody) { m.addParam("xsip_type",strBody->getType()); m.addParam("xsip_body",strBody->text()); } else { MimeLinesBody* txtBody = YOBJECT(MimeLinesBody,message->body); if (txtBody) { String bodyText((const char*)txtBody->getBody().data(),txtBody->getBody().length()); m.addParam("xsip_type",txtBody->getType()); m.addParam("xsip_body",bodyText); } else if (message->body) { const DataBlock& binBody = message->body->getBody(); String bodyText; Base64 b64(binBody.data(),binBody.length(),false); b64.encode(bodyText); b64.clear(false); m.addParam("xsip_type",message->body->getType()); m.addParam("xsip_body_encoding","base64"); m.addParam("xsip_body",bodyText); } } int code = 0; if (Engine::dispatch(m)) { const String* ret = m.getParam(YSTRING("code")); if (!ret) ret = &m.retValue(); code = ret->toInteger(m.getIntValue(YSTRING("reason"),dict_errors,200)); } else { code = m.getIntValue(YSTRING("code"),m.getIntValue(YSTRING("reason"),dict_errors,0)); if (code < 300) code = 0; } if ((code >= 200) && (code < 700)) { SIPMessage* resp = new SIPMessage(message,code); copySipHeaders(*resp,m); t->setResponse(resp); resp->deref(); return true; } return false; } // Build the transfer thread // transferorID: Channel id of the sip connection that received the REFER request // transferredID: Channel id of the transferor's peer // transferredDrv: Channel driver of the transferor's peer // msg: already populated 'call.route' // sipNotify: already populated SIPMessage("NOTIFY") YateSIPRefer::YateSIPRefer(const String& transferorID, const String& transferredID, Driver* transferredDrv, Message* msg, SIPMessage* sipNotify, SIPTransaction* transaction) : Thread("YSIP Transfer"), m_transferorID(transferorID), m_transferredID(transferredID), m_transferredDrv(transferredDrv), m_msg(msg), m_sipNotify(sipNotify), m_notifyCode(200), m_transaction(0), m_rspCode(500) { if (transaction && transaction->ref()) m_transaction = transaction; } void YateSIPRefer::run() { String* attended = m_msg->getParam(YSTRING("transfer_callid")); #ifdef DEBUG if (attended) Debug(&plugin,DebugAll,"%s(%s) running callid=%s fromtag=%s totag=%s [%p]", name(),m_transferorID.c_str(),attended->c_str(), m_msg->getValue(YSTRING("transfer_fromtag")), m_msg->getValue(YSTRING("transfer_totag")),this); else Debug(&plugin,DebugAll,"%s(%s) running [%p]",name(),m_transferorID.c_str(),this); #endif // Use a while() to break to the end while (m_transferredDrv && m_msg) { // Attended transfer: check if the requested channel is owned by our plugin // NOTE: Remove the whole 'if' when a routing module will be able to route // attended transfer requests if (attended) { String* from = m_msg->getParam(YSTRING("transfer_fromtag")); String* to = m_msg->getParam(YSTRING("transfer_totag")); if (null(from) || null(to)) { m_rspCode = m_notifyCode = 487; // Request Terminated break; } YateSIPConnection* conn = plugin.findDialog(*attended,*from,*to,true); if (conn) { m_transferredDrv->lock(); RefPointer chan = m_transferredDrv->find(m_transferredID); m_transferredDrv->unlock(); if (chan && conn->getPeer() && chan->connect(conn->getPeer(),m_msg->getValue(YSTRING("reason")))) { m_rspCode = 202; m_notifyCode = 200; } else m_rspCode = m_notifyCode = 487; // Request Terminated TelEngine::destruct(conn); break; } // Not ours m_msg->clearParam("called"); YateSIPConnection::addCallId(*m_msg,*attended,*from,*to); } // Route the call bool ok = Engine::dispatch(m_msg); m_transferredDrv->lock(); RefPointer chan = m_transferredDrv->find(m_transferredID); m_transferredDrv->unlock(); if (!(ok && chan)) { #ifdef DEBUG if (ok) Debug(&plugin,DebugAll,"%s(%s). Connection vanished while routing! [%p]", name(),m_transferorID.c_str(),this); else Debug(&plugin,DebugAll,"%s(%s). 'call.route' failed [%p]", name(),m_transferorID.c_str(),this); #endif m_rspCode = m_notifyCode = (ok ? 487 : 481); break; } m_msg->userData(chan); if ((m_msg->retValue() == "-") || (m_msg->retValue() == YSTRING("error"))) m_rspCode = m_notifyCode = 603; // Decline else if (m_msg->getIntValue(YSTRING("antiloop"),1) <= 0) m_rspCode = m_notifyCode = 482; // Loop Detected else { DDebug(&plugin,DebugAll,"%s(%s). Call succesfully routed [%p]", name(),m_transferorID.c_str(),this); *m_msg = "call.execute"; m_msg->setParam("callto",m_msg->retValue()); m_msg->clearParam(YSTRING("error")); m_msg->retValue().clear(); if (Engine::dispatch(m_msg)) { DDebug(&plugin,DebugAll,"%s(%s). 'call.execute' succeeded [%p]", name(),m_transferorID.c_str(),this); m_rspCode = 202; m_notifyCode = 200; } else { DDebug(&plugin,DebugAll,"%s(%s). 'call.execute' failed [%p]", name(),m_transferorID.c_str(),this); m_rspCode = m_notifyCode = 603; // Decline } } break; } release(); } // Respond the transaction and deref() it void YateSIPRefer::setTrResponse(int code) { if (!m_transaction) return; SIPTransaction* t = m_transaction; m_transaction = 0; m_rspCode = code; t->setResponse(m_rspCode); TelEngine::destruct(t); } // Set transaction response. Send the notification message. Notify the // connection and release other objects void YateSIPRefer::release(bool fromCleanup) { setTrResponse(m_rspCode); TelEngine::destruct(m_msg); // Set NOTIFY response and send it (only if the transaction was accepted) if (m_sipNotify) { if (m_rspCode < 300 && plugin.ep() && plugin.ep()->engine()) { String s; s << "SIP/2.0 " << m_notifyCode << " " << lookup(m_notifyCode,SIPResponses) << "\r\n"; m_sipNotify->setBody(new MimeStringBody("message/sipfrag;version=2.0",s)); plugin.ep()->engine()->addMessage(m_sipNotify); m_sipNotify = 0; } else TelEngine::destruct(m_sipNotify); // If we still have a NOTIFY message in cleanup() the thread // was cancelled in the hard way if (fromCleanup) Debug(&plugin,DebugWarn,"YateSIPRefer(%s) thread terminated abnormally [%p]", m_transferorID.c_str(),this); } // Notify transferor on termination if (m_transferorID) { plugin.lock(); YateSIPConnection* conn = static_cast(plugin.find(m_transferorID)); if (conn) conn->referTerminated(); plugin.unlock(); m_transferorID = ""; } } // Incoming call constructor - just before starting the routing thread YateSIPConnection::YateSIPConnection(SIPEvent* ev, SIPTransaction* tr) : Channel(plugin,0,false), SDPSession(&plugin.parser()), YateSIPPartyHolder(driver()), m_tr(tr), m_tr2(0), m_hungup(false), m_byebye(true), m_cancel(false), m_state(Incoming), m_port(0), m_route(0), m_routes(0), m_authBye(true), m_inband(s_inband), m_info(s_info), m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0) { Debug(this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,%p) [%p]",ev,tr,this); setReason(); m_tr->ref(); m_routes = m_tr->initialMessage()->getRoutes(); m_callid = m_tr->getCallID(); m_dialog = *m_tr->initialMessage(); m_tr->initialMessage()->getParty()->getAddr(m_host,m_port,false); m_address << m_host << ":" << m_port; filterDebug(m_address); m_uri = m_tr->initialMessage()->getHeader("From"); m_uri.parse(); m_tr->setUserData(this); // Set channel SIP party setParty(m_tr->initialMessage()->getParty()); URI uri(m_tr->getURI()); YateSIPLine* line = plugin.findLine(m_host,m_port,uri.getUser()); Message *m = message("call.preroute"); decodeIsupBody(*m,m_tr->initialMessage()->body); m->addParam("caller",m_uri.getUser()); m->addParam("called",uri.getUser()); if (m_uri.getDescription()) m->addParam("callername",m_uri.getDescription()); const MimeHeaderLine* hl = m_tr->initialMessage()->getHeader("Call-Info"); if (hl) { const NamedString* type = hl->getParam("purpose"); if (!type || *type == YSTRING("info")) m->addParam("caller_info_uri",*hl); else if (*type == YSTRING("icon")) m->addParam("caller_icon_uri",*hl); else if (*type == YSTRING("card")) m->addParam("caller_card_uri",*hl); } if (line) { // call comes from line we have registered to - trust it... m_user = line->getUserName(); m_externalAddr = line->getLocalAddr(); m_line = *line; m_domain = line->domain(); m->addParam("username",m_user); m->addParam("domain",m_domain); m->addParam("in_line",m_line); } else { String user; int age = tr->authUser(user,false,m); DDebug(this,DebugAll,"User '%s' age %d",user.c_str(),age); if (age >= 0) { if (age < 10) { m_user = user; m->addParam("username",m_user); } else m->addParam("expired_user",user); m->addParam("xsip_nonce_age",String(age)); } m_domain = m->getValue(YSTRING("domain")); } if (s_privacy) copyPrivacy(*m,*ev->getMessage()); String tmp(ev->getMessage()->getHeaderValue("Max-Forwards")); int maxf = tmp.toInteger(s_maxForwards); if (maxf > s_maxForwards) maxf = s_maxForwards; tmp = maxf-1; m->addParam("antiloop",tmp); m->addParam("ip_host",m_host); m->addParam("ip_port",String(m_port)); m->addParam("ip_transport",m_tr->initialMessage()->getParty()->getProtoName()); m->addParam("sip_uri",uri); m->addParam("sip_from",m_uri); m->addParam("sip_to",ev->getMessage()->getHeaderValue("To")); m->addParam("sip_callid",m_callid); m->addParam("device",ev->getMessage()->getHeaderValue("User-Agent")); copySipHeaders(*m,*ev->getMessage()); const char* reason = 0; hl = m_tr->initialMessage()->getHeader("Referred-By"); if (hl) reason = "transfer"; else { hl = m_tr->initialMessage()->getHeader("Diversion"); if (hl) { reason = "divert"; const String* par = hl->getParam("reason"); if (par) { tmp = par->c_str(); MimeHeaderLine::delQuotes(tmp); if (tmp.trimBlanks()) m->addParam("divert_reason",tmp); } par = hl->getParam("privacy"); if (par) { tmp = par->c_str(); MimeHeaderLine::delQuotes(tmp); if (tmp.trimBlanks()) m->addParam("divert_privacy",tmp); } par = hl->getParam("screen"); if (par) { tmp = par->c_str(); MimeHeaderLine::delQuotes(tmp); if (tmp.trimBlanks()) m->addParam("divert_screen",tmp); } } } if (hl) { URI div(*hl); m->addParam("diverter",div.getUser()); if (div.getDescription()) m->addParam("divertername",div.getDescription()); m->addParam("diverteruri",div); } setRtpLocalAddr(m_rtpLocalAddr); MimeSdpBody* sdp = getSdpBody(ev->getMessage()->body); if (sdp) { setMedia(plugin.parser().parse(sdp,m_rtpAddr,m_rtpMedia)); if (m_rtpMedia) { m_rtpForward = true; // guess if the call comes from behind a NAT bool nat = isNatBetween(m_rtpAddr,m_host); if (m->getBoolValue(YSTRING("nat_support"),s_auto_nat && nat)) { Debug(this,DebugInfo,"RTP NAT detected: private '%s' public '%s'", m_rtpAddr.c_str(),m_host.c_str()); m->addParam("rtp_nat_addr",m_rtpAddr); m_rtpAddr = m_host; } m->addParam("rtp_addr",m_rtpAddr); putMedia(*m); } if (plugin.parser().sdpForward()) { const DataBlock& raw = sdp->getBody(); String tmp((const char*)raw.data(),raw.length()); m->addParam("sdp_raw",tmp); m_rtpForward = true; } if (m_rtpForward) m->addParam("rtp_forward","possible"); } DDebug(this,DebugAll,"RTP addr '%s' [%p]",m_rtpAddr.c_str(),this); if (reason) m->addParam("reason",reason); m_route = m; Message* s = message("chan.startup"); s->addParam("caller",m_uri.getUser()); s->addParam("called",uri.getUser()); if (m_user) s->addParam("username",m_user); Engine::enqueue(s); } // Outgoing call constructor - in call.execute handler YateSIPConnection::YateSIPConnection(Message& msg, const String& uri, const char* target) : Channel(plugin,0,true), SDPSession(&plugin.parser()), YateSIPPartyHolder(driver()), m_tr(0), m_tr2(0), m_hungup(false), m_byebye(true), m_cancel(true), m_state(Outgoing), m_port(0), m_route(0), m_routes(0), m_authBye(false), m_inband(s_inband), m_info(s_info), m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0) { Debug(this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,'%s') [%p]", &msg,uri.c_str(),this); m_targetid = target; setReason(); m_inband = msg.getBoolValue(YSTRING("dtmfinband"),s_inband); m_info = msg.getBoolValue(YSTRING("dtmfinfo"),s_info); m_secure = msg.getBoolValue(YSTRING("secure"),plugin.parser().secure()); setRfc2833(msg.getParam(YSTRING("rfc2833"))); m_rtpForward = msg.getBoolValue(YSTRING("rtp_forward")); m_user = msg.getValue(YSTRING("user")); m_line = msg.getValue(YSTRING("line")); String tmp; YateSIPLine* line = 0; if (m_line) { line = plugin.findLine(m_line); if (line) { if (uri.find('@') < 0 && !uri.startsWith("tel:")) { if (!uri.startsWith("sip:")) tmp = "sip:"; tmp << uri << "@" << line->domain(); } m_externalAddr = line->getLocalAddr(); } } if (tmp.null()) { if (!(uri.startsWith("tel:") || uri.startsWith("sip:"))) { int sep = uri.find(':'); if ((sep < 0) || ((sep > 0) && (uri.substr(sep+1).toInteger(-1) > 0))) tmp = "sip:"; } tmp << uri; } m_uri = tmp; m_uri.parse(); if (!setParty(msg,false,"o",m_uri.getHost(),m_uri.getPort()) && line) { SIPParty* party = line->party(); setParty(party); TelEngine::destruct(party); } SIPMessage* m = new SIPMessage("INVITE",m_uri); setSipParty(m,line,true,msg.getValue("host"),msg.getIntValue("port")); if (!m->getParty()) { Debug(this,DebugWarn,"Could not create party for '%s' [%p]",m_uri.c_str(),this); TelEngine::destruct(m); tmp = "Invalid address: "; tmp << m_uri; msg.setParam("reason",tmp); setReason(tmp,500); return; } int maxf = msg.getIntValue(YSTRING("antiloop"),s_maxForwards); m->addHeader("Max-Forwards",String(maxf)); copySipHeaders(*m,msg); m_domain = msg.getValue(YSTRING("domain")); const String* callerId = msg.getParam(YSTRING("caller")); String caller; if (callerId) caller = *callerId; else if (line) { caller = line->getUserName(); callerId = &caller; m_domain = line->domain(m_domain); } String display = msg.getValue(YSTRING("callername"),(line ? line->getFullName().c_str() : (const char*)0)); m->complete(plugin.ep()->engine(), callerId ? (callerId->null() ? "anonymous" : callerId->c_str()) : (const char*)0, m_domain, 0, msg.getIntValue(YSTRING("xsip_flags"),-1)); if (display) { MimeHeaderLine* hl = const_cast(m->getHeader("From")); if (hl) { MimeHeaderLine::addQuotes(display); *hl = display + " " + *hl; } } if (msg.getParam(YSTRING("calledname"))) { display = msg.getValue(YSTRING("calledname")); MimeHeaderLine* hl = const_cast(m->getHeader("To")); if (hl) { MimeHeaderLine::addQuotes(display); *hl = display + " " + *hl; } } if (plugin.ep()->engine()->prack()) m->addHeader("Supported","100rel"); m->getParty()->getAddr(m_host,m_port,false); m_address << m_host << ":" << m_port; filterDebug(m_address); m_dialog = *m; if (s_privacy) copyPrivacy(*m,msg); // Check if this is a transferred call String* diverter = msg.getParam(YSTRING("diverter")); if (!null(diverter)) { const MimeHeaderLine* from = m->getHeader("From"); if (from) { URI fr(*from); URI d(fr.getProtocol(),*diverter,fr.getHost(),fr.getPort(), msg.getValue(YSTRING("divertername"),"")); String* reason = msg.getParam(YSTRING("divert_reason")); String* privacy = msg.getParam(YSTRING("divert_privacy")); String* screen = msg.getParam(YSTRING("divert_screen")); bool divert = !(TelEngine::null(reason) && TelEngine::null(privacy) && TelEngine::null(screen)); divert = msg.getBoolValue(YSTRING("diversion"),divert); MimeHeaderLine* hl = new MimeHeaderLine(divert ? "Diversion" : "Referred-By",d); if (divert) { if (!TelEngine::null(reason)) hl->setParam("reason",MimeHeaderLine::quote(*reason)); if (!TelEngine::null(privacy)) hl->setParam("privacy",MimeHeaderLine::quote(*privacy)); if (!TelEngine::null(screen)) hl->setParam("screen",MimeHeaderLine::quote(*screen)); } m->addHeader(hl); } } // add some Call-Info headers const char* info = msg.getValue(YSTRING("caller_info_uri")); if (info) { MimeHeaderLine* hl = new MimeHeaderLine("Call-Info",info); hl->setParam("purpose","info"); m->addHeader(hl); } info = msg.getValue(YSTRING("caller_icon_uri")); if (info) { MimeHeaderLine* hl = new MimeHeaderLine("Call-Info",info); hl->setParam("purpose","icon"); m->addHeader(hl); } info = msg.getValue(YSTRING("caller_card_uri")); if (info) { MimeHeaderLine* hl = new MimeHeaderLine("Call-Info",info); hl->setParam("purpose","card"); m->addHeader(hl); } setRtpLocalAddr(m_rtpLocalAddr,&msg); MimeSdpBody* sdp = createPasstroughSDP(msg); if (!sdp) sdp = createRtpSDP(m_host,msg); m->setBody(buildSIPBody(msg,sdp)); m_tr = plugin.ep()->engine()->addMessage(m); if (m_tr) { m_tr->ref(); m_callid = m_tr->getCallID(); m_tr->setUserData(this); } m->deref(); setMaxcall(msg); Message* s = message("chan.startup",msg); s->setParam("caller",caller); s->copyParams(msg,"callername,called,billid,callto,username"); s->setParam("calledfull",m_uri.getUser()); if (m_callid) s->setParam("sip_callid",m_callid); Engine::enqueue(s); } YateSIPConnection::~YateSIPConnection() { Debug(this,DebugAll,"YateSIPConnection::~YateSIPConnection() [%p]",this); } void YateSIPConnection::destroyed() { DDebug(this,DebugAll,"YateSIPConnection::destroyed() [%p]",this); hangup(); clearTransaction(); TelEngine::destruct(m_route); TelEngine::destruct(m_routes); Channel::destroyed(); } void YateSIPConnection::startRouter() { Message* m = m_route; m_route = 0; Channel::startRouter(m); } void YateSIPConnection::clearTransaction() { if (!(m_tr || m_tr2)) return; Lock lock(driver()); if (m_tr) { m_tr->setUserData(0); if (m_tr->setResponse()) { SIPMessage* m = new SIPMessage(m_tr->initialMessage(),m_reasonCode, m_reason.safe("Request Terminated")); copySipHeaders(*m,parameters(),0); m->setBody(buildSIPBody()); m_tr->setResponse(m); TelEngine::destruct(m); m_byebye = false; } else if (m_hungup && m_tr->isIncoming() && m_dialog.localTag.null()) m_dialog.localTag = m_tr->getDialogTag(); m_tr->deref(); m_tr = 0; } // cancel any pending reINVITE if (m_tr2) { m_tr2->setUserData(0); if (m_tr2->isIncoming()) m_tr2->setResponse(487); m_tr2->deref(); m_tr2 = 0; } } void YateSIPConnection::detachTransaction2() { Lock lock(driver()); if (m_tr2) { m_tr2->setUserData(0); m_tr2->deref(); m_tr2 = 0; if (m_reInviting != ReinvitePending) m_reInviting = ReinviteNone; } startPendingUpdate(); } void YateSIPConnection::hangup() { if (m_hungup) return; m_hungup = true; const char* error = lookup(m_reasonCode,dict_errors); Debug(this,DebugAll,"YateSIPConnection::hangup() state=%d trans=%p error='%s' code=%d reason='%s' [%p]", m_state,m_tr,error,m_reasonCode,m_reason.c_str(),this); setMedia(0); Message* m = message("chan.hangup"); if (m_reason) m->setParam("reason",m_reason); Engine::enqueue(m); switch (m_state) { case Cleared: clearTransaction(); return; case Incoming: if (m_tr) { clearTransaction(); return; } break; case Outgoing: case Ringing: if (m_cancel && m_tr) { SIPMessage* m = new SIPMessage("CANCEL",m_uri); setSipParty(m,plugin.findLine(m_line),true,m_host,m_port); if (!m->getParty()) Debug(this,DebugWarn,"Could not create party for '%s:%d' [%p]", m_host.c_str(),m_port,this); else { const SIPMessage* i = m_tr->initialMessage(); m->copyHeader(i,"Via"); m->copyHeader(i,"From"); m->copyHeader(i,"To"); m->copyHeader(i,"Call-ID"); String tmp; tmp << i->getCSeq() << " CANCEL"; m->addHeader("CSeq",tmp); if (m_reason == YSTRING("pickup")) { MimeHeaderLine* hl = new MimeHeaderLine("Reason","SIP"); hl->setParam("cause","200"); hl->setParam("text","\"Call completed elsewhere\""); m->addHeader(hl); } m->setBody(buildSIPBody()); plugin.ep()->engine()->addMessage(m); } m->deref(); } break; } clearTransaction(); m_state = Cleared; if (m_byebye && m_dialog.localTag && m_dialog.remoteTag) { SIPMessage* m = createDlgMsg("BYE"); if (m) { if (m_reason) { // FIXME: add SIP and Q.850 cause codes, set the proper reason MimeHeaderLine* hl = new MimeHeaderLine("Reason","SIP"); if ((m_reasonCode >= 300) && (m_reasonCode <= 699) && (m_reasonCode != 487)) hl->setParam("cause",String(m_reasonCode)); hl->setParam("text",MimeHeaderLine::quote(m_reason)); m->addHeader(hl); } const char* stats = parameters().getValue(YSTRING("rtp_stats")); if (stats) m->addHeader("P-RTP-Stat",stats); copySipHeaders(*m,parameters(),0); m->setBody(buildSIPBody()); plugin.ep()->engine()->addMessage(m); m->deref(); } } m_byebye = false; if (!error) error = m_reason.c_str(); disconnect(error,parameters()); } // Creates a new message in an existing dialog SIPMessage* YateSIPConnection::createDlgMsg(const char* method, const char* uri) { if (!uri) uri = m_uri; SIPMessage* m = new SIPMessage(method,uri); m->addRoutes(m_routes); setSipParty(m,plugin.findLine(m_line),true,m_host,m_port); if (!m->getParty()) { Debug(this,DebugWarn,"Could not create party for '%s:%d' [%p]", m_host.c_str(),m_port,this); m->destruct(); return 0; } m->addHeader("Call-ID",m_callid); String tmp; tmp << "<" << m_dialog.localURI << ">"; MimeHeaderLine* hl = new MimeHeaderLine("From",tmp); tmp = m_dialog.localTag; if (tmp.null() && m_tr) tmp = m_tr->getDialogTag(); if (tmp) hl->setParam("tag",tmp); m->addHeader(hl); tmp.clear(); tmp << "<" << m_dialog.remoteURI << ">"; hl = new MimeHeaderLine("To",tmp); tmp = m_dialog.remoteTag; if (tmp.null() && m_tr) tmp = m_tr->getDialogTag(); if (tmp) hl->setParam("tag",tmp); m->addHeader(hl); return m; } // Emit a call.update to notify cdrbuild of callid dialog tags change void YateSIPConnection::emitUpdate() { Message* m = message("call.update"); m->addParam("operation","cdrbuild"); Engine::enqueue(m); } // Emit a PRovisional ACK if enabled in the engine, return true to handle them bool YateSIPConnection::emitPRACK(const SIPMessage* msg) { if (!(msg && msg->isAnswer() && (msg->code > 100) && (msg->code < 200))) return false; if (!plugin.ep()->engine()->prack()) return true; const MimeHeaderLine* rs = msg->getHeader("RSeq"); const MimeHeaderLine* cs = msg->getHeader("CSeq"); if (!(rs && cs)) return true; int seq = rs->toInteger(0,10); // return false only if we already seen this provisional response if (seq == m_lastRseq) return false; if (seq < m_lastRseq) { Debug(this,DebugMild,"Not sending PRACK for RSeq %d < %d [%p]", seq,m_lastRseq,this); return false; } String tmp; const MimeHeaderLine* co = msg->getHeader("Contact"); if (co) { tmp = *co; static const Regexp r("^[^<]*<\\([^>]*\\)>.*$"); if (tmp.matches(r)) tmp = tmp.matchString(1); } SIPMessage* m = createDlgMsg("PRACK",tmp); if (!m) return true; m_lastRseq = seq; tmp = *rs; tmp << " " << *cs; m->addHeader("RAck",tmp); plugin.ep()->engine()->addMessage(m); m->deref(); return true; } // Creates a SDP for provisional (1xx) messages MimeSdpBody* YateSIPConnection::createProvisionalSDP(Message& msg) { if (!msg.getBoolValue(YSTRING("earlymedia"),true)) return 0; if (m_rtpForward) return createPasstroughSDP(msg); // check if our peer can source at least audio data if (!(getPeer() && getPeer()->getSource())) return 0; if (m_rtpAddr.null()) return 0; if (s_1xx_formats) updateFormats(msg); return createRtpSDP(true); } // Build and populate a chan.rtp message Message* YateSIPConnection::buildChanRtp(RefObject* context) { Message* m = new Message("chan.rtp"); if (context) m->userData(context); else { complete(*m,true); m->addParam("call_direction",direction()); m->addParam("call_address",address()); m->addParam("call_status",status()); m->addParam("call_billid",billid()); m->userData(static_cast(this)); } return m; } // Media changed notification, reimplemented from SDPSession void YateSIPConnection::mediaChanged(const SDPMedia& media) { SDPSession::mediaChanged(media); if (media.id() && media.transport()) { Message m("chan.rtp"); m.addParam("rtpid",media.id()); m.addParam("media",media); m.addParam("transport",media.transport()); m.addParam("terminate",String::boolText(true)); m.addParam("call_direction",direction()); m.addParam("call_address",address()); m.addParam("call_status",status()); m.addParam("call_billid",billid()); Engine::dispatch(m); const char* stats = m.getValue(YSTRING("stats")); if (stats) parameters().setParam("rtp_stats"+media.suffix(),stats); } // Clear the data endpoint, will be rebuilt later if required clearEndpoint(media); } // Process SIP events belonging to this connection bool YateSIPConnection::process(SIPEvent* ev) { const SIPMessage* msg = ev->getMessage(); int code = ev->getTransaction()->getResponseCode(); DDebug(this,DebugInfo,"YateSIPConnection::process(%p) %s %s code=%d [%p]", ev,ev->isActive() ? "active" : "inactive", SIPTransaction::stateName(ev->getState()),code,this); #ifdef XDEBUG if (msg) Debug(this,DebugInfo,"Message %p '%s' %s %s code=%d body=%p", msg,msg->method.c_str(), msg->isOutgoing() ? "outgoing" : "incoming", msg->isAnswer() ? "answer" : "request", msg->code,msg->body); #endif Lock mylock(driver()); if (ev->getTransaction() == m_tr2) { mylock.drop(); return processTransaction2(ev,msg,code); } bool updateTags = true; SIPDialog oldDlg(m_dialog); m_dialog = *ev->getTransaction()->recentMessage(); mylock.drop(); if (msg && !msg->isOutgoing() && msg->isAnswer() && (code >= 300) && (code <= 699)) { updateTags = false; m_cancel = false; m_byebye = false; parameters().clearParams(); parameters().addParam("cause_sip",String(code)); parameters().addParam("reason_sip",msg->reason); setReason(msg->reason,code); if (msg->body) { Message tmp("isup.decode"); if (decodeIsupBody(tmp,msg->body)) parameters().copyParams(tmp); } copySipHeaders(parameters(),*msg); if (code < 400) { // this is a redirect, it should provide a Contact and possibly a Diversion const MimeHeaderLine* hl = msg->getHeader("Contact"); if (hl) { parameters().addParam("redirect",String::boolText(true)); URI uri(*hl); parameters().addParam("called",uri.getUser()); if (uri.getDescription()) parameters().addParam("calledname",uri.getDescription()); parameters().addParam("calleduri",uri); hl = msg->getHeader("Diversion"); if (hl) { uri = *hl; parameters().addParam("diverter",uri.getUser()); if (uri.getDescription()) parameters().addParam("divertername",uri.getDescription()); parameters().addParam("diverteruri",uri); String tmp = hl->getParam("reason"); MimeHeaderLine::delQuotes(tmp); if (tmp.trimBlanks()) parameters().addParam("divert_reason",tmp); tmp = hl->getParam("privacy"); MimeHeaderLine::delQuotes(tmp); if (tmp.trimBlanks()) parameters().addParam("divert_privacy",tmp); tmp = hl->getParam("screen"); MimeHeaderLine::delQuotes(tmp); if (tmp.trimBlanks()) parameters().addParam("divert_screen",tmp); } } else Debug(this,DebugMild,"Received %d redirect without Contact [%p]",code,this); } hangup(); } else if (code == 408) { // Proxy timeout does not provide an answer message updateTags = false; if (m_dialog.remoteTag.null()) m_byebye = false; parameters().setParam("cause_sip","408"); setReason("Request Timeout",code); hangup(); } // Only update channels' callid if dialog tags change if (updateTags) { Lock lock(driver()); updateTags = (oldDlg |= m_dialog); } if (!ev->isActive()) { Lock lock(driver()); if (m_tr) { DDebug(this,DebugInfo,"YateSIPConnection clearing transaction %p [%p]", m_tr,this); m_tr->setUserData(0); m_tr->deref(); m_tr = 0; } if (m_state != Established) hangup(); else if (s_ack_required && (code == 408)) { // call was established but we didn't got the ACK setReason("Not received ACK",code); hangup(); } else { if (updateTags) emitUpdate(); startPendingUpdate(); } return false; } if (!msg || msg->isOutgoing()) { if (updateTags) emitUpdate(); return false; } String natAddr; MimeSdpBody* sdp = getSdpBody(msg->body); if (sdp) { DDebug(this,DebugInfo,"YateSIPConnection got SDP [%p]",this); setMedia(plugin.parser().parse(sdp,m_rtpAddr,m_rtpMedia)); // guess if the call comes from behind a NAT if (s_auto_nat && isNatBetween(m_rtpAddr,m_host)) { Debug(this,DebugInfo,"RTP NAT detected: private '%s' public '%s'", m_rtpAddr.c_str(),m_host.c_str()); natAddr = m_rtpAddr; m_rtpAddr = m_host; } DDebug(this,DebugAll,"RTP addr '%s' [%p]",m_rtpAddr.c_str(),this); } if ((!m_routes) && msg->isAnswer() && (msg->code > 100) && (msg->code < 300)) m_routes = msg->getRoutes(); if (msg->isAnswer() && m_externalAddr.null() && m_line) { // see if we should detect our external address const YateSIPLine* line = plugin.findLine(m_line); if (line && line->localDetect()) { const MimeHeaderLine* hl = msg->getHeader("Via"); if (hl) { const NamedString* par = hl->getParam("received"); if (par && *par) { m_externalAddr = *par; Debug(this,DebugInfo,"Detected local address '%s' [%p]", m_externalAddr.c_str(),this); } } } } if (msg->isAnswer() && ((msg->code / 100) == 2)) { updateTags = false; m_cancel = false; Lock lock(driver()); const SIPMessage* ack = m_tr ? m_tr->latestMessage() : 0; if (ack && ack->isACK()) { // accept any URI change caused by a Contact: header in the 2xx m_uri = ack->uri; m_uri.parse(); DDebug(this,DebugInfo,"YateSIPConnection clearing answered transaction %p [%p]", m_tr,this); m_tr->setUserData(0); m_tr->deref(); m_tr = 0; } lock.drop(); setReason("",0); setStatus("answered",Established); Message *m = message("call.answered"); copySipHeaders(*m,*msg); decodeIsupBody(*m,msg->body); addRtpParams(*m,natAddr,msg->body); Engine::enqueue(m); startPendingUpdate(); } if (emitPRACK(msg)) { if (s_multi_ringing || (m_state < Ringing)) { const char* name = "call.progress"; const char* reason = 0; switch (msg->code) { case 180: updateTags = false; name = "call.ringing"; setStatus("ringing",Ringing); break; case 181: reason = "forwarded"; setStatus("progressing"); break; case 182: reason = "queued"; setStatus("progressing"); break; case 183: setStatus("progressing"); break; // for all others emit a call.progress but don't change status } if (name) { Message* m = message(name); copySipHeaders(*m,*msg); decodeIsupBody(*m,msg->body); if (reason) m->addParam("reason",reason); addRtpParams(*m,natAddr,msg->body); if (m_rtpAddr.null()) m->addParam("earlymedia","false"); Engine::enqueue(m); } } } if (updateTags) emitUpdate(); if (msg->isACK()) { DDebug(this,DebugInfo,"YateSIPConnection got ACK [%p]",this); startRtp(); } return false; } // Process secondary transaction (reINVITE) belonging to this connection bool YateSIPConnection::processTransaction2(SIPEvent* ev, const SIPMessage* msg, int code) { if (ev->getState() == SIPTransaction::Cleared) { bool fatal = (m_reInviting == ReinviteRequest); detachTransaction2(); if (fatal) { setReason("Request Timeout",408); hangup(); } else { Message* m = message("call.update"); m->addParam("operation","reject"); m->addParam("error","timeout"); Engine::enqueue(m); } return false; } if (!msg || msg->isOutgoing() || !msg->isAnswer()) return false; if (code < 200) return false; if (m_reInviting == ReinviteRequest) { detachTransaction2(); // we emitted a client reINVITE, now we are forced to deal with it if (code < 300) { MimeSdpBody* sdp = getSdpBody(msg->body); while (sdp) { String addr; ObjList* lst = plugin.parser().parse(sdp,addr,0,String::empty(),m_rtpForward); if (!lst) break; if ((addr == m_rtpAddr) || isNatBetween(addr,m_host)) { ObjList* l = m_rtpMedia; for (; l; l = l->next()) { SDPMedia* m = static_cast(l->get()); if (!m) continue; SDPMedia* m2 = static_cast((*lst)[*m]); if (!m2) continue; // both old and new media exist, compare ports if (m->remotePort() != m2->remotePort()) { DDebug(this,DebugWarn,"Port for '%s' changed: '%s' -> '%s' [%p]", m->c_str(),m->remotePort().c_str(), m2->remotePort().c_str(),this); TelEngine::destruct(lst); break; } } if (lst) { setMedia(lst); return false; } } TelEngine::destruct(lst); setReason("Media information changed during reINVITE",415); hangup(); return false; } setReason("Missing media information",415); } else setReason(msg->reason,code); hangup(); return false; } Message* m = message("call.update"); decodeIsupBody(*m,msg->body); if (code < 300) { m->addParam("operation","notify"); String natAddr; MimeSdpBody* sdp = getSdpBody(msg->body); if (sdp) { DDebug(this,DebugInfo,"YateSIPConnection got reINVITE SDP [%p]",this); setMedia(plugin.parser().parse(sdp,m_rtpAddr,m_rtpMedia,String::empty(),m_rtpForward)); // guess if the call comes from behind a NAT if (s_auto_nat && isNatBetween(m_rtpAddr,m_host)) { Debug(this,DebugInfo,"RTP NAT detected: private '%s' public '%s'", m_rtpAddr.c_str(),m_host.c_str()); natAddr = m_rtpAddr; m_rtpAddr = m_host; } DDebug(this,DebugAll,"RTP addr '%s' [%p]",m_rtpAddr.c_str(),this); if (m_rtpForward) { // drop any local RTP we might have before m_mediaStatus = m_rtpAddr.null() ? MediaMuted : MediaMissing; m_rtpLocalAddr.clear(); clearEndpoint(); } } if (!addRtpParams(*m,natAddr,sdp)) addSdpParams(*m,sdp); } else { m->addParam("operation","reject"); m->addParam("error",lookup(code,dict_errors,"failure")); m->addParam("reason",msg->reason); } detachTransaction2(); Engine::enqueue(m); return false; } void YateSIPConnection::reInvite(SIPTransaction* t) { if (!checkUser(t)) return; DDebug(this,DebugAll,"YateSIPConnection::reInvite(%p) [%p]",t,this); Lock mylock(driver()); int invite = m_reInviting; if (m_tr || m_tr2 || (invite == ReinviteRequest) || (invite == ReinviteReceived)) { // another request pending - refuse this one t->setResponse(491); return; } if (m_hungup) { t->setResponse(481); return; } m_reInviting = ReinviteReceived; mylock.drop(); // hack: use a while instead of if so we can return or break out of it MimeSdpBody* sdp = getSdpBody(t->initialMessage()->body); while (sdp) { // for pass-trough RTP we need support from our peer if (m_rtpForward) { String addr; String natAddr; ObjList* lst = plugin.parser().parse(sdp,addr,0,String::empty(),true); if (!lst) break; // guess if the call comes from behind a NAT if (s_auto_nat && isNatBetween(addr,m_host)) { Debug(this,DebugInfo,"RTP NAT detected: private '%s' public '%s'", addr.c_str(),m_host.c_str()); natAddr = addr; addr = m_host; } Debug(this,DebugAll,"reINVITE RTP addr '%s'",addr.c_str()); Message msg("call.update"); complete(msg); msg.addParam("operation","request"); copySipHeaders(msg,*t->initialMessage()); msg.addParam("rtp_forward","yes"); msg.addParam("rtp_addr",addr); if (natAddr) msg.addParam("rtp_nat_addr",natAddr); putMedia(msg,lst); addSdpParams(msg,sdp); bool ok = Engine::dispatch(msg); Lock mylock2(driver()); // if peer doesn't support updates fail the reINVITE if (!ok) { t->setResponse(msg.getIntValue(YSTRING("error"),dict_errors,488),msg.getValue(YSTRING("reason"))); m_reInviting = invite; } else if (m_tr2) { // ouch! this shouldn't have happened! t->setResponse(491); // media is uncertain now so drop the call setReason("Internal Server Error",500); mylock2.drop(); hangup(); } else { // we remember the request and leave it pending t->ref(); t->setUserData(this); m_tr2 = t; } return; } // refuse request if we had no media at all before if (m_mediaStatus == MediaMissing) break; String addr; ObjList* lst = plugin.parser().parse(sdp,addr); if (!lst) break; // guess if the call comes from behind a NAT if (s_auto_nat && isNatBetween(addr,m_host)) { Debug(this,DebugInfo,"RTP NAT detected: private '%s' public '%s'", addr.c_str(),m_host.c_str()); addr = m_host; } // TODO: check if we should accept the new media // many implementation don't handle well failure so we should drop if (m_rtpAddr != addr) { m_rtpAddr = addr; Debug(this,DebugAll,"New RTP addr '%s'",m_rtpAddr.c_str()); // clear all data endpoints - createRtpSDP will build new ones clearEndpoint(); } setMedia(lst); m_mediaStatus = MediaMissing; // let RTP guess again the local interface or use the enforced address setRtpLocalAddr(m_rtpLocalAddr); SIPMessage* m = new SIPMessage(t->initialMessage(), 200); MimeSdpBody* sdpNew = createRtpSDP(true); m->setBody(sdpNew); t->setResponse(m); m->deref(); Message* msg = message("call.update"); msg->addParam("operation","notify"); msg->addParam("mandatory","false"); msg->addParam("mute",String::boolText(MediaStarted != m_mediaStatus)); putMedia(*msg); Engine::enqueue(msg); m_reInviting = invite; return; } m_reInviting = invite; if (s_refresh_nosdp && !sdp) { // be permissive, accept session refresh with no SDP SIPMessage* m = new SIPMessage(t->initialMessage(),200); // if required provide our own media offer if (!m_rtpForward) m->setBody(createSDP()); t->setResponse(m); m->deref(); return; } t->setResponse(488); } bool YateSIPConnection::checkUser(SIPTransaction* t, bool refuse) { // don't try to authenticate requests from server if (m_user.null() || m_line) return true; NamedList params(""); params.addParam("billid",billid(),false); int age = t->authUser(m_user,false,¶ms); if ((age >= 0) && (age <= 10)) return true; DDebug(this,DebugAll,"YateSIPConnection::checkUser(%p) failed, age %d [%p]",t,age,this); if (refuse) { Lock lck(s_globalMutex); t->requestAuth(s_realm,m_domain,age >= 0); } return false; } void YateSIPConnection::doBye(SIPTransaction* t) { if (m_authBye && !checkUser(t)) return; DDebug(this,DebugAll,"YateSIPConnection::doBye(%p) [%p]",t,this); const SIPMessage* msg = t->initialMessage(); if (msg->body) { Message tmp("isup.decode"); if (decodeIsupBody(tmp,msg->body)) parameters().copyParams(tmp); } copySipHeaders(parameters(),*msg); const MimeHeaderLine* hl = msg->getHeader("Reason"); if (hl) { const NamedString* text = hl->getParam("text"); if (text) m_reason = MimeHeaderLine::unquote(*text); // FIXME: add SIP and Q.850 cause codes } setMedia(0); SIPMessage* m = new SIPMessage(t->initialMessage(),200); const char* stats = parameters().getValue(YSTRING("rtp_stats")); if (stats) m->addHeader("P-RTP-Stat",stats); t->setResponse(m); m->deref(); m_byebye = false; hangup(); } void YateSIPConnection::doCancel(SIPTransaction* t) { #ifdef DEBUG // CANCEL cannot be challenged but it may (should?) be authenticated with // an old nonce from the transaction that is being cancelled if (m_user && (t->authUser(m_user) < 0)) Debug(&plugin,DebugMild,"User authentication failed for user '%s' but CANCELing anyway [%p]", m_user.c_str(),this); #endif DDebug(this,DebugAll,"YateSIPConnection::doCancel(%p) [%p]",t,this); if (m_tr) { t->setResponse(200); m_byebye = false; clearTransaction(); disconnect("Cancelled"); } else t->setResponse(481); } bool YateSIPConnection::doInfo(SIPTransaction* t) { if (m_authBye && !checkUser(t)) return true; DDebug(this,DebugAll,"YateSIPConnection::doInfo(%p) [%p]",t,this); if (m_hungup) { t->setResponse(481); return true; } int sig = -1; const MimeLinesBody* lb = YOBJECT(MimeLinesBody,getOneBody(t->initialMessage()->body,"application/dtmf-relay")); const MimeStringBody* sb = YOBJECT(MimeStringBody,getOneBody(t->initialMessage()->body,"application/dtmf")); if (lb) { const ObjList* l = lb->lines().skipNull(); for (; l; l = l->skipNext()) { String tmp = static_cast(l->get()); tmp.toUpper(); if (tmp.startSkip("SIGNAL=",false)) { sig = tmp.trimBlanks().toInteger(info_signals,-1); break; } } } else if (sb) { String tmp = sb->text(); tmp.trimSpaces(); sig = tmp.toInteger(info_signals,-1); } else return false; t->setResponse(200); if ((sig >= 0) && (sig <= 16)) { char tmp[2]; tmp[0] = s_dtmfs[sig]; tmp[1] = '\0'; Message* msg = message("chan.dtmf"); copySipHeaders(*msg,*t->initialMessage()); msg->addParam("text",tmp); msg->addParam("detected","sip-info"); dtmfEnqueue(msg); } return true; } void YateSIPConnection::doRefer(SIPTransaction* t) { if (m_authBye && !checkUser(t)) return; DDebug(this,DebugAll,"doRefer(%p) [%p]",t,this); if (m_hungup) { t->setResponse(481); return; } if (m_referring) { DDebug(this,DebugAll,"doRefer(%p). Already referring [%p]",t,this); t->setResponse(491); // Request Pending return; } m_referring = true; const MimeHeaderLine* refHdr = t->initialMessage()->getHeader("Refer-To"); if (!(refHdr && refHdr->length())) { DDebug(this,DebugAll,"doRefer(%p). Empty or missing 'Refer-To' header [%p]",t,this); t->setResponse(400); // Bad request m_referring = false; return; } // Get 'Refer-To' URI and its parameters URI uri(*refHdr); ObjList params; // Find the first parameter separator. Ignore everything before it int start = findURIParamSep(uri.getExtra(),0); if (start >= 0) start++; else start = uri.getExtra().length(); while (start < (int)uri.getExtra().length()) { int end = findURIParamSep(uri.getExtra(),start); // Check if this is the last parameter or an empty one if (end < 0) end = uri.getExtra().length(); else if (end == start) { start++; continue; } String param; param = uri.getExtra().substr(start,end - start); start = end + 1; if (!param) continue; param = param.uriUnescape(); int eq = param.find("="); if (eq < 0) { DDebug(this,DebugInfo,"doRefer(%p). Skipping 'Refer-To' URI param '%s' [%p]", t,param.c_str(),this); continue; } String name = param.substr(0,eq).trimBlanks(); String value = param.substr(eq + 1); DDebug(this,DebugAll,"doRefer(%p). Found 'Refer-To' URI param %s=%s [%p]", t,name.c_str(),value.c_str(),this); if (name) params.append(new MimeHeaderLine(name,value)); } // Check attended transfer request parameters ObjList* repl = params.find("Replaces"); const MimeHeaderLine* replaces = repl ? static_cast(repl->get()) : 0; if (replaces) { const String* fromTag = replaces->getParam("from-tag"); const String* toTag = replaces->getParam("to-tag"); if (null(replaces) || null(fromTag) || null(toTag)) { DDebug(this,DebugAll, "doRefer(%p). Invalid 'Replaces' '%s' from-tag=%s to-tag=%s [%p]", t,replaces->safe(),c_safe(fromTag),c_safe(toTag),this); t->setResponse(501); // Not implemented m_referring = false; return; } // Avoid replacing the same connection if (isDialog(*replaces,*fromTag,*toTag)) { DDebug(this,DebugAll, "doRefer(%p). Attended transfer request for the same dialog [%p]", t,this); t->setResponse(400,"Can't replace the same dialog"); // Bad request m_referring = false; return; } } Message* msg = 0; SIPMessage* sipNotify = 0; Channel* ch = YOBJECT(Channel,getPeer()); if (ch && ch->driver() && initTransfer(msg,sipNotify,t->initialMessage(),refHdr,uri,replaces)) { (new YateSIPRefer(id(),ch->id(),ch->driver(),msg,sipNotify,t))->startup(); return; } DDebug(this,DebugAll,"doRefer(%p). No peer or peer has no driver [%p]",t,this); t->setResponse(503); // Service Unavailable m_referring = false; } void YateSIPConnection::complete(Message& msg, bool minimal) const { Channel::complete(msg,minimal); if (minimal) return; Lock mylock(driver()); if (m_domain) msg.setParam("domain",m_domain); addCallId(msg,m_dialog,m_dialog.fromTag(isOutgoing()),m_dialog.toTag(isOutgoing())); } void YateSIPConnection::disconnected(bool final, const char *reason) { Debug(this,DebugAll,"YateSIPConnection::disconnected() '%s' [%p]",reason,this); if (reason) { int code = lookup(reason,dict_errors); if (code >= 300 && code <= 699) setReason(lookup(code,SIPResponses,reason),code); else setReason(reason); } Channel::disconnected(final,reason); } bool YateSIPConnection::msgProgress(Message& msg) { Channel::msgProgress(msg); int code = 183; const NamedString* reason = msg.getParam(YSTRING("reason")); if (reason) { // handle the special progress types that have provisional codes if (*reason == YSTRING("forwarded")) code = 181; else if (*reason == YSTRING("queued")) code = 182; } Lock lock(driver()); if (m_hungup) return false; if (m_tr && (m_tr->getState() == SIPTransaction::Process)) { SIPMessage* m = new SIPMessage(m_tr->initialMessage(), code); copySipHeaders(*m,msg); m->setBody(buildSIPBody(msg,createProvisionalSDP(msg))); m_tr->setResponse(m); m->deref(); } setStatus("progressing"); return true; } bool YateSIPConnection::msgRinging(Message& msg) { Channel::msgRinging(msg); Lock lock(driver()); if (m_hungup) return false; if (m_tr && (m_tr->getState() == SIPTransaction::Process)) { SIPMessage* m = new SIPMessage(m_tr->initialMessage(), 180); copySipHeaders(*m,msg); m->setBody(buildSIPBody(msg,createProvisionalSDP(msg))); m_tr->setResponse(m); m->deref(); } setStatus("ringing"); return true; } bool YateSIPConnection::msgAnswered(Message& msg) { Lock lock(driver()); if (m_hungup) return false; if (m_tr && (m_tr->getState() == SIPTransaction::Process)) { updateFormats(msg,true); SIPMessage* m = new SIPMessage(m_tr->initialMessage(), 200); copySipHeaders(*m,msg); MimeSdpBody* sdp = createPasstroughSDP(msg); if (!sdp) { m_rtpForward = false; bool startNow = msg.getBoolValue(YSTRING("rtp_start"),s_start_rtp); if (startNow && !m_rtpMedia) { // early RTP start but media list yet unknown - build best guess String fmts; plugin.parser().getAudioFormats(fmts); ObjList* lst = new ObjList; lst->append(new SDPMedia("audio","RTP/AVP",msg.getValue(YSTRING("formats"),fmts))); setMedia(lst); m_rtpAddr = m_host; } // normally don't start RTP yet, only when we get the ACK sdp = createRtpSDP(startNow); } m->setBody(buildSIPBody(msg,sdp)); const MimeHeaderLine* co = m_tr->initialMessage()->getHeader("Contact"); if (co) { // INVITE had a Contact: header - time to change remote URI m_uri = *co; m_uri.parse(); } // and finally send the answer, transaction will finish soon afterwards m_tr->setResponse(m); m->deref(); } setReason("",0); setStatus("answered",Established); return true; } bool YateSIPConnection::msgTone(Message& msg, const char* tone) { if (m_hungup) return false; bool info = m_info; bool inband = m_inband; const String* method = msg.getParam(YSTRING("method")); if (method) { if ((*method == YSTRING("info")) || (*method == YSTRING("sip-info"))) { info = true; inband = false; } else if (*method == YSTRING("rfc2833")) { info = false; inband = false; } else if (*method == YSTRING("inband")) { info = false; inband = true; } } // RFC 2833 and inband require that we have an active local RTP stream if (m_rtpMedia && (m_mediaStatus == MediaStarted) && !info) { ObjList* l = m_rtpMedia->find("audio"); const SDPMedia* m = static_cast(l ? l->get() : 0); if (m) { if (!(inband || m->rfc2833().toBoolean(true))) { Debug(this,DebugNote,"Forcing DTMF '%s' inband, format '%s' [%p]", tone,m->format().c_str(),this); inband = true; } if (inband && dtmfInband(tone)) return true; msg.setParam("targetid",m->id()); return false; } } // either INFO was requested or we have no other choice for (; tone && *tone; tone++) { char c = *tone; for (int i = 0; i <= 16; i++) { if (s_dtmfs[i] == c) { SIPMessage* m = createDlgMsg("INFO"); if (m) { copySipHeaders(*m,msg); String tmp; tmp << "Signal=" << i << "\r\n"; m->setBody(new MimeStringBody("application/dtmf-relay",tmp)); plugin.ep()->engine()->addMessage(m); m->deref(); } break; } } } return true; } bool YateSIPConnection::msgText(Message& msg, const char* text) { if (m_hungup || null(text)) return false; SIPMessage* m = createDlgMsg("MESSAGE"); if (m) { copySipHeaders(*m,msg); m->setBody(new MimeStringBody("text/plain",text)); plugin.ep()->engine()->addMessage(m); m->deref(); return true; } return false; } bool YateSIPConnection::msgDrop(Message& msg, const char* reason) { if (!Channel::msgDrop(msg,reason)) return false; int code = lookup(reason,dict_errors); if (code >= 300 && code <= 699) { m_reasonCode = code; m_reason = lookup(code,SIPResponses,reason); } return true; } bool YateSIPConnection::msgUpdate(Message& msg) { String* oper = msg.getParam(YSTRING("operation")); if (!oper || oper->null()) return false; Lock lock(driver()); if (m_hungup) return false; if (*oper == YSTRING("request")) { if (m_tr || m_tr2) { DDebug(this,DebugWarn,"Update request rejected, pending:%s%s [%p]", m_tr ? " invite" : "",m_tr2 ? " reinvite" : "",this); msg.setParam("error","pending"); msg.setParam("reason","Another INVITE Pending"); return false; } return startClientReInvite(msg); } if (*oper == YSTRING("initiate")) { if (m_reInviting != ReinviteNone) { msg.setParam("error","pending"); msg.setParam("reason","Another INVITE Pending"); return false; } m_reInviting = ReinvitePending; startPendingUpdate(); return true; } if (!m_tr2) { if ((m_reInviting == ReinviteRequest) && (*oper == YSTRING("notify"))) { if (startClientReInvite(msg)) return true; Debug(this,DebugMild,"Failed to start reINVITE, %s: %s [%p]", msg.getValue(YSTRING("error"),"unknown"), msg.getValue(YSTRING("reason"),"No reason"),this); return false; } msg.setParam("error","nocall"); return false; } if (!(m_tr2->isIncoming() && (m_tr2->getState() == SIPTransaction::Process))) { msg.setParam("error","failure"); msg.setParam("reason","Incompatible Transaction State"); return false; } if (*oper == YSTRING("notify")) { bool rtpSave = m_rtpForward; m_rtpForward = msg.getBoolValue(YSTRING("rtp_forward"),m_rtpForward); MimeSdpBody* sdp = createPasstroughSDP(msg); if (!sdp) { m_rtpForward = rtpSave; m_tr2->setResponse(500,"Server failed to build the SDP"); detachTransaction2(); return false; } if (m_rtpForward != rtpSave) Debug(this,DebugInfo,"RTP forwarding changed: %s -> %s", String::boolText(rtpSave),String::boolText(m_rtpForward)); SIPMessage* m = new SIPMessage(m_tr2->initialMessage(), 200); m->setBody(sdp); m_tr2->setResponse(m); detachTransaction2(); m->deref(); return true; } else if (*oper == YSTRING("reject")) { m_tr2->setResponse(msg.getIntValue(YSTRING("error"),dict_errors,488),msg.getValue(YSTRING("reason"))); detachTransaction2(); return true; } return false; } void YateSIPConnection::endDisconnect(const Message& msg, bool handled) { const String* reason = msg.getParam(YSTRING("reason")); if (!TelEngine::null(reason)) { int code = reason->toInteger(dict_errors); if (code >= 300 && code <= 699) setReason(lookup(code,SIPResponses,*reason),code); else setReason(*reason,m_reasonCode); } const char* sPrefix = msg.getValue(YSTRING("osip-prefix")); const char* mPrefix = msg.getValue(YSTRING("message-prefix")); if (!(sPrefix || mPrefix)) return; parameters().clearParams(); if (sPrefix) { parameters().setParam("osip-prefix",sPrefix); parameters().copySubParams(msg,sPrefix,false); } if (mPrefix) { parameters().setParam("message-prefix",mPrefix); parameters().copySubParams(msg,mPrefix,false); } } void YateSIPConnection::statusParams(String& str) { Channel::statusParams(str); if (m_line) str << ",line=" << m_line; if (m_user) str << ",user=" << m_user; if (m_rtpForward) str << ",forward=" << (m_sdpForward ? "sdp" : "rtp"); str << ",inviting=" << (m_tr != 0); } bool YateSIPConnection::callRouted(Message& msg) { // try to disable RTP forwarding earliest possible if (m_rtpForward && !msg.getBoolValue(YSTRING("rtp_forward"))) m_rtpForward = false; setRfc2833(msg.getParam(YSTRING("rfc2833"))); Channel::callRouted(msg); Lock lock(driver()); if (m_hungup) return false; if (m_tr && (m_tr->getState() == SIPTransaction::Process)) { String s(msg.retValue()); if (s.startSkip("sip/",false) && s && msg.getBoolValue(YSTRING("redirect"))) { Debug(this,DebugAll,"YateSIPConnection redirecting to '%s' [%p]",s.c_str(),this); String tmp(msg.getValue(YSTRING("calledname"))); if (tmp) { MimeHeaderLine::addQuotes(tmp); tmp += " "; } s = tmp + "<" + s + ">"; int code = msg.getIntValue(YSTRING("reason"),dict_errors,302); if ((code < 300) || (code > 399)) code = 302; SIPMessage* m = new SIPMessage(m_tr->initialMessage(),code); m->addHeader("Contact",s); tmp = msg.getValue(YSTRING("diversion")); if (tmp.trimBlanks() && tmp.toBoolean(true)) { // if diversion is a boolean true use the dialog local URI if (tmp.toBoolean(false)) tmp = m_dialog.localURI; if (!(tmp.startsWith("<") && tmp.endsWith(">"))) tmp = "<" + tmp + ">"; MimeHeaderLine* hl = new MimeHeaderLine("Diversion",tmp); tmp = msg.getValue(YSTRING("divert_reason")); if (tmp) { MimeHeaderLine::addQuotes(tmp); hl->setParam("reason",tmp); } tmp = msg.getValue(YSTRING("divert_privacy")); if (tmp) { MimeHeaderLine::addQuotes(tmp); hl->setParam("privacy",tmp); } tmp = msg.getValue(YSTRING("divert_screen")); if (tmp) { MimeHeaderLine::addQuotes(tmp); hl->setParam("screen",tmp); } m->addHeader(hl); } copySipHeaders(*m,msg); m_tr->setResponse(m); m->deref(); m_byebye = false; setReason("Redirected",302); setStatus("redirected"); return false; } updateFormats(msg); if (msg.getBoolValue(YSTRING("progress"),s_progress)) m_tr->setResponse(183); } return true; } void YateSIPConnection::callAccept(Message& msg) { m_user = msg.getValue(YSTRING("username")); if (m_authBye) m_authBye = msg.getBoolValue(YSTRING("xsip_auth_bye"),true); if (m_rtpForward) { String tmp(msg.getValue(YSTRING("rtp_forward"))); if (tmp != YSTRING("accepted")) m_rtpForward = false; } m_secure = m_secure && msg.getBoolValue(YSTRING("secure"),true); Channel::callAccept(msg); if ((m_reInviting == ReinviteNone) && !m_rtpForward && !isAnswered() && msg.getBoolValue(YSTRING("autoreinvite"),false)) { // remember we want to switch to RTP forwarding when party answers m_reInviting = ReinvitePending; startPendingUpdate(); } } void YateSIPConnection::callRejected(const char* error, const char* reason, const Message* msg) { Channel::callRejected(error,reason,msg); int code = lookup(error,dict_errors,500); if (code < 300 || code > 699) code = 500; Lock lock(driver()); if (m_tr && (m_tr->getState() == SIPTransaction::Process)) { if (code == 401) { Lock lck(s_globalMutex); m_tr->requestAuth(s_realm,m_domain,false); } else m_tr->setResponse(code,reason); } setReason(reason,code); } // Start a client reINVITE transaction bool YateSIPConnection::startClientReInvite(Message& msg) { bool hadRtp = !m_rtpForward; bool rtpFwd = msg.getBoolValue(YSTRING("rtp_forward"),m_rtpForward); if (!rtpFwd) { msg.setParam("error","failure"); msg.setParam("reason","RTP forwarding is not enabled"); return false; } m_rtpForward = true; // this is the point of no return if (hadRtp) clearEndpoint(); MimeSdpBody* sdp = createPasstroughSDP(msg,false); if (!sdp) { msg.setParam("error","failure"); msg.setParam("reason","Could not build the SDP"); if (hadRtp) { Debug(this,DebugWarn,"Could not build SDP for reINVITE, hanging up [%p]",this); disconnect("nomedia"); } return false; } Debug(this,DebugNote,"Initiating reINVITE (%s RTP before) [%p]", hadRtp ? "had" : "no",this); SIPMessage* m = createDlgMsg("INVITE"); copySipHeaders(*m,msg); if (s_privacy) copyPrivacy(*m,msg); m->setBody(sdp); m_tr2 = plugin.ep()->engine()->addMessage(m); if (m_tr2) { m_tr2->ref(); m_tr2->setUserData(this); } m->deref(); return true; } // Emit pending update if possible, method is called with driver mutex hold void YateSIPConnection::startPendingUpdate() { Lock mylock(driver()); if (m_hungup || m_tr || m_tr2 || (m_reInviting != ReinvitePending)) return; if (m_rtpAddr.null()) { Debug(this,DebugWarn,"Cannot start update, remote RTP address unknown [%p]",this); m_reInviting = ReinviteNone; return; } if (!m_rtpMedia) { Debug(this,DebugWarn,"Cannot start update, remote media unknown [%p]",this); m_reInviting = ReinviteNone; return; } m_reInviting = ReinviteRequest; mylock.drop(); Message msg("call.update"); complete(msg); msg.addParam("operation","request"); msg.addParam("rtp_forward","yes"); msg.addParam("rtp_addr",m_rtpAddr); putMedia(msg); // if peer doesn't support updates fail the reINVITE if (!Engine::dispatch(msg)) { Debug(this,DebugWarn,"Cannot start update by '%s', %s: %s [%p]", getPeerId().c_str(), msg.getValue(YSTRING("error"),"not supported"), msg.getValue(YSTRING("reason"),"No reason provided"),this); m_reInviting = ReinviteNone; } } // Build the 'call.route' and NOTIFY messages needed by the transfer thread // msg: 'call.route' message to create & fill // sipNotify: NOTIFY message to create & fill // sipRefer: received REFER message, refHdr: 'Refer-To' header // refHdr: The 'Refer-To' header // uri: The already parsed 'Refer-To' URI // replaces: An already checked Replaces parameter from 'Refer-To' or // 0 for unattended transfer // If return false, msg and sipNotify are 0 bool YateSIPConnection::initTransfer(Message*& msg, SIPMessage*& sipNotify, const SIPMessage* sipRefer, const MimeHeaderLine* refHdr, const URI& uri, const MimeHeaderLine* replaces) { // call.route msg = new Message("call.route"); msg->addParam("id",getPeer()->id()); if (m_billid) msg->addParam("billid",m_billid); if (m_user) msg->addParam("username",m_user); const MimeHeaderLine* sh = sipRefer->getHeader("To"); // caller if (sh) { URI uriCaller(*sh); uriCaller.parse(); msg->addParam("caller",uriCaller.getUser()); msg->addParam("callername",uriCaller.getDescription()); } if (replaces) { // called or replace const String* fromTag = replaces->getParam("from-tag"); const String* toTag = replaces->getParam("to-tag"); msg->addParam("transfer_callid",*replaces); msg->addParam("transfer_fromtag",c_safe(fromTag)); msg->addParam("transfer_totag",c_safe(toTag)); } else { msg->addParam("called",uri.getUser()); msg->addParam("calledname",uri.getDescription()); } sh = sipRefer->getHeader("Referred-By"); // diverter URI referBy; if (sh) referBy = *sh; else referBy = m_dialog.remoteURI; msg->addParam("diverter",referBy.getUser()); msg->addParam("divertername",referBy.getDescription()); msg->addParam("reason","transfer"); // reason // NOTIFY String tmp; const MimeHeaderLine* co = sipRefer->getHeader("Contact"); // TODO: Handle contact: it might require a different transport // If we need another transport and is a connected one, try to delay party creation: // we won't need it if the transfer fails // Set notify party from received REFER? // Remember: createDlgMsg() sets the party from channel's party Debug(this,DebugStub,"initTransfer. Possible incomplete NOTIFY party creation [%p]",this); if (co) { tmp = *co; static const Regexp r("^[^<]*<\\([^>]*\\)>.*$"); if (tmp.matches(r)) tmp = tmp.matchString(1); } sipNotify = createDlgMsg("NOTIFY",tmp); if (!sipNotify->getParty() && plugin.ep()) plugin.ep()->buildParty(sipNotify); if (!sipNotify->getParty()) { DDebug(this,DebugAll,"initTransfer. Could not create party to send NOTIFY [%p]",this); TelEngine::destruct(sipNotify); TelEngine::destruct(msg); return false; } copySipHeaders(*msg,*sipRefer); sipNotify->complete(plugin.ep()->engine()); sipNotify->addHeader("Event","refer"); sipNotify->addHeader("Subscription-State","terminated;reason=noresource"); sipNotify->addHeader("Contact",sipRefer->uri); return true; } // Decode an application/isup body into 'msg' if configured to do so bool YateSIPConnection::decodeIsupBody(Message& msg, MimeBody* body) { return doDecodeIsupBody(this,msg,body); } // Build the body of a SIP message from an engine message MimeBody* YateSIPConnection::buildSIPBody(Message& msg, MimeSdpBody* sdp) { return doBuildSIPBody(this,msg,sdp); } // Build the body of a hangup SIP message from disconnect parameters MimeBody* YateSIPConnection::buildSIPBody() { if (!s_sipt_isup) return 0; Message msg(""); msg.copyParams(parameters()); return doBuildSIPBody(this,msg,0); } YateSIPLine::YateSIPLine(const String& name) : String(name), Mutex(true,"YateSIPLine"), m_resend(0), m_keepalive(0), m_interval(0), m_alive(0), m_flags(-1), m_tr(0), m_marked(false), m_valid(false), m_localPort(0), m_partyPort(0), m_localDetect(false), m_keepTcpOffline(s_lineKeepTcpOffline) { m_partyMutex = this; DDebug(&plugin,DebugInfo,"YateSIPLine::YateSIPLine('%s') [%p]",c_str(),this); s_lines.append(this); } YateSIPLine::~YateSIPLine() { DDebug(&plugin,DebugInfo,"YateSIPLine::~YateSIPLine() '%s' [%p]",c_str(),this); s_lines.remove(this,false); logout(); } void YateSIPLine::setupAuth(SIPMessage* msg) const { if (msg) msg->setAutoAuth(getAuthName(),m_password); } void YateSIPLine::setValid(bool valid, const char* reason) { DDebug(&plugin,DebugInfo,"YateSIPLine(%s) setValid(%u,%s) current=%u [%p]", c_str(),valid,reason,m_valid,this); if ((m_valid == valid) && !reason) return; m_valid = valid; if (m_registrar && m_username) { Message* m = new Message("user.notify"); m->addParam("account",*this); m->addParam("protocol","sip"); m->addParam("username",m_username); if (m_domain) m->addParam("domain",m_domain); m->addParam("registered",String::boolText(valid)); if (reason) m->addParam("reason",reason); Engine::enqueue(m); } } void YateSIPLine::changing() { // we need to log out before any parameter changes logout(); } SIPMessage* YateSIPLine::buildRegister(int expires) const { String exp(expires); String tmp; tmp << "sip:" << m_registrar; SIPMessage* m = new SIPMessage("REGISTER",tmp); setSipParty(m,this); if (!m->getParty()) { Debug(&plugin,DebugWarn,"Could not create party for '%s' [%p]", m_registrar.c_str(),this); m->destruct(); return 0; } tmp.clear(); if (m_display) tmp = MimeHeaderLine::quote(m_display) + " "; tmp << "getParty()->mutex()); tmp << m->getParty()->getLocalAddr() << ":"; tmp << m->getParty()->getLocalPort() << ">"; lckParty.drop(); m->addHeader("Contact",tmp); m->addHeader("Expires",exp); tmp = ""; m->addHeader("To",tmp); if (m_callid) m->addHeader("Call-ID",m_callid); m->complete(plugin.ep()->engine(),m_username,domain(),0,m_flags); return m; } void YateSIPLine::login() { m_keepalive = 0; if (m_registrar.null() || m_username.null()) { logout(); setValid(true); return; } DDebug(&plugin,DebugInfo,"YateSIPLine '%s' logging in [%p]",c_str(),this); clearTransaction(); // prepare a sane resend interval, just in case something goes wrong int interval = m_interval / 2; if (interval) { if (interval < 30) interval = 30; else if (interval > 600) interval = 600; m_resend = interval*(int64_t)1000000 + Time::now(); } buildParty(false); // Wait for the transport to become valid Lock lckParty(m_partyMutex); YateSIPTransport* trans = transport(); if (!(trans && trans->valid())) { DDebug(&plugin,DebugInfo, "YateSIPLine '%s' delaying login (transport not ready) [%p]",c_str(),this); return; } lckParty.drop(); SIPMessage* m = buildRegister(m_interval); if (!m) { setValid(false); if (!m_keepTcpOffline) setParty(); return; } if (m_localDetect) { Lock lck(m->getParty()->mutex()); if (m_localAddr.null()) m_localAddr = m->getParty()->getLocalAddr(); if (!m_localPort) m_localPort = m->getParty()->getLocalPort(); } DDebug(&plugin,DebugInfo,"YateSIPLine '%s' emiting %p [%p]", c_str(),m,this); m_tr = plugin.ep()->engine()->addMessage(m); if (m_tr) { m_tr->ref(); m_tr->setUserData(this); if (m_callid.null()) m_callid = m_tr->getCallID(); } m->deref(); } void YateSIPLine::logout(bool sendLogout, const char* reason) { m_resend = 0; m_keepalive = 0; if (sendLogout) sendLogout = m_valid && m_registrar && m_username; clearTransaction(); setValid(false,reason); if (sendLogout) { DDebug(&plugin,DebugInfo,"YateSIPLine '%s' logging out [%p]",c_str(),this); buildParty(false); SIPMessage* m = buildRegister(0); m_partyAddr.clear(); m_partyPort = 0; if (!m) return; plugin.ep()->engine()->addMessage(m); m->deref(); } m_callid.clear(); } bool YateSIPLine::process(SIPEvent* ev) { DDebug(&plugin,DebugInfo,"YateSIPLine::process(%p) %s [%p]", ev,SIPTransaction::stateName(ev->getState()),this); if (ev->getTransaction() != m_tr) return false; if (ev->getState() == SIPTransaction::Cleared) { clearTransaction(); setValid(false,"timeout"); if (!m_keepTcpOffline) setParty(); m_keepalive = 0; Debug(&plugin,DebugWarn,"SIP line '%s' logon timeout",c_str()); return false; } const SIPMessage* msg = ev->getMessage(); if (!(msg && msg->isAnswer())) return false; if (ev->getState() != SIPTransaction::Process) return false; clearTransaction(); DDebug(&plugin,DebugAll,"YateSIPLine '%s' got answer %d [%p]", c_str(),msg->code,this); switch (msg->code) { case 200: { int exp = m_interval; const MimeHeaderLine* hl = msg->getHeader("Contact"); if (hl) { const NamedString* e = hl->getParam("expires"); if (e) exp = e->toInteger(exp); else hl = 0; } if (!hl) { hl = msg->getHeader("Expires"); if (hl) exp = hl->toInteger(exp); } if ((exp != m_interval) && (exp >= 60)) { Debug(&plugin,DebugNote,"SIP line '%s' changed expire interval from %d to %d", c_str(),m_interval,exp); m_interval = exp; } // Reset transport timeout from expires or flow timer resetTransportIdle(msg,m_alive ? m_alive : m_interval); } // re-register at 3/4 of the expire interval m_resend = m_interval*(int64_t)750000 + Time::now(); m_keepalive = m_alive ? m_alive*(int64_t)1000000 + Time::now() : 0; detectLocal(msg); if (msg->getParty()) msg->getParty()->getAddr(m_partyAddr,m_partyPort,false); setValid(true); Debug(&plugin,DebugCall,"SIP line '%s' logon success to %s:%d", c_str(),m_partyAddr.c_str(),m_partyPort); break; default: // detect local address even from failed attempts - helps next time detectLocal(msg); setValid(false,msg->reason); if (!m_keepTcpOffline) setParty(); Debug(&plugin,DebugWarn,"SIP line '%s' logon failure %d: %s", c_str(),msg->code,msg->reason.safe()); } return false; } void YateSIPLine::detectLocal(const SIPMessage* msg) { if (!(m_localDetect && msg->getParty())) return; String laddr = m_localAddr; int lport = m_localPort; MimeHeaderLine* hl = const_cast(msg->getHeader("Via")); if (hl) { const NamedString* par = hl->getParam("received"); if (par && *par) laddr = *par; par = hl->getParam("rport"); if (par) { int port = par->toInteger(0,10); if (port > 0) lport = port; } } Lock lckParty(msg->getParty()->mutex()); if (laddr.null()) laddr = msg->getParty()->getLocalAddr(); if (!lport) lport = msg->getParty()->getLocalPort(); lckParty.drop(); if ((laddr != m_localAddr) || (lport != m_localPort)) { Debug(&plugin,DebugInfo,"Detected local address %s:%d for SIP line '%s'", laddr.c_str(),lport,c_str()); m_localAddr = laddr; m_localPort = lport; // since local address changed register again in 2 seconds m_resend = 2000000 + Time::now(); // Update now party local ip/port SIPParty* p = party(); if (p) { p->setAddr(m_localAddr,m_localPort,true); TelEngine::destruct(p); } } } void YateSIPLine::keepalive() { if (!m_party) return; Lock lock(m_partyMutex); if (!m_party || m_party->isReliable()) return; YateUDPParty* udp = static_cast(m_party); YateSIPUDPTransport* t = static_cast(m_party->getTransport()); if (t) { Debug(&plugin,DebugAll,"Sending UDP keepalive to %s:%d for '%s'", udp->addr().host().c_str(),udp->addr().port(),c_str()); t->send("\r\n",2,udp->addr()); } m_keepalive = m_alive ? m_alive*(int64_t)1000000 + Time::now() : 0; } void YateSIPLine::timer(const Time& when) { if (!m_resend || (m_resend > when)) { if (m_keepalive && (m_keepalive <= when)) keepalive(); return; } m_resend = 0; login(); } void YateSIPLine::clearTransaction() { if (m_tr) { DDebug(&plugin,DebugInfo,"YateSIPLine clearing transaction %p [%p]", m_tr,this); m_tr->setUserData(0); m_tr->deref(); m_tr = 0; } } bool YateSIPLine::update(const Message& msg) { DDebug(&plugin,DebugInfo,"YateSIPLine::update() '%s' [%p]",c_str(),this); const String& oper = msg[YSTRING("operation")]; if (oper == YSTRING("logout")) { logout(); setParty(); return true; } bool chg = updateProto(msg); bool transChg = chg; transChg = updateLocalAddr(msg) || transChg; chg = change(m_registrar,msg.getValue(YSTRING("registrar"),msg.getValue(YSTRING("server")))) || chg; chg = change(m_username,msg.getValue(YSTRING("username"))) || chg; chg = change(m_authname,msg.getValue(YSTRING("authname"))) || chg; chg = change(m_password,msg.getValue(YSTRING("password"))) || chg; chg = change(m_domain,msg.getValue(YSTRING("domain"))) || chg; chg = change(m_flags,msg.getIntValue(YSTRING("xsip_flags"),-1)) || chg; m_display = msg.getValue(YSTRING("description")); m_interval = msg.getIntValue(YSTRING("interval"),600); String tmp(msg.getValue(YSTRING("localaddress"),s_auto_nat ? "auto" : "")); // "auto", "yes", "enable" or "true" to autodetect local address m_localDetect = (tmp == YSTRING("auto")) || tmp.toBoolean(false); if (!m_localDetect) { // "no", "disable" or "false" to just disable detection if (!tmp.toBoolean(true)) tmp.clear(); int port = 0; if (tmp) { int sep = tmp.find(':'); if (sep > 0) { port = tmp.substr(sep+1).toInteger(5060); tmp = tmp.substr(0,sep); } else if (sep < 0) port = 5060; } chg = change(m_localAddr,tmp) || chg; chg = change(m_localPort,port) || chg; } String raddr; int rport = 0; tmp = msg.getValue(YSTRING("outbound")); if (tmp) { int sep = tmp.find(':'); if (sep > 0) { rport = tmp.substr(sep + 1).toInteger(0); raddr = tmp.substr(0,sep); } else raddr = tmp; } if (!raddr) { int sep = m_registrar.find(':'); if (sep > 0) { rport = m_registrar.substr(sep + 1).toInteger(0); raddr = m_registrar.substr(0,sep); } else raddr = m_registrar; } if (!raddr) raddr = m_transRemoteAddr; if (rport <= 0) rport = sipPort(protocol() != Tls); bool rAddrChg = change(m_transRemoteAddr,raddr); rAddrChg = change(m_transRemotePort,rport) || rAddrChg; if (rAddrChg) { transChg = true; chg = true; } m_alive = msg.getIntValue(YSTRING("keepalive"),(m_localDetect ? 25 : 0)); // (Re)Set party if (transChg || !m_party) { // Logout if not already done if (!chg) { chg = true; logout(); } buildParty(); if (!m_party) Debug(&plugin,DebugNote,"Line '%s' failed to set party [%p]",c_str(),this); } // if something changed we logged out so try to climb back if (chg || (oper == YSTRING("login"))) login(); return chg; } // Transport status changed notification void YateSIPLine::transportChangedStatus(int stat, const String& reason) { Debug(&plugin,DebugAll,"Line '%s' transport status is %s", c_str(),YateSIPTransport::statusName(stat)); YateSIPTransport* trans = transport(); if (stat == YateSIPTransport::Terminated) { u_int64_t old = m_resend; logout(trans && trans->udpTransport(),reason); setParty(); // Try to re-login if set to do that m_resend = old; } else if (stat == YateSIPTransport::Connected) { if (trans) { Lock lock(trans); m_localAddr = trans->local().host(); m_localPort = trans->local().port(); } // Pending login if (trans && m_resend) login(); } } YateSIPGenerate::YateSIPGenerate(SIPMessage* m) : m_tr(0), m_code(0) { m_tr = plugin.ep()->engine()->addMessage(m); if (m_tr) { m_tr->ref(); m_tr->setUserData(this); } m->deref(); } YateSIPGenerate::~YateSIPGenerate() { clearTransaction(); } bool YateSIPGenerate::process(SIPEvent* ev) { DDebug(&plugin,DebugInfo,"YateSIPGenerate::process(%p) %s [%p]", ev,SIPTransaction::stateName(ev->getState()),this); if (ev->getTransaction() != m_tr) return false; if (ev->getState() == SIPTransaction::Cleared) { clearTransaction(); return false; } const SIPMessage* msg = ev->getMessage(); if (!(msg && msg->isAnswer())) return false; if (ev->getState() != SIPTransaction::Process) return false; clearTransaction(); Debug(&plugin,DebugAll,"YateSIPGenerate got answer %d [%p]", m_code,this); return false; } void YateSIPGenerate::clearTransaction() { if (m_tr) { DDebug(&plugin,DebugInfo,"YateSIPGenerate clearing transaction %p [%p]", m_tr,this); m_code = m_tr->getResponseCode(); m_tr->setUserData(0); m_tr->deref(); m_tr = 0; } } bool UserHandler::received(Message &msg) { String tmp(msg.getValue(YSTRING("protocol"))); if (tmp != YSTRING("sip")) return false; tmp = msg.getValue(YSTRING("account")); if (tmp.null()) return false; YateSIPLine* line = plugin.findLine(tmp); if (!line) line = new YateSIPLine(tmp); line->update(msg); return true; } bool SipHandler::received(Message &msg) { Debug(&plugin,DebugInfo,"SipHandler::received() [%p]",this); RefPointer conn; String uri; const char* id = msg.getValue(YSTRING("id")); if (id) { plugin.lock(); conn = static_cast(plugin.find(id)); plugin.unlock(); if (!conn) { msg.setParam("error","noconn"); return false; } uri = conn->m_uri; } const char* method = msg.getValue(YSTRING("method")); uri = msg.getValue(YSTRING("uri"),uri); static const Regexp r("<\\([^>]\\+\\)>"); if (uri.matches(r)) uri = uri.matchString(1); if (!(method && uri)) return false; int maxf = msg.getIntValue(YSTRING("antiloop"),s_maxForwards); if (maxf <= 0) { Debug(&plugin,DebugMild,"Blocking looping request '%s %s' [%p]", method,uri.c_str(),this); msg.setParam("error","looping"); return false; } SIPMessage* sip = 0; YateSIPLine* line = 0; const char* domain = msg.getValue(YSTRING("domain")); if (conn) { line = plugin.findLine(conn->getLine()); sip = conn->createDlgMsg(method,uri); conn = 0; } else { line = plugin.findLine(msg.getValue(YSTRING("line"))); if (line && !line->valid()) { msg.setParam("error","offline"); return false; } sip = new SIPMessage(method,uri); YateSIPPartyHolder holder; const char* host = msg.getValue("host"); int port = msg.getIntValue("port"); holder.setParty(msg,false,String::empty(),host,port); holder.setSipParty(sip,line,true,host,port); if (line) domain = line->domain(domain); } if (!sip->getParty()) { Debug(&plugin,DebugWarn,"Could not create party to generate '%s'", sip->method.c_str()); TelEngine::destruct(sip); msg.setParam("error","notransport"); return false; } sip->addHeader("Max-Forwards",String(maxf)); copySipHeaders(*sip,msg,"sip_"); const String& type = msg[YSTRING("xsip_type")]; const String& body = msg[YSTRING("xsip_body")]; if (type && body) { const String& bodyEnc = msg[YSTRING("xsip_body_encoding")]; if (bodyEnc.null()) sip->setBody(new MimeStringBody(type,body.c_str(),body.length())); else { DataBlock binBody; bool ok = false; if (bodyEnc == YSTRING("base64")) { Base64 b64; b64 << body; ok = b64.decode(binBody); } else if (bodyEnc == YSTRING("hex")) ok = binBody.unHexify(body,body.length()); else if (bodyEnc == YSTRING("hexs")) ok = binBody.unHexify(body,body.length(),' '); if (ok) sip->setBody(new MimeBinaryBody(type,(const char*)binBody.data(),binBody.length())); else Debug(&plugin,DebugWarn,"Invalid xsip_body_encoding '%s'",bodyEnc.c_str()); } } sip->complete(plugin.ep()->engine(),msg.getValue(YSTRING("user")),domain,0, msg.getIntValue(YSTRING("xsip_flags"),-1)); if (!msg.getBoolValue(YSTRING("wait"))) { // no answer requested - start transaction and forget plugin.ep()->engine()->addMessage(sip); sip->deref(); return true; } YateSIPGenerate gen(sip); while (gen.busy()) Thread::idle(); if (gen.code()) msg.setParam("code",String(gen.code())); else msg.clearParam("code"); return true; } YateSIPConnection* SIPDriver::findCall(const String& callid, bool incRef) { XDebug(this,DebugAll,"SIPDriver finding call '%s'",callid.c_str()); Lock mylock(this); ObjList* l = channels().skipNull(); for (; l; l = l->skipNext()) { YateSIPConnection* c = static_cast(l->get()); if (c->callid() == callid) return (incRef ? c->ref() : c->alive()) ? c : 0; } return 0; } YateSIPConnection* SIPDriver::findDialog(const SIPDialog& dialog, bool incRef) { XDebug(this,DebugAll,"SIPDriver finding dialog '%s'",dialog.c_str()); Lock mylock(this); ObjList* l = channels().skipNull(); for (; l; l = l->skipNext()) { YateSIPConnection* c = static_cast(l->get()); if (c->dialog() &= dialog) return (incRef ? c->ref() : c->alive()) ? c : 0; } return 0; } YateSIPConnection* SIPDriver::findDialog(const String& dialog, const String& fromTag, const String& toTag, bool incRef) { XDebug(this,DebugAll,"SIPDriver finding dialog '%s' fromTag='%s' toTag='%s'", dialog.c_str(),fromTag.c_str(),toTag.c_str()); Lock mylock(this); for (ObjList* o = channels().skipNull(); o; o = o->skipNext()) { YateSIPConnection* c = static_cast(o->get()); if (c->isDialog(dialog,fromTag,toTag)) return (incRef ? c->ref() : c->alive()) ? c : 0; } return 0; } // find line by name YateSIPLine* SIPDriver::findLine(const String& line) const { if (line.null()) return 0; ObjList* l = s_lines.find(line); return l ? static_cast(l->get()) : 0; } // find line by party address and port YateSIPLine* SIPDriver::findLine(const String& addr, int port, const String& user) { if (!(port && addr)) return 0; Lock mylock(this); ObjList* l = s_lines.skipNull(); for (; l; l = l->skipNext()) { YateSIPLine* sl = static_cast(l->get()); if (sl->getPartyPort() && (sl->getPartyPort() == port) && (sl->getPartyAddr() == addr)) { if (user && (sl->getUserName() != user)) continue; return sl; } } return 0; } // Drop channels belonging using a given transport // Return the number of disconnected channels unsigned int SIPDriver::transportTerminated(YateSIPTransport* trans) { unsigned int n = 0; lock(); ListIterator iter(channels()); while (true) { RefPointer conn = static_cast(iter.get()); unlock(); if (!conn) break; if (conn->isTransport(trans)) { Debug(this,DebugNote,"Disconnecting '%s': transport terminated", conn->id().c_str()); n++; conn->disconnect("notransport"); } conn = 0; lock(); } return n; } // check if a line is either empty or valid (logged in or no registrar) bool SIPDriver::validLine(const String& line) { if (line.null()) return true; YateSIPLine* l = findLine(line); return l && l->valid(); } bool SIPDriver::received(Message& msg, int id) { if (id == Timer) { ObjList* l = s_lines.skipNull(); for (; l; l = l->skipNext()) static_cast(l->get())->timer(msg.msgTime()); } else if (id == Stop) { s_engineStop++; dropAll(msg); m_endpoint->cancelListener(); // Logout lines on first handle // Delay engine.halt until all lines logged out, we have no more channels // and there are no more transactions in initial state // This will give some time to TCP transports to send pending data bool noHalt = false; for (ObjList* o = s_lines.skipNull(); o; o = o->skipNext()) { YateSIPLine* line = static_cast(o->get()); noHalt = noHalt || line->valid(); if (s_engineStop == 1) line->logout(); } if (!noHalt) { Lock lock(this); noHalt = (0 != channels().skipNull()); } if (!noHalt) noHalt = m_endpoint->engine()->hasInitialTransaction(); Debug(this,DebugAll,"Returning %s from %s handler",String::boolText(noHalt),msg.c_str()); return noHalt; } else if (id == Halt) { s_engineHalt = true; dropAll(msg); channels().clear(); s_lines.clear(); // Clear transactions: they keep references to parties and transports m_endpoint->engine()->clearTransactions(); m_endpoint->clearUdpTransports("Exiting"); // Wait for transports to terminate unsigned int n = 100; while (--n) { Lock lck(m_endpoint->m_mutex); if (!m_endpoint->m_transports.skipNull()) break; lck.drop(); Thread::idle(); } m_endpoint->m_mutex.lock(); n = m_endpoint->m_transports.count(); if (n) Debug(this,DebugGoOn,"Exiting with %u transports in queue",n); m_endpoint->m_mutex.unlock(); m_endpoint->cancel(); } else if (id == Status) { String target = msg.getValue(YSTRING("module")); if (target && target.startsWith(name(),true) && !target.startsWith(prefix())) { msgStatus(msg); return false; } } return Driver::received(msg,id); } bool SIPDriver::hasLine(const String& line) const { return line && findLine(line); } bool SIPDriver::msgExecute(Message& msg, String& dest) { if (!msg.userData()) { Debug(this,DebugWarn,"SIP call found but no data channel!"); return false; } const String& line = msg["line"]; if (!validLine(line)) { // asked to use a line but it's not registered msg.setParam("error","offline"); return false; } YateSIPConnection* conn = new YateSIPConnection(msg,dest,msg.getValue(YSTRING("id"))); conn->initChan(); if (conn->getTransaction()) { CallEndpoint* ch = static_cast(msg.userData()); if (ch && conn->connect(ch,msg.getValue(YSTRING("reason")))) { conn->callConnect(msg); msg.setParam("peerid",conn->id()); msg.setParam("targetid",conn->id()); conn->deref(); return true; } } conn->destruct(); return false; } SIPDriver::SIPDriver() : Driver("sip","varchans"), m_parser("sip","SIP Call"), m_endpoint(0) { Output("Loaded module SIP Channel"); m_parser.debugChain(this); } SIPDriver::~SIPDriver() { Output("Unloading module SIP Channel"); } void SIPDriver::initialize() { static bool first = true; Output("Initializing module SIP Channel"); s_cfg = Engine::configFile("ysipchan"); s_cfg.load(); s_maxForwards = s_cfg.getIntValue("general","maxforwards",20); s_floodEvents = s_cfg.getIntValue("general","floodevents",20); s_privacy = s_cfg.getBoolValue("general","privacy"); s_auto_nat = s_cfg.getBoolValue("general","nat",true); s_progress = s_cfg.getBoolValue("general","progress",false); s_inband = s_cfg.getBoolValue("general","dtmfinband",false); s_info = s_cfg.getBoolValue("general","dtmfinfo",false); s_start_rtp = s_cfg.getBoolValue("general","rtp_start",false); s_multi_ringing = s_cfg.getBoolValue("general","multi_ringing",false); s_refresh_nosdp = s_cfg.getBoolValue("general","refresh_nosdp",true); s_ignoreVia = s_cfg.getBoolValue("general","ignorevia",true); s_printMsg = s_cfg.getBoolValue("general","printmsg",true); s_tcpMaxpkt = getMaxpkt(s_cfg.getIntValue("general","tcp_maxpkt",4096),4096); s_lineKeepTcpOffline = s_cfg.getBoolValue("general","line_keeptcpoffline",!Engine::clientMode()); s_sipt_isup = s_cfg.getBoolValue("sip-t","isup",false); s_expires_min = s_cfg.getIntValue("registrar","expires_min",EXPIRES_MIN); s_expires_def = s_cfg.getIntValue("registrar","expires_def",EXPIRES_DEF); s_expires_max = s_cfg.getIntValue("registrar","expires_max",EXPIRES_MAX); s_auth_register = s_cfg.getBoolValue("registrar","auth_required",true); s_nat_refresh = s_cfg.getIntValue("registrar","nat_refresh",25); s_reg_async = s_cfg.getBoolValue("registrar","async_process",true); s_ack_required = !s_cfg.getBoolValue("hacks","ignore_missing_ack",false); s_1xx_formats = s_cfg.getBoolValue("hacks","1xx_change_formats",true); m_parser.initialize(s_cfg.getSection("codecs"),s_cfg.getSection("hacks"),s_cfg.getSection("general")); if (!m_endpoint) { Thread::Priority prio = Thread::priority(s_cfg.getValue("general","thread")); unsigned int partyMutexCount = s_cfg.getIntValue("general","party_mutexcount",47,13,101); m_endpoint = new YateSIPEndPoint(prio,partyMutexCount); if (!(m_endpoint->Init())) { delete m_endpoint; m_endpoint = 0; return; } m_endpoint->startup(); setup(); installRelay(Halt); installRelay(Progress); installRelay(Update); installRelay(Route); installRelay(Status); installRelay(Stop,"engine.stop"); Engine::install(new UserHandler); if (s_cfg.getBoolValue("general","generate")) Engine::install(new SipHandler); } else m_endpoint->engine()->initialize(s_cfg.getSection("general")); // Unsafe globals s_globalMutex.lock(); s_realm = s_cfg.getValue("general","realm","Yate"); s_tcpOutRtpip = s_cfg.getValue("general","tcp_out_rtp_localip"); s_sslCertFile = s_cfg.getValue("general","ssl_certificate_file"); s_sslKeyFile = s_cfg.getValue("general","ssl_key_file"); s_globalMutex.unlock(); // Adjust here the TCP idle interval: it uses the SIP engine s_tcpIdle = tcpIdleInterval(s_cfg.getIntValue("general","tcp_idle",TCP_IDLE_DEF)); // Mark listeners m_endpoint->initializing(true); // Setup general listener NamedList* general = s_cfg.getSection("general"); NamedList dummy("general"); NamedList* def = general; if (!def) def = &dummy; NamedList* generalListener = s_cfg.getSection("listener general"); if (generalListener) { bool enabled = generalListener->getBoolValue("enable",true); m_endpoint->setupUdpTransport("general",enabled,*generalListener,*def); } else if (general) m_endpoint->setupUdpTransport("general",true,*general,*def); else m_endpoint->setupUdpTransport("general",true,*def); // Setup listeners unsigned int n = s_cfg.sections(); for (unsigned int i = 0; i < n; i++) { NamedList* nl = s_cfg.getSection(i); String name = nl ? nl->c_str() : ""; if (!name.startSkip("listener ",false)) continue; name.trimBlanks(); if (!name || name == YSTRING("general")) continue; const String& type = (*nl)[YSTRING("type")]; int proto = ProtocolHolder::lookupProtoAny(type); if (proto == ProtocolHolder::Unknown) { proto = ProtocolHolder::Udp; Debug(this,DebugNote,"Invalid listener type '%s' in section '%s': defaults to %s", type.c_str(),nl->c_str(),ProtocolHolder::lookupProtoName(proto,false)); } bool enabled = nl->getBoolValue(YSTRING("enable"),true); switch (proto) { case ProtocolHolder::Udp: m_endpoint->cancelListener(name,"Type changed"); m_endpoint->setupUdpTransport(name,enabled,*nl,*def); break; case ProtocolHolder::Tcp: case ProtocolHolder::Tls: m_endpoint->setupUdpTransport(name,false,NamedList::empty(), NamedList::empty(),"Type changed"); m_endpoint->setupListener(proto,name,enabled,*nl); break; default: if (enabled) Debug(this,DebugNote,"Unknown listener type '%s' in section '%s'", type.c_str(),nl->c_str()); } } // Remove deleted listeners m_endpoint->initializing(false); // Everything set: update default udp transport m_endpoint->updateDefUdpTransport(); first = false; } void SIPDriver::genUpdate(Message& msg) { DDebug(this,DebugInfo,"fill module.update message"); Lock l(this); if (m_endpoint) { msg.setParam("failed_auths",String(m_endpoint->failedAuths())); msg.setParam("transaction_timeouts",String(m_endpoint->timedOutTrs())); msg.setParam("bye_timeouts",String(m_endpoint->timedOutByes())); } } bool SIPDriver::commandComplete(Message& msg, const String& partLine, const String& partWord) { String cmd = s_statusCmd + " " + name(); String overviewCmd = s_statusCmd + " overview " + name(); if (partLine == cmd || partLine == overviewCmd) { itemComplete(msg.retValue(),YSTRING("accounts"),partWord); itemComplete(msg.retValue(),YSTRING("listeners"),partWord); itemComplete(msg.retValue(),YSTRING("transports"),partWord); } String cmdTrans = cmd + " transports"; String cmdOverViewTrans = overviewCmd + " transports"; if (partLine == cmdTrans || partLine == cmdOverViewTrans) { itemComplete(msg.retValue(),YSTRING("all"),partWord); itemComplete(msg.retValue(),YSTRING("udp"),partWord); itemComplete(msg.retValue(),YSTRING("tcp"),partWord); itemComplete(msg.retValue(),YSTRING("tls"),partWord); if (partLine == cmdTrans) { Lock lock(m_endpoint->m_mutex); for (ObjList* o = m_endpoint->m_transports.skipNull(); o; o = o->skipNext()) { YateSIPTransport* t = static_cast(o->get()); itemComplete(msg.retValue(),t->toString(),partWord); } } } else return Driver::commandComplete(msg,partLine,partWord); return false; } void SIPDriver::msgStatus(Message& msg) { String str = msg.getValue(YSTRING("module")); while (str.startSkip(name())) { str.trimBlanks(); if (str.null()) Module::msgStatus(msg); else if (str.startSkip("accounts")) { msgStatusAccounts(msg); return; } else if (str.startSkip("transports")) { String tmp = str; tmp.trimBlanks().toLower(); if (tmp == YSTRING("udp")) msgStatusTransports(msg,true,false,false); else if (tmp == YSTRING("tcp")) msgStatusTransports(msg,false,true,false); else if (tmp == YSTRING("tls")) msgStatusTransports(msg,false,false,true); else if (!tmp || tmp == YSTRING("all")) msgStatusTransports(msg,true,true,true); else if (msg.getBoolValue("details",true)) msgStatusTransport(msg,str); return; } else if (str.startSkip("listeners")) { msgStatusListener(msg); return; } } } // Build and dispatch a socket.ssl message bool SIPDriver::socketSsl(Socket** sock, bool server, const String& context) { Message m("socket.ssl"); m.addParam("module",name()); m.addParam("server",String::boolText(server)); m.addParam("context",context,false); if (!server) { Lock lock(s_globalMutex); m.addParam("certificate",s_sslCertFile,false); m.addParam("key",s_sslKeyFile,false); } if (sock && *sock) { RefObjectProxy* p = new RefObjectProxy(sock); m.userData(p); TelEngine::destruct(p); } else m.addParam("test",String::boolText(true)); return Engine::dispatch(m); } // Add accounts status void SIPDriver::msgStatusAccounts(Message& msg) { msg.retValue().clear(); msg.retValue() << "module=" << name(); msg.retValue() << ",protocol=SIP"; msg.retValue() << ",format=Username|Status;"; msg.retValue() << "accounts=" << s_lines.count(); if (!msg.getBoolValue("details",true)) { msg.retValue() << "\r\n"; return; } String accounts = ""; for (ObjList* o = s_lines.skipNull(); o; o = o->skipNext()) { YateSIPLine* line = static_cast(o->get()); accounts.append(line->c_str(),",") << "="; accounts.append(line->getUserName()) << "|"; accounts << (line->valid() ? "online" : "offline"); } msg.retValue().append(accounts,";"); msg.retValue() << "\r\n"; } // Add transports status void SIPDriver::msgStatusTransports(Message& msg, bool showUdp, bool showTcp, bool showTls) { msg.retValue().clear(); msg.retValue() << "module=" << name(); msg.retValue() << ",protocol=SIP"; YateSIPUDPTransport* def = m_endpoint ? m_endpoint->defTransport() : 0; msg.retValue() << ",udp_default=" << (def ? def->toString() : String::empty()); TelEngine::destruct(def); msg.retValue() << ",format=Proto|Status|Local|Remote|Outgoing|Reason;"; String buf; unsigned int n = 0; if (m_endpoint) { Lock lock(m_endpoint->m_mutex); bool details = msg.getBoolValue("details",true); for (ObjList* o = m_endpoint->m_transports.skipNull(); o; o = o->skipNext()) { YateSIPTransport* t = static_cast(o->get()); YateSIPTCPTransport* tcp = t->tcpTransport(); if (!tcp) { if (!showUdp) continue; } else if (!tcp->tls()) { if (!showTcp) continue; } else if (!showTls) continue; n++; if (!details) continue; Lock lck(t); buf.append(String(n),",") << "=" << t->protoName() << "|"; buf << YateSIPTransport::statusName(t->status()) << "|"; buf << t->local().host() << ":" << t->local().port() << "|"; if (tcp) { buf << t->remote().host() << ":" << t->remote().port() << "|"; buf << String::boolText(tcp->outgoing()); } else buf << "|"; buf << "|" << t->m_reason; } } msg.retValue() << "transports=" << n; msg.retValue().append(buf,";"); msg.retValue() << "\r\n"; } // Add listeners status void SIPDriver::msgStatusListener(Message& msg) { msg.retValue().clear(); msg.retValue() << "module=" << name(); msg.retValue() << ",protocol=SIP"; msg.retValue() << ",format=Proto|Address|Status|Reason;"; String buf; unsigned int n = 0; if (m_endpoint) { bool details = msg.getBoolValue("details",true); Lock lock(m_endpoint->m_mutex); for (ObjList* o = m_endpoint->m_transports.skipNull(); o; o = o->skipNext()) { YateSIPTransport* t = static_cast(o->get()); YateSIPUDPTransport* udp = t->udpTransport(); if (!udp) continue; n++; if (!details) continue; Lock lck(udp); buf.append(udp->toString(),",") << "=" << udp->protoName() <<"|"; buf << udp->local().host() << ":" << udp->local().port() << "|"; buf << (udp->status() == YateSIPTransport::Connected ? "Listening|" : "Idle|"); buf << udp->m_reason; } if (details) { for (ObjList* o = m_endpoint->m_listeners.skipNull(); o; o = o->skipNext()) { YateSIPTCPListener* l = static_cast(o->get()); n++; buf.append(l->toString(),",") << "="; buf << l->protoName() << "|"; Lock lck(l->m_mutex); buf << l->address() << ":" << l->port() << "|"; buf << (l->listening() ? "Listening|" : "Idle|"); buf << l->m_reason; } } else n += m_endpoint->m_listeners.count(); } msg.retValue() << "listeners=" << n; msg.retValue().append(buf,";"); msg.retValue() << "\r\n"; } // Add transport status void SIPDriver::msgStatusTransport(Message& msg, const String& id) { msg.retValue().clear(); msg.retValue() << "module=" << name(); msg.retValue() << ",protocol=SIP;"; String tmp = id; tmp.trimBlanks(); YateSIPTransport* t = m_endpoint ? m_endpoint->findTransport(tmp) : 0; if (t) { YateSIPTCPTransport* tcp = t->tcpTransport(); t->lock(); msg.retValue() << "name=" << t->toString(); msg.retValue() << ",protocol=" << t->protoName(); msg.retValue() << ",status=" << YateSIPTransport::statusName(t->status()); msg.retValue() << ",statustime=" << (msg.msgTime().sec() - t->m_statusChgTime); msg.retValue() << ",local=" << t->local().host() << ":" << t->local().port(); if (tcp) { msg.retValue() << ",remote=" << t->remote().host() << ":" << t->remote().port(); msg.retValue() << ",outgoing=" << String::boolText(tcp->outgoing()); } String lines; for (ObjList* ol = s_lines.skipNull(); ol; ol = ol->skipNext()) { YateSIPLine* line = static_cast(ol->get()); if (line->isTransport(t)) lines.append(line->toString(),","); } msg.retValue() << ",lines=" << lines; msg.retValue() << ",references=" << (t->refcount() - 1); msg.retValue() << ",reason=" << t->m_reason; t->unlock(); TelEngine::destruct(t); } msg.retValue() << "\r\n"; } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */