How to get unexported types with reflect?

Issue

Imagine someone has initialized a rpc client with client, _ := rpc.Dial('tcp', 'example.com:port') and provides you only the rpc client instance. For some reason, you’d like to get the address example.com from the client instance. However, as the client type is highly abstracted, you cannot directly get the net.Conn instance which serves a RemoteAddr() method. In this case, the reflect package might be helpful.

Lets start with the following attempt:

func getRemoteAddr(client *rpc.Client) net.Addr {
  codec := reflect.Indirect(rpclient).FieldByName("codec")
  connV := codec.FieldByName("rwc")
  conn := connV.Interface().(net.Conn)
  return conn.RemoteAddr()
}

However, an error jumped out immediately from the second line:

panic: reflect: call of reflect.Value.FieldByName on interface Value

This is reasonable as codec is declared as type ClientCodec which is just an interface with four methods’ signatures. However, from the code, we know that the detailed type of codec might be the unexported struct gobClientCodec. Unfortunately, the Go compiler is not smart enough to trace down that deep and figure out the actual type. Therefore, is there a way with the reflect package to tell the compiler that I’m confident with the type of codec to be the unexported gobClientCodec? If this is not possible, I’ll probably have to calculate the address offset and play with the unsafe pointers, which is the last choice.

P.S. The default Type() method from the reflect method won’t work as codec is declared as ClientCodec and initialized by passing the address of a gobClientCodec struct in the NewClient method as follow:

func NewClient(conn io.ReadWriteCloser) *Client {
    encBuf := bufio.NewWriter(conn)
    client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
    return NewClientWithCodec(client)
}

Also, reflect.TypeOf(rpc.gobClientCodec) won’t work as well as the compiler refuses unexported type usages.

Solution

Use .Elem() to get the value contained in the interface.

Elem returns the value that the interface v contains or that the
pointer v points to. It panics if v’s Kind is not Interface or Ptr. It
returns the zero Value if v is nil.

And for your specific use-case you’ll also need to use the unsafe package to get access to unexported fields. Make sure to read unsafe’s documentation before you use it.

func getRemoteAddr(client *rpc.Client) net.Addr {
  rv := reflect.ValueOf(client)
  rv = rv.Elem()               // deref *rpc.Client
  rv = rv.FieldByName("codec") // get "codec" field from rpc.Client
  rv = rv.Elem()               // get the value that ClientCodec contains
  rv = rv.Elem()               // deref *gobClientCodec
  rv = rv.FieldByName("rwc")   // get "rwc" field from gobClientCodec

  conn := *(*net.Conn)(unsafe.Pointer(rv.UnsafeAddr()))
  return conn.RemoteAddr()
}

https://go.dev/play/p/cHHdwQJ7DE7

Answered By – mkopriva

Answer Checked By – Mary Flores (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.