|
|
nono 1.7.0
//
// nono
// Copyright (C) 2021 nono project
// Licensed under nono-license.txt
//
//
// シリアルポートの TCP ドライバ
//
#include "comdriver_tcp.h"
#include "config.h"
#include "hostcom.h"
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/telnet.h>
// コンストラクタ
COMDriverTCP::COMDriverTCP(HostDevice *hostdev_)
: inherited(hostdev_, "tcp")
{
port = -1;
}
// デストラクタ
COMDriverTCP::~COMDriverTCP()
{
if (sock.Valid()) {
hostdev->DelOuter(sock);
sock.Close();
}
if (ls.Valid()) {
hostdev->DelListen(ls);
ls.Close();
}
}
// ドライバ初期化
bool
COMDriverTCP::InitDriver(bool startup)
{
struct addrinfo hints;
struct addrinfo *res, *ai;
int r;
// host, port を取得
const char *host = "::1";
std::string key = hostdev->GetConfigKey();
const ConfigItem& item = gConfig->Find(key + "-tcp-port");
std::string portstr = item.AsString();
port = atoi(portstr.c_str());
if (port < 1 || port > 65535) {
port = -1;
errmsg = item.ErrMsg();
putmsg(1, "%s", errmsg.c_str());
return false;
}
// hints を用意
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
r = getaddrinfo(host, portstr.c_str(), &hints, &res);
if (r != 0) {
const char *em;
if (r == EAI_SYSTEM) {
em = strerror(errno);
} else {
em = gai_strerror(r);
}
errmsg = item.ErrMsg("getaddrinfo(%s): %s", host, em);
putmsg(1, "%s", errmsg.c_str());
return false;
}
if (res == NULL) {
errmsg = item.ErrMsg("getaddrinfo(%s): no address found", host);
putmsg(1, "%s", errmsg.c_str());
return false;
}
// res を順番に試して成功したらそれで待ち受ける
ls = -1;
for (ai = res; ai && ls == -1; ai = ai->ai_next) {
int on = 1;
ls = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (ls == -1) {
errmsg = string_format("socket(family=%d): %s",
(int)ai->ai_family, strerror(errno));
putmsg(1, "%s", errmsg.c_str());
continue;
}
if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
errmsg = string_format("setsockopt(SO_REUSEADDR): %s",
strerror(errno));
putmsg(1, "%s", errmsg.c_str());
ls.Close();
continue;
}
if (setsockopt(ls, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) {
errmsg = string_format("setsockopt(SO_REUSEPORT): %s",
strerror(errno));
putmsg(1, "%s", errmsg.c_str());
ls.Close();
continue;
}
if (bind(ls, ai->ai_addr, ai->ai_addrlen) == -1) {
errmsg = string_format("bind: %s", strerror(errno));
putmsg(1, "%s", errmsg.c_str());
ls.Close();
continue;
}
if (listen(ls, 0) == -1) {
errmsg = string_format("listen: %s", strerror(errno));
putmsg(1, "%s", errmsg.c_str());
ls.Close();
continue;
}
// ここまで来たら成功
break;
}
freeaddrinfo(res);
if (ls == -1) {
return false;
}
if (hostdev->AddListen(ls, EV_ADD) < 0) {
errmsg = string_format("AddListen(ADD): %s", strerror(errno));
putmsg(1, "%s", errmsg.c_str());
return false;
}
return true;
}
// Driver の Dispatch() は udata を返す。
int
COMDriverTCP::Dispatch(int udata)
{
if (udata == HostDevice::LISTEN_SOCKET) {
if (Accept()) {
// 呼び出し側に戻って通知を出したいので LISTEN_SOCKET のまま帰る
return udata;
} else {
// 失敗時は何もしない
return HostDevice::DONE;
}
}
return udata;
}
// 待ち受けソケットに着信した。
// 成功すれば true を返す。
bool
COMDriverTCP::Accept()
{
struct sockaddr addr;
socklen_t addrlen;
for (;;) {
addrlen = sizeof(addr);
sock = accept(ls, &addr, &addrlen);
if (sock < 0) {
if (errno == EINTR) {
continue;
}
// XXX どうする?
putmsg(0, "accept: %s", strerror(errno));
return false;
}
break;
}
// 待ち受けソケットのほうは一旦通知オフにする?
if (hostdev->AddListen(ls, EV_DISABLE) < 0) {
putmsg(0, "AddListen(DISABLE): %s", strerror(errno));
return false;
}
putmsg(1, "Accept a connection");
// エコーオフ
WriteCmd({ WILL, TELOPT_ECHO });
negotiation++;
// 全二重を使用
WriteCmd({ WILL, TELOPT_SGA });
negotiation++;
// コマンド送信中にエラーが起きたら終了
if (sock.Valid() == false) {
return false;
}
// 新しいソケットを kevent に登録
if (hostdev->AddOuter(sock) < 0) {
putmsg(0, "AddOuter: %s", strerror(errno));
Close();
return false;
}
return true;
}
// 外部への1バイト送信
void
COMDriverTCP::Write(uint32 data)
{
uint8 buf[1];
int n;
if (sock.Valid()) {
buf[0] = data;
n = write(sock, buf, sizeof(buf));
if (n < 0) {
putmsg(0, "write: %s", strerror(errno));
// エラーならソケットをクローズ
Close();
return;
}
if (n < sizeof(buf)) {
putmsg(0, "write: short");
return;
}
}
}
// TELNET コマンドを送信。
// cmd には IAC を除いたデータ列を指定する。
void
COMDriverTCP::WriteCmd(const std::vector<uint8>& cmd)
{
if (loglevel >= 1) {
std::string str;
for (auto data : cmd) {
str += ' ';
str += GetCommandName(data);
}
putmsgn("Send %s", str.c_str() + 1);
}
Write(IAC);
for (auto data : cmd) {
Write(data);
}
}
// 外部からの1バイト受信
int
COMDriverTCP::Read()
{
uint8 buf[1];
int r;
r = read(sock, buf, sizeof(buf));
if (r < 0) {
putmsg(0, "read: %s", strerror(errno));
Close();
return NODATA;
}
if (r == 0) {
Close();
return NODATA;
}
uint8 data = buf[0];
if (__predict_false(recvcmd.empty() == false || data == IAC)) {
// TELNET コマンド
return Command(data);
} else {
// 平文
return data;
}
}
// データソケットをクローズして、待ち受けソケットの通知を再開する。
void
COMDriverTCP::Close()
{
putmsg(1, "Close the connection");
// データソケットをクローズ
if (sock.Valid()) {
hostdev->DelOuter(sock);
sock.Close();
}
// 待ち受けソケットを再開
if (hostdev->AddListen(ls, EV_ENABLE) < 0) {
putmsg(0, "AddListen(ENABLE): %s", strerror(errno));
return;
}
}
// TELNET コマンド受信。
// VM 側に文字を渡す場合は 0..255 を返す。
// VM に文字を渡さない場合は NODATA を返す。
int
COMDriverTCP::Command(uint8 code)
{
recvcmd.push_back(code);
putmsg(2, "Read cmd $%02x %s", code, GetCommandName(code).c_str());
if (cmdstate == WILL) {
// WILL: 送信側がこのオプション使用を宣言
// DONT: 送信側がこのオプション無効を宣言
// (今の所動作が同じなので同じ state を使う)
if (negotiation > 0) {
// 送ったコマンドに対する応答を受信したのなら、何もしない
putmsg(1, "Recv response %s %s",
GetCommandName(recvcmd[1]).c_str(),
GetCommandName(recvcmd[2]).c_str());
negotiation--;
} else {
// 向こう発のコマンドを受信したのなら、応答する
putmsg(1, "Recv command %s %s",
GetCommandName(recvcmd[1]).c_str(),
GetCommandName(recvcmd[2]).c_str());
// どのコマンドもサポートしていないので、
// WILL に対しては DONT で無視。
// DONT に対しては DONT で応答。
WriteCmd({ DONT, recvcmd[2] });
}
recvcmd.clear();
cmdstate = 0;
} else if (cmdstate == DO) {
// DO: 送信側がこちらにこのオプション使用を要求
// WONT: 送信側がこちらにこのオプション無効を要求
// (今の所動作が同じなので同じ state を使う)
if (negotiation > 0) {
// 送ったコマンドに対する応答を受信したのなら、何もしない
putmsg(1, "Recv response %s %s",
GetCommandName(recvcmd[1]).c_str(),
GetCommandName(recvcmd[2]).c_str());
negotiation--;
} else {
// 向こう発のコマンドを受信したのなら、応答する
putmsg(1, "Recv command %s %s",
GetCommandName(recvcmd[1]).c_str(),
GetCommandName(recvcmd[2]).c_str());
// DO に対しては WONT で拒否。
// WONT に対しては WONT で応答。
WriteCmd({ WONT, recvcmd[2] });
}
recvcmd.clear();
cmdstate = 0;
} else if (cmdstate == SB) {
// IAC, SE で終端
if (recvcmd[recvcmd.size() - 2] == IAC &&
recvcmd[recvcmd.size() - 1] == SE )
{
// 未知のシーケンスを読み飛ばすだけで、何もしない
recvcmd.clear();
cmdstate = 0;
}
} else if (recvcmd.size() < 2) {
// 1バイト目は IAC のはず
} else if (recvcmd.size() == 2) {
// 2バイト目がコマンド
cmdstate = 0;
switch (recvcmd[1]) {
case SE: // SE はここに来ないはずだが、来てもここで終了でいい。
case NOP: // NOP はたぶん何もしない。
FALLTHROUGH;
case DM:
// XXX 未確認
FALLTHROUGH;
case BREAK:
// XXX Break 文字を送る
FALLTHROUGH;
case IP:
case AO:
case AYT:
case EC:
case EL:
case GA:
// XXX 未実装、たぶん引数はない
putmsg(1, "Recv command %s", GetCommandName(recvcmd[1]).c_str());
recvcmd.clear();
break;
case SB:
// この後 SE までがサブネゴシエーション
putmsg(1, "Recv command %s", GetCommandName(recvcmd[1]).c_str());
cmdstate = SB;
break;
case WILL:
case DONT:
// 次の1文字がオプション
cmdstate = WILL;
break;
case DO:
case WONT:
// 次の1文字がオプション
cmdstate = DO;
break;
default:
// それ以外なら、その文字
recvcmd.clear();
return code;
}
}
return NODATA;
}
// TELNET のコマンド、オプション名を返す
/*static*/ std::string
COMDriverTCP::GetCommandName(uint8 code)
{
switch (code) {
case 0x00: return "Transmit-Binary";
case 0x01: return "Echo";
case 0x03: return "Suppress-Go-Ahead";
case 0x05: return "Telnet-Status-Option";
case 0x06: return "Telnet-Timing-Mark";
case 0x18: return "Terminal-Type";
case 0x22: return "Telnet-Line-Mode";
case 0xf0: return "SE";
case 0xf1: return "NOP";
case 0xf2: return "DataMark";
case 0xf3: return "Break";
case 0xf4: return "IP";
case 0xf5: return "AO"; // Abort Output
case 0xf6: return "AYT"; // Are You There
case 0xf7: return "EC"; // Erase Character
case 0xf8: return "EL"; // Erase Line
case 0xf9: return "GA"; // Go Ahead
case 0xfa: return "SB";
case 0xfb: return "WILL";
case 0xfc: return "WONT";
case 0xfd: return "DO";
case 0xfe: return "DONT";
case 0xff: return "IAC"; // Interpret As Command
default:
return string_format("(unknown $%02x)", code);
}
}
// モニタ (ドライバ依存情報のみ)
void
COMDriverTCP::MonitorScreenMD(TextScreen& screen, int y)
{
screen.Print(0, y++, "Listen Port: %u", port);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.