Occassional posts about VoIP, SIP, WebRTC and Bitcoin.
The Bitcoin Lightning Network is hopefully the first step towards instant and cheap transactions backed by the security of the Bitcoin blockchain. While that’s an easy sentence to write it’s very tricky concept to implement. I’m nowhere near qualified to delve into the trust and cryptographic issues designed to ensure the parties involved in a lightning payment can’t cheat each other. Instead this post documents the steps involved in the rather contrived experiment of attempting to transfer a 1 milli Satoshi BTC payment from a customer lightning node to a merchant lightning node.
There are a few different lightning implementations available, and hopefully more to come, the ones I have been testing out are c-lightning and lnd. For this experiment I’ve used c-lightning.
The steps to install c-lightning are available here. A bitcoin-core daemon is required to interface with the Bitcoin network.
At this point I should point out that a 1 milli satoshi payment made in isolation makes no sense. That is if a customer opened a lightning channel to a merchant, paid the 1 milli satoshi and closed the channel then the cost of the Bitcoin transaction fees to fund and withdraw from the channel are likely to be in the order of 300 Satoshis. That means the transaction fees are 300,000 times the cost of the payment BUT that’s not the point. With a lightning channel the Bitcoin fees are only paid when a channel is initially funded and withdrawn. If a merchant has a product that costs 1 milli satoshi (USD0.000004 at the time of writing) then hopefully they will be selling lots of them meaning that a customer will keep the channel open and make 100’s of payments before closing the channel (or even just keep it open until all funds are used up).
In the exchange documented below I have two separate lightning/bitcoin-core nodes. One running on my Windows 10 machine, using the customer hostname, and the second on my QNAP NAS in a Linux virtual container, using the merchant hostname.
merchant$ lightning-cli getinfo { "id": "03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be", "alias": "BIZARREMONTANA", "color": "03a91f", "address": [ { "type": "ipv6", "address": "2a02:120b:2c13:a510:5054:ff:fed8:d0a4", "port": 9735 } ], "binding": [ { "type": "ipv6", "address": "::", "port": 9735 }, { "type": "ipv4", "address": "0.0.0.0", "port": 9735 } ], "version": "v0.6.2-51-gd5aaa11", "blockheight": 1445970, "network": "testnet", "msatoshi_fees_collected": 0 } customer$ lightning-cli getinfo { "id": "032465b71102c76c473f35927b1b2825c9bdeb11624fdc8d1a705f77e655f1c442", "alias": "ORANGENET", "color": "032465", "address": [ { "type": "ipv6", "address": "2a02:120b:2c13:a510:f4b9:89fb:e41c:16be", "port": 9735 } ], "binding": [ { "type": "ipv6", "address": "::", "port": 9735 }, { "type": "ipv4", "address": "0.0.0.0", "port": 9735 } ], "version": "v0.6.2-51-gd5aaa11", "blockheight": 1445970, "network": "testnet", "msatoshi_fees_collected": 0 }
After the two test nodes are running the next steps are to connect them together and fund the client channel.
customer$ lightning-cli newaddr { "address": "tb1qvf26cxv45vudsawwspl09nzvkfrw87mvyc0dr0" } # Use your favourite Bitcoin wallet to send funds your respective address (TESTNET only of course). Recommended minimum is 2000 Satoshis or 0.00002 tBTC. # Connect to the merchant node. The public key is shown in the getinfo response and you need to know the IP address. customer$ lightning-cli connect 03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be@192.168.1.10 # After the two lightning nodes are connected a channel needs to be set up and funded. # There is a minimum channel reserve of 1000 Satoshis which I assume is for the closing fee. 2000 Satoshis was what I settled on for the minimum channel funding amount. customer$ lightning-cli fundchannel 03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be 2000
Generate the 1 milli Satoshi invoice on the merchant
merchant$ lightning-cli invoice 1 onemilli onemilli { "payment_hash": "742c20e3cc37d903bd227afca050504a979fe2962a2ab14552d2f7d142409bbe", "expires_at": 1543527370, "bolt11": "lntb10p1pwqqnd6pp5wskzpc7vxlvs80fz0t72q5zsf2telc5k9g4tz32j6tmazsjqnwlqdqddahx2mtfd3kxjcqp2rzjqvjxtdc3qtrkc3elxkf8kxegyhymm6c3vf8aerg6wp0h0ej478zyy9ssfyqqqggqqqqqqqqpqqqqqzsqqcllp9hknpu5fmyvgenw3r7mp4sgukkhxy5hp3ulawgueluwav07hs6g4vzs8wshs7chdvkl5t8h0ltwrg0t57k7083rtlrfwm2ngm33gq42cn3x" }
During testing I frequently got a warning message of “No channels have sufficient incoming capacity” which I initially took as a meaning any attempt to pay the invoice would fail but as best I can tell it’s not an issue and payments will be accepted.
Pay the invoice with the client.
customer$ lightning-cli pay lntb10p1pwqqnd6pp5wskzpc7vxlvs80fz0t72q5zsf2telc5k9g4tz32j6tmazsjqnwlqdqddahx2mtfd3kxjcqp2rzjqvjxtdc3qtrkc3elxkf8kxegyhymm6c3vf8aerg6wp0h0ej478zyy9ssfyqqqggqqqqqqqqpqqqqqzsqqcllp9hknpu5fmyvgenw3r7mp4sgukkhxy5hp3ulawgueluwav07hs6g4vzs8wshs7chdvkl5t8h0ltwrg0t57k7083rtlrfwm2ngm33gq42cn3x { "id": 3, "payment_hash": "742c20e3cc37d903bd227afca050504a979fe2962a2ab14552d2f7d142409bbe", "destination": "03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be", "msatoshi": 1, "msatoshi_sent": 1, "created_at": 1543524861, "status": "complete", "payment_preimage": "2d9ede49827fdb67ec1fa9a9356aa17fcda839e2d2499b1fc2d509b346eef803", "description": "onemilli", "getroute_tries": 1, "sendpay_tries": 1, "route": [ { "id": "03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be", "channel": "1445961:33:0", "msatoshi": 1, "delay": 10 } ], "failures": [ ] }
Check the merchant and customer node to confirm the payment has occurred.
merchant$ lightning-cli listpeers { "peers": [ { "id": "032465b71102c76c473f35927b1b2825c9bdeb11624fdc8d1a705f77e655f1c442", "connected": true, "netaddr": [ "[::ffff:192.168.1.50]:57236" ], "global_features": "", "local_features": "8a", "globalfeatures": "", "localfeatures": "8a", "channels": [ { "state": "CHANNELD_NORMAL", "scratch_txid": "ef8832dbc3ea36a1bb622d1db3097c368d8ed144fcf8c1b76421724ab2888146", "owner": "lightning_channeld", "short_channel_id": "1445961:33:0", "channel_id": "5da300775abce3c1fd276a37ba2f3548303bf06fe0c27f8959ca5f763f1281aa", "funding_txid": "aa81123f765fca59897fc2e06ff03b3048352fba376a27fdc1e3bc5a7700a35d", "msatoshi_to_us": 1, "msatoshi_to_us_min": 0, "msatoshi_to_us_max": 1, "msatoshi_total": 2000000, "dust_limit_satoshis": 546, "max_htlc_value_in_flight_msat": 18446744073709551615, "their_channel_reserve_satoshis": 546, "our_channel_reserve_satoshis": 546, "spendable_msatoshi": 0, "htlc_minimum_msat": 0, "their_to_self_delay": 6, "our_to_self_delay": 6, "max_accepted_htlcs": 483, "status": [ "CHANNELD_NORMAL:Funding transaction locked. Channel announced." ], "in_payments_offered": 1, "in_msatoshi_offered": 1, "in_payments_fulfilled": 1, "in_msatoshi_fulfilled": 1, "out_payments_offered": 0, "out_msatoshi_offered": 0, "out_payments_fulfilled": 0, "out_msatoshi_fulfilled": 0, "htlcs": [ ] } ] } ] } customer$ lightning-cli listpeers { "peers": [ { "id": "03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be", "connected": true, "netaddr": [ "192.168.1.10:9735" ], "global_features": "", "local_features": "8a", "globalfeatures": "", "localfeatures": "8a", "channels": [ { "state": "CHANNELD_NORMAL", "scratch_txid": "0b105e7e51503471fd74992ea3cd7f73e07a713a2a88d0351f599492775f7dd4", "owner": "lightning_channeld", "short_channel_id": "1445961:33:0", "channel_id": "5da300775abce3c1fd276a37ba2f3548303bf06fe0c27f8959ca5f763f1281aa", "funding_txid": "aa81123f765fca59897fc2e06ff03b3048352fba376a27fdc1e3bc5a7700a35d", "msatoshi_to_us": 1999999, "msatoshi_to_us_min": 1999999, "msatoshi_to_us_max": 2000000, "msatoshi_total": 2000000, "dust_limit_satoshis": 546, "max_htlc_value_in_flight_msat": 18446744073709551615, "their_channel_reserve_satoshis": 546, "our_channel_reserve_satoshis": 546, "spendable_msatoshi": 1453999, "htlc_minimum_msat": 0, "their_to_self_delay": 6, "our_to_self_delay": 6, "max_accepted_htlcs": 483, "status": [ "CHANNELD_NORMAL:Funding transaction locked. Channel announced." ], "in_payments_offered": 0, "in_msatoshi_offered": 0, "in_payments_fulfilled": 0, "in_msatoshi_fulfilled": 0, "out_payments_offered": 1, "out_msatoshi_offered": 1, "out_payments_fulfilled": 1, "out_msatoshi_fulfilled": 1, "htlcs": [ ] } ] } ] }
The 1 milli Satoshi payment has now been accepted so the channel can be closed.
customer$ lightning-cli close 03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be { "tx": "02000000015da300775abce3c1fd276a37ba2f3548303bf06fe0c27f8959ca5f763f1281aa0000000000ffffffff011807000000000000160014d5f1e21cab97890c2b938318dda83024fb5ce8fa00000000", "txid": "0179d50a2b96a709d91ba36235092ed4db4341c85f881d38458700603d58b41b", "type": "mutual" } customer$ lightning-cli listpeers { "peers": [ { "id": "03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be", "connected": false, "channels": [ { "state": "CLOSINGD_COMPLETE", "scratch_txid": "0179d50a2b96a709d91ba36235092ed4db4341c85f881d38458700603d58b41b", "short_channel_id": "1445961:33:0", "channel_id": "5da300775abce3c1fd276a37ba2f3548303bf06fe0c27f8959ca5f763f1281aa", "funding_txid": "aa81123f765fca59897fc2e06ff03b3048352fba376a27fdc1e3bc5a7700a35d", "msatoshi_to_us": 1999999, "msatoshi_to_us_min": 1999999, "msatoshi_to_us_max": 2000000, "msatoshi_total": 2000000, "dust_limit_satoshis": 546, "max_htlc_value_in_flight_msat": 18446744073709551615, "their_channel_reserve_satoshis": 546, "our_channel_reserve_satoshis": 546, "spendable_msatoshi": 1453999, "htlc_minimum_msat": 0, "their_to_self_delay": 6, "our_to_self_delay": 6, "max_accepted_htlcs": 483, "status": [ "CLOSINGD_SIGEXCHANGE:We agreed on a closing fee of 183 satoshi" ], "in_payments_offered": 0, "in_msatoshi_offered": 0, "in_payments_fulfilled": 0, "in_msatoshi_fulfilled": 0, "out_payments_offered": 1, "out_msatoshi_offered": 1, "out_payments_fulfilled": 1, "out_msatoshi_fulfilled": 1, "htlcs": [ ] } ] } ] }
The channel is now closed and the Bitcoin transaction that returns the funds to each lightning node has now been broadcast. The next thing that happens I don’t understand. Once the close transaction has one confirmation the channel status reports that 100 more blocks are required before the channel can be forgotten. And until that happens the funds from the channel don’t show up in the lightning node’s available funds.
customer$ lightning-cli listpeers { "peers": [ { "id": "03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be", "connected": false, "channels": [ { "state": "ONCHAIN", "scratch_txid": "0179d50a2b96a709d91ba36235092ed4db4341c85f881d38458700603d58b41b", "owner": "lightning_onchaind", "short_channel_id": "1445961:33:0", "channel_id": "5da300775abce3c1fd276a37ba2f3548303bf06fe0c27f8959ca5f763f1281aa", "funding_txid": "aa81123f765fca59897fc2e06ff03b3048352fba376a27fdc1e3bc5a7700a35d", "msatoshi_to_us": 1999999, "msatoshi_to_us_min": 1999999, "msatoshi_to_us_max": 2000000, "msatoshi_total": 2000000, "dust_limit_satoshis": 546, "max_htlc_value_in_flight_msat": 18446744073709551615, "their_channel_reserve_satoshis": 546, "our_channel_reserve_satoshis": 546, "spendable_msatoshi": 1453999, "htlc_minimum_msat": 0, "their_to_self_delay": 6, "our_to_self_delay": 6, "max_accepted_htlcs": 483, "status": [ "CLOSINGD_SIGEXCHANGE:We agreed on a closing fee of 183 satoshi", "ONCHAIN:Tracking mutual close transaction", "ONCHAIN:All outputs resolved: waiting 96 more blocks before forgetting channel" ], "in_payments_offered": 0, "in_msatoshi_offered": 0, "in_payments_fulfilled": 0, "in_msatoshi_fulfilled": 0, "out_payments_offered": 1, "out_msatoshi_offered": 1, "out_payments_fulfilled": 1, "out_msatoshi_fulfilled": 1, "htlcs": [ ] } ] } ] } customer$ lightning-cli listfunds { "outputs": [ { "txid": "0179d50a2b96a709d91ba36235092ed4db4341c85f881d38458700603d58b41b", "output": 0, "value": 1816, "address": "tb1q6hc7y89tj7ysc2unsvvdm2psyna4e686pn98g5", "status": "confirmed" } ], "channels": [ { "peer_id": "03a91ff1bf824e398e07ae2c9dd3ea50081c88350ee896a3eb81c8517e5b0ab7be", "short_channel_id": "1445961:33:0", "channel_sat": 2000, "channel_total_sat": 2000, "funding_txid": "aa81123f765fca59897fc2e06ff03b3048352fba376a27fdc1e3bc5a7700a35d" } ] }
The merchant has no funds. It’s almost certain that the mutual 183 Satoshi fee for the close transaction removes any entitlement the merchant has to 1 milli Satoshi.
Approximately 28 hours later (after 100 x 10 min blocks) the funds on the client return from the channel to the outputs and can be sent from the lightning node using the withdraw <amount> all command.
The lightning channel does a fantastic job of facilitating instant Bitcoin transfers of funds. It’s also easy to get the add the initial funds into a channel. The main pain points are the inability to add funds to an existing channel, to be able to add funds to both ends of a channel and the delay on the extraction of funds.
Update – 2 Jun 2019: An updated and more comprehensive article for a BTCPay Server Manual install is now available in the official documentation.
BTCPay Server is an open source project that allows merchants to accept cryptocurrency payments using their OWN self-hosted server. Being an OWN solution is a big deal. As someone who has dealt with payment processors for online payment processing for 20 years I can vouch for the fact that it’s always been a big pain point to the hip pocket. It generally boils down to a choice between PayPal or the hassle of setting up a merchant account to be able to accept credit card payments. In the last 4 or 5 years crypto-currencies have become another alternative but that option has always involved another 3rd party processor and it hardly seems worth the effort to switch from PayPal to a cryptocurrency gateway and incur the inevitable customer confusion for roughly the same level of cost.
With BTCPay Server things have changed. Now as a merchant it’s feasible to accept payments without the need for any kind of central payment processor. If you need to convert your cytpo-currency, such as Bitcoin, to a fiat currency, such as USD, then you’ll need to use a gateway but hopefully over time the need to do that will diminish.
The easy way to get up and running with BTCPayServer is to use a hosted set up as per the all-in-one wizard from Luna Node (note that this existence of the wizard owes a huge amount to bitcoinshirt.co if you find it useful buy something from him).
This post is about the hard way of setting up a minimal BTCPayServer:
The instructions in this post are probably only useful for very technical users or programmers. For merchants a far easier and more sensible option is the Luna Node wizard.
The steps below were all been carried out on an Ubuntu 18.04 virtual machine.
First step is to set up a bitcoin daemon and synchronise the blockchain which should definitely be testnet at this stage.
Below are the steps I use but with bitcoin you should always be careful about what to trust. Ideally double check that the PGP key I refer to matches what’s listed on other other web sites. This verification process is not academic. For example in 2017 when Bitcoin Gold forked their blockchain I downloaded their software and performed the verification steps only to find the SHA checksum failed. Consequently I did not install the software and shortly thereafter a critical warning notice was issued.
To run bitcoind as a service there is a systemd configuration template available here.
The basic /etc/bitcoin/bitcoin.conf file I use is:
server=1 datadir=/blockchains/bitcoin listen=1 rpcauth=admin:91f1e6765bb4f8d6ba3a2da16cbd4b1$24f22f6fee475c15a24182d01cd060922e2dff1de9b633ce486f2a68cf9687b0 # admin/password rpcallowip=192.168.1.0/24 testnet=1 whitelist=0.0.0.0/0 walletbroadcast=0
If the bitcoind service doesn’t start use:
sudo journalctl –unit bitcoind –follow
Check the status of the blockchain synchronisation:
bitcoin-cli -testnet -rpccookiefile=/blockchains/bitcoin/testnet3/.cookie getblockchaininfo
NBXplorer and any Lightning servers will need to connect to the bitcoind via its RPC interface. To use the interface either a username/password is required or alternatively bitcoind generates a cookie file with a random value that can be used. If the bitcoind data directory is left in its default location then NBXplorer and the Lightning servers should automatically find the cookie file. If the data directory is moved, for example to take advantage of a network drive then an extra configuration option will be required and the instructions in the relevant sections below show them.
The bitcoin-cli client also needs to be authenticated. To save some typing create a ~/.bitcoin directory, e.g. /home/admin/.bitcoin, and within it create a bitcoin.conf file along the lines of:
testnet=1 rpccookiefile=/blockchains/bitcoin/testnet3/.cookie
The second step is to install a Lightning node. For c-lightning the software must be built from source as per the instructions here. Once built lightning can also be configured to run as a systemd daemon.
lightning-cli getinfo
.To run lightningd as a service there is a systemd configuration template available here.
The basic /etc/lightningd/lightningd.conf that I use is:
network=testnet log-level=debug addr=0.0.0.0 bitcoin-datadir=/blockchains/bitcoin
If the lightningd service doesn’t start use:
sudo journalctl –unit lightningd –follow
Check the daemon with:
lightning-cli getinfo
The third step is to install NBXplorer. Its purpose is to monitor the Bitcoin blockchain so merchants know when they have received a payment. NBXplorer requires dotnet core.
NBXplorer config file in /home/admin/.nbxplorer/TestNet/settings.conf
rpccookiefile=/blockchains/bitcoin/testnet3/.cookie port=24445 bind=0.0.0.0 testnet=1
My basic NBXplorer systemd service file:
[Unit] Description=NBXplorer daemon Requires=bitcoind.service After=bitcoind.service [Service] ExecStart=/usr/bin/dotnet "/home/admin/src/NBXplorer/NBXplorer/bin/Release/netcoreapp2.1/NBXplorer.dll" --network=testnet User=admin Group=admin Type=simple PIDFile=/run/nbxplorer/nbxplorer.pid Restart=on-failure PrivateTmp=true ProtectSystem=full NoNewPrivileges=true PrivateDevices=true [Install] WantedBy=multi-user.target
The fourth step is to install BTCPayServer. This is the web application that will be used by the merchant for administering their stores, invoices etc.
BtcPayServer config file in /home/admin/.btcpayserver/TestNet/settings.conf
network=testnet port=23001 bind=0.0.0.0 testnet=1
My basic BTCPayServer systemd service file:
[Unit] Description=BtcPayServer daemon Requires=btcpayserverd.service After=nbxplorerd.service [Service] WorkingDirectory=/home/admin/src/btcpayserver/BTCPayServer ExecStart=/usr/bin/dotnet "/home/admin/src/btcpayserver/BTCPayServer/bin/Release/netcoreapp2.1/BTCPayServer.dll" --testnet User=admin Group=admin Type=simple PIDFile=/run/btcpayserverd/btcpayserverd.pid Restart=on-failure [Install] WantedBy=multi-user.target
There are two additional steps that the Luna Node wizard performs:
Once BTCPayServer is running there are still quite a few steps required before being able to accept payments:
With WebRTC starting to gain momentum I have been doing a bit of work recently on integrating a custom .Net/C++ application with WebRTC capable browsers (at the time of writing Chrome and FireFox). It’s been challenging work and there are a lot of moving parts involved with WebRTC. The good thing is once that once the integration challenges have been overcome WebRTC streaming works very well and my experience so far of WebRTC video streams are a lot better than those I’ve had with SIP video soft phones.
The SIPSorcery code base now has all the components needed to develop prototype applications that can integrate with WebRTC browsers. An example of a WebRTC video test pattern can be found here. While it is a very rudimentary application that simply adds a timestamp to a static JPEG image it does demonstrate the network and encoding pipeline necessary to get media streams working between a .Net application and a WebRTC browser.
The good thing is that if you have a project that needs .Net and WebRTC you might be able to use the SIPSorcery code as your starting point and perhaps even contribute to it. To create your own WebRTC project you need to add the SIPSorcery and SIPSorceryMedia nuget packages. The SIPSorceryMedia package uses some native and C++ dll’s and does require the Visual C++ Redistributable Packages for Visual Studio 2013 to be installed on any machine that executes it. It’s also built for x64 only so won’t run on 32 bit systems (I can build it for other platforms if anyone ever has a need).
An example of how to use the WebRTC components is in the source code of the WebRTCVideoSample project referenced above. The main classes are WebRTCDaemon and WebRtcSession. The WebRtcSession class is where the ICE connection establishment is coordinated and the connection to the WebRTC browser is set up. The WebRTCDaemon does the web socket signalling, which is not part of WebRTC but does provide a handy way to get the session established, and also the VP8 encoding of the timestamped JPEG image. I’ve included the WebRtcSession code below to show the pretty small amount of code required.
using System; using System.Linq; using System.Net; using System.Net.Sockets; using SIPSorceryMedia; using SIPSorcery.Net; using SIPSorcery.Sys; using log4net; namespace WebRTCVideoServer { public class WebRtcSession { private const int RTP_MAX_PAYLOAD = 1400; private const int TIMESTAMP_SPACING = 3000; private const int PAYLOAD_TYPE_ID = 100; private const int SRTP_AUTH_KEY_LENGTH = 10; private static ILog logger = AppState.logger; public WebRtcPeer Peer; public DtlsManaged DtlsContext; public SRTPManaged SrtpContext; public SRTPManaged SrtpReceiveContext; // Used to decrypt packets received from the remote peer. public string CallID { get { return Peer.CallID; } } public WebRtcSession(string callID) { Peer = new WebRtcPeer() { CallID = callID }; } public void DtlsPacketReceived(IceCandidate iceCandidate, byte[] buffer, IPEndPoint remoteEndPoint) { logger.Debug("DTLS packet received " + buffer.Length + " bytes from " + remoteEndPoint.ToString() + "."); if (DtlsContext == null) { DtlsContext = new DtlsManaged(); int res = DtlsContext.Init(); logger.Debug("DtlsContext initialisation result=" + res); } int bytesWritten = DtlsContext.Write(buffer, buffer.Length); if (bytesWritten != buffer.Length) { logger.Warn("The required number of bytes were not successfully written to the DTLS context."); } else { byte[] dtlsOutBytes = new byte[2048]; int bytesRead = DtlsContext.Read(dtlsOutBytes, dtlsOutBytes.Length); if (bytesRead == 0) { logger.Debug("No bytes read from DTLS context :(."); } else { logger.Debug(bytesRead + " bytes read from DTLS context sending to " + remoteEndPoint.ToString() + "."); iceCandidate.LocalRtpSocket.SendTo(dtlsOutBytes, 0, bytesRead, SocketFlags.None, remoteEndPoint); //if (client.DtlsContext.IsHandshakeComplete()) if (DtlsContext.GetState() == 3) { logger.Debug("DTLS negotiation complete for " + remoteEndPoint.ToString() + "."); SrtpContext = new SRTPManaged(DtlsContext, false); SrtpReceiveContext = new SRTPManaged(DtlsContext, true); Peer.IsDtlsNegotiationComplete = true; iceCandidate.RemoteRtpEndPoint = remoteEndPoint; } } } } public void MediaPacketReceived(IceCandidate iceCandidate, byte[] buffer, IPEndPoint remoteEndPoint) { if ((buffer[0] >= 128) && (buffer[0] <= 191)) { //logger.Debug("A non-STUN packet was received Receiver Client."); if (buffer[1] == 0xC8 /* RTCP SR */ || buffer[1] == 0xC9 /* RTCP RR */) { // RTCP packet. //webRtcClient.LastSTUNReceiveAt = DateTime.Now; } else { // RTP packet. //int res = peer.SrtpReceiveContext.UnprotectRTP(buffer, buffer.Length); //if (res != 0) //{ // logger.Warn("SRTP unprotect failed, result " + res + "."); //} } } else { logger.Debug("An unrecognised packet was received on the WebRTC media socket."); } } public void Send(byte[] buffer) { try { Peer.LastTimestamp = (Peer.LastTimestamp == 0) ? RTSPSession.DateTimeToNptTimestamp32(DateTime.Now) : Peer.LastTimestamp + TIMESTAMP_SPACING; for (int index = 0; index * RTP_MAX_PAYLOAD < buffer.Length; index++) { int offset = (index == 0) ? 0 : (index * RTP_MAX_PAYLOAD); int payloadLength = (offset + RTP_MAX_PAYLOAD < buffer.Length) ? RTP_MAX_PAYLOAD : buffer.Length - offset; byte[] vp8HeaderBytes = (index == 0) ? new byte[] { 0x10 } : new byte[] { 0x00 }; RTPPacket rtpPacket = new RTPPacket(payloadLength + SRTP_AUTH_KEY_LENGTH + vp8HeaderBytes.Length); rtpPacket.Header.SyncSource = Peer.SSRC; rtpPacket.Header.SequenceNumber = Peer.SequenceNumber++; rtpPacket.Header.Timestamp = Peer.LastTimestamp; rtpPacket.Header.MarkerBit = ((offset + payloadLength) >= buffer.Length) ? 1 : 0; // Set marker bit for the last packet in the frame. rtpPacket.Header.PayloadType = PAYLOAD_TYPE_ID; Buffer.BlockCopy(vp8HeaderBytes, 0, rtpPacket.Payload, 0, vp8HeaderBytes.Length); Buffer.BlockCopy(buffer, offset, rtpPacket.Payload, vp8HeaderBytes.Length, payloadLength); var rtpBuffer = rtpPacket.GetBytes(); int rtperr = SrtpContext.ProtectRTP(rtpBuffer, rtpBuffer.Length - SRTP_AUTH_KEY_LENGTH); if (rtperr != 0) { logger.Warn("SRTP packet protection failed, result " + rtperr + "."); } else { var connectedIceCandidate = Peer.LocalIceCandidates.Where(y => y.RemoteRtpEndPoint != null).First(); connectedIceCandidate.LocalRtpSocket.SendTo(rtpBuffer, connectedIceCandidate.RemoteRtpEndPoint); } } } catch (Exception sendExcp) { // logger.Error("SendRTP exception sending to " + client.SocketAddress + ". " + sendExcp.Message); } } } }
The SIPSorceryMedia assembly utilises the Cisco libsrtp (for secure RTP), openssl (for DTLS) and libvpx (for VP8 encoding). It also makes use of Microsoft’s Media Foundation and the ffmpeg libraries for various bits and pieces.
After a brief interlude (nearly 3 years) I recently got motivated to look at using the SIPSorcery SIP stack to build a video softphone. Of course there are already a number of fully featured video softphones available so the project was for fun rather than to solve any particular problem.
Unlike my previous attempts this time I have been successful and the image below shows the video softphone prototype on a call with CounterPath’s Bria Softphone (that’s me chatting to Max).
I did end up using Microsoft’s Media Foundation (MF) for the getting samples from my web cam but I gave up on trying to use the MF H264 codec and instead used the VP8 codec from the webproject. A motivation to use the VP8 codec is that it was the initial codec proposed for WebRTC and at some point I’d like to experiment with placing calls from the softphone to a browser.
The video softphone is available in at the sipsorcery codeplex repository under the sipsorcery-softphonev2 folder. All the MF and libvpx integration is contained in the sipsorcery-media folder.
The new softphone is purely experimental and video calls do not even work with the only other softphone I tested with, jitsi, due to a VP8 framing problem. But it is a working implementation of a Windows video softphone so may prove useful for anyone who wants to do some work in those areas.
Enjoy.
The SIPSorcery SIP server will be moving to a new virtual machine instance tomorrow, Thursday 8 Jan 2015, at 0200 PST. There will be a brief disruption to the SIP services of approximately 5 to 10 minutes while the IP address is switched over. The reason for the migration is to move to a more up to date operating system version. No user action is required. The existing IP address will continue to be used on the new virtual machine.
The SSL certificate for the sipsorcery web server (www.sipsorcery.com) has been updated. Unfortunately GoDaddy signed the new certificate with a different intermediate certificate. This doesn’t impact browsers but anyone using the web services from Ruby may need to reference a different certificate bundle file. The new bundle can be downloaded from here.
The SIPSorcery web site and web services will be moving to a new server within the next few days. As a few people have noted the current web site hosting arrangements are not ideal with regular interruptions due to the site getting “recycled” by the host due to “excessive resource utilisation”. Apparently the issue relates to the connections from the Silverlight client Console page which repeatedly poll the SIPSorcery SIP servers for new messages.
To overcome the issue the web sites will be moved onto a new virtual machine that is physically next to the SIPSorcery SIP servers.
There should be minimal disruption as a result of the web site move since the change will be automatically propagated by DNS hence no action by SIPSorcery users is required. For any one that requires it the IP address of the new web server will be 67.222.131.148. No changes are being made to the SIP services and they will be unaffected by the move.
The prices for new SIP Sorcery plans have today been increased to $69/year for a Premium plan and $199/year for a Professional plan.
Part of the increase is due to the extra features now included in the plans. Both now include the use of the online Switchboard which previously was only available as a separate add on. The Professional plan also has a new Real-time Call Control and Billing feature that allows sub-accounts to be created for managing calls on behalf of a small to medium VoIP business.
Existing customers with a PayPal subscription set up for renewing their account will be entitled to remain on the price that they signed up with.
The SIPSorcery REST provisioning service is now publicly available. More information can be found on the Provisioning Help page.
The service allows the management of SIP Account and SIP Provider resources from your favourite programming language.
The SIPSorcery server will be moving to a new host in one week on Sunday the 17th of June 2012 at 0200 PST. The move is to take advantage of some synergies with another SIP service, there will be more information on that further down the track. The consequence of the server move will be that the IP address of the service will change from 69.59.142.213 to 67.222.131.147. Ideally all users should be using sipsorcery.com and it’s recommended that anyone that may have configured their device with the SIPSorcery server’s IP address now switch to the host name.
For users that are using sipsorcery.com NO ACTION IS REQUIRED. For users that want to use the IP address then you will need to update your devices to use to the new IP address AFTER the 17th of June. Both the old and new servers will both work up until the the 21st of June 2012 after which the old IP address and server will be de-commissioned.
During the migration their will be a short outage of between 15 and 20 minutes while the database is migrated to the new server. The SIPSorcery Twitter account will be updated prior to and subsequent to the migration.
If anyone has any concerns regarding the migration please email admin@sipsorcery.com.