1 #include "mock_syscall.hpp"
2 
3 #include "util.hpp"
4 
5 #include <arpa/inet.h>
6 #include <dlfcn.h>
7 #include <linux/netlink.h>
8 #include <linux/rtnetlink.h>
9 #include <net/ethernet.h>
10 #include <net/if.h>
11 #include <netinet/in.h>
12 #include <sys/ioctl.h>
13 #include <sys/socket.h>
14 #include <sys/types.h>
15 #include <unistd.h>
16 
17 #include <stdplus/raw.hpp>
18 
19 #include <cstdarg>
20 #include <cstdio>
21 #include <cstring>
22 #include <map>
23 #include <queue>
24 #include <stdexcept>
25 #include <string>
26 #include <string_view>
27 #include <vector>
28 
29 std::map<int, std::queue<std::string>> mock_rtnetlinks;
30 
31 using phosphor::network::InterfaceInfo;
32 
33 std::map<std::string, InterfaceInfo> mock_if;
34 
35 void phosphor::network::system::mock_clear()
36 {
37     mock_rtnetlinks.clear();
38     mock_if.clear();
39 }
40 
41 void phosphor::network::system::mock_addIF(const InterfaceInfo& info)
42 {
43     if (info.idx == 0)
44     {
45         throw std::invalid_argument("Bad interface index");
46     }
47     for (const auto& [_, iinfo] : mock_if)
48     {
49         if (iinfo.idx == info.idx || iinfo.name == info.name)
50         {
51             throw std::invalid_argument("Interface already exists");
52         }
53     }
54     mock_if.emplace(info.name.value(), info);
55 }
56 
57 void validateMsgHdr(const struct msghdr* msg)
58 {
59     if (msg->msg_namelen != sizeof(sockaddr_nl))
60     {
61         fprintf(stderr, "bad namelen: %u\n", msg->msg_namelen);
62         abort();
63     }
64     const auto& from = *reinterpret_cast<sockaddr_nl*>(msg->msg_name);
65     if (from.nl_family != AF_NETLINK)
66     {
67         fprintf(stderr, "recvmsg bad family data\n");
68         abort();
69     }
70     if (msg->msg_iovlen != 1)
71     {
72         fprintf(stderr, "recvmsg unsupported iov configuration\n");
73         abort();
74     }
75 }
76 
77 void appendRTAttr(std::string& msgBuf, unsigned short type,
78                   std::string_view data)
79 {
80     const auto rta_begin = msgBuf.size();
81     msgBuf.append(RTA_SPACE(data.size()), '\0');
82     auto& rta = *reinterpret_cast<rtattr*>(msgBuf.data() + rta_begin);
83     rta.rta_len = RTA_LENGTH(data.size());
84     rta.rta_type = type;
85     std::copy(data.begin(), data.end(),
86               msgBuf.data() + rta_begin + RTA_LENGTH(0));
87 }
88 
89 ssize_t sendmsg_link_dump(std::queue<std::string>& msgs, std::string_view in)
90 {
91     if (const auto& hdrin = *reinterpret_cast<const nlmsghdr*>(in.data());
92         hdrin.nlmsg_type != RTM_GETLINK)
93     {
94         return 0;
95     }
96 
97     std::string msgBuf;
98     msgBuf.reserve(8192);
99     for (const auto& [name, i] : mock_if)
100     {
101         if (msgBuf.size() > 4096)
102         {
103             msgs.emplace(std::move(msgBuf));
104         }
105         const auto nlbegin = msgBuf.size();
106         msgBuf.append(NLMSG_SPACE(sizeof(ifinfomsg)), '\0');
107         {
108             auto& info = *reinterpret_cast<ifinfomsg*>(msgBuf.data() + nlbegin +
109                                                        NLMSG_HDRLEN);
110             info.ifi_index = i.idx;
111             info.ifi_flags = i.flags;
112         }
113         if (i.name)
114         {
115             appendRTAttr(msgBuf, IFLA_IFNAME, {name.data(), name.size() + 1});
116         }
117         if (i.mac)
118         {
119             appendRTAttr(msgBuf, IFLA_ADDRESS,
120                          stdplus::raw::asView<char>(*i.mac));
121         }
122         if (i.mtu)
123         {
124             appendRTAttr(msgBuf, IFLA_MTU, stdplus::raw::asView<char>(*i.mtu));
125         }
126         auto& hdr = *reinterpret_cast<nlmsghdr*>(msgBuf.data() + nlbegin);
127         hdr.nlmsg_len = msgBuf.size() - nlbegin;
128         hdr.nlmsg_type = RTM_NEWLINK;
129         hdr.nlmsg_flags = NLM_F_MULTI;
130         msgBuf.resize(NLMSG_ALIGN(msgBuf.size()), '\0');
131     }
132     const auto nlbegin = msgBuf.size();
133     msgBuf.append(NLMSG_SPACE(0), '\0');
134     auto& hdr = *reinterpret_cast<nlmsghdr*>(msgBuf.data() + nlbegin);
135     hdr.nlmsg_len = NLMSG_LENGTH(0);
136     hdr.nlmsg_type = NLMSG_DONE;
137     hdr.nlmsg_flags = NLM_F_MULTI;
138 
139     msgs.emplace(std::move(msgBuf));
140     return in.size();
141 }
142 
143 ssize_t sendmsg_ack(std::queue<std::string>& msgs, std::string_view in)
144 {
145     nlmsgerr ack{};
146     nlmsghdr hdr{};
147     hdr.nlmsg_len = NLMSG_LENGTH(sizeof(ack));
148     hdr.nlmsg_type = NLMSG_ERROR;
149     auto& out = msgs.emplace(hdr.nlmsg_len, '\0');
150     memcpy(out.data(), &hdr, sizeof(hdr));
151     memcpy(NLMSG_DATA(out.data()), &ack, sizeof(ack));
152     return in.size();
153 }
154 
155 extern "C"
156 {
157 int ioctl(int fd, unsigned long int request, ...)
158 {
159     va_list vl;
160     va_start(vl, request);
161     void* data = va_arg(vl, void*);
162     va_end(vl);
163 
164     auto req = reinterpret_cast<ifreq*>(data);
165     if (request == SIOCGIFFLAGS)
166     {
167         auto it = mock_if.find(req->ifr_name);
168         if (it == mock_if.end())
169         {
170             errno = ENXIO;
171             return -1;
172         }
173         req->ifr_flags = it->second.flags;
174         return 0;
175     }
176     else if (request == SIOCGIFMTU)
177     {
178         auto it = mock_if.find(req->ifr_name);
179         if (it == mock_if.end())
180         {
181             errno = ENXIO;
182             return -1;
183         }
184         if (!it->second.mtu)
185         {
186             errno = EOPNOTSUPP;
187             return -1;
188         }
189         req->ifr_mtu = *it->second.mtu;
190         return 0;
191     }
192 
193     static auto real_ioctl =
194         reinterpret_cast<decltype(&ioctl)>(dlsym(RTLD_NEXT, "ioctl"));
195     return real_ioctl(fd, request, data);
196 }
197 
198 int socket(int domain, int type, int protocol)
199 {
200     static auto real_socket =
201         reinterpret_cast<decltype(&socket)>(dlsym(RTLD_NEXT, "socket"));
202     int fd = real_socket(domain, type, protocol);
203     if (domain == AF_NETLINK && !(type & SOCK_RAW))
204     {
205         fprintf(stderr, "Netlink sockets must be RAW\n");
206         abort();
207     }
208     if (domain == AF_NETLINK && protocol == NETLINK_ROUTE)
209     {
210         mock_rtnetlinks[fd] = {};
211     }
212     return fd;
213 }
214 
215 int close(int fd)
216 {
217     auto it = mock_rtnetlinks.find(fd);
218     if (it != mock_rtnetlinks.end())
219     {
220         mock_rtnetlinks.erase(it);
221     }
222 
223     static auto real_close =
224         reinterpret_cast<decltype(&close)>(dlsym(RTLD_NEXT, "close"));
225     return real_close(fd);
226 }
227 
228 ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags)
229 {
230     auto it = mock_rtnetlinks.find(sockfd);
231     if (it == mock_rtnetlinks.end())
232     {
233         static auto real_sendmsg =
234             reinterpret_cast<decltype(&sendmsg)>(dlsym(RTLD_NEXT, "sendmsg"));
235         return real_sendmsg(sockfd, msg, flags);
236     }
237     auto& msgs = it->second;
238 
239     validateMsgHdr(msg);
240     if (!msgs.empty())
241     {
242         fprintf(stderr, "Unread netlink responses\n");
243         abort();
244     }
245 
246     ssize_t ret;
247     std::string_view iov(reinterpret_cast<char*>(msg->msg_iov[0].iov_base),
248                          msg->msg_iov[0].iov_len);
249 
250     ret = sendmsg_link_dump(msgs, iov);
251     if (ret != 0)
252     {
253         return ret;
254     }
255 
256     ret = sendmsg_ack(msgs, iov);
257     if (ret != 0)
258     {
259         return ret;
260     }
261 
262     errno = ENOSYS;
263     return -1;
264 }
265 
266 ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags)
267 {
268     auto it = mock_rtnetlinks.find(sockfd);
269     if (it == mock_rtnetlinks.end())
270     {
271         static auto real_recvmsg =
272             reinterpret_cast<decltype(&recvmsg)>(dlsym(RTLD_NEXT, "recvmsg"));
273         return real_recvmsg(sockfd, msg, flags);
274     }
275     auto& msgs = it->second;
276 
277     validateMsgHdr(msg);
278     constexpr size_t required_buf_size = 8192;
279     if (msg->msg_iov[0].iov_len < required_buf_size)
280     {
281         fprintf(stderr, "recvmsg iov too short: %zu\n",
282                 msg->msg_iov[0].iov_len);
283         abort();
284     }
285     if (msgs.empty())
286     {
287         fprintf(stderr, "No pending netlink responses\n");
288         abort();
289     }
290 
291     ssize_t ret = 0;
292     auto data = reinterpret_cast<char*>(msg->msg_iov[0].iov_base);
293     while (!msgs.empty())
294     {
295         const auto& msg = msgs.front();
296         if (NLMSG_ALIGN(ret) + msg.size() > required_buf_size)
297         {
298             break;
299         }
300         ret = NLMSG_ALIGN(ret);
301         memcpy(data + ret, msg.data(), msg.size());
302         ret += msg.size();
303         msgs.pop();
304     }
305     return ret;
306 }
307 
308 } // extern "C"
309