Принятая в Go реализация конкурентности через обмен сообщениями между независимыми друг от друга потоками называется CSP (Communicating Sequential Processes). В ее основе — каналы, передающие сообщения от одного потока (горутины) к другому.
Если ты знаком с концепцией системных каналов (pipes), то уже примерно понимаешь, что к чему. Однако, рантайм Go не использует средства операционной системы, у него имеется собственная реализация горутин и каналов.
В саму методику фаззинга мы здесь погружаться не будем: все‑таки основная цель — освоить Go, а потому концентрируемся сейчас на инструментах, предоставляемых языком программирования. Так что реализация будет максимально простая: добавляем к имени хоста слово из словаря, затем выполняем GET запрос по получившемуся адресу и сохраняем код ответа.
Словарь можно взять, например, здесь, но я советую не брать очень большой список, а написать свой буквально из десятка позиций — этого более чем достаточно для начала.
info
Часто модель CSP описывают одним утверждением: «не реализовывай коммуникацию посредством совместного использования памяти; вместо этого совместно используй память посредством коммуникации».
www
Модель CSP разработана и описана Чарльзом Энтони Хоаром — возможно, ты уже слышал его имя в связи с алгоритмом быстрой сортировки (qsort).
Создадим новый модуль:
mkdir dirfuzzer && cd $_go mod init dirfuzzer
touch main.go
Разделяем код на модули
В файл main.go вставим следующий код:
package mainimport ( "bufio" "fmt" "io" "net/http" "os" "strings" "sync" "time")type Result struct { Name string Code int}// produce генерирует задания для обработки,// комбинируя host со значениями, считанными из filename,// и помещает их в канал outCh.func produce(filename string, host string, outCh chan<- string) { file, err := os.Open(filename) if err != nil { fmt.Fprintf(os.Stderr, "opening %s: %v\n", filename, err) return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { s := strings.TrimSpace(scanner.Text()) if s == "" { continue } outCh <- "https://" + host + "/" + s } if err := scanner.Err(); err != nil { fmt.Fprintf(os.Stderr, "reading %s: %v\n", filename, err) }}// worker получает значения из канала inCh, пока он остается открытым, выполняет обработку и помещает результаты в outCh.func worker(c *http.Client, inCh <-chan string, outCh chan<- Result) { for job := range inCh { resp, err := c.Get(job) if err != nil { continue } resp.Body.Close() io.Copy(io.Discard, resp.Body) result := Result{ Name: job, Code: resp.StatusCode, } outCh <- result }}// collect получает значения из канала resultCh, пока он остается открытым, и записывает их в файл filename.func collect(filename string, resultCh <-chan Result) { dstFile, err := os.Create(filename) if err != nil { fmt.Fprintf(os.Stderr, "creating %s: %v\n", filename, err) return } defer dstFile.Close() writer := bufio.NewWriter(dstFile) for r := range resultCh { s := fmt.Sprintf("%s - %d %s\n", r.Name, r.Code, http.StatusText(r.Code)) _, err = writer.WriteString(s) if err != nil { fmt.Fprintf(os.Stderr, "writing to %s: %v\n", filename, err) } } if err = writer.Flush(); err != nil { fmt.Fprintf(os.Stderr, "writing to %s: %v\n", filename, err) }}func main() { // TODO}Как видишь, мы логически распределили фрагменты кода между отдельными изолированными функциями в соответствии с ролями, которые на этот код возложены.
Функция produce( генерирует задания для последующей обработки. Она вычитывает строки из списка и формирует URL, по которым будут осуществляться попытки соединения. Сама обработка лежит вне зоны ответственности этой функции и никак не влияет на ее код. Другие часто используемые имена для такого кода: generate, source.
Функция worker( обрабатывает задания. В данном случае, выполняется GET запрос к серверу и сохранение кода ответа. Как ты видишь, функция «не знает» ничего ни о происхождении заданий, ни о дальнейшей судьбе результатов — это к ней не относится. Другие часто встречающиеся имена для такой роли: process, handle.
Функция collect( вычитывает результаты и сохраняет их в файл. По аналогии с предыдущими она ничего не знает и не должна знать о том, откуда и каким образом эти результаты взялись. У нее есть только одна четко очерченная зона ответственности — собрать и сохранить. Другие часто встречающиеся имена: consume, sink, save.
Подобное разделение воплощает принцип построения программных систем loose couping, high cohesion (один из вариантов перевода: «слабая связанность, сильное сцепление»). Согласно этому принципу, система строится из отдельных компонентов, которые минимально зависят друг от друга (это и есть loose coupling). При этом каждый такой компонент выполняет четко очерченную роль, и код, ответственный за ту или иную функцию, сконцентрирован внутри «своего» компонента, а не размазан по другим (а это — high cohesion).
Такой подход облегчает понимание работы системы, позволяет применять модульное тестирование, улучшает повторное использование кода и дает возможность в будущем наращивать возможности.
Если мы захотим, к примеру, сохранять результаты в другом формате или вообще писать их не в файл, а, скажем, отправлять по сети на другой сервер — нам нужно только заменить компонент collect, не трогая остальные и даже не читая их код. Это общий принцип, он применим не только к отдельным функциям или классам, но и к любым компонентам программной системы в широком понимании этого слова.
info
Разделение большого монолитного куска кода, решающего ту или иную задачу, на отдельные компоненты, называется модуляризацией (modularization). Не знаю, как тебе, а мне это трудно даже произнести, поэтому я предпочитаю родственное понятие — декомпозиция. Хотя, конечно, оно больше про подход к решению задачи, нежели про структурирование реализации этого решения.
Каналы
Запускать эти функции будем как отдельные горутины. При этом мы откажемся от «классического» подхода к обмену информацией через совместно используемые объекты, защищенные мьютексами. Вместо этого воспользуемся каналами — специальным инструментом коммуникации между горутинами. Каналы потокобезопасны по определению и не требуют использования мьютексов или иных примитивов синхронизации.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»
