E-MailRelay
ginterfaces_unix.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 <ifaddrs.h>
43#include <functional>
44#include <memory>
45#include <utility>
46#include <sstream>
47
48namespace GNet
49{
50 class InterfacesNotifierImp ;
51}
52
53//| \class GNet::InterfacesNotifierImp
54/// Handles read events on a routing netlink socket.
55///
56class GNet::InterfacesNotifierImp : public InterfacesNotifier
57{
58public:
59 static bool active() ;
60 InterfacesNotifierImp( Interfaces * , ExceptionSink es ) ;
61 template <typename T> std::pair<T*,std::size_t> readSocket() ;
62
63public: // overrides
64 std::string readEvent() override ; // unix
65 std::string onFutureEvent() override ; // windows
66
67public:
68 G::Buffer<char> m_buffer ;
69 std::unique_ptr<RawSocket> m_socket ;
70} ;
71
72// ==
73
75{
76 return InterfacesNotifierImp::active() ;
77}
78
79void GNet::Interfaces::loadImp( ExceptionSink es , std::vector<Item> & list )
80{
81 if( !m_notifier )
82 m_notifier = std::make_unique<InterfacesNotifierImp>( this , es ) ;
83
84 ifaddrs * info_p = nullptr ;
85 int rc = getifaddrs( &info_p ) ;
86 if( rc < 0 )
87 {
88 int e = G::Process::errno_() ;
89 throw G::Exception( "getifaddrs error" , G::Process::strerror(e) ) ;
90 }
91
92 using deleter_fn_t = std::function<void(ifaddrs*)> ;
93 using deleter_t = std::unique_ptr<ifaddrs,deleter_fn_t> ;
94 deleter_t deleter( info_p , deleter_fn_t(freeifaddrs) ) ;
95
96 const std::size_t nmax = AddressStorage().n() ;
97 const bool scope_id_fixup = G::is_bsd() ;
98 for( ; info_p != nullptr ; info_p = info_p->ifa_next )
99 {
100 G_ASSERT( info_p->ifa_name && info_p->ifa_name[0] ) ;
101 if( info_p->ifa_name == nullptr )
102 continue ;
103
104 if( info_p->ifa_addr == nullptr )
105 continue ;
106
107 if( !Address::supports(info_p->ifa_addr->sa_family,0) )
108 continue ;
109
110 Item item ;
111 item.name = std::string( info_p->ifa_name ) ;
112 item.ifindex = index( item.name ) ;
113 item.address_family = info_p->ifa_addr->sa_family ;
114 item.address = Address( info_p->ifa_addr , nmax , scope_id_fixup ) ;
115 item.valid_address = !item.address.isAny() ; // just in case
116 item.up = !!( info_p->ifa_flags & IFF_UP ) ;
117 item.loopback = !!( info_p->ifa_flags & IFF_LOOPBACK ) ;
118 item.has_netmask = info_p->ifa_netmask != nullptr ;
119
120 if( item.has_netmask )
121 {
122 if( info_p->ifa_netmask->sa_family == AF_UNSPEC ) // openbsd
123 info_p->ifa_netmask->sa_family = info_p->ifa_addr->sa_family ;
124
125 Address netmask( info_p->ifa_netmask , nmax ) ;
126 item.netmask_bits = netmask.bits() ;
127 }
128
129 list.push_back( item ) ;
130 }
131}
132
133#if GCONFIG_HAVE_IFINDEX
134#include <sys/ioctl.h>
135#include <net/if.h>
136int GNet::Interfaces::index( const std::string & name )
137{
138 struct ifreq req {} ;
139 G::Str::strncpy_s( req.ifr_name , sizeof(req.ifr_name) , name.c_str() , G::Str::truncate ) ;
140 int fd = socket( AF_INET , SOCK_DGRAM , 0 ) ; // man netdevice(7): "any socket.. regardless of.. family or type"
141 if( fd < 0 ) return 0 ;
142 int rc = ioctl( fd , SIOCGIFINDEX , &req , sizeof(req) ) ;
143 close( fd ) ;
144 return rc ? 0 : req.ifr_ifindex ;
145}
146#else
147int GNet::Interfaces::index( const std::string & )
148{
149 return 0 ;
150}
151#endif
152
153// ==
154
155template <typename T>
156std::pair<T*,std::size_t> GNet::InterfacesNotifierImp::readSocket()
157{
158 static_assert( sizeof(T) <= 4096U , "" ) ;
159 m_buffer.resize( 4096U ) ;
160
161 ssize_t rc = m_socket->read( &m_buffer[0] , m_buffer.size() ) ;
162 if( rc < 0 )
163 {
164 GDEF_UNUSED int e = G::Process::errno_() ;
165 G_DEBUG( "GNet::InterfacesNotifierImp: read error: " << G::Process::strerror(e) ) ;
166 }
167
168 T * p = G::buffer_cast<T*>( m_buffer , std::nothrow ) ;
169 std::size_t n = static_cast<std::size_t>( rc >= 0 && p ? rc : 0 ) ;
170 return { p , n } ;
171}
172
174{
175 return std::string() ;
176}
177
178#if GCONFIG_HAVE_RTNETLINK
179
180#include <asm/types.h>
181#include <sys/socket.h>
182#include <linux/netlink.h>
183#include <linux/rtnetlink.h>
184
185bool GNet::InterfacesNotifierImp::active()
186{
187 return true ;
188}
189
190GNet::InterfacesNotifierImp::InterfacesNotifierImp( Interfaces * outer , ExceptionSink es )
191{
192 if( EventLoop::exists() )
193 {
194 union netlink_address_union
195 {
196 struct sockaddr_nl specific ;
197 struct sockaddr generic ;
198 } ;
199 netlink_address_union address {} ;
200 address.specific.nl_family = AF_NETLINK ;
201 #if GCONFIG_HAVE_IPV6
202 address.specific.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR ;
203 #else
204 address.specific.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR ;
205 #endif
206 {
207 G::Root claim_root ;
208 m_socket = std::make_unique<RawSocket>( AF_NETLINK , SOCK_RAW , NETLINK_ROUTE ) ;
209 int rc = ::bind( m_socket->fd() , &address.generic , sizeof(address.specific) ) ;
210 int e = G::Process::errno_() ;
211 if( rc < 0 )
212 throw G::Exception( "netlink socket bind error" , G::Process::strerror(e) ) ;
213 }
214 m_socket->addReadHandler( *outer , es ) ;
215 }
216}
217
219{
220 auto buffer_pair = readSocket<nlmsghdr>() ;
221
222 const nlmsghdr * hdr = buffer_pair.first ;
223 std::size_t size = buffer_pair.second ;
224 if( hdr == nullptr || size == 0U )
225 return std::string() ;
226
227 const char * sep = "" ;
228 std::ostringstream ss ;
229 for( ; NLMSG_OK(hdr,size) ; hdr = NLMSG_NEXT(hdr,size) , sep = ", " )
230 {
231 if( hdr->nlmsg_type == NLMSG_DONE || hdr->nlmsg_type == NLMSG_ERROR )
232 break ;
233
234 if( hdr->nlmsg_type == RTM_NEWLINK ||
235 hdr->nlmsg_type == RTM_DELLINK ||
236 hdr->nlmsg_type == RTM_GETLINK )
237 {
238 GDEF_UNUSED ifinfomsg * p = static_cast<ifinfomsg*>( NLMSG_DATA(hdr) ) ;
239 GDEF_UNUSED int n = NLMSG_PAYLOAD( hdr , size ) ;
240 ss << sep << "link" ;
241 if( hdr->nlmsg_type == RTM_NEWLINK ) ss << " new" ;
242 if( hdr->nlmsg_type == RTM_DELLINK ) ss << " deleted" ;
243 }
244 else if( hdr->nlmsg_type == RTM_NEWADDR ||
245 hdr->nlmsg_type == RTM_DELADDR ||
246 hdr->nlmsg_type == RTM_GETADDR )
247 {
248 GDEF_UNUSED ifaddrmsg * p = static_cast<ifaddrmsg*>( NLMSG_DATA(hdr) ) ;
249 GDEF_UNUSED int n = NLMSG_PAYLOAD( hdr , size ) ;
250 ss << sep << "address" ;
251 if( hdr->nlmsg_type == RTM_NEWADDR ) ss << " new" ;
252 if( hdr->nlmsg_type == RTM_DELADDR ) ss << " deleted" ;
253 }
254 }
255 return ss.str() ;
256}
257
258#else
259
260#if GCONFIG_HAVE_NETROUTE
261
262// see route(4)
263#include <net/route.h>
264
265bool GNet::InterfacesNotifierImp::active()
266{
267 return true ;
268}
269
270GNet::InterfacesNotifierImp::InterfacesNotifierImp( Interfaces * outer , ExceptionSink es )
271{
272 if( EventLoop::exists() )
273 {
274 {
275 G::Root claim_root ;
276 m_socket = std::make_unique<RawSocket>( PF_ROUTE , SOCK_RAW , AF_UNSPEC ) ;
277 }
278 m_socket->addReadHandler( *outer , es ) ;
279 }
280}
281
283{
284 using Header = struct rt_msghdr ;
285 std::string result ;
286 auto buffer_pair = readSocket<Header>() ;
287 if( buffer_pair.second >= 4U )
288 {
289 Header * p = buffer_pair.first ;
290 if( p->rtm_msglen != buffer_pair.second )
291 G_DEBUG( "GNet::InterfacesNotifierImp::readEvent: invalid message length" ) ;
292 if( p->rtm_type == RTM_NEWADDR )
293 result = "address new" ;
294 else if( p->rtm_type == RTM_DELADDR )
295 result = "address deleted" ;
296 else if( p->rtm_type == RTM_IFINFO )
297 result = "interface change" ;
298 }
299 return result ;
300}
301
302#else
303
304bool GNet::InterfacesNotifierImp::active()
305{
306 return false ;
307}
308
309GNet::InterfacesNotifierImp::InterfacesNotifierImp( Interfaces * , ExceptionSink )
310{
311}
312
314{
315 return std::string() ;
316}
317
318#endif
319#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:582
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:1493
Network classes.
Definition: gdef.h:1144
A substitute for std::vector<char> that has more useful alignment guarantees and explicitly avoids de...
Definition: gbuffer.h:63