What sort of message does Snapd's API expect? - c#

Snapd has documentation on a REST API.
I'm able to connect to the socket from C# using the following
var snapSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
var snapEndpoint = new UnixEndPoint("/run/snapd.socket");
snapSocket.Connect(snapEndpoint);
var req = Encoding.ASCII.GetBytes("GET /v2/system-info HTTP/1.1");
snapSocket.Send(req, req.Length, 0);
var bytesReceived = new byte[256];
var bytes = 0;
var response = "";
do
{
bytes = snapSocket.Receive(bytesReceived, bytesReceived.Length, 0);
response = response + Encoding.ASCII.GetString(bytesReceived, 0, bytes);
} while (bytes > 0);
Console.WriteLine(response);
But everything halts at snapSocket.Receive - a response is never sent. I suspect that there's something wrong with the message that I'm sending it.

It turns out that it expects a standard HTTP request, which means a Host: line, a Connection: Close line, and two \ns at the very end are required.
The documentation's following claim...
While it is expected to allow clients to connect using HTTPS over a TCP socket, at this point only a UNIX socket is supported.
... is meant only to imply that HTTPS and TCP do not work yet - HTTP is currently the valid request format even when using the UNIX Socket.

I am not fluent in C# at all, but maybe this python snippet can help lead into a solution:
import requests_unixsocket
session = requests_unixsocket.Session()
r = session.get('http+unix://%2Frun%2Fsnapd.socket/v2/snaps')
r.raise_for_status()
r.json()

Related

How can I send GET request with sockets?

I want to send GET/POST request with sockets, and I have this code:
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(Url, 80);
byte[] contentLenght = Encoding.ASCII.GetBytes(Data);
string[] masRequestString ={
"GET /"+Data+" HTTP/1.1\r\n" ,
"Host: "+Url+"\r\n",
"User-Agent: "+textBox1.Text+"\r\n",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n",
"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n",
"Referer: "+textBox2.Text+"\r\n"};
string request = string.Concat(masRequestString);
Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
socket.Send(bytesSent, bytesSent.Length, 0);
Byte[] bytesReceived = new Byte[0x400];
int bytes = socket.Receive(bytesReceived, bytesReceived.Length, 0);
string content = Encoding.ASCII.GetString(bytesReceived, 0, bytes);
When I try to send the request sniffers don't see it. Why?
I have no idea of how you do sniffing but
You are at not sending a correct request, because it misses an \r\n at the end.
You are expecting the server to close the connection after the response is done. Instead you need to care about content-length header and chunked encoding.
And apart from that sending a Content-type header with a GET request makes no sense, because there will be no content sent inside a GET request (only in the response).
I suggest you first get a deeper knowledge of HTTP works before you are trying to implement it. Much better of course would be to use already existing implementations, because HTTP is not the simply protocol it seems to be after having only a short look.
Don't try implement HTTP yourself, socket can take your data and wrap it.
Use HTTP library:
using System.Net;
string url = "https://www.example.com/scriptname.php?var1=hello";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream resStream = response.GetResponseStream();

PTTH or Reverse Http to/from Apple TV

I've been trying to get the event connection running to/from my Apple TV for some time but I can't seem to figure out what I am doing wrong.
I write my request and I get the proper response. But I do not get any events when I pause or when the video stops.
The relevant code for the event connection is as follow:
var endpoint = new IPEndPoint(IPAddress.Parse("192.168.13.37"), 23579);
var socket = new Socket(endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(endpoint);
socket.Connect("192.168.13.13", 7000);
socket.NoDelay = true;
var request = Encoding.UTF8.GetBytes("POST /Reverse HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: PTTH/1.0\r\nX-Apple-Purpose: event\r\nUser-Agent: ItsMe/5.7\r\nX-Apple-Session-ID: f519e023-da6d-4fc2-902f-791c07dd9ff8\r\nContent-Length: 0\r\n\r\n");
socket.Send(request, request.Length, 0);
var response = new Byte[4096];
var read = socket.Receive(response, response.Length, 0);
var result = Encoding.UTF8.GetString(response, 0, read);
Response from Apple TV:
HTTP/1.1 101 Switching Protocols
Date: Thu, 23 Feb 2012 17:33:41 GMT
Upgrade: PTTH/1.0
Connection: Upgrade
My first tries I closed the socket at this point and started a TcpListener (also tried HttpListener). All three approaches have been totally fruitless, just to be complete I'll include my current code (that does not work). I am currently back to TcpListener:
socket.Disconnect(true);
socket.Close();
var listener = new TcpListener(lep);
listener.ExclusiveAddressUse = true;
listener.Start();
while (true)
{
var client = listener.AcceptTcpClient();
using (var reader = new StreamReader(client.GetStream()))
{
while (isRunning)
{
if (reader.Peek() != -1)
{
Console.WriteLine(reader.ReadLine());
}
}
}
client.Close();
}
I am not planning to keep the while (true) but until I get events and can know when the video stops playing I have nothing else to put there.
I do this part first then I send a play command with a URL to a file on my machine and it starts playing on the Apple TV just fine. I alos can send scrub commands and get info back about Position/Duration but no events what so ever.
Any help/suggestions is much appreciated.
I think you make false assumptions about how the Connection works:
You client sends a UPGRADE to the AirPlay-Server.
The AirPlay-Server answers with 101 on that connection and abandones it
The AirPlay-Server initiates a new connection with your client. It will use HTTP for that
Your client answers with 200 or whatever.
You will probably need an opened http-listener, before you post your UPGRADE request, to handle incoming events.
See here for reference: http://tools.ietf.org/id/draft-lentczner-rhttp-00.txt

Proxy won't work in chrome and only partly in firefox

I've written a proxy in c#. It works by getting the http request from the browser sending the request to the site and sending back the site's response to the client. It works in firefox but some of the pages are cut like if it did not send all the response and in chrome it gives blank pages and for google.co.uk in chrome the browser gives "no data received". Can you see a mistake in my code which might be causing all this?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
namespace LexProxy
{
class ProxyServer
{
private TcpListener tcpListener;
public ProxyServer()
{
this.tcpListener = new TcpListener(IPAddress.Any, 3000);
this.tcpListener.Start();
while (true)
{
Console.Write("Waiting for a connection... ");
TcpClient client = tcpListener.AcceptTcpClient();
Thread thread = new Thread(delegate()
{
Serve(client);
});
thread.Start();
}
}
private void Serve(TcpClient client)
{
Console.WriteLine("Connected!");
NetworkStream stream = client.GetStream();
byte[] request = GetBytesFromStream(stream, client.ReceiveBufferSize);
if (request != null)
{
string requestString = System.Text.Encoding.UTF8.GetString(request);
string[] requestParts = requestString.Split(' ');
if (requestParts.Length >= 2)
{
string method = requestParts[0];
if (!requestParts[1].Contains("http://") && !requestParts[1].Contains("https://"))
requestParts[1] = "http://" + requestParts[1];
Uri uri = new Uri(requestParts[1], UriKind.RelativeOrAbsolute);
string host = StringUtils.ReplaceFirst(uri.Host, "www.", "");
int port = uri.Port;
byte[] response = getResponse(host, port, request);
if (response != null)
stream.Write(response, 0, response.Length);
client.Close();
}
}
}
private byte[] getResponse(string host, int port, byte[] request)
{
TcpClient client = new TcpClient(host, port);
NetworkStream stream = client.GetStream();
stream.Write(request, 0, request.Length);
byte[] response = GetBytesFromStream(stream, client.ReceiveBufferSize);
return response;
}
private byte[] GetBytesFromStream(NetworkStream stream, int bufferSize)
{
Byte[] bytes = new Byte[bufferSize];
int i;
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
return bytes;
}
return null;
}
}
}
One minor error is that you're using UTF8 to read first line of request. HTTP request line and headers are ASCII (and body may be UTF8, but doesn't have to be, and may not even be a string at all). The reason it works is b/c for english charset, UTF8 and ASCII happen to be encoded using the same bytes. But that's the minor issue.
Most likely primary issue is hiding in your GetBytesFromStream. You only call .Read once, but that doesn't guarantee that the whole message is returned. It may return as little as just 1 byte... so you need to continue getting more data. How do you know how much more? It's dictated by HTTP protocol which you'd need to properly parse and examine. (At a minimum you're reading until you reach \r\n\r\n byte sequence indicating the end of request headers.
However, this is not enough as there may be a request body, length of which will be specified via an HTTP header, Content-Length: (IIRC), or possibly using chunked encoding. I don't see any code beyond URI examination, so most likely you do not handle HTTP messaging protocol itself, and so it is unlikely to work properly (unless you somehow manage to force browser to use HTTP/0.9 or HTTP/1.0 since those do not reuse connection and send one message per connection, at which point you could just blindly read everything the socket has to give you until browser signals the end of stream by closing its write endpoint of the connection).
But your main problem is that GetBytesFromStream as written won't give you "all" bytes.

Can't get entire response from proxy C#

I'm building a small HTTP proxy that runs between the browser and squid proxy. The browser sends the HTTP request to my proxy that redirects it to the squid proxy, then my application gets the response from the squid proxy and returns it back to the browser.
the problem is that i can't get the full response from the proxy, i get HTTP 200 OK ... (just the response header), but with out the body then i have to call receive method another time to get the body. but if i debug my code (which make the application slower) it get all the response (response header and body)
is there any propriety in the TCPClass that indicates to me that the remote server still have data to send to me ?
here is my code :
static void Main(string[] args)
{
int ServerPort = 8888;
IPAddress localHost = new IPAddress(0x0100007f);
TcpListener listener = new TcpListener(localHost,ServerPort);
listener.Start();
while(true)
{
string requestString = "";
String respenseString = "";
TcpClient application = listener.AcceptTcpClient();
string source = application.Client.RemoteEndPoint.ToString();
byte[] dataFromApp = new byte[application.ReceiveBufferSize];
application.Client.Receive(dataFromApp);
TcpClient tunnel = new TcpClient("127.0.0.1",8080);
tunnel.Client.Send(dataFromApp);
while (tunnel.Client.Connected ==true)
{
if(tunnel.Available != 0)
{
byte[] responseFromProxy = new byte[tunnel.ReceiveBufferSize];
tunnel.Client.Receive(responseFromProxy);
respenseString += Encoding.UTF8.GetString(responseFromProxy);
}
else
{
break;
}
}
application.Client.Send(Encoding.UTF8.GetBytes(respenseString));
}
You should check the return value of tunnel.Client.Receive and application.Client.Receive. Receive doesn't gurantee that it will read dataFromApp.Length bytes
REMARKS: The Receive method reads data into the buffer parameter and returns the number of bytes successfully read
PS: You may also want to try FiddlerCore to write an Http Proxy
There is no "there are N bytes remaining for this message" property on a socket, because a TCP socket is streaming: it sends and receives bytes, not messages.
HTTP defines messages, and if you are implementing an HTTP proxy, you should be familiar with the HTTP 1.1 RFC. There are various ways to determine the lenght of an HTTP message, all of which you have to implement to make sure you can successfully receive and send HTTP messages.
Thanks guys
I've done it :
while (tunnel.Client.Receive(oneByte) != 0)
{
byte[] responseFromProxy = new byte[tunnel.Available];
tunnel.Client.Receive(responseFromProxy);
application.Client.Send(oneByte);
application.Client.Send(responseFromProxy);
}

Sending custom WCF requests via Sockets

I've seen a few questions left half-answered regarding this topic.
I want to send a request to my self hosted WCF application, which uses NetTcpBinding.
The problem is, I have to use sockets.
I've written a transport binding element which opens a TcpListener(on the ChannelListener OnOpen)
This works fine, but in that case - I'll have to use my own message framing model.
Obviously, I'm not going to implement the net.tcp message framing model( http://msdn.microsoft.com/en-us/library/ff470920%28v=prot.10%29.aspx)
I've read a few posts(including http://blogs.msdn.com/b/carlosfigueira/archive/2008/01/13/writing-custom-requests-to-simple-wcf-services.aspx) which suggest I should send a request and interpret the bytes sent via a MessageEncoder/MessageInspector.
This way, I can basically create a message for each of my methods/operations(with minor
changes per request)
I tried this method and I got the bytes in question(through the use of a custom MessageEncoder) when I used a WCF channel(from a ChannelFactory).
I've saved those bytes, and sent them through a TcpClient - the MessageEncoder wouldn't fire up.
When I Open a normal channel:
ChannelFactory<ITestService> factory = new ChannelFactory<ITestService>(new NetTcpBinding(),
"net.tcp://localhost:76599");
factory.CreateChannel().DoSomething(string.Empty); //gets to MessageEncoder
When I try the same via Sockets:
TcpClient cli = new TcpClient("localhost", 76599);
byte[] fileArray = File.ReadAllBytes("c:\\fileFromMessageEncoder.bin");
cli.GetStream().Write(fileArray, 0, fileArray.Length); // Does not get to MessageEncoder
any ideas?
Finally got it to work.
I initiated the default handshake for a Net.Tcp connection prior to sending the bytes I got via the MessageEncoder.
It's important to note that the endpoint uri must be UTF-8 encoded.
The process is pretty easy - send the negotiation request, send an End Preamble message(0x0C) and wait for the server to return a Preamble Ack message(0x0B).
Then you can send the binary envelope and recieve the response from the WCF application.
I hope this post could help someone in the future.
NegotiationConsts.Via via = new NegotiationConsts.Via(uri);
int arraySize = via.EndpointString.Length +
NegotiationConsts.DefaultRequestLength;
byte[] request = new byte[arraySize];
int count = 0;
request[count++] = NegotiationConsts.Version.RECORD_TYPE;
request[count++] = NegotiationConsts.Version.MAJOR_VERSION;
request[count++] = NegotiationConsts.Version.MINOR_VERSION;
request[count++] = NegotiationConsts.Mode.RECORD_TYPE;
request[count++] = NegotiationConsts.Mode.DUPLEX;
request[count++] = NegotiationConsts.Via.RECORD_TYPE;
request[count++] = via.Length;
via.EndpointString.CopyTo(request,count);
count+=via.EndpointString.Length;
request[count++] = NegotiationConsts.WCFEncoding.RECORD_TYPE;
request[count++] = NegotiationConsts.WCFEncoding.BINARY_ENCODING;
return request;

Categories

Resources