/** * ystunchan.cpp * This file is part of the YATE Project http://YATE.null.ro * * STUN support module * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2006 Null Team * Author: Marian Podgoreanu * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include using namespace TelEngine; namespace { // anonymous /* socket.stun parameters uselocalusername Add USERNAME attribute when sending requests Defaults to true localusername The USERNAME attribute for outgoing requests useremoteusername Check USERNAME attribute when receiving requests Defaults to true remoteusername The USERNAME attribute for incoming requests remoteip The initial remote address remoteport The initial remote port userid The id of the user that requested the filter Defaults to 'UNKNOWN' The message's userdata must be a RefObject with the socket to filter */ class YStunError; // STUN errors class YStunAttribute; // Message attributes class YStunAttributeError; // ERROR-CODE class YStunAttributeChangeReq; // CHANGE-REQUEST class YStunAttributeAuth; // USERNAME, PASSWORD class YStunAttributeAddr; // MAPPED-ADDRESS, RESPONSE-ADDRESS, // SOURCE-ADDRESS, CHANGED-ADDRESS, REFLECTED-FROM class YStunAttributeUnknown; // The others class YStunMessage; // STUN message class YStunUtils; // General usefull functions class YStunMessageOut; // Outgoing STUN message (message + retransmission info) class YStunSocketFilter; // Socket filter for STUN class StunHandler; // socket.stun handler class YStunPlugin; // The plugin /** * Defines */ // *** Message #define STUN_MSG_IDLENGTH 16 // Size in bytes of message id #define STUN_MSG_HEADERLENGTH 20 // Size in bytes of message header (type+length+id) // *** Attributes #define STUN_ATTR_OPTIONAL 0x7fff // Start value for optional attributes #define STUN_ATTR_HEADERLENGTH 4 // Size in bytes of attribute header (type+length) #define STUN_ATTR_ADDR_IPV4 0x01 // IPv4 type address for address attributes #define STUN_ATTR_CHGREQ_PORT 2 // CHANGE-REQUEST: Change port flag #define STUN_ATTR_CHGREQ_ADDR 4 // CHANGE-REQUEST: Change address flag // *** Filters #define FILTER_SECURITYLENGTH 8 // The length of the string used in id generation // to validate binding responses // *** Plugin #define STUN_SERVER_DEFAULTPORT 3478 // Server port // Bind request #define STUN_BINDINTERVAL_MIN 5000 // Bind request interval margins #define STUN_BINDINTERVAL_MAX 60000 #define STUN_BINDINTERVAL 15000 // Bind request interval in miliseconds // Message retransmission #define STUN_RETRANS_COUNT 5 // Retransmission counter #define STUN_RETRANS_INTERVAL 500 // Starting value (in miliseconds) for retransmission interval // Set message header values (type+length) inline void setHeader(u_int8_t* buffer, u_int16_t type, u_int16_t len) { buffer[0] = type >> 8; buffer[1] = (u_int8_t)type; buffer[2] = len >> 8; buffer[3] = (u_int8_t)len; } // Get message header values (type+length) inline void getHeader(const u_int8_t* buffer, u_int16_t& type, u_int16_t& len) { type = buffer[0] << 8 | buffer[1]; len = buffer[2] << 8 | buffer[3]; } /** * YStunError */ class YStunError { public: enum Type { BadReq = 144, // BAD REQUEST Auth = 174, // STALE CREDENDIALS }; static TokenDict s_tokens[]; // Error strings }; /** * Attributes */ class YStunAttribute : public RefObject { public: enum Type { MappedAddress = 0x0001, // MAPPED-ADDRESS ResponseAddress = 0x0002, // RESPONSE-ADDRESS ChangeRequest = 0x0003, // CHANGE-REQUEST SourceAddress = 0x0004, // SOURCE-ADDRESS ChangedAddress = 0x0005, // CHANGED-ADDRESS Username = 0x0006, // USERNAME Password = 0x0007, // PASSWORD MessageIntegrity = 0x0008, // MESSAGE-INTEGRITY ErrorCode = 0x0009, // ERROR-CODE UnknownAttributes = 0x000a, // UNKNOWN-ATTRIBUTES ReflectedFrom = 0x000b, // REFLECTED-FROM Unknown, // None of the above }; inline YStunAttribute(u_int16_t type) : m_type(type) {} virtual ~YStunAttribute() {} inline u_int16_t type() const { return m_type; } const char* text() const { return lookup(m_type,s_tokens); } /** * Add the attribute to a string. * @param dest The destination. */ virtual void toString(String& dest) = 0; /** * Create from received buffer. * @param buffer Data to process. Doesn't include the attribute header (type+length). * @param len Data length (for this attribute). * @return False on invalid data. */ virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len) = 0; /** * Append this attribute to a buffer to be sent. * @param buffer The destination buffer. */ virtual void toBuffer(DataBlock& buffer) = 0; static TokenDict s_tokens[]; private: u_int16_t m_type; // Attribute type }; class YStunAttributeError : public YStunAttribute { public: inline YStunAttributeError(u_int16_t code = 0, const char* text = 0) : YStunAttribute(ErrorCode), m_code(code), m_text(text) {} virtual ~YStunAttributeError() {} virtual void toString(String& dest); virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len); virtual void toBuffer(DataBlock& buffer); private: u_int16_t m_code; // Error code String m_text; // Error string }; // Change request // 4 bytes. Bits 1 and 2 are used class YStunAttributeChangeReq : public YStunAttribute { public: inline YStunAttributeChangeReq(bool chg_port = false, bool chg_addr = false) : YStunAttribute(ChangeRequest), m_flags(0) { if (chg_port) m_flags |= STUN_ATTR_CHGREQ_PORT; if (chg_addr) m_flags |= STUN_ATTR_CHGREQ_ADDR; } virtual ~YStunAttributeChangeReq() {} virtual void toString(String& dest); virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len); virtual void toBuffer(DataBlock& buffer); private: u_int32_t m_flags; // Change request flags }; // Username or Password // The length MUST be a multiple of 4 class YStunAttributeAuth : public YStunAttribute { public: inline YStunAttributeAuth(u_int16_t type) : YStunAttribute(type) {} inline YStunAttributeAuth(const char* value, bool username = true) : YStunAttribute(username ? Username : Password), m_auth(value) {} virtual ~YStunAttributeAuth() {} virtual void toString(String& dest); virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len); virtual void toBuffer(DataBlock& buffer); private: String m_auth; // Username/Password value }; // IP Address + port class YStunAttributeAddr : public YStunAttribute { public: inline YStunAttributeAddr(u_int16_t type) : YStunAttribute(type), m_port(0) {} inline YStunAttributeAddr(u_int16_t type, const String& addr, u_int16_t port) : YStunAttribute(type), m_addr(addr), m_port(port) {} virtual ~YStunAttributeAddr() {} virtual void toString(String& dest); virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len); virtual void toBuffer(DataBlock& buffer); private: String m_addr; // Address u_int16_t m_port; // Port }; // Unknown class YStunAttributeUnknown : public YStunAttribute { public: inline YStunAttributeUnknown(u_int16_t type) : YStunAttribute(Unknown), m_unknownType(type) {} virtual ~YStunAttributeUnknown() {} virtual void toString(String& dest); virtual bool fromBuffer(u_int8_t* buffer, u_int16_t len); virtual void toBuffer(DataBlock& buffer); private: u_int16_t m_unknownType; // The unknown type DataBlock m_data; // Data }; /** * YStunMessage */ class YStunMessage : public RefObject { public: enum Type { BindReq = 0x0001, // Binding Request BindRsp = 0x0101, // Binding Response BindErr = 0x0111, // Binding Error Response SecretReq = 0x0002, // Shared Secret Request SecretRsp = 0x0102, // Shared Secret Response SecretErr = 0x0112, // Shared Secret Error Response }; YStunMessage(Type type, const char* id = 0); virtual ~YStunMessage() {} inline Type type() const { return m_type; } inline const String& id() const { return m_id; } const char* text() const { return lookup(m_type,s_tokens); } inline void addAttribute(YStunAttribute* attr) { m_attributes.append(attr); } YStunAttribute* getAttribute(u_int16_t attrType, bool remove = false); void toMessage(Message& msg) const; bool toBuffer(DataBlock& buffer) const; void print(); static TokenDict s_tokens[]; private: Type m_type; // Message type String m_id; // Message id ObjList m_attributes; // Message attributes }; /** * YStunUtils */ class YStunUtils { public: YStunUtils(); static bool isStun(const void* data, u_int32_t len, YStunMessage::Type& type); static YStunMessage* decode(const void* data, u_int32_t len, bool& isStun); // Create an id used to send a binding request static void createId(String& id); // Send a message through the given socket static bool sendMessage(Socket* socket, const YStunMessage* msg, const SocketAddr& addr, void* sender = 0); // Get the error attribute of a message. // Return false if the message doesn't have one static bool getAttrError(YStunMessage* msg, String& errStr); // Get an address attribute of a message. // Return false if the message doesn't have this attribute static bool getAttrAddr(YStunMessage* msg, YStunAttribute::Type type, String& addr); // Get an auth attribute of a message. // Return false if the message doesn't have this attribute static bool getAttrAuth(YStunMessage* msg, YStunAttribute::Type type, String& auth); protected: static Mutex s_idMutex; // Lock id changes static unsigned int m_id; // Used to generate unique id for requests }; /** * YStunMessageOut */ class YStunMessageOut : public RefObject { friend class YStunSocketFilter; public: YStunMessageOut(YStunMessage* msg, const SocketAddr addr, void* sender = 0); virtual ~YStunMessageOut(); inline bool isId(const YStunMessage* msg) const { if (!(m_msg && msg)) return false; return msg->id().endsWith(m_msg->id().c_str() + FILTER_SECURITYLENGTH); } inline bool timeToSend(u_int64_t time) { return time >= m_next; } inline bool timeout() { return !m_count; } inline bool send(Socket* socket, u_int64_t time) { update(time); return YStunUtils::sendMessage(socket,m_msg,m_addr,m_sender); } // Reset retransmission info: m_count, m_interval, m_next // Set the address to the new one void reset(const SocketAddr& addr); protected: inline void update(u_int64_t time) { m_count--; m_interval *= 2; m_next = time + m_interval; } private: YStunMessage* m_msg; // The message SocketAddr m_addr; // Remote peer's address void* m_sender; // The sender u_int16_t m_count; // Retransmission counter u_int64_t m_interval; // Retransmission interval u_int64_t m_next; // Time for next retransmission }; /** * YStunSocketFilter */ class YStunSocketFilter : public SocketFilter { friend class YStunPlugin; public: YStunSocketFilter(); virtual ~YStunSocketFilter(); // Received: call by the socket virtual bool received(void* buffer, int length, int flags, const struct sockaddr* addr, socklen_t addrlen); // Timer handler: Handle retransmission for binding request virtual void timerTick(const Time& when); // Install the filter. Return false if it fails bool install(Socket* sock, const Message* msg); protected: // Process a received message. Destroy it after processing bool processMessage(YStunMessage* msg); // Process a received binding request // Respond to it void processBindReq(YStunMessage* msg); // Process a received binding response (error or response) void processBindResult(YStunMessage* msg); private: SocketAddr m_remoteAddr; // Last received packet's address bool m_useLocalUsername; // True to use local username when sending bind request bool m_useRemoteUsername; // True to use remote username to check bind requests String m_localUsername; // Local username String m_remoteUsername; // Remote username // Filter's user String m_userId; // User's id // Bind request YStunMessageOut* m_bindReq; // The message Mutex m_bindReqMutex; // Lock message operations u_int64_t m_bindReqNext; // Time for the next request // Address authenticated ? bool m_notFound; // Flag set when we found the right address for the remote peer String m_security; // Random string used to build id for a binding request }; /** * socket.stun message handler */ class StunHandler : public MessageHandler { public: StunHandler() : MessageHandler("socket.stun") {} // Process message. Create and install filter. virtual bool received(Message &msg); }; /** * YStunPlugin */ class YStunPlugin : public Module { public: YStunPlugin(); virtual ~YStunPlugin(); virtual void initialize(); inline u_int64_t bindInterval() { return m_bindInterval; } private: u_int32_t m_bindInterval; // Bind request interval }; /** * Local data */ static YStunPlugin iplugin; static Configuration s_cfg; /** * YStunError */ TokenDict YStunError::s_tokens[] = { {"BAD REQUEST", BadReq}, {"STALE CREDENDIALS", Auth}, {0,0} }; /** * Attributes */ TokenDict YStunAttribute::s_tokens[] = { {"MAPPED-ADDRESS", MappedAddress}, {"RESPONSE-ADDRESS", ResponseAddress}, {"CHANGE-REQUEST", ChangeRequest}, {"SOURCE-ADDRESS", SourceAddress}, {"CHANGED-ADDRESS", ChangedAddress}, {"USERNAME", Username}, {"PASSWORD", Password}, {"MESSAGE-INTEGRITY", MessageIntegrity}, {"ERROR-CODE", ErrorCode}, {"UNKNOWN-ATTRIBUTES", UnknownAttributes}, {"REFLECTED-FROM", ReflectedFrom}, {"UNKNOWN", Unknown}, {0,0} }; // YStunAttributeError void YStunAttributeError::toString(String& dest) { dest = ""; dest << m_code << ":" << m_text; } bool YStunAttributeError::fromBuffer(u_int8_t* buffer, u_int16_t len) { // 'len' must be at least 4 and a multiple of 4 // buffer[2]: Error class (3 bits) // buffer[3]: Error code modulo 100 (Values: 0..99) if (!(buffer && len >= 4 && (len % 4) == 0)) return false; m_code = (buffer[2] & 0x07) * 100 + (buffer[3] < 100 ? buffer[3] : 0); if (len > 4) m_text.assign((const char*)buffer + 4,len - 4); return true; } void YStunAttributeError::toBuffer(DataBlock& buffer) { u_int8_t header[8] = {0,0,0,0, 0,0,m_code / 100,m_code % 100}; setHeader(header,type(),4 + m_text.length()); DataBlock tmp(header,sizeof(header)); buffer += tmp; buffer.append(m_text); } // YStunAttributeChangeReq void YStunAttributeChangeReq::toString(String& dest) { dest = (unsigned int)m_flags; } bool YStunAttributeChangeReq::fromBuffer(u_int8_t* buffer, u_int16_t len) { if (!(buffer && len == 4)) return false; m_flags = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]; return true; } void YStunAttributeChangeReq::toBuffer(DataBlock& buffer) { u_int8_t header[8] = {0,0,0,0, m_flags >> 24,m_flags >> 16,m_flags >> 8,m_flags}; setHeader(header,type(),4); DataBlock tmp(header,sizeof(header)); buffer += tmp; } // YStunAttributeAuth void YStunAttributeAuth::toString(String& dest) { dest = m_auth; } bool YStunAttributeAuth::fromBuffer(u_int8_t* buffer, u_int16_t len) { if (!(buffer && (len % 4) == 0)) return false; m_auth.assign((const char*)buffer,len); return true; } void YStunAttributeAuth::toBuffer(DataBlock& buffer) { u_int8_t header[4]; setHeader(header,type(),m_auth.length()); DataBlock tmp(header,sizeof(header)); buffer += tmp; buffer.append(m_auth); } // YStunAttributeAddr void YStunAttributeAddr::toString(String& dest) { dest = ""; dest << m_addr << ":" << m_port; } bool YStunAttributeAddr::fromBuffer(u_int8_t* buffer, u_int16_t len) { if (!(buffer && len == 8 && buffer[1] == STUN_ATTR_ADDR_IPV4)) return false; m_port = buffer[2] << 8 | buffer[3]; m_addr = ""; m_addr << buffer[4] << "." << buffer[5] << "." << buffer[6] << "." << buffer[7]; return true; } void YStunAttributeAddr::toBuffer(DataBlock& buffer) { u_int8_t header[12] = {0,0,0,0, 0,STUN_ATTR_ADDR_IPV4,m_port >> 8,(u_int8_t)m_port, 0,0,0,0}; setHeader(header,type(),8); for (int start = 0, i = 8; i < 12; i++) { int end = m_addr.find('.',start); if (end == -1) end = m_addr.length(); if (end != start) header[i] = m_addr.substr(start,end - start).toInteger(); if (end == (int)m_addr.length()) break; start = end + 1; } DataBlock tmp(header,sizeof(header)); buffer += tmp; } // YStunAttributeUnknown void YStunAttributeUnknown::toString(String& dest) { dest = ""; dest << "Data length: " << m_data.length(); } bool YStunAttributeUnknown::fromBuffer(u_int8_t* buffer, u_int16_t len) { if (!(buffer && (len % 4) == 0)) return false; DataBlock tmp(buffer,len); m_data = tmp; return true; } void YStunAttributeUnknown::toBuffer(DataBlock& buffer) { u_int8_t header[4]; setHeader(header,m_unknownType,m_data.length()); DataBlock tmp(header,sizeof(header)); buffer += tmp; buffer += m_data; } /** * YStunMessage */ TokenDict YStunMessage::s_tokens[] = { {"BindReq", BindReq}, {"BindRsp", BindRsp}, {"BindErr", BindErr}, {"SecretReq", SecretReq}, {"SecretRsp", SecretRsp}, {"SecretErr", SecretErr}, {0,0} }; YStunMessage::YStunMessage(Type type, const char* id) : m_type(type), m_id(id) { if (!id) YStunUtils::createId(m_id); } YStunAttribute* YStunMessage::getAttribute(u_int16_t attrType, bool remove) { ObjList* obj = m_attributes.skipNull(); for (; obj; obj = obj->skipNext()) { YStunAttribute* attr = static_cast(obj->get()); if (attr->type() == attrType) { if (remove) m_attributes.remove(attr,false); return attr; } } return 0; } void YStunMessage::toMessage(Message& msg) const { msg.addParam("message_type",text()); msg.addParam("message_id",m_id); // Add attributes ObjList* obj = m_attributes.skipNull(); for (; obj; obj = obj->skipNext()) { YStunAttribute* attr = static_cast(obj->get()); String tmp; attr->toString(tmp); msg.addParam(attr->text(),tmp); } } bool YStunMessage::toBuffer(DataBlock& buffer) const { // Create attributes DataBlock attr_buffer; ObjList* obj = m_attributes.skipNull(); for(; obj; obj = obj->skipNext()) { YStunAttribute* attr = static_cast(obj->get()); attr->toBuffer(attr_buffer); } // Set message buffer u_int8_t header[4]; setHeader(header,m_type,attr_buffer.length()); buffer.assign(header,sizeof(header)); buffer.append(m_id); buffer.append(attr_buffer); return true; } void YStunMessage::print() { Debug(&iplugin,DebugAll,"YStunMessage [%p]. Type: '%s'. ID: '%s'.", this,text(),m_id.c_str()); // Print attributes ObjList* obj = m_attributes.skipNull(); for (; obj; obj = obj->skipNext()) { YStunAttribute* attr = static_cast(obj->get()); String tmp; attr->toString(tmp); Debug(&iplugin,DebugAll,"YStunMessage [%p]. Attribute: %s=%s", this,attr->text(),tmp.c_str()); } } /** * YStunUtils */ unsigned int YStunUtils::m_id = 1; Mutex YStunUtils::s_idMutex(true,"YStunUtils::id"); YStunUtils::YStunUtils() { } bool YStunUtils::isStun(const void* data, u_int32_t len, YStunMessage::Type& type) { // Check if received buffer is a STUN message: // - Length: Greater then or equal to STUN_MSG_HEADERLENGTH // Multiple of 4 // Match the length field of the header // - Type: YStunMessage::Type const u_int8_t* buffer = (const u_int8_t*)data; // Check length if (!(data && len >= STUN_MSG_HEADERLENGTH && !(len % 4))) return false; u_int16_t msg_type, msg_len; getHeader(buffer,msg_type,msg_len); //TODO: Check if the message contains a message integrity attribute: Size is different if (msg_len != len - STUN_MSG_HEADERLENGTH) return false; // Check type switch (msg_type) { case YStunMessage::BindReq: case YStunMessage::BindRsp: case YStunMessage::BindErr: case YStunMessage::SecretReq: case YStunMessage::SecretRsp: case YStunMessage::SecretErr: break; default: return false; } type = (YStunMessage::Type)msg_type; // OK: go on! return true; } YStunMessage* YStunUtils::decode(const void* data, u_int32_t len, bool& isStun) { u_int8_t* buffer = (u_int8_t*)data; YStunMessage::Type type; isStun = YStunUtils::isStun(data,len,type); if (!isStun) return 0; // Get ID String id((const char*)buffer + 4,STUN_MSG_IDLENGTH); YStunMessage* msg = new YStunMessage(type,id); // Get attributes u_int32_t i = STUN_MSG_HEADERLENGTH; for (; i < len;) { // Check if we have an attribute header if (i + 4 > len) break; // Get type & length u_int16_t attr_type, attr_len; getHeader(buffer+i,attr_type,attr_len); i += 4; // Check if length matches if (i + attr_len > len) break; // Create object YStunAttribute* attr = 0; switch (attr_type) { // Addresses case YStunAttribute::MappedAddress: case YStunAttribute::ResponseAddress: case YStunAttribute::SourceAddress: case YStunAttribute::ChangedAddress: case YStunAttribute::ReflectedFrom: attr = new YStunAttributeAddr(attr_type); break; // Error case YStunAttribute::ErrorCode: attr = new YStunAttributeError(attr_type); break; // Flags case YStunAttribute::ChangeRequest: attr = new YStunAttributeChangeReq(); break; // Auth case YStunAttribute::Username: case YStunAttribute::Password: attr = new YStunAttributeAuth(attr_type); break; // case YStunAttribute::MessageIntegrity: case YStunAttribute::UnknownAttributes: attr = new YStunAttributeUnknown(attr_type); break; default: attr = new YStunAttributeUnknown(attr_type); } // Parse attribute. Add on success if (!attr->fromBuffer(buffer + i,attr_len)) { attr->deref(); break; } msg->addAttribute(attr); // Skip processed data i += attr_len; } if (i < len) { msg->deref(); return 0; } return msg; } void YStunUtils::createId(String& id) { id = ""; s_idMutex.lock(); id << m_id++ << "_"; s_idMutex.unlock(); for (; id.length() < STUN_MSG_IDLENGTH;) id << (int)random(); id = id.substr(0,STUN_MSG_IDLENGTH); } bool YStunUtils::sendMessage(Socket* socket, const YStunMessage* msg, const SocketAddr& addr, void* sender) { if (!(socket && msg)) return false; XDebug(&iplugin,DebugAll,"Send message ('%s') to '%s:%d'. [%p]", msg->text(),addr.host().c_str(),addr.port(),sender); DataBlock buffer; msg->toBuffer(buffer); int result = socket->sendTo(buffer.data(),buffer.length(),addr); if (result != Socket::socketError()) return true; if (!socket->canRetry()) Debug(&iplugin,DebugWarn,"Socket write error: '%s' (%d). [%p]", ::strerror(socket->error()),socket->error(),sender); #ifdef DEBUG else Debug(&iplugin,DebugMild,"Socket temporary unavailable: '%s' (%d). [%p]", ::strerror(socket->error()),socket->error(),sender); #endif return false; } bool YStunUtils::getAttrError(YStunMessage* msg, String& errStr) { if (!msg) return false; YStunAttributeError* errAttr = static_cast (msg->getAttribute(YStunAttribute::ErrorCode)); if (!errAttr) return false; errAttr->toString(errStr); return true; } bool YStunUtils::getAttrAddr(YStunMessage* msg, YStunAttribute::Type type, String& addr) { if (!msg) return false; YStunAttributeAddr* attr = 0; switch (type) { case YStunAttribute::MappedAddress: case YStunAttribute::ResponseAddress: case YStunAttribute::SourceAddress: case YStunAttribute::ChangedAddress: case YStunAttribute::ReflectedFrom: attr = static_cast(msg->getAttribute(type)); break; default: return false; } if (!attr) return false; attr->toString(addr); return true; } bool YStunUtils::getAttrAuth(YStunMessage* msg, YStunAttribute::Type type, String& auth) { if (!msg) return false; YStunAttributeAuth* attr = 0; switch (type) { case YStunAttribute::Username: case YStunAttribute::Password: attr = static_cast(msg->getAttribute(type)); break; default: return false; } if (!attr) return false; attr->toString(auth); return true; } /** * YStunMessageOut */ YStunMessageOut::YStunMessageOut(YStunMessage* msg, const SocketAddr addr, void* sender) : m_msg(msg), m_addr(addr), m_sender(sender), m_count(STUN_RETRANS_COUNT), m_interval(STUN_RETRANS_INTERVAL), m_next(0) { } YStunMessageOut::~YStunMessageOut() { if (m_msg) m_msg->deref(); } void YStunMessageOut::reset(const SocketAddr& addr) { m_addr = addr; m_count = STUN_RETRANS_COUNT; m_interval = STUN_RETRANS_INTERVAL; m_next = 0; } /** * YStunSocketFilter */ YStunSocketFilter::YStunSocketFilter() : SocketFilter(), m_remoteAddr(AF_INET), m_useLocalUsername(false), m_useRemoteUsername(false), m_bindReq(0), m_bindReqMutex(true,"YStunSocketFilter::bindReq"), m_bindReqNext(0), m_notFound(true) { DDebug(&iplugin,DebugAll,"YStunSocketFilter. [%p]",this); for (; m_security.length() < FILTER_SECURITYLENGTH; ) m_security << (int)random(); m_security = m_security.substr(0,FILTER_SECURITYLENGTH); } YStunSocketFilter::~YStunSocketFilter() { DDebug(&iplugin,DebugAll,"~YStunSocketFilter. [%p]",this); } bool YStunSocketFilter::received(void* buffer, int length, int flags, const struct sockaddr* addr, socklen_t addrlen) { bool isStun = false; YStunMessage* msg = YStunUtils::decode(buffer,length,isStun); if (!isStun) return false; if (msg) { SocketAddr tmp(addr,addrlen); if (m_remoteAddr != tmp) { if (m_notFound) { Debug(&iplugin,DebugNote, "Filter remote address changed from '%s:%d' to '%s:%d'. [%p]", m_remoteAddr.host().c_str(),m_remoteAddr.port(), tmp.host().c_str(),tmp.port(),this); m_remoteAddr = tmp; // Remote address changed: reset bind request m_bindReqMutex.lock(); if (m_bindReq) m_bindReq->reset(m_remoteAddr); else timerTick(Time()); m_bindReqMutex.unlock(); } else { Debug(&iplugin,DebugInfo, "Filter ignoring message from invalid address '%s:%d'. [%p]", tmp.host().c_str(),tmp.port(),this); msg->deref(); return true; } } processMessage(msg); } return true; } bool YStunSocketFilter::install(Socket* sock, const Message* msg) { if (socket() || !(sock && msg)) return false; // Set data m_useLocalUsername = msg->getBoolValue("uselocalusername",true); m_localUsername = msg->getValue("localusername"); m_useRemoteUsername = msg->getBoolValue("useremoteusername",true); m_remoteUsername = msg->getValue("remoteusername"); m_remoteAddr.host(msg->getValue("remoteip")); m_remoteAddr.port(msg->getIntValue("remoteport")); m_userId = msg->getValue("userid"); // Install if (!sock->installFilter(this)) { Debug(&iplugin,DebugGoOn, "Error installing filter for '%s'. [%p]", m_userId.c_str(),this); return false; } DDebug(&iplugin,DebugAll, "Filter installed for '%s'. [%p]", m_userId.c_str(),this); // Send first bind request Time when; timerTick(when); return true; } void YStunSocketFilter::timerTick(const Time& when) { u_int64_t time = when.msec(); Lock lock(m_bindReqMutex); // Send another request ? if (!m_bindReq) { // It's time to send ? if (time < m_bindReqNext) return; String id; YStunUtils::createId(id); id = id.substr(0,FILTER_SECURITYLENGTH) + m_security; YStunMessage* req = new YStunMessage(YStunMessage::BindReq,id); if (m_useLocalUsername) req->addAttribute(new YStunAttributeAuth(m_localUsername)); m_bindReq = new YStunMessageOut(req,m_remoteAddr,this); m_bindReq->send(socket(),time); m_bindReqNext = time + iplugin.bindInterval(); return; } // We have a pending request // Time to send ? if (!m_bindReq->timeToSend(time)) return; // Timeout or resend if (m_bindReq->timeout()) { processBindResult(0); m_bindReq->deref(); m_bindReq = 0; } else m_bindReq->send(socket(),time); m_bindReqNext = time + iplugin.bindInterval(); } bool YStunSocketFilter::processMessage(YStunMessage* msg) { if (!msg) return false; DDebug(&iplugin,DebugAll, "Filter received %s (%p) from '%s:%d'. Id: '%s'. [%p]", msg->text(),msg,m_remoteAddr.host().c_str(), m_remoteAddr.port(),msg->id().c_str(),this); switch(msg->type()) { case YStunMessage::BindReq: processBindReq(msg); break; case YStunMessage::BindRsp: case YStunMessage::BindErr: m_bindReqMutex.lock(); if (m_bindReq && m_bindReq->isId(msg)) { processBindResult(msg); m_bindReqMutex.unlock(); break; } m_bindReqMutex.unlock(); DDebug(&iplugin,DebugNote, "Filter: (%p) is a response to a non existing request. [%p]", msg,this); break; default: Debug(&iplugin,DebugNote, "Filter got unexpected message (%p). [%p]",msg,this); } msg->deref(); return true; } void YStunSocketFilter::processBindReq(YStunMessage* msg) { YStunMessage::Type response = YStunMessage::BindRsp; String username; // Check username if (m_useRemoteUsername && (!YStunUtils::getAttrAuth(msg,YStunAttribute::Username,username) || username != m_remoteUsername)) response = YStunMessage::BindErr; // Create response YStunMessage* rspMsg = new YStunMessage(response,msg->id()); rspMsg->addAttribute(new YStunAttributeAuth(username)); if (response == YStunMessage::BindErr) { Debug(&iplugin,DebugInfo, "Filter: Bind request (%p) has invalid username. [%p]",msg,this); // Add error attribute rspMsg->addAttribute(new YStunAttributeError(YStunError::Auth, lookup(YStunError::Auth,YStunError::s_tokens))); } else { // Add mapped address attribute rspMsg->addAttribute(new YStunAttributeAddr( YStunAttribute::MappedAddress,m_remoteAddr.host(), m_remoteAddr.port())); } YStunUtils::sendMessage(socket(),rspMsg,m_remoteAddr,this); rspMsg->deref(); } void YStunSocketFilter::processBindResult(YStunMessage* msg) { // msg is 0: timeout if (!msg) { Debug(&iplugin,DebugNote, "Filter: Bind request to '%s:%d' timed out. [%p]", m_bindReq->m_addr.host().c_str(),m_bindReq->m_addr.port(),this); // TODO: WHAT ???? return; } // Check username if (m_useLocalUsername) { String username; YStunUtils::getAttrAuth(msg,YStunAttribute::Username,username); if (username != m_localUsername) { Debug(&iplugin,DebugInfo, "Filter: Bind response with bad username from '%s:%d'. We expect '%s' and received '%s'. [%p]", m_remoteAddr.host().c_str(),m_remoteAddr.port(),m_remoteUsername.c_str(),username.c_str(),this); } // Authenticated: notify RTP chan else if (m_notFound) { Debug(&iplugin,DebugNote, "Filter: Response authenticated for '%s:%d' - notifying RTP. [%p]", m_remoteAddr.host().c_str(),m_remoteAddr.port(),this); m_notFound = false; Message* m = new Message("chan.rtp"); m->addParam("direction","bidir"); m->addParam("remoteip",m_remoteAddr.host()); m->addParam("remoteport",String(m_remoteAddr.port())); m->addParam("rtpid",m_userId); Engine::enqueue(m); } } if (msg->type() == YStunMessage::BindRsp) { // Mandatory: MappedAddress, SourceAddress, ChangedAddress // Conditional: ReflectedFrom // Optional: MessageIntegrity String mapped; if (YStunUtils::getAttrAddr(msg,YStunAttribute::MappedAddress,mapped)) DDebug(&iplugin,DebugAll, "Filter mapped address: '%s'. [%p]",mapped.c_str(),this); else Debug(&iplugin,DebugAll, "Filter: Invalid message: No MAPPED-ADDRESS attribute. [%p]", this); } else if (msg->type() == YStunMessage::BindErr) { // Mandatory: ErrorCode // Conditional: UnknownAttributes String errStr; if (YStunUtils::getAttrError(msg,errStr)) Debug(&iplugin,DebugAll, "Filter: Received error: '%s'. [%p]",errStr.c_str(),this); else Debug(&iplugin,DebugAll, "Filter: Invalid message (%p): No ERROR-CODE attribute. [%p]", msg,this); } else return; // Result is: error, response // TODO: WHAT ???? // Remove request m_bindReq->deref(); m_bindReq = 0; } /** * StunHandler */ bool StunHandler::received(Message& msg) { Socket* socket = static_cast(msg.userObject("Socket")); if (!socket) { Debug(&iplugin,DebugGoOn,"StunHandler: No socket to install filter for."); return true; } YStunSocketFilter* filter = new YStunSocketFilter(); if (!filter->install(socket,&msg)) filter->destruct(); return true; } /** * YStunPlugin */ YStunPlugin::YStunPlugin() : Module("stun","misc"), m_bindInterval(STUN_BINDINTERVAL) { Output("Loaded module YSTUN"); } YStunPlugin::~YStunPlugin() { Output("Unloading module YSTUN"); } void YStunPlugin::initialize() { static bool notFirst = false; Output("Initializing module YSTUN"); // Load configuration s_cfg = Engine::configFile("ystunchan"); s_cfg.load(); // Bind request interval m_bindInterval = s_cfg.getIntValue("filters","bindrequest_interval", STUN_BINDINTERVAL); if (m_bindInterval < STUN_BINDINTERVAL_MIN) m_bindInterval = STUN_BINDINTERVAL_MIN; else if (m_bindInterval > STUN_BINDINTERVAL_MAX) m_bindInterval = STUN_BINDINTERVAL_MAX; Debug(this,DebugAll,"Bind request interval set to %u msec.", m_bindInterval); // Do the first time jobs if (notFirst) return; notFirst = true; // Install message handlers Engine::install(new StunHandler); setup(); } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */