Jan. 8th, 2024

tcplol

Jan. 8th, 2024 06:04
henry_flower: A melancholy wolf (Default)

ncat -lk -e cmd 127.0.0.1 8000 з попереднього посту, яке може слухати на 127.0.0.1:8000 і форкати cmd (під'єднуючи діскріптора 0 (stdin) cmd до сокету для для читання та діскріптора 1 (stdout) для писання ув нього), не є чимось оригінальним: саме так працював супер-пупер-сервер inetd або Бернштайновський tcpserver.

Steps performed by inetd

(Кожного разу коли хтось посилається на Бернштайна, я згадую його подорожній нарис як його труїли газом ув готелі СПб літом 2000 року.)

З появою т.з. сокет октивації ув systemd, деякі контори (ойбіем) перестали (x)inetd поставляти взагалі: користувачам залишили або переписування файлів конфігурації inetd ув юніти systemd, або плигання на крижину убунту.

Корисний аспекта від цього був у тому, що супер-пупер-сервер inetd хоча міг зміювати юзера у форку, сам запускався під рута і конфігурацію тримав ув /etc (що вимагало права адміністратора для ігр зі своїми експериментальними сервісами), а systemd запровадив режима --user і міг шукати юніти ув ~/.config/systemd/user/.

Наприклад, складний мережевий сервіс, який питає ім'я клаенту і вітається з ним:

$ cat hello.sh
#!/bin/sh
uname 1>&2
while [ -z "$name" ]; do
    printf "What is your name? "
    read -r name || exit 1
done
echo "Hello, $name!"

можна писати з 0м рядків коду які оперують сокетами, а замість того додати 2 юніт файла:

$ cat ~/.config/systemd/user/hello.socket
[Unit]
Description=hello.sh socket

[Socket]
ListenStream=12345
Accept=yes

[Install]
WantedBy=sockets.target

та

$ cat ~/.config/systemd/user/hello@.service
[Unit]
Description=hello.sh

[Service]
ExecStart=-/home/alex/lib/software/example/ruby/fork/hello.sh
StandardInput=socket

Після чого:

$ systemctl --user daemon-reload
$ systemctl --user start hello.socket
$ ncat 127.0.0.1 12345
Linux
What is your name? Dude
Hello, Dude!

1й рядок з "Linux" з'явився тому що systemd по-замовчуванню під'єднує діскриптора 2 до сокету також, що я вважаю неввічливим.

Замість ncat тут краще перевіряти socat'ом, т.я. ncat не закриває з'єднання після того як systemd закрив сокета і продовжує висіти ув CLOSE_WAIT:

$ netstat -4an | grep 12345
tcp    0  0 127.0.0.1:34552     127.0.0.1:12345     CLOSE_WAIT

(Так само дивно поводиться nc ув busybox; що робить nc з bsd перевіряти лінь.)

Кому подобається писанина з ini-файлами є ув захваті. Всім іншим залишається журитися та писати форкові або тредові сервери вручну.

Емулювати ncat -lk можна також на Рубі, де аналога вдається втиснути ув 37 рядків коду. Той самий діалог

$ socat - TCP4:127.0.0.1:8000
What is your name? Dude
Hello, Dude!

Зі сторони сервера виглядає так:

$ ./tcplol -v -p 8000 ./hello.sh
Client 127.0.0.1:36512
Linux
Client 127.0.0.1:36512, pid 42528: disconnect

stderr під'єднується до сокету тільки з опцією -2.

$ cat tcplol
#!/usr/bin/env ruby

require 'optparse'
require 'socket'

usage = 'Usage: tcplol [-2v] [-h 127.0.0.1] -p 1234 program [args...]'
opt = ARGV.getopts "h:p:2v"
abort usage unless opt['p'] && ARGV[0]
$VERBOSE = nil unless opt['v']

at_exit do
  Thread.list.filter {|t| t != Thread.current}.each do |t|
    warn "\n" + "Waiting for " + t[:cid]
    t.join
  end
end

server = TCPServer.new opt['h'] || '127.0.0.1', opt['p']
loop do
  begin
    client = server.accept
    cid = client.remote_address.ip_unpack.join ':'
  rescue
    warn "Client error: #{$!}"
    next
  end

  warn "Client #{cid}"
  pid = fork do
    $stdin.reopen client
    $stdout.reopen client
    $stderr.reopen client if opt['2']
    client.close
    exec(*ARGV)
  end
  client.close
  cid += ", pid #{pid}"
  Thread.new(cid, pid) do
    Thread.current[:cid] = cid
    Process.wait pid
    warn "Client #{cid}: disconnect"
  end
end

Щоб не залишати зімбі нам доводиться створювати окремого треда для кожного форку. at_exit потрібен лише коли tcplol спілкується з клаентами, але отримує щось на кшталт SIGINT.

Єдина цікава деталь тут є ув чеканні на іксепшона поряд з server.accept. Не знаю чому, але я вважав що лайнаксне ядро деталі TCP handshake ховає та унеможливлює його ламання з сокетного інтерхфейсу (але не з raw sockets, звичайно). Еге. Як прибрати begin…rescue, tcplol гепнеться на спробі дізнатися адресу хоста клаента-злодія, якщо ув tcplol штирнути nmap'ом:

$ nmap -sT -p 8000 127.0.0.1

Page Summary

May 2025

M T W T F S S
   12 34
5678910 11
1213 1415 161718
19202122232425
262728293031 

Expand Cut Tags

No cut tags