Skip to content

Commit 19c614d

Browse files
authored
netcat: add -N/-q, cleanup logging (#204)
Add functionality -N/-q to shutdown connection after stdin is closed, analogous to the corresponding flags in BSD netcat. Cleanup logging; use only one logger library, write logs to stderr (not stdout). Simplify the different log levels, keep only debug or error messages, and correspondingly only have a -v flag for for debug logs. Integrate the netcat/modes/ subpackage into the main netcat package to simplify the structure, and to share helper functions (for logging).
1 parent 95aa0b8 commit 19c614d

File tree

5 files changed

+167
-116
lines changed

5 files changed

+167
-116
lines changed

netcat/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# scion-netcat
2-
A SCION port of the netcat process.
2+
A SCION port of the netcat utility.
33

44

55
## Usage

netcat/main.go

Lines changed: 97 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,12 @@ import (
1818
"flag"
1919
"fmt"
2020
"io"
21-
golog "log"
21+
"log"
2222
"os"
2323
"os/exec"
2424
"strconv"
2525
"sync"
26-
27-
"github.com/netsec-ethz/scion-apps/netcat/modes"
28-
scionlog "github.com/scionproto/scion/go/lib/log"
29-
30-
log "github.com/inconshreveable/log15"
26+
"time"
3127
)
3228

3329
var (
@@ -36,13 +32,14 @@ var (
3632

3733
udpMode bool
3834

39-
repeatAfter bool
40-
repeatDuring bool
35+
repeatAfter bool
36+
repeatDuring bool
37+
shutdownAfterEOF bool
38+
shutdownAfterEOFTimeout time.Duration
4139

4240
commandString string
4341

44-
verboseMode bool
45-
veryVerboseMode bool
42+
verboseMode bool
4643
)
4744

4845
func printUsage() {
@@ -58,74 +55,72 @@ func printUsage() {
5855
fmt.Println(" -l: Listen mode")
5956
fmt.Println(" -k: After the connection ended, accept new connections. Requires -l flag. If -u flag is present, requires -c flag. Incompatible with -K flag")
6057
fmt.Println(" -K: After the connection has been established, accept new connections. Requires -l and -c flags. Incompatible with -k flag")
58+
fmt.Println(" -N: shutdown the network socket after EOF on the input.")
59+
fmt.Println(" -q: after EOF on stdin, wait the specified duration and then quit. Implies -N.")
6160
fmt.Println(" -c: Instead of piping the connection to stdin/stdout, run the given command using /bin/sh")
6261
fmt.Println(" -u: UDP mode")
6362
fmt.Println(" -b: Send or expect an extra (throw-away) byte before the actual data")
6463
fmt.Println(" -v: Enable verbose mode")
65-
fmt.Println(" -vv: Enable very verbose mode")
6664
}
6765

6866
func main() {
69-
7067
flag.Usage = printUsage
7168
flag.BoolVar(&extraByte, "b", false, "Expect extra byte")
7269
flag.BoolVar(&listen, "l", false, "Listen mode")
7370
flag.BoolVar(&udpMode, "u", false, "UDP mode")
7471
flag.BoolVar(&repeatAfter, "k", false, "Accept new connections after connection end")
7572
flag.BoolVar(&repeatDuring, "K", false, "Accept multiple connections concurrently")
73+
flag.BoolVar(&shutdownAfterEOF, "N", false, "Shutdown the network socket after EOF on the input.")
74+
flag.DurationVar(&shutdownAfterEOFTimeout, "q", 0, "After EOF on stdin, wait the specified number of seconds and then quit. Implies -N.")
7675
flag.StringVar(&commandString, "c", "", "Command")
7776
flag.BoolVar(&verboseMode, "v", false, "Verbose mode")
78-
flag.BoolVar(&veryVerboseMode, "vv", false, "Very verbose mode")
7977
flag.Parse()
8078

81-
if veryVerboseMode {
82-
_ = scionlog.Setup(scionlog.Config{Console: scionlog.ConsoleConfig{Level: "debug"}})
83-
} else if verboseMode {
84-
_ = scionlog.Setup(scionlog.Config{Console: scionlog.ConsoleConfig{Level: "info"}})
85-
} else {
86-
_ = scionlog.Setup(scionlog.Config{Console: scionlog.ConsoleConfig{Level: "error"}})
87-
}
88-
8979
tail := flag.Args()
9080
if len(tail) != 1 {
9181
expected := "host-address:port"
9282
if listen {
9383
expected = "port"
9484
}
95-
golog.Panicf("Incorrect number of arguments! Expected %s, got: %v", expected, tail)
85+
log.Fatalf("Incorrect number of arguments! Expected %s, got: %v", expected, tail)
9686
}
9787

9888
if repeatAfter && repeatDuring {
99-
golog.Panicf("-k and -K flags are exclusive!")
89+
log.Fatalf("-k and -K flags are exclusive!")
10090
}
10191
if repeatAfter && !listen {
102-
golog.Panicf("-k flag requires -l flag!")
92+
log.Fatalf("-k flag requires -l flag!")
10393
}
10494
if repeatDuring && !listen {
105-
golog.Panicf("-K flag requires -l flag!")
95+
log.Fatalf("-K flag requires -l flag!")
10696
}
10797
if repeatAfter && udpMode && commandString == "" {
108-
golog.Panicf("-k flag in UDP mode requires -c flag!")
98+
log.Fatalf("-k flag in UDP mode requires -c flag!")
10999
}
110100
if repeatDuring && commandString == "" {
111-
golog.Panicf("-K flag requires -c flag!")
101+
log.Fatalf("-K flag requires -c flag!")
112102
}
113103

114-
log.Info("Launching netcat")
115-
116104
var conns chan io.ReadWriteCloser
117105

118106
if listen {
119107
port, err := strconv.Atoi(tail[0])
120108
if err != nil {
121109
printUsage()
122-
golog.Panicf("Invalid port %s: %v", tail[0], err)
110+
log.Fatalf("Invalid port %s: %v", tail[0], err)
111+
}
112+
conns, err = doListen(uint16(port))
113+
if err != nil {
114+
log.Fatal(err)
123115
}
124-
conns = doListen(uint16(port))
125116
} else {
126117
remoteAddr := tail[0]
118+
conn, err := doDial(remoteAddr)
119+
if err != nil {
120+
log.Fatal(err)
121+
}
127122
conns = make(chan io.ReadWriteCloser, 1)
128-
conns <- doDial(remoteAddr)
123+
conns <- conn
129124
}
130125

131126
if repeatAfter {
@@ -137,7 +132,7 @@ func main() {
137132
pipeConn(conn)
138133
<-isAvailable
139134
default:
140-
log.Info("Closing new connection as there's already a connection", "conn", conn)
135+
logDebug("Closing new connection as there's already a connection", "conn", conn)
141136
conn.Close()
142137
}
143138
}(conn)
@@ -159,58 +154,56 @@ func main() {
159154

160155
// Note that we don't close the connection currently
161156

162-
log.Debug("Done, closing now")
157+
logDebug("Done, closing now")
163158
}
164159

165160
func pipeConn(conn io.ReadWriteCloser) {
166161
closeThis := func() {
167-
log.Debug("Closing connection...", "conn", conn)
162+
logDebug("Closing connection...", "conn", conn)
168163
err := conn.Close()
169164
if err != nil {
170-
log.Crit("Error closing connection", "conn", conn)
165+
logError("Error closing connection", "conn", conn)
171166
}
172167
}
173168

174-
log.Info("Piping new connection", "conn", conn)
169+
logDebug("Piping new connection", "conn", conn)
175170

171+
var readerDesc, writerDesc string
176172
var reader io.Reader
177-
var writer io.Writer
173+
var writer io.WriteCloser
178174
if commandString == "" {
179175
reader = os.Stdin
176+
readerDesc = "stdin"
180177
writer = os.Stdout
178+
writerDesc = "stdout"
181179
} else {
182180
cmd := exec.Command("/bin/sh", "-c", commandString)
183-
log.Debug("Created cmd object", "cmd", cmd, "commandString", commandString)
181+
logDebug("Created cmd object", "cmd", cmd, "commandString", commandString)
184182
var err error
185183
writer, err = cmd.StdinPipe()
186184
if err != nil {
187-
log.Crit("Error getting command's stdin pipe", "cmd", cmd, "err", err)
185+
logError("Error getting command's stdin pipe", "cmd", cmd, "err", err)
188186
return
189187
}
188+
writerDesc = "process input"
190189
reader, err = cmd.StdoutPipe()
191190
if err != nil {
192-
log.Crit("Error getting command's stdout pipe", "cmd", cmd, "err", err)
193-
return
194-
}
195-
errreader, err := cmd.StderrPipe()
196-
if err != nil {
197-
log.Crit("Error getting command's stderr pipe", "cmd", cmd, "err", err)
191+
logError("Error getting command's stdout pipe", "cmd", cmd, "err", err)
198192
return
199193
}
200-
go func() {
201-
io.Copy(os.Stderr, errreader) //nolint:errcheck // XXX(matzf): should an error here be handled?
202-
}()
194+
readerDesc = "process output"
195+
cmd.Stderr = os.Stderr
203196
err = cmd.Start()
204197
if err != nil {
205-
log.Crit("Error starting command", "cmd", cmd, "err", err)
198+
logError("Error starting command", "cmd", cmd, "err", err)
206199
return
207200
}
208201
prevCloseThis := closeThis
209202
closeThis = func() {
210-
log.Debug("Waiting for command to end...")
203+
logDebug("Waiting for command to end...")
211204
err := cmd.Wait()
212205
if err != nil {
213-
log.Warn("Command exited with error", "err", err)
206+
logError("Command exited with error", "err", err)
214207
}
215208
prevCloseThis()
216209
}
@@ -221,67 +214,100 @@ func pipeConn(conn io.ReadWriteCloser) {
221214

222215
go func() {
223216
_, err := io.Copy(conn, reader)
224-
log.Debug("Done copying from (std/process) input", "conn", conn, "error", err)
217+
logDebug(fmt.Sprintf("Done copying from %s", readerDesc), "conn", conn, "error", err)
218+
if shutdownAfterEOF || shutdownAfterEOFTimeout > 0 {
219+
time.Sleep(shutdownAfterEOFTimeout)
220+
if cw, ok := conn.(interface{ CloseWrite() error }); ok { // quic mode, close stream for writing
221+
_ = cw.CloseWrite()
222+
} else {
223+
_ = conn.Close()
224+
}
225+
}
225226
pipesWait.Done()
226227
}()
227228
_, err := io.Copy(writer, conn)
228-
log.Debug("Done copying to (std/process) output", "conn", conn, "error", err)
229+
logDebug(fmt.Sprintf("Done copying to %s", writerDesc), "conn", conn, "error", err)
229230
pipesWait.Done()
230231

231232
pipesWait.Wait()
232233
closeThis()
233234

234-
log.Info("Connection closed", "conn", conn)
235+
logDebug("Connection closed", "conn", conn)
235236
}
236237

237-
func doDial(remoteAddr string) io.ReadWriteCloser {
238+
func doDial(remoteAddr string) (io.ReadWriteCloser, error) {
238239
var conn io.ReadWriteCloser
240+
var err error
239241
if udpMode {
240-
conn = modes.DoDialUDP(remoteAddr)
242+
conn, err = DoDialUDP(remoteAddr)
241243
} else {
242-
conn = modes.DoDialQUIC(remoteAddr)
244+
conn, err = DoDialQUIC(remoteAddr)
243245
}
246+
if err != nil {
247+
return nil, err
248+
}
249+
logDebug("Connected")
244250

245251
if extraByte {
246252
_, err := conn.Write([]byte{88}) // ascii('X')
247253
if err != nil {
248-
golog.Panicf("Error writing extra byte: %v", err)
254+
return nil, fmt.Errorf("error writing extra byte: %w", err)
249255
}
250-
251-
log.Debug("Sent extra byte!")
256+
logDebug("Sent extra byte!")
252257
}
253258

254-
return conn
259+
return conn, nil
255260
}
256261

257-
func doListen(port uint16) chan io.ReadWriteCloser {
262+
func doListen(port uint16) (chan io.ReadWriteCloser, error) {
258263
var conns chan io.ReadWriteCloser
264+
var err error
259265
if udpMode {
260-
conns = modes.DoListenUDP(port)
266+
conns, err = DoListenUDP(port)
261267
} else {
262-
conns = modes.DoListenQUIC(port)
268+
conns, err = DoListenQUIC(port)
269+
}
270+
if err != nil {
271+
return nil, err
263272
}
264273

265-
var nconns chan io.ReadWriteCloser
266274
if extraByte {
267-
nconns = make(chan io.ReadWriteCloser, 16)
275+
nconns := make(chan io.ReadWriteCloser, 16)
268276
go func() {
269277
for conn := range conns {
270278
buf := make([]byte, 1)
271279
_, err := io.ReadAtLeast(conn, buf, 1)
272280
if err != nil {
273-
log.Crit("Failed to read extra byte!", "err", err, "conn", conn)
281+
logError("Failed to read extra byte!", "err", err, "conn", conn)
274282
continue
275283
}
276284

277-
log.Debug("Received extra byte", "connection", conn, "extraByte", buf)
285+
logDebug("Received extra byte", "connection", conn, "extraByte", buf)
278286

279287
nconns <- conn
280288
}
281289
}()
290+
return nconns, nil
282291
} else {
283-
nconns = conns
292+
return conns, nil
284293
}
294+
}
295+
296+
func logDebug(msg string, ctx ...interface{}) {
297+
if !verboseMode {
298+
return
299+
}
300+
logWithCtx("DEBUG: ", msg, ctx...)
301+
}
285302

286-
return nconns
303+
func logError(msg string, ctx ...interface{}) {
304+
logWithCtx("ERROR: ", msg, ctx...)
305+
}
306+
307+
func logWithCtx(prefix, msg string, ctx ...interface{}) {
308+
line := prefix + msg
309+
for i := 0; i < len(ctx); i += 2 {
310+
line += fmt.Sprintf(" %s=%v", ctx[i], ctx[i+1])
311+
}
312+
log.Println(line)
287313
}

netcat/netcat_integration_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestIntegrationScionNetcat(t *testing.T) {
4141
}
4242
// Start a scion-netcat server socket and query it with a scion-netcat client
4343
// Common arguments
44-
cmnArgs := []string{"-vv"}
44+
cmnArgs := []string{"-v"}
4545
// Server
4646
serverPort := "1234"
4747
serverArgs := []string{"-l", serverPort}
@@ -84,7 +84,7 @@ func TestIntegrationScionNetcat(t *testing.T) {
8484
integration.RegExp(fmt.Sprintf("^%s$", testMessage)),
8585
nil,
8686
nil,
87-
integration.NoPanic(),
87+
nil, // integration.NoPanic() // XXX: this requires specific log output
8888
},
8989
}
9090

@@ -109,7 +109,7 @@ func TestIntegrationScionNetcat(t *testing.T) {
109109
func TestIntegrationScionNetcatUDP(t *testing.T) {
110110
// UDP tests
111111
// Common arguments
112-
cmnArgs := []string{"-vv", "-u"}
112+
cmnArgs := []string{"-v", "-u"}
113113

114114
// Server
115115
serverPort := "1234"
@@ -143,8 +143,8 @@ func TestIntegrationScionNetcatUDP(t *testing.T) {
143143
append(cmnArgs, integration.DstAddrPattern+":"+serverPort),
144144
nil,
145145
nil,
146-
integration.RegExp("^.*Connected.*$"),
147146
nil,
147+
integration.RegExp("^.*Connected.*$"),
148148
},
149149
}
150150

0 commit comments

Comments
 (0)