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

Expose Conn.BufReader #54

Closed
wants to merge 1 commit into from
Closed

Expose Conn.BufReader #54

wants to merge 1 commit into from

Conversation

guysv
Copy link
Contributor

@guysv guysv commented Nov 23, 2020

To prevent data loss from internal buffer when unwrapping Conn back to the underlying connection, the private buffered reader was made external. Follows-up #51.

To prevent data loss from internal buffer when unwrapping Conn
back to the underlying connection, the private buffered reader
was made external.
@coveralls
Copy link

Coverage Status

Coverage remained the same at 93.487% when pulling eb536e9 on guysv:master into 9d53ed5 on pires:master.

@coveralls
Copy link

coveralls commented Nov 23, 2020

Coverage Status

Coverage remained the same at 93.487% when pulling eb536e9 on guysv:master into 9d53ed5 on pires:master.

@guysv
Copy link
Contributor Author

guysv commented Nov 23, 2020

Also, off-topic. #51 mentioned a generic Conn.Raw() to get the underlying connection without casting. where did that go?

@emersion
Copy link
Contributor

Hmm, still not clear to me why it's not possible to just read from proxyproto.Conn.

@emersion
Copy link
Contributor

Also, off-topic. #51 mentioned a generic Conn.Raw() to get the underlying connection without casting. where did that go?

Feel free to submit a PR for it. I don't need that yet, so haven't bothered sending a patch.

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

Hmm, still not clear to me why it's not possible to just read from proxyproto.Conn.

Reading larger data than whats buffered in bufio.Reader will cause it to read its underlying reader. To drain just the internal buffer, you need to know exactly how much you can read without triggering a refill. That amount you can read is exposed via Reader.Buffered(), which is what I just exposed.

@emersion
Copy link
Contributor

OK, but why do you need to drain the internal bufio reader?

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

Ah. easy. When you detach the underlying connection from the proxy struct (which is now done using #51), you're going to want that extra data when you repurpose that connection.

In my use case, I'm writing a connection forwarder (open new connection to remote, pipe data between existing (proxy) connection and the new connection). To do that properly, I need to call the underlying TCPConn's CloseRead and CloseWrite

Because those methods are unaware to the fact that some of the data read from the connection is actually buffered somewhere, I'm going to need to:

  1. drain the buffer.
  2. write the drained data to the outgoing connection.
  3. do the usual connection forwarding move (io.Copy(in, out), io.Copy(out, in)).
  4. call TCPConn methods to close the connection properly.

If I don't drain the buffer, some application data will be forgotten in that buffer.

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

By the way, another approach could be setting Conn's net.Conn as the embedded struct. That way you could e.g. pass a Conn to my forwarding routine (I would need to do the casts over there, but that's OK i guess)

@emersion
Copy link
Contributor

Thanks for taking the time to explain everything!

call TCPConn methods to close the connection properly

So in two separate goroutines you want to:

  • io.Copy(in, out) and then CloseRead
  • io.Copy(out, in) and then CloseWrite

to properly support half-opened connections?

To do that properly, I need to call the underlying TCPConn's CloseRead and CloseWrite

Maybe you can call Read on the proxyproto.Conn and CloseRead on the underlying connection? I guess that makes your internal logic more complicated?

@emersion
Copy link
Contributor

By the way, another approach could be setting Conn's net.Conn as the embedded struct. That way you could e.g. pass a Conn to my forwarding routine (I would need to do the casts over there, but that's OK i guess)

Hm, I don't quite understand this one. What is the "embedded struct"?

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

yea! that's why I offered

By the way, another approach could be setting Conn's net.Conn as the embedded struct. That way you could e.g. pass a Conn to my forwarding routine (I would need to do the casts over there, but that's OK i guess)

so I could just cast proxyproto.Conn like I would've done with any net.Conn anyway.

I guess that will do fine for my use-case :)

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

well, in our case we have an embedded interface, but we could pull it off like this

type Conn struct {
-	conn              net.Conn
+	net.Conn
	bufReader         *bufio.Reader
	header            *Header
	once              sync.Once
	ProxyHeaderPolicy Policy
	Validate          Validator
	readErr           error
}

that way proxyproto.Conn "inherits" all of conn's methods (but can override them with implemention of its own for e.g. Read.)

@emersion
Copy link
Contributor

I see, that makes sense.

I believe there are other use-cases that would benefit of exposing the underlying bufio.Reader. For instance, if you want to read a HTTP request from the connection, you may want to use textproto.NewReader. However if you do it like this:

var conn *proxyproto.Conn
tr := textproto.NewReader(bufio.NewReader(conn))

Then the pipeline will contain two buffered readers, one internal to proxyproto.Conn, the other created by the user. This would avoid the overhead:

var conn *proxyproto.Conn
tr := textproto.NewReader(conn.BufReader)

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

well, buffered reader actually checks if the internal reader is a buffered reader too and reuses it if so, but I see where you're going with that.

@emersion
Copy link
Contributor

emersion commented Nov 24, 2020

well, buffered reader actually checks if the internal reader is a buffered reader too and reuses it if so, but I see where you're going with that.

Yeah, but that won't work here, because *proxyproto.Conn (which is an io.Reader, so can be passed to bufio.NewReader) won't type-assert to a *bufio.Reader.

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

Well, i've been thinking about it, and at least for my end goal turning net.Conn into the embedded interface will make my code better than draining the however-long bufio.Reader chain I might be facing. May I add that too to the PR for further discussion?

@guysv
Copy link
Contributor Author

guysv commented Nov 24, 2020

My bad. Excuse me. What I just outlined won't work because i'll need to type-assert to net.Conn before calling my routine, so i'm losing the internal buffer again. I guess I'll have to pass both the buffered reader and the conn to the routine.

@guysv
Copy link
Contributor Author

guysv commented Nov 25, 2020

OK. After doing some offline evaluation, this PR is irrelevant for my needs, as I could just type-assert to proxyproto.Conn, unwrap to tcpconn with proxyproto.TCPConn() and call the needed CloseRead/Write methods.

I would, however prefer to have that Raw() method we talked about earlier. I'll open another PR for that.

Because we did outline some useful use-cases for this PR's change, feel free to merge it (or close it)

@pires
Copy link
Owner

pires commented Nov 27, 2020

hey @guysv! Thank you very much for your contribution, however I'd prefer to not introduce this and have #55 instead 🙇

@pires pires closed this Nov 27, 2020
@emersion
Copy link
Contributor

Have you read #54 (comment)?

@pires pires reopened this Nov 27, 2020
@pires
Copy link
Owner

pires commented Nov 27, 2020

I re-read it now, thanks for calling it out.

@@ -21,7 +21,7 @@ type Listener struct {
// may be speaking the Proxy Protocol. If it is, the RemoteAddr() will
// return the address of the client instead of the proxy address.
type Conn struct {
bufReader *bufio.Reader
BufReader *bufio.Reader
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is exported, please add a descriptive comment with an example of a use-case. Also, please add a test or two.

@pires
Copy link
Owner

pires commented Jan 17, 2021

Gentle ping in case this got lost in doom of github notifications.

@pires
Copy link
Owner

pires commented Jan 17, 2021

Actually, I was reading this for the nth time and I want to go back to my initial assessment: this will be closed until it really becomes needed. Right now, it seems like it would be doing something for future users that may never show up and I'd rather not do it, particularly as exposing the buffer this way may also result in dangerous behavior.

@pires pires closed this Jan 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants