WebRTC + IOS + Freeswitch : Can't hear audio - ios

I'm trying to implement mod_verto on IOS (calling from iPhone to Desktop). I'm using Google's libjingle library for the RTC side, got it up and running using this excellent tutorial.
When making a call from my iPhone, I get the call on the desktop browser using the Verto Communicator (downloaded and running on my local machine).
On the iPhone side, I can hear the audio from my desktop, but I hear nothing on the desktop side.
If I'm making the call using 2 browser windows (with the Verto Communicator), all works fine.
Full Disclosure, I'm using ws:// unsecure websocket to connect to
FreeSwitch
Here is my JSONRPC log:
Sending login request:
{"jsonrpc":"2.0","method":"login","id":1,"params":{"login":"1000#MY-IP-ADDRESS","loginParams":{},"userVariables":{},"passwd":"1234","sessid":"53FB0781-B586-4CDA-98C6-558680663B46"}}
Login Response:
{"jsonrpc":"2.0","id":1,"result":{"message":"logged in","sessid":"53FB0781-B586-4CDA-98C6-558680663B46"}}
verto.invite (including the iPhone sdp):
{"jsonrpc":"2.0","method":"verto.invite","id":2,"params":{"dialogParams":{"remote_caller_id_number":"1008","useVideo":false,"useMic":"any","useStereo":false,"tag":"webcam","login":"1000#159.203.164.7","useCamera":"any","videoParams":{"minFrameRate":30,"minWidth":"1280","minHeight":"720"},"destination_number":"1008","screenShare":false,"caller_id_name":"FreeSWITCH User","caller_id_number":"1000","callID":"0CD433FC-A909-4DF2-BC46-0A4A94E9B800","remote_caller_id_name":"Outbound Call","useSpeak":"any"},"sessid":"53FB0781-B586-4CDA-98C6-558680663B46","sdp":"v=0\r\no=- 8564086442942257834 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE audio video\r\na=msid-semantic: WMS\r\nm=audio 58157 UDP\/TLS\/RTP\/SAVPF 111 103 104 9 102 0 8 106 105 13 127 126\r\nc=IN IP4 82.166.93.197\r\na=rtcp:52576 IN IP4 82.166.93.197\r\na=candidate:3168280865 1 udp 2122260223 11.0.0.244 58157 typ host generation 0\r\na=candidate:1260196625 1 udp 2122194687 10.134.172.254 58951 typ host generation 0\r\na=candidate:3168280865 2 udp 2122260222 11.0.0.244 52576 typ host generation 0\r\na=candidate:1260196625 2 udp 2122194686 10.134.172.254 58945 typ host generation 0\r\na=candidate:4066106833 1 tcp 1518280447 11.0.0.244 60562 typ host tcptype passive generation 0\r\na=candidate:94302177 1 tcp 1518214911 10.134.172.254 60563 typ host tcptype passive generation 0\r\na=candidate:4066106833 2 tcp 1518280446 11.0.0.244 60564 typ host tcptype passive generation 0\r\na=candidate:94302177 2 tcp 1518214910 10.134.172.254 60565 typ host tcptype passive generation 0\r\na=candidate:1610196941 1 udp 1686052607 82.166.93.197 58157 typ srflx raddr 11.0.0.244 rport 58157 generation 0\r\na=candidate:1610196941 2 udp 1686052606 82.166.93.197 52576 typ srflx raddr 11.0.0.244 rport 52576 generation 0\r\na=candidate:2274372738 2 udp 1685987070 176.13.15.205 5834 typ srflx raddr 10.134.172.254 rport 58945 generation 0\r\na=candidate:2274372738 1 udp 1685987071 176.13.15.205 5840 typ srflx raddr 10.134.172.254 rport 58951 generation 0\r\na=ice-ufrag:g8lHDtPwH7m5xRex\r\na=ice-pwd:Q6jcBJNTWAyu0JTuIaQAeNI3\r\na=fingerprint:sha-256 0F:A1:68:51:87:3E:B4:C1:0D:33:97:40:78:22:2A:8C:D2:B6:46:23:F5:99:C9:88:5D:34:DB:E2:C5:94:B3:DD\r\na=setup:actpass\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:3 http:\/\/www.webrtc.org\/experiments\/rtp-hdrext\/abs-send-time\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:111 opus\/48000\/2\r\na=fmtp:111 minptime=10; useinbandfec=1\r\na=rtpmap:103 ISAC\/16000\r\na=rtpmap:104 ISAC\/32000\r\na=rtpmap:9 G722\/8000\r\na=rtpmap:102 ILBC\/8000\r\na=rtpmap:0 PCMU\/8000\r\na=rtpmap:8 PCMA\/8000\r\na=rtpmap:106 CN\/32000\r\na=rtpmap:105 CN\/16000\r\na=rtpmap:13 CN\/8000\r\na=rtpmap:127 red\/8000\r\na=rtpmap:126 telephone-event\/8000\r\na=maxptime:60\r\nm=video 61966 UDP\/TLS\/RTP\/SAVPF 100 101 116 117 96\r\nc=IN IP4 82.166.93.197\r\na=rtcp:63816 IN IP4 82.166.93.197\r\na=candidate:3168280865 1 udp 2122260223 11.0.0.244 61966 typ host generation 0\r\na=candidate:1260196625 1 udp 2122194687 10.134.172.254 50435 typ host generation 0\r\na=candidate:3168280865 2 udp 2122260222 11.0.0.244 63816 typ host generation 0\r\na=candidate:1260196625 2 udp 2122194686 10.134.172.254 63396 typ host generation 0\r\na=candidate:4066106833 1 tcp 1518280447 11.0.0.244 60566 typ host tcptype passive generation 0\r\na=candidate:94302177 1 tcp 1518214911 10.134.172.254 60567 typ host tcptype passive generation 0\r\na=candidate:4066106833 2 tcp 1518280446 11.0.0.244 60568 typ host tcptype passive generation 0\r\na=candidate:94302177 2 tcp 1518214910 10.134.172.254 60569 typ host tcptype passive generation 0\r\na=candidate:1610196941 1 udp 1686052607 82.166.93.197 61966 typ srflx raddr 11.0.0.244 rport 61966 generation 0\r\na=candidate:1610196941 2 udp 1686052606 82.166.93.197 63816 typ srflx raddr 11.0.0.244 rport 63816 generation 0\r\na=candidate:2274372738 1 udp 1685987071 176.13.15.205 5879 typ srflx raddr 10.134.172.254 rport 50435 generation 0\r\na=candidate:2274372738 2 udp 1685987070 176.13.15.205 5860 typ srflx raddr 10.134.172.254 rport 63396 generation 0\r\na=ice-ufrag:g8lHDtPwH7m5xRex\r\na=ice-pwd:Q6jcBJNTWAyu0JTuIaQAeNI3\r\na=fingerprint:sha-256 0F:A1:68:51:87:3E:B4:C1:0D:33:97:40:78:22:2A:8C:D2:B6:46:23:F5:99:C9:88:5D:34:DB:E2:C5:94:B3:DD\r\na=setup:actpass\r\na=mid:video\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http:\/\/www.webrtc.org\/experiments\/rtp-hdrext\/abs-send-time\r\na=extmap:4 urn:3gpp:video-orientation\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:100 VP8\/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtpmap:101 VP9\/90000\r\na=rtcp-fb:101 ccm fir\r\na=rtcp-fb:101 nack\r\na=rtcp-fb:101 nack pli\r\na=rtcp-fb:101 goog-remb\r\na=rtcp-fb:101 transport-cc\r\na=rtpmap:116 red\/90000\r\na=rtpmap:117 ulpfec\/90000\r\na=rtpmap:96 rtx\/90000\r\na=fmtp:96 apt=100\r\n"}}
Call Created response:
{"jsonrpc":"2.0","id":2,"result":{"message":"CALL CREATED","callID":"0CD433FC-A909-4DF2-BC46-0A4A94E9B800","sessid":"53FB0781-B586-4CDA-98C6-558680663B46"}}
verto.media invoked:
{"jsonrpc":"2.0","method":"verto.media","id":637,"params":{"sdp":"v=0\no=FreeSWITCH 1457232832 1457232833 IN IP4 159.203.164.7\ns=FreeSWITCH\nc=IN IP4 159.203.164.7\nt=0 0\na=msid-semantic: WMS TcxpBqoS0j04fOIzkIArKYrlV7LCs9Ub\nm=audio 30784 UDP/TLS/RTP/SAVPF 111 126\na=rtpmap:111 opus/48000/2\na=fmtp:111 useinbandfec=1; minptime=10\na=rtpmap:126 telephone-event/8000\na=silenceSupp:off - - - -\na=ptime:20\na=sendonly\na=fingerprint:sha-256 FE:CD:54:3E:2A:D7:DB:00:57:B7:D4:55:A8:EB:79:08:16:BB:B0:EA:43:44:42:9A:90:01:49:37:7B:31:48:F8\na=setup:active\na=rtcp-mux\na=rtcp:30784 IN IP4 159.203.164.7\na=ice-ufrag:qLh1zzclxONPNyQO\na=ice-pwd:G7g4Drkist37beYsP5jfvlqS\na=candidate:9922185636 1 udp 659136 159.203.164.7 30784 typ host generation 0\na=ssrc:1323504502 cname:bhqCyFkpPbjUPSk0\na=ssrc:1323504502 msid:TcxpBqoS0j04fOIzkIArKYrlV7LCs9Ub a0\na=ssrc:1323504502 mslabel:TcxpBqoS0j04fOIzkIArKYrlV7LCs9Ub\na=ssrc:1323504502 label:TcxpBqoS0j04fOIzkIArKYrlV7LCs9Uba0\nm=video 31380 UDP/TLS/RTP/SAVPF 100\na=rtpmap:100 VP8/90000\na=sendonly\na=fingerprint:sha-256 FE:CD:54:3E:2A:D7:DB:00:57:B7:D4:55:A8:EB:79:08:16:BB:B0:EA:43:44:42:9A:90:01:49:37:7B:31:48:F8\na=setup:active\na=rtcp-mux\na=rtcp:31380 IN IP4 159.203.164.7\nb=AS:1024\na=rtcp-fb:100 ccm fir\na=rtcp-fb:100 nack\na=rtcp-fb:100 nack pli\na=ssrc:594893571 cname:bhqCyFkpPbjUPSk0\na=ssrc:594893571 msid:TcxpBqoS0j04fOIzkIArKYrlV7LCs9Ub v0\na=ssrc:594893571 mslabel:TcxpBqoS0j04fOIzkIArKYrlV7LCs9Ub\na=ssrc:594893571 label:TcxpBqoS0j04fOIzkIArKYrlV7LCs9Ubv0\na=ice-ufrag:2KDK4wDMYuAuVdAZ\na=ice-pwd:YTpxObqpLuBEfig7TKHN6bqU\na=candidate:7508673635 1 udp 659136 159.203.164.7 31380 typ host generation 0\n","callID":"0CD433FC-A909-4DF2-BC46-0A4A94E9B800"}}
verto.answer invoked:
{"jsonrpc":"2.0","method":"verto.answer","id":638,"params":{"callID":"0CD433FC-A909-4DF2-BC46-0A4A94E9B800"}}
Q:What am I missing in order to hear audio on the browser side ?
Any information is appreciated :)
Update, added the freeswitch log
Update 2 IOS : audio stream code
...
let audioTrack = self.factory.audioTrackWithID("Local-Audio")
self.localMediaStream?.addAudioTrack(audioTrack);
self.peerConnection!.addStream(self.localMediaStream)
...
Update 3 - Partial solution
While inspecting my code, I found old code that used to add a video track to my local media stream, disabling this section solves the audio problem, but why? what is wrong with that code?
P.S the Promise class was created by a friend, and mimic the JS Promise approach.
func getUserMedia(mediaOptions:Dictionary<String , Any>? = nil) -> Promise<RTCMediaStream>{
return Promise<RTCMediaStream>(executor: { (resolve, reject) -> () in
var cameraID:String?
self.localMediaStream = self.factory.mediaStreamWithLabel("Local-Meida")
//if video option is enabled (default true)
//-------------- Disabling this section solves the audio issues --------------
if(mediaOptions?["video"] as? Bool ?? true){
for captureDevice in AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo){
if (captureDevice.position == mediaOptions?["devicePosition"] as? AVCaptureDevicePosition ?? AVCaptureDevicePosition.Front){
cameraID = captureDevice.localizedName
break
}
}
if(cameraID == nil){
reject(NSError(domain: "No cammera detected", code: 0, userInfo: nil))
}
let capturer = RTCVideoCapturer.init(deviceName: cameraID)
let videoSource = self.factory.videoSourceWithCapturer(capturer, constraints: mediaOptions?["constraints"] as? RTCMediaConstraints ?? nil)
if let localVideoTrack = self.factory.videoTrackWithID("Local-Video", source: videoSource){
//!!!! THIS IS THE PROBLEMATIC LINE !!!!
self.localMediaStream?.addVideoTrack(localVideoTrack)
}else{
reject(NSError(domain: "No Video track", code: 0, userInfo: nil))
}
}
//-------------- Disabling this section solves the audio issues --------------
if(mediaOptions?["audio"] as? Bool ?? true){
let audioTrack = self.factory.audioTrackWithID("Local-Audio")
self.localMediaStream?.addAudioTrack(audioTrack);
}
self.peerConnection!.addStream(self.localMediaStream)
resolve(self.localMediaStream!)
})
}
Debug at problematic line

Update
It is very difficult to understand what is going wrong with the WebRTC implementation unless you have check Media Server, Web Client and iOS Client in our case.
Your case is an Audio Call, so no video streams need to be included on your localStream, but if you take a look closely you see that you are actually adding a videoTrack on your mobile stream:
if(mediaOptions?["video"] as? Bool ?? true){
for captureDevice in AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo){
if (captureDevice.position == mediaOptions?["devicePosition"] as? AVCaptureDevicePosition ?? AVCaptureDevicePosition.Front){
cameraID = captureDevice.localizedName
break
}
}
if(cameraID == nil){
reject(NSError(domain: "No cammera detected", code: 0, userInfo: nil))
}
let capturer = RTCVideoCapturer.init(deviceName: cameraID)
let videoSource = self.factory.videoSourceWithCapturer(capturer, constraints: mediaOptions?["constraints"] as? RTCMediaConstraints ?? nil)
if let localVideoTrack = self.factory.videoTrackWithID("Local-Video", source: videoSource){
//!!!! THIS IS THE PROBLEMATIC LINE !!!!
self.localMediaStream?.addVideoTrack(localVideoTrack)
}else{
reject(NSError(domain: "No Video track", code: 0, userInfo: nil))
}
}
So the line which causes the trouble is: self.localMediaStream?.addVideoTrack(localVideoTrack), because you are attaching the video to the localStream.
Opinions
We may have different scenarios of trouble as I mentioned, here I am listing some opinions based on my experience when we built a similar system:
Your MediaServer may not have an implementation which can redirect and handle your calls in a successful state, because there are additional things added when you attach a video (Please see your session description what you actually send there), and it simply refuses to create a call.
Even if your MediaServer handles the scenario, this will include the right implementation of the Client (both Desktop and Mobile) to conform to signaling of its protocols.
You passed all the tests, and you are now adding video and audio, so you are initiating localStream from mobile, and the same need to be created other way. Then you need to handle events when you add streams, remove stream and other stuff via websockets.
Solution in this case
Remove the part that adds localTrack inside localStream, and then even if you have errors, are not caused by creating your localStream, so this step is currently solved.
Original Answer
Here I have a working version of mine, but adapted for your needs as you use only audio.
Creating and setting up the peerConnection (localSide)
// Connecting to the socket
.........
// Create PeerConnectionFactory
self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
// Initialize peerConnection based on a list of ICE Servers
self.peerConnection = [self.peerConnectionFactory peerConnectionWithICEServers:[self getICEServers] constraints:constraints delegate:self];
// Create the localStram which contains the audioTrack
RTCMediaStream *localStream = [self createLocalMediaStream];
// Add this stream to the peerConnection
[self.peerConnection addStream:localStream];
// Please be aware here that I am using blocks, as I created a wrapper for easier maintenance, but you can use createOfferWithDelegate: which will go back at your delegation
NSLog(#"Creating peer offer");
RTCManager *strongSelf = self;
[strongSelf.peerConnection createOfferWithCallback:^(RTCSessionDescription *sdp, NSError *error) {
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Success at creating offer, now setting local description");
[strongSelf.peerConnection setLocalDescriptionWithCallback:^(NSError *error) {
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Success at setting local description");
// On my type of signalization here I am connected, but yours is based on what type of signalization requires
});
}
} sessionDescription:sdp];
});
}
} constraints:[strongSelf defaultPeerConnectionConstraints]];
Helpers
// Now here we create the stream which contains the audio (Please note the ID)
- (RTCMediaStream *)createLocalMediaStream {
RTCMediaStream *localStream = [self.peerConnectionFactory mediaStreamWithLabel:#"ARDAMS"];
[localStream addAudioTrack:[self.peerConnectionFactory audioTrackWithID:#"ARDAMSa0"]];
return localStream;
}
- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
// DtlsSrtpKeyAgreement is required for Chrome and Firefox to interoperate.
NSArray *optionalConstraints = #[[[RTCPair alloc] initWithKey:#"DtlsSrtpKeyAgreement" value:#"true"]];
RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:nil optionalConstraints:optionalConstraints];
return constraints;
}
Please be aware that your problem might be caused also for not calling stuff on the main thread.

Related

Soft wdt reset on ESP8266 when using Speed Sensor

I'm on my personal project to create a esp8266 module that can read several sensor from a vehicle and it can sends data over mqtt. so I have written the code where I try to reading the speed sensor its getting wdt reset and the esp8266 resetting again
so heres my code
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
lcd.init();
lcd.backlight();
pinMode(speedSensor,INPUT_PULLUP);
// attempt to connect to WiFi network:
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
lcd.setCursor(0,0);
lcd.print("Connecting to");
lcd.setCursor(0,1);
lcd.print("the network");
Serial.print(".");
delay(5000);
}
lcd.clear();
Serial.println("You're connected to the network");
Serial.println();
lcd.setCursor(0,0);
lcd.print("Connected to");
lcd.setCursor(0,1);
lcd.print("the network");
delay(2000);
lcd.clear();
// attempt to connect to the MQTT broker:
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
if (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
while (1);
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
// subscribe to the topic and set callback function
mqttClient.onMessage(SwitchMotor);
mqttClient.subscribe(topicSwitchMotor);
lcd.setCursor(0,0);
lcd.print("Connected to");
lcd.setCursor(0,1);
lcd.print("the MQTT broker");
delay(2000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(" Fuel :");
lcd.setCursor(0,1);
lcd.print(" RPM :");
SPI.begin();
rfid.PCD_Init();
Serial.println("I am waiting for card...");
pinMode(pinRelay, OUTPUT);
digitalWrite(pinRelay, HIGH);
}
void loop() {
mqttClient.poll(); // check for incoming messages
FuelSensor(); // Fuel Sensor
SpeedMotor(); // Speed Sensor
StartMotor(); // Start Motor with RFID
}
void SpeedMotor(){
start_time=millis();
end_time=start_time+1000;
while(millis()<end_time){
yield();
if(digitalRead(speedSensor)){
steps=steps+1;
while(digitalRead(speedSensor));
}
}
// calculate the speed
temp=steps-steps_old;
steps_old=steps;
rps=(temp/20);
rpm=(rps*60);
lcd.setCursor(9,1);
lcd.print(rpm);
lcd.print(" ");
// publish the message
mqttClient.beginMessage(topicSpeedRpm);
mqttClient.print(rpm);
mqttClient.endMessage();
mqttClient.beginMessage(topicSpeedRps);
mqttClient.print(rps);
mqttClient.endMessage();
}
and this is the output from serial monitor
Attempting to connect to WPA SSID: cieciecie
...You're connected to the network
Attempting to connect to the MQTT broker: xx.xxx.xxx.xx
You're connected to the MQTT broker!
I am waiting for card...
--------------- CUT HERE FOR EXCEPTION DECODER ---------------
Soft WDT reset
>>>stack>>>
ctx: cont
sp: 3ffffde0 end: 3fffffc0 offset: 01a0
3fffff80: 3fffdad0 3ffee7bc 3ffee7c0 40201143
3fffff90: 3fffdad0 00000000 3ffeeaa8 402016e9
3fffffa0: 3fffdad0 00000000 3ffeeaa8 40205d5c
3fffffb0: feefeffe feefeffe 3ffe8600 40100e61
<<<stack<<<
--------------- CUT HERE FOR EXCEPTION DECODER ---------------
⸮⸮Attempting to connect to WPA SSID: cieciecie
...You're connected to the network
Attempting to connect to the MQTT broker: xx.xxx.xxx.xx
You're connected to the MQTT broker!
I am waiting for card...
--------------- CUT HERE FOR EXCEPTION DECODER ---------------
Soft WDT reset
it'll soft wdt reset all over again and again
I'd already try Stack ESP execption decoder and it given this results
ESP Exception decoder results
at the results its mentioning line 152 at speedMotor() function
void SpeedMotor(){
start_time=millis();
end_time=start_time+1000;
while(millis()<end_time){
yield(); // already use yield(); on while() loop
if(digitalRead(speedSensor)){ // line 152
steps=steps+1;
while(digitalRead(speedSensor));
}
}
// calculate the speed
temp=steps-steps_old;
steps_old=steps;
rps=(temp/20);
rpm=(rps*60);
lcd.setCursor(9,1);
lcd.print(rpm);
lcd.print(" ");
// publish the message
mqttClient.beginMessage(topicSpeedRpm);
mqttClient.print(rpm);
mqttClient.endMessage();
mqttClient.beginMessage(topicSpeedRps);
mqttClient.print(rps);
mqttClient.endMessage();
}
and i already try too from arduino forum https://forum.arduino.cc/t/solved-esp8266-millis-crash/636543
it says try using yield(); in the while() loop but its still given the soft wdt rest
i would like to have clear answer how to use the yield(); function properly so theres no more soft wdt resetting again
As per the comments the issue was in this section of code:
while(millis()<end_time){
yield(); // already use yield(); on while() loop
if(digitalRead(speedSensor)){ // line 152
steps=steps+1;
while(digitalRead(speedSensor));
}
}
The problem is that if digitalRead(speedSensor) returns true then a loop runs until digitalRead(speedSensor) returns false; this may run long enough to trigger the watchdog.
while(digitalRead(speedSensor));
One way of fixing this is to yield within the loop i.e.:
while(digitalRead(speedSensor)) {
yield();
}

How to measure RTT/Latency in a concurrent UDP server and clients created in GoLang?

I was able to run a concurrent UDP server and connect UDP clients to this concurrent server. However, I would like to know how to measure the latency of this UDP server.
For some reason, Netcat commands in Linux do not work and they work well with TCP. I can't seem to measure it because I am hosting this server on my machine and I am trying to measure the latency. Additionally, a lot of UDP clients can connect to the UDP server concurrently, there seems to be a very high limit.
Should I instead create a docker container and run it on a router and then measure the latency to the router? How would you proceed with this?
Below is the code for the UDP Web server:
package main
import (
"fmt"
"math/rand"
"net"
"os"
"strconv"
"strings"
"time"
)
func random(min, max int) int {
return rand.Intn(max-min) + min
}
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide a port number!")
return
}
PORT := ":" + arguments[1]
s, err := net.ResolveUDPAddr("udp4", PORT)
if err != nil {
fmt.Println(err)
return
}
connection, err := net.ListenUDP("udp4", s)
if err != nil {
fmt.Println(err)
return
}
defer connection.Close()
buffer := make([]byte, 1024)
rand.Seed(time.Now().Unix())
for {
n, addr, err := connection.ReadFromUDP(buffer)
go func() {
fmt.Print("-> ", string(buffer[0:n-1]))
fmt.Println(addr.String())
if strings.TrimSpace(string(buffer[0:n])) == "STOP" {
fmt.Println("Exiting UDP server!")
return
}
data := []byte(strconv.Itoa(random(1, 1001)))
fmt.Printf("data: %s\n", string(data))
_, err = connection.WriteToUDP(data, addr)
if err != nil {
fmt.Println(err)
return
}
}()
}
}
Below is the code for the UDP clients:
package main
import (
"fmt"
"net"
"os"
"time"
)
func check(err error) {
if err != nil {
panic(err)
}
}
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide a host:port string")
return
}
CONNECT := arguments[1]
s, err := net.ResolveUDPAddr("udp4", CONNECT)
check(err)
c, err := net.DialUDP("udp4", nil, s)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("The UDP server is %s\n", c.RemoteAddr().String())
defer c.Close()
for {
time.Sleep(1 * time.Second)
go func() {
text := []byte("heyyyy")
_, err := c.Write(text)
if err != nil {
fmt.Println(err)
return
}
buffer := make([]byte, 1024)
n, _, err := c.ReadFromUDP(buffer)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Reply: %s\n", string(buffer[0:n]))
}()
}
}
I was able to run a concurrent UDP server and connect UDP clients to this concurrent server. However, I would like to know how to measure the latency of this UDP server.
In order to measure one-way latency you will need to send a timestamp in the UDP segment payload and then check the elapsed time in the receiver.
To get the RTT latency, you can send the timestamp in the payload, make the receiver to bounce the packet back, receive it at the sender and then finally check the elapsed time.
Measuring one-way latency across network would be hard since you would need high accurately synchronized clocks in the sender host and the receiver host. Hence across networks, usually people measure only jitter and RTT latency, not one-way latency.
For some reason, Netcat commands in Linux do not work and they work well with TCP.
netcat does work well with UDP. Maybe you missed the -u switch?
$ nc -l -u -p 8080 &
[1] 2171
$ echo hi | nc -u localhost 8080
hi
I can't seem to measure it because I am hosting this server on my machine and I am trying to measure the latency. Additionally, a lot of UDP clients can connect to the UDP server concurrently, there seems to be a very high limit.
Just for the sake of accurate terminology, UDP is a connectionless protocol, so we usually don't speak of connecting with UDP, but speak of just sending and receiving datagrams.

iOS Packet Tunnel Provider with Local On-Device Server

I'm using the Network Extension framework provided by Apple to build a packet sniffing/monitoring application similar to Charles Proxy and Surge 4 for iOS.
So far, I have the basic structure of the project up and running with the Main Application triggering the PacketTunnelProvider Extension where I can see packets being forwarded via the packetFlow.readPackets(completionHandler:) method. My background isn't in networking so I'm confused on the basic structure of these kinds of apps. Do they host a server on the device that act as the proxy which intercepts network requests? Could anyone provide a diagram of the general flow of the network requests? I.e. what is the relationship between the Packet Tunnel Provider, Proxy Server, Virtual Interface, and Tunnel?
If these apps do use a local on-device server, how do you configure the NEPacketTunnelNetworkSettings to allow for a connection? I have tried incorporating a local on-device server such as GCDWebServer with no luck in establishing a link between the two.
For example, if the GCDWebServer was reachable at 192.168.1.231:8080, how would I change the code below for the client to communicate with the server?
Main App:
let proxyServer = NEProxyServer(address: "192.168.1.231", port: 8080)
let proxySettings = NEProxySettings()
proxySettings.exceptionList = []
proxySettings.httpEnabled = true
proxySettings.httpServer = proxyServer
let providerProtocol = NETunnelProviderProtocol()
providerProtocol.providerBundleIdentifier = self.tunnelBundleId
providerProtocol.serverAddress = "My Server"
providerProtocol.providerConfiguration = [:]
providerProtocol.proxySettings = proxySettings
let newManager = NETunnelProviderManager()
newManager.localizedDescription = "Custom VPN"
newManager.protocolConfiguration = providerProtocol
newManager.isEnabled = true
saveLoadManager()
self.vpnManager = newManager
PacketTunnelProviderExtension:
func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
...
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.143")
settings.ipv4Settings = NEIPv4Settings(addresses: ["198.17.203.2"], subnetMasks: ["255.255.255.255"])
settings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()]
settings.ipv4Settings?.excludedRoutes = []
settings.dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "8.8.4.4"])
settings.dnsSettings?.matchDomains = [""]
self.setTunnelNetworkSettings(settings) { error in
if let e = error {
NSLog("Settings error %#", e.localizedDescription)
} else {
completionHandler(error)
self.readPackets()
}
}
...
}
I'm working on the iOS version of Proxyman and my experience can help you:
Do they host a server on the device that acts as the proxy which intercepts network requests?
Yes, you have to start a Listener on the Network Extension (not the main app) to act as a Proxy Server. You can write a simple Proxy Server by using Swift NIO or CocoaAsyncSocket.
To intercept the HTTPS traffic, it's a quite big challenge, but I won't mention here since it's out of the scope.
Could anyone provide a diagram of the general flow of the network requests?
As the Network Extension and the Main app are two different processes, so they couldn't communicate directly like normal apps.
Thus, the flow may look like:
The Internet -> iPhone -> Your Network Extension (VPN) -> Forward to your Local Proxy Server -> Intercept or monitor -> Save to a local database (in Shared Container Group) -> Forward again to the destination server.
From the main app, you can receive the data by reading the local database.
how do you configure the NEPacketTunnelNetworkSettings to allow for a connection?
In the Network extension, let start a Proxy Server at Host:Port, then init the NetworkSetting, like the sample:
private func initTunnelSettings(proxyHost: String, proxyPort: Int) -> NEPacketTunnelNetworkSettings {
let settings: NEPacketTunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
/* proxy settings */
let proxySettings: NEProxySettings = NEProxySettings()
proxySettings.httpServer = NEProxyServer(
address: proxyHost,
port: proxyPort
)
proxySettings.httpsServer = NEProxyServer(
address: proxyHost,
port: proxyPort
)
proxySettings.autoProxyConfigurationEnabled = false
proxySettings.httpEnabled = true
proxySettings.httpsEnabled = true
proxySettings.excludeSimpleHostnames = true
proxySettings.exceptionList = [
"192.168.0.0/16",
"10.0.0.0/8",
"172.16.0.0/12",
"127.0.0.1",
"localhost",
"*.local"
]
settings.proxySettings = proxySettings
/* ipv4 settings */
let ipv4Settings: NEIPv4Settings = NEIPv4Settings(
addresses: [settings.tunnelRemoteAddress],
subnetMasks: ["255.255.255.255"]
)
ipv4Settings.includedRoutes = [NEIPv4Route.default()]
ipv4Settings.excludedRoutes = [
NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"),
NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0")
]
settings.ipv4Settings = ipv4Settings
/* MTU */
settings.mtu = 1500
return settings
}
Then start a VPN,
let networkSettings = initTunnelSettings(proxyHost: ip, proxyPort: port)
// Start
setTunnelNetworkSettings(networkSettings) { // Handle success }
Then forward the package to your local proxy server:
let endpoint = NWHostEndpoint(hostname: proxyIP, port: proxyPort)
self.connection = self.createTCPConnection(to: endpoint, enableTLS: false, tlsParameters: nil, delegate: nil)
packetFlow.readPackets {[weak self] (packets, protocols) in
guard let strongSelf = self else { return }
for packet in packets {
strongSelf.connection.write(packet, completionHandler: { (error) in
})
}
// Repeat
strongSelf.readPackets()
}
From that, your local server can receive the packages then forwarding to the destination server.

Cannot Connect to IPv6 with CocoaAsyncSocket

Having issues connecting to iPv6 hosts with the CocoaAsyncSocket library
I successfully had GCDUDPAsyncSocket working but realized TCP was more appropriate for my use case.
Unfortunately - I can never successfully connect with a bonjour published and discovered NSNetService. The service is discovered and the address is discovered as well. A connection attempt without failure happens but the connection is never secured.
I can connect using "connectWithHost" and passing in the IP address assigned to my mac but this the only way i can get that ip is by hard coding it. Is there a way to obtain this IP through NSNetService?
I'm using swift, Xcode 7.1.1 and iOS 9.1. I am connecting between an iPhone and a Mac running an Apple TV Simulator. This works fine with UDP.
No matter what - the connection attempt times out even though an appropriate address is supplied!
Socket is Disconnecting - Error Domain=NSPOSIXErrorDomain Code=60 "Operation timed out" UserInfo={NSLocalizedDescription=Operation timed out, NSLocalizedFailureReason=Error in connect() function}
Anyone run into this before? Here is my connection code:
func connectToAddress(sender: NSNetService) {
if let addresses = sender.addresses {
for address in addresses {
print(address)
}
self.serverAddresses = addresses
var done = false
while !done && (self.serverAddresses.count > 0) {
let address = self.serverAddresses[0]
self.socket = GCDAsyncSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
do {
try self.socket.connectToAddress(address)
done = true
} catch {
print("Unable to Connect")
}
}
if !done {
print("Could Not Connect To Address")
}
}
}
Please update your CocoaAsyncSocket library. The issue was fixed in May 2nd commit. So this should work with the following flag set to false
socket.IPv4PreferredOverIPv6 = NO;
This will allow your app / game to connect in both IPv4 and IPv6. As of June 1 Apple is rejecting the apps which are not compliant with IPv6 networks. You app should work without any problems in an IPv6 network.
You can setting
socket.IPv4PreferredOverIPv6 = NO;
as #Kumar C mentioned, it will works well in IPv6, but if you still need work with IP address(use IP address as Host), you can update the code as below in GCDAsyncSocket.m(IPv4PreferredOverIPv6 should be set to NO first):
+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
{
......
for (res = res0; res; res = res->ai_next)
{
if (res->ai_family == AF_INET)
{
// Found IPv4 address.
// Wrap the native address structure, and add to results.
if (((struct sockaddr_in *)res->ai_addr)->sin_port == 0)
((struct sockaddr_in *)res->ai_addr)->sin_port = htons(port);
NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
[addresses addObject:address4];
}
else if (res->ai_family == AF_INET6)
{
// Found IPv6 address.
// Wrap the native address structure, and add to results.
if (((struct sockaddr_in6 *)res->ai_addr)->sin6_port == 0)
((struct sockaddr_in6 *)res->ai_addr)->sin6_port = htons(port);
NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
[addresses addObject:address6];
}
}
.......
}
This will allows you app work well in IPv4/IPv6 whatever the HOST is IP address or domain.

Arduino WiFi shield post with header problems

I'm trying to do a post from the arduino wifi shield to my java servlet. The servlet functions with url get, and jquery post, but I can't sort the headers out in my arduino code. Any help will be greatly appreciated!
The server returns 200, but I'm not getting the payload "content" as value. I'm not exactly sure what I'm doing wrong but I'm pretty sure it's in how my headers are setup. I've spent the last two days trying to get it.
#include <SPI.h>
#include <WiFi.h>
char ssid[] = "jesussavesforjust19.95"; // your network SSID (name)
char pass[] = "********"; // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0; // your network key Index number (needed only for WEP)
int status = WL_IDLE_STATUS;
IPAddress server(192,168,10,149); // numeric IP for Google (no DNS)
WiFiClient client;
void setup() {
Serial.begin(9600);
// attempt to connect to Wifi network:
while ( status != WL_CONNECTED) {
Serial.println("Attempting to connect to SSID: ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
Serial.println("Connected to wifi");
printWifiStatus();
sendData("0600890876");
}
void loop() {
// if there's incoming data from the net connection.
// send it out the serial port. This is for debugging
// purposes only:
if (client.available()) {
char c = client.read();
Serial.println(c);
}
//String dataString = "060088765";
// if you're not connected, and ten seconds have passed since
// your last connection, then connect again and send data:
if(!client.connected())
{
Serial.println();
Serial.println("disconnecting.");
client.stop();
//sendData(dataString);
for(;;)
;
}
}
// this method makes a HTTP connection to the server:
void sendData(String thisData) {
// if there's a successful connection:
Serial.println("send data");
if (client.connect(server, 8080)) {
String content = "value=0600887654";
Serial.println(content);
Serial.println("connected");
client.println("POST /hos HTTP/1.1");
client.println("Host:localhost");
client.println("Connection:Keep-Alive");
client.println("Cache-Control:max-age=0");
client.println("Content-Type: application/x-www-form-urlencoded\n");
client.println("Content-Length: ");
client.println(content.length());
client.println("\n\n");
client.println(content);
}
else {
// if you couldn't make a connection:
Serial.println("form connection failed");
Serial.println();
Serial.println("disconnecting.");
client.stop();
}
}
void printWifiStatus() {
// print the SSID of the network you're attached to:
Serial.println("SSID: ");
Serial.println(WiFi.SSID());
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.println("IP Address: ");
Serial.println(ip);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.println("signal strength (RSSI):");
Serial.println(rssi);
Serial.println(" dBm");
}
Perhaps, some of your "Serial.println" and "client.println" commands should be "Serial.print" and "client.print" instead. For example:
client.print("Content-Length: ");
client.println(content.length());
would avoid adding a line break between the text and the number.
This is maybe more advice on an approach than an answer.
If I was doing something like this I would not start on the Arduino. The endless compile, download, run, look at print()'s would drive me crazy. I would fully prototype the client/server interaction in whatever you have at your fingertips, preferably something with a debugger. (Java, Python, PHP, VB, whatever you know that you can slap together)
Second, I would run Wireshark on the server so that I could see exactly what was being sent and responded.
Then I would port the same interaction over to the Arduino. Again inspect with Wireshark to confirm you are getting what you expected. If you send the same bytes, you should get the same response.
Even if you choose to implement straight on Arduino, consider having Wireshark to capture the actual network traffic.
With Wireshark, you might see that the Arduino println() is not sending the correct line end for the server.
Also, there is no guarantee that last println() is actually sent. The network stack implementation is free to buffer as it sees fit. You might need a flush(). A packet trace would show this.
With a packet capture you might find that time matters. In theory TCP is a stream and you should be able to send that POST data 1 character at a time in 1 packet and everything would work. But the Arduino might be so slow executing those println()'s by the server's standards that it times out. In such case you would see the server respond before the Arduino even finished sending.

Resources