[BlueLeaf1336]> PROGRAM> WinSock勉強会>

WinSockの基本 -- その3

historyTOP

2002/12/02:作成

T**EntTOP

これまでに、THostEntに関する関数を使ってきたので、WinSock.pasにある似たような構造体についても、考えてみることにする。 で、TNetEnt(PNetEnt)TServEnt(PServEnt)TProtoEnt(PProtoEnt) の3つが見つかる。宣言はこんな感じ。

type
  PNetEnt = ^TNetEnt;
  {$EXTERNALSYM netent}
  netent = record
    n_name: PChar;
    n_aliases: ^PChar;
    n_addrtype: Smallint;
    n_net: u_long;
  end;
  TNetEnt = netent;

あんまり情報がないが、わかる範囲で。Joint Endeavour of Delphi Innovators (Project JEDI)にあったらしいWinSock2.pasのコメントを参考に。

n_name: PChar;
official name of net(ネットの正式名称)
n_aliases: ^PChar;
alias list(別名リスト)
n_addrtype: Smallint;
net address type(ネットアドレスタイプ)
n_net: u_long;
network #(ネットワークNO)
type
  PServEnt = ^TServEnt;
  {$EXTERNALSYM servent}
  servent = record
    s_name: PChar;
    s_aliases: ^PChar;
    s_port: Word;
    s_proto: PChar;
  end;
  TServEnt = servent;

コピー〜あんまり情報がないが、わかる範囲で。

s_name: PChar;
official service name(サービスの正式名称)
s_aliases: ^PChar;
alias list(別名リスト)
s_port: Word;
port #(ポート番号)
s_proto: PChar;
protocol to use(使用プロトコル)
type
  PProtoEnt = ^TProtoEnt;
  {$EXTERNALSYM protoent}
  protoent = record
    p_name: PChar;
    p_aliases: ^Pchar;
    p_proto: Smallint;
  end;
  TProtoEnt = protoent;

コピー(2)〜あんまり情報がないが、わかる範囲で。

p_name: PChar;
official protocol name(プロトコル正式名称)
p_aliases: ^Pchar;
alias list(別名リスト)
p_proto: Smallint;
protocol #(プロトコルNO)

何がなんだか。ただ、THostEntに名前も構造も似ているので、おんなじような関数があるはず。例によって、WinSock.pasより、次のようなものが見つかった。 ただし、TNetEntに関しては、それらしきものが見つからず、いったい何者なのか不明だ。

{$EXTERNALSYM getservbyport}
function getservbyport(port: Integer; proto: PChar): PServEnt; stdcall;
port: Integer;
ポート番号だろうね。
proto: PChar
プロトコルの名称。どうやって書けばいいんだ。
{$EXTERNALSYM getservbyname}
function getservbyname(name, proto: PChar): PServEnt; stdcall;
name: PChar
サービス名だろうね。
proto: PChar
プロトコルの名称。どうやって書けばいいんだ。
{$EXTERNALSYM getprotobynumber}
function getprotobynumber(proto: Integer): PProtoEnt; stdcall;
proto: Integer
プロトコル番号だろうね。
{$EXTERNALSYM getprotobyname}
function getprotobyname(name: PChar): PProtoEnt; stdcall;
name: PChar
プロトコル名だと思う。

getprotobynameTOP

とりあえず、いけそうなところから攻めていこう。最初は、getprotobyname。なぜかというと、FTPってのを知っている。FileTransferProtocolの略のはず。 つまり、

getprotobyname('FTP')
で、何かよさそうなものが返ってくる感じがする。やってみよう。こんな感じ。 基本的には、THostEntでやったのと同じ内容。
function ProtoAliases(ProtoEntry: PProtoEnt): string;
type
    PPChar=^PChar;
var
    ppc: PPChar;
begin
    Result := '';
    ppc := PPChar(ProtoEntry^.p_aliases);
    while assigned(ppc^) do
    begin
        Result := Result + ',' + ppc^;
        inc(ppc);
    end;
    Result := Copy(Result, 2, MaxInt);
end;

procedure TForm1.Button7Click(Sender: TObject);
var
    ProtoEntry: PProtoEnt;
    ProtoName : string;
begin
    ProtoName  := 'FTP';
    ProtoEntry := getprotobyname(PChar(ProtoName));
    ShowMessage(SysErrorMessage(WSAGetLastError));

    ShowMessage('p_name:' + ProtoEntry^.p_name);
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('p_aliases:' + ProtoAliases(ProtoEntry));
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('p_proto:' + IntToStr(ProtoEntry^.p_proto));
    ShowMessage(SysErrorMessage(WSAGetLastError));
end;

...だめだ。「要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。」が出た。getprotobynameの実行直後のWSAGetLastErrorが吐いたエラーだ。

困ったときのWinSock。「Proto」で検索してみると...

const
{ Protocols }
  {$EXTERNALSYM IPPROTO_TCP}
  IPPROTO_TCP    =   6;             { tcp }

他にもあるが、こんな感じの定数宣言が並んでいるところがあった。この中には、「FTP」はない。「TCP」で試してみよう。...来た。 Button7Clickのbeginの次行をいろいろ変更した結果をまとめて見る。 と、その前に、さっき見つけた定数宣言はこんなの。

const
{ Protocols }

  {$EXTERNALSYM IPPROTO_IP}
  IPPROTO_IP     =   0;             { dummy for IP }
  {$EXTERNALSYM IPPROTO_ICMP}
  IPPROTO_ICMP   =   1;             { control message protocol }
  {$EXTERNALSYM IPPROTO_IGMP}
  IPPROTO_IGMP   =   2;             { group management protocol }
  {$EXTERNALSYM IPPROTO_GGP}
  IPPROTO_GGP    =   3;             { gateway^2 (deprecated) }
  {$EXTERNALSYM IPPROTO_TCP}
  IPPROTO_TCP    =   6;             { tcp }
  {$EXTERNALSYM IPPROTO_PUP}
  IPPROTO_PUP    =  12;             { pup }
  {$EXTERNALSYM IPPROTO_UDP}
  IPPROTO_UDP    =  17;             { user datagram protocol }
  {$EXTERNALSYM IPPROTO_IDP}
  IPPROTO_IDP    =  22;             { xns idp }
  {$EXTERNALSYM IPPROTO_ND}
  IPPROTO_ND     =  77;             { UNOFFICIAL net disk proto }

  {$EXTERNALSYM IPPROTO_RAW}
  IPPROTO_RAW    =  255;            { raw IP packet }
  {$EXTERNALSYM IPPROTO_MAX}
  IPPROTO_MAX    =  256;
多分、コレの、IPPROTO_をどけた部分が、プロトコル名称になると読んだ。では行ってみよう。

name: PCharp_namep_aliasesp_proto予想値
IPipIP00
ICMPicmpICMP11
IGMP要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。2
GGPggpGGP33
TCPtcpTCP66
PUPpupPUP1212
UDPudpUDP1717
IDP要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。22
ND要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。77
RAW要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。255
MAX要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。256

うーん。これ以上突っ込みようがない。まあ、過半数は予想通りなので、よしとしよう。

getprotobynumberTOP

しかし、この関数とこの定数宣言に関連があるとわかったことで、getprotobynumberについても突破口が開いた。それに、今度の関数は、定数宣言から勝手に想像した名称ではなく、宣言そのものを使えばよいに決まっているので、さっきエラーが出たやつらの正式名称もわかるはず。行ってみよう。 こんな感じで。

procedure TForm1.Button8Click(Sender: TObject);
var
    ProtoEntry: PProtoEnt;
begin
    ProtoEntry := getprotobynumber(IPPROTO_IP);
    ShowMessage(SysErrorMessage(WSAGetLastError));

    ShowMessage('p_name:' + ProtoEntry^.p_name);
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('p_aliases:' + ProtoAliases(ProtoEntry));
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('p_proto:' + IntToStr(ProtoEntry^.p_proto));
    ShowMessage(SysErrorMessage(WSAGetLastError));
end;

基本的には、さっきとおんなじ。getprotobynumberの引数を、変えた結果がコレ。

proto: Integerp_namep_aliasesp_proto
IPPROTO_IPipIP0
IPPROTO_ICMPicmpICMP1
IPPROTO_IGMP要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
IPPROTO_GGPggpGGP3
IPPROTO_TCPtcpTCP6
IPPROTO_PUPpupPUP12
IPPROTO_UDPudpUDP17
IPPROTO_IDPxns-idpXNS-IDP22
IPPROTO_ND要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
IPPROTO_RAW要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
IPPROTO_MAX要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。

さっきだめで、今回いけたのはxns-idpだけ。残りはやっぱりだめ。 ということは、WinSock1.1ではだめなんだろうか。わからんけどここまでにしておこう。 あ。getprotobyname(PChar('xns-idp'))は試しておこう。...予想通り。

次へいこう。

getservbyport/getservbynameTOP

あと残ってるのは、getservbyportgetservbynameだが、 なんとなく、さっきとおんなじような定数宣言があると見た。WinSock.pasを検索。

const
{ Port/socket numbers: network standard functions}
  {$EXTERNALSYM IPPORT_ECHO}
  IPPORT_ECHO    =   7;
  {$EXTERNALSYM IPPORT_DISCARD}
  IPPORT_DISCARD =   9;
  {$EXTERNALSYM IPPORT_SYSTAT}
  IPPORT_SYSTAT  =   11;
  {$EXTERNALSYM IPPORT_DAYTIME}
  IPPORT_DAYTIME =   13;
  {$EXTERNALSYM IPPORT_NETSTAT}
  IPPORT_NETSTAT =   15;
  {$EXTERNALSYM IPPORT_FTP}
  IPPORT_FTP     =   21;
  {$EXTERNALSYM IPPORT_TELNET}
  IPPORT_TELNET  =   23;
  {$EXTERNALSYM IPPORT_SMTP}
  IPPORT_SMTP    =   25;
  {$EXTERNALSYM IPPORT_TIMESERVER}
  IPPORT_TIMESERVER  =  37;
  {$EXTERNALSYM IPPORT_NAMESERVER}
  IPPORT_NAMESERVER  =  42;
  {$EXTERNALSYM IPPORT_WHOIS}
  IPPORT_WHOIS       =  43;
  {$EXTERNALSYM IPPORT_MTP}
  IPPORT_MTP         =  57;
const
{ Port/socket numbers: host specific functions }
  {$EXTERNALSYM IPPORT_TFTP}
  IPPORT_TFTP        =  69;
  {$EXTERNALSYM IPPORT_RJE}
  IPPORT_RJE         =  77;
  {$EXTERNALSYM IPPORT_FINGER}
  IPPORT_FINGER      =  79;
  {$EXTERNALSYM IPPORT_TTYLINK}
  IPPORT_TTYLINK     =  87;
  {$EXTERNALSYM IPPORT_SUPDUP}
  IPPORT_SUPDUP      =  95;
const
{ UNIX TCP sockets }
  {$EXTERNALSYM IPPORT_EXECSERVER}
  IPPORT_EXECSERVER  =  512;
  {$EXTERNALSYM IPPORT_LOGINSERVER}
  IPPORT_LOGINSERVER =  513;
  {$EXTERNALSYM IPPORT_CMDSERVER}
  IPPORT_CMDSERVER   =  514;
  {$EXTERNALSYM IPPORT_EFSSERVER}
  IPPORT_EFSSERVER   =  520;
const
{ UNIX UDP sockets }
  {$EXTERNALSYM IPPORT_BIFFUDP}
  IPPORT_BIFFUDP     =  512;
  {$EXTERNALSYM IPPORT_WHOSERVER}
  IPPORT_WHOSERVER   =  513;
  {$EXTERNALSYM IPPORT_ROUTESERVER}
  IPPORT_ROUTESERVER =  520;
const
{ Ports < IPPORT_RESERVED are reserved for
  privileged processes (e.g. root). }
  {$EXTERNALSYM IPPORT_RESERVED}
  IPPORT_RESERVED    =  1024;

あほみたいにあった。IPPORT_で始まる定数宣言。コレを使っていってみよう。とりあえず。getservbyportから。と、あっさり思ったが、いきなり壁だ。 portは、間違いなく、上のIPPORT_**だろう。 protoのほうは、その前にやったIPPROTO_**の**の部分だろう。 って、いったいどれだけの組み合わせがあんねん。

ただ、1024かその辺までは、Well Known Portとして予約済みです。 みたいな記述を読んだことがある。それでやってみよう。こんな感じで。 の前に、portprotoの組み合わせをまとめておこう。

いかん。違う。ポートとサービスが大体決められてるだけで、ポートとプロトコルはまた別の話ちゃうの?うーん。全パターン行ってみようか。 ただ、さっきプロトコル名称が最後まで取れなかったのは無視で。

というわけで組み合わせ表はコレ。

port: Integerproto: PChar
IPPORT_ECHO(7)ipicmpggptcppupudpxns-idp
IPPORT_DISCARD(9)ipicmpggptcppupudpxns-idp
IPPORT_SYSTAT(11)ipicmpggptcppupudpxns-idp
IPPORT_DAYTIME(13)ipicmpggptcppupudpxns-idp
IPPORT_NETSTAT(15)ipicmpggptcppupudpxns-idp
IPPORT_FTP(21)ipicmpggptcppupudpxns-idp
IPPORT_TELNET(23)ipicmpggptcppupudpxns-idp
IPPORT_SMTP(25)ipicmpggptcppupudpxns-idp
IPPORT_TIMESERVER(37)ipicmpggptcppupudpxns-idp
IPPORT_NAMESERVER(42)ipicmpggptcppupudpxns-idp
IPPORT_WHOIS(43)ipicmpggptcppupudpxns-idp
IPPORT_MTP(57)ipicmpggptcppupudpxns-idp
IPPORT_TFTP(69)ipicmpggptcppupudpxns-idp
IPPORT_RJE(77)ipicmpggptcppupudpxns-idp
IPPORT_FINGER(79)ipicmpggptcppupudpxns-idp
IPPORT_TTYLINK(87)ipicmpggptcppupudpxns-idp
IPPORT_SUPDUP(95)ipicmpggptcppupudpxns-idp
IPPORT_EXECSERVER(512)ipicmpggptcppupudpxns-idp
IPPORT_LOGINSERVER(513)ipicmpggptcppupudpxns-idp
IPPORT_CMDSERVER(514)ipicmpggptcppupudpxns-idp
IPPORT_EFSSERVER(520)ipicmpggptcppupudpxns-idp
IPPORT_BIFFUDP(512)ipicmpggptcppupudpxns-idp
IPPORT_WHOSERVER(513)ipicmpggptcppupudpxns-idp
IPPORT_ROUTESERVER(520)ipicmpggptcppupudpxns-idp
IPPORT_RESERVED(1024)ipicmpggptcppupudpxns-idp

量が見えたほうがやる気になるかと思ったら、余計やる気がなくなってしまった。 まあ、適当に。こんな感じで。

procedure TForm1.Button9Click(Sender: TObject);
var
    ServEntry: PServEnt;
    ProtoName: string;
begin
    ProtoName := 'ip';
//    ProtoName := RadioGroup1.Items.Names[RadioGroup1.ItemIndex];
    ServEntry := getservbyport(IPPORT_ECHO, PChar(ProtoName));
    ShowMessage(SysErrorMessage(WSAGetLastError));
    if ServEntry = nil then Exit;

    ShowMessage('s_name:' + ServEntry^.s_name);
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('s_aliases:' + ServAliases(ServEntry));
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('s_port:' + IntToStr(ServEntry^.s_port));
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('s_proto:' + ServEntry^.s_proto);
    ShowMessage(SysErrorMessage(WSAGetLastError));
end;

get***は、失敗すると、nilが返るらしいので、判定行をやっと追加してみた。コレを使って、全組み合わせを試してみよう。

表の最初から10個ほど試したけど、全部「要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。」 今思えば、このコンピュータ、サービスなんてなーんも開始してないんじゃないのか?ひょっとして。意味ないやん。

となると、多分getservbynameも同じだ。きっと。 しかも、さっきの結果を使って、サービスの正式名称を取得しようと思ってたのに、だめになったおかげで、名称に何を書けばよいのやら。多分、(今度こそ)「FTP」とかなんだろうけど、どうせサービス動いてないし確かめようもねーよ。

ま、やるとしたらこんな感じか。の前に、組み合わせ表だ。さっきの役立たずの表にほぼ同じ。

name: PCharproto: PChar
ECHO(7)ipicmpggptcppupudpxns-idp
DISCARD(9)ipicmpggptcppupudpxns-idp
SYSTAT(11)ipicmpggptcppupudpxns-idp
DAYTIME(13)ipicmpggptcppupudpxns-idp
NETSTAT(15)ipicmpggptcppupudpxns-idp
FTP(21)ipicmpggptcppupudpxns-idp
TELNET(23)ipicmpggptcppupudpxns-idp
SMTP(25)ipicmpggptcppupudpxns-idp
TIMESERVER(37)ipicmpggptcppupudpxns-idp
NAMESERVER(42)ipicmpggptcppupudpxns-idp
WHOIS(43)ipicmpggptcppupudpxns-idp
MTP(57)ipicmpggptcppupudpxns-idp
TFTP(69)ipicmpggptcppupudpxns-idp
RJE(77)ipicmpggptcppupudpxns-idp
FINGER(79)ipicmpggptcppupudpxns-idp
TTYLINK(87)ipicmpggptcppupudpxns-idp
SUPDUP(95)ipicmpggptcppupudpxns-idp
EXECSERVER(512)ipicmpggptcppupudpxns-idp
LOGINSERVER(513)ipicmpggptcppupudpxns-idp
CMDSERVER(514)ipicmpggptcppupudpxns-idp
EFSSERVER(520)ipicmpggptcppupudpxns-idp
BIFFUDP(512)ipicmpggptcppupudpxns-idp
WHOSERVER(513)ipicmpggptcppupudpxns-idp
ROUTESERVER(520)ipicmpggptcppupudpxns-idp
RESERVED(1024)ipicmpggptcppupudpxns-idp

で、こんな感じのコードで。

procedure TForm1.Button10Click(Sender: TObject);
var
    ServEntry: PServEnt;
    ProtoName: string;
begin
    ProtoName := 'ip';
//    ProtoName := RadioGroup1.Items.Names[RadioGroup1.ItemIndex];
    ServEntry := getservbyname(PChar('TELNET'), PChar(ProtoName));
    ShowMessage(SysErrorMessage(WSAGetLastError));

    if ServEntry = nil then Exit;

    ShowMessage('s_name:' + ServEntry^.s_name);
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('s_aliases:' + ServAliases(ServEntry));
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('s_port:' + IntToStr(ServEntry^.s_port));
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage('s_proto:' + ServEntry^.s_proto);
    ShowMessage(SysErrorMessage(WSAGetLastError));
end;

...なに?。かえってきたで。答えが。もうコレで終わりにしようと思ってたのに。

しかし、あれだね。定数の宣言が、IPPORT_**にもかかわらず、全パターン試そうっても抜けとるな。proto=ip限定じゃないのか?

いやいや。それも含めて勉強会だ。というわけで、エラーにならなかった結果を挙げる。いやいや。さっきのprotoを変えても同じだ。組み合わせる必要なし。

上の2つが、両方ともおんなじ答えを返すということ。 それから、結構戻ってきたので、エラーありも含めて全部書こう。

name: PChars_names_aliasess_ports_proto
ECHOecho''(空欄)1792tcp
DISCARDdiscardsink,null2304tcp
SYSTATsystatusers2816tcp
DAYTIMEdaytime''(空欄)3328tcp
NETSTAT要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
FTPftp''(空欄)5376tcp
TELNETtelnet''(空欄)5888tcp
SMTPsmtpmail6400tcp
TIMESERVERtimedtimeserver3330udp
NAMESERVERnameservername10752tcp
WHOISnicnamewhois11008tcp
MTP要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
TFTPtftp''(空欄)17664udp
RJE要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
FINGERfinger''(空欄)20224tcp
TTYLINK要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
SUPDUP要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
EXECSERVER要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
LOGINSERVER要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
CMDSERVER要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
EFSSERVER要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
BIFFUDP要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
WHOSERVER要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
ROUTESERVER要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。
RESERVED要求した名前は有効で、データベースにありますが、解決された正しい関連データがありません。

結局半分近くエラーだった。全部書かんでよかった。 しかし、なんと言うか、ひょっとして知らんところで動いてるのか?たとえば、TELNET。そんなことないと思うけど。どうなんだろう。

しかし、こういう組み合わせ対決するときは、もっと考えて楽なようにテストコード作るべきだ。コーディングが楽なのは、あとがしんどくて仕方がない。しかし、進まんな。

今日はここまで。

EOFTOP