├── README.md ├── client ├── asset-manifest.json ├── config.js ├── favicon.ico ├── index.html ├── manifest.json ├── precache-manifest.2da2d0d268e58779c701c9f88d99aed8.js ├── service-worker.js └── static │ ├── css │ ├── main.5d4f940f.chunk.css │ └── main.5d4f940f.chunk.css.map │ ├── js │ ├── 1.56b88b18.chunk.js │ ├── 1.56b88b18.chunk.js.map │ ├── main.410ce508.chunk.js │ ├── main.410ce508.chunk.js.map │ ├── runtime~main.229c360f.js │ └── runtime~main.229c360f.js.map │ └── media │ └── background.5a9013a5.svg ├── etc ├── default │ ├── coturn │ ├── kamailio │ └── ngcp-rtpengine-daemon ├── kamailio │ ├── kamailio-old.cfg │ ├── kamailio.cfg │ ├── kamctlrc │ └── tls.cfg ├── network │ └── if-up.d │ │ └── iptables ├── nginx │ ├── conf.d │ │ └── default.conf │ ├── nginx.conf │ └── sites-available │ │ └── default ├── rtpengine │ └── rtpengine.conf └── turnserver.conf ├── images ├── webrtc-sip.png └── webrtc-sip.svg └── iptables.sh /README.md: -------------------------------------------------------------------------------- 1 | # WEBRTC to SIP client and server 2 | How to setup Kamailio + RTPEngine + TURN server to enable calling between WebRTC client and legacy SIP clients. This config is IPv6 enabled by default. This setup will bridge SRTP --> RTP and ICE --> nonICE to make a WebRTC client (sip.js) be able to call legacy SIP clients. The WebRTC client can be found [here](https://github.com/havfo/SipCaller). 3 | 4 | This setup is for Debian 12 Bookworm. 5 | 6 | This setup is configured to run with the following services: 7 | 8 | - Kamailio + RTPEngine + Nginx (proxy + WebRTC client) + coturn 9 | 10 | The configuration is setup to always bridge via RTPEngine. To change the behavior, take a look in the `NATMANAGE` route. 11 | 12 | ## Architecture 13 | ![WebRTC - SIP architecture](https://raw.githubusercontent.com/havfo/WEBRTC-to-SIP/master/images/webrtc-sip.png "WebRTC to SIP architecture") 14 | 15 | ## Get certificates 16 | For the certificates you need, a simple solution is Let's Encrypt certificates. They will work for both Kamailio TLS, Nginx TLS and TURN TLS. Run the following (you must stop services running on port 443 during certificate request/renewal): 17 | ```bash 18 | apt-get install certbot 19 | certbot certonly --standalone -d YOUR-DOMAIN 20 | ``` 21 | You will then find the certificates under: 22 | ```bash 23 | /etc/letsencrypt/live/YOUR-DOMAIN/privkey.pem 24 | /etc/letsencrypt/live/YOUR-DOMAIN/fullchain.pem 25 | ``` 26 | 27 | ## Get configuration files 28 | ```bash 29 | git clone https://github.com/havfo/WEBRTC-to-SIP.git 30 | cd WEBRTC-to-SIP 31 | find . -type f -print0 | xargs -0 sed -i 's/XXXXXX-XXXXXX/PUT-IPV6-OF-YOUR-SIP-SERVER-HERE/g' 32 | find . -type f -print0 | xargs -0 sed -i 's/XXXXX-XXXXX/PUT-IPV4-OF-YOUR-SIP-SERVER-HERE/g' 33 | find . -type f -print0 | xargs -0 sed -i 's/XXXX-XXXX/PUT-DOMAIN-OF-YOUR-SIP-SERVER-HERE/g' 34 | ``` 35 | 36 | ## Install RTPEngine 37 | This will do the SRTP-RTP bridging needed to make WebRTC clients talk to legacy SIP server/clients. You can find the latest build instructions in their [readme](https://github.com/sipwise/rtpengine#on-a-debian-system). 38 | 39 | The easiest way of installing is to get it from Sipwise repository: 40 | ```bash 41 | echo 'deb https://deb.sipwise.com/spce/mr11.5.1/ bookworm main' > /etc/apt/sources.list.d/sipwise.list 42 | echo 'deb-src https://deb.sipwise.com/spce/mr11.5.1/ bookworm main' >> /etc/apt/sources.list.d/sipwise.list 43 | wget -q -O - https://deb.sipwise.com/spce/keyring/sipwise-keyring-bootstrap.gpg | apt-key add - 44 | apt-get update 45 | apt-get install -y ngcp-keyring ngcp-rtpengine 46 | ``` 47 | 48 | After you have successfully installed RTPEngine, copy the configuration from this repository. 49 | ```bash 50 | cd WEBRTC-to-SIP 51 | cp etc/default/ngcp-rtpengine-daemon /etc/default/ 52 | cp etc/rtpengine/rtpengine.conf /etc/rtpengine/ 53 | /etc/init.d/ngcp-rtpengine-daemon restart 54 | ``` 55 | 56 | ## Install IPTables firewall (optional) 57 | RTPEngine handles the chain for itself, but make sure to not block the RTP-ports it is using. Take a look in iptables.sh for details, and apply it by doing the following. This will persist after reboot. You can run the iptables.sh script at any time after it is set up. 58 | ```bash 59 | cd WEBRTC-to-SIP 60 | chmod +x iptables.sh 61 | cp etc/network/if-up.d/iptables /etc/network/if-up.d/ 62 | chmod +x /etc/network/if-up.d/iptables 63 | touch /etc/iptables/firewall.conf 64 | touch /etc/iptables/firewall6.conf 65 | ./iptables.sh 66 | ``` 67 | 68 | ## Install Kamailio 69 | ```bash 70 | apt-get install kamailio kamailio-websocket-modules kamailio-mysql-modules kamailio-tls-modules kamailio-presence-modules mysql-server 71 | cd WEBRTC-to-SIP 72 | cp etc/kamailio/* /etc/kamailio/ 73 | kamdbctl create 74 | ``` 75 | Select yes (Y) to all options. 76 | 77 | ```bash 78 | kamctl add websip websip 79 | service kamailio restart 80 | ``` 81 | 82 | ## Install WebRTC client 83 | This will install the client that can be found [here](https://github.com/havfo/SipCaller). 84 | 85 | Install Nginx: 86 | ```sh 87 | apt-get update 88 | apt-get install nginx libnginx-mod-stream 89 | cd WEBRTC-to-SIP 90 | cp etc/nginx/nginx.conf /etc/nginx/ 91 | cp etc/nginx/conf.d/default.conf /etc/nginx/conf.d/ 92 | cp -r client/* /var/www/html/ 93 | service nginx restart 94 | ``` 95 | 96 | ## Install TURN server 97 | ```sh 98 | apt-get install coturn 99 | cp etc/default/coturn /etc/default/ 100 | cp etc/turnserver.conf /etc/ 101 | service coturn restart 102 | ``` 103 | 104 | ## Testing 105 | You should now be able to go to https://XXXX-XXXX/ and call legacy SIP clients. Click the account icon in the top right corner and add the following settings: 106 | 107 | - Display name: Whatever 108 | - SIP URI: websip@XXXX-XXXX 109 | - Password: websip 110 | - Outbound Proxy: wss://XXXX-XXXX/ws 111 | 112 | To manually configure other TURN servers, change the config in `client/config.js`. 113 | -------------------------------------------------------------------------------- /client/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "/static/css/main.5d4f940f.chunk.css", 3 | "main.js": "/static/js/main.410ce508.chunk.js", 4 | "main.js.map": "/static/js/main.410ce508.chunk.js.map", 5 | "static/js/1.56b88b18.chunk.js": "/static/js/1.56b88b18.chunk.js", 6 | "static/js/1.56b88b18.chunk.js.map": "/static/js/1.56b88b18.chunk.js.map", 7 | "runtime~main.js": "/static/js/runtime~main.229c360f.js", 8 | "runtime~main.js.map": "/static/js/runtime~main.229c360f.js.map", 9 | "static/media/index.css": "/static/media/background.5a9013a5.svg", 10 | "static/css/main.5d4f940f.chunk.css.map": "/static/css/main.5d4f940f.chunk.css.map", 11 | "index.html": "/index.html", 12 | "precache-manifest.2da2d0d268e58779c701c9f88d99aed8.js": "/precache-manifest.2da2d0d268e58779c701c9f88d99aed8.js", 13 | "service-worker.js": "/service-worker.js" 14 | } -------------------------------------------------------------------------------- /client/config.js: -------------------------------------------------------------------------------- 1 | var iceServers = [ 2 | { 3 | urls : 'turn:XXXX-XXXX:443?transport=tcp', 4 | username : 'websip', 5 | credential : 'websip' 6 | }, 7 | { 8 | urls : 'turns:XXXX-XXXX:80?transport=tcp', 9 | username : 'websip', 10 | credential : 'websip' 11 | } 12 | ]; 13 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/havfo/WEBRTC-to-SIP/65d9e7684ce3fb140a147470268e8dd50c8f243a/client/favicon.ico -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | SipCaller
-------------------------------------------------------------------------------- /client/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "SipCaller", 3 | "name": "SIP user agent", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/precache-manifest.2da2d0d268e58779c701c9f88d99aed8.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "5a9013a55d9599bc8b5afa277aca900b", 4 | "url": "/static/media/background.5a9013a5.svg" 5 | }, 6 | { 7 | "revision": "229c360febb4351a89df", 8 | "url": "/static/js/runtime~main.229c360f.js" 9 | }, 10 | { 11 | "revision": "410ce5084caac7b605bc", 12 | "url": "/static/js/main.410ce508.chunk.js" 13 | }, 14 | { 15 | "revision": "56b88b1883f1a1d7cd2e", 16 | "url": "/static/js/1.56b88b18.chunk.js" 17 | }, 18 | { 19 | "revision": "410ce5084caac7b605bc", 20 | "url": "/static/css/main.5d4f940f.chunk.css" 21 | }, 22 | { 23 | "revision": "0e29d683cec21d3306ebf3af25813fec", 24 | "url": "/index.html" 25 | } 26 | ]; -------------------------------------------------------------------------------- /client/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.2da2d0d268e58779c701c9f88d99aed8.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("/index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /client/static/css/main.5d4f940f.chunk.css: -------------------------------------------------------------------------------- 1 | html{font-family:Roboto;font-weight:300}body,html{height:100%;overflow-x:hidden;overflow-y:hidden}body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#646464;background-image:url(/static/media/background.5a9013a5.svg);background-attachment:fixed;background-position:50%;background-size:cover;background-repeat:no-repeat}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}#sipcaller,.LoadingView{height:100%;width:100%} 2 | /*# sourceMappingURL=main.5d4f940f.chunk.css.map */ -------------------------------------------------------------------------------- /client/static/css/main.5d4f940f.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["/home/havfo/Utvikling/SipCaller/app/src/index.css","main.5d4f940f.chunk.css","/home/havfo/Utvikling/SipCaller/app/src/components/LoadingView.css"],"names":[],"mappings":"AAAA,KAGC,kBAAA,CACA,eCGD,CDEA,UAPC,WAAA,CAGA,iBAAA,CACA,iBCmBD,CDhBA,KAKC,QAAA,CACA,SAAA,CACA,mIAAA,CACA,kCAAA,CACA,iCAAA,CACA,wBAAA,CACA,2DAAyC,CACzC,2BAAA,CACA,uBAAA,CACA,qBAAA,CACA,2BCCD,CDEA,KAEC,uECCD,CC9BA,wBAEC,WAAA,CACA,UDsCD","file":"main.5d4f940f.chunk.css","sourcesContent":["html\n{\n\theight: 100%;\n\tfont-family: 'Roboto';\n\tfont-weight: 300;\n\toverflow-x: hidden;\n\toverflow-y: hidden;\n}\n\nbody\n{\n\theight: 100%;\n\toverflow-x: hidden;\n\toverflow-y: hidden;\n\tmargin: 0;\n\tpadding: 0;\n\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tbackground-color: rgba(100, 100, 100, 1.0);\n\tbackground-image: url('./background.svg');\n\tbackground-attachment: fixed;\n\tbackground-position: center;\n\tbackground-size: cover;\n\tbackground-repeat: no-repeat;\n}\n\ncode\n{\n\tfont-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\n\n#sipcaller\n{\n\theight: 100%;\n\twidth: 100%;\n}","html\n{\n\theight: 100%;\n\tfont-family: 'Roboto';\n\tfont-weight: 300;\n\toverflow-x: hidden;\n\toverflow-y: hidden;\n}\n\nbody\n{\n\theight: 100%;\n\toverflow-x: hidden;\n\toverflow-y: hidden;\n\tmargin: 0;\n\tpadding: 0;\n\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tbackground-color: rgba(100, 100, 100, 1.0);\n\tbackground-image: url(/static/media/background.5a9013a5.svg);\n\tbackground-attachment: fixed;\n\tbackground-position: center;\n\tbackground-size: cover;\n\tbackground-repeat: no-repeat;\n}\n\ncode\n{\n\tfont-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\n\n#sipcaller\n{\n\theight: 100%;\n\twidth: 100%;\n}\n.LoadingView\n{\n\theight: 100%;\n\twidth: 100%;\n}\n",".LoadingView\n{\n\theight: 100%;\n\twidth: 100%;\n}"]} -------------------------------------------------------------------------------- /client/static/js/main.410ce508.chunk.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{112:function(e,t,n){"use strict";var a=n(29),r=n(27),i=n(90),o=n(7),s=n(191),c=n.n(s),u=function(e){var t=e.type,n=void 0===t?"info":t,a=e.text,r=e.timeout;if(!r)switch(n){case"warning":case"error":r=5e3;break;case"success":case"info":default:r=3e3}var i={id:c()({length:6}).toLowerCase(),type:n,text:a,timeout:r};return function(e){e(o.b(i)),setTimeout(function(){e(o.f(i.id))},r)}},l=n(17),d=n(83);n.d(t,"a",function(){return m});var p,f=new d.a("SipCaller"),m=function(){function e(){Object(a.a)(this,e),f.debug("constructor()"),this._ua=null,this._init()}return Object(r.a)(e,null,[{key:"init",value:function(e){p=e.store}}]),Object(r.a)(e,[{key:"_init",value:function(){f.debug("_init()"),p.getState().user.autoRegister&&this.register()}},{key:"register",value:function(){var e=this;f.debug("register()");var t=p.getState().user,n=t.displayName,a=t.sipUri,r=t.password,s=t.outboundProxy,c=window.iceServers;p.dispatch(o.m()),this._ua=new i.UA({uri:a,password:r,displayName:n,transportOptions:{wsServers:[s],traceSip:!0},sessionDescriptionHandlerFactoryOptions:{peerConnectionOptions:{rtcConfiguration:{iceServers:c}}}}),this._ua.on("registered",function(){f.debug("Registered"),p.dispatch(u({type:"success",text:"Successfully registered."})),p.dispatch(o.o({registrationMessage:"Success"})),p.dispatch(o.n({registered:!0}))}),this._ua.on("registrationFailed",function(e,t){f.debug("Registration failed [cause: %s]",t),p.dispatch(u({type:"error",text:"Registration failed: ".concat(t)})),p.dispatch(o.o({registrationMessage:t})),p.dispatch(o.n({registered:!1}))}),this._ua.on("unregistered",function(e,t){f.debug("Unregistered [cause: %s]",t),p.dispatch(u({text:"Unregistered: ".concat(t)})),p.dispatch(o.o({registrationMessage:t})),p.dispatch(o.n({registered:!1}))}),this._ua.on("invite",function(t){f.debug("Incoming invite [sipSession: %o]",t),p.dispatch(u({text:"Incoming call from: ".concat(t.remoteIdentity.uri.user)})),e._handleSession(t,l.d)})}},{key:"unRegister",value:function(){f.debug("unRegister()"),this._ua.unregister()}},{key:"_handleSession",value:function(e,t){var n=this;f.debug("_handleSession() [sipSession: %o]",e);var a=Date.now();e.on("trackAdded",function(){f.debug("SipSession trackAdded [sipSession: %o]",e);var t=e.sessionDescriptionHandler.peerConnection,n=new MediaStream;t.getReceivers().forEach(function(e){e.track&&n.addTrack(e.track)}),p.dispatch(o.c({sipSession:e,remoteStream:n}));var a=new MediaStream;t.getSenders().forEach(function(e){e.track&&a.addTrack(e.track)}),p.dispatch(o.a({sipSession:e,localStream:a}))}),e.on("replaced",function(a){f.debug("SipSession replaced [oldSipSession: %o, newSipSession: %o]",e,a),n._handleSession(a,t)}),e.on("referRequested",function(t){t instanceof i.ReferClientContext&&(t.on("referRequestAccepted",function(){p.dispatch(o.q({sipSession:e,sessionState:l.h}))}),t.on("referRequestRejected",function(){p.dispatch(o.q({sipSession:e,sessionState:l.i}))})),t instanceof i.ReferServerContext&&t.accept()}),e.on("directionChanged",function(){"sendrecv"===e.sessionDescriptionHandler.getDirection()?f.debug("SipSession not on hold [sipSession: %o]",e):f.debug("SipSession on hold [sipSession: %o]",e)}),e.on("progress",function(t){f.debug("SipSession progress [response: %o, sipSession: %o]",t,e),p.dispatch(o.q({sipSession:e,sessionState:l.g}))}),e.on("accepted",function(t){f.debug("SipSession accepted [data: %o, sipSession: %o]",t,e),p.dispatch(o.q({sipSession:e,sessionState:l.a}))}),e.on("bye",function(t){f.debug("SipSession bye [request: %o, sipSession: %o]",t,e),p.dispatch(o.q({sipSession:e,sessionState:l.k}))}),e.on("cancel",function(){f.debug("SipSession canceled [sipSession: %o]",e),p.dispatch(o.q({sipSession:e,sessionState:l.b}))}),e.on("rejected",function(t,n){f.debug("SipSession rejected [response: %o, cause: %s, sipSession: %o]",t,n,e),p.dispatch(o.q({sipSession:e,sessionState:l.j}))}),e.on("failed",function(t,n){f.debug("SipSession failed [response: %o, cause: %s, sipSession: %o]",t,n,e),p.dispatch(o.q({sipSession:e,sessionState:l.c}))}),e.on("terminated",function(t,n){f.debug("SipSession terminated [message: %o, cause: %s, sipSession: %o]",t,n,e),p.dispatch(u({text:"Call terminated: ".concat(e.remoteIdentity.uri.user)})),p.dispatch(o.q({sipSession:e,sessionState:l.k}));var r=e.remoteIdentity.displayName||e.remoteIdentity.uri.user,i=e.remoteIdentity.uri.toString();p.dispatch(o.e({displayName:r,sipUri:i,startTime:a})),setTimeout(function(){if(f.debug("SipSession removed [sipSession: %o]",e),p.dispatch(o.g({sipSession:e})),!p.getState().userStatus.currentSession){var t=p.getState().sessions;t&&p.dispatch(o.i({currentSession:Object.keys(t)[0]}))}},3e3)}),p.dispatch(o.d({sipSession:e,direction:t}))}},{key:"accept",value:function(e){f.debug("accept() [sipSession: %o]",e);var t=p.getState().user.videoEnabled;e.accept({sessionDescriptionHandlerOptions:{constraints:{audio:!0,video:t}}})}},{key:"terminate",value:function(e){f.debug("terminate() [sipSession: %o]",e),e.terminate()}},{key:"invite",value:function(e){f.debug("invite() [sipUri: %s]",e);var t=p.getState().user.videoEnabled,n=this._ua.invite(e,{sessionDescriptionHandlerOptions:{constraints:{audio:!0,video:t}}});this._handleSession(n,l.f),p.dispatch(o.i({currentSession:n.request.callId}))}},{key:"refer",value:function(e,t){f.debug("refer() [sipSession: %o, sipUri: %s]",e,t),e.refer(t)}},{key:"toggleMedia",value:function(e,t,n){f.debug("toggleMedia() [sipSession: %o, type: %s, mute: %s]",e,t,n);var a=e.request.callId,r=p.getState().sessions[a].remoteStream;if(r)if("audio"===t)r.getAudioTracks()[0].enabled=!n,p.dispatch(o.t({sipSession:e}));else{if("video"!==t)throw new Error("Unknown media type.");r.getVideoTracks()[0].enabled=!n,p.dispatch(o.u({sipSession:e}))}}},{key:"toggleMyMedia",value:function(e,t,n){if(f.debug("toggleMyMedia() [session: %o, type: %s, mute: %s]",e,t,n),"audio"===t)e.localStream.getAudioTracks()[0].enabled=!n;else{if("video"!==t)throw new Error("Unknown media type.");e.localStream.getVideoTracks()[0].enabled=!n}}}]),e}()},17:function(e,t,n){"use strict";n.d(t,"e",function(){return a}),n.d(t,"g",function(){return r}),n.d(t,"j",function(){return i}),n.d(t,"a",function(){return o}),n.d(t,"b",function(){return s}),n.d(t,"c",function(){return c}),n.d(t,"k",function(){return u}),n.d(t,"h",function(){return l}),n.d(t,"i",function(){return d}),n.d(t,"d",function(){return p}),n.d(t,"f",function(){return f});var a=Symbol("NEW"),r=Symbol("PROGRESS"),i=Symbol("REJECTED"),o=(Symbol("IGNORED"),Symbol("ACCEPTED")),s=Symbol("CANCELED"),c=Symbol("FAILED"),u=Symbol("TERMINATED"),l=(Symbol("REFERRED"),Symbol("REFER_REQUEST_ACCEPTED")),d=Symbol("REFER_REQUEST_REJECTED"),p=(Symbol("REFER_PROGRESS"),Symbol("REFER_ACCEPTED"),Symbol("REFER_REJECTED"),Symbol("INCOMING")),f=Symbol("OUTGOING")},189:function(e,t,n){"use strict";var a=n(0),r=n.n(a);n(377);t.a=function(){return r.a.createElement("div",{className:"LoadingView"})}},190:function(e,t,n){"use strict";n.d(t,"a",function(){return r});var a=Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));function r(e){if("serviceWorker"in navigator){if(new URL("",window.location.href).origin!==window.location.origin)return;window.addEventListener("load",function(){var t="".concat("","/service-worker.js");a?(!function(e,t){fetch(e).then(function(n){var a=n.headers.get("content-type");404===n.status||null!=a&&-1===a.indexOf("javascript")?navigator.serviceWorker.ready.then(function(e){e.unregister().then(function(){window.location.reload()})}):i(e,t)}).catch(function(){})}(t,e),navigator.serviceWorker.ready.then(function(){})):i(t,e)})}}function i(e,t){navigator.serviceWorker.register(e).then(function(e){e.onupdatefound=function(){var n=e.installing;null!=n&&(n.onstatechange=function(){"installed"===n.state&&(navigator.serviceWorker.controller?t&&t.onUpdate&&t.onUpdate(e):t&&t.onSuccess&&t.onSuccess(e))})}}).catch(function(){})}},192:function(e,t,n){"use strict";var a=n(29),r=n(27),i=n(45),o=n(44),s=n(46),c=n(31),u=n(0),l=n.n(u),d=n(21),p=n(20),f=n(185),m=n.n(f),S=n(186),E=n.n(S),h=n(18),g=n.n(h),b=n(36),y=n.n(b),v=n(107),O=n.n(v),R=n(89),_=n.n(R),T=n(188),w=n.n(T),I=n(106),C=n.n(I),j=n(110),k=n.n(j),N=n(108),A=n.n(N),M=n(109),U=n.n(M),D=n(187),x=n.n(D),P=n(30),G=n(7),L=n(34),q=n.n(L),V=n(24),W=n.n(V),F=n(28),H=n.n(F),z=n(49),B=n.n(z),Y=n(170),J=n.n(Y),Q=n(171),X=n.n(Q),K=Object(P.b)(Object(d.b)(function(e){return{displayName:e.user.displayName,sipUri:e.user.sipUri,password:e.user.password,outboundProxy:e.user.outboundProxy,autoRegister:e.user.autoRegister,registered:e.userStatus.registered}},function(e){return{setDisplayName:function(t){return e(G.j({displayName:t}))},setSipUri:function(t){return e(G.r({sipUri:t}))},setPassword:function(t){return e(G.l({password:t}))},setOutboundProxy:function(t){return e(G.k({outboundProxy:t}))},setAutoRegister:function(t){return e(G.h({autoRegister:t}))}}})(Object(p.withStyles)(function(e){return{paper:{padding:2*e.spacing.unit,width:"20vw"}}})(function(e){var t=e.sipCaller,n=e.displayName,a=e.sipUri,r=e.password,i=e.outboundProxy,o=e.autoRegister,s=e.setDisplayName,c=e.setSipUri,u=e.setPassword,d=e.setOutboundProxy,p=e.setAutoRegister,f=e.registered,m=e.classes;return l.a.createElement(q.a,{className:m.paper},f?l.a.createElement(W.a,{container:!0,spacing:8},l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(g.a,{variant:"body1",noWrap:!0},n),l.a.createElement(g.a,{noWrap:!0},a),l.a.createElement(g.a,{variant:"caption",noWrap:!0},"Registered")),l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(H.a,{variant:"contained",color:"secondary",onClick:function(){return t.unRegister()}},"Unregister"))):l.a.createElement(W.a,{container:!0,spacing:8},l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(B.a,{id:"displayname",label:"Displayname",value:n||"",style:{width:"100%"},onChange:function(e){return s(e.target.value)}})),l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(B.a,{id:"sipuri",label:"SIP URI",type:"email",value:a||"",style:{width:"100%"},onChange:function(e){return c(e.target.value)}})),l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(B.a,{id:"password",label:"Password",type:"password",value:r||"",style:{width:"100%"},onChange:function(e){return u(e.target.value)}})),l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(B.a,{id:"outboundproxy",label:"Outbound Proxy",value:i||"",style:{width:"100%"},onChange:function(e){return d(e.target.value)}})),l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(J.a,{control:l.a.createElement(X.a,{checked:o,onChange:function(){return p(!o)}}),label:"Autoregister"})),l.a.createElement(W.a,{item:!0,xs:12},l.a.createElement(H.a,{variant:"contained",color:"primary",onClick:function(){return t.register()}},"Register"))))}))),$=n(65),Z=n.n($),ee=n(66),te=n.n(ee),ne=n(84),ae=n.n(ne),re=Object(d.b)(function(e){return{sessionHistory:e.sessionHistory}},function(e){return{setRequestUri:function(t){return e(G.p({requestUri:t}))}}})(Object(p.withStyles)(function(e){return{paper:{padding:2*e.spacing.unit}}})(function(e){var t=e.sessionHistory,n=e.setRequestUri,a=e.classes;return l.a.createElement(q.a,{className:a.paper},t.length>0?l.a.createElement(Z.a,null,t.map(function(e,t){return l.a.createElement(te.a,{key:t,button:!0,onClick:function(){return n(e.sipUri)}},l.a.createElement(ae.a,{primary:e.displayName,secondary:e.sipUri}))})):l.a.createElement(g.a,{color:"inherit",noWrap:!0},"No history"))})),ie=n(17),oe=n(85),se=n.n(oe),ce=n(86),ue=n.n(ce),le=Object(P.b)(Object(d.b)(function(e){return{sessions:e.sessions,currentSession:e.userStatus.currentSession}},function(e){return{setCurrentSession:function(t){return e(G.i({currentSession:t}))}}})(Object(p.withStyles)(function(e){return{paper:{padding:2*e.spacing.unit},iconGreen:{color:"green"},iconRed:{color:"red"}}})(function(e){var t=e.sipCaller,n=e.sessions,a=e.currentSession,r=e.setCurrentSession,i=e.classes;return l.a.createElement(q.a,{className:i.paper},Object.keys(n).length>0?l.a.createElement(Z.a,null,Object.keys(n).map(function(e){var o=n[e],s=o.sipSession,c=s.remoteIdentity.displayName||s.remoteIdentity.uri.user,u=s.remoteIdentity.uri.toString(),d=o.direction===ie.d&&o.sessionState===ie.e;return l.a.createElement(te.a,{key:e,button:!0,selected:e===a,onClick:function(){return r(e)},disabled:o.sessionState===ie.k},l.a.createElement(ae.a,{primary:c,secondary:u}),l.a.createElement(y.a,{className:d?i.iconGreen:i.iconRed,"aria-label":"Answer",onClick:function(){d?t.accept(s):t.terminate(s)}},d?l.a.createElement(se.a,null):l.a.createElement(ue.a,null)))})):l.a.createElement(g.a,{color:"inherit",noWrap:!0},"No sessions"))}))),de=n(43),pe=n(64),fe=n.n(pe),me=Object(P.b)(Object(d.b)(function(e){return{requestUri:e.user.requestUri,registered:e.userStatus.registered}},function(e){return{setRequestUri:function(t){return e(G.p({requestUri:t}))}}})(Object(p.withStyles)(function(e){return{grid:{width:"50vw"},call:Object(c.a)({position:"relative",borderRadius:e.shape.borderRadius,backgroundColor:Object(de.fade)(e.palette.common.white,.15),"&:hover":{backgroundColor:Object(de.fade)(e.palette.common.white,.25)},marginLeft:0,width:"100%"},e.breakpoints.up("sm"),{marginLeft:3*e.spacing.unit,width:"auto"}),callIcon:{width:5*e.spacing.unit,height:"100%",position:"absolute",pointerEvents:"none",display:"flex",alignItems:"center",justifyContent:"center"},inputRoot:{color:"inherit",width:"100%"},inputInput:Object(c.a)({paddingTop:e.spacing.unit,paddingRight:e.spacing.unit,paddingBottom:e.spacing.unit,paddingLeft:5*e.spacing.unit,transition:e.transitions.create("width"),width:"100%"},e.breakpoints.up("md"),{width:"100%"})}})(function(e){var t=e.sipCaller,n=e.requestUri,a=e.registered,r=e.setRequestUri,i=e.classes;return l.a.createElement(W.a,{className:i.grid,container:!0,spacing:8},l.a.createElement(W.a,{item:!0,xs:9},l.a.createElement("div",{className:i.call},l.a.createElement("div",{className:i.callIcon},l.a.createElement(se.a,null)),l.a.createElement(fe.a,{placeholder:"SIP URI",classes:{root:i.inputRoot,input:i.inputInput},value:n||"",onChange:function(e){return r(e.target.value)},onKeyPress:function(e){"Enter"===e.key&&(e.preventDefault(),t.invite(n))},autoFocus:!0}))),l.a.createElement(W.a,{item:!0,xs:3},l.a.createElement(H.a,{variant:"contained",color:"primary",disabled:!a,onClick:function(){return t.invite(n)}},"Call")))}))),Se=function(e){function t(e){var n;return Object(a.a)(this,t),(n=Object(i.a)(this,Object(o.a)(t).call(this,e)))._mediaStream=null,n}return Object(s.a)(t,e),Object(r.a)(t,[{key:"render",value:function(){var e=this.props.classes;return l.a.createElement("video",{className:e.videoView,ref:"video",autoPlay:!0,playsInline:!0})}},{key:"componentDidMount",value:function(){var e=this.props.mediaStream;this._setStream(e)}},{key:"componentWillReceiveProps",value:function(e){var t=e.mediaStream;this._setStream(t)}},{key:"_setStream",value:function(e){if(this._mediaStream!==e){this._mediaStream=e;var t=this.refs.video;t.srcObject=e||null}}}]),t}(u.Component),Ee=Object(p.withStyles)(function(){return{videoView:{objectFit:"cover",width:"100%",height:"100%"}}})(Se),he=n(67),ge=n.n(he),be=n(182),ye=n.n(be),ve=n(181),Oe=n.n(ve),Re=n(180),_e=n.n(Re),Te=n(179),we=n.n(Te),Ie=n(178),Ce=n.n(Ie),je=n(87),ke=n.n(je),Ne=n(88),Ae=n.n(Ne),Me=n(172),Ue=n.n(Me),De=n(173),xe=n.n(De),Pe=n(177),Ge=n.n(Pe),Le=n(175),qe=n.n(Le),Ve=n(176),We=n.n(Ve),Fe=n(174),He=n.n(Fe),ze=function(e){function t(){var e,n;Object(a.a)(this,t);for(var r=arguments.length,s=new Array(r),c=0;c0&&void 0!==arguments[0]?arguments[0]:d,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"SET_USER":var n=t.payload,a=n.displayName,r=n.sipUri,i=n.password,o=n.outboundProxy,s=n.autoRegister;return Object(l.a)({},e,{displayName:a,sipUri:r,password:i,outboundProxy:o,autoRegister:s});case"SET_DISPLAY_NAME":var c=t.payload.displayName;return Object(l.a)({},e,{displayName:c});case"SET_SIP_URI":var u=t.payload.sipUri;return Object(l.a)({},e,{sipUri:u});case"SET_PASSWORD":var p=t.payload.password;return Object(l.a)({},e,{password:p});case"SET_OUTBOUND_PROXY":var f=t.payload.outboundProxy;return Object(l.a)({},e,{outboundProxy:f});case"SET_AUTO_REGISTER":var m=t.payload.autoRegister;return Object(l.a)({},e,{autoRegister:m});case"TOGGLE_VIDEO":var S=!e.videoEnabled;return Object(l.a)({},e,{videoEnabled:S});case"SET_REQUEST_URI":var E=t.payload.requestUri;return Object(l.a)({},e,{requestUri:E});case"SET_TRANSFER_URI":var h=t.payload.transferUri;return Object(l.a)({},e,{transferUri:h});default:return e}},f=n(31),m=n(169),S=n.n(m),E={sipSession:null,direction:null,remoteStream:null,remoteAudioMuted:!1,remoteVideoMuted:!1,localStream:null,localAudioMuted:!1,localVideoMuted:!1,sessionState:n(17).e},h=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:E,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"ADD_SESSION":var n=t.payload,a=n.sipSession,r=n.direction;return Object(l.a)({},e,{sipSession:a,direction:r});case"ADD_REMOTE_STREAM":var i=t.payload.remoteStream;return Object(l.a)({},e,{remoteStream:i});case"ADD_LOCAL_STREAM":var o=t.payload.localStream;return Object(l.a)({},e,{localStream:o});case"SET_SESSION_STATE":var s=t.payload.sessionState;return Object(l.a)({},e,{sessionState:s});case"TOGGLE_LOCAL_AUDIO":var c=!e.localAudioMuted;return Object(l.a)({},e,{localAudioMuted:c});case"TOGGLE_LOCAL_VIDEO":var u=!e.localVideoMuted;return Object(l.a)({},e,{localVideoMuted:u});case"TOGGLE_REMOTE_AUDIO":var d=!e.remoteAudioMuted;return Object(l.a)({},e,{remoteAudioMuted:d});case"TOGGLE_REMOTE_VIDEO":var p=!e.remoteVideoMuted;return Object(l.a)({},e,{remoteVideoMuted:p});default:return e}},g=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"ADD_SESSION":var n=t.payload.sipSession.request.callId;return Object(l.a)({},e,Object(f.a)({},n,h(void 0,t)));case"REMOVE_SESSION":var a=t.payload.sipSession.request.callId;return S()(e,a);case"TOGGLE_LOCAL_AUDIO":case"TOGGLE_LOCAL_VIDEO":case"TOGGLE_REMOTE_AUDIO":case"TOGGLE_REMOTE_VIDEO":case"ADD_REMOTE_STREAM":case"ADD_LOCAL_STREAM":case"SET_SESSION_STATE":var r=t.payload.sipSession.request.callId,i=e[r];if(!i)throw new Error("No session found");return Object(l.a)({},e,Object(f.a)({},r,h(i,t)));default:return e}},b=n(57),y=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"ADD_SESSION_ENTRY":var n=t.payload,a=n.displayName,r=n.sipUri,i=n.direction,o=n.startTime;return[].concat(Object(b.a)(e),[{displayName:a,sipUri:r,direction:i,startTime:o}]);case"CLEAR_SESSION_HISTORY":return[];default:return e}},v={registerInProgress:!1,registered:!1,registrationMessage:null,currentSession:null},O=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:v,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"SET_REGISTERED":var n=t.payload.registered;return Object(l.a)({},e,{registered:n,registerInProgress:!1});case"SET_REGISTRATION_MESSAGE":var a=t.payload.registrationMessage;return Object(l.a)({},e,{registrationMessage:a});case"SET_REGISTER_IN_PROGRESS":return Object(l.a)({},e,{registerInProgress:!0});case"ADD_SESSION":var r=t.payload.sipSession.request.callId;return e.currentSession?e:Object(l.a)({},e,{currentSession:r});case"REMOVE_SESSION":var i=t.payload.sipSession.request.callId;return e.currentSession===i?Object(l.a)({},e,{currentSession:null}):e;case"SET_CURRENT_SESSION":var o=t.payload.currentSession;return Object(l.a)({},e,{currentSession:o});default:return e}},R=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"ADD_NOTIFICATION":var n=t.payload.notification;return[].concat(Object(b.a)(e),[n]);case"REMOVE_NOTIFICATION":var a=t.payload.notificationId;return e.filter(function(e){return e.id!==a});case"REMOVE_ALL_NOTIFICATIONS":return[];default:return e}},_=Object(a.c)({user:p,sessions:g,sessionHistory:y,userStatus:O,notifications:R});n.d(t,"b",function(){return C}),n.d(t,"a",function(){return j});var T={key:"root",storage:s.a,stateReconciler:u.a,blacklist:["sessions","userStatus"]},w=[r.a],I=Object(i.a)(T,_),C=Object(a.d)(I,a.a.apply(void 0,w)),j=Object(i.b)(C)}},[[193,2,1]]]); 2 | //# sourceMappingURL=main.410ce508.chunk.js.map -------------------------------------------------------------------------------- /client/static/js/runtime~main.229c360f.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];csize=8;autoexpire=300;") 247 | #!endif 248 | 249 | #!ifdef WITH_DEBUG 250 | # ----- debugger params ----- 251 | modparam("debugger", "cfgtrace", 1) 252 | #!endif 253 | 254 | ####### Routing Logic ######## 255 | request_route { 256 | 257 | #!ifdef WITH_HOMER 258 | # start duplicate the SIP message here 259 | sip_trace(); 260 | setflag(22); 261 | #!endif 262 | 263 | # per request initial checks 264 | route(REQINIT); 265 | 266 | #!ifdef WITH_WEBSOCKETS 267 | if (nat_uac_test(64)) { 268 | # Do NAT traversal stuff for requests from a WebSocket 269 | # connection - even if it is not behind a NAT! 270 | # This won't be needed in the future if Kamailio and the 271 | # WebSocket client support Outbound and Path. 272 | force_rport(); 273 | if (is_method("REGISTER")) { 274 | fix_nated_register(); 275 | } else { 276 | if (!add_contact_alias()) { 277 | xlog("L_ERR", "Error aliasing contact <$ct>\n"); 278 | sl_send_reply("400", "Bad Request"); 279 | exit; 280 | } 281 | } 282 | } 283 | #!endif 284 | 285 | # NAT detection 286 | route(NATDETECT); 287 | 288 | # CANCEL processing 289 | if (is_method("CANCEL")) 290 | { 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 | route(RTP_BRIDGE); 314 | 315 | # dispatch requests to foreign domains 316 | route(SIPOUT); 317 | 318 | ### requests for my local domains 319 | 320 | # handle presence related requests 321 | route(PRESENCE); 322 | 323 | # handle registrations 324 | route(REGISTRAR); 325 | 326 | if ($rU==$null) 327 | { 328 | # request with no Username in RURI 329 | sl_send_reply("484","Address Incomplete"); 330 | exit; 331 | } 332 | 333 | # user location service 334 | route(LOCATION); 335 | } 336 | 337 | # Wrapper for relaying requests 338 | route[RELAY] { 339 | 340 | # enable additional event routes for forwarded requests 341 | # - serial forking, RTP relaying handling, a.s.o. 342 | if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) { 343 | if(!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH"); 344 | } 345 | if (is_method("INVITE|SUBSCRIBE|UPDATE")) { 346 | if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY"); 347 | } 348 | if (is_method("INVITE")) { 349 | if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE"); 350 | } 351 | 352 | if (!t_relay()) { 353 | sl_reply_error(); 354 | } 355 | exit; 356 | } 357 | 358 | # Per SIP request initial checks 359 | route[REQINIT] { 360 | #!ifdef WITH_ANTIFLOOD 361 | # flood dection from same IP and traffic ban for a while 362 | # be sure you exclude checking trusted peers, such as pstn gateways 363 | # - local host excluded (e.g., loop to self) 364 | if(src_ip!=myself) 365 | { 366 | if($sht(ipban=>$si)!=$null) 367 | { 368 | # ip is already blocked 369 | xdbg("request from blocked IP - $rm from $fu (IP:$si:$sp)\n"); 370 | exit; 371 | } 372 | if (!pike_check_req()) 373 | { 374 | xlog("L_ALERT","ALERT: pike blocking $rm from $fu (IP:$si:$sp)\n"); 375 | $sht(ipban=>$si) = 1; 376 | exit; 377 | } 378 | } 379 | #!endif 380 | 381 | if (!mf_process_maxfwd_header("10")) { 382 | sl_send_reply("483","Too Many Hops"); 383 | exit; 384 | } 385 | 386 | if(!sanity_check("1511", "7")) 387 | { 388 | xlog("Malformed SIP message from $si:$sp\n"); 389 | exit; 390 | } 391 | } 392 | 393 | # Handle requests within SIP dialogs 394 | route[WITHINDLG] { 395 | if (has_totag()) { 396 | # sequential request withing a dialog should 397 | # take the path determined by record-routing 398 | if (loose_route()) { 399 | #!ifdef WITH_WEBSOCKETS 400 | if ($du == "") { 401 | if (!handle_ruri_alias()) { 402 | xlog("L_ERR", "Bad alias <$ru>\n"); 403 | sl_send_reply("400", "Bad Request"); 404 | exit; 405 | } 406 | } 407 | #!endif 408 | route(DLGURI); 409 | if ( is_method("ACK") ) { 410 | # ACK is forwarded statelessy 411 | route(NATMANAGE); 412 | } 413 | else if ( is_method("NOTIFY") ) { 414 | # Add Record-Route for in-dialog NOTIFY as per RFC 6665. 415 | record_route(); 416 | } 417 | route(RELAY); 418 | } else { 419 | if (is_method("SUBSCRIBE") && uri == myself) { 420 | # in-dialog subscribe requests 421 | route(PRESENCE); 422 | exit; 423 | } 424 | if ( is_method("ACK") ) { 425 | if ( t_check_trans() ) { 426 | # no loose-route, but stateful ACK; 427 | # must be an ACK after a 487 428 | # or e.g. 404 from upstream server 429 | route(RELAY); 430 | exit; 431 | } else { 432 | # ACK without matching transaction ... ignore and discard 433 | exit; 434 | } 435 | } 436 | sl_send_reply("404","Not here"); 437 | } 438 | exit; 439 | } 440 | } 441 | 442 | # Handle SIP registrations 443 | route[REGISTRAR] { 444 | if (is_method("REGISTER")) 445 | { 446 | if(isflagset(FLT_NATS)) 447 | { 448 | setbflag(FLB_NATB); 449 | # uncomment next line to do SIP NAT pinging 450 | ## setbflag(FLB_NATSIPPING); 451 | } 452 | if (!save("location")) 453 | sl_reply_error(); 454 | 455 | exit; 456 | } 457 | } 458 | 459 | # USER location service 460 | route[LOCATION] { 461 | $avp(oexten) = $rU; 462 | if (!lookup("location")) { 463 | $var(rc) = $rc; 464 | t_newtran(); 465 | switch ($var(rc)) { 466 | case -1: 467 | case -3: 468 | send_reply("404", "Not Found"); 469 | exit; 470 | case -2: 471 | send_reply("405", "Method Not Allowed"); 472 | exit; 473 | } 474 | } 475 | 476 | route(RELAY); 477 | exit; 478 | } 479 | 480 | # Presence server route 481 | route[PRESENCE] { 482 | if(!is_method("PUBLISH|SUBSCRIBE")) 483 | return; 484 | 485 | if(is_method("SUBSCRIBE") && $hdr(Event)=="message-summary") { 486 | # returns here if no voicemail server is configured 487 | sl_send_reply("404", "No voicemail service"); 488 | exit; 489 | } 490 | 491 | #!ifdef WITH_PRESENCE 492 | if (!t_newtran()) 493 | { 494 | sl_reply_error(); 495 | exit; 496 | } 497 | 498 | if(is_method("PUBLISH")) 499 | { 500 | handle_publish(); 501 | t_release(); 502 | } else if(is_method("SUBSCRIBE")) { 503 | handle_subscribe(); 504 | t_release(); 505 | } 506 | exit; 507 | #!endif 508 | 509 | # if presence enabled, this part will not be executed 510 | if (is_method("PUBLISH") || $rU==$null) 511 | { 512 | sl_send_reply("404", "Not here"); 513 | exit; 514 | } 515 | return; 516 | } 517 | 518 | # Authentication route 519 | route[AUTH] { 520 | #!ifdef WITH_AUTH 521 | 522 | if (is_method("REGISTER") || from_uri==myself) 523 | { 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 | # if caller is not local subscriber, then check if it calls 534 | # a local destination, otherwise deny, not an open relay here 535 | if (from_uri!=myself && uri!=myself) 536 | { 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 | setflag(FLT_NATS); 556 | } 557 | return; 558 | } 559 | 560 | # NAT handling 561 | route[NATMANAGE] { 562 | if (is_request()) { 563 | if(has_totag()) { 564 | if(check_route_param("nat=yes")) { 565 | setbflag(FLB_NATB); 566 | } 567 | } 568 | } 569 | if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB))) 570 | return; 571 | 572 | if (is_request()) { 573 | if (!has_totag()) { 574 | if(t_is_branch_route()) { 575 | add_rr_param(";nat=yes"); 576 | } 577 | } 578 | } 579 | if (is_reply()) { 580 | if(isbflagset(FLB_NATB)) { 581 | if(is_first_hop()) 582 | set_contact_alias(); 583 | } 584 | } 585 | return; 586 | } 587 | 588 | # URI update for dialog requests 589 | route[DLGURI] { 590 | if(!isdsturiset()) { 591 | handle_ruri_alias(); 592 | } 593 | return; 594 | } 595 | 596 | # Routing to foreign domains 597 | route[SIPOUT] { 598 | if (!uri==myself) 599 | { 600 | append_hf("P-hint: outbound\r\n"); 601 | route(RELAY); 602 | } 603 | } 604 | 605 | route[RTP_BRIDGE] { 606 | #!ifdef WITH_ALWAYS_BRIDGE 607 | if (is_method("INVITE")) { 608 | if ($ru =~ "transport=ws") { 609 | xlog("L_INFO", "SIP -> WebRTC, bridging RTP->SRTP and adding ICE"); 610 | rtpengine_manage("trust-address replace-origin replace-session-connection rtcp-mux-accept rtcp-mux-offer ICE=force UDP/TLS/RTP/SAVPF"); 611 | t_on_reply("REPLY_FROM_WS"); 612 | } else if ($proto =~ "ws") { 613 | xlog("L_INFO", "WebRTC -> SIP, bridging SRTP->RTP and removing ICE"); 614 | rtpengine_manage("trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove RTP/AVP"); 615 | t_on_reply("REPLY_TO_WS"); 616 | } 617 | } 618 | #!endif 619 | } 620 | 621 | # manage outgoing branches 622 | branch_route[MANAGE_BRANCH] { 623 | xdbg("new branch [$T_branch_idx] to $ru\n"); 624 | route(NATMANAGE); 625 | } 626 | 627 | onreply_route[REPLY_TO_WS] { 628 | xlog("L_INFO", "Reply from softphone: $rs"); 629 | 630 | if (t_check_status("183")) { 631 | change_reply_status("180", "Ringing"); 632 | remove_body(); 633 | route(NATMANAGE); 634 | exit; 635 | } 636 | 637 | if(!(status=~"[12][0-9][0-9]") || !(sdp_content())) 638 | return; 639 | 640 | rtpengine_manage(); 641 | 642 | route(NATMANAGE); 643 | } 644 | 645 | onreply_route[REPLY_FROM_WS] { 646 | 647 | xlog("L_INFO", "Reply from webrtc client: $rs"); 648 | 649 | if(status=~"[12][0-9][0-9]") { 650 | rtpengine_manage(); 651 | route(NATMANAGE); 652 | } 653 | } 654 | 655 | # manage incoming replies 656 | onreply_route[MANAGE_REPLY] { 657 | xdbg("incoming reply\n"); 658 | if(status=~"[12][0-9][0-9]") 659 | route(NATMANAGE); 660 | } 661 | 662 | # manage failure routing cases 663 | failure_route[MANAGE_FAILURE] { 664 | xlog("L_INFO", "Failure: $rs"); 665 | #!ifndef WITH_ALWAYS_BRIDGE 666 | if (t_check_status("488") && sdp_content()) { 667 | if ($ru =~ "transport=ws") { 668 | xlog("L_INFO", "WebRTC client responded 488 Not Supported Here, bridging RTP->SRTP and adding ICE"); 669 | rtpengine_offer("trust-address replace-origin replace-session-connection ICE=force rtcp-mux-accept rtcp-mux-offer UDP/TLS/RTP/SAVPF"); 670 | t_on_reply("REPLY_FROM_WS"); 671 | } else if ($proto =~ "ws") { 672 | xlog("L_INFO", "SIP client at the other end responded 488 Not Supported Here, bridging SRTP->RTP and removing ICE"); 673 | rtpengine_offer("trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove RTP/AVP"); 674 | t_on_reply("REPLY_TO_WS"); 675 | } 676 | 677 | append_branch(); 678 | route(RELAY); 679 | } 680 | #!endif 681 | } 682 | 683 | #!ifdef WITH_WEBSOCKETS 684 | onreply_route { 685 | if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT) 686 | && !(proto == WS || proto == WSS))) { 687 | xlog("L_WARN", "SIP response received on $Rp\n"); 688 | drop; 689 | } 690 | 691 | if (nat_uac_test(64)) { 692 | # Do NAT traversal stuff for replies to a WebSocket connection 693 | # - even if it is not behind a NAT! 694 | # This won't be needed in the future if Kamailio and the 695 | # WebSocket client support Outbound and Path. 696 | add_contact_alias(); 697 | } 698 | } 699 | 700 | event_route[xhttp:request] { 701 | set_reply_close(); 702 | set_reply_no_connect(); 703 | 704 | if ($Rp != MY_WS_PORT 705 | #!ifdef WITH_TLS 706 | && $Rp != MY_WSS_PORT 707 | #!endif 708 | ) { 709 | xlog("L_WARN", "HTTP request received on $Rp\n"); 710 | xhttp_reply("403", "Forbidden", "", ""); 711 | exit; 712 | } 713 | 714 | xlog("L_DBG", "HTTP Request Received\n"); 715 | 716 | if ($hdr(Upgrade)=~"websocket" 717 | && $hdr(Connection)=~"Upgrade" 718 | && $rm=~"GET") { 719 | 720 | # Validate Host - make sure the client is using the correct 721 | # alias for WebSockets 722 | if ($hdr(Host) == $null || !is_myself("sip:" + $hdr(Host))) { 723 | xlog("L_WARN", "Bad host $hdr(Host)\n"); 724 | xhttp_reply("403", "Forbidden", "", ""); 725 | exit; 726 | } 727 | 728 | # Optional... validate Origin - make sure the client is from an 729 | # authorised website. For example, 730 | # 731 | # if ($hdr(Origin) != "https://example.com" 732 | # && $hdr(Origin) != "https://example.com") { 733 | # xlog("L_WARN", "Unauthorised client $hdr(Origin)\n"); 734 | # xhttp_reply("403", "Forbidden", "", ""); 735 | # exit; 736 | # } 737 | 738 | # Optional... perform HTTP authentication 739 | 740 | # ws_handle_handshake() exits (no further configuration file 741 | # processing of the request) when complete. 742 | if (ws_handle_handshake()) 743 | { 744 | # Optional... cache some information about the 745 | # successful connection 746 | exit; 747 | } 748 | } 749 | 750 | xhttp_reply("404", "Not Found", "", ""); 751 | } 752 | 753 | event_route[websocket:closed] { 754 | xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n"); 755 | } 756 | #!endif 757 | -------------------------------------------------------------------------------- /etc/kamailio/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 | #!define WITH_IPV6 12 | ##!define WITH_BRIDGE_ON_FAIL 13 | #!define WITH_LOCALHOST_WS 14 | ##!define WITH_LOCALHOST_SIP 15 | 16 | #!substdef "!MY_SIP_PORT!5060!g" 17 | #!substdef "!MY_SIPS_PORT!5061!g" 18 | #!substdef "!MY_WS_PORT!8080!g" 19 | #!substdef "!MY_WSS_PORT!4443!g" 20 | 21 | #!substdef "!MY_IP4_ADDR!XXXXX-XXXXX!g" 22 | #!substdef "!IP4_LOCALHOST!127.0.0.1!g" 23 | #!substdef "!MY_WS4_ADDR!tcp:MY_IP4_ADDR:MY_WS_PORT!g" 24 | #!substdef "!MY_WSS4_ADDR!tls:MY_IP4_ADDR:MY_WSS_PORT!g" 25 | #!substdef "!LOCALHOST_WS4_ADDR!tcp:IP4_LOCALHOST:MY_WS_PORT!g" 26 | #!substdef "!LOCALHOST_WSS4_ADDR!tls:IP4_LOCALHOST:MY_WSS_PORT!g" 27 | 28 | #!ifdef WITH_IPV6 29 | #!substdef "!MY_IP6_ADDR![XXXXXX-XXXXXX]!g" 30 | #!substdef "!IP6_LOCALHOST![::1]!g" 31 | #!substdef "!MY_WS6_ADDR!tcp:MY_IP6_ADDR:MY_WS_PORT!g" 32 | #!substdef "!MY_WSS6_ADDR!tls:MY_IP6_ADDR:MY_WSS_PORT!g" 33 | #!substdef "!LOCALHOST_WS6_ADDR!tcp:IP6_LOCALHOST:MY_WS_PORT!g" 34 | #!substdef "!LOCALHOST_WSS6_ADDR!tls:IP6_LOCALHOST:MY_WSS_PORT!g" 35 | #!endif 36 | 37 | #!substdef "!MY_DOMAIN!XXXX-XXXX!g" 38 | 39 | # *** Value defines - IDs used later in config 40 | #!ifdef WITH_MYSQL 41 | # - database URL - used to connect to database server by modules such 42 | # as: auth_db, acc, usrloc, a.s.o. 43 | #!ifndef DBURL 44 | #!define DBURL "mysql://kamailio:kamailiorw@localhost/kamailio" 45 | #!endif 46 | #!endif 47 | 48 | # - flags 49 | # FLT_ - per transaction (message) flags 50 | # FLB_ - per branch flags 51 | #!define FLT_NATS 5 52 | 53 | #!define FLB_NATB 6 54 | #!define FLB_NATSIPPING 7 55 | #!define FLB_RTPWS 8 56 | #!define FLB_IPV6 9 57 | #!define FLB_V4V6 10 58 | #!define FLB_BRIDGE 11 59 | 60 | ####### Global Parameters ######### 61 | 62 | ### LOG Levels: 3=DBG, 2=INFO, 1=NOTICE, 0=WARN, -1=ERR 63 | #!ifdef WITH_DEBUG 64 | debug=4 65 | log_stderror=no 66 | #!else 67 | debug=2 68 | log_stderror=no 69 | #!endif 70 | 71 | memdbg=5 72 | memlog=5 73 | 74 | log_facility=LOG_LOCAL0 75 | 76 | fork=yes 77 | children=4 78 | 79 | port=MY_SIP_PORT 80 | tls_port_no=MY_SIPS_PORT 81 | 82 | #!ifdef WITH_TLS 83 | enable_tls=yes 84 | #!endif 85 | 86 | 87 | listen=MY_IP4_ADDR 88 | #!ifdef WITH_LOCALHOST_SIP 89 | listen=IP4_LOCALHOST 90 | #!endif 91 | #!ifdef WITH_IPV6 92 | listen=MY_IP6_ADDR 93 | #!ifdef WITH_LOCALHOST_SIP 94 | listen=IP6_LOCALHOST 95 | #!endif 96 | #!endif 97 | 98 | #!ifdef WITH_WEBSOCKETS 99 | listen=MY_WS4_ADDR 100 | #!ifdef WITH_LOCALHOST_WS 101 | listen=LOCALHOST_WS4_ADDR 102 | #!endif 103 | #!ifdef WITH_IPV6 104 | listen=MY_WS6_ADDR 105 | #!ifdef WITH_LOCALHOST_WS 106 | listen=LOCALHOST_WS6_ADDR 107 | #!endif 108 | #!endif 109 | #!ifdef WITH_TLS 110 | listen=MY_WSS4_ADDR 111 | #!ifdef WITH_LOCALHOST_WS 112 | listen=LOCALHOST_WSS4_ADDR 113 | #!endif 114 | #!ifdef WITH_IPV6 115 | listen=MY_WSS6_ADDR 116 | #!ifdef WITH_LOCALHOST_WS 117 | listen=LOCALHOST_WSS6_ADDR 118 | #!endif 119 | #!endif 120 | #!endif 121 | #!endif 122 | 123 | use_dns_cache = on # Use KAMAILIO internal DNS cache 124 | use_dns_failover = on # Depends on KAMAILIO internal DNS cache 125 | dns_srv_loadbalancing = on # 126 | dns_try_naptr = on # 127 | dns_retr_time=1 # Time in seconds before retrying a DNS request 128 | dns_retr_no=3 # Number of DNS retransmissions before giving up 129 | 130 | # Set protocol preference order - ignore target priority 131 | dns_naptr_ignore_rfc= yes # Ignore target NAPTR priority 132 | dns_tls_pref=50 # First priority: TLS 133 | dns_tcp_pref=30 # Second priority: TCP 134 | dns_udp_pref=10 # Third priority: UDP 135 | 136 | tcp_connection_lifetime=3604 137 | tcp_accept_no_cl=yes 138 | tcp_rd_buf_size=16384 139 | 140 | 141 | # set paths to location of modules (to sources or installation folders) 142 | #!ifdef WITH_SRCPATH 143 | mpath="modules/" 144 | #!else 145 | mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules/" 146 | #!endif 147 | 148 | #!ifdef WITH_MYSQL 149 | loadmodule "db_mysql.so" 150 | #!endif 151 | 152 | loadmodule "kex.so" 153 | loadmodule "corex.so" 154 | loadmodule "tm.so" 155 | loadmodule "tmx.so" 156 | loadmodule "sl.so" 157 | loadmodule "rr.so" 158 | loadmodule "pv.so" 159 | loadmodule "maxfwd.so" 160 | loadmodule "usrloc.so" 161 | loadmodule "registrar.so" 162 | loadmodule "textops.so" 163 | loadmodule "siputils.so" 164 | loadmodule "xlog.so" 165 | loadmodule "sanity.so" 166 | loadmodule "ctl.so" 167 | loadmodule "cfg_rpc.so" 168 | loadmodule "sdpops.so" 169 | loadmodule "textopsx.so" 170 | 171 | #!ifdef WITH_AUTH 172 | loadmodule "auth.so" 173 | loadmodule "auth_db.so" 174 | #!ifdef WITH_IPAUTH 175 | loadmodule "permissions.so" 176 | #!endif 177 | #!endif 178 | 179 | #!ifdef WITH_PRESENCE 180 | loadmodule "presence.so" 181 | loadmodule "presence_xml.so" 182 | #!endif 183 | 184 | #!ifdef WITH_TLS 185 | loadmodule "tls.so" 186 | #!endif 187 | 188 | #!ifdef WITH_HOMER 189 | loadmodule "siptrace.so" 190 | #!endif 191 | 192 | #!ifdef WITH_WEBSOCKETS 193 | loadmodule "xhttp.so" 194 | loadmodule "websocket.so" 195 | loadmodule "nathelper.so" 196 | loadmodule "rtpengine.so" 197 | #!endif 198 | 199 | #!ifdef WITH_ANTIFLOOD 200 | loadmodule "htable.so" 201 | loadmodule "pike.so" 202 | #!endif 203 | 204 | #!ifdef WITH_DEBUG 205 | loadmodule "debugger.so" 206 | #!endif 207 | 208 | # ----------------- setting module-specific parameters --------------- 209 | 210 | 211 | # ----- rr params ----- 212 | # add value to ;lr param to cope with most of the UAs 213 | modparam("rr", "enable_full_lr", 1) 214 | # do not append from tag to the RR (no need for this script) 215 | modparam("rr", "append_fromtag", 0) 216 | 217 | 218 | # ----- registrar params ----- 219 | modparam("registrar", "method_filtering", 1) 220 | # max value for expires of registrations 221 | modparam("registrar", "max_expires", 3600) 222 | 223 | 224 | # ----- usrloc params ----- 225 | /* enable DB persistency for location entries */ 226 | #!ifdef WITH_USRLOCDB 227 | modparam("usrloc", "db_url", DBURL) 228 | modparam("usrloc", "db_mode", 2) 229 | #!endif 230 | 231 | 232 | # ----- auth_db params ----- 233 | #!ifdef WITH_AUTH 234 | modparam("auth_db", "db_url", DBURL) 235 | modparam("auth_db", "calculate_ha1", 1) 236 | modparam("auth_db", "password_column", "password") 237 | modparam("auth_db", "load_credentials", "") 238 | #!endif 239 | 240 | #!ifdef WITH_PRESENCE 241 | # ----- presence params ----- 242 | modparam("presence", "db_url", DBURL) 243 | 244 | # ----- presence_xml params ----- 245 | modparam("presence_xml", "db_url", DBURL) 246 | modparam("presence_xml", "force_active", 1) 247 | #!endif 248 | 249 | 250 | ##!ifdef WITH_NAT 251 | # ----- rtpproxy params ----- 252 | modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:22222") 253 | modparam("rtpengine", "extra_id_pv", "$avp(extra_id)") 254 | 255 | # ----- nathelper params ----- 256 | modparam("nathelper", "natping_interval", 30) 257 | modparam("nathelper", "ping_nated_only", 1) 258 | modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) 259 | modparam("nathelper", "sipping_from", "sip:pinger@XXXX-XXXX") 260 | modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") 261 | modparam("usrloc", "nat_bflag", FLB_NATB) 262 | ##!endif 263 | 264 | # ----- corex params ----- 265 | modparam("corex", "alias_subdomains", "MY_DOMAIN") 266 | 267 | #!ifdef WITH_TLS 268 | # ----- tls params ----- 269 | modparam("tls", "config", "/etc/kamailio/tls.cfg") 270 | modparam("tls", "tls_force_run", 1) 271 | #!endif 272 | 273 | #!ifdef WITH_WEBSOCKETS 274 | modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") 275 | #!endif 276 | 277 | #!ifdef WITH_HOMER 278 | #Siptrace 279 | modparam("siptrace", "duplicate_uri", "sip:127.0.0.1:9060") 280 | modparam("siptrace", "hep_mode_on", 1) 281 | modparam("siptrace", "trace_to_database", 0) 282 | modparam("siptrace", "trace_flag", 22) 283 | modparam("siptrace", "trace_on", 1) 284 | #!endif 285 | 286 | #!ifdef WITH_ANTIFLOOD 287 | # ----- pike params ----- 288 | modparam("pike", "sampling_time_unit", 2) 289 | modparam("pike", "reqs_density_per_unit", 16) 290 | modparam("pike", "remove_latency", 4) 291 | 292 | # ----- htable params ----- 293 | # ip ban htable with autoexpire after 5 minutes 294 | modparam("htable", "htable", "ipban=>size=8;autoexpire=300;") 295 | #!endif 296 | 297 | #!ifdef WITH_DEBUG 298 | # ----- debugger params ----- 299 | modparam("debugger", "cfgtrace", 1) 300 | #!endif 301 | 302 | ####### Routing Logic ######## 303 | request_route { 304 | #!ifdef WITH_HOMER 305 | # start duplicate the SIP message here 306 | sip_trace(); 307 | setflag(22); 308 | #!endif 309 | 310 | # per request initial checks 311 | route(REQINIT); 312 | 313 | xlog("L_INFO", "START: $rm from $fu (IP:$si:$sp)\n"); 314 | 315 | #!ifdef WITH_WEBSOCKETS 316 | if (nat_uac_test(64)) { 317 | # Do NAT traversal stuff for requests from a WebSocket 318 | # connection - even if it is not behind a NAT! 319 | # This won't be needed in the future if Kamailio and the 320 | # WebSocket client support Outbound and Path. 321 | force_rport(); 322 | if (is_method("REGISTER")) { 323 | fix_nated_register(); 324 | } else if (!add_contact_alias()) { 325 | xlog("L_ERR", "Error aliasing contact <$ct>\n"); 326 | sl_send_reply("400", "Bad Request"); 327 | exit; 328 | } 329 | } 330 | #!endif 331 | 332 | # NAT detection 333 | route(NATDETECT); 334 | 335 | # CANCEL processing 336 | if (is_method("CANCEL")) { 337 | if (t_check_trans()) { 338 | route(RELAY); 339 | } 340 | exit; 341 | } 342 | 343 | # handle requests within SIP dialogs 344 | route(WITHINDLG); 345 | 346 | ### only initial requests (no To tag) 347 | 348 | t_check_trans(); 349 | 350 | # authentication 351 | route(AUTH); 352 | 353 | # record routing for dialog forming requests (in case they are routed) 354 | # - remove preloaded route headers 355 | remove_hf("Route"); 356 | if (is_method("INVITE|SUBSCRIBE")) { 357 | record_route(); 358 | } 359 | 360 | # dispatch requests to foreign domains 361 | route(SIPOUT); 362 | 363 | ### requests for my local domains 364 | 365 | # handle presence related requests 366 | route(PRESENCE); 367 | 368 | # handle registrations 369 | route(REGISTRAR); 370 | 371 | if ($rU == $null) { 372 | # request with no Username in RURI 373 | sl_send_reply("484","Address Incomplete"); 374 | exit; 375 | } 376 | 377 | # user location service 378 | route(LOCATION); 379 | } 380 | 381 | # Wrapper for relaying requests 382 | route[RELAY] { 383 | # enable additional event routes for forwarded requests 384 | # - serial forking, RTP relaying handling, a.s.o. 385 | if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) { 386 | if (!t_is_set("branch_route")) { 387 | t_on_branch("MANAGE_BRANCH"); 388 | } 389 | } 390 | 391 | if (is_method("INVITE|SUBSCRIBE|UPDATE")) { 392 | if (!t_is_set("onreply_route")) { 393 | t_on_reply("MANAGE_REPLY"); 394 | } 395 | } 396 | 397 | if (is_method("INVITE")) { 398 | if (!t_is_set("failure_route")) { 399 | t_on_failure("MANAGE_FAILURE"); 400 | } 401 | } 402 | 403 | if (!t_relay()) { 404 | sl_reply_error(); 405 | } 406 | exit; 407 | } 408 | 409 | # Per SIP request initial checks 410 | route[REQINIT] { 411 | #!ifdef WITH_ANTIFLOOD 412 | # flood dection from same IP and traffic ban for a while 413 | # be sure you exclude checking trusted peers, such as pstn gateways 414 | # - local host excluded (e.g., loop to self) 415 | if (src_ip != myself) { 416 | if ($sht(ipban=>$si) != $null) { 417 | # ip is already blocked 418 | xdbg("request from blocked IP - $rm from $fu (IP:$si:$sp)\n"); 419 | exit; 420 | } 421 | 422 | if (!pike_check_req()) { 423 | xlog("L_ALERT","ALERT: pike blocking $rm from $fu (IP:$si:$sp)\n"); 424 | $sht(ipban=>$si) = 1; 425 | exit; 426 | } 427 | } 428 | #!endif 429 | 430 | if (!mf_process_maxfwd_header("10")) { 431 | sl_send_reply("483","Too Many Hops"); 432 | exit; 433 | } 434 | 435 | if (!sanity_check("1511", "7")) { 436 | xlog("Malformed SIP message from $si:$sp\n"); 437 | exit; 438 | } 439 | } 440 | 441 | # Handle requests within SIP dialogs 442 | route[WITHINDLG] { 443 | if (has_totag()) { 444 | # sequential request withing a dialog should 445 | # take the path determined by record-routing 446 | if (loose_route()) { 447 | #!ifdef WITH_WEBSOCKETS 448 | if ($du == "") { 449 | if (!handle_ruri_alias()) { 450 | xlog("L_ERR", "Bad alias <$ru>\n"); 451 | sl_send_reply("400", "Bad Request"); 452 | exit; 453 | } 454 | } 455 | #!endif 456 | route(DLGURI); 457 | if (is_method("ACK")) { 458 | # ACK is forwarded statelessy 459 | route(NATMANAGE); 460 | } else if (is_method("NOTIFY")) { 461 | # Add Record-Route for in-dialog NOTIFY as per RFC 6665. 462 | record_route(); 463 | } 464 | route(RELAY); 465 | } else { 466 | if (is_method("SUBSCRIBE") && uri == myself) { 467 | # in-dialog subscribe requests 468 | route(PRESENCE); 469 | exit; 470 | } 471 | if (is_method("ACK")) { 472 | if (t_check_trans()) { 473 | # no loose-route, but stateful ACK; 474 | # must be an ACK after a 487 475 | # or e.g. 404 from upstream server 476 | route(RELAY); 477 | exit; 478 | } else { 479 | # ACK without matching transaction ... ignore and discard 480 | exit; 481 | } 482 | } 483 | sl_send_reply("404","Not here"); 484 | } 485 | exit; 486 | } 487 | } 488 | 489 | # Handle SIP registrations 490 | route[REGISTRAR] { 491 | if (is_method("REGISTER")) { 492 | if (isflagset(FLT_NATS)) { 493 | setbflag(FLB_NATB); 494 | # uncomment next line to do SIP NAT pinging 495 | ## setbflag(FLB_NATSIPPING); 496 | } 497 | 498 | #!ifdef WITH_IPV6 499 | if (af == INET6) { 500 | setbflag(FLB_IPV6); 501 | } 502 | #!endif 503 | 504 | if (!save("location")) { 505 | sl_reply_error(); 506 | } 507 | 508 | exit; 509 | } 510 | } 511 | 512 | # USER location service 513 | route[LOCATION] { 514 | if (!lookup("location")) { 515 | $var(rc) = $rc; 516 | t_newtran(); 517 | switch ($var(rc)) { 518 | case -1: 519 | case -3: 520 | send_reply("404", "Not Found"); 521 | exit; 522 | case -2: 523 | send_reply("405", "Method Not Allowed"); 524 | exit; 525 | } 526 | } 527 | 528 | route(RELAY); 529 | exit; 530 | } 531 | 532 | # Presence server route 533 | route[PRESENCE] { 534 | if (!is_method("PUBLISH|SUBSCRIBE")) { 535 | return; 536 | } 537 | 538 | if (is_method("SUBSCRIBE") && $hdr(Event) == "message-summary") { 539 | # returns here if no voicemail server is configured 540 | sl_send_reply("404", "No voicemail service"); 541 | exit; 542 | } 543 | 544 | #!ifdef WITH_PRESENCE 545 | if (!t_newtran()) { 546 | sl_reply_error(); 547 | exit; 548 | } 549 | 550 | if (is_method("PUBLISH")) { 551 | handle_publish(); 552 | t_release(); 553 | } else if (is_method("SUBSCRIBE")) { 554 | handle_subscribe(); 555 | t_release(); 556 | } 557 | exit; 558 | #!endif 559 | 560 | # if presence enabled, this part will not be executed 561 | if (is_method("PUBLISH") || $rU == $null) { 562 | sl_send_reply("404", "Not here"); 563 | exit; 564 | } 565 | return; 566 | } 567 | 568 | # Authentication route 569 | route[AUTH] { 570 | #!ifdef WITH_AUTH 571 | if (is_method("REGISTER") || from_uri == myself) { 572 | # authenticate requests 573 | if (!auth_check("$fd", "subscriber", "1")) { 574 | auth_challenge("$fd", "0"); 575 | exit; 576 | } 577 | # user authenticated - remove auth header 578 | if (!is_method("REGISTER|PUBLISH")) { 579 | consume_credentials(); 580 | } 581 | } 582 | # if caller is not local subscriber, then check if it calls 583 | # a local destination, otherwise deny, not an open relay here 584 | if (from_uri != myself && uri != myself) { 585 | sl_send_reply("403","Not relaying"); 586 | exit; 587 | } 588 | 589 | #!endif 590 | return; 591 | } 592 | 593 | # Caller NAT detection route 594 | route[NATDETECT] { 595 | #!ifdef WITH_IPV6 596 | if(af==INET6) { 597 | return; 598 | } 599 | #!endif 600 | 601 | force_rport(); 602 | if (nat_uac_test("19")) { 603 | if (is_method("REGISTER")) { 604 | fix_nated_register(); 605 | } else if (is_first_hop()) { 606 | set_contact_alias(); 607 | } 608 | setflag(FLT_NATS); 609 | } 610 | return; 611 | } 612 | 613 | # NAT handling 614 | route[NATMANAGE] { 615 | if (is_request()) { 616 | if (has_totag()) { 617 | if (check_route_param("nat=yes")) { 618 | setbflag(FLB_NATB); 619 | } 620 | 621 | if (check_route_param("rtp=bridge")) { 622 | setbflag(FLB_BRIDGE); 623 | } 624 | 625 | if (check_route_param("rtp=ws")) { 626 | setbflag(FLB_RTPWS); 627 | } 628 | 629 | #!ifdef WITH_IPV6 630 | if (check_route_param("rtp=v46")) { 631 | setbflag(FLB_V4V6); 632 | } 633 | #!endif 634 | } 635 | } 636 | 637 | if (!isbflagset(FLB_BRIDGE)) { 638 | return; 639 | } 640 | 641 | if ( 642 | !(isflagset(FLT_NATS) 643 | || isbflagset(FLB_NATB) 644 | || isbflagset(FLB_RTPWS) 645 | #!ifdef WITH_IPV6 646 | || isbflagset(FLB_V4V6) 647 | #!endif 648 | )) { 649 | return; 650 | } 651 | 652 | $xavp(r=>$T_branch_idx) = "replace-origin replace-session-connection"; 653 | 654 | if (!nat_uac_test("8")) { 655 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " trust-address"; 656 | } 657 | 658 | 659 | if (is_request()) { 660 | if (!has_totag()) { 661 | if (!t_is_failure_route()) { 662 | $avp(extra_id) = @via[1].branch + $T_branch_idx; 663 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " via-branch=extra"; 664 | } 665 | } 666 | } 667 | 668 | if (is_reply()) { 669 | $avp(extra_id) = @via[2].branch + $T_branch_idx; 670 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " via-branch=extra"; 671 | } 672 | 673 | #!ifdef WITH_IPV6 674 | if (af == INET && isbflagset(FLB_IPV6)) { # IPv4 --> IPv6 675 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " address-family=IP6"; 676 | } else if (af == INET6 && !isbflagset(FLB_IPV6)) { # IPv6 --> IPv4 677 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " address-family=IP4"; 678 | } 679 | #!endif 680 | 681 | if (isbflagset(FLB_RTPWS)) { 682 | if ($proto =~ "ws") { # web --> SIP 683 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " rtcp-mux-demux DTLS=off SDES-off ICE=remove RTP/AVP"; 684 | } else { # SIP --> web 685 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " rtcp-mux-offer generate-mid DTLS=passive SDES-off ICE=force RTP/SAVPF"; 686 | } 687 | } else { 688 | if ($proto =~ "ws") { # web --> web 689 | $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " generate-mid DTLS=passive SDES-off ICE=force"; 690 | } 691 | # else { 692 | # $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + ""; 693 | # } 694 | } 695 | 696 | xlog("L_INFO", "NATMANAGE branch_id:$T_branch_idx ruri: $ru, method:$rm, status:$rs, extra_id: $avp(extra_id), rtpengine_manage: $xavp(r=>$T_branch_idx)\n"); 697 | 698 | rtpengine_manage($xavp(r=>$T_branch_idx)); 699 | 700 | if (is_request()) { 701 | if (!has_totag()) { 702 | if (t_is_branch_route()) { 703 | if (isbflagset(FLB_NATB)) { 704 | add_rr_param(";nat=yes"); 705 | } 706 | 707 | if (isbflagset(FLB_BRIDGE)) { 708 | add_rr_param(";rtp=bridge"); 709 | } 710 | 711 | if (isbflagset(FLB_RTPWS)) { 712 | add_rr_param(";rtp=ws"); 713 | } 714 | 715 | #!ifdef WITH_IPV6 716 | if (isbflagset(FLB_V4V6)) { 717 | add_rr_param(";rtp=v46"); 718 | } 719 | #!endif 720 | } 721 | } 722 | } 723 | 724 | if (is_reply()) { 725 | if (isbflagset(FLB_NATB)) { 726 | if (is_first_hop()) { 727 | if (af == INET) { 728 | set_contact_alias(); 729 | } 730 | } 731 | } 732 | } 733 | return; 734 | } 735 | 736 | # URI update for dialog requests 737 | route[DLGURI] { 738 | if (!isdsturiset()) { 739 | handle_ruri_alias(); 740 | } 741 | return; 742 | } 743 | 744 | # Routing to foreign domains 745 | route[SIPOUT] { 746 | if (!uri == myself) { 747 | append_hf("P-hint: outbound\r\n"); 748 | route(RELAY); 749 | } 750 | } 751 | 752 | route[BRIDGING] { 753 | if (!has_totag()) { 754 | if ($proto =~ "ws" && !($ru =~ "transport=ws")) { # Coming from WS, NOT to WS 755 | setbflag(FLB_RTPWS); # Need bridging 756 | } else if (!($proto =~ "ws") && $ru =~ "transport=ws") { # Coming from NOT WS, going to WS 757 | setbflag(FLB_RTPWS); # Need bridging 758 | } 759 | 760 | #!ifdef WITH_IPV6 761 | if (af == INET6 && !isbflagset(FLB_IPV6)) { 762 | setbflag(FLB_V4V6); 763 | } else if(af == INET && isbflagset(FLB_IPV6)) { 764 | setbflag(FLB_V4V6); 765 | } 766 | #!endif 767 | } 768 | } 769 | 770 | # manage outgoing branches 771 | branch_route[MANAGE_BRANCH] { 772 | xlog("L_INFO", "MANAGE_BRANCH: New branch [$T_branch_idx] to $ru\n"); 773 | 774 | t_on_branch_failure("rtpengine"); 775 | 776 | #!ifndef WITH_BRIDGE_ON_FAIL 777 | setbflag(FLB_BRIDGE); 778 | #!endif 779 | 780 | route(BRIDGING); 781 | route(NATMANAGE); 782 | } 783 | 784 | # manage incoming replies 785 | onreply_route[MANAGE_REPLY] { 786 | xdbg("incoming reply\n"); 787 | if (status =~ "[12][0-9][0-9]") { 788 | route(NATMANAGE); 789 | } 790 | } 791 | 792 | # manage failure routing cases 793 | failure_route[MANAGE_FAILURE] { 794 | xlog("L_INFO", "Failure: $rs"); 795 | } 796 | 797 | #!ifdef WITH_WEBSOCKETS 798 | onreply_route { 799 | if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT) 800 | && !(proto == WS || proto == WSS))) { 801 | xlog("L_WARN", "SIP response received on $Rp\n"); 802 | drop; 803 | } 804 | 805 | if (nat_uac_test(64)) { 806 | # Do NAT traversal stuff for replies to a WebSocket connection 807 | # - even if it is not behind a NAT! 808 | # This won't be needed in the future if Kamailio and the 809 | # WebSocket client support Outbound and Path. 810 | add_contact_alias(); 811 | } 812 | } 813 | 814 | event_route[tm:branch-failure:rtpengine] { 815 | xlog("L_INFO", "BRANCH FAILED: $sel(via[1].branch) + $T_branch_idx"); 816 | 817 | #!ifdef WITH_BRIDGE_ON_FAIL 818 | if (!isbflagset(FLB_BRIDGE) && t_check_status("415|488")) { 819 | t_reuse_branch(); 820 | setbflag(FLB_BRIDGE); 821 | xlog("L_INFO", "event_route[branch-failure:rtpengine]: trying again\n"); 822 | 823 | route(RELAY); 824 | } else { 825 | $avp(extra_id) = @via[1].branch + $T_branch_idx; 826 | rtpengine_delete("via-branch=extra"); 827 | xlog("L_INFO", "event_route[branch-failure:rtpengine]: failed\n"); 828 | } 829 | #!else 830 | $avp(extra_id) = @via[1].branch + $T_branch_idx; 831 | rtpengine_delete("via-branch=extra"); 832 | #!endif 833 | } 834 | 835 | event_route[xhttp:request] { 836 | set_reply_close(); 837 | set_reply_no_connect(); 838 | 839 | if ($Rp != MY_WS_PORT 840 | #!ifdef WITH_TLS 841 | && $Rp != MY_WSS_PORT 842 | #!endif 843 | ) { 844 | xlog("L_WARN", "HTTP request received on $Rp\n"); 845 | xhttp_reply("403", "Forbidden", "", ""); 846 | exit; 847 | } 848 | 849 | xlog("L_INFO", "HTTP Request Received\n"); 850 | 851 | if ($hdr(Upgrade) =~ "websocket" 852 | && $hdr(Connection) =~ "Upgrade" 853 | && $rm =~ "GET" 854 | ) { 855 | 856 | # Validate Host - make sure the client is using the correct 857 | # alias for WebSockets 858 | if ($hdr(Host) == $null || !is_myself("sip:" + $hdr(Host))) { 859 | xlog("L_WARN", "Bad host $hdr(Host)\n"); 860 | xhttp_reply("403", "Forbidden", "", ""); 861 | exit; 862 | } 863 | 864 | # Optional... validate Origin - make sure the client is from an 865 | # authorised website. For example, 866 | # 867 | # if ($hdr(Origin) != "https://example.com" 868 | # && $hdr(Origin) != "https://example.com") { 869 | # xlog("L_WARN", "Unauthorised client $hdr(Origin)\n"); 870 | # xhttp_reply("403", "Forbidden", "", ""); 871 | # exit; 872 | # } 873 | 874 | # Optional... perform HTTP authentication 875 | 876 | # ws_handle_handshake() exits (no further configuration file 877 | # processing of the request) when complete. 878 | if (ws_handle_handshake()) { 879 | # Optional... cache some information about the 880 | # successful connection 881 | exit; 882 | } 883 | } 884 | 885 | xhttp_reply("404", "Not Found", "", ""); 886 | } 887 | 888 | event_route[websocket:closed] { 889 | xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n"); 890 | } 891 | #!endif 892 | -------------------------------------------------------------------------------- /etc/kamailio/kamctlrc: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | # 3 | # The Kamailio configuration file for the control tools. 4 | # 5 | # Here you can set variables used in the kamctl and kamdbctl setup 6 | # scripts. Per default all variables here are commented out, the control tools 7 | # will use their internal default values. 8 | 9 | ## your SIP domain 10 | SIP_DOMAIN=XXXX-XXXX 11 | 12 | ## chrooted directory 13 | # $CHROOT_DIR="/path/to/chrooted/directory" 14 | 15 | ## database type: MYSQL, PGSQL, ORACLE, DB_BERKELEY, DBTEXT, or SQLITE 16 | # by default none is loaded 17 | # 18 | # If you want to setup a database with kamdbctl, you must at least specify 19 | # this parameter. 20 | DBENGINE=MYSQL 21 | 22 | ## database host 23 | DBHOST=localhost 24 | 25 | ## database name (for ORACLE this is TNS name) 26 | DBNAME=kamailio 27 | 28 | # database path used by dbtext, db_berkeley or sqlite 29 | # DB_PATH="/usr/local/etc/kamailio/dbtext" 30 | 31 | ## database read/write user 32 | DBRWUSER="kamailio" 33 | 34 | ## password for database read/write user 35 | DBRWPW="kamailiorw" 36 | 37 | ## database read only user 38 | DBROUSER="kamailioro" 39 | 40 | ## password for database read only user 41 | DBROPW="kamailioro" 42 | 43 | ## database access host (from where is kamctl used) 44 | # DBACCESSHOST=192.168.0.1 45 | 46 | ## database super user (for ORACLE this is 'scheme-creator' user) 47 | # DBROOTUSER="root" 48 | 49 | # user name column 50 | # USERCOL="username" 51 | 52 | 53 | # SQL definitions 54 | # If you change this definitions here, then you must change them 55 | # in db/schema/entities.xml too. 56 | # FIXME 57 | 58 | # FOREVER="2030-05-28 21:32:15" 59 | # DEFAULT_Q="1.0" 60 | 61 | 62 | # Program to calculate a message-digest fingerprint 63 | # MD5="md5sum" 64 | 65 | # awk tool 66 | # AWK="awk" 67 | 68 | # gdb tool 69 | # GDB="gdb" 70 | 71 | # If you use a system with a grep and egrep that is not 100% gnu grep compatible, 72 | # e.g. solaris, install the gnu grep (ggrep) and specify this below. 73 | # 74 | # grep tool 75 | # GREP="grep" 76 | 77 | # egrep tool 78 | # EGREP="egrep" 79 | 80 | # sed tool 81 | # SED="sed" 82 | 83 | # tail tool 84 | # LAST_LINE="tail -n 1" 85 | 86 | # expr tool 87 | # EXPR="expr" 88 | 89 | 90 | # Describe what additional tables to install. Valid values for the variables 91 | # below are yes/no/ask. With ask (default) it will interactively ask the user 92 | # for an answer, while yes/no allow for automated, unassisted installs. 93 | # 94 | 95 | # If to install tables for the modules in the EXTRA_MODULES variable. 96 | # INSTALL_EXTRA_TABLES=ask 97 | 98 | # If to install presence related tables. 99 | # INSTALL_PRESENCE_TABLES=ask 100 | 101 | # If to install uid modules related tables. 102 | # INSTALL_DBUID_TABLES=ask 103 | 104 | # Define what module tables should be installed. 105 | # If you use the postgres database and want to change the installed tables, then you 106 | # must also adjust the STANDARD_TABLES or EXTRA_TABLES variable accordingly in the 107 | # kamdbctl.base script. 108 | 109 | # Kamailio standard modules 110 | # STANDARD_MODULES="standard acc lcr domain group permissions registrar usrloc msilo 111 | # alias_db uri_db speeddial avpops auth_db pdt dialog dispatcher 112 | # dialplan" 113 | 114 | # Kamailio extra modules 115 | # EXTRA_MODULES="imc cpl siptrace domainpolicy carrierroute userblacklist htable purple sca" 116 | 117 | 118 | ## type of aliases used: DB - database aliases; UL - usrloc aliases 119 | ## - default: none 120 | # ALIASES_TYPE="DB" 121 | 122 | ## control engine: FIFO or UNIXSOCK 123 | ## - default FIFO 124 | # CTLENGINE="FIFO" 125 | 126 | ## path to FIFO file 127 | # FIFOPATH="/var/run/kamailio/kamailio_fifo" 128 | 129 | ## check ACL names; default on (1); off (0) 130 | # VERIFY_ACL=1 131 | 132 | ## ACL names - if VERIFY_ACL is set, only the ACL names from below list 133 | ## are accepted 134 | # ACL_GROUPS="local ld int voicemail free-pstn" 135 | 136 | ## verbose - debug purposes - default '0' 137 | # VERBOSE=1 138 | 139 | ## do (1) or don't (0) store plaintext passwords 140 | ## in the subscriber table - default '1' 141 | # STORE_PLAINTEXT_PW=0 142 | 143 | ## Kamailio START Options 144 | ## PID file path - default is: /var/run/kamailio.pid 145 | # PID_FILE=/var/run/kamailio/kamailio.pid 146 | 147 | ## Extra start options - default is: not set 148 | # example: start Kamailio with 64MB share memory: STARTOPTIONS="-m 64" 149 | # STARTOPTIONS= 150 | 151 | -------------------------------------------------------------------------------- /etc/kamailio/tls.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Example Kamailio TLS Configuration File 3 | # 4 | 5 | [server:default] 6 | method = TLSv1+ 7 | verify_certificate = no 8 | require_certificate = no 9 | private_key = privkey.pem 10 | certificate = fullchain.pem 11 | 12 | [client:default] 13 | verify_certificate = yes 14 | require_certificate = yes 15 | -------------------------------------------------------------------------------- /etc/network/if-up.d/iptables: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Restore iptables from stored table 4 | iptables-restore < /etc/iptables/firewall.conf 5 | ip6tables-restore < /etc/iptables/firewall6.conf 6 | -------------------------------------------------------------------------------- /etc/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | server_tokens off; 2 | 3 | add_header X-Frame-Options SAMEORIGIN; 4 | add_header X-Content-Type-Options nosniff; 5 | add_header X-XSS-Protection "1; mode=block"; 6 | add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://assets.zendesk.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://assets.zendesk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://assets.zendesk.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://assets.zendesk.com https://www.facebook.com https://s-static.ak.facebook.com https://tautt.zendesk.com; object-src 'none'"; 7 | 8 | # redirect all http traffic to https 9 | server { 10 | listen 3480 default_server; 11 | listen [::]:3480 default_server; 12 | 13 | server_name _; 14 | 15 | return 301 https://$host$request_uri; 16 | } 17 | 18 | server { 19 | listen 3443 ssl http2; 20 | 21 | server_name _; 22 | 23 | root /var/www/html; 24 | index index.html index.htm; 25 | 26 | ssl_certificate /etc/letsencrypt/live/XXXX-XXXX/fullchain.pem; 27 | ssl_certificate_key /etc/letsencrypt/live/XXXX-XXXX/privkey.pem; 28 | 29 | ssl_session_cache shared:SSL:50m; 30 | ssl_session_timeout 1d; 31 | ssl_session_tickets off; 32 | 33 | # ssl_dhparam /etc/nginx/ssl/dhparam.pem; 34 | ssl_prefer_server_ciphers on; 35 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 36 | ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; 37 | 38 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; 39 | 40 | location /ws { 41 | proxy_pass https://127.0.0.1:4443/; 42 | proxy_http_version 1.1; 43 | proxy_set_header Upgrade $http_upgrade; 44 | proxy_set_header Connection "upgrade"; 45 | proxy_read_timeout 86400; 46 | } 47 | } 48 | 49 | server { 50 | listen [::]:3443 ssl http2; 51 | 52 | server_name _; 53 | 54 | root /var/www/html; 55 | index index.html index.htm; 56 | 57 | ssl_certificate /etc/letsencrypt/live/XXXX-XXXX/fullchain.pem; 58 | ssl_certificate_key /etc/letsencrypt/live/XXXX-XXXX/privkey.pem; 59 | 60 | ssl_session_cache shared:SSL:50m; 61 | ssl_session_timeout 1d; 62 | ssl_session_tickets off; 63 | 64 | # ssl_dhparam /etc/nginx/ssl/dhparam.pem; 65 | ssl_prefer_server_ciphers on; 66 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 67 | ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; 68 | 69 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; 70 | 71 | location /ws { 72 | proxy_pass https://[::1]:4443/; 73 | proxy_http_version 1.1; 74 | proxy_set_header Upgrade $http_upgrade; 75 | proxy_set_header Connection "upgrade"; 76 | proxy_read_timeout 86400; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /etc/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | user nginx; 3 | worker_processes 1; 4 | 5 | error_log /var/log/nginx/error.log warn; 6 | pid /var/run/nginx.pid; 7 | 8 | 9 | events { 10 | worker_connections 1024; 11 | } 12 | 13 | 14 | http { 15 | include /etc/nginx/mime.types; 16 | default_type application/octet-stream; 17 | 18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 19 | '$status $body_bytes_sent "$http_referer" ' 20 | '"$http_user_agent" "$http_x_forwarded_for"'; 21 | 22 | access_log /var/log/nginx/access.log main; 23 | 24 | sendfile on; 25 | #tcp_nopush on; 26 | 27 | keepalive_timeout 65; 28 | 29 | #gzip on; 30 | 31 | include /etc/nginx/conf.d/*.conf; 32 | } 33 | 34 | stream { 35 | log_format basic '$remote_addr [$time_local] ' 36 | '$protocol $status $bytes_sent $bytes_received ' 37 | '$session_time'; 38 | 39 | access_log /var/log/nginx/stream.log basic; 40 | error_log /var/log/nginx/stream-error.log debug; 41 | 42 | upstream turns_ipv4_80 { 43 | server XXXXX-XXXXX:3479; 44 | } 45 | 46 | upstream http_ipv4_80 { 47 | server 127.0.0.1:3480; 48 | } 49 | 50 | map $ssl_preread_protocol $upstream_ipv4_80 { 51 | default turns_ipv4_80; 52 | "" http_ipv4_80; 53 | } 54 | 55 | upstream turns_ipv6_80 { 56 | server [XXXXXX-XXXXXX]:3479; 57 | } 58 | 59 | upstream http_ipv6_80 { 60 | server [::1]:3480; 61 | } 62 | 63 | map $ssl_preread_protocol $upstream_ipv6_80 { 64 | default turns_ipv6_80; 65 | "" http_ipv6_80; 66 | } 67 | 68 | upstream turn_ipv4_443 { 69 | server XXXXX-XXXXX:3478; 70 | } 71 | 72 | upstream https_ipv4_443 { 73 | server 127.0.0.1:3443; 74 | } 75 | 76 | map $ssl_preread_protocol $upstream_ipv4_443 { 77 | default https_ipv4_443; 78 | "" turn_ipv4_443; 79 | } 80 | 81 | upstream turn_ipv6_443 { 82 | server [XXXXXX-XXXXXX]:3478; 83 | } 84 | 85 | upstream https_ipv6_443 { 86 | server [::1]:3443; 87 | } 88 | 89 | map $ssl_preread_protocol $upstream_ipv6_443 { 90 | default https_ipv6_443; 91 | "" turn_ipv6_443; 92 | } 93 | 94 | server { 95 | listen 80; 96 | proxy_pass $upstream_ipv4_80; 97 | ssl_preread on; 98 | proxy_buffer_size 16k; 99 | } 100 | 101 | server { 102 | listen [::]:80; 103 | proxy_pass $upstream_ipv6_80; 104 | ssl_preread on; 105 | proxy_buffer_size 16k; 106 | } 107 | 108 | server { 109 | listen 443; 110 | proxy_pass $upstream_ipv4_443; 111 | ssl_preread on; 112 | proxy_buffer_size 16k; 113 | } 114 | 115 | server { 116 | listen [::]:443; 117 | proxy_pass $upstream_ipv6_443; 118 | ssl_preread on; 119 | proxy_buffer_size 16k; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /etc/nginx/sites-available/default: -------------------------------------------------------------------------------- 1 | server_tokens off; 2 | 3 | add_header X-Frame-Options SAMEORIGIN; 4 | add_header X-Content-Type-Options nosniff; 5 | add_header X-XSS-Protection "1; mode=block"; 6 | add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://assets.zendesk.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://assets.zendesk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://assets.zendesk.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://assets.zendesk.com https://www.facebook.com https://s-static.ak.facebook.com https://tautt.zendesk.com; object-src 'none'"; 7 | 8 | # redirect all http traffic to https 9 | server { 10 | listen 80 default_server; 11 | listen [::]:80 default_server; 12 | 13 | server_name _; 14 | 15 | return 301 https://$host$request_uri; 16 | } 17 | 18 | server { 19 | listen 443 ssl http2; 20 | 21 | server_name _; 22 | 23 | root /var/www/html; 24 | index index.html index.htm; 25 | 26 | ssl_certificate /etc/letsencrypt/live/XXXX-XXXX/fullchain.pem; 27 | ssl_certificate_key /etc/letsencrypt/live/XXXX-XXXX/privkey.pem; 28 | 29 | ssl_session_cache shared:SSL:50m; 30 | ssl_session_timeout 1d; 31 | ssl_session_tickets off; 32 | 33 | # ssl_dhparam /etc/nginx/ssl/dhparam.pem; 34 | ssl_prefer_server_ciphers on; 35 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 36 | ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; 37 | 38 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; 39 | 40 | location /ws { 41 | proxy_pass https://127.0.0.1:4443/; 42 | proxy_http_version 1.1; 43 | proxy_set_header Upgrade $http_upgrade; 44 | proxy_set_header Connection "upgrade"; 45 | proxy_read_timeout 86400; 46 | } 47 | } 48 | 49 | server { 50 | listen [::]:443 ssl http2; 51 | 52 | server_name _; 53 | 54 | root /var/www/html; 55 | index index.html index.htm; 56 | 57 | ssl_certificate /etc/letsencrypt/live/XXXX-XXXX/fullchain.pem; 58 | ssl_certificate_key /etc/letsencrypt/live/XXXX-XXXX/privkey.pem; 59 | 60 | ssl_session_cache shared:SSL:50m; 61 | ssl_session_timeout 1d; 62 | ssl_session_tickets off; 63 | 64 | # ssl_dhparam /etc/nginx/ssl/dhparam.pem; 65 | ssl_prefer_server_ciphers on; 66 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 67 | ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; 68 | 69 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; 70 | 71 | location /ws { 72 | proxy_pass https://[::1]:4443/; 73 | proxy_http_version 1.1; 74 | proxy_set_header Upgrade $http_upgrade; 75 | proxy_set_header Connection "upgrade"; 76 | proxy_read_timeout 86400; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /etc/rtpengine/rtpengine.conf: -------------------------------------------------------------------------------- 1 | [rtpengine] 2 | 3 | table = 0 4 | # no-fallback = false 5 | ### for userspace forwarding only: 6 | # table = -1 7 | 8 | ### a single interface: 9 | interface = pub/XXXXX-XXXXX;pub/XXXXXX-XXXXXX 10 | ### separate multiple interfaces with semicolons: 11 | # interface = internal/12.23.34.45;external/23.34.45.54 12 | ### for different advertised address: 13 | # interface = 12.23.34.45!23.34.45.56 14 | 15 | 16 | listen-ng = 127.0.0.1:22222 17 | # listen-tcp = 25060 18 | # listen-udp = 12222 19 | 20 | timeout = 60 21 | silent-timeout = 3600 22 | tos = 184 23 | # delete-delay = 30 24 | # final-timeout = 10800 25 | 26 | # foreground = false 27 | # pidfile = /var/run/ngcp-rtpengine-daemon.pid 28 | # num-threads = 16 29 | 30 | port-min = 16384 31 | port-max = 16485 32 | # max-sessions = 5000 33 | 34 | # recording-dir = /var/spool/rtpengine 35 | # recording-method = proc 36 | # recording-format = raw 37 | 38 | # redis = 127.0.0.1:6379/5 39 | # redis-write = password@12.23.34.45:6379/42 40 | # redis-num-threads = 8 41 | # no-redis-required = false 42 | # redis-expires = 86400 43 | 44 | # b2b-url = http://127.0.0.1:8090/ 45 | # xmlrpc-format = 0 46 | 47 | # log-level = 6 48 | # log-stderr = false 49 | # log-facility = daemon 50 | # log-facility-cdr = local0 51 | # log-facility-rtcp = local1 52 | 53 | # graphite = 127.0.0.1:9006 54 | # graphite-interval = 60 55 | # graphite-prefix = foobar. 56 | 57 | # homer = 127.0.0.1:9060 58 | # homer-protocol = udp 59 | # homer-id = 2001 60 | 61 | # sip-source = false 62 | # dtls-passive = false 63 | -------------------------------------------------------------------------------- /etc/turnserver.conf: -------------------------------------------------------------------------------- 1 | listening-ip=XXXXX-XXXXX 2 | listening-ip=XXXXXX-XXXXXX 3 | 4 | fingerprint 5 | lt-cred-mech 6 | user=websip:websip 7 | realm=XXXX-XXXX 8 | log-file=/var/log/turn.log 9 | simple-log 10 | 11 | cert=/etc/letsencrypt/live/XXXX-XXXX/fullchain.pem 12 | pkey=/etc/letsencrypt/live/XXXX-XXXX/privkey.pem 13 | -------------------------------------------------------------------------------- /images/webrtc-sip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/havfo/WEBRTC-to-SIP/65d9e7684ce3fb140a147470268e8dd50c8f243a/images/webrtc-sip.png -------------------------------------------------------------------------------- /images/webrtc-sip.svg: -------------------------------------------------------------------------------- 1 | 2 |
WebRTC
WebRTC
WebRTC - SIP gateway



















[Not supported by viewer]
Command
Command
Kamailio
Kamailio
RTPEngine
RTPEngine
SRTP/ICE/RTCP-mux/BUNDLE
SRTP/ICE/RTCP-mux/BUNDLE
SIP - UDP/TCP/TLS
SIP - UDP/TCP/TLS
RTP/noICE/RTCP/noBUNDLE
RTP/noICE/RTCP/noBUNDLE
SIP
SIP
SIP - WS/WSS
SIP - WS/WSS
-------------------------------------------------------------------------------- /iptables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables used in the script 4 | IPTABLES="/sbin/iptables" 5 | IP6TABLES="/sbin/ip6tables" 6 | RTP="16384:16485" 7 | TURN="49152:65535" 8 | 9 | #Flush tables 10 | $IPTABLES -F 11 | $IPTABLES -X 12 | 13 | $IP6TABLES -F 14 | $IP6TABLES -X 15 | 16 | #Default policy 17 | $IPTABLES -P INPUT DROP 18 | $IPTABLES -P FORWARD ACCEPT 19 | $IPTABLES -P OUTPUT ACCEPT 20 | 21 | $IP6TABLES -P INPUT DROP 22 | $IP6TABLES -P FORWARD ACCEPT 23 | $IP6TABLES -P OUTPUT ACCEPT 24 | 25 | # Allow replies to outgoing requests 26 | $IPTABLES -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 27 | $IP6TABLES -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 28 | 29 | # Allow ping 30 | $IPTABLES -A INPUT -p icmp --icmp-type echo-request -j ACCEPT 31 | 32 | $IP6TABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type echo-request -j ACCEPT 33 | $IP6TABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 130 -j ACCEPT 34 | $IP6TABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 133 -m hl --hl-eq 255 -j ACCEPT 35 | $IP6TABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 134 -m hl --hl-eq 255 -j ACCEPT 36 | $IP6TABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 135 -m hl --hl-eq 255 -j ACCEPT 37 | $IP6TABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 136 -m hl --hl-eq 255 -j ACCEPT 38 | $IP6TABLES -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 137 -m hl --hl-eq 255 -j ACCEPT 39 | 40 | # loopback rules 41 | $IPTABLES -A INPUT -i lo -j ACCEPT 42 | $IP6TABLES -A INPUT -i lo -j ACCEPT 43 | 44 | # SIP 45 | $IPTABLES -A INPUT -p tcp --dport 5061 -j ACCEPT 46 | $IPTABLES -A INPUT -p tcp --dport 5060 -j ACCEPT 47 | $IPTABLES -A INPUT -p udp --dport 5060 -j ACCEPT 48 | $IPTABLES -A INPUT -p tcp --dport 4443 -j ACCEPT 49 | $IPTABLES -A INPUT -p tcp --dport 8080 -j ACCEPT 50 | $IP6TABLES -A INPUT -p tcp --dport 5061 -j ACCEPT 51 | $IP6TABLES -A INPUT -p tcp --dport 5060 -j ACCEPT 52 | $IP6TABLES -A INPUT -p udp --dport 5060 -j ACCEPT 53 | $IP6TABLES -A INPUT -p tcp --dport 4443 -j ACCEPT 54 | $IP6TABLES -A INPUT -p tcp --dport 8080 -j ACCEPT 55 | 56 | # HTTP (load + web) 57 | $IPTABLES -A INPUT -p tcp --dport 80 -j ACCEPT 58 | $IPTABLES -A INPUT -p tcp --dport 443 -j ACCEPT 59 | $IPTABLES -A INPUT -p tcp --dport 3443 -j ACCEPT 60 | $IPTABLES -A INPUT -p tcp --dport 3480 -j ACCEPT 61 | $IP6TABLES -A INPUT -p tcp --dport 80 -j ACCEPT 62 | $IP6TABLES -A INPUT -p tcp --dport 443 -j ACCEPT 63 | $IP6TABLES -A INPUT -p tcp --dport 3443 -j ACCEPT 64 | $IP6TABLES -A INPUT -p tcp --dport 3480 -j ACCEPT 65 | 66 | # TURN 67 | $IPTABLES -A INPUT -p udp --dport 3478 -j ACCEPT 68 | $IPTABLES -A INPUT -p tcp --dport 3478 -j ACCEPT 69 | $IPTABLES -A INPUT -p tcp --dport 3479 -j ACCEPT 70 | $IP6TABLES -A INPUT -p udp --dport 3478 -j ACCEPT 71 | $IP6TABLES -A INPUT -p tcp --dport 3478 -j ACCEPT 72 | $IP6TABLES -A INPUT -p tcp --dport 3479 -j ACCEPT 73 | 74 | # RTPEngine 75 | $IPTABLES -I INPUT -p udp -j RTPENGINE --dport $RTP --id 0 76 | $IP6TABLES -I INPUT -p udp -j RTPENGINE --dport $RTP --id 0 77 | 78 | # Allow TURN RTP 79 | $IPTABLES -I INPUT -p udp --dport $TURN -j ACCEPT 80 | $IP6TABLES -I INPUT -p udp --dport $TURN -j ACCEPT 81 | 82 | # Brute-force block 83 | $IPTABLES -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name DEFAULT --rsource 84 | $IP6TABLES -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name DEFAULT --rsource 85 | $IPTABLES -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 180 --hitcount 3 --name DEFAULT --rsource -j DROP 86 | $IP6TABLES -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 180 --hitcount 3 --name DEFAULT --rsource -j DROP 87 | 88 | # Allow SSH 89 | $IPTABLES -A INPUT -p tcp --dport 22 -j ACCEPT 90 | $IP6TABLES -A INPUT -p tcp --dport 22 -j ACCEPT 91 | 92 | # Save IP-tables and save for enabling after reboot 93 | /sbin/iptables-save > /etc/iptables/firewall.conf 94 | /sbin/ip6tables-save > /etc/iptables/firewall6.conf 95 | --------------------------------------------------------------------------------