The information I wanted to capture is sent to the LogEvent() function. Up until this point all it did was dump information to standard output with fmt.Println then return to the caller.
Here's the new function:
func LogEvent(strFrom string, strMessage string) { var strLogFile string var err error var fileLogFile *os.File strLogFile, _ = os.Getwd() strLogFile = strLogFile + "/port_listen_logs/port_listen.log" if _, err = os.Stat(strLogFile); err == nil { fileLogFile, err = os.OpenFile(strLogFile, os.O_APPEND|os.O_RDWR, 0644) if err != nil { fmt.Println("Log file could not be opened! Error " + err.Error()) os.Exit(1) } } else { fileLogFile, err = os.Create(strLogFile) if err != nil { fmt.Println("Log file could not be created! Error " + err.Error()) os.Exit(1) } } defer fileLogFile.Close() fileLogFile.WriteString(time.Now().String() + " From port " + strFrom + ":: " + strMessage + "\n") }
Some of the code should look familiar; it was used to set up the previous checks for the existence of the logfile directory. I created a string to hold the logfile name and populated it with the current working directory using os.Getwd(), then in the next line I appended "/port_listen_logs/port_listen.log". This ensures the logfile is created in a directory just under the directory we're running the program from.
The directories were already created moments earlier with the SetupEnvironment() call, so I'm taking the (admittedly lazy and incomplete) path of not checking for possible errors as much as I probably should.
The next thing I want to do is check if the logfile exists; if it doesn't exist, create the file. If it does exist, open it in a mode that allows the application to append to the file.
To do this, I use and if/else code blocks. The first checks the existence of the file using os.Stat(), with the returned value ignored (the underscore ignores the value, but we do capture err.)
If err is nil, that means the file exists so I open the file in read/write using the os library's OpenFile function, which takes the files name (strLogFile), the mode (O_APPEND|O_RDWR), and the permissions (0644). If the error code returned is not nil, we're assuming the file doesn't exist. The else{} block opens the file with the os.Create() call, taking the filename as the argument.
If an error is encountered in either of those file-open attempts, the error is dumped to standard output and the program exits using a call to os.Exit with a nonzero exit code.
The file open code blocks are now finished; the next line calls Close coupled with the keyword defer, meaning that the file will be closed just before the running function returns. No matter where it happens, the file will get closed, and I don't have to remember to do it later.
The last bit is the actual writing to the file using WriteString. It writes the current timestamp, reports the port (passed as an argument to LogEvent), and the message (also passed as an argument.) I added a newline and also used a double-colon to distinguish the information coming from the application from the message being sent to the application. Then the file is closed and the function returns!
Here's the source code as it stands at this point:
package main import ( "fmt" "strconv" "os" "strings" "net" "os/exec" "syscall" "time" ) func main() { var i uint16 var strUserInput string var intEnvironmentReady int chanCommLine := make(chan string) intEnvironmentReady = SetupEnvironment() if intEnvironmentReady != 0 { fmt.Println("Something has gone wrong setting up the system environment, error " + strconv.Itoa(intEnvironmentReady)) os.Exit(1) } LogEvent("0", "Application initializing ports to monitor...") for i = 1; i < 1025; i++ { go ListenToPort(i, chanCommLine) } i = 0 for { strChanData := <-chanCommLine if strChanData != "" { i++ } if i == 1024 { break } } for { LogEvent("0", "Application started and monitoring") fmt.Println("Type 'quit' and hit enter to exit the program...") fmt.Scanf("%s", &strUserInput) strUserInput = strings.ToLower(strUserInput) if strUserInput == "quit" { os.Exit(0) } } } func ListenToPort(uint16Port uint16, chanOutGoing chan<- string) { var strPortToListenOn string var strRemoteAddr string var strRemoteAddrSansPort string strPortToListenOn = strconv.FormatInt(int64(uint16Port), 10) netListenFor, err := net.Listen("tcp", ":"+strPortToListenOn) if err != nil { LogEvent(strPortToListenOn, err.Error()) chanOutGoing <- "done" + strconv.FormatInt(int64(uint16Port), 10) return } chanOutGoing <- "done" + strconv.FormatInt(int64(uint16Port), 10) defer netListenFor.Close() for { conn, err := netListenFor.Accept() if err != nil { LogEvent(strPortToListenOn, err.Error()) } strRemoteAddr = conn.RemoteAddr().String() strRemoteAddrSansPort, _, _ = net.SplitHostPort(strRemoteAddr) LogEvent(strPortToListenOn, "Connection attempt made from "+strRemoteAddrSansPort) go GrabInput(conn, strPortToListenOn, strRemoteAddrSansPort) } } func GrabInput(conn net.Conn, strFromListenerNumber string, strClientIP string) { var strMessage string bufIncoming := make([]byte, 1024) for { bytesRead, err := conn.Read(bufIncoming) if err != nil { if err.Error() == "EOF" { LogEvent(strFromListenerNumber, "Client located at "+strClientIP+" has disconnected.") } else { LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, err.Error()) } return } strMessage = string(bufIncoming[0 : bytesRead-1]) LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, strMessage) } } func LogEvent(strFrom string, strMessage string) { var strLogFile string var err error var fileLogFile *os.File strLogFile, _ = os.Getwd() strLogFile = strLogFile + "/port_listen_logs/port_listen.log" if _, err = os.Stat(strLogFile); err == nil { fileLogFile, err = os.OpenFile(strLogFile, os.O_APPEND|os.O_RDWR, 0644) if err != nil { fmt.Println("Log file could not be opened! Error " + err.Error()) os.Exit(1) } } else { fileLogFile, err = os.Create(strLogFile) if err != nil { fmt.Println("Log file could not be created! Error " + err.Error()) os.Exit(1) } } defer fileLogFile.Close() fileLogFile.WriteString(time.Now().String() + " From port " + strFrom + ":: " + strMessage + "\n") } func SetupEnvironment() int { var strSudoUserUid string var strSudoUserGid string var intSudoUserUid int var intSudoUserGid int var strWorkDir string var err error var fiDirectory os.FileInfo var rLimit syscall.Rlimit if os.Getuid() == 0 { strSudoUserUid = os.Getenv("SUDO_UID") strSudoUserGid = os.Getenv("SUDO_GID") } else { fmt.Println("Please use sudo to run application (sudo ./port_listen)") return 4 } strWorkDir, err = os.Getwd() if err != nil { fmt.Println("Error getting working directory: " + err.Error()) } strWorkDir = strWorkDir + "/port_listen_logs" fiDirectory, err = os.Stat(strWorkDir) if err != nil { fmt.Println("Logs directory does not exist. Creating directory " + strWorkDir) err = os.Mkdir(strWorkDir, 0700) if err != nil { fmt.Println("Error creating directory; " + err.Error()) return 5 } intSudoUserUid, _ = strconv.Atoi(strSudoUserUid) intSudoUserGid, _ = strconv.Atoi(strSudoUserGid) err = os.Chown(strWorkDir, intSudoUserUid, intSudoUserGid) if err != nil { fmt.Println("Error changing permissions on directory: " + err.Error()) return 7 } } fiDirectory, err = os.Stat(strWorkDir) if !fiDirectory.IsDir() { fmt.Println(strWorkDir + " is not a directory!") return 6 } command := exec.Command("/bin/launchctl", "limit", "maxfiles", "1000000","1000000") _ , err = command.Output() if err != nil { return 1 } err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) if err != nil { return 3 } rLimit.Cur = 1400 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) if err != nil { return 2 } return 0 }
But we're not done yet! Go includes a simple tool to format your source code using Go formatting standards. To view what changes would be made to the source code I use:
gofmt -d ./port_listen.go
...whose output is shown in Diff format. To make the changes, I run:
gofmt -l -w -s ./port_listen.go
The changes in this case were very simple, but it was easy to have this automated housekeeping and it made the source file more consistent. My final version:
package main import ( "fmt" "net" "os" "os/exec" "strconv" "strings" "syscall" "time" ) func main() { var i uint16 var strUserInput string var intEnvironmentReady int chanCommLine := make(chan string) intEnvironmentReady = SetupEnvironment() if intEnvironmentReady != 0 { fmt.Println("Something has gone wrong setting up the system environment, error " + strconv.Itoa(intEnvironmentReady)) os.Exit(1) } LogEvent("0", "Application initializing ports to monitor...") for i = 1; i < 1025; i++ { go ListenToPort(i, chanCommLine) } i = 0 for { strChanData := <-chanCommLine if strChanData != "" { i++ } if i == 1024 { break } } for { LogEvent("0", "Application started and monitoring") fmt.Println("Type 'quit' and hit enter to exit the program...") fmt.Scanf("%s", &strUserInput) strUserInput = strings.ToLower(strUserInput) if strUserInput == "quit" { os.Exit(0) } } } func ListenToPort(uint16Port uint16, chanOutGoing chan<- string) { var strPortToListenOn string var strRemoteAddr string var strRemoteAddrSansPort string strPortToListenOn = strconv.FormatInt(int64(uint16Port), 10) netListenFor, err := net.Listen("tcp", ":"+strPortToListenOn) if err != nil { LogEvent(strPortToListenOn, err.Error()) chanOutGoing <- "done" + strconv.FormatInt(int64(uint16Port), 10) return } chanOutGoing <- "done" + strconv.FormatInt(int64(uint16Port), 10) defer netListenFor.Close() for { conn, err := netListenFor.Accept() if err != nil { LogEvent(strPortToListenOn, err.Error()) } strRemoteAddr = conn.RemoteAddr().String() strRemoteAddrSansPort, _, _ = net.SplitHostPort(strRemoteAddr) LogEvent(strPortToListenOn, "Connection attempt made from "+strRemoteAddrSansPort) go GrabInput(conn, strPortToListenOn, strRemoteAddrSansPort) } } func GrabInput(conn net.Conn, strFromListenerNumber string, strClientIP string) { var strMessage string bufIncoming := make([]byte, 1024) for { bytesRead, err := conn.Read(bufIncoming) if err != nil { if err.Error() == "EOF" { LogEvent(strFromListenerNumber, "Client located at "+strClientIP+" has disconnected.") } else { LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, err.Error()) } return } strMessage = string(bufIncoming[0 : bytesRead-1]) LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, strMessage) } } func LogEvent(strFrom string, strMessage string) { var strLogFile string var err error var fileLogFile *os.File strLogFile, _ = os.Getwd() strLogFile = strLogFile + "/port_listen_logs/port_listen.log" if _, err = os.Stat(strLogFile); err == nil { fileLogFile, err = os.OpenFile(strLogFile, os.O_APPEND|os.O_RDWR, 0644) if err != nil { fmt.Println("Log file could not be opened! Error " + err.Error()) os.Exit(1) } } else { fileLogFile, err = os.Create(strLogFile) if err != nil { fmt.Println("Log file could not be created! Error " + err.Error()) os.Exit(1) } } defer fileLogFile.Close() fileLogFile.WriteString(time.Now().String() + " From port " + strFrom + ":: " + strMessage + "\n") } func SetupEnvironment() int { var strSudoUserUid string var strSudoUserGid string var intSudoUserUid int var intSudoUserGid int var strWorkDir string var err error var fiDirectory os.FileInfo var rLimit syscall.Rlimit if os.Getuid() == 0 { strSudoUserUid = os.Getenv("SUDO_UID") strSudoUserGid = os.Getenv("SUDO_GID") } else { fmt.Println("Please use sudo to run application (sudo ./port_listen)") return 4 } strWorkDir, err = os.Getwd() if err != nil { fmt.Println("Error getting working directory: " + err.Error()) } strWorkDir = strWorkDir + "/port_listen_logs" fiDirectory, err = os.Stat(strWorkDir) if err != nil { fmt.Println("Logs directory does not exist. Creating directory " + strWorkDir) err = os.Mkdir(strWorkDir, 0700) if err != nil { fmt.Println("Error creating directory; " + err.Error()) return 5 } intSudoUserUid, _ = strconv.Atoi(strSudoUserUid) intSudoUserGid, _ = strconv.Atoi(strSudoUserGid) err = os.Chown(strWorkDir, intSudoUserUid, intSudoUserGid) if err != nil { fmt.Println("Error changing permissions on directory: " + err.Error()) return 7 } } fiDirectory, err = os.Stat(strWorkDir) if !fiDirectory.IsDir() { fmt.Println(strWorkDir + " is not a directory!") return 6 } command := exec.Command("/bin/launchctl", "limit", "maxfiles", "1000000", "1000000") _, err = command.Output() if err != nil { return 1 } err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) if err != nil { return 3 } rLimit.Cur = 1400 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) if err != nil { return 2 } return 0 }
Here ends part 5! But I'm not done yet...
No comments:
Post a Comment