Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support bridge descriptors and consensus #13

Merged
merged 5 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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