A little background of what I'm doing. I have a home-brew smart thermostat that is connected to my wifi. The device hosts a website that is used to control everything. My main gripe is how unreliable it is to access the device via hostname (especially in android which will be the main controller). I'm trying to find out a way of reliably accessing the device without knowing what the IP address is.
There's two options I'm thinking of, but would like some thoughts on if there are better ways of doing it, or other possible downfalls for my ideas here.
External Website
After wifi configuration, device turns on and sends its internal IP to an external server
External server gets request and logs external IP and associated internal IP
User goes to external website: my.thermostat.com
Website looks up external IP from request, determines internal IP
Website redirects page to internal IP
Therefore, going to my.thermostat.com will redirect to the internal IP.
Downsides:
Requires internet access
Multiple devices with the same external ip?
Android/iOS App
Launch app
App scans current subnet for device
Sends request to each IP: http://x.x.x.x/isThermostat.html
When an expected response is received, launch browser to that IP
Downsides:
Requires app to access control panel
What to do if you want to access via computer? Rely on hostname?
The app route seems like it would be the least complicated, but I like the idea of being able to access the thermostat with any device, so it would still be a little tricky on computers.
Any thoughts would be greatly appreciated!
EDIT
Giving some more info on the hardware itself, it's an arduino yun that will either be connected either via ethernet or wifi. I'd like to stay away from static configurations or setting static DHCP leases. I imagine making a couple of these to give to friends and whatnot, so the least amount of user setup the better.
Can you give an static internal IP ?
Normally in your home router you have a option to give static IP to a given MAC address, or you the definition of the device to setup the static IP.
I think both of your solutions are two complex for something simple.
You should consider a static IP for the device, disabling DHCP on the device and configuring a static IP in the router that refers to the MAC hardware address of the device. This should avoid problems with changing a hostname. Essentially it will allow for you to refer the address within the LAN with a consistent IP.
You haven't mentioned exactly what device it is, so I can't suggest a clear configuration setup, but you should be able to Google for answers. What you do next depends on what you need to achieve.
If you need to access the device from the WAN (Internet) then you need a dynamic DNS service like https://duckdns.org/why.jsp
A DDNS is a hosted service that forwards requests to a subdomain, like: thermostat.duckdns.org to your WAN IP (your router/modem). You install a helper application inside your network that lets the DDNS know every 5 minutes what your WAN IP is, therefore keeping it pointing at you.
From there if you visit: thermostat.duckdns.org it will forward you to your WAN IP, from which point your router then decides what to do with the request. You'll want to setup your router to port forward (port 80 for a web server such as apache) that points to your internal static IP of your thermostat device. Your device then serves the website.
This method allows you to run a web server on an computer or device within a LAN that is accessible from WAN (Internet).
So I ended up going with the android app that scans the local subnet for the device. I have an html file on the server called knockknock.html that simply has the text "HELLO". For anyone interested, here's a first rendition of the code I'm using:
/*
* Get the IP and subnet info and start scanning the local subnet
*/
void findServer() {
WifiManager wifi = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
DhcpInfo dhcp = wifi.getDhcpInfo();
if (dhcp == null) {
Log.d(TAG, "No DHCPInfo on WiFi side.");
}
//Get the string verison of the IP/subnet from the int given by WifiManager
final String ip = intToIp(dhcp.ipAddress);
final String mask = intToIp(dhcp.netmask);
//Start the scanner on a separate thread from the UI
Thread thread = new Thread() {
#Override
public void run() {
serverScanner(ip, mask);
}
};
thread.start();
}
/*
* Convert an integer to a human readable format
*/
String intToIp(int i) {
return ( i & 0xFF) + "." +
((i >> 8 ) & 0xFF) + "." +
((i >> 16 ) & 0xFF) + "." +
((i >> 24 ) & 0xFF ) ;
}
/*
* Scan the given subnet for the server
*/
void serverScanner(String ip, String netmask) {
//Get the first part of the IP
//TODO: Add support for various subnet masks
String iIPv4 = ip.substring(0, ip.lastIndexOf("."));
iIPv4 += ".";
Log.v(TAG, "Current IP Structure: " + iIPv4);
// Loop to scan each address on the local subnet
for (int i = 1; i < 255; i++) {
//Check to see if the server exists
if (checkServer(iIPv4 + i)) {
//It does exist, so let's set the variable and preferences
mServerIP = iIPv4 + i;
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString("LastServerIP", mServerIP);
editor.commit();
return; //And we're done here
}
}
}
/*
* Send a request to the server and see if it's alive and what we're looking for
*/
boolean checkServer(String ip) {
BufferedReader inputStream = null;
try {
HttpGet httpget = new HttpGet("http://" + ip + "/knockknock.html");
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 100);
HttpConnectionParams.setSoTimeout(httpParameters, 100);
HttpClient httpclient = new DefaultHttpClient(httpParameters);
HttpResponse response;
response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
String result = convertStreamToString(instream);
instream.close();
if (result.equals("HELLO\n")) {
return true;
}
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
//No response or not the response we're looking for, so return false
return false;
}
Related
I'm new to the subject area so sorry If I've not explained things well.
I created a web API client that sends strings from my HTML (imbedded with JavaScript) back to a node.js server using Web Sockets.
I then swapped out the node.js server with a C++ application that still receives the string data perfectly.
My JS Client(within html script) :
function SendWSData(){
const ws = new WebSocket("ws://localhost:PORT");
ws.addEventListener("open", () => {
console.log("We are connected!");
const stringvalue = `this is a test string`
ws.send(stringvalue);
});
ws.addEventListener("message", ({ data }) => {
console.log(data);
});
}
My C++ Server:
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
using namespace std;
using tcp = boost::asio::ip::tcp;
string stringVal;
int main() {
auto const address = boost::asio::ip::make_address("IP");
auto const port = static_cast<unsigned short>(std::atoi("PORT"));
boost::asio::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
tcp::socket socket{ioc};
acceptor.accept(socket);
std::cout<<"socket accepted"<<std::endl;
std::thread{[q = std::move(socket)]() mutable {
boost::beast::websocket::stream<tcp::socket> ws {std::move(q)};
ws.accept();
while(1)
{
boost::beast::flat_buffer buffer;
ws.read(buffer);
auto out = boost::beast::buffers_to_string(buffer.cdata());
stringVal = out
std::cout<<"stringVal = "<<stringVal<<std::endl;
}
}
This reads data coming into the buffer, and assigns it to a string variable on my C++ app, for me to use.
My question is how would I go about sending back a string to the web page from the C++ app? (especially as my web page is the client and the C++ app is the server)
I know that the server is meant to return a response which is standard, however does that change if I'm trying to implement a function based response? I want to generate a button on the webpage and textbox, and when clicked I get string data from the C++ app.
I've not found much online about sending data from server back to client, as I'm trying to get a request (which is the behaviour of a client). (Could I possibly generate another Web Socket and swap the roles of my web API and C++ app for this particular case; possibly multithreading it?)
I tried to create a simple IP address spoofing program. The goal is to convince computer game on local network to connect to an internet server. The game only discovers servers via UDP broadcast and won't accept server IP.
So instead I want to trick the game into thinking that it received answer info from the internet server by creating a fake UDP packet.
I successfully used raw-socket to generate IPV4 packet with UDP payload. As long as I set the correct IP it gets sent.
However, If I put fake IP in the packet, it won't get out of the machine. I can't see it in Wireshark on my machine. I also noticed that something corrects IPV4 checksums on my packet. I always send checksum 0xFFFF, but Wireshark sees this:
How I send it using raw-socket:
const raw = require("raw-socket");
const UDPPacket = require("../generic/UDPPacket");
const IPV4Packet = require("../generic/IPV4Packet");
var socket = raw.createSocket({ protocol: raw.Protocol.UDP });
socket.on("message", function (buffer, source) {
console.log("received " + buffer.length + " bytes from " + source);
});
// UDPPacket and IPV4 packet are classes that I wrote in order to
// generate the UDP and IPV4 byte data
const packet = new UDPPacket();
packet.srcPort = 27888;
packet.dstPort = 1234;
packet.data = responseBuffer;
const buf = packet.fullBuffer;
const ipv4packet = new IPV4Packet();
ipv4packet.payloadBuffer = buf;
// I send the message form several IPs, but only mine works
const iprand = "192.168.110.";
let ipincrement = 75 * 2;
// my actual IP right now
ipv4packet.srcAddr = "192.168.110.79";
ipv4packet.dstAddr = "192.168.110.1";
setInterval(() => {
// Try to send it from another IP
ipv4packet.srcAddr = iprand + Math.round((++ipincrement)/2);
const ipv4buf = ipv4packet.fullBuffer;
socket.send(ipv4buf, 0, ipv4buf.length, ipv4packet.dstAddr, function (error, bytes) {
// I'm not sure what does this exactly do,
// I found it on the internet ¯\_(ツ)_/¯
// But without it, I cannot send the IPV4 headers
socket.setOption(
raw.SocketLevel.IPPROTO_IP,
raw.SocketOption.IP_HDRINCL,
new Buffer([0x01, 0x00, 0x00, 0x00]),
4
);
},
function (error, bytes) {
// always prints that bytes were sent
if (error)
console.log(error.toString());
else
console.log(bytes, " bytes sent!");
}
);
}, 700)
Who is blocking and changing my packets? I tried disabling firewall, didn't help.
Note that the messages are getting lost EVEN IF THE TARGET IS THE LOCAL MACHINE.
See https://msdn.microsoft.com/en-us/library/windows/desktop/ms740548(v=vs.85).aspx:
On Windows 7, Windows Vista, Windows XP with Service Pack 2 (SP2), and Windows XP with Service Pack 3 (SP3), the ability to send traffic over raw sockets has been restricted in several ways:
UDP datagrams with an invalid source address cannot be sent over raw sockets. The IP source address for any outgoing UDP datagram must exist on a network interface or the datagram is dropped. This change was made to limit the ability of malicious code to create distributed denial-of-service attacks and limits the ability to send spoofed packets (TCP/IP packets with a forged source IP address).
For example it needs to call a web service hosted with SSL.
If it can, how to pass the client certificate then?
Thanks a lot!!
WinJS.xhr({
type: "GET",
url: "https://localhost:442/WebService1.asmx?op=Login",
}).then(function success(res) {
var debug1 = res.responseText + res.responseURL;
}, function error(err) {
var debug2 = err.responseText + err.responseURL;
}, function completed(result) {
if (result.status === 200) {
// do something
}
});
The debugging point will jump to 'complete(result)' function, but the status code is '0'. Even if I change URL to other https site (e.g. https://www.w3.org), result is the same.
------------- Update 1 ---------------------
If it's in C# I could use following code to pass client certificate. However if I want to change origial WinJs.xhr to HttpClient, just copy & paste seems not working as .js file could not understand all syntax?
var certQuery = new CertificateQuery();
var cert = (await CertificateStores.FindAllAsync(certQuery)).FirstOrDefault(c=>c.Issuer.StartsWith("xxxx",StringComparison.CurrentCultureIgnoreCase));
var filter = new HttpBaseProtocolFilter();
if (cert != null)
{
filter.ClientCertificate = cert;
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted | ChainValidationResult.InvalidName);
}
var hc = new Windows.Web.Http.HttpClient(filter);
var uri = new Windows.Foundation.Uri(url);
hc.getStringAsync(uri).done({.......});
E.g.
1) How to write 'Using .... ' in JS file?
2) How to use "await" or "'FindAllAsync'" in this line? etc.
var cert = (await CertificateStores.FindAllAsync(certQuery)).FirstOrDefault(c=>c.Issuer.StartsWith("xxxx",StringComparison.CurrentCultureIgnoreCase));
WinJS.xhr wraps XMLHttpRequest( https://msdn.microsoft.com/en-us/library/windows/apps/br229787.aspx ) with a Promise-like interface (a WinJS Promise, not an ES6 Promise, but the concept is similar).
XMLHttpRequest has the withCredentials property which allows you to specify whether client-side credentials, including client-side certificates, should be sent or not - but there is no API that would allow you to specify which specific client-side certificate should be used.
Fortunately WinJS exposes the Windows.Web.Http.HttpClient type which gives you more control over client authentication, including client-side certificates - but your UWP application must have "Enterprise capability" to use the user's My certificate store - otherwise non-Enterprise UWP applications only have certificates in their Application Certificate Store:
https://blogs.windows.com/buildingapps/2015/11/23/demystifying-httpclient-apis-in-the-universal-windows-platform/#Dr3C9IMHv5pTPOrB.97
You must first add it to the app’s certificate store by following these instructions. Apps with enterprise capability can also use existing client certificates in the user’s ‘My’ store.
On the iOS clients, I'm using SocketRocket by Square: https://github.com/square/SocketRocket
Everywhere I have looked, I have found comparisons of Websocket libraries based on web applications accessed from browser, or queried in a database, but nothing as yet for clients that are iOS smartphone apps.
The clients would connect to the remote server on request through the app (i.e. the connection isn't "always-on" or done through a mobile browser or proxy or GameCenter), and, once connected, be paired with other clients in a two-player "game" situation. Until a match ends, the connection would need to persist, and the server would be responsible for timing each user's turn and receiving & issuing commands from/to each user, sort of like a turn-based game except each turn has a server-managed time limit. After a match ends (generally 15-20 minutes), if a user doesn't want another match with another random opponent, then the connection would be closed and the user logged off; users that want to continue would then be matched with another user by the hosting server (running Node.js and the Websocket library).
Some of the options I have considered include
Socket.IO 1.0: http://socket.io/
Sockjs: https://github.com/sockjs
ws: https://github.com/einaros/ws
nodejs-websocket: https://www.npmjs.com/package/nodejs-websocket
but heard from https://medium.com/#denizozger/finding-the-right-node-js-websocket-implementation-b63bfca0539 that Socket.IO isn't optimal for heavy user traffic (and I'm anticipating more than 300 users requesting matches at any one point), and that Sockjs doesn't have some command query feature, but didn't quite find a conclusive answer in the context of smartphones or iOS devices -- not browsers -- either way, in any situation.
The question is what Node.js server Websocket library might play nicest or interface with the fewest stability/scalability/complexity concerns with the iOS clients running SocketRocket? The SocketRocket wiki itself isn't helpful as it uses a Python/Go-based server side test.
EDIT: Potentially helpful resource:
http://www.teehanlax.com/blog/how-to-socket-io-swift/
Only missing thing is a comparison or discussion of other potential websocket APIs, not just Socket.IO. But this is a start in that it seems to be working with the latest iOS, SocketRocket, and Socket.IO builds.
I like Sockjs because it is simple. Here is an implementation for SocketRocket --> Sockjs that works as proof of concept
NEED:
-SocketRocket (add libicucore.dylib, Security.framework and CFNetwork.framework to your project)
-Node.js
-Sockjs Server
SERVER:
var http = require('http'),
sockjs = require('sockjs'),
sockserver = sockjs.createServer(),
connections = [];
sockserver.on('connection', function(conn) {
console.log('Connected');
connections.push(conn);
conn.on('data', function(message) {
console.log('Message: ' + message);
// send the message to all clients
for (var i=0; i < connections.length; ++i) {
connections[i].write(message);
}
//
});
conn.on('close', function() {
connections.splice(connections.indexOf(conn), 1); // remove the connection
console.log('Disconnected');
});
});
var server = http.createServer();
sockserver.installHandlers(server, {prefix:'/sockserver'});
server.listen(3000, '0.0.0.0'); // http://localhost:3000/sockserver/websocket
CLIENT (ViewController.m):
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
{
SRWebSocket *myWebSocket;
__weak IBOutlet UILabel *connectionStatus;
__weak IBOutlet UITextView *myTextView;
}
- (void)viewDidLoad {
[super viewDidLoad];
connectionStatus.textColor = [UIColor redColor];
myWebSocket = [[SRWebSocket alloc] initWithURL:[[NSURL alloc] initWithString:#"http://localhost:3000/sockserver/websocket"]];
myWebSocket.delegate = self;
[myWebSocket open];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
myTextView.text = message;
NSLog(#"message: %#",message);
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
connectionStatus.text = #"Disconnected";
connectionStatus.textColor = [UIColor redColor];
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket{
connectionStatus.text = #"Connected";
connectionStatus.textColor = [UIColor greenColor];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
}
#end
src: http://nunoferro.pt/?p=22
I want to create an application where a web server can get the MAC Address of the clients logging in. The only possible way I could think of was to create a JAVA applet which contains java.net methods to find the mac address
I am using javascript to call the applet methods, but the browser is not allowing those methods to execute. Below is the applet I have created.
import java.applet.*;
import java.awt.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
public class AppletRunner extends Applet{
// The method that will be automatically called when the applet is started
public void init()
{
// It is required but does not need anything.
}
//This method gets called when the applet is terminated
//That's when the user goes to another page or exits the browser.
public void stop()
{
// no actions needed here now.
}
//The standard method that you have to use to paint things on screen
//This overrides the empty Applet method, you can't called it "display" for example.
public void paint(Graphics g)
{
//method to draw text on screen
// String first, then x and y coordinate.
g.drawString(getMacAddr(),20,20);
g.drawString("Hello World",20,40);
}
public String getMacAddr() {
String macAddr= "";
InetAddress addr;
try {
addr = InetAddress.getLocalHost();
System.out.println(addr.getHostAddress());
NetworkInterface dir = NetworkInterface.getByInetAddress(addr);
byte[] dirMac = dir.getHardwareAddress();
int count=0;
for (int b:dirMac){
if (b<0) b=256+b;
if (b==0) {
macAddr=macAddr.concat("00");
}
if (b>0){
int a=b/16;
if (a==10) macAddr=macAddr.concat("A");
else if (a==11) macAddr=macAddr.concat("B");
else if (a==12) macAddr=macAddr.concat("C");
else if (a==13) macAddr=macAddr.concat("D");
else if (a==14) macAddr=macAddr.concat("E");
else if (a==15) macAddr=macAddr.concat("F");
else macAddr=macAddr.concat(String.valueOf(a));
a = (b%16);
if (a==10) macAddr=macAddr.concat("A");
else if (a==11) macAddr=macAddr.concat("B");
else if (a==12) macAddr=macAddr.concat("C");
else if (a==13) macAddr=macAddr.concat("D");
else if (a==14) macAddr=macAddr.concat("E");
else if (a==15) macAddr=macAddr.concat("F");
else macAddr=macAddr.concat(String.valueOf(a));
}
if (count<dirMac.length-1)macAddr=macAddr.concat("-");
count++;
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
macAddr=e.getMessage();
} catch (SocketException e) {
// TODO Auto-generated catch block
macAddr = e.getMessage();
}
return macAddr;
}
}
Applets cannot normally access these functions for security reasons. To avoid these restrictions, you need a signed applet, along with a policy file.
You can then write a policy file which grants your applet access to the functionality it needs. If the user then grants your applet the necessary permissions (it will prompt for them), your applet can use the functions.
In Netbeans, you can sign an application enabling the WebStart:
Access to Your project > properties > Application > WebStart
Check "Enable Web Start". This show a sectin titled signing.
Click the "Customize" button located in the signing section.
Select "self-sign by generated key".
I don't think this will be possible. Web servers communicate with clients several layers above the link layer where MAC addresses live -- it's abstracted away by TCP/IP and there's no reason for the client to send it unless you specifically have client code to do that.
The reason your Java code isn't working is because the Java sandbox's security manager disallows such low-level calls -- which it should! If you ever do find a way to get that thing to work (which I doubt you will) you should promptly report it to Oracle because it shouldn't be happening at all.
I can't see much of a reason why you'd want it either, to be honest.
The Java applet is prevented to access those methods on the client because it runs in a protected sandbox.
It might not be possible within a browser, since it is against the sandboxing paradigm. You might have some luck with browser-specific native code extensions.
However, the important exception is if your web server is in the same local area network (same switch) as the client - then, the MAC address of the client is known to the server because it is still present in the IP packet.