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

Feature/get interface ip nonforwardable network adapters handling #38

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

deblasis
Copy link

@deblasis deblasis commented Nov 1, 2021

Hi there, this PR addresses what was discovered in #31 quite a while ago and that I found by looking at the Consul issue hashicorp/consul#5371.

Basically it simply clarifies GetInterfaceIP's behaviour and also adds GetInterfaceIPWithoutInterfaceFlags (not sure about the naming of this one since we are only preventing the exclusion of forwardable=false interfaces).

The interfaces are filtered by the `forwardable` flag as described
in the equivalent sockaddr command.
However, the user might rely on the name/description assuming that all
network interfaces are considered without exclusions.

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
…nterfaceFlags

GetInterfaceIP currently implicitly filters the interfaces excluding
the ones with a non-forwardable address.
This commit clarifies the behaviour and also adds a new command that
can be used to retrieve the IP of an interface even if it's not
forwardable such as: 169.254.0.0/16

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
ifaddr.go Outdated Show resolved Hide resolved
ifaddr_private_test.go Outdated Show resolved Hide resolved
ifaddr.go Outdated Show resolved Hide resolved
Comment on lines 8 to 9
//Test_getInterfaceIPByFlags is here to facilitate testing of the private function getInterfaceIPByFlags
func Test_getInterfaceIPByFlags(t *testing.T) {
Copy link

Choose a reason for hiding this comment

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

my preference (and I may be wrong here) is to test the interface. IOW, let's go thru the exported/ "public" funcs and test all the test cases below? What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

I totally agree with you, I would like to test reliably the outer layer too but we cannot control what's returned by GetAllInterfaces (called by GetInterfaceIP and GetInterfaceIPWithoutInterfaceFlags) and consequently, we can only test around the test runner host's interfaces. Unless I am missing something 🤔

I went this way in order to have a quick and dirty way to stub IfAddrs for the various use cases I tested.
Also, I wanted to have a discussion with you guys about the correct way to go about this 😊

@acpana
Copy link

acpana commented Nov 15, 2021

This overall LGTM! Thanks for the contribution here!

--

Left a few nits, in the meantime I will ping another team mate for an extra pair of 👀

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
@deblasis
Copy link
Author

Hi @FFMMM ! You are very welcome.

Awesome! In the meantime, I fixed the easy ones and now I am looking forward to some guidance regarding how to properly test this, I left a comment with my 2cents.

this commit introduces a way of controlling what's returned by `sockaddr.GetAllInterfaces()` in tests.
Useful in tests that can now stub real world setups.

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
@deblasis
Copy link
Author

Hi there!

I have learnt something while working on another project where I found myself in a similar situation and I thought about this PR.
I took the liberty of committing what I believe could be a way of testing this change in a "black-box" way, as a bonus, we could now test extensively GetInterfaceIP because we can control what is returned by sockaddr.GetAllInterfaces in tests.

I have also removed the private func tests with a separate commit cb0bec0 so it would be easy to revert in case you'd like to keep it.

ifaddr_test.go Outdated
Comment on lines 297 to 298
realGetAllInterfaces := sockaddr.GetAllInterfaces
defer func() { sockaddr.GetAllInterfaces = realGetAllInterfaces }()
Copy link

Choose a reason for hiding this comment

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

Hm, I think there's nothing wrong with mocking the GetAllInterfaces func here but curious if we could mock a something "external" to the system.

Let me comment below what I mean.

// available IP addresses on each interface and converts them to
// sockaddr.IPAddrs, and returning the result as an array of IfAddr.
func GetAllInterfaces() (IfAddrs, error) {
func getAllInterfaces() (IfAddrs, error) {
ifs, err := net.Interfaces()
Copy link

Choose a reason for hiding this comment

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

to aid the set up of the "fake environment", we were curious if we could try to do some dependency injection type thing here and mock out subsequent calls to net.Interfaces().

Does that make sense? Happy to chat more as needed

Copy link
Author

Choose a reason for hiding this comment

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

Hi @FFMMM, yes it makes sense, I hope I fully understood what you meant, I would have asked for clarification but given the holidays I gave it a shot by introducing NetworkInterfacesProvider (naming is hard), its default implementation called OSNetProvider (that I thought made sense to move into a separate file but I may be wrong) and I also attempted in improving the way the "fake environment" in tests is configured.

One thing I don't particularly like is the fact that sockaddr.NetProvider is public, I would fix that by using a separate file with a public accessor and a build flag that would make it available only when testing.

If I missed the point, please clarify how you would like to inject the dependency that takes care of enumerating the net interfaces and I will adjust accordingly.

@acpana
Copy link

acpana commented Dec 13, 2021

hey @deblasis thanks for your patience -- I had some folks on the team look over the PR and it definitely looks good!

We had one small call out regarding mocking calls that are external to the system! (See inline comments)

@deblasis
Copy link
Author

Hi @FFMMM! Sorry for not getting back to you earlier but I found myself very busy with a project I am working on.

I already wrote some code in my spare time, I think I should be able to push an update on Sunday or next week at the latest.
It would make use of an interface that abstracts the calls to net.Interfaces() and also perhaps provide better semantics in terms of configurability in tests.

In the meantime, I wish you happy holidays!! 🎅

default implementation (OSNetProvider) and overridability in tests

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
// available IP addresses on each interface and converts them to
// sockaddr.IPAddrs, and returning the result as an array of IfAddr.
func (n *OSNetProvider) GetAllInterfaces() (IfAddrs, error) {
ifs, err := net.Interfaces()
Copy link

Choose a reason for hiding this comment

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

hey @deblasis thanks for following up on the team's latest -- we really appreciate your efforts here and receptiveness to feedback.

I may not have been super clear in a previous comment but the line we want to stub out is this one!

We probably want all of the code here to be executed during the tests, except for this line.

This line should look something like

ifs, err := netProvider.Interfaces()

and in the testing we can set up a FakeNetProvider. What do you think?

Copy link

@kisunji kisunji Jan 18, 2022

Choose a reason for hiding this comment

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

Actually, I'm not sure if we can easily inject a netProvider-like dependency here since the exported functions are func(string) (string, error).

It will probably be easiest to declare a global function variable

packages sockaddr

var netInterfaces = net.Interfaces

and stub out global variable sockaddr.netInterfaces in tests

Copy link
Author

Choose a reason for hiding this comment

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

Hi there, sorry for the delayed reply, I have been very busy freelancing.
I had a quick look and I believe we are oversimplifying. I'll try to explain what I mean:

We can sure stub out net.Interfaces but that means that we are stubbing out the hardware information about our interfaces, what about the software part?
We are forgetting the calls to intf.Addrs() that look like they are returning network protocol and IP Addresses for the NICs by querying the RIB (Routing Information Base) which returns the address table.

https://github.com/hashicorp/go-sockaddr/pull/38/files#diff-9753b911177155deeb161b6f775d09bdbb6cb51fb315e9b6e9bc3130affd0b46R21

that internally call net.interfaceAddrTable
image

and net.addrTable
image

As far as I understand, in order to be able to stub out all the system calls that are involved in computing the return value of our sockaddr.GetAllInterfaces(), I think we should also look into stubbing these other system calls as well...

Please correct me if I am wrong and let me know how to proceed.

Copy link

Choose a reason for hiding this comment

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

That's a great point @deblasis, I agree that the entire GetAllInterfaces function should be mocked.

I think exporting new interfaces and implementation structs to mock it are overkill; could we perhaps simply do

var GetAllInterfaces func() (IfAddrs, error) = getAllInterfaces

func getAllInterfaces() (IfAddrs, error) {
        // implementation ...
}

// ifaddr_test.go
func TestGetInterfaceIP(t *testing.T) {

	orig := sockaddr.GetAllInterfaces
        sockaddr.GetAllInterfaces = mockFunc()
        t.Cleanup(func(){ sockaddr.GetAllInterfaces = orig })

        // tests ...
}

Copy link
Author

Choose a reason for hiding this comment

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

Hi @kisunji!

I apologize for the delay.
Sounds good, I have updated the PR accordingly.

ifaddrs.go Outdated Show resolved Hide resolved
@hashicorp-cla
Copy link

hashicorp-cla commented Mar 12, 2022

CLA assistant check
All committers have signed the CLA.

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
@deblasis deblasis requested a review from kisunji March 13, 2022 17:00
Copy link

@kisunji kisunji left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you for your efforts! Much appreciated 🙏

@acpana acpana removed their assignment Mar 22, 2022
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.

5 participants