Skip to content

Commit

Permalink
Merge pull request #13 from meskio/bridges
Browse files Browse the repository at this point in the history
Add support bridge descriptors and consensus
  • Loading branch information
NullHypothesis committed Oct 12, 2021
2 parents fd8f41d + 5a7a70f commit 017a7be
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 18 deletions.
83 changes: 70 additions & 13 deletions consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ var consensusAnnotations = map[Annotation]bool{
Annotation{"network-status-consensus-3", "1", "0"}: true,
}

var bridgeNetworkStatusAnnotations = map[Annotation]bool{
// The file format we currently (try to) support.
Annotation{"bridge-network-status", "1", "2"}: true,
}

var ErrNoStatusEntry = fmt.Errorf("cannot find the end of status entry: \"\\nr \" or \"directory-signature\"")

type GetStatus func() *RouterStatus

type RouterFlags struct {
Expand Down Expand Up @@ -474,7 +481,7 @@ func extractStatusEntry(data []byte, atEOF bool) (advance int, token []byte, err
return start + end, data[start : start+end], bufio.ErrFinalToken
}
if atEOF {
return start, nil, fmt.Errorf("cannot find the end of status entry: \"\\nr \" or \"directory-signature\"")
return len(data), data[start:], ErrNoStatusEntry
}
// Request more data.
return 0, nil, nil
Expand All @@ -483,9 +490,8 @@ func extractStatusEntry(data []byte, atEOF bool) (advance int, token []byte, err
// extractMetainfo extracts meta information of the open consensus document
// (such as its validity times) and writes it to the provided consensus struct.
// It assumes that the type annotation has already been read.
func extractMetaInfo(r io.Reader, c *Consensus) error {
func extractMetaInfo(br *bufio.Reader, c *Consensus) error {

br := bufio.NewReader(r)
c.MetaInfo = make(map[string][]byte)

// Read the initial metadata. We'll later extract information of particular
Expand All @@ -505,11 +511,11 @@ func extractMetaInfo(r io.Reader, c *Consensus) error {
c.MetaInfo[key] = bytes.TrimSpace(split[1])

// Look ahead to check if we've reached the end of the unique keys.
nextKey, err := br.Peek(10)
nextKey, err := br.Peek(11)
if err != nil {
return err
}
if bytes.Equal(nextKey, []byte("dir-source")) {
if bytes.HasPrefix(nextKey, []byte("dir-source")) || bytes.HasPrefix(nextKey, []byte("fingerprint")) {
break
}
}
Expand Down Expand Up @@ -590,8 +596,9 @@ func (filter *ObjectFilter) MatchesRouterStatus(status *RouterStatus) bool {
// correct type. The function returns a network consensus if parsing was
// successful. If there were any errors, an error string is returned. If the
// lazy argument is set to true, parsing of the router statuses is delayed until
// they are accessed.
func parseConsensusUnchecked(r io.Reader, lazy bool) (*Consensus, error) {
// they are accessed. If strict it will only accept valid consensus files, not
// strict is used to parse bridge networkstatus files or when unknown what kind is.
func parseConsensusUnchecked(r io.Reader, lazy bool, strict bool) (*Consensus, error) {

var consensus = NewConsensus()
var statusParser func(string) (Fingerprint, GetStatus, error)
Expand All @@ -602,19 +609,20 @@ func parseConsensusUnchecked(r io.Reader, lazy bool) (*Consensus, error) {
statusParser = ParseRawStatus
}

err := extractMetaInfo(r, consensus)
if err != nil {
br := bufio.NewReader(r)
err := extractMetaInfo(br, consensus)
if strict && err != nil {
return nil, err
}

// We will read raw router statuses from this channel.
queue := make(chan QueueUnit)
go DissectFile(r, extractStatusEntry, queue)
go DissectFile(br, extractStatusEntry, queue)

// Parse incoming router statuses until the channel is closed by the remote
// end.
for unit := range queue {
if unit.Err != nil {
if unit.Err != nil && (strict || !errors.Is(unit.Err, ErrNoStatusEntry)) {
return nil, unit.Err
}

Expand All @@ -634,12 +642,18 @@ func parseConsensusUnchecked(r io.Reader, lazy bool) (*Consensus, error) {
// consensusAnnotations.
func parseConsensus(r io.Reader, lazy bool) (*Consensus, error) {

r, err := readAndCheckAnnotation(r, consensusAnnotations)
strict := false
annotation, r, err := readAnnotation(r)
if err != nil {
return nil, err
}
if _, ok := consensusAnnotations[*annotation]; ok {
strict = true
} else if _, ok := bridgeNetworkStatusAnnotations[*annotation]; !ok {
return nil, fmt.Errorf("unexpected file annotation: %s", annotation)
}

return parseConsensusUnchecked(r, lazy)
return parseConsensusUnchecked(r, lazy, strict)
}

// parseConsensusFile is a wrapper around parseConsensus that opens the named
Expand All @@ -655,6 +669,19 @@ func parseConsensusFile(fileName string, lazy bool) (*Consensus, error) {
return parseConsensus(fd, lazy)
}

// parseConsensusFileUnchecked is a wrapper around parseConsensusUnchecked that opens the named
// file for parsing.
func parseConsensusFileUnchecked(fileName string, lazy bool) (*Consensus, error) {

fd, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer fd.Close()

return parseConsensusUnchecked(fd, lazy, false)
}

// ParseRawConsensus parses a raw consensus (in string format) and
// returns a network consensus if parsing was successful.
func ParseRawConsensus(rawConsensus string, lazy bool) (*Consensus, error) {
Expand Down Expand Up @@ -682,3 +709,33 @@ func ParseConsensusFile(fileName string) (*Consensus, error) {

return parseConsensusFile(fileName, false)
}

// ParseRawUnsafeConsensus parses a raw consensus (in string format) and
// returns a network consensus if parsing was successful.
func ParseRawUnsafeConsensus(rawConsensus string, lazy bool) (*Consensus, error) {
r := strings.NewReader(rawConsensus)

return parseConsensusUnchecked(r, lazy, false)
}

// LazilyParseUnsafeConsensusFile parses the given file without checking the
// annotations and returns a network consensus if parsing was successful. If
// there were any errors, consensus if parsing was successful. If there were
// any errors, an error string is returned. Parsing of the router statuses is
// delayed until they are accessed using the Get method. As a result, this
// function is recommended as long as you won't access more than ~50% of all
// statuses.
func LazilyParseUnsafeConsensusFile(fileName string) (*Consensus, error) {

return parseConsensusFileUnchecked(fileName, true)
}

// ParseUnsafeConsensusFile parses the given file without checking the annotations
// and returns a network consensus if parsing was successful. If there were any
// errors, an error string is returned. In contrast to LazilyParseConsensusFile,
// parsing of router statuses is *not* delayed. As a result, this function is
// recommended as long as you will access most of all statuses.
func ParseUnsafeConsensusFile(fileName string) (*Consensus, error) {

return parseConsensusFileUnchecked(fileName, false)
}
6 changes: 4 additions & 2 deletions consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ func TestExtractMetaInfo(t *testing.T) {
t.Fatal(err)
}

err = extractMetaInfo(r, consensus)
br := bufio.NewReader(r)
err = extractMetaInfo(br, consensus)
if err != nil {
t.Errorf("unable to extractMetaInfo with error: %v", err)
}
Expand Down Expand Up @@ -326,7 +327,8 @@ func TestExtractSharedRandom(t *testing.T) {
t.Error(err)
}

err = extractMetaInfo(r, c)
br := bufio.NewReader(r)
err = extractMetaInfo(br, c)
if err != nil {
t.Error(err)
}
Expand Down
45 changes: 43 additions & 2 deletions descriptor.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Parses files containing server descriptors.
// Parses files containing server or bridge descriptors.

package zoossh

Expand All @@ -20,7 +20,8 @@ const (

var descriptorAnnotations = map[Annotation]bool{
// The file format we currently (try to) support.
Annotation{"server-descriptor", "1", "0"}: true,
Annotation{"server-descriptor", "1", "0"}: true,
Annotation{"bridge-server-descriptor", "1", "2"}: true,
}

type GetDescriptor func() *RouterDescriptor
Expand Down Expand Up @@ -72,6 +73,10 @@ type RouterDescriptor struct {
// The "hidden-service-dir" line.
HiddenServiceDir bool

// The "bridge-distribution-request" line
// it only exist on the bridge-descriptors
BridgeDistributionRequest string

OnionKey string
NTorOnionKey string
SigningKey string
Expand Down Expand Up @@ -327,6 +332,9 @@ func ParseRawDescriptor(rawDescriptor string) (Fingerprint, GetDescriptor, error
case "accept":
descriptor.RawAccept += words[1] + " "
descriptor.RawExitPolicy += words[0] + " " + words[1] + "\n"

case "bridge-distribution-request":
descriptor.BridgeDistributionRequest = words[1]
}
}

Expand Down Expand Up @@ -452,6 +460,19 @@ func parseDescriptorFile(fileName string, lazy bool) (*RouterDescriptors, error)
return parseDescriptor(fd, lazy)
}

// parseDescriptorFileUnchecked is a wrapper around parseDescriptorUnchecked
// that opens the named file for parsing.
func parseDescriptorFileUnchecked(fileName string, lazy bool) (*RouterDescriptors, error) {

fd, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer fd.Close()

return parseDescriptorUnchecked(fd, lazy)
}

// LazilyParseDescriptorFile parses the given file and returns a pointer to
// RouterDescriptors containing the router descriptors. If there were any
// errors, an error string is returned. Note that parsing is done lazily which
Expand All @@ -471,3 +492,23 @@ func ParseDescriptorFile(fileName string) (*RouterDescriptors, error) {

return parseDescriptorFile(fileName, false)
}

// LazilyParseDescriptorFile parses the given file without checking the annotations
// and returns a pointer to RouterDescriptors containing the router descriptors.
// If there were any errors, an error string is returned. Note that parsing is done
// lazily which means that it is delayed until a given router descriptor is accessed.
// That pays off when you know that you will not parse most router descriptors.
func LazilyParseUnsafeDescriptorFile(fileName string) (*RouterDescriptors, error) {

return parseDescriptorFileUnchecked(fileName, true)
}

// ParseDescriptorFile parses the given file without checking the annotations and
// returns a pointer to RouterDescriptors containing the router descriptors.
// If there were any errors, an error string is returned. Note that in contrast to
// LazilyParseDescriptorFile, parsing is *not* delayed. That pays off when you
// know that you will parse most router descriptors.
func ParseUnsafeDescriptorFile(fileName string) (*RouterDescriptors, error) {

return parseDescriptorFileUnchecked(fileName, false)
}
6 changes: 5 additions & 1 deletion generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ func parseWithAnnotation(r io.Reader, annotation *Annotation) (ObjectSet, error)
}

if _, ok := consensusAnnotations[*annotation]; ok {
return parseConsensusUnchecked(r, false)
return parseConsensusUnchecked(r, false, true)
}

if _, ok := bridgeNetworkStatusAnnotations[*annotation]; ok {
return parseConsensusUnchecked(r, false, false)
}

return nil, fmt.Errorf("could not find suitable parser")
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/NullHypothesis/zoossh

go 1.15

0 comments on commit 017a7be

Please sign in to comment.