E-MailRelay
ginterfaces_unix.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file ginterfaces_unix.cpp
19///
20// Linux:
21// ip address add 127.0.0.2/8 dev lo
22// ip address del 127.0.0.2/8 dev lo
23// ip address add fe80::dead:beef/64 dev eth0
24// ip address del fe80::dead:beef/64 dev eth0
25//
26// BSD:
27// ifconfig lo0 inet 127.0.0.2 alias netmask 255.0.0.0
28// ifconfig lo0 inet 127.0.0.2 -alias
29// ifconfig em0 inet6 fe80::dead:beef/64 alias
30// ifconfig em0 inet6 fe80::dead:beef/64 -alias
31//
32
33#include "gdef.h"
34#include "ginterfaces.h"
35#include "gassert.h"
36#include "gexception.h"
37#include "geventloop.h"
38#include "gprocess.h"
39#include "gbuffer.h"
40#include "gstr.h"
41#include "groot.h"
42#include "glog.h"
43#include <ifaddrs.h>
44#include <functional>
45#include <memory>
46#include <utility>
47#include <sstream>
48
49namespace GNet
50{
51 class InterfacesNotifierImp ;
52}
53
54//| \class GNet::InterfacesNotifierImp
55/// Handles read events on a routing netlink socket.
56///
57class GNet::InterfacesNotifierImp : public InterfacesNotifier
58{
59public:
60 static bool active() ;
61 InterfacesNotifierImp( Interfaces * , EventState es ) ;
62 template <typename T> std::pair<T*,std::size_t> readSocket() ;
63
64public: // overrides
65 std::string readEvent() override ; // unix
66 std::string onFutureEvent() override ; // windows
67
68public:
69 G::Buffer<char> m_buffer ;
70 std::unique_ptr<RawSocket> m_socket ;
71} ;
72
73// ==
74
76{
77 return InterfacesNotifierImp::active() ;
78}
79
80void GNet::Interfaces::loadImp( EventState es , std::vector<Item> & list )
81{
82 if( !m_notifier )
83 m_notifier = std::make_unique<InterfacesNotifierImp>( this , es ) ;
84
85 ifaddrs * info_p = nullptr ;
86 int rc = getifaddrs( &info_p ) ;
87 if( rc < 0 )
88 {
89 int e = G::Process::errno_() ;
90 throw G::Exception( "getifaddrs error" , G::Process::strerror(e) ) ;
91 }
92
93 using deleter_fn_t = std::function<void(ifaddrs*)> ;
94 using deleter_t = std::unique_ptr<ifaddrs,deleter_fn_t> ;
95 deleter_t deleter( info_p , deleter_fn_t(freeifaddrs) ) ;
96
97 const std::size_t nmax = AddressStorage().n() ;
98 const bool scope_id_fixup = G::is_bsd() ;
99 for( ; info_p != nullptr ; info_p = info_p->ifa_next )
100 {
101 G_ASSERT( info_p->ifa_name && info_p->ifa_name[0] ) ;
102 if( info_p->ifa_name == nullptr )
103 continue ;
104
105 if( info_p->ifa_addr == nullptr )
106 continue ;
107
108 if( !Address::supports(info_p->ifa_addr->sa_family,0) )
109 continue ;
110
111 Item item ;
112 item.name = std::string( info_p->ifa_name ) ;
113 item.ifindex = index( item.name ) ;
114 item.address_family = info_p->ifa_addr->sa_family ;
115 item.address = Address( info_p->ifa_addr , nmax , scope_id_fixup ) ;
116 item.valid_address = !item.address.isAny() ; // just in case
117 item.up = !!( info_p->ifa_flags & IFF_UP ) ;
118 item.loopback = !!( info_p->ifa_flags & IFF_LOOPBACK ) ;
119 item.has_netmask = info_p->ifa_netmask != nullptr ;
120
121 if( item.has_netmask )
122 {
123 if( info_p->ifa_netmask->sa_family == AF_UNSPEC ) // openbsd
124 info_p->ifa_netmask->sa_family = info_p->ifa_addr->sa_family ;
125
126 Address netmask( info_p->ifa_netmask , nmax ) ;
127 item.netmask_bits = netmask.bits() ;
128 }
129
130 list.push_back( item ) ;
131 }
132}
133
134#if GCONFIG_HAVE_IFINDEX
135#include <sys/ioctl.h>
136#include <net/if.h>
137int GNet::Interfaces::index( const std::string & name )
138{
139 struct ifreq req {} ;
140 G::Str::strncpy_s( req.ifr_name , sizeof(req.ifr_name) , name.c_str() , G::Str::truncate ) ;
141 int fd = socket( AF_INET , SOCK_DGRAM , 0 ) ; // man netdevice(7): "any socket.. regardless of.. family or type"
142 if( fd < 0 ) return 0 ;
143 int rc = ioctl( fd , SIOCGIFINDEX , &req , sizeof(req) ) ;
144 close( fd ) ;
145 return rc ? 0 : req.ifr_ifindex ;
146}
147#else
148int GNet::Interfaces::index( const std::string & )
149{
150 return 0 ;
151}
152#endif
153
154// ==
155
156template <typename T>
157std::pair<T*,std::size_t> GNet::InterfacesNotifierImp::readSocket()
158{
159 static_assert( sizeof(T) <= 4096U , "" ) ;
160 m_buffer.resize( 4096U ) ;
161
162 ssize_t rc = m_socket->read( m_buffer.data() , m_buffer.size() ) ;
163 if( rc < 0 )
164 {
165 GDEF_UNUSED int e = G::Process::errno_() ;
166 G_DEBUG( "GNet::InterfacesNotifierImp: read error: " << G::Process::strerror(e) ) ;
167 }
168
169 T * p = G::buffer_cast<T*>( m_buffer , std::nothrow ) ;
170 std::size_t n = static_cast<std::size_t>( rc >= 0 && p ? rc : 0 ) ;
171 return { p , n } ;
172}
173
175{
176 return {} ;
177}
178
179#if GCONFIG_HAVE_RTNETLINK
180
181#include <asm/types.h>
182#include <sys/socket.h>
183#include <linux/netlink.h>
184#include <linux/rtnetlink.h>
185
186bool GNet::InterfacesNotifierImp::active()
187{
188 return true ;
189}
190
191GNet::InterfacesNotifierImp::InterfacesNotifierImp( Interfaces * outer , EventState es )
192{
193 if( EventLoop::exists() )
194 {
195 union netlink_address_union
196 {
197 struct sockaddr_nl specific ;
198 struct sockaddr generic ;
199 } ;
200 netlink_address_union address {} ;
201 address.specific.nl_family = AF_NETLINK ;
202 #if GCONFIG_HAVE_IPV6
203 address.specific.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR ;
204 #else
205 address.specific.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR ;
206 #endif
207 {
208 G::Root claim_root ;
209 m_socket = std::make_unique<RawSocket>( AF_NETLINK , SOCK_RAW , NETLINK_ROUTE ) ;
210 int rc = ::bind( m_socket->fd() , &address.generic , sizeof(address.specific) ) ;
211 int e = G::Process::errno_() ;
212 if( rc < 0 )
213 throw G::Exception( "netlink socket bind error" , G::Process::strerror(e) ) ;
214 }
215 m_socket->addReadHandler( *outer , es ) ;
216 }
217}
218
220{
221 auto buffer_pair = readSocket<nlmsghdr>() ;
222
223 const nlmsghdr * hdr = buffer_pair.first ;
224 std::size_t size = buffer_pair.second ;
225 if( hdr == nullptr || size == 0U )
226 return {} ;
227
228 const char * sep = "" ;
229 std::ostringstream ss ;
230 for( ; NLMSG_OK(hdr,size) ; hdr = NLMSG_NEXT(hdr,size) , sep = ", " ) // NOLINT
231 {
232 if( hdr->nlmsg_type == NLMSG_DONE || hdr->nlmsg_type == NLMSG_ERROR )
233 break ;
234
235 if( hdr->nlmsg_type == RTM_NEWLINK ||
236 hdr->nlmsg_type == RTM_DELLINK ||
237 hdr->nlmsg_type == RTM_GETLINK )
238 {
239 GDEF_UNUSED ifinfomsg * p = static_cast<ifinfomsg*>( NLMSG_DATA(hdr) ) ; // NOLINT
240 GDEF_UNUSED int n = NLMSG_PAYLOAD( hdr , size ) ;
241 ss << sep << "link" ;
242 if( hdr->nlmsg_type == RTM_NEWLINK ) ss << " new" ;
243 if( hdr->nlmsg_type == RTM_DELLINK ) ss << " deleted" ;
244 }
245 else if( hdr->nlmsg_type == RTM_NEWADDR ||
246 hdr->nlmsg_type == RTM_DELADDR ||
247 hdr->nlmsg_type == RTM_GETADDR )
248 {
249 GDEF_UNUSED ifaddrmsg * p = static_cast<ifaddrmsg*>( NLMSG_DATA(hdr) ) ; // NOLINT
250 GDEF_UNUSED int n = NLMSG_PAYLOAD( hdr , size ) ;
251 ss << sep << "address" ;
252 if( hdr->nlmsg_type == RTM_NEWADDR ) ss << " new" ;
253 if( hdr->nlmsg_type == RTM_DELADDR ) ss << " deleted" ;
254 }
255 }
256 return ss.str() ;
257}
258
259#else
260
261#if GCONFIG_HAVE_NETROUTE
262
263// see route(4)
264#include <net/route.h>
265
266bool GNet::InterfacesNotifierImp::active()
267{
268 return true ;
269}
270
271GNet::InterfacesNotifierImp::InterfacesNotifierImp( Interfaces * outer , EventState es )
272{
273 if( EventLoop::exists() )
274 {
275 {
276 G::Root claim_root ;
277 m_socket = std::make_unique<RawSocket>( PF_ROUTE , SOCK_RAW , AF_UNSPEC ) ;
278 }
279 m_socket->addReadHandler( *outer , es ) ;
280 }
281}
282
284{
285 using Header = struct rt_msghdr ;
286 std::string result ;
287 auto buffer_pair = readSocket<Header>() ;
288 if( buffer_pair.second >= 4U )
289 {
290 Header * p = buffer_pair.first ;
291 if( p->rtm_msglen != buffer_pair.second )
292 G_DEBUG( "GNet::InterfacesNotifierImp::readEvent: invalid message length" ) ;
293 if( p->rtm_type == RTM_NEWADDR )
294 result = "address new" ;
295 else if( p->rtm_type == RTM_DELADDR )
296 result = "address deleted" ;
297 else if( p->rtm_type == RTM_IFINFO )
298 result = "interface change" ;
299 }
300 return result ;
301}
302
303#else
304
305bool GNet::InterfacesNotifierImp::active()
306{
307 return false ;
308}
309
310GNet::InterfacesNotifierImp::InterfacesNotifierImp( Interfaces * , EventState )
311{
312}
313
315{
316 return {} ;
317}
318
319#endif
320#endif
static bool supports(Family) noexcept
Returns true if the implementation supports the given address family.
Definition: gaddress.cpp:33
static bool exists()
Returns true if an instance exists.
Definition: geventloop.cpp:52
virtual std::string readEvent()=0
Called by GNet::Interfaces to handle a read event.
virtual std::string onFutureEvent()=0
Called by GNet::Interfaces to handle a future event.
static bool active()
Returns true if the implementation can raise InterfacesHandler events.
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:64
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:52
static constexpr std::size_t truncate
A special value for the G::Str::strncpy_s() 'count' parameter.
Definition: gstr.h:578
static errno_t strncpy_s(char *dst, std::size_t n_dst, const char *src, std::size_t count) noexcept
Does the same as windows strncpy_s().
Definition: gstr.cpp:1490
Network classes.
Definition: gdef.h:1243
A substitute for std::vector<char> that has more useful alignment guarantees and explicitly avoids de...
Definition: gbuffer.h:63