Version 13 of netcat

Updated 2015-08-13 13:43:28 by dbohdan

http://netcat.sourceforge.net

From the web site:

Netcat is a featured networking utility which reads and writes data across network connections, using the TCP/IP protocol. It is designed to be a reliable "back-end" tool that can be used directly or easily driven by other programs and scripts. At the same time, it is a feature-rich network debugging and exploration tool, since it can create almost any kind of connection you would need and has several interesting built-in capabilities.

Also see: http://en.wikipedia.org/wiki/Netcat

JBR's implementation

JBR 2012-11-06

I was using netcat on ubuntu linux to send some commands to a sever and get the return data into my processing pipe. The server can take some time to compute the annswer (an image) and responde, soI wanted the client (nc) to wait forever for the data after reading the commands from stdin. I used the "-q" flag with -1 as a argument. Now sitting at home on my MacBook, I find that the OS X version of netcat doesn't have this flag. I poked around on the net to see if I could find the Ubuntu versions's source but I haven't found it yet.

I thought that it should be easy enough to do what I want in Tcl. So I wrote this, but it doesn't work either, it won't read any lines from stdin:

#!/usr/bin/env tclkit8.6
#

set host [lindex $argv 0]
set port [lindex $argv 1]

set sock [socket $host $port]
fconfigure $sock -translation binary

fcopy $sock stdout -command exit


while { [gets stdin line] >= 0 } {
    puts $sock $line
}
close $sock w

vwait forever

RLE (2012-11-07): You are not properly entering the event loop here if you are looking to make a "nc" substitute. You need to setup fileevent bindings on both your socket and on stdin, and do your read/copy from the socket or stdin from within the fileevent handlers.

dbohdan's implementation

dbohdan 2015-08-13: The following is my take on netcat in Tcl. It is event-based and mildly scriptable. In theory it should be possible to use from other Tcl application and can handle multiple simultaneous connections as long as they don't use the same channels as each other (i.e., only one connection can have the stdin and the stdout). However, I have not tested that functionality extensively.

#!/usr/bin/env tclsh
# An event-based netcat clone for Tcl 8.5+, version 0.1.0 (2015-08-13).
# Written by dbohdan.
# License: MIT.

namespace eval ::netcat {
    variable bufferSize 4096
    variable connectionCount 0
    variable connections
}

proc ::netcat::copy-when-readable {chanFrom chanTo} {
    puts -nonewline $chanTo [read $chanFrom $::netcat::bufferSize]
    if {[eof $chanFrom]} {
        close $chanFrom
        delete-connection $chanFrom
    }
}

proc ::netcat::delete-connection chan {
    variable connections
    set n [dict get $connections $chan]
    foreach {key _} [dict filter $connections value $n] {
        dict unset connections $key
    }

    set varName [gen-variable-name $n]
    set $varName 1
}

proc ::netcat::gen-variable-name n {
    return [namespace current]::nc${n}Done
}

proc ::netcat::connect {host port {inputChan stdin} {outputChan stdout}
        {prelude {}}} {
    set socket [socket $host $port]
    fconfigure $socket -blocking 0 -translation binary -buffering none
    fconfigure $inputChan -blocking 0 -translation binary -buffering none
    fconfigure $outputChan -blocking 0 -translation binary -buffering none
    fileevent $socket readable \
            [namespace code [list copy-when-readable $socket $outputChan]]
    fileevent $inputChan readable \
            [namespace code [list copy-when-readable $inputChan $socket]]
    apply [list socket $prelude] $socket

    variable connectionCount
    variable connections
    set n $connectionCount
    incr connectionCount
    dict set connections $socket $n
    dict set connections $inputChan $n
    dict set connections $outputChan $n
    set varName [gen-variable-name $n]
    set $varName 0
    return $varName
}

# If this is the main script...
if {[info exists argv0] && ([file tail [info script]] eq [file tail $argv0])} {
    lassign $argv host port preludeScript
    if {($host eq {}) || ![string is integer -strict $port]} {
        set u {}
        append u "usage: $argv0 host port \[preludeScript\]\n"
        append u "\nThe optional parameter preludeScript is a Tcl script "
        append u "that can manipulate the connection socket after the "
        append u "connection is established but before any input is "
        append u "transmitted or any output is received. Example:\n"
        append u "\t$argv0 localhost 7777 'puts \$socket \$::env(REMOTE_ADDR)'"
        puts $u
        exit 1
    } else {
        vwait [::netcat::connect $host $port stdin stdout $preludeScript]
    }
}