Overwrite RTCPeerConnection callback 'onicecandidate' - javascript

I am trying to spoof ip address leaked from WebRTC, so I want to override 'onicecandidate' callback function but the code below does not work, I cannot figure out why.
Object.defineProperty(RTCPeerConnection.prototype, 'onicecandidate', {
set: function (eventHandler) {
console.log('hook set');
this._onicecandidateEventHandler = eventHandler;
this._onicecandidate = function (event) {
console.log('hook');
this._onicecandidateEventHandler.apply(this, arguments);
};
},
get: function () {
return this._onicecandidate;
}
})
The code above supposed to hook the receiver function assigned by the "fingerprinting script".
A example of fingerprinting script is like this :
function findIP() {
var myPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var pc = new myPeerConnection({iceServers: [{urls: "stun:stun.l.google.com:19302"}]}),
noop = function() {},
localIPs = {},
ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g,
key;
function ipIterate(ip) {
if (!localIPs[ip]) {console.log('got ip: ', ip);}
localIPs[ip] = true;
}
pc.createDataChannel("");
pc.createOffer(function(sdp) {
sdp.sdp.split('\n').forEach(function(line) {
if (line.indexOf('candidate') < 0) return;
line.match(ipRegex).forEach(ipIterate);
});
pc.setLocalDescription(sdp, noop, noop);
}, noop);
pc.onicecandidate = function(ice) {
if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) return;
ice.candidate.candidate.match(ipRegex).forEach(ipIterate);
};
}
As you can see: the method to get your real ip from webRTC is trying to estisibale a connection then set a callback on 'onicecandidate' event, the event information contains the real Ip information.
What I'd like to do is to override 'set' function of 'onicecandidate' assignment so it will be replaced by my own hook function and after "alter" the ip address the hook will call the real receiver set by the fingerprint script.
In my tests : I can observed, after executed my code from console, the RTCPeerConnection.prototype has been override, if I assigned a function to RTCPeerConnection.onicecandidate , the console will print "hook set", so appeared that override is success, also if I called RTCPeerConnection.onicecandidate(xxx) mannullay, both my hook function and original function is executed, it works as expected . However this code is not working when I used in the real fingerprint script like I pasted the above. The onicecandidate event are never fired after the override applied.
I am a beginner for javascript, hope someone can explain my confusion .
Thank you in advance.

Without commenting on why this doesn't work, this alone won't help you against scripts that use addEventListener('icecandidate').
adapter.js contains a "wrapPeerConnectionEvent" helper function which handles both variants. With that helper it becomes quite a simple task:
wrapPeerConnectionEvent(window, 'icecandidate', (e) => {
if (e.candidate) {
const parts = e.candidate.candidate.split(' ');
parts[4] = '127.0.0.1'; // replace the real ip with 127.0.0.1
e.candidate.candidate = parts.join(' ');
}
return e;
});
See https://jsfiddle.net/krgz5qu1/ for a full example.
Note that you may need to take care of the ip in the relAddr field of server-reflexive and relay candidates as well.

Related

Accessing Service Worker saved IndexedDB data from Content Script via Chrome Runtime Messaging

In a Chrome Extension, I have no problem adding, updating, and removing data to/from an IndexedDB database accessed by my service worker with Chrome Runtime Messaging sent from my content script. My trouble is doing a full table read from my content script. I do a console.log() to dump out the property before I send it back in my sendResponse in the Chrome Runtime Messaging, and I see the data there properly, but the content script receives an undefined. I assume this is because of the asynchronous nature of getting the data. I tried promises and async/await and the combination thereof and I just can't seem to get anything except an undefined in my content script on the message back from the service worker. I also ran sending a test array back and that worked just fine -- but receiving the IndexedDB table data does not work in the message passing. I also tried to JSONify the data and that didn't help either. What's the catch?
service-worker.js
importScripts('modules/idb.js');
var SW = {};
SW.onReady = function(){
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
(o.readTable) && sendResponse(SW.readTable(o,sender));
});
};
SW.readTable = function(o,sender){
var sTable = o.table;
new Promise((resolve) => {
IDB.readTable(sTable,function(asEntries){
resolve(asEntries);
});
}).then((asEntries) => {
console.log('SW asEntries',asEntries); // this shows me valid data in tests
var o = {};
// can also change this to fake data with asEntries being a string array and bug goes away in content.js
o.response = asEntries;
return o;
});
};
SW.onReady();
modules/idb.js
var IDB = {};
// Requires storage (or, even better, unlimitedStorage) permission in your manifest.json file.
// Note also that dev console of service worker will not show data -- have to use toolbar button popup panel (if you have one) and
// dev console from there, or code to access it, which sucks.
IDB.connectStore = function(sTable,sReadWriteSetting,fn){
var conn = indexedDB.open('unlimitedStorage', 1);
conn.onupgradeneeded = function(e) {
var db = e.target.result;
db.createObjectStore(sTable);
};
conn.onsuccess = function(e) {
var db = e.target.result;
var tx = db.transaction(sTable,sReadWriteSetting);
var store = tx.objectStore(sTable);
fn(db,tx,store);
};
};
IDB.addToTable = function(sTable,sKey,sVal){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
if ((sKey === undefined) || (sKey === '') || (sKey === null) || (sKey === false)) { // auto key by increment
var req = store.count();
req.onsuccess = function(e){
sKey = e.target.result + 1;
store.add(sVal,sKey);
tx.complete;
}
} else {
store.add(sVal,sKey);
tx.complete;
}
});
};
IDB.removeFromTable = function(sTable,sKey){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
store.delete(sKey);
tx.complete;
});
};
IDB.readTableByKey = function(sTable,sKey,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.get(sKey);
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
IDB.readTable = function(sTable,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.getAll();
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
content.js
var CONTENT = {};
CONTENT.onReady = function(){
var o = {};
o.readTable = true;
o.table = 'loadTimes';
chrome.runtime.sendMessage(o,function(response){
if (response.response) { // errors here with response property being undefined
console.log('CONTENT RCVD asEntries',response.response);
}
});
};
CONTENT.onReady();
Chrome extensions API, unlike Firefox WebExtensions, can't handle Promise returned from a callback or provided in sendResponse, https://crbug.com/1185241.
There's also a bug in your readTable: you need to add return before new Promise((resolve)
The solution is two-fold:
Use return true from the callback to allow asynchronous sendResponse
Call sendReponse inside .then of a Promise chain.
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
if (o.readTable) {
SW.readTable(o,sender).then(sendResponse);
return true;
} else {
sendResponse(); // Chrome 99-101 bug workaround, https://crbug.com/1304272
}
});
Do not use this answer. It is here for posterity reasons and is just a workaround. The chosen solution works.
The fix is to return data in a different message thread:
In the service worker in SW.readTable(), just return variable o with o.response = true and then ignore the response in the content script.
Before returning the variable o from SW.readTable(), do a chrome.runtime.sendMessage({readTableResult = true, data: asEntries},function(response){ /* ignore response */});
In the content script, ignore any response back from the readTable message. So, the if (response.response) {...} condition can be eliminated.
In the content script, add a message listener with chrome.runtime.onMessage.addListener(o, sender, sendResponse) and look for the condition (o.readTableResult). Once received, the o.data will now contain the asEntries data.

How to prevent page reload after JavaScript function is executed?

I am developing a web application in java. But for certain purposes, I have to use javascript. I am facing an infinitive loop issue caused by I believe that very same javascript.
I trigger the init method in java bean by clicking on the command link. This command link should open a new XHTML page. During init, I have to resolve the local IP address which I need for further implementation. I found javascript here on Stack Over Flow which will resolve that for me. After executing javascript I need to store that IP into some variable and pass it back to bean. I found a way of doing it by using <p:remoteCommand> which I trigger right from javascript itself. I managed to pass the variable to bean successfully, and use it for other functionality. At this point, I'm facing a problem, which I have no clue how to solve. After the script is executed successfully somehow the page is reloaded, and the init method is invoked again. So it basically creates an infinite loop.
Here is my init method in java bean:
public void init() {
RequestContext.getCurrentInstance()
.execute("getLocalIP();");
}
JavaScript for resolving local IP address:
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var pc = new RTCPeerConnection({
iceServers : []
}), noop = function() {};
pc.createDataChannel('');
pc.createOffer(pc.setLocalDescription.bind(pc), noop);
pc.onicecandidate = function(ice) {
if (ice && ice.candidate && ice.candidate.candidate) {
var localIP = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4})
{7})/.exec(ice.candidate.candidate)[1];
console.log('my IP: ', localIP);
// this will trigger the remoteCommand
// which will pass value from JS to java bean
loadLocalIP([{name:'localIPParam', value: localIP}]);
pc.onicecandidate = noop;
}
}
Here is my xhtml(remoteCommand) which is triggered from javascript:
<h:form id="localIPForm">
<p:remoteCommand name="loadLocalIP" action="#{aggregationStationController.loadData()}"/>
</h:form>
This method is invoked by remoteCommand:
public void loadData() {
Map<String, String> params = FacesContext.getCurrentInstance()
.getExternalContext()
.getRequestParameterMap();
ipValue = params.get("localIPParam").toString();
System.out.println(ipValue);
if (!ipValue.isEmpty()) {
resolveAggregationStationByIP(ipValue);
}
// after this line init method is invoked again and creates an infinite loop
}
private void resolveAggregationStationByIP(String ipAddress) {
aggregationStation = organizationStructureService.getOrgEntityByTypesAndAttrCodeAndAttrValue(
Arrays.asList(OrgEntityTypeCode.AGGREGATION_STATION.getCode(),
OrgEntityTypeCode.PROD_LINE.getCode()), "IP", ipAddress);
if (aggregationStation != null) {
printerName = organizationStructureService.
getOrgEntityAttrValue(aggregationStation, "PRINTER_NAME");
if (printerName == null) {
printerName = "";
}
// check if there is order in progress for station
selectedPackagingOrder = getInProgressOrderForStation();
loadOperationTypes();
populateDataFromDB();
} else {
error = true;
errorMessage = MessageUtil.interpolate("ip_not_registred_as_station", ipAddress,
MessageUtil.interpolate(OrgEntityTypeCode.AGGREGATION_STATION.getCode()));
}
}
My question is: How to avoid javascript execution more than once?
I would appreciate any help I can get to sort this one out. Thanks in advance!
Try and use event.preventDefault() to prevent the page refreshing.

Get local IP with Javascript with chrome return an hash/hostname

I'm using this code https://ourcodeworld.com/articles/read/257/how-to-get-the-client-ip-address-with-javascript-only that allow to get local IP within a browser (Chrome in my case) using ice.candidate, but in some situation it doesn't return the IP but a sort of hostname (for example "0ddcdf5f-6ba6-47fb-8183-e44f51a1afc7.local").
I haven't found a real reason because sometimes it works other not.
I didn't found an alternative either.
Here a test https://jsfiddle.net/42g5jzpx/, works fine in my computer but not in others, returning the hostname above.
/**
* Get the user IP throught the webkitRTCPeerConnection
* #param onNewIP {Function} listener function to expose the IP locally
* #return undefined
*/
function getUserIP(onNewIP) { // onNewIp - your listener function for new IPs
//compatibility for firefox and chrome
var myPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var pc = new myPeerConnection({
iceServers: []
}),
noop = function() {},
localIPs = {},
ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g,
key;
function iterateIP(ip) {
if (!localIPs[ip]) onNewIP(ip);
localIPs[ip] = true;
}
//create a bogus data channel
pc.createDataChannel("");
// create offer and set local description
pc.createOffer().then(function(sdp) {
sdp.sdp.split('\n').forEach(function(line) {
if (line.indexOf('candidate') < 0) return;
line.match(ipRegex).forEach(iterateIP);
});
pc.setLocalDescription(sdp, noop, noop);
}).catch(function(reason) {
// An error occurred, so handle the failure to connect
});
//listen for candidate events
pc.onicecandidate = function(ice) {
if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) return;
ice.candidate.candidate.match(ipRegex).forEach(iterateIP);
};
}
// Usage
getUserIP(function(ip){
alert("Got IP! :" + ip);
});
Thanks
Paolo
Thanks for your question.
I understand how you tried but please share your tries on JSFiddle or other.
In my experience, I used db-ip.com api and it works really fine.
Check DBIP
Thanks
I found the solution as specified here : Google Chrome hide local IP by default. It shows something similar to e87e041d-15e1-4662-adad-7a6601fca9fb.local . This behaviour can be changes by setting the variable #enable-webrtc-hide-local-ips-with-mdns to disabled in Chrome://flags

Multiple Webrtc Datachannels, Override anonymous funciton in peerconnection.onicecandidate? [duplicate]

I have the following callback for the onicecandidate event of RTCPeerConnection:
function iceCallback(event) {
if (event.candidate) {
var candidate = event.candidate;
socSend("candidate", candidate. event.target.id);
}
}
I am able to read the candidate from the event.candidate. But when I try to read the event.target.id, I get an exception, cannot read property target of undefined.
"id" is a property I previously created for the RTCPeerConnection. This is for a multiuser video chat.
I am trying to figure out which RTCPeerConnection fired the onicecandidate event so I know to which client I need to send the icecandidate. I am using the adapter.js library.
I assumed that some part of your code would be like below, I have just added a simple modification:
peerConnections ={};
function createNewConncection(id){
var pc = new RTCPeerConnection();
pc.onaddstream = ...
...
//pc.onicecandidate = iceCallback; // OLD CODE
pc.onicecandidate = iceCallback.bind({pc:pc, id:id}); // NEW CODE
peerConnections[id] = pc;
}
function iceCallback(event) {
if (event.candidate) {
var candidate = event.candidate;
//socSend("candidate", candidate. event.target.id); // OLD CODE
socSend("candidate", this.id); // NEW CODE
}
}

Detecting that the peer's browser was closed in a webrtc videochat

I've been implementing a webrtc videochat.
Everything is working smoothly except for the case when the peer closes the browser.
I've been trying to handle this event by implementing an onended callback on the remote mediastream. Though, this callback does not seem to ever be called.
How can I detect that the peer's browser has been closed or that the connection was finished on the other side?
You can use the ICE connection status to determine this. If you disconnect one peer it takes some seconds (~5?) to recoginize it, but it works even without a signalling server.
(assuming you called your peer connection pc)
pc.oniceconnectionstatechange = function() {
if(pc.iceConnectionState == 'disconnected') {
console.log('Disconnected');
}
}
Use signaling gateway to send message to all connected peers that you're leaving; like this:
window.addEventListener('beforeunload', function () {
userLeft();
}, false);
window.addEventListener('keyup', function (e) {
if (e.keyCode == 116)
userLeft();
}, false);
function userLeft() {
signalingGateway.send({
userLeft: true,
whoLeft: 'user-id'
});
}
signalingGateway.on('message', function (signal) {
if (signal.userLeft && signal.whoLeft != 'current-user-id') {
var relevantPeer = listOfPeers[signal.whoLeft];
if (relevantPeer) {
relevantPeer.close();
relevantPeer = null;
}
var relevantLocalStreams = listOfLocalStreams[signal.whoLeft];
if (relevantLocalStreams.length) {
for (var i = 0; i < relevantLocalStreams.length; i++) {
if (relevantLocalStreams[i].stop) {
relevantLocalStreams[i].stop();
}
// it is suggested to stop media tracks instead!
}
}
}
});
I am using a solution like this:
const connection = new RTCPeerConnection(configuration);
connection.onconnectionstatechange = () => {
const connectionStatus = connection.connectionState;
if (["disconnected", "failed", "closed"].includes(connectionStatus)) {
console.log("disconnected");
}
};
I also had this issue. I've solved this by closing the connection at the desired moment(eg. on destructor).
A <- connected -> B
...
Close(A); // after this, B's ice status will go almost instantly to 'kDisconnected'.
Just Use
connection.onclose = function(event){
event.useid // gives the connection closed userid
}

Categories

Resources