├── images ├── sipnode.png └── sipnode.svg ├── package.json ├── .gitignore ├── README.md ├── server.js └── config └── kamailio.cfg /images/sipnode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/havfo/Kurento-Nodejs-SIP/HEAD/images/sipnode.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sipnode", 3 | "version": "0.0.1", 4 | "description": "Simple Node SIP server", 5 | "dependencies": { 6 | "sip.js": "^0.7.7", 7 | "kurento-client": "6.6.0", 8 | "socket.io-client": "^1.7.3", 9 | "rtc-sdp": "^1.3.0" 10 | }, 11 | "main": "server.js", 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "author": "Håvar Aambø Fosstveit", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Connect to WebRTC using SIP and Kurento 2 | This project uses SIP.js in Node.js, and Kurento media server to enable SIP endpoints to connect to peer-to-peer WebRTC meetings. The WebRTC meeting server in question is [Knockplop](https://github.com/so010/knockplop). 3 | 4 | ## Architecture 5 | ![SIP Kurento architecture](https://raw.githubusercontent.com/havfo/Kurento-Nodejs-SIP/master/images/sipnode.png "SIP Kurento architecture") 6 | 7 | ## Installation 8 | You need a SIP registrar/proxy that supports SIP over websockets. You need an account on this SIP server to register to. Configure credentials in `server.js`. The room it joins is specified by the `X-Room` SIP-header. You need a Kurento media server installation. Configure the settings in `server.js`. 9 | 10 | You can install Kamailio as the SIP registrar/proxy using the [WEBRTC-to-SIP](https://github.com/havfo/WEBRTC-to-SIP) and use the configuration file `config/kamailio.cfg` in this repository instead. Create the Node.js SIP user with `kamctl add mcu DFOdH1abdsTDCqp`. 11 | 12 | To install: 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | ## Running 18 | To run: 19 | ```bash 20 | npm start 21 | ``` 22 | The Node.js server registers to the SIP server and waits for incoming calls. On an incoming call the server joins the corresponding room on the knockplop server specified in `server.js`. 23 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var SIP = require('sip.js'); 2 | var kurento = require('kurento-client'); 3 | var util = require('util'); 4 | var fs = require('fs'); 5 | var io = require('socket.io-client'); 6 | var sdpparser = require('rtc-sdp'); 7 | 8 | const ws_uri = 'ws://meet.akademia.no:8080/kurento'; 9 | const io_uri = 'https://meet.uninett.no'; 10 | var kurentoClient = null; 11 | 12 | var idCounter = 0; 13 | var rooms = {}; 14 | 15 | function nextUniqueId() { 16 | idCounter++; 17 | return idCounter.toString(); 18 | } 19 | 20 | var ua = new SIP.UA({ 21 | traceSip: true, 22 | register: true, 23 | uri: 'mcu@meeting.akademia.no', 24 | password: 'DFOdH1abdsTDCqp', 25 | rel100: SIP.C.supported.SUPPORTED, 26 | wsServers: 'wss://meeting.akademia.no', 27 | mediaHandlerFactory: function() { 28 | var sessionId = nextUniqueId(); 29 | var roomName = null; 30 | var offer = null; 31 | var answer = null; 32 | 33 | this.isReady = function isReady() { 34 | return true; 35 | } 36 | 37 | this.close = function() { 38 | console.log('Closing SIP dialog: ' + sessionId + ' to room: ' + roomName); 39 | stop(sessionId); 40 | } 41 | 42 | this.render = new Function(); 43 | this.mute = new Function(); 44 | this.unmute = new Function(); 45 | 46 | this.getDescription = (onSuccess, onFailure, mediaHint) => { 47 | console.log('Getdescription called for dialog: ' + sessionId + ' to room: ' + roomName); 48 | 49 | joinRoom(roomName, sessionId, offer, (error, sdpAnswer) => { 50 | if (error) { 51 | onFailure(); 52 | } else { 53 | onSuccess({ 54 | body: sdpAnswer, 55 | contentType: 'application/sdp' 56 | }); 57 | } 58 | }); 59 | } 60 | 61 | this.hasDescription = message => { 62 | return true; 63 | } 64 | 65 | this.setDescription = (message, onSuccess, onFailure) => { 66 | offer = message.body; 67 | roomName = message.getHeader('X-Room'); 68 | console.log('Setdescription called for dialog: ' + sessionId + ' to room: ' + roomName); 69 | onSuccess(); 70 | } 71 | 72 | return this; 73 | } 74 | }); 75 | 76 | ua.on('invite', session => { 77 | session.accept(); 78 | }); 79 | 80 | 81 | function getKurentoClient(callback) { 82 | if (kurentoClient !== null) { 83 | console.log('KurentoClient already created'); 84 | return callback(null, kurentoClient); 85 | } 86 | 87 | kurento(ws_uri, (error, _kurentoClient) => { 88 | if (error) { 89 | console.log('Coult not find media server at address ' + ws_uri); 90 | return callback('Could not find media server at address' + ws_uri + 91 | '. Exiting with error ' + error); 92 | } 93 | console.log('Created KurentoClient, connected to: ' + ws_uri); 94 | kurentoClient = _kurentoClient; 95 | callback(null, kurentoClient); 96 | }); 97 | } 98 | 99 | // Retrieve or create mediaPipeline 100 | function prepareMediaPipeline(id, callback) { 101 | getKurentoClient((error, _kurentoClient) => { 102 | if (error) { 103 | return callback(error); 104 | } 105 | 106 | _kurentoClient.create('MediaPipeline', (error, _pipeline) => { 107 | if (error) { 108 | return callback(error); 109 | } 110 | console.log('Created MediaPipeline for room: ' + rooms[id].room + ' with ID: ' + id); 111 | 112 | rooms[id].MediaPipeline = _pipeline; 113 | rooms[id].MediaPipeline.create('Composite', (error, _composite) => { 114 | if (error) { 115 | stop(id); 116 | return callback(error); 117 | } 118 | console.log('Created Composite Hub for room: ' + rooms[id].room + ' with ID: ' + id); 119 | 120 | rooms[id].Composite = _composite; 121 | callback(null); 122 | }); 123 | }); 124 | }); 125 | } 126 | 127 | function createSIPEndpoint(id, callback) { 128 | rooms[id].Composite.createHubPort((error, _hubPort) => { 129 | if (error) { 130 | return callback(error); 131 | } 132 | console.log('Created HubPort for WebRtcEndpoint in room: ' + rooms[id].room + ' with ID: ' + id); 133 | 134 | rooms[id].SIPClient.HubPort = _hubPort; 135 | rooms[id].MediaPipeline.create('WebRtcEndpoint', (error, _webRtcEndpoint) => { 136 | if (error) { 137 | stop(id); 138 | return callback(error); 139 | } 140 | console.log('Created WebRtcEndpoint in room: ' + rooms[id].room + ' with ID: ' + id); 141 | 142 | rooms[id].SIPClient.WebRtcEndpoint = _webRtcEndpoint; 143 | 144 | rooms[id].SIPClient.HubPort.connect(rooms[id].SIPClient.WebRtcEndpoint, error => { 145 | if (error) { 146 | stop(id); 147 | console.log('Error connecting ' + error); 148 | return callback(error); 149 | } 150 | console.log('Connected HubPort to WebRtcEndpoint in room: ' + rooms[id].room + ' with ID: ' + id); 151 | 152 | callback(null); 153 | }); 154 | }); 155 | }); 156 | } 157 | 158 | function createWebRtcEndpoint(id, pid, callback) { 159 | rooms[id].MediaPipeline.create('WebRtcEndpoint', (error, _webrtcEndpoint) => { 160 | if (error) { 161 | removeParticipant(id, pid); 162 | return callback(error); 163 | } 164 | console.info('Created WebRtcEndpoint in room: ' + rooms[id].room + ' with ID: ' + id); 165 | 166 | rooms[id].WebRTCClients[pid].WebRTCEndpoint = _webrtcEndpoint; 167 | 168 | if (rooms[id].WebRTCClients[pid].iceCandidateQueue) { 169 | while (rooms[id].WebRTCClients[pid].iceCandidateQueue.length) { 170 | let iceCandidate = rooms[id].WebRTCClients[pid].iceCandidateQueue.shift(); 171 | rooms[id].WebRTCClients[pid].WebRTCEndpoint.addIceCandidate(iceCandidate.candidate); 172 | } 173 | delete rooms[id].WebRTCClients[pid].WebRTCEndpoint.iceCandidateQueue; 174 | } 175 | 176 | rooms[id].WebRTCClients[pid].WebRTCEndpoint.on('IceCandidateFound', event => { 177 | let candidate = kurento.register.complexTypes.IceCandidate(event.candidate); 178 | rooms[id].RoomSocket.emit('iceCandidate', { 179 | pid: pid, 180 | candidate: candidate 181 | }); 182 | }); 183 | 184 | rooms[id].Composite.createHubPort((error, _hubPort) => { 185 | if (error) { 186 | removeParticipant(id, pid); 187 | return callback(error); 188 | } 189 | console.info('Created HubPort for WebRtcEndpoint in room: ' + rooms[id].room + ' with ID: ' + id); 190 | 191 | rooms[id].WebRTCClients[pid].HubPort = _hubPort; 192 | 193 | rooms[id].WebRTCClients[pid].WebRTCEndpoint.connect(rooms[id].WebRTCClients[pid].HubPort, error => { 194 | if (error) { 195 | removeParticipant(id, pid); 196 | return callback(error); 197 | } 198 | console.log('Connected WebRtcEndpoint to HubPort in room: ' + rooms[id].room + ' with ID: ' + id); 199 | 200 | rooms[id].SIPClient.WebRtcEndpoint.connect(rooms[id].WebRTCClients[pid].WebRTCEndpoint, error => { 201 | if (error) { 202 | removeParticipant(id, pid); 203 | return callback(error); 204 | } 205 | console.log('Connected WebRtcEndpoint to WebRtcEndpoint: ' + pid + ' in room: ' + rooms[id].room + ' with ID: ' + id); 206 | callback(null); 207 | }); 208 | }); 209 | }); 210 | }); 211 | } 212 | 213 | function createRoomSocket(id) { 214 | rooms[id].RoomSocket = io.connect(io_uri); 215 | rooms[id].RoomSocket.on('connection', socket => { 216 | console.log('RoomSocket connected to ' + io_uri + '/' + rooms[id].room); 217 | }); 218 | 219 | rooms[id].RoomSocket.on('sdp', msg => { 220 | console.log('RoomSocket received sdpOffer from: ', msg.pid); 221 | if (msg.sdp.type == 'offer') { 222 | if (typeof(rooms[id].WebRTCClients[msg.pid]) == 'undefined') { 223 | rooms[id].WebRTCClients[msg.pid] = {}; 224 | } 225 | 226 | createWebRtcEndpoint(id, msg.pid, error => { 227 | if (error) { 228 | console.log('Error creating WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 229 | removeParticipant(id, msg.pid); 230 | return error; 231 | } 232 | 233 | rooms[id].WebRTCClients[msg.pid].WebRTCEndpoint.processOffer(msg.sdp.sdp, (error, sdpAnswer) => { 234 | if (error) { 235 | console.log('Error processing offer for WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 236 | removeParticipant(id, msg.pid); 237 | return error; 238 | } 239 | 240 | rooms[id].RoomSocket.emit('sdp', { 241 | pid: msg.pid, 242 | sdp: { 243 | sdp: sdpAnswer, 244 | type: 'answer' 245 | } 246 | }); 247 | 248 | rooms[id].WebRTCClients[msg.pid].WebRTCEndpoint.gatherCandidates(error => { 249 | if (error) { 250 | console.log('Error gathering ICE for WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 251 | return error; 252 | } 253 | }); 254 | }); 255 | }); 256 | } else if (msg.sdp.type == 'answer') { 257 | rooms[id].WebRTCClients[msg.pid].WebRTCEndpoint.processAnswer(msg.sdp.sdp, error => { 258 | if (error) { 259 | console.log('Error processing answer for WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 260 | removeParticipant(id, msg.pid); 261 | return error; 262 | } 263 | }); 264 | } 265 | }); 266 | 267 | rooms[id].RoomSocket.on('iceCandidate', msg => { 268 | if (typeof(rooms[id].WebRTCClients[msg.pid]) == 'undefined') { 269 | rooms[id].WebRTCClients[msg.pid] = {}; 270 | } 271 | 272 | console.log('RoomSocket got iceCandidate from %s: %s', msg.pid, msg.candidate); 273 | let candidate = kurento.register.complexTypes.IceCandidate(msg.candidate); 274 | if (rooms[id].WebRTCClients[msg.pid].WebRTCEndpoint) { 275 | rooms[id].WebRTCClients[msg.pid].WebRTCEndpoint.addIceCandidate(candidate); 276 | } else { 277 | if (typeof(rooms[id].WebRTCClients[msg.pid].iceCandidateQueue) == 'undefined') { 278 | rooms[id].WebRTCClients[msg.pid].iceCandidateQueue = []; 279 | } 280 | rooms[id].WebRTCClients[msg.pid].iceCandidateQueue.push({ 281 | candidate: candidate 282 | }); 283 | } 284 | }); 285 | 286 | rooms[id].RoomSocket.on('participantReady', msg => { 287 | if (typeof(rooms[id].WebRTCClients[msg.pid]) == 'undefined') { 288 | rooms[id].WebRTCClients[msg.pid] = {}; 289 | } 290 | console.log('RoomSocket got participantReady: ', msg.pid); 291 | 292 | createWebRtcEndpoint(id, msg.pid, error => { 293 | if (error) { 294 | console.log('Error creating WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 295 | removeParticipant(id, msg.pid); 296 | return error; 297 | } 298 | 299 | rooms[id].WebRTCClients[msg.pid].WebRTCEndpoint.generateOffer((error, sdpOffer) => { 300 | if (error) { 301 | console.log('Error generating offer for WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 302 | removeParticipant(id, msg.pid); 303 | return error; 304 | } 305 | 306 | rooms[id].RoomSocket.emit('sdp', { 307 | pid: msg.pid, 308 | sdp: { 309 | sdp: sdpOffer, 310 | type: 'offer' 311 | } 312 | }); 313 | 314 | rooms[id].WebRTCClients[msg.pid].WebRTCEndpoint.gatherCandidates(error => { 315 | if (error) { 316 | console.log('Error gathering ICE for WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 317 | return error; 318 | } 319 | }); 320 | }); 321 | }); 322 | }); 323 | 324 | rooms[id].RoomSocket.on('bye', msg => { 325 | console.log('Got bye from WebRtcEndpoint: ' + msg.pid + ' in room: ' + rooms[id].room + ' with ID: ' + id); 326 | removeParticipant(id, msg.pid); 327 | }); 328 | 329 | rooms[id].RoomSocket.on('participantDied', msg => { 330 | console.log('WebRtcEndpoint: ' + msg.pid + ' died in room: ' + rooms[id].room + ' with ID: ' + id); 331 | removeParticipant(id, msg.pid); 332 | }); 333 | 334 | console.log('Emitting ready in room: ' + rooms[id].room + ' with ID: ' + id); 335 | rooms[id].RoomSocket.emit('ready', rooms[id].room); 336 | } 337 | 338 | function joinRoom(room, id, sdpOffer, callback) { 339 | rooms[id] = { 340 | room: room, 341 | SIPClient: { 342 | WebRtcEndpoint: null, 343 | HubPort: null, 344 | sdp: sdpOffer 345 | }, 346 | WebRTCClients: {}, 347 | MediaPipeline: null, 348 | Composite: null, 349 | RoomSocket: null 350 | } 351 | 352 | prepareMediaPipeline(id, error => { 353 | if (error) { 354 | console.log('Error preparing MediaPipeline in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 355 | stop(id); 356 | return callback(error); 357 | } 358 | 359 | createSIPEndpoint(id, error => { 360 | if (error) { 361 | console.log('Error creating SIPEndpoint in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 362 | stop(id); 363 | return callback(error); 364 | } 365 | 366 | rooms[id].SIPClient.WebRtcEndpoint.on('IceCandidateFound', event => { 367 | event.candidate.candidate = 'a=' + event.candidate.candidate; 368 | 369 | rooms[id].SIPClient.sdp.addIceCandidate(event.candidate); 370 | }); 371 | 372 | rooms[id].SIPClient.WebRtcEndpoint.on('IceGatheringDone', event => { 373 | console.info('gathering done'); 374 | 375 | callback(null, rooms[id].SIPClient.sdp.toString()); 376 | }); 377 | 378 | rooms[id].SIPClient.WebRtcEndpoint.processOffer(sdpOffer, (error, sdpAnswer) => { 379 | if (error) { 380 | stop(id); 381 | console.log('Error processing offer from WebRtcEndpoint in room: ' + rooms[id].room + ' with ID: ' + id + ' error: ' + error); 382 | return callback(error); 383 | } 384 | 385 | rooms[id].SIPClient.sdp = sdpparser(sdpAnswer); 386 | 387 | createRoomSocket(id); 388 | 389 | rooms[id].SIPClient.WebRtcEndpoint.gatherCandidates(error => { 390 | if (error) { 391 | return error; 392 | } 393 | 394 | console.info('gathering candidates'); 395 | }); 396 | }); 397 | }); 398 | }); 399 | } 400 | 401 | function removeParticipant(id, pid) { 402 | if (rooms[id].WebRTCClients) { 403 | if (rooms[id].WebRTCClients[pid]) { 404 | if (rooms[id].WebRTCClients[pid].WebRTCEndpoint) { 405 | console.log('Releasing WebRtcEndpoint: ' + pid + ' in room: ' + rooms[id].room + ' with ID: ' + id); 406 | rooms[id].WebRTCClients[pid].WebRTCEndpoint.release(); 407 | } 408 | 409 | if (rooms[id].WebRTCClients[pid].HubPort) { 410 | console.log('Releasing WebRtcEndpoint HubPort: ' + pid + ' in room: ' + rooms[id].room + ' with ID: ' + id); 411 | rooms[id].WebRTCClients[pid].HubPort.release(); 412 | } 413 | 414 | delete rooms[id].WebRTCClients[pid]; 415 | } 416 | } 417 | } 418 | 419 | function stop(id) { 420 | if (rooms[id]) { 421 | if (rooms[id].RoomSocket) { 422 | console.log('Emitting bye in room: ' + rooms[id].room + ' with ID: ' + id); 423 | rooms[id].RoomSocket.emit('bye'); 424 | rooms[id].RoomSocket.disconnect(); 425 | } 426 | 427 | if (rooms[id].SIPClient.WebRtcEndpoint) { 428 | console.log('Releasing WebRtcEndpoint: ' + id + ' in room: ' + rooms[id].room); 429 | rooms[id].SIPClient.WebRtcEndpoint.release(); 430 | } 431 | 432 | if (rooms[id].SIPClient.HubPort) { 433 | console.log('Releasing WebRtcEndpoint HubPort: ' + id + ' in room: ' + rooms[id].room); 434 | rooms[id].SIPClient.HubPort.release(); 435 | } 436 | 437 | if (rooms[id].WebRTCClients) { 438 | while (rooms[id].WebRTCClients.length) { 439 | let WebRTCClient = rooms[id].WebRTCClients.shift(); 440 | 441 | if (WebRTCClient.WebRTCEndpoint) { 442 | console.log('Releasing WebRtcEndpoint: ' + id + ' in room: ' + rooms[id].room); 443 | WebRTCClient.WebRTCEndpoint.release(); 444 | } 445 | 446 | if (WebRTCClient.HubPort) { 447 | console.log('Releasing WebRtcEndpoint HubPort: ' + id + ' in room: ' + rooms[id].room); 448 | WebRTCClient.HubPort.release(); 449 | } 450 | } 451 | } 452 | 453 | if (rooms[id].Composite) { 454 | console.log('Releasing Composite: ' + id + ' in room: ' + rooms[id].room); 455 | rooms[id].Composite.release(); 456 | } 457 | 458 | if (rooms[id].MediaPipeline) { 459 | console.log('Releasing MediaPipeline: ' + id + ' in room: ' + rooms[id].room); 460 | rooms[id].MediaPipeline.release(); 461 | } 462 | 463 | delete rooms[id]; 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /config/kamailio.cfg: -------------------------------------------------------------------------------- 1 | #!KAMAILIO 2 | # 3 | 4 | #!define WITH_MYSQL 5 | #!define WITH_AUTH 6 | #!define WITH_USRLOCDB 7 | #!define WITH_TLS 8 | #!define WITH_HOMER 9 | #!define WITH_WEBSOCKETS 10 | #!define WITH_ANTIFLOOD 11 | 12 | #!substdef "!MY_IP_ADDR!XXXXX-XXXXX!g" 13 | #!substdef "!MY_DOMAIN!XXXX-XXXX!g" 14 | #!substdef "!MY_WS_PORT!80!g" 15 | #!substdef "!MY_WSS_PORT!443!g" 16 | #!substdef "!MY_WS_ADDR!tcp:MY_IP_ADDR:MY_WS_PORT!g" 17 | #!substdef "!MY_WSS_ADDR!tls:MY_IP_ADDR:MY_WSS_PORT!g" 18 | 19 | # *** Value defines - IDs used later in config 20 | #!ifdef WITH_MYSQL 21 | # - database URL - used to connect to database server by modules such 22 | # as: auth_db, acc, usrloc, a.s.o. 23 | #!ifndef DBURL 24 | #!define DBURL "mysql://kamailio:kamailiorw@localhost/kamailio" 25 | #!endif 26 | #!endif 27 | 28 | # - flags 29 | # FLT_ - per transaction (message) flags 30 | # FLB_ - per branch flags 31 | #!define FLT_NATS 5 32 | 33 | #!define FLB_NATB 6 34 | #!define FLB_NATSIPPING 7 35 | 36 | ####### Global Parameters ######### 37 | 38 | ### LOG Levels: 3=DBG, 2=INFO, 1=NOTICE, 0=WARN, -1=ERR 39 | #!ifdef WITH_DEBUG 40 | debug=4 41 | log_stderror=no 42 | #!else 43 | debug=2 44 | log_stderror=no 45 | #!endif 46 | 47 | memdbg=5 48 | memlog=5 49 | 50 | log_facility=LOG_LOCAL0 51 | 52 | fork=yes 53 | children=4 54 | 55 | /* port to listen to 56 | * - can be specified more than once if needed to listen on many ports */ 57 | port=5060 58 | 59 | #!ifdef WITH_TLS 60 | enable_tls=yes 61 | #!endif 62 | 63 | listen=MY_IP_ADDR 64 | #!ifdef WITH_WEBSOCKETS 65 | #listen=MY_WS_ADDR 66 | #!ifdef WITH_TLS 67 | listen=MY_WSS_ADDR 68 | #!endif 69 | #!endif 70 | 71 | use_dns_cache = on # Use KAMAILIO internal DNS cache 72 | use_dns_failover = on # Depends on KAMAILIO internal DNS cache 73 | dns_srv_loadbalancing = on # 74 | dns_try_naptr = on # 75 | dns_retr_time=1 # Time in seconds before retrying a DNS request 76 | dns_retr_no=3 # Number of DNS retransmissions before giving up 77 | 78 | # Set protocol preference order - ignore target priority 79 | dns_naptr_ignore_rfc= yes # Ignore target NAPTR priority 80 | dns_tls_pref=50 # First priority: TLS 81 | dns_tcp_pref=30 # Second priority: TCP 82 | dns_udp_pref=10 # Third priority: UDP 83 | 84 | tcp_connection_lifetime=3604 85 | tcp_accept_no_cl=yes 86 | tcp_rd_buf_size=16384 87 | 88 | 89 | # set paths to location of modules (to sources or installation folders) 90 | #!ifdef WITH_SRCPATH 91 | mpath="modules/" 92 | #!else 93 | mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules/" 94 | #!endif 95 | 96 | #!ifdef WITH_MYSQL 97 | loadmodule "db_mysql.so" 98 | #!endif 99 | 100 | loadmodule "mi_fifo.so" 101 | loadmodule "kex.so" 102 | loadmodule "corex.so" 103 | loadmodule "tm.so" 104 | loadmodule "tmx.so" 105 | loadmodule "sl.so" 106 | loadmodule "rr.so" 107 | loadmodule "pv.so" 108 | loadmodule "maxfwd.so" 109 | loadmodule "usrloc.so" 110 | loadmodule "registrar.so" 111 | loadmodule "textops.so" 112 | loadmodule "siputils.so" 113 | loadmodule "xlog.so" 114 | loadmodule "sanity.so" 115 | loadmodule "ctl.so" 116 | loadmodule "cfg_rpc.so" 117 | loadmodule "mi_rpc.so" 118 | loadmodule "sdpops.so" 119 | loadmodule "textopsx.so" 120 | 121 | #!ifdef WITH_AUTH 122 | loadmodule "auth.so" 123 | loadmodule "auth_db.so" 124 | #!ifdef WITH_IPAUTH 125 | loadmodule "permissions.so" 126 | #!endif 127 | #!endif 128 | 129 | #!ifdef WITH_PRESENCE 130 | loadmodule "presence.so" 131 | loadmodule "presence_xml.so" 132 | #!endif 133 | 134 | #!ifdef WITH_TLS 135 | loadmodule "tls.so" 136 | #!endif 137 | 138 | #!ifdef WITH_HOMER 139 | loadmodule "siptrace.so" 140 | #!endif 141 | 142 | #!ifdef WITH_WEBSOCKETS 143 | loadmodule "xhttp.so" 144 | loadmodule "websocket.so" 145 | loadmodule "nathelper.so" 146 | loadmodule "rtpengine.so" 147 | #!endif 148 | 149 | #!ifdef WITH_ANTIFLOOD 150 | loadmodule "htable.so" 151 | loadmodule "pike.so" 152 | #!endif 153 | 154 | #!ifdef WITH_DEBUG 155 | loadmodule "debugger.so" 156 | #!endif 157 | 158 | # ----------------- setting module-specific parameters --------------- 159 | 160 | 161 | # ----- mi_fifo params ----- 162 | modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo") 163 | 164 | # ----- rr params ----- 165 | # add value to ;lr param to cope with most of the UAs 166 | modparam("rr", "enable_full_lr", 1) 167 | # do not append from tag to the RR (no need for this script) 168 | modparam("rr", "append_fromtag", 0) 169 | 170 | 171 | # ----- registrar params ----- 172 | modparam("registrar", "method_filtering", 1) 173 | # max value for expires of registrations 174 | modparam("registrar", "max_expires", 3600) 175 | 176 | 177 | # ----- usrloc params ----- 178 | /* enable DB persistency for location entries */ 179 | #!ifdef WITH_USRLOCDB 180 | modparam("usrloc", "db_url", DBURL) 181 | modparam("usrloc", "db_mode", 2) 182 | #!endif 183 | 184 | 185 | # ----- auth_db params ----- 186 | #!ifdef WITH_AUTH 187 | modparam("auth_db", "db_url", DBURL) 188 | modparam("auth_db", "calculate_ha1", 1) 189 | modparam("auth_db", "password_column", "password") 190 | modparam("auth_db", "load_credentials", "") 191 | #!endif 192 | 193 | #!ifdef WITH_PRESENCE 194 | # ----- presence params ----- 195 | modparam("presence", "db_url", DBURL) 196 | 197 | # ----- presence_xml params ----- 198 | modparam("presence_xml", "db_url", DBURL) 199 | modparam("presence_xml", "force_active", 1) 200 | #!endif 201 | 202 | 203 | ##!ifdef WITH_NAT 204 | # ----- rtpproxy params ----- 205 | modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:22222") 206 | 207 | # ----- nathelper params ----- 208 | modparam("nathelper", "natping_interval", 30) 209 | modparam("nathelper", "ping_nated_only", 1) 210 | modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) 211 | modparam("nathelper", "sipping_from", "sip:pinger@XXXX-XXXX") 212 | modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") 213 | modparam("usrloc", "nat_bflag", FLB_NATB) 214 | ##!endif 215 | 216 | # ----- corex params ----- 217 | modparam("corex", "alias_subdomains", "MY_DOMAIN") 218 | 219 | #!ifdef WITH_TLS 220 | # ----- tls params ----- 221 | modparam("tls", "config", "/etc/kamailio/tls.cfg") 222 | #!endif 223 | 224 | #!ifdef WITH_WEBSOCKETS 225 | modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") 226 | #!endif 227 | 228 | #!ifdef WITH_HOMER 229 | #Siptrace 230 | modparam("siptrace", "duplicate_uri", "sip:127.0.0.1:9060") 231 | modparam("siptrace", "hep_mode_on", 1) 232 | modparam("siptrace", "trace_to_database", 0) 233 | modparam("siptrace", "trace_flag", 22) 234 | modparam("siptrace", "trace_on", 1) 235 | #!endif 236 | 237 | #!ifdef WITH_ANTIFLOOD 238 | # ----- pike params ----- 239 | modparam("pike", "sampling_time_unit", 2) 240 | modparam("pike", "reqs_density_per_unit", 16) 241 | modparam("pike", "remove_latency", 4) 242 | 243 | # ----- htable params ----- 244 | # ip ban htable with autoexpire after 5 minutes 245 | modparam("htable", "htable", "ipban=>size=8;autoexpire=300;") 246 | #!endif 247 | 248 | #!ifdef WITH_DEBUG 249 | # ----- debugger params ----- 250 | modparam("debugger", "cfgtrace", 1) 251 | #!endif 252 | 253 | ####### Routing Logic ######## 254 | request_route { 255 | 256 | #!ifdef WITH_HOMER 257 | # start duplicate the SIP message here 258 | sip_trace(); 259 | setflag(22); 260 | #!endif 261 | 262 | # per request initial checks 263 | route(REQINIT); 264 | 265 | #!ifdef WITH_WEBSOCKETS 266 | if (nat_uac_test(64)) { 267 | # Do NAT traversal stuff for requests from a WebSocket 268 | # connection - even if it is not behind a NAT! 269 | # This won't be needed in the future if Kamailio and the 270 | # WebSocket client support Outbound and Path. 271 | force_rport(); 272 | if (is_method("REGISTER")) { 273 | fix_nated_register(); 274 | } else { 275 | if (!add_contact_alias()) { 276 | xlog("L_ERR", "Error aliasing contact <$ct>\n"); 277 | sl_send_reply("400", "Bad Request"); 278 | exit; 279 | } 280 | } 281 | } 282 | #!endif 283 | 284 | route(POINT_TO_MCU); 285 | 286 | # NAT detection 287 | route(NATDETECT); 288 | 289 | # CANCEL processing 290 | if (is_method("CANCEL")) { 291 | if (t_check_trans()) { 292 | route(RELAY); 293 | } 294 | exit; 295 | } 296 | 297 | # handle requests within SIP dialogs 298 | route(WITHINDLG); 299 | 300 | ### only initial requests (no To tag) 301 | 302 | t_check_trans(); 303 | 304 | # authentication 305 | route(AUTH); 306 | 307 | # record routing for dialog forming requests (in case they are routed) 308 | # - remove preloaded route headers 309 | remove_hf("Route"); 310 | if (is_method("INVITE|SUBSCRIBE")) { 311 | record_route(); 312 | } 313 | 314 | # dispatch requests to foreign domains 315 | route(SIPOUT); 316 | 317 | ### requests for my local domains 318 | 319 | # handle presence related requests 320 | route(PRESENCE); 321 | 322 | # handle registrations 323 | route(REGISTRAR); 324 | 325 | if ($rU==$null) { 326 | # request with no Username in RURI 327 | sl_send_reply("484","Address Incomplete"); 328 | exit; 329 | } 330 | 331 | # user location service 332 | route(LOCATION); 333 | } 334 | 335 | # Wrapper for relaying requests 336 | route[RELAY] { 337 | # enable additional event routes for forwarded requests 338 | # - serial forking, RTP relaying handling, a.s.o. 339 | if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) { 340 | if(!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH"); 341 | } 342 | 343 | if (is_method("INVITE|SUBSCRIBE|UPDATE")) { 344 | if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY"); 345 | } 346 | 347 | if (is_method("INVITE")) { 348 | if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE"); 349 | } 350 | 351 | if (!t_relay()) { 352 | sl_reply_error(); 353 | } 354 | exit; 355 | } 356 | 357 | # Per SIP request initial checks 358 | route[REQINIT] { 359 | #!ifdef WITH_ANTIFLOOD 360 | # flood dection from same IP and traffic ban for a while 361 | # be sure you exclude checking trusted peers, such as pstn gateways 362 | # - local host excluded (e.g., loop to self) 363 | if(src_ip!=myself) { 364 | if($sht(ipban=>$si)!=$null) { 365 | # ip is already blocked 366 | xdbg("request from blocked IP - $rm from $fu (IP:$si:$sp)\n"); 367 | exit; 368 | } 369 | 370 | if (!pike_check_req()) { 371 | xlog("L_ALERT","ALERT: pike blocking $rm from $fu (IP:$si:$sp)\n"); 372 | $sht(ipban=>$si) = 1; 373 | exit; 374 | } 375 | } 376 | #!endif 377 | 378 | if (!mf_process_maxfwd_header("10")) { 379 | sl_send_reply("483","Too Many Hops"); 380 | exit; 381 | } 382 | 383 | if(!sanity_check("1511", "7")) { 384 | xlog("Malformed SIP message from $si:$sp\n"); 385 | exit; 386 | } 387 | } 388 | 389 | # Handle requests within SIP dialogs 390 | route[WITHINDLG] { 391 | if (has_totag()) { 392 | # sequential request withing a dialog should 393 | # take the path determined by record-routing 394 | if (loose_route()) { 395 | #!ifdef WITH_WEBSOCKETS 396 | if ($du == "") { 397 | if (!handle_ruri_alias()) { 398 | xlog("L_ERR", "Bad alias <$ru>\n"); 399 | sl_send_reply("400", "Bad Request"); 400 | exit; 401 | } 402 | } 403 | #!endif 404 | route(DLGURI); 405 | if (is_method("ACK")) { 406 | # ACK is forwarded statelessy 407 | route(NATMANAGE); 408 | } else if (is_method("NOTIFY")) { 409 | # Add Record-Route for in-dialog NOTIFY as per RFC 6665. 410 | record_route(); 411 | } 412 | route(RELAY); 413 | } else { 414 | if (is_method("SUBSCRIBE") && uri == myself) { 415 | # in-dialog subscribe requests 416 | route(PRESENCE); 417 | exit; 418 | } 419 | if (is_method("ACK")) { 420 | if (t_check_trans()) { 421 | # no loose-route, but stateful ACK; 422 | # must be an ACK after a 487 423 | # or e.g. 404 from upstream server 424 | route(RELAY); 425 | exit; 426 | } else { 427 | # ACK without matching transaction ... ignore and discard 428 | exit; 429 | } 430 | } 431 | sl_send_reply("404","Not here"); 432 | } 433 | exit; 434 | } 435 | } 436 | 437 | route[POINT_TO_MCU] { 438 | if (is_method("INVITE|SUBSCRIBE|UPDATE")) { 439 | append_hf("X-Room: $rU\r\n", "Contact"); 440 | $rU = "mcu"; 441 | $tU = "mcu"; 442 | } 443 | } 444 | 445 | # Handle SIP registrations 446 | route[REGISTRAR] { 447 | if (is_method("REGISTER")) { 448 | if(isflagset(FLT_NATS)) { 449 | setbflag(FLB_NATB); 450 | # uncomment next line to do SIP NAT pinging 451 | ## setbflag(FLB_NATSIPPING); 452 | } 453 | 454 | if (!save("location")) { 455 | sl_reply_error(); 456 | } 457 | 458 | exit; 459 | } 460 | } 461 | 462 | # USER location service 463 | route[LOCATION] { 464 | $avp(oexten) = $rU; 465 | if (!lookup("location")) { 466 | $var(rc) = $rc; 467 | t_newtran(); 468 | switch ($var(rc)) { 469 | case -1: 470 | case -3: 471 | send_reply("404", "Not Found"); 472 | exit; 473 | case -2: 474 | send_reply("405", "Method Not Allowed"); 475 | exit; 476 | } 477 | } 478 | 479 | route(RELAY); 480 | exit; 481 | } 482 | 483 | # Presence server route 484 | route[PRESENCE] { 485 | if(!is_method("PUBLISH|SUBSCRIBE")) { 486 | return; 487 | } 488 | 489 | if(is_method("SUBSCRIBE") && $hdr(Event)=="message-summary") { 490 | # returns here if no voicemail server is configured 491 | sl_send_reply("404", "No voicemail service"); 492 | exit; 493 | } 494 | 495 | #!ifdef WITH_PRESENCE 496 | if (!t_newtran()) { 497 | sl_reply_error(); 498 | exit; 499 | } 500 | 501 | if(is_method("PUBLISH")) { 502 | handle_publish(); 503 | t_release(); 504 | } else if(is_method("SUBSCRIBE")) { 505 | handle_subscribe(); 506 | t_release(); 507 | } 508 | exit; 509 | #!endif 510 | 511 | # if presence enabled, this part will not be executed 512 | if (is_method("PUBLISH") || $rU==$null) { 513 | sl_send_reply("404", "Not here"); 514 | exit; 515 | } 516 | return; 517 | } 518 | 519 | # Authentication route 520 | route[AUTH] { 521 | #!ifdef WITH_AUTH 522 | 523 | if (is_method("REGISTER") || from_uri==myself) { 524 | # authenticate requests 525 | if (!auth_check("$fd", "subscriber", "1")) { 526 | auth_challenge("$fd", "0"); 527 | exit; 528 | } 529 | # user authenticated - remove auth header 530 | if(!is_method("REGISTER|PUBLISH")) { 531 | consume_credentials(); 532 | } 533 | } 534 | # if caller is not local subscriber, then check if it calls 535 | # a local destination, otherwise deny, not an open relay here 536 | if (from_uri!=myself && uri!=myself) { 537 | sl_send_reply("403","Not relaying"); 538 | exit; 539 | } 540 | 541 | #!endif 542 | return; 543 | } 544 | 545 | # Caller NAT detection route 546 | route[NATDETECT] { 547 | force_rport(); 548 | if (nat_uac_test("19")) { 549 | if (is_method("REGISTER")) { 550 | fix_nated_register(); 551 | } else { 552 | if(is_first_hop()) { 553 | set_contact_alias(); 554 | } 555 | } 556 | setflag(FLT_NATS); 557 | } 558 | return; 559 | } 560 | 561 | # NAT handling 562 | route[NATMANAGE] { 563 | if (is_request()) { 564 | if(has_totag()) { 565 | if(check_route_param("nat=yes")) { 566 | setbflag(FLB_NATB); 567 | } 568 | } 569 | } 570 | 571 | if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB))) { 572 | return; 573 | } 574 | 575 | if (is_request()) { 576 | if (!has_totag()) { 577 | if(t_is_branch_route()) { 578 | add_rr_param(";nat=yes"); 579 | } 580 | } 581 | } 582 | 583 | if (is_reply()) { 584 | if(isbflagset(FLB_NATB)) { 585 | if(is_first_hop()) { 586 | set_contact_alias(); 587 | } 588 | } 589 | } 590 | return; 591 | } 592 | 593 | # URI update for dialog requests 594 | route[DLGURI] { 595 | if(!isdsturiset()) { 596 | handle_ruri_alias(); 597 | } 598 | return; 599 | } 600 | 601 | # Routing to foreign domains 602 | route[SIPOUT] { 603 | if (!uri==myself) { 604 | append_hf("P-hint: outbound\r\n"); 605 | route(RELAY); 606 | } 607 | } 608 | 609 | route[SETUP_BRIDGING] { 610 | # if(!has_totag()) { 611 | if ($proto =~ "ws") { # Coming from websocket 612 | if ($ru =~ "transport=ws") { # WebRTC > WebRTC 613 | xlog("L_INFO", "WebRTC > WebRTC"); 614 | rtpengine_manage("trust-address replace-origin replace-session-connection ICE=force"); 615 | t_on_reply("REPLY_WS_TO_WS"); 616 | } else { # WebRTC > SIP 617 | xlog("L_INFO", "WebRTC > SIP"); 618 | rtpengine_manage("trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove RTP/AVP"); 619 | t_on_reply("REPLY_WS_TO_SIP"); 620 | } 621 | } else { # Coming from SIP 622 | if ($ru =~ "transport=ws") { # SIP > WebRTC 623 | xlog("L_INFO", "SIP > WebRTC"); 624 | 625 | if(nat_uac_test("8")) { 626 | rtpengine_manage("replace-origin replace-session-connection rtcp-mux-accept rtcp-mux-offer ICE=force RTP/SAVPF"); 627 | } else { 628 | rtpengine_manage("trust-address replace-origin replace-session-connection rtcp-mux-accept rtcp-mux-offer ICE=force RTP/SAVPF"); 629 | } 630 | 631 | t_on_reply("REPLY_SIP_TO_WS"); 632 | } else { # SIP > SIP 633 | xlog("L_INFO", "SIP > SIP"); 634 | 635 | if(nat_uac_test("8")) { 636 | rtpengine_manage("replace-origin replace-session-connection"); 637 | } else { 638 | rtpengine_manage("trust-address replace-origin replace-session-connection"); 639 | } 640 | 641 | t_on_reply("REPLY_SIP_TO_SIP"); 642 | } 643 | } 644 | # } 645 | } 646 | 647 | # manage outgoing branches 648 | branch_route[MANAGE_BRANCH] { 649 | xdbg("new branch [$T_branch_idx] to $ru\n"); 650 | route(SETUP_BRIDGING); 651 | route(NATMANAGE); 652 | } 653 | 654 | onreply_route[REPLY_WS_TO_WS] { 655 | xlog("L_INFO", "Reply from websocket to websocket: $rs"); 656 | 657 | if(status=~"[12][0-9][0-9]") { 658 | rtpengine_manage("trust-address replace-origin replace-session-connection ICE=force"); 659 | route(NATMANAGE); 660 | } 661 | } 662 | 663 | onreply_route[REPLY_WS_TO_SIP] { 664 | xlog("L_INFO", "Reply from SIP to websocket: $rs"); 665 | 666 | if (t_check_status("183")) { 667 | change_reply_status("180", "Ringing"); 668 | remove_body(); 669 | route(NATMANAGE); 670 | exit; 671 | } 672 | 673 | if(!(status=~"[12][0-9][0-9]") || !(sdp_content())) { 674 | return; 675 | } 676 | 677 | if(nat_uac_test("8")) { 678 | rtpengine_manage("replace-origin replace-session-connection rtcp-mux-accept rtcp-mux-offer ICE=force RTP/SAVPF"); 679 | } else { 680 | rtpengine_manage("trust-address replace-origin replace-session-connection rtcp-mux-accept rtcp-mux-offer ICE=force RTP/SAVPF"); 681 | } 682 | 683 | route(NATMANAGE); 684 | } 685 | 686 | onreply_route[REPLY_SIP_TO_WS] { 687 | xlog("L_INFO", "Reply from websocket to SIP: $rs"); 688 | 689 | if(status=~"[12][0-9][0-9]") { 690 | rtpengine_manage("trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove RTP/AVP"); 691 | route(NATMANAGE); 692 | } 693 | } 694 | 695 | onreply_route[REPLY_SIP_TO_SIP] { 696 | xlog("L_INFO", "Reply from SIP to SIP: $rs"); 697 | 698 | if(status=~"[12][0-9][0-9]") { 699 | if(nat_uac_test("8")) { 700 | rtpengine_manage("replace-origin replace-session-connection"); 701 | } else { 702 | rtpengine_manage("trust-address replace-origin replace-session-connection"); 703 | } 704 | route(NATMANAGE); 705 | } 706 | } 707 | 708 | # manage incoming replies 709 | onreply_route[MANAGE_REPLY] { 710 | xdbg("incoming reply\n"); 711 | if(status=~"[12][0-9][0-9]") { 712 | route(NATMANAGE); 713 | } 714 | } 715 | 716 | # manage failure routing cases 717 | failure_route[MANAGE_FAILURE] { 718 | xlog("L_INFO", "Failure: $rs"); 719 | } 720 | 721 | #!ifdef WITH_WEBSOCKETS 722 | onreply_route { 723 | if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT) 724 | && !(proto == WS || proto == WSS))) { 725 | xlog("L_WARN", "SIP response received on $Rp\n"); 726 | drop; 727 | } 728 | 729 | if (nat_uac_test(64)) { 730 | # Do NAT traversal stuff for replies to a WebSocket connection 731 | # - even if it is not behind a NAT! 732 | # This won't be needed in the future if Kamailio and the 733 | # WebSocket client support Outbound and Path. 734 | add_contact_alias(); 735 | } 736 | } 737 | 738 | event_route[xhttp:request] { 739 | set_reply_close(); 740 | set_reply_no_connect(); 741 | 742 | if ($Rp != MY_WS_PORT 743 | #!ifdef WITH_TLS 744 | && $Rp != MY_WSS_PORT 745 | #!endif 746 | ) { 747 | xlog("L_WARN", "HTTP request received on $Rp\n"); 748 | xhttp_reply("403", "Forbidden", "", ""); 749 | exit; 750 | } 751 | 752 | xlog("L_DBG", "HTTP Request Received\n"); 753 | 754 | if ($hdr(Upgrade)=~"websocket" 755 | && $hdr(Connection)=~"Upgrade" 756 | && $rm=~"GET") { 757 | 758 | # Validate Host - make sure the client is using the correct 759 | # alias for WebSockets 760 | if ($hdr(Host) == $null || !is_myself("sip:" + $hdr(Host))) { 761 | xlog("L_WARN", "Bad host $hdr(Host)\n"); 762 | xhttp_reply("403", "Forbidden", "", ""); 763 | exit; 764 | } 765 | 766 | # Optional... validate Origin - make sure the client is from an 767 | # authorised website. For example, 768 | # 769 | # if ($hdr(Origin) != "https://example.com" 770 | # && $hdr(Origin) != "https://example.com") { 771 | # xlog("L_WARN", "Unauthorised client $hdr(Origin)\n"); 772 | # xhttp_reply("403", "Forbidden", "", ""); 773 | # exit; 774 | # } 775 | 776 | # Optional... perform HTTP authentication 777 | 778 | # ws_handle_handshake() exits (no further configuration file 779 | # processing of the request) when complete. 780 | if (ws_handle_handshake()) { 781 | # Optional... cache some information about the 782 | # successful connection 783 | exit; 784 | } 785 | } 786 | 787 | xhttp_reply("404", "Not Found", "", ""); 788 | } 789 | 790 | event_route[websocket:closed] { 791 | xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n"); 792 | } 793 | #!endif 794 | -------------------------------------------------------------------------------- /images/sipnode.svg: -------------------------------------------------------------------------------- 1 | 2 |
Socket-signaling
Socket-signaling
WebRTC client
WebRTC client
Media
Media
Socket-signaling
Socket-signaling
WebRTC client
WebRTC client
Media
Media
Kurento Media Server
Kurento Media Server
WebRTCEndpoint
WebRTCEndpoint
WebRTCEndpoint
WebRTCEndpoint
Composite Hub
Composite Hub
WebRTCEndpoint
WebRTCEndpoint
JSON-RPC
JSON-RPC
Socket-signaling
Socket-signaling
SIPNode
SIPNode
Media
Media
SIP
SIP
SIP Client
SIP Client
Media
Media
Knockplop
Knockplop
Media
Media
Media
Media
Media
Media
Media
Media
--------------------------------------------------------------------------------