W5100: Multiple Sessions on Same Port

I would like to support multiple telnet sessions on port 23 using a W5100, but have so far been unable to make this work. May seem odd, but what I'd really like is for a new client connecting on port 23 to be able to kick an older session off. But, I never even see the second client attempt to connect. It works fine if the two clients are on different ports, like 23 and 24 - When I see a new client connecting on one port I can easily cause the connection to the other port to be closed, and start communicating with the new client. But if both clients are on the same port, I never see any indication that a new client even exists.

Is this just a limitation of the W5100? I've had no problem doing this on other (non-Arduino) hardware.

Regards,
Ray L.

Use 23 to connect to your 'server', then pass the conversation off to another free port.
Typically you can use an array of sessions, si it's easy to drop, or insert sessions as needed.

lastchancename:
Use 23 to connect to your 'server', then pass the conversation off to another free port.
Typically you can use an array of sessions, si it's easy to drop, or insert sessions as needed.

How do you do that? The client (a Putty session) is pretty much hard-wired to port 23. How do I shift it to another port?

Regards,
Ray L.

Good point - I've done similar in the past - with my own client end-points....!

Let me think back what I did then. I should have thunk deeper before replying!

lastchancename:
Good point - I've done similar in the past - with my own client end-points....!

Let me think back what I did then. I should have thunk deeper before replying!

I've done it in the past on Unix/Llnux systems, but I assume the network drivers did some voodoo to handle multiple clients. With the W5100, I don't get any indication a client is even attempting to connect, unless it is on a different port. I suspect it's just a limitation of the W5100, but don't know enough about it to confirm.

Regards,
Ray L.

I remember some of how I did it! (Quite a while ago on a Rabbit core CPU)
I didn’t use a canned Telnet server library - I wrote it on top of IP sockets (not so hard for simple serial sessions).
Then I could see the incoming connection request - and create a new server instance (on a new port)…

The trick I can’t remember was how I told the client to move to the new port…
I need to dig back ten years or so! keep breathing.

lastchancename:
I remember some of how I did it! (Quite a while ago on a Rabbit core CPU)
I didn't use a canned Telnet server library - I wrote it on top of IP sockets (not so hard for simple serial sessions).
Then I could see the incoming connection request - and create a new server instance (on a new port)...

The trick I can't remember was how I told the client to move to the new port...
I need to dig back ten years or so! keep breathing.

Well it doesn't appear it would work here, since I get no indication whatsoever another client is even out there, so I'd never get anywhere near the point of asking it to change ports, even if I knew how. I think perhaps this is just the way the W5100 is.

Regards,
Ray L.

No, the W5100 isn't the problem.
You need to ditch the TelnetServer instance - and create your own.
But I still need to dig back if I can find it - and figure how the clients negotiated away from the base port 23.

It must be possible - because a 'proper' Telnet server can support multiple client sessions that arrive on port 23.

You might beat me to it - do some google searches on telnet session negotiation.
That's what I did all those years ago.

I have a telnet-like server on the playground. It handles up to the maximum number of sockets concurrently.
http://playground.arduino.cc/Code/Telnet

edit: The code has a delay in loop to slow down the serial output so it is readable. Change or remove that delay to speed up the server.

Tim,

I looked at your code when I first came up with this earlier this year. But it still does not address the problem i seem to be seeing.

First, I'm not really using a telnet server. I'm simply setting up an EthernetServer on port 23, and using the PuTTY telnet client in "passive" mode to communicate. So, there is no communication/negotiation between the client and server - the port is opened, and data flows. It all works just fine, except only a single session is allowed at any given time, on any given port. I have code that continuously polls the SnSR register on the W5100, and records the status for each of the four sockets. Whenever the status for any socket changes, it displays the change on the Serial Monitor. Here is a portion of an example trace:

socket 1: SOCK_LISTEN=>SOCK_CLOSED
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_ESTABLISHED=>SOCK_LISTEN
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_ESTABLISHED=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_CLOSED
socket 1: 16=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: 16=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 0: SOCK_LISTEN=>SOCK_CLOSED
Found new client on socket 0 port 23
Opening session on socket 0 port 23
telnet client Connected
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 1: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 1: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 1: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 1: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 1: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 1: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 1: SOCK_LISTEN=>SOCK_ESTABLISHED
socket 3: SOCK_CLOSED=>SOCK_CLOSED

Note that in the middle, a connection is established on port 23. However, shortly after that, I opened another PuTTY instance, also on port 23, yet there is NO change of status on ANY socket resulting from that second PuTTY instance. The W5100 seems to be completely ignoring it. If I instead open a second PuTTY instance on any other port, the connection is established.

If I add a second EthernetServer listening on port 24, then I can open a PuTTY instance on port 23, open another PuTTY instance on port 24, and all works as expected (note my code currently explicitly closes the first connection when the second one opens).

socket 0: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 0: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 0: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 0: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 0: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 0: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_CLOSED
Found new client on socket 1 port 23
Opening session on socket 1 port 23
telnet client Connected
socket 0: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 0: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 0: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 0: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 0: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 2: SOCK_LISTEN=>SOCK_CLOSED
Found new client on socket 2 port 24
Closing session on socket 1
Opening session on socket 2 port 24
telnet client Connected
socket 0: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_ESTABLISHED=>SOCK_LISTEN
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN
socket 1: SOCK_LISTEN=>SOCK_LISTEN
socket 3: SOCK_CLOSED=>SOCK_CLOSED
socket 1: SOCK_CLOSED=>SOCK_CLOSED
socket 3: SOCK_LISTEN=>SOCK_LISTEN

So, how can I have more than one session on a given port, when the W5100 does not so much as give an indication that another client is even trying to connect?

Regards,
Ray L.

One other thing: I find sometimes, especially after updating the firmware, it can take a long time (up to a minute or more) before the Ethernet will respond to external requests at all. It seems to take a long time to connect to the network. Is that normal? Sometimes it seems nearly instantaneous, other times not, and it seems completely random as far as when, and how often, it will misbehave.

Regards,
Ray L.

So, there is no communication/negotiation between the client and server - the port is opened, and data flows.

You must explain this. If there is no communication, how does the "data flows"?

SurferTim:
You must explain this. If there is no communication, how does the "data flows"?

There is no telnet "command" communications or negotiations. The TCP/IP connection is established and data flows only between the users keyboard/display on the PuTTY side, and my application code on the Arduino side. This is the whole point of "Passive" mode telnet - no loging, no negotiating settings or options.

Regards,
Ray L.

RayLivingston:
There is no telnet "command" communications or negotiations. The TCP/IP connection is established and data flows only between the users keyboard/display on the PuTTY side, and my application code on the Arduino side. This is the whole point of "Passive" mode telnet - no loging, no negotiating settings or options.

Regards,
Ray L.

Then you didn't try my code in the playground. No logging, not initial entry, nothing. Just type and it goes. I use PuTTY in raw mode and it works fine with 4 sessions simultaneously.

For reasons I don’t understand, your code does not build for Due, which is my target:

C:\Users\RayL\AppData\Local\Temp\build3888235994780909452.tmp/syscalls_sam3.c.o: file not recognized: File format not recognized
collect2.exe: error: ld returned 1 exit status
Error compiling.

I’m not sure its worth spending time figuring out what it’s unhappy about, since it’s undoubtedly some stupid Arduino build quirk.

Regards,
Ray L.

OK, it does not build using the Arduino IDE, but does build using VisualStudio/VisualMicro. It does indeed allow multiple sessions on the same port.

It appears to me the one significant difference between what you're doing and what I'm doing is that you are using socket.h, while I am trying to do this using EthernetServer/EthernetClient. I'll try to integrate your method in my code and see what happens.

Regards,
Ray L.

OK, next headache.... I need the socket implementation wrapped as a member of Stream, since I re-direct my "console" to various devices at various times, by means of "Stream *pConsole". Am I correct in assuming that to turn your telnet server code into a Stream device I need to implement only these functions?

from Stream:
virtual int available() = 0;
virtual int read() = 0;
virtual int peek() = 0;
virtual void flush() = 0;
from Print:
virtual size_t write(uint8_t) = 0;
virtual size_t write(const uint8_t *buffer, size_t size);

Regards,
Ray L.

For some reason, my telnet playground sketch does compile and run on a Due using IDE v1.6.11.

Don't know what to tell you.

So close, yet so far… I took your code, turned it into a class which inherits from Stream, and built a test program, which works fine. I plumbed it into my big application, and as soon as I open a single telnet session, things go sideways. I figured maybe the fact that my application also runs an EthernetServer on port 80 for the web server could have something to do with the problem, so I added that to the test program, and sure enough it failed. I added a line of code to make the telnet code ignore any sockets on port 80, and the test program works fine again. Put that change into my big app, and I can now open a single telnet session, which works fine. When I either open a second PuTTY instance, or close the first one, something is losing its mind, and the app crashes or at least stops communicating with the SerialMonitor.

I have to believe this still has something to do with the other EthernetServer on port 80, as that is the only other code that is even aware the Ethernet exists. But I don’t know why it’s going wonky when the telnet session is disconnected. Since the console stops functioning at the point where things go wonky, I don’t yet know what’s going on.

Here’s the code for my TelnetServer class:

#ifndef TELNETSERVER_H
#define TELNETSERVER_H

#include <Ethernet.h>
#include <EthernetServer.h>
#include <utility/w5100.h>
#include <utility/socket.h>

class TelnetServer : Stream
{
public:
	TelnetServer();
	TelnetServer(byte port);
	~TelnetServer();
	void begin(void);
	boolean update(void);
	boolean connected(void) { return (activeSocket >= 0); }

	// Virtual methods required by Stream:
	int available();
	int read();
	int peek();
	void flush();

	// Virtual methods required by Print:
	size_t write(uint8_t);
	size_t write(const uint8_t *buf, size_t size);

private:
	static const uint8_t SOCK_CLOSED = 0x00;
	static const uint8_t SOCK_ARP1 = 0x11;
	static const uint8_t SOCK_INIT = 0x13;
	static const uint8_t SOCK_LISTEN = 0x14;
	static const uint8_t SOCK_SYNSENT = 0x15;
	static const uint8_t SOCK_ESTABLISHED = 0x17;
	static const uint8_t SOCK_FIN_WAIT = 0x18;
	static const uint8_t SOCK_CLOSING = 0x1a;
	static const uint8_t SOCK_TIME_WAIT = 0x1b;
	static const uint8_t SOCK_CLOSE_WAIT = 0x1c;
	static const uint8_t SOCK_LAST_ACK = 0x1d;
	static const uint8_t SOCK_ARP2 = 0x21;
	static const uint8_t SOCK_UDP = 0x22;
	static const uint8_t SOCK_ARP3 = 0x31;
	static const uint8_t SOCK_IPRAW = 0x32;
	static const uint8_t SOCK_MACRAW = 0x42;
	static const uint8_t SOCK_PPOE = 0x5f;

	static const uint8_t RX_BUF_LEN = 255;

	void SendReceive(void);
	uint8_t RxBufFree(void) { return RX_BUF_LEN - RxCnt; }

	inline uint32_t saveIRQState(void) { uint32_t pmask = __get_PRIMASK() & 1; __set_PRIMASK(1); return pmask; }
	inline void restoreIRQState(uint32_t pmask) { __set_PRIMASK(pmask); }

	byte socketStat[MAX_SOCK_NUM];
	boolean connectStatus[MAX_SOCK_NUM];
	uint16_t Port;
	EthernetServer *pServer = NULL;
	int8_t activeSocket = -1;
	uint8_t RxBuf[RX_BUF_LEN];
	uint8_t RxGet = 0;
	uint8_t RxPut = 0;
	uint8_t RxCnt = 0;
};

#endif
#include "TelnetServer.h"

TelnetServer::TelnetServer()
{
}


TelnetServer::TelnetServer(byte port)
{
	Port = port;
	pServer = new EthernetServer(port);
	for (int i = 0; i < MAX_SOCK_NUM; i++)
	{
		socketStat[i] = 0;
		connectStatus[i] = 0;
	}
}


TelnetServer::~TelnetServer()
{
}


void TelnetServer::begin(void)
{
	pServer->begin();
}


boolean TelnetServer::update(void)
{
	boolean listening = false;

Serial.printf("\n\n");
	for (int i = 0; i < MAX_SOCK_NUM; i++)
	{
		uint16_t socketport = W5100.readSnPORT(i);
		if (socketport == 80)
			continue;

		uint8_t s = W5100.readSnSR(i);
		socketStat[i] = s;
Serial.printf("Socket %d = %02x\n", i, s);
		if (s == SOCK_CLOSE_WAIT)
		{
Serial.printf("Closing socket %d\n", i);
			close(i);
			connectStatus[i] = false;
			if (activeSocket == i)
				activeSocket = -1;
		}

		if (s == SOCK_LISTEN) 
			listening = true;

		if (s == SOCK_ESTABLISHED && connectStatus[i] == 0)
		{
			// Found a new connection, so close any old ones
			Serial.printf("Connecting on socket %d\n", i);
			if (activeSocket >= 0)
			{
				disconnect(activeSocket);
				//while (W5100.readSnSR(i) == SOCK_ESTABLISHED)
				//	;
				connectStatus[activeSocket] = false;
			}
			activeSocket = i;
			connectStatus[i] = true;
		}
	}

	if (!listening)
	{
		for (int i = 0; i < MAX_SOCK_NUM; i++)
		{
			if (socketStat[i] == 0)
			{
				socket(i, SnMR::TCP, Port, 0);
				listen(i);
				break;
			}
		}
	}
	SendReceive();
	return (activeSocket >= 0);
}


void TelnetServer::SendReceive(void)
{
	if (activeSocket == -1)
		return;

	while (W5100.getRXReceivedSize(activeSocket) && RxBufFree())
	{	
		// Read as much as we can
		int16_t cnt = recv(activeSocket, &RxBuf[RxPut], RxBufFree());
		if (cnt == 0)
			break;
		RxPut += cnt;
		uint32_t pmask = saveIRQState();
		RxCnt += cnt;
		restoreIRQState(pmask);
	}
}


// Virtual methods required by Stream
int TelnetServer::available()
{
	return (int)RxCnt;
}


int TelnetServer::read()
{
	int ret = -1;

	if (RxCnt)
	{
		ret = RxBuf[RxGet];
		RxGet++;
		uint32_t pmask = saveIRQState();
		RxCnt--;
		restoreIRQState(pmask);
	}
	return ret;
}


int TelnetServer::peek()
{
	int ret = -1;

	if (RxCnt)
	{
		ret = RxBuf[RxGet];
	}
	return ret;
}


void TelnetServer::flush()
{
	RxCnt = RxGet = RxPut = 0;
}



// Virtual methods required by Print:
size_t TelnetServer::write(uint8_t c)
{
        uint16_t ret = 0;

	if (activeSocket >= 0)
		ret = send(activeSocket, &c, 1);
        return ret;
}


size_t TelnetServer::write(const uint8_t *buf, size_t size)
{
        uint16_t ret = 0;

	if (activeSocket >= 0)
		ret = send(activeSocket, buf, size);
        return ret;
}

The following is called from loop() to keep the telnet status up-to-date, and to do the switching of the Stream *pConsole pointer that the rest of the app uses to read/write the console.

void UpdateTelnet(void)
{
	if (pTelnetServer->update())
	{
		if (pConsole != (Stream *)pTelnetServer)
		{
			pConsole->printf("\nDisconnecting...\n");
			delay(10);
			pConsole = (Stream *)pTelnetServer;
			pConsole->flush();
			pConsole->printf("\nConnected\n");
		}
	}
	else if (pConsole == (Stream *)pTelnetServer)
	{
		pConsole->printf("\nDisconnecting...\n");
		delay(10);
		pConsole = (Stream *)&Serial;
		pConsole->flush();
		pConsole->printf("\nConnected\n");
	}
}

Regards,
Ray L.

A couple of minor dumb-a$$ bugs in the code posted above, which I have edited to correct.

However, there is some conflict with the web server. When the web server is disabled, the above all works correctly first time, every time, AFAICT. But, when the web server is enabled, the above code is a bit flaky. Telnet sessions sometimes connect, sometimes do not, seemingly randomly. And eventually things just stop working - I can't make any telnet connections, and the serial monitor will eventually stop working.

Regards,
Ray L.