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

RawSocketDevice cannot bind NIC on the centos7.9 #1640

Open
vectorzjl opened this issue Nov 13, 2024 · 14 comments
Open

RawSocketDevice cannot bind NIC on the centos7.9 #1640

vectorzjl opened this issue Nov 13, 2024 · 14 comments
Labels

Comments

@vectorzjl
Copy link

vectorzjl commented Nov 13, 2024

When I use RawSocketDevice to capture data, binding to the network card does not take effect, and it captures data from all available network cards. Perhaps I should use bind in the implementation to bind to the network card. Adding the following code should allow normal binding to the network card for data capture.

struct sockaddr_ll device;
memset(&device, 0, sizeof(device));
device.sll_family = AF_PACKET;
device.sll_protocol = htons(ETH_P_IP);
device.sll_ifindex = if_nametoindex(ifaceName.c_str());  // 替换为你的网络接口

// 绑定到网络接口
if (bind(fd, (struct sockaddr*)&device, sizeof(device)) < 0)
{
	PCPP_LOG_ERROR("Cannot bind raw socket to interface '" << ifaceName << "'");
	::close(fd);
	return false;
}
@vectorzjl vectorzjl changed the title RawSocketDevice cannot bind RawSocketDevice cannot bind NIC on the centos7.9 Nov 13, 2024
@tigercosmos
Copy link
Collaborator

Could you provide the minimum compilable sample code? Also please provide the NIC information if possible (ifconfig or ip address).

@vectorzjl
Copy link
Author

Could you provide the minimum compilable sample code? Also please provide the NIC information if possible (ifconfig or ip address).

ok , here is my sample code:


int main()
{
	const std::string ip_str = "192.168.199.1";
	pcpp::IPAddress ip_addr = pcpp::IPAddress(ip_str);
	auto rawSocket = std::make_shared<pcpp::RawSocketDevice>(ip_addr);

	if (!rawSocket->open())
	{
		return -1;
	}
	while (true)
	{
		pcpp::RawPacket packet;
		int ret = rawSocket->receivePacket(packet);
		if (pcpp::RawSocketDevice::RecvSuccess == ret)
		{
			pcpp::Packet parsePacket(&packet);

			auto* macLayer = parsePacket.getLayerOfType<pcpp::EthLayer>();
			if (macLayer != nullptr)
			{
				// print source and dest MAC addresses
				std::cout << "Source MAC address: " << macLayer->getSourceMac() << std::endl
				          << "Destination MAC address: " << macLayer->getDestMac() << std::endl;
			}
			auto* ipLayer = parsePacket.getLayerOfType<pcpp::IPv4Layer>();
			if (ipLayer != nullptr)
			{
				std::cout << "Source IP address: " << ipLayer->getSrcIPAddress() << std::endl
				          << "Destination IP address: " << ipLayer->getDstIPAddress() << std::endl
				          << "IP ID: 0x" << std::hex << pcpp::netToHost16(ipLayer->getIPv4Header()->ipId) << std::endl
				          << "TTL: " << std::dec << (int)ipLayer->getIPv4Header()->timeToLive << std::endl;
			}
			auto* tcpLayer = parsePacket.getLayerOfType<pcpp::TcpLayer>();
			if (tcpLayer != nullptr)
			{
				std::cout << "Source port: " << tcpLayer->getSrcPort() << std::endl
				          << "Destination port: " << tcpLayer->getDstPort() << std::endl;
			}
			std::cout << "============" << std::endl;
		}
		else if (pcpp::RawSocketDevice::RecvError == ret)
		{
			std::cout << "recvPacket failed" << std::endl;
			break;
		}
	}
	return 0;
}

here is my NIC information

[root@KL20070 share]# ip a
1: lo: <LOOPBACK,ALLMULTI,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 7e:eb:74:aa:39:e7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.xx.xx/24 brd 192.168.20.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::83ac:d9bf:2bda:3fbb/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
13: veth1@veth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether da:a7:32:5f:ac:e8 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.2/24 scope global veth1
       valid_lft forever preferred_lft forever
    inet6 fe80::d8a7:32ff:fe5f:ace8/64 scope link
       valid_lft forever preferred_lft forever
14: veth0@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 42:50:62:5f:0b:bb brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.1/24 scope global veth0
       valid_lft forever preferred_lft forever
    inet6 fe80::4050:62ff:fe5f:bbb/64 scope link
       valid_lft forever preferred_lft forever
15: veth3@veth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 96:e2:b7:c8:fc:fb brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.4/24 scope global veth3
       valid_lft forever preferred_lft forever
    inet6 fe80::94e2:b7ff:fec8:fcfb/64 scope link
       valid_lft forever preferred_lft forever
16: veth2@veth3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 32:92:2a:a3:0c:16 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.3/24 scope global veth2
       valid_lft forever preferred_lft forever
    inet6 fe80::3092:2aff:fea3:c16/64 scope link
       valid_lft forever preferred_lft forever

My veth1 network card has no network traffic, so under normal circumstances, it should not be able to capture any data. However, this code is still able to capture packets, and these packets should be coming from eth0. After I modified the RawSocketDevice.cpp code to include a bind operation, I was able to meet my requirement of capturing packets on the bound network card. Please check if there is an issue with the code. I am looking forward to your response.

@seladb
Copy link
Owner

seladb commented Nov 14, 2024

@vectorzjl I don't remember why we're using setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr) instead of bind() as you suggested. This is something I need to test and figure out...

@seladb
Copy link
Owner

seladb commented Nov 14, 2024

@vectorzjl I did some testing and your solution seems to be working!

Would you consider opening a PR with the fix?

@vectorzjl
Copy link
Author

I won't consider pushing PR, I hope you can fix it yourself

@tigercosmos
Copy link
Collaborator

@seladb Both setsockopt with setsockopt and bind() should work. There might be some reasons that we didn't notice.

@vectorzjl
Copy link
Author

vectorzjl commented Nov 18, 2024

setsockopt-man
@tigercosmos @seladb That's the problem
https://linux.die.net/man/7/socket

@seladb
Copy link
Owner

seladb commented Nov 20, 2024

Thanks for pulling out the relevant documentation @vectorzjl !

I opened a PR to make this change: #1642

But unfortunately CI fails on Linux as we get EWOULDBLOCK when trying to recv() from the socket:

if ((errorCode == EAGAIN) || (errorCode == EWOULDBLOCK))

A link to the failing CI: https://github.com/seladb/PcapPlusPlus/actions/runs/11851898136/job/33029159452?pr=1642

Any idea why we get EWOULDBLOCK when using raw sockets, but do see packets when capturing packets in other ways using the same NIC?

@vectorzjl
Copy link
Author

@seladb you can try enable promiscuous mode on a raw socket, which allows the network interface card (NIC) to receive all traffic passing through it, not just packets destined for the local machine, you need to follow different steps depending on your operating system.

Windows

On Windows, you can use the ioctlsocket function to set the promiscuous mode. Here are the general steps to enable promiscuous mode:

  1. Create a raw socket:

    SOCKET rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
  2. Bind the socket to a specific network interface (replace "your_ip_address" with the actual IP address):

    sockaddr_in addr;
    ZeroMemory(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("your_ip_address");
    bind(rawSocket, (SOCKADDR*)&addr, sizeof(addr));
  3. Use ioctlsocket to set promiscuous mode:

    DWORD mode = 1;
    ioctlsocket(rawSocket, SIO_RCVALL, &mode);

    Here, SIO_RCVALL is the option to control the reception of all packets by the socket, and mode is set to 1 to enable promiscuous mode.

Linux

On Linux, you can use the ioctl system call to set the network interface to promiscuous mode. Here are the general steps to enable promiscuous mode:

  1. Create a raw socket:

    int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  2. Get the index of the network interface and bind the socket (replace "your_interface_name" with the actual interface name):

    struct sockaddr_ll device;
    memset(&device, 0, sizeof(device));
    device.sll_family = AF_PACKET;
    device.sll_protocol = htons(ETH_P_ALL);
    device.sll_ifindex = if_nametoindex("your_interface_name");
    bind(sockfd, (struct sockaddr*)&device, sizeof(device));
  3. Use ioctl to set promiscuous mode:

    struct ifreq ifr;
    strncpy(ifr.ifr_name, "your_interface_name", IFNAMSIZ);
    ioctl(sockfd, SIOCGIFFLAGS, &ifr);
    ifr.ifr_flags |= IFF_PROMISC;
    ioctl(sockfd, SIOCSIFFLAGS, &ifr);

    Here, IFF_PROMISC flag is used to set the promiscuous mode.

By following these steps, you can enable promiscuous mode on raw sockets in both Windows and Linux operating systems. Note that these operations typically require administrative privileges.

@seladb
Copy link
Owner

seladb commented Nov 20, 2024

Thank @vectorzjl ! I didn't think about setting promiscuous mode for the socket 🤦‍♂️

I tried setting promiscuous mode, but it failed in GitHub Actions with errno == 1 (EPERM). Maybe it happens because the tests are running in a docker container? Maybe you have a suggestion how to solve it? 🤔

Failing CI: https://github.com/seladb/PcapPlusPlus/actions/runs/11930346556/job/33250959818
PR: #1642

@tigercosmos
Copy link
Collaborator

Interesting, PcapLiveDevice is set to promiscuous mode by default, but not for RawSocketDevice. Maybe we can add open(const DeviceConfiguration& config) for RawSocketDevice.

@vectorzjl
Copy link
Author

Thank @vectorzjl ! I didn't think about setting promiscuous mode for the socket 🤦‍♂️

I tried setting promiscuous mode, but it failed in GitHub Actions with errno == 1 (EPERM). Maybe it happens because the tests are running in a docker container? Maybe you have a suggestion how to solve it? 🤔

Failing CI: https://github.com/seladb/PcapPlusPlus/actions/runs/11930346556/job/33250959818 PR: #1642

When setting promiscuous mode in a Docker container, the container may need to be in privileged mode (--privileged) or have specific Linux capabilities, such as NET_ADMIN. Privileged mode grants the container additional permissions, allowing it to access all devices on the host, including network interfaces.

@tigercosmos
Copy link
Collaborator

It sounds weird, because we already use the promiscuous mode for PcapLiveDevice?

@seladb
Copy link
Owner

seladb commented Nov 21, 2024

@vectorzjl I tried this approach as well, and it didn't work. I even tried running it on GitHub Actions Runner VM (without docker) but it failed with the same error 😕
You can see both jobs in the PR: https://github.com/seladb/PcapPlusPlus/pull/1642/files

And as you can see here, both failed with the same error

Any idea how to approach this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants